refactor: 建立动态面板注册表,消除硬编码,实现步骤界面的数据驱动渲染与依赖路由

This commit is contained in:
DXC
2026-06-17 16:02:17 +08:00
parent 1949711cda
commit a58744cfbb
2 changed files with 362 additions and 283 deletions

View File

@ -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

View File

@ -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"""
@ -608,35 +565,14 @@ 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)
@ -662,7 +598,7 @@ class WaterQualityGUI(QMainWindow):
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")), "水域掩膜")
# 添加各步骤面板(从注册表动态实例化)
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.step2_panel = Step2Panel()
self.step_stack.addTab(self.create_scroll_area(self.step2_panel), QIcon(self.get_icon_path("2.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.step3_panel = Step3Panel()
self.step_stack.addTab(self.create_scroll_area(self.step3_panel), QIcon(self.get_icon_path("3.png")), "耀斑去除")
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,20 +817,11 @@ class WaterQualityGUI(QMainWindow):
if index < 0:
return
# Tab索引到步骤ID的反向映射13个Tabindex 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)
@ -929,27 +833,10 @@ class WaterQualityGUI(QMainWindow):
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 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
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)
panel.auto_fill_inputs = create_auto_fill_method(step_id)
# 尝试添加自动填充按钮到面板(如果面板支持的话)
self.try_add_auto_fill_button(panel)
# 尝试添加自动填充按钮到面板(如果面板支持的话)
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()):