diff --git a/src/gui/core/panel_registry.py b/src/gui/core/panel_registry.py new file mode 100644 index 0000000..135fc46 --- /dev/null +++ b/src/gui/core/panel_registry.py @@ -0,0 +1,253 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +面板注册中心 + +集中定义所有步骤面板的结构化配置,包括: +- 步骤 ID / 类引用 / 标题 / 图标 / 阶段 / 导航显示名 +- 步骤间依赖关系(输入字段 → 上游步骤/输出类型/面板属性) +- 构造参数(如 Step13ReportPanel 需要 main_window) + +WaterQualityGUI 通过遍历 PANEL_REGISTRY 动态生成导航树、Tab 页、 +依赖传递和配置读写,彻底消除硬编码。 +""" + +from src.gui.panels.step1_panel import Step1Panel +from src.gui.panels.step2_panel import Step2Panel +from src.gui.panels.step3_panel import Step3Panel +from src.gui.panels.step4_sampling_panel import Step4SamplingPanel +from src.gui.panels.step5_clean_panel import Step5CleanPanel +from src.gui.panels.step6_feature_panel import Step6FeaturePanel +from src.gui.panels.step7_index_panel import Step7IndexPanel +from src.gui.panels.step8_ml_train_panel import Step8MlTrainPanel +from src.gui.panels.step9_ml_predict_panel import Step9MlPredictPanel +from src.gui.panels.step10_watercolor_panel import Step10WatercolorPanel +from src.gui.panels.step11_map_panel import Step11MapPanel +from src.gui.panels.step12_viz_panel import Step12VizPanel +from src.gui.panels.step13_report_panel import Step13ReportPanel + + +PANEL_REGISTRY = [ + # ═══════════════════════════════════════════════════════════════ + # 阶段一:影像预处理 + # ═══════════════════════════════════════════════════════════════ + { + 'step_id': 'step1', + 'class_ref': Step1Panel, + 'title': '水域掩膜', + 'icon': '1.png', + 'stage': '阶段一:影像预处理', + 'display_name': '1. 水域掩膜生成', + 'dependencies': None, + 'constructor_kwargs': None, + }, + { + 'step_id': 'step2', + 'class_ref': Step2Panel, + 'title': '耀斑检测', + 'icon': '2.png', + 'stage': '阶段一:影像预处理', + 'display_name': '2. 耀斑区域识别', + 'dependencies': { + 'img_path': ('step1', 'reference_img', 'img_file'), + 'water_mask_path': ('step1', 'water_mask', 'water_mask_file'), + }, + 'constructor_kwargs': None, + }, + { + 'step_id': 'step3', + 'class_ref': Step3Panel, + 'title': '耀斑去除', + 'icon': '3.png', + 'stage': '阶段一:影像预处理', + 'display_name': '3. 耀斑去除与修复', + 'dependencies': { + 'img_path': ('step1', 'reference_img', 'img_file'), + 'water_mask': ('step1', 'water_mask', 'water_mask_file'), + }, + 'constructor_kwargs': None, + }, + + # ═══════════════════════════════════════════════════════════════ + # 阶段二:样本数据准备 + # ═══════════════════════════════════════════════════════════════ + { + 'step_id': 'step4_sampling', + 'class_ref': Step4SamplingPanel, + 'title': '采样点布设', + 'icon': '4.png', + 'stage': '阶段二:样本数据准备', + 'display_name': '4. 采样点布设', + 'dependencies': { + 'deglint_img_path': ('step3', 'deglint_image', 'deglint_img_file'), + 'water_mask_path': ('step1', 'water_mask', 'water_mask_file'), + }, + 'constructor_kwargs': None, + }, + { + 'step_id': 'step5_clean', + 'class_ref': Step5CleanPanel, + 'title': '数据清洗', + 'icon': '5.png', + 'stage': '阶段二:样本数据准备', + 'display_name': '5. 数据清洗', + # 业务要求保持输入源独立,不自动抓取 step4_sampling 的输出 + 'dependencies': None, + 'constructor_kwargs': None, + }, + { + 'step_id': 'step6_feature', + 'class_ref': Step6FeaturePanel, + 'title': '光谱特征', + 'icon': '6.png', + 'stage': '阶段二:样本数据准备', + 'display_name': '6. 光谱特征提取', + 'dependencies': { + 'deglint_img_path': ('step3', 'deglint_image', 'deglint_img_file'), + 'csv_path': ('step5_clean', 'processed_data', 'csv_file'), + 'boundary_mask_path': ('step1', 'water_mask', 'water_mask_file'), + 'glint_mask_path': ('step2', 'glint_mask', 'glint_mask_file'), + }, + 'constructor_kwargs': None, + }, + { + 'step_id': 'step7_index', + 'class_ref': Step7IndexPanel, + 'title': '水质光谱指数计算', + 'icon': '7.png', + 'stage': '阶段二:样本数据准备', + 'display_name': '7. 水质指数计算', + 'dependencies': { + 'training_csv_path': ('step6_feature', 'training_spectra', 'training_data_widget'), + }, + 'constructor_kwargs': None, + }, + + # ═══════════════════════════════════════════════════════════════ + # 阶段三:模型构建与训练 + # ═══════════════════════════════════════════════════════════════ + { + 'step_id': 'step8_ml_train', + 'class_ref': Step8MlTrainPanel, + 'title': '机器学习建模', + 'icon': '8.png', + 'stage': '阶段三:模型构建与训练', + 'display_name': '8. 机器学习建模', + 'dependencies': { + 'training_csv_file': ('step7_index', 'training_spectra_indices', 'training_csv_file'), + }, + 'constructor_kwargs': None, + }, + + # ═══════════════════════════════════════════════════════════════ + # 阶段四:预测与成果输出 + # ═══════════════════════════════════════════════════════════════ + { + 'step_id': 'step9_ml_predict', + 'class_ref': Step9MlPredictPanel, + 'title': '机器学习预测', + 'icon': '10.png', + 'stage': '阶段四:预测与成果输出', + 'display_name': '9. 机器学习预测', + 'dependencies': { + 'models_dir': ('step8_ml_train', 'Supervised_Model_Training', 'models_dir_widget'), + }, + 'constructor_kwargs': None, + }, + { + 'step_id': 'step10_watercolor', + 'class_ref': Step10WatercolorPanel, + 'title': '水色指数反演', + 'icon': '10.png', + 'stage': '阶段四:预测与成果输出', + 'display_name': '10. 水色指数反演', + 'dependencies': { + 'bsq_file': ('step3', 'deglint_image', 'bsq_file'), + }, + 'constructor_kwargs': None, + }, + { + 'step_id': 'step11_map', + 'class_ref': Step11MapPanel, + 'title': '专题图生成', + 'icon': '10.png', + 'stage': '阶段四:预测与成果输出', + 'display_name': '11. 专题图生成', + 'dependencies': { + 'prediction_csv_dir_edit': ('step9_ml_predict', '9_ML_Prediction', 'prediction_csv_dir_edit'), + 'geotiff_dir_edit': ('step10_watercolor', 'WaterIndex_Images', 'geotiff_dir_edit'), + }, + 'constructor_kwargs': None, + }, + { + 'step_id': 'step12_viz', + 'class_ref': Step12VizPanel, + 'title': '可视化', + 'icon': '9.png', + 'stage': '阶段四:预测与成果输出', + 'display_name': '12. 可视化展示', + 'dependencies': None, + 'constructor_kwargs': None, + }, + { + 'step_id': 'step13_report', + 'class_ref': Step13ReportPanel, + 'title': '报告生成', + 'icon': '10.png', + 'stage': '阶段四:预测与成果输出', + 'display_name': '13. 分析报告生成', + 'dependencies': None, + 'constructor_kwargs': {'main_window'}, # 需要注入 main_window=self + }, +] + + +def build_step_dependencies(): + """从 PANEL_REGISTRY 构建 step_dependencies 字典。 + + Returns: + dict: {step_id: {input_field: (dep_step, output_type, panel_attr)}} + """ + deps = {} + for entry in PANEL_REGISTRY: + if entry['dependencies']: + deps[entry['step_id']] = entry['dependencies'] + return deps + + +def build_stage_groups(): + """从 PANEL_REGISTRY 构建阶段分组字典。 + + Returns: + dict: {stage_name: [(step_id, display_name), ...]} + """ + groups = {} + for entry in PANEL_REGISTRY: + stage = entry['stage'] + if stage not in groups: + groups[stage] = [] + groups[stage].append((entry['step_id'], entry['display_name'])) + return groups + + +def get_tab_index(step_id): + """根据 step_id 获取其在 PANEL_REGISTRY 中的索引(即 Tab 索引)。""" + for i, entry in enumerate(PANEL_REGISTRY): + if entry['step_id'] == step_id: + return i + return -1 + + +def get_step_id_by_tab_index(tab_index): + """根据 Tab 索引获取 step_id。""" + if 0 <= tab_index < len(PANEL_REGISTRY): + return PANEL_REGISTRY[tab_index]['step_id'] + return None + + +def get_entry(step_id): + """根据 step_id 获取注册表条目。""" + for entry in PANEL_REGISTRY: + if entry['step_id'] == step_id: + return entry + return None diff --git a/src/gui/water_quality_gui.py b/src/gui/water_quality_gui.py index 88417a1..a4f08b4 100644 --- a/src/gui/water_quality_gui.py +++ b/src/gui/water_quality_gui.py @@ -115,21 +115,16 @@ from src.gui.components.custom_widgets import FileSelectWidget from src.gui.components.chart_dialogs import ChartViewerDialog, ChartBrowserDialog, InteractiveViewerDialog, PandasTableModel from src.gui.components.image_viewer_components import ImageCategoryTree, ImageViewerWidget -# 导入面板组件 -from src.gui.panels.step1_panel import Step1Panel -from src.gui.panels.step2_panel import Step2Panel -from src.gui.panels.step3_panel import Step3Panel -from src.gui.panels.step4_sampling_panel import Step4SamplingPanel # 采样点布设 -from src.gui.panels.step5_clean_panel import Step5CleanPanel # 数据清洗 -from src.gui.panels.step6_feature_panel import Step6FeaturePanel # 光谱特征 -from src.gui.panels.step7_index_panel import Step7IndexPanel # 水质光谱指数 -from src.gui.panels.step10_watercolor_panel import Step10WatercolorPanel # 水色指数反演 -from src.gui.panels.step8_ml_train_panel import Step8MlTrainPanel # 机器学习建模 -from src.gui.panels.step9_ml_predict_panel import Step9MlPredictPanel # 机器学习预测 +# 导入面板注册中心(数据驱动,替代硬编码面板导入) +from src.gui.core.panel_registry import ( + PANEL_REGISTRY, + build_step_dependencies, + build_stage_groups, + get_tab_index, + get_step_id_by_tab_index, + get_entry, +) from src.gui.dialogs import BandConfirmDialog, AISettingsDialog -from src.gui.panels.step11_map_panel import Step11MapPanel # 专题图生成 -from src.gui.panels.step12_viz_panel import Step12VizPanel # 可视化 -from src.gui.panels.step13_report_panel import Step13ReportPanel # 报告生成 # Pipeline 核心异常(用于预检弹窗) from src.core.pipeline.runner import PipelineHalt @@ -187,8 +182,11 @@ class WaterQualityGUI(QMainWindow): # 工作空间管理器(文件扫描、路径发现、配置裁剪) self.workspace_manager = WorkspaceManager() - # 定义步骤依赖关系 - self._init_step_dependencies() + # 面板实例字典(step_id → panel instance),由 create_content_area 填充 + self._panels = {} + + # 从注册表构建步骤依赖关系 + self.step_dependencies = build_step_dependencies() self.init_ui() self.apply_stylesheet() @@ -198,48 +196,6 @@ class WaterQualityGUI(QMainWindow): # 100ms 延迟足以让 GUI 事件循环启动并显示主窗口 QTimer.singleShot(100, self.init_workspace) - def _init_step_dependencies(self): - """初始化步骤依赖关系""" - # 依赖关系字典结构: - # '当前步骤ID': { '依赖参数名': ('上游步骤ID', '上游输出类型/Key', '当前步骤接收该路径的组件属性名') } - self.step_dependencies = { - 'step2': { - 'img_path': ('step1', 'reference_img', 'img_file'), - 'water_mask_path': ('step1', 'water_mask', 'water_mask_file') - }, - 'step3': { - 'img_path': ('step1', 'reference_img', 'img_file'), - 'water_mask': ('step1', 'water_mask', 'water_mask_file') - }, - 'step4_sampling': { - 'deglint_img_path': ('step3', 'deglint_image', 'deglint_img_file'), - 'water_mask_path': ('step1', 'water_mask', 'water_mask_file') - }, - # 'step5_clean': 业务要求保持输入源独立,不自动抓取 step4_sampling 的输出;用户手动浏览导入 - 'step6_feature': { - 'deglint_img_path': ('step3', 'deglint_image', 'deglint_img_file'), - 'csv_path': ('step5_clean', 'processed_data', 'csv_file'), - 'boundary_mask_path': ('step1', 'water_mask', 'water_mask_file'), # step6_panel里叫water_mask_file - 'glint_mask_path': ('step2', 'glint_mask', 'glint_mask_file') - }, - 'step7_index': { - 'training_csv_path': ('step6_feature', 'training_spectra', 'training_data_widget') # step7 找 step6 的光谱提取 - }, - 'step8_ml_train': { - 'training_csv_file': ('step7_index', 'training_spectra_indices', 'training_csv_file') # step8 找 step7 的指数宽表 - }, - 'step9_ml_predict': { - 'models_dir': ('step8_ml_train', 'Supervised_Model_Training', 'models_dir_widget') - }, - 'step10_watercolor': { - 'bsq_file': ('step3', 'deglint_image', 'bsq_file') # 水色反演需要去耀斑BSQ影像 - }, - 'step11_map': { - 'prediction_csv_dir_edit': ('step9_ml_predict', '9_ML_Prediction', 'prediction_csv_dir_edit'), - 'geotiff_dir_edit': ('step10_watercolor', 'WaterIndex_Images', 'geotiff_dir_edit') - } - } - def get_icon_path(self, icon_filename): """获取图标文件的完整路径(统一使用 get_resource_path)。""" return get_resource_path(f"data/icons/{icon_filename}") @@ -314,8 +270,9 @@ class WaterQualityGUI(QMainWindow): # Step1: 只传递工作目录引用,不直接填充路径 # 路径填充由 Step1Panel 根据单选按钮状态自动控制 - if hasattr(self, 'step1_panel'): - self.step1_panel.update_work_directory(self.work_dir) + step1_panel = self._panels.get('step1') + if step1_panel: + step1_panel.update_work_directory(self.work_dir) def init_ui(self): """初始化UI""" @@ -607,36 +564,15 @@ class WaterQualityGUI(QMainWindow): # 步骤列表 - 分层结构 self.step_list = QListWidget() self.step_list.setStyleSheet(ModernStylesheet.get_sidebar_stylesheet()) - - # 定义四阶段结构 - self.process_stages = { - "阶段一:影像预处理": [ - ("step1", "1. 水域掩膜生成"), - ("step2", "2. 耀斑区域识别"), - ("step3", "3. 耀斑去除与修复") - ], - "阶段二:样本数据准备": [ - ("step4_sampling", "4. 采样点布设"), - ("step5_clean", "5. 数据清洗"), - ("step6_feature", "6. 光谱特征提取"), - ("step7_index", "7. 水质指数计算") - ], - "阶段三:模型构建与训练": [ - ("step8_ml_train", "8. 机器学习建模") - ], - "阶段四:预测与成果输出": [ - ("step9_ml_predict", "9. 机器学习预测"), - ("step10_watercolor", "10. 水色指数反演"), - ("step11_map", "11. 专题图生成"), - ("step12_viz", "12. 可视化展示"), - ("step13_report", "13. 分析报告生成") - ] - } - + + # 从注册表动态构建阶段分组 + self.process_stages = build_stage_groups() + # 存储步骤映射 self.step_name_map = {} - + # 添加分组项到列表 + stage_names = list(self.process_stages.keys()) for stage_idx, (stage_name, steps) in enumerate(self.process_stages.items()): # 添加阶段标题项(可视化分组) stage_item = QListWidgetItem(stage_name) @@ -647,22 +583,22 @@ class WaterQualityGUI(QMainWindow): stage_item.setFlags(stage_item.flags() & ~Qt.ItemIsEnabled) stage_item.setData(Qt.UserRole, "stage_header") self.step_list.addItem(stage_item) - + # 添加该阶段的所有步骤 for step_id, step_display in steps: item = QListWidgetItem(f" └─ {step_display}") item.setData(Qt.UserRole, step_id) self.step_name_map[step_display] = step_id - + # 设置步骤项的样式 step_font = QFont("Arial", 10) item.setFont(step_font) item.setForeground(QColor(ModernStylesheet.COLORS.get('text_secondary', '#666666'))) self.step_list.addItem(item) - + # 在阶段间添加分隔符 - if stage_idx < len(self.process_stages) - 1: + if stage_idx < len(stage_names) - 1: separator_item = QListWidgetItem("") separator_item.setFlags(separator_item.flags() & ~Qt.ItemIsSelectable) separator_item.setFlags(separator_item.flags() & ~Qt.ItemIsEnabled) @@ -709,45 +645,30 @@ class WaterQualityGUI(QMainWindow): self.step_stack.setTabsClosable(False) self.step_stack.setStyleSheet(ModernStylesheet.get_main_stylesheet()) - # 添加各步骤面板 - self.step1_panel = Step1Panel() - self.step_stack.addTab(self.create_scroll_area(self.step1_panel), QIcon(self.get_icon_path("1.png")), "水域掩膜") - - self.step2_panel = Step2Panel() - self.step_stack.addTab(self.create_scroll_area(self.step2_panel), QIcon(self.get_icon_path("2.png")), "耀斑检测") + # 添加各步骤面板(从注册表动态实例化) + for entry in PANEL_REGISTRY: + step_id = entry['step_id'] + cls = entry['class_ref'] + title = entry['title'] + icon_name = entry['icon'] + kwargs = entry.get('constructor_kwargs') - self.step3_panel = Step3Panel() - self.step_stack.addTab(self.create_scroll_area(self.step3_panel), QIcon(self.get_icon_path("3.png")), "耀斑去除") + # 处理特殊构造参数(如 Step13ReportPanel 需要 main_window=self) + if kwargs: + resolved_kwargs = {} + for k in kwargs: + if k == 'main_window': + resolved_kwargs[k] = self + panel = cls(**resolved_kwargs) + else: + panel = cls() - self.step4_sampling_panel = Step4SamplingPanel() - self.step_stack.addTab(self.create_scroll_area(self.step4_sampling_panel), QIcon(self.get_icon_path("4.png")), "采样点布设") - - self.step5_clean_panel = Step5CleanPanel() - self.step_stack.addTab(self.create_scroll_area(self.step5_clean_panel), QIcon(self.get_icon_path("5.png")), "数据清洗") - - self.step6_feature_panel = Step6FeaturePanel() - self.step_stack.addTab(self.create_scroll_area(self.step6_feature_panel), QIcon(self.get_icon_path("6.png")), "光谱特征") - - self.step7_index_panel = Step7IndexPanel() - self.step_stack.addTab(self.create_scroll_area(self.step7_index_panel), QIcon(self.get_icon_path("7.png")), "水质光谱指数计算") - - self.step8_ml_train_panel = Step8MlTrainPanel() - self.step_stack.addTab(self.create_scroll_area(self.step8_ml_train_panel), QIcon(self.get_icon_path("8.png")), "机器学习建模") - - self.step9_ml_predict_panel = Step9MlPredictPanel() - self.step_stack.addTab(self.create_scroll_area(self.step9_ml_predict_panel), QIcon(self.get_icon_path("10.png")), "机器学习预测") - - self.step10_watercolor_panel = Step10WatercolorPanel() - self.step_stack.addTab(self.create_scroll_area(self.step10_watercolor_panel), QIcon(self.get_icon_path("10.png")), "水色指数反演") - - self.step11_map_panel = Step11MapPanel() - self.step_stack.addTab(self.create_scroll_area(self.step11_map_panel), QIcon(self.get_icon_path("10.png")), "专题图生成") - - self.step12_viz_panel = Step12VizPanel() - self.step_stack.addTab(self.create_scroll_area(self.step12_viz_panel), QIcon(self.get_icon_path("9.png")), "可视化") - - self.step13_report_panel = Step13ReportPanel(main_window=self) - self.step_stack.addTab(self.create_scroll_area(self.step13_report_panel), QIcon(self.get_icon_path("10.png")), "报告生成") + self._panels[step_id] = panel + self.step_stack.addTab( + self.create_scroll_area(panel), + QIcon(self.get_icon_path(icon_name)), + title + ) # 连接Tab切换信号,实现双向同步(必须在step_stack创建后) self.step_stack.currentChanged.connect(self.on_tab_changed) @@ -884,17 +805,9 @@ class WaterQualityGUI(QMainWindow): # 是阶段标题或分隔符,不切换 return - # 根据步骤ID查找对应的tab索引 - step_id_to_tab = { - 'step1': 0, 'step2': 1, 'step3': 2, 'step4_sampling': 3, - 'step5_clean': 4, 'step6_feature': 5, 'step7_index': 6, - 'step8_ml_train': 7, 'step9_ml_predict': 8, - 'step10_watercolor': 9, 'step11_map': 10, - 'step12_viz': 11, 'step13_report': 12, - } - - if item_data in step_id_to_tab: - tab_index = step_id_to_tab[item_data] + # 根据步骤ID查找对应的tab索引(从注册表动态映射) + tab_index = get_tab_index(item_data) + if tab_index >= 0: self.step_stack.setCurrentIndex(tab_index) # 切换到步骤时自动填充输入路径 self.auto_populate_step_inputs(item_data) @@ -904,52 +817,26 @@ class WaterQualityGUI(QMainWindow): if index < 0: return - # Tab索引到步骤ID的反向映射(13个Tab,index 0-12) - tab_to_step_id = { - 0: 'step1', 1: 'step2', 2: 'step3', 3: 'step4_sampling', - 4: 'step5_clean', 5: 'step6_feature', 6: 'step7_index', - 7: 'step8_ml_train', 8: 'step9_ml_predict', - 9: 'step10_watercolor', 10: 'step11_map', - 11: 'step12_viz', 12: 'step13_report', - } - - if index not in tab_to_step_id: + # Tab索引到步骤ID的反向映射(从注册表动态获取) + target_step_id = get_step_id_by_tab_index(index) + if target_step_id is None: return - - target_step_id = tab_to_step_id[index] - + # 在step_list中查找对应的步骤项 for row in range(self.step_list.count()): item = self.step_list.item(row) if not item: continue - + item_data = item.data(Qt.UserRole) if item_data == target_step_id: self.step_list.setCurrentRow(row) break - # 面板自动填充:统一 mapping 覆盖 index 0-12 - mapping = { - 0: (self.step1_panel, "Step1"), - 1: (self.step2_panel, "Step2"), - 2: (self.step3_panel, "Step3"), - 3: (self.step4_sampling_panel, "Step4"), - 4: (self.step5_clean_panel, "Step5"), - 5: (self.step6_feature_panel, "Step6"), - 6: (self.step7_index_panel, "Step7"), - 7: (self.step8_ml_train_panel, "Step8"), - 8: (self.step9_ml_predict_panel, "Step9"), - 9: (self.step10_watercolor_panel, "Step10"), # 水色指数反演 - 10: (self.step11_map_panel, "Step11"), # 专题图生成 - 11: (self.step12_viz_panel, "Step12"), - 12: (self.step13_report_panel, "Step13") - } - - if index in mapping: - panel, _ = mapping[index] - if hasattr(panel, 'update_from_config'): - panel.update_from_config(work_dir=self.work_dir, pipeline=self.pipeline) + # 面板自动填充:从注册表获取面板实例 + panel = self._panels.get(target_step_id) + if panel and hasattr(panel, 'update_from_config'): + panel.update_from_config(work_dir=self.work_dir, pipeline=self.pipeline) def apply_stylesheet(self): """应用样式表 - 应用现代化设计风格""" @@ -981,29 +868,10 @@ class WaterQualityGUI(QMainWindow): with open(file_path, 'r', encoding='utf-8') as f: config = json.load(f) - # 应用配置到各面板 - if 'step1' in config: - self.step1_panel.set_config(config['step1']) - if 'step2' in config: - self.step2_panel.set_config(config['step2']) - if 'step3' in config: - self.step3_panel.set_config(config['step3']) - if 'step4_sampling' in config: - self.step4_sampling_panel.set_config(config['step4_sampling']) - if 'step5_clean' in config: - self.step5_clean_panel.set_config(config['step5_clean']) - if 'step6_feature' in config: - self.step6_feature_panel.set_config(config['step6_feature']) - if 'step7_index' in config: - self.step7_index_panel.set_config(config['step7_index']) - if 'step9_ml_predict' in config: - self.step9_ml_predict_panel.set_config(config['step9_ml_predict']) - if 'step11_map' in config: - self.step11_map_panel.set_config(config['step11_map']) - if 'step12_viz' in config: - self.step12_viz_panel.set_config(config['step12_viz']) - if 'step13_report' in config: - self.step13_report_panel.set_config(config['step13_report']) + # 应用配置到各面板(动态遍历,仅调用有 set_config 的面板) + for step_id, panel in self._panels.items(): + if step_id in config and hasattr(panel, 'set_config'): + panel.set_config(config[step_id]) self.config_file = file_path self.log_message(f"已加载配置: {file_path}", "info") @@ -1035,21 +903,11 @@ class WaterQualityGUI(QMainWindow): QMessageBox.critical(self, "错误", f"保存配置失败:\n{str(e)}") def get_current_config(self): - """获取当前配置""" - config = { - 'step1': self.step1_panel.get_config(), - 'step2': self.step2_panel.get_config(), - 'step3': self.step3_panel.get_config(), - 'step4_sampling': self.step4_sampling_panel.get_config(), - 'step5_clean': self.step5_clean_panel.get_config(), - 'step6_feature': self.step6_feature_panel.get_config(), - 'step7_index': self.step7_index_panel.get_config(), - 'step8_ml_train': self.step8_ml_train_panel.get_config(), - 'step9_ml_predict': self.step9_ml_predict_panel.get_config(), - 'step11_map': self.step11_map_panel.get_config(), - 'step12_viz': self.step12_viz_panel.get_config(), - 'step13_report': self.step13_report_panel.get_config(), - } + """获取当前配置(动态遍历所有面板,仅收集有 get_config 的面板)""" + config = {} + for step_id, panel in self._panels.items(): + if hasattr(panel, 'get_config'): + config[step_id] = panel.get_config() return config def auto_populate_step_inputs(self, step_id): @@ -1069,8 +927,9 @@ class WaterQualityGUI(QMainWindow): filled_count = 0 ref_img_path = None - if hasattr(self, 'step1_panel'): - ref_img_path = self.step1_panel.img_file.get_path() + step1_panel = self._panels.get('step1') + if step1_panel and hasattr(step1_panel, 'img_file'): + ref_img_path = step1_panel.img_file.get_path() for input_field, (dep_step, output_type, panel_attr) in dependencies.items(): # 检查面板是否有对应的属性 @@ -1105,22 +964,8 @@ class WaterQualityGUI(QMainWindow): return filled_count def get_step_panel(self, step_id): - """根据步骤ID获取对应的面板对象""" - panel_map = { - 'step1': self.step1_panel, - 'step2': self.step2_panel, - 'step3': self.step3_panel, - 'step4_sampling': self.step4_sampling_panel, - 'step5_clean': self.step5_clean_panel, - 'step6_feature': self.step6_feature_panel, - 'step7_index': self.step7_index_panel, - 'step8_ml_train': self.step8_ml_train_panel, - 'step9_ml_predict': self.step9_ml_predict_panel, - 'step11_map': self.step11_map_panel, - 'step12_viz': self.step12_viz_panel, - 'step13_report': self.step13_report_panel, - } - return panel_map.get(step_id) + """根据步骤ID获取对应的面板对象(从动态注册表查找)""" + return self._panels.get(step_id) def auto_populate_all_steps(self): """自动填充所有步骤的输入路径""" @@ -1134,8 +979,8 @@ class WaterQualityGUI(QMainWindow): # 首先扫描工作目录发现已有的输出文件 self.workspace_manager.scan_work_directory_for_files(work_path) - step_order = ['step2', 'step3', 'step4_sampling', 'step5_clean', 'step6_feature', 'step7_index', - 'step8_ml_train', 'step9_ml_predict', 'step11_map', 'step12_viz', 'step13_report'] + # 从注册表推导步骤顺序(跳过 step1,从 step2 开始) + step_order = [e['step_id'] for e in PANEL_REGISTRY if e['step_id'] != 'step1'] filled_count = 0 for step_id in step_order: @@ -1150,35 +995,20 @@ class WaterQualityGUI(QMainWindow): QMessageBox.information(self, "完成", "未发现可自动填充的路径。\n请确保工作目录中有相关的输出文件。") def add_auto_fill_buttons_to_panels(self): - """为各个步骤面板添加自动填充按钮""" - # 这个方法会在UI初始化完成后调用 - # 为每个有依赖关系的步骤面板添加自动填充功能 - panels_with_dependencies = [ - ('step2', self.step2_panel), - ('step3', self.step3_panel), - ('step4_sampling', self.step4_sampling_panel), - ('step5_clean', self.step5_clean_panel), - ('step6_feature', self.step6_feature_panel), - ('step7_index', self.step7_index_panel), - ('step8_ml_train', self.step8_ml_train_panel), - ('step9_ml_predict', self.step9_ml_predict_panel), - ('step11_map', self.step11_map_panel), - ('step12_viz', self.step12_viz_panel), - ('step13_report', self.step13_report_panel), - ] - - for step_id, panel in panels_with_dependencies: - if panel: - # 为面板添加自动填充方法 - def create_auto_fill_method(sid): - def auto_fill_inputs(): - self.auto_populate_step_inputs(sid) - return auto_fill_inputs - - panel.auto_fill_inputs = create_auto_fill_method(step_id) - - # 尝试添加自动填充按钮到面板(如果面板支持的话) - self.try_add_auto_fill_button(panel) + """为各个步骤面板添加自动填充按钮(动态遍历所有面板)""" + for step_id, panel in self._panels.items(): + if not panel: + continue + # 为面板添加自动填充方法 + def create_auto_fill_method(sid): + def auto_fill_inputs(): + self.auto_populate_step_inputs(sid) + return auto_fill_inputs + + panel.auto_fill_inputs = create_auto_fill_method(step_id) + + # 尝试添加自动填充按钮到面板(如果面板支持的话) + self.try_add_auto_fill_button(panel) def try_add_auto_fill_button(self, panel): """尝试为面板添加自动填充按钮""" @@ -1233,11 +1063,11 @@ class WaterQualityGUI(QMainWindow): self.log_message(f"工作目录已设置: {dir_path}", "info") self.statusBar().showMessage(f"工作目录: {dir_path}") - # 同步到可视化面板 - if hasattr(self, 'step12_viz_panel'): - self.step12_viz_panel.set_work_dir(dir_path) - if hasattr(self, 'step13_report_panel'): - self.step13_report_panel.set_work_dir(dir_path) + # 同步到可视化/报告面板 + for sid in ('step12_viz', 'step13_report'): + panel = self._panels.get(sid) + if panel and hasattr(panel, 'set_work_dir'): + panel.set_work_dir(dir_path) def open_work_directory(self): """打开工作目录""" @@ -1345,9 +1175,11 @@ class WaterQualityGUI(QMainWindow): """ # 1) 取 step1 影像路径 + step3 配置 + enabled 标志 try: - img_path = self.step1_panel.img_file.get_path() if hasattr(self, 'step1_panel') else None - step3_cfg = self.step3_panel.get_config() if hasattr(self, 'step3_panel') else None - step3_enabled = self.step3_panel.enable_checkbox.isChecked() if hasattr(self, 'step3_panel') else False + step1_panel = self._panels.get('step1') + step3_panel = self._panels.get('step3') + img_path = step1_panel.img_file.get_path() if step1_panel else None + step3_cfg = step3_panel.get_config() if step3_panel else None + step3_enabled = step3_panel.enable_checkbox.isChecked() if step3_panel else False except Exception as e: self.log_message(f"⚠ step3 波段预检:读取面板状态失败 - {e}", "warning") return True # 失败不阻断(防御性:放行比误杀好) @@ -1422,7 +1254,7 @@ class WaterQualityGUI(QMainWindow): new_band = dlg.selected_band() try: - spin = getattr(self.step3_panel, panel_attr) + spin = getattr(step3_panel, panel_attr) spin.setValue(new_band) except AttributeError: self.log_message(f"⚠ step3 panel 缺控件 {panel_attr},跳过回写", "warning") @@ -1598,8 +1430,9 @@ class WaterQualityGUI(QMainWindow): def auto_populate_dependent_steps(self, completed_step): """自动填充依赖于已完成步骤的后续步骤""" ref_img_path = None - if hasattr(self, 'step1_panel'): - ref_img_path = self.step1_panel.img_file.get_path() + step1_panel = self._panels.get('step1') + if step1_panel and hasattr(step1_panel, 'img_file'): + ref_img_path = step1_panel.img_file.get_path() for step_id, dependencies in self.step_dependencies.items(): for input_field, (dep_step, output_type, panel_attr) in dependencies.items(): @@ -1740,18 +1573,11 @@ class WaterQualityGUI(QMainWindow): # 需要禁用的步骤ID(对应无训练数据模式下需要禁用的步骤) disabled_step_ids = ['step4_sampling', 'step5_clean', 'step6_feature', 'step7_index', 'step9_ml_predict'] - # 更新标签页的启用/禁用状态 - step_id_to_tab_training = { - 'step1': 0, 'step2': 1, 'step3': 2, 'step4_sampling': 3, - 'step5_clean': 4, 'step6_feature': 5, 'step7_index': 6, 'step9_ml_predict': 7, - 'step10_watercolor': 9, 'step11_map': 10, 'step12_viz': 11, 'step13_report': 12 - } - + # 更新标签页的启用/禁用状态(从注册表动态获取 Tab 索引) for step_id in disabled_step_ids: - if step_id in step_id_to_tab_training: - tab_index = step_id_to_tab_training[step_id] - if tab_index < self.step_stack.count(): - self.step_stack.setTabEnabled(tab_index, self.has_training_data) + tab_index = get_tab_index(step_id) + if tab_index >= 0 and tab_index < self.step_stack.count(): + self.step_stack.setTabEnabled(tab_index, self.has_training_data) # 同时更新导航列表的启用状态 for i in range(self.step_list.count()):