步骤十一页面修改

This commit is contained in:
DXC
2026-06-29 11:11:32 +08:00
parent ef24398df9
commit c6c0b8fdf5

View File

@ -21,7 +21,7 @@ from PyQt5.QtWidgets import (
QWidget, QVBoxLayout, QGroupBox, QFormLayout, QHBoxLayout,
QLabel, QCheckBox, QPushButton, QLineEdit, QDoubleSpinBox,
QRadioButton, QButtonGroup, QMessageBox, QFileDialog, QComboBox,
QProgressBar,
QProgressBar, QSizePolicy,
)
from src.gui.components.custom_widgets import FileSelectWidget
@ -161,207 +161,191 @@ class Step11MapPanel(QWidget):
def init_ui(self):
layout = QVBoxLayout()
layout.setContentsMargins(24, 24, 24, 24)
hint = QLabel(
"独立运行:可选「单个 CSV」或「文件夹批量」扫描目录下所有 .csv"
"GeoTIFF 栅格模式下亦支持批量渲染步骤8输出的所有水色指数 GeoTIFF 文件。"
)
hint.setWordWrap(True)
hint.setStyleSheet(
f"color: {ModernStylesheet.COLORS.get('text_secondary', '#666')};"
)
layout.addWidget(hint)
# 【核心黑科技1】定义一个闭包函数彻底剥夺 FileSelectWidget 内部的各种默认间距,完美归一化
def apply_fs_style(fs_widget, placeholder=None):
# 1. 锁死标签宽度,保证右侧输入框起跑线 100% 垂直对齐
fs_widget.label.setMinimumWidth(120)
fs_widget.label.setMaximumWidth(120)
if placeholder:
fs_widget.line_edit.setPlaceholderText(placeholder)
fs_widget.browse_btn.setText("浏览...")
# 2. 剥夺组件内部的默认边距;锁定内部间距为 10px
fs_layout = fs_widget.layout()
fs_layout.setContentsMargins(0, 0, 0, 0)
fs_layout.setSpacing(10)
# 绝对不要再用 setStyleSheet完全信任你的全局 ModernStylesheet 绝美主题!
mode_row = QHBoxLayout()
self.mode_single_rb = QRadioButton("单个 CSV 文件")
self.mode_folder_rb = QRadioButton("文件夹批量")
self._mode_group = QButtonGroup(self)
self._mode_group.addButton(self.mode_single_rb, 0)
self._mode_group.addButton(self.mode_folder_rb, 1)
mode_row.addWidget(self.mode_single_rb)
mode_row.addWidget(self.mode_folder_rb)
mode_row.addStretch()
layout.addLayout(mode_row)
# 【核心黑科技2】把下拉框、数字框等也封装成同样规格的行
def create_standard_row(label_text, widget):
row = QHBoxLayout()
row.setContentsMargins(0, 0, 0, 0)
row.setSpacing(10) # 间距锁死 10px和上面的 FileSelectWidget 一模一样
label = QLabel(label_text)
label.setMinimumWidth(120)
label.setMaximumWidth(120) # 标签宽度锁死 120px
row.addWidget(label)
row.addWidget(widget)
return row
# ---------- 渲染模式选择器CSV vs GeoTIFF ----------
render_row = QHBoxLayout()
render_row.addWidget(QLabel("渲染模式:"))
# ==========================================
# 卡片 1输入数据与模式
# ==========================================
input_group = QGroupBox("📁 输入数据与模式")
input_layout = QVBoxLayout()
input_layout.setSpacing(16)
input_layout.setContentsMargins(20, 24, 20, 20)
# ---------- 渲染模式选择器 ----------
self.render_mode_combo = QComboBox()
self.render_mode_combo.addItems(["CSV 插值模式", "GeoTIFF 栅格模式"])
self.render_mode_combo.setMinimumWidth(180)
self.render_mode_combo.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
self.render_mode_combo.currentTextChanged.connect(self._toggle_input_mode)
render_row.addWidget(self.render_mode_combo)
render_row.addStretch()
layout.addLayout(render_row)
input_layout.addLayout(create_standard_row("渲染模式:", self.render_mode_combo))
# ---------- RadioButton 美化样式(选中状态为方形实心块,贴合主界面风格) ----------
radio_style = """
QRadioButton {
font-size: 14px;
spacing: 8px;
color: #333333;
}
QRadioButton::indicator {
width: 16px;
height: 16px;
border: 2px solid #999999;
border-radius: 3px;
background-color: white;
}
QRadioButton::indicator:checked {
border: 2px solid #0078d4;
background-color: #0078d4;
image: none;
}
QRadioButton::indicator:hover {
border: 2px solid #005a9e;
}
"""
self.mode_single_rb.setStyleSheet(radio_style)
self.mode_folder_rb.setStyleSheet(radio_style)
# ---------- 处理范围选择器 (回归你最爱的全局下拉框) ----------
self.batch_mode_combo = QComboBox()
self.batch_mode_combo.addItems(["单个文件", "文件夹批量"])
self.batch_mode_combo.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
self.batch_mode_combo.currentTextChanged.connect(self._toggle_input_mode)
input_layout.addLayout(create_standard_row("处理范围:", self.batch_mode_combo))
self.prediction_csv_file = FileSelectWidget(
"预测结果CSV:",
"CSV Files (*.csv);;All Files (*.*)"
)
layout.addWidget(self.prediction_csv_file)
# ---------- 统一组件预测结果CSV (单文件) ----------
self.prediction_csv_file = FileSelectWidget("预测结果 CSV:", "CSV Files (*.csv);;All Files (*.*)")
apply_fs_style(self.prediction_csv_file, "选择预测结果 CSV 文件…")
input_layout.addWidget(self.prediction_csv_file)
folder_row = QHBoxLayout()
self.prediction_csv_dir_label = QLabel("预测CSV目录:")
self.prediction_csv_dir_label.setMinimumWidth(120)
self.prediction_csv_dir_edit = QLineEdit()
self.prediction_csv_dir_edit.setPlaceholderText("选择含多个预测结果 CSV 的文件夹…")
pred_dir_btn = QPushButton("浏览…")
pred_dir_btn.setMaximumWidth(80)
pred_dir_btn.clicked.connect(self.browse_prediction_csv_dir)
folder_row.addWidget(self.prediction_csv_dir_label)
folder_row.addWidget(self.prediction_csv_dir_edit, 1)
folder_row.addWidget(pred_dir_btn)
self._folder_row_widget = QWidget()
self._folder_row_widget.setLayout(folder_row)
layout.addWidget(self._folder_row_widget)
# ---------- 统一组件预测CSV目录 (批量) ----------
self._folder_row_widget = FileSelectWidget("预测 CSV 目录:", "Directories")
apply_fs_style(self._folder_row_widget, "选择含多个预测结果 CSV 的文件夹…")
self._folder_row_widget.browse_btn.clicked.disconnect()
self._folder_row_widget.browse_btn.clicked.connect(self.browse_prediction_csv_dir)
self.prediction_csv_dir_edit = self._folder_row_widget.line_edit
input_layout.addWidget(self._folder_row_widget)
# ---------- GeoTIFF 栅格文件选择器 ----------
self.geotiff_file = FileSelectWidget(
"水色指数 GeoTIFF:",
"GeoTIFF Files (*.tif);;All Files (*.*)"
)
self.geotiff_file.line_edit.setPlaceholderText("选择步骤8输出的水色指数 GeoTIFF 文件…")
# ---------- 统一组件:水色指数 GeoTIFF (单文件) ----------
self.geotiff_file = FileSelectWidget("水色指数 GeoTIFF:", "GeoTIFF Files (*.tif);;All Files (*.*)")
apply_fs_style(self.geotiff_file, "选择步骤8输出的水色指数 GeoTIFF 文件…")
self.geotiff_file.setVisible(False)
layout.addWidget(self.geotiff_file)
input_layout.addWidget(self.geotiff_file)
# ---------- GeoTIFF 文件夹批量选择器GeoTIFF + 文件夹模式时显示) ----------
geotiff_dir_row = QHBoxLayout()
self.geotiff_dir_label = QLabel("水色指数目录:")
self.geotiff_dir_label.setMinimumWidth(120)
self.geotiff_dir_edit = QLineEdit()
self.geotiff_dir_edit.setPlaceholderText("选择 10_WaterIndex_Images 文件夹(批量渲染)…")
geotiff_dir_btn = QPushButton("浏览…")
geotiff_dir_btn.setMaximumWidth(80)
geotiff_dir_btn.clicked.connect(self.browse_geotiff_dir)
geotiff_dir_row.addWidget(self.geotiff_dir_label)
geotiff_dir_row.addWidget(self.geotiff_dir_edit, 1)
geotiff_dir_row.addWidget(geotiff_dir_btn)
self._geotiff_dir_widget = QWidget()
self._geotiff_dir_widget.setLayout(geotiff_dir_row)
# ---------- 统一组件:水色指数目录 (批量) ----------
self._geotiff_dir_widget = FileSelectWidget("水色指数目录:", "Directories")
apply_fs_style(self._geotiff_dir_widget, "选择 10_WaterIndex_Images 文件夹(批量渲染)…")
self._geotiff_dir_widget.browse_btn.clicked.disconnect()
self._geotiff_dir_widget.browse_btn.clicked.connect(self.browse_geotiff_dir)
self.geotiff_dir_edit = self._geotiff_dir_widget.line_edit
self._geotiff_dir_widget.setVisible(False)
layout.addWidget(self._geotiff_dir_widget)
input_layout.addWidget(self._geotiff_dir_widget)
self.recursive_csv_cb = QCheckBox("包含子文件夹(递归扫描 *.csv")
layout.addWidget(self.recursive_csv_cb)
input_group.setLayout(input_layout)
layout.addWidget(input_group)
self.boundary_file = FileSelectWidget(
"边界文件:",
"Shapefiles (*.shp);;All Files (*.*)"
)
layout.addWidget(self.boundary_file)
# ==========================================
# 卡片 2生成参数配置
# ==========================================
params_group = QGroupBox("⚙️ 生成参数配置")
params_layout = QVBoxLayout()
params_layout.setSpacing(16)
params_layout.setContentsMargins(20, 24, 20, 20)
# 参数设置
params_group = QGroupBox("生成参数")
params_layout = QFormLayout()
# ---------- 统一组件:边界文件 ----------
self.boundary_file = FileSelectWidget("边界文件:", "Shapefiles (*.shp);;All Files (*.*)")
apply_fs_style(self.boundary_file, "选择水域掩膜或边界 Shapefile…")
params_layout.addWidget(self.boundary_file)
# ---------- 统一组件:分辨率 ----------
self.resolution = QDoubleSpinBox()
self.resolution.setRange(1, 1000)
self.resolution.setValue(30)
params_layout.addRow("分辨率(米):", self.resolution)
self.resolution.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
params_layout.addLayout(create_standard_row("分辨率(米):", self.resolution))
# ---------- 统一组件:输入坐标系 ----------
self.input_crs = QLineEdit()
self.input_crs.setText("EPSG:32651")
params_layout.addRow("输入坐标系:", self.input_crs)
params_layout.addLayout(create_standard_row("输入坐标系:", self.input_crs))
# ---------- 统一组件:输出坐标系 ----------
self.output_crs = QLineEdit()
# ★★★ 强制默认输出坐标系与输入一致,禁止从 GUI 误改为 EPSG:4326 ★★★
# 历史默认值 'EPSG:4326' 会让 ContentMapper 把栅格重投影到经纬度,
# 与基于 EPSG:32651 的水域掩膜叠加时发生仿射变换撕裂(栅格错位、坐标轴扭曲)。
self.output_crs.setText("EPSG:32651")
params_layout.addRow("输出坐标系:", self.output_crs)
params_layout.addLayout(create_standard_row("输出坐标系:", self.output_crs))
# ---------- 复选框 ----------
check_row = QHBoxLayout()
check_row.setContentsMargins(0, 0, 0, 0)
check_row.setSpacing(10)
check_placeholder = QLabel("")
check_placeholder.setMinimumWidth(120)
check_placeholder.setMaximumWidth(120) # 保持对齐方阵
check_row.addWidget(check_placeholder)
self.show_points = QCheckBox("显示采样点")
params_layout.addRow("", self.show_points)
self.use_diffusion = QCheckBox("启用距离扩散")
self.use_diffusion.setChecked(True)
params_layout.addRow("", self.use_diffusion)
check_row.addWidget(self.show_points)
check_row.addWidget(self.use_diffusion)
check_row.addStretch()
params_layout.addLayout(check_row)
params_group.setLayout(params_layout)
layout.addWidget(params_group)
# 输出目录
self.output_dir = FileSelectWidget(
"输出分布图目录:",
"Directories;;All Files (*.*)"
)
self.output_dir.line_edit.setPlaceholderText("留空→工作目录/14_visualization")
# ==========================================
# 卡片 3输出与执行
# ==========================================
execute_group = QGroupBox("🚀 输出与执行")
execute_layout = QVBoxLayout()
execute_layout.setSpacing(16)
execute_layout.setContentsMargins(20, 24, 20, 20)
self.output_dir = FileSelectWidget("输出分布图目录:", "Directories;;All Files (*.*)")
apply_fs_style(self.output_dir, "留空→工作目录/14_visualization")
self.output_dir.browse_btn.clicked.disconnect()
self.output_dir.browse_btn.clicked.connect(self.browse_output_dir)
layout.addWidget(self.output_dir)
execute_layout.addWidget(self.output_dir)
# 启用步骤
self.enable_checkbox = QCheckBox("启用此步骤")
self.enable_checkbox.setChecked(True)
layout.addWidget(self.enable_checkbox)
# 独立运行按钮
self.run_button = QPushButton("独立运行此步骤")
self.run_button.setStyleSheet(ModernStylesheet.get_button_stylesheet('success'))
self.run_button.clicked.connect(self.run_step)
layout.addWidget(self.run_button)
# 批量渲染进度条
self.progress_bar = QProgressBar()
self.progress_bar.setVisible(False)
self.progress_bar.setMinimum(0)
self.progress_bar.setMaximum(100)
self.progress_bar.setValue(0)
layout.addWidget(self.progress_bar)
execute_layout.addWidget(self.progress_bar)
action_layout = QHBoxLayout()
action_layout.addStretch()
self.run_button = QPushButton("独立运行步骤")
self.run_button.setStyleSheet(ModernStylesheet.get_button_stylesheet('primary'))
self.run_button.setMinimumWidth(140)
self.run_button.clicked.connect(self.run_step)
action_layout.addWidget(self.run_button)
execute_layout.addLayout(action_layout)
execute_group.setLayout(execute_layout)
layout.addWidget(execute_group)
layout.addStretch()
self.setLayout(layout)
# 信号绑定与初始状态
self.mode_single_rb.toggled.connect(self._toggle_input_mode)
self.mode_folder_rb.toggled.connect(self._toggle_input_mode)
self.mode_single_rb.setChecked(True) # 默认选中"单个 CSV"
self._toggle_input_mode() # 根据默认值设置初始显示状态
self.batch_mode_combo.setCurrentIndex(0)
self._toggle_input_mode()
def _toggle_input_mode(self):
"""槽函数:根据渲染模式和输入模式动态显示/隐藏对应的输入组件。"""
geotiff_mode = self.render_mode_combo.currentText() == "GeoTIFF 栅格模式"
folder_mode = self.mode_folder_rb.isChecked()
folder_mode = self.batch_mode_combo.currentText() == "文件夹批量"
# CSV 插值模式
if not geotiff_mode:
self.prediction_csv_file.setVisible(not folder_mode)
self._folder_row_widget.setVisible(folder_mode)
self.recursive_csv_cb.setVisible(folder_mode)
self.geotiff_file.setVisible(False)
self._geotiff_dir_widget.setVisible(False)
# GeoTIFF 栅格模式
else:
self.prediction_csv_file.setVisible(False)
self._folder_row_widget.setVisible(False)
self.recursive_csv_cb.setVisible(False)
# GeoTIFF + 文件夹批量 → 显示文件夹选择器;否则 → 显示单文件选择器
self.geotiff_file.setVisible(not folder_mode)
self._geotiff_dir_widget.setVisible(folder_mode)
@ -387,10 +371,8 @@ class Step11MapPanel(QWidget):
if not folder or not os.path.isdir(folder):
return []
root = Path(folder)
if self.recursive_csv_cb.isChecked():
files = sorted(root.rglob("*.csv"))
else:
files = sorted(root.glob("*.csv"))
# 统一取消递归,只扫描当前直接文件夹下的 CSV防误操作
files = sorted(root.glob("*.csv"))
return [str(p) for p in files if p.is_file()]
def browse_geotiff_dir(self):
@ -428,14 +410,13 @@ class Step11MapPanel(QWidget):
def get_config(self):
pred_csv = (self.prediction_csv_file.get_path() or "").strip()
folder_mode = self.mode_folder_rb.isChecked()
folder_mode = self.batch_mode_combo.currentText() == "文件夹批量"
pred_dir = (self.prediction_csv_dir_edit.text() or "").strip()
geotiff_path = (self.geotiff_file.get_path() or "").strip()
config = {
'step10_batch_mode': 'folder' if folder_mode else 'single',
'render_mode': self.render_mode_combo.currentText(),
'prediction_csv_dir': pred_dir if pred_dir else None,
'recursive_csv_scan': self.recursive_csv_cb.isChecked(),
'prediction_csv_path': None if folder_mode else (pred_csv if pred_csv else None),
'geotiff_path': geotiff_path if geotiff_path else None,
'geotiff_dir': (self.geotiff_dir_edit.text() or "").strip() or None,
@ -458,17 +439,15 @@ class Step11MapPanel(QWidget):
def set_config(self, config):
mode = config.get('step10_batch_mode', 'single')
if mode == 'folder':
self.mode_folder_rb.setChecked(True)
self.batch_mode_combo.setCurrentIndex(1)
else:
self.mode_single_rb.setChecked(True)
self.batch_mode_combo.setCurrentIndex(0)
render_mode = config.get('render_mode', 'CSV 插值模式')
idx = self.render_mode_combo.findText(render_mode)
if idx >= 0:
self.render_mode_combo.setCurrentIndex(idx)
if config.get('prediction_csv_dir'):
self.prediction_csv_dir_edit.setText(str(config['prediction_csv_dir']))
if 'recursive_csv_scan' in config:
self.recursive_csv_cb.setChecked(bool(config['recursive_csv_scan']))
if 'prediction_csv_path' in config and config['prediction_csv_path']:
self.prediction_csv_file.set_path(str(config['prediction_csv_path']))
if 'geotiff_path' in config and config['geotiff_path']:
@ -509,7 +488,7 @@ class Step11MapPanel(QWidget):
path = step9_panel.output_file.get_path()
if path:
self.prediction_csv_dir_edit.setText(path)
self.mode_folder_rb.setChecked(True)
self.batch_mode_combo.setCurrentIndex(1)
# 2. 安全抓取 Step 1 的真实掩膜文件(彻底拒绝瞎猜 roi.shp
step1_panel = factory.get_panel('step1')
@ -590,7 +569,7 @@ class Step11MapPanel(QWidget):
# 修正:将后面代码中所有的 parent 替换为 main_win
parent = main_win
if self.mode_folder_rb.isChecked():
if self.batch_mode_combo.currentText() == "文件夹批量":
# -------- CSV 插值批量 --------
if self.render_mode_combo.currentText() != "GeoTIFF 栅格模式":
csv_list = self._collect_csv_paths_from_folder()
@ -599,7 +578,7 @@ class Step11MapPanel(QWidget):
self,
"输入验证失败",
"所选文件夹中未找到 .csv 文件,或目录无效。\n"
"可勾选「包含子文件夹」以递归扫描",
"请确认文件夹路径正确,且当前目录下存在 CSV 预测结果",
)
return
if not PIPELINE_AVAILABLE: