fix: 修复下游面板自动填充断裂的三处根因 + 清理过时 pipeline→panel 映射
This commit is contained in:
@ -25,9 +25,9 @@ class WorkspaceManager:
|
|||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.step_default_outputs = {
|
self.step_default_outputs = {
|
||||||
'step1': {'water_mask': "1_water_mask/water_mask_from_ndwi.dat"},
|
'step1': {'water_mask': "1_water_mask/water_mask_out.dat"},
|
||||||
'step2': {'glint_mask': "2_Glint_Detection/severe_glint_area.dat"},
|
'step2': {'glint_mask': "2_Glint_Detection/severe_glint_area.dat"},
|
||||||
'step3': {'deglint_image': "3_deglint/deglint_goodman.bsq"},
|
'step3': {'deglint_image': "3_deglint/deglint_image.bsq"},
|
||||||
'step4_sampling': {'sampling_points': "4_sampling/sampling_spectra.csv"},
|
'step4_sampling': {'sampling_points': "4_sampling/sampling_spectra.csv"},
|
||||||
'step5_clean': {'processed_data': "5_Data_Cleaning/processed_data.csv"},
|
'step5_clean': {'processed_data': "5_Data_Cleaning/processed_data.csv"},
|
||||||
'step6_feature': {'training_spectra': "6_Spectral_Feature_Extraction/training_spectra.csv"},
|
'step6_feature': {'training_spectra': "6_Spectral_Feature_Extraction/training_spectra.csv"},
|
||||||
@ -38,12 +38,6 @@ class WorkspaceManager:
|
|||||||
'step11_map': {'14_visualization': "14_visualization/"},
|
'step11_map': {'14_visualization': "14_visualization/"},
|
||||||
}
|
}
|
||||||
self.step_outputs = {}
|
self.step_outputs = {}
|
||||||
# pipeline step_id → panel step_id 映射(由 WaterQualityGUI 注入)
|
|
||||||
self._pipeline_to_panel = {}
|
|
||||||
|
|
||||||
def set_step_id_mapping(self, mapping: dict):
|
|
||||||
"""注入 pipeline step_id → panel step_id 映射,用于事件发布时统一 ID。"""
|
|
||||||
self._pipeline_to_panel = mapping
|
|
||||||
|
|
||||||
def _publish_outputs(self, step_id: str, outputs: dict):
|
def _publish_outputs(self, step_id: str, outputs: dict):
|
||||||
"""将发现的产出发布到 EventBus。
|
"""将发现的产出发布到 EventBus。
|
||||||
@ -206,18 +200,11 @@ class WorkspaceManager:
|
|||||||
return discovered_outputs
|
return discovered_outputs
|
||||||
|
|
||||||
def update_step_outputs(self, step_name, work_path):
|
def update_step_outputs(self, step_name, work_path):
|
||||||
"""更新指定步骤的输出路径记录并发布 EventBus 事件。
|
"""更新指定步骤的输出路径记录并发布 EventBus 事件。"""
|
||||||
|
if step_name not in self.step_default_outputs:
|
||||||
step_name 可能是 pipeline step_id(如 'step4'),
|
|
||||||
会先通过 _pipeline_to_panel 映射为面板 step_id(如 'step5_clean')。
|
|
||||||
"""
|
|
||||||
# 映射 pipeline step_id → panel step_id
|
|
||||||
panel_step_id = self._pipeline_to_panel.get(step_name, step_name)
|
|
||||||
|
|
||||||
if panel_step_id not in self.step_default_outputs:
|
|
||||||
return
|
return
|
||||||
|
|
||||||
step_outputs = self.step_default_outputs[panel_step_id]
|
step_outputs = self.step_default_outputs[step_name]
|
||||||
published = {}
|
published = {}
|
||||||
|
|
||||||
for output_type, relative_path in step_outputs.items():
|
for output_type, relative_path in step_outputs.items():
|
||||||
@ -227,17 +214,17 @@ class WorkspaceManager:
|
|||||||
if matching_files:
|
if matching_files:
|
||||||
latest_file = max(matching_files, key=lambda p: p.stat().st_mtime)
|
latest_file = max(matching_files, key=lambda p: p.stat().st_mtime)
|
||||||
path_str = str(latest_file)
|
path_str = str(latest_file)
|
||||||
self.step_outputs.setdefault(panel_step_id, {})[output_type] = path_str
|
self.step_outputs.setdefault(step_name, {})[output_type] = path_str
|
||||||
published[output_type] = path_str
|
published[output_type] = path_str
|
||||||
else:
|
else:
|
||||||
output_path = work_path / relative_path
|
output_path = work_path / relative_path
|
||||||
if output_path.exists():
|
if output_path.exists():
|
||||||
path_str = str(output_path)
|
path_str = str(output_path)
|
||||||
self.step_outputs.setdefault(panel_step_id, {})[output_type] = path_str
|
self.step_outputs.setdefault(step_name, {})[output_type] = path_str
|
||||||
published[output_type] = path_str
|
published[output_type] = path_str
|
||||||
|
|
||||||
if published:
|
if published:
|
||||||
self._publish_outputs(panel_step_id, published)
|
self._publish_outputs(step_name, published)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def prune_config_for_prediction_mode(config: dict) -> dict:
|
def prune_config_for_prediction_mode(config: dict) -> dict:
|
||||||
|
|||||||
@ -150,7 +150,7 @@ PANEL_REGISTRY = [
|
|||||||
'stage': '阶段四:预测与成果输出',
|
'stage': '阶段四:预测与成果输出',
|
||||||
'display_name': '9. 机器学习预测',
|
'display_name': '9. 机器学习预测',
|
||||||
'dependencies': {
|
'dependencies': {
|
||||||
'models_dir': ('step8_ml_train', 'Supervised_Model_Training', 'models_dir_widget'),
|
'models_dir': ('step8_ml_train', 'Supervised_Model_Training', 'models_dir_file'),
|
||||||
},
|
},
|
||||||
'constructor_kwargs': None,
|
'constructor_kwargs': None,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -43,23 +43,6 @@ from src.gui.core.pipeline_mode_dialog import PipelineModeDialog
|
|||||||
from src.gui.dialogs import BandConfirmDialog
|
from src.gui.dialogs import BandConfirmDialog
|
||||||
from src.core.pipeline.runner import PipelineHalt
|
from src.core.pipeline.runner import PipelineHalt
|
||||||
|
|
||||||
# pipeline step_id → panel step_id 映射
|
|
||||||
PIPELINE_TO_PANEL_STEP = {
|
|
||||||
'step1': 'step1',
|
|
||||||
'step2': 'step2',
|
|
||||||
'step3': 'step3',
|
|
||||||
'step4': 'step5_clean',
|
|
||||||
'step5': 'step6_feature',
|
|
||||||
'step7': 'step7_index',
|
|
||||||
'step8': 'step8_ml_train',
|
|
||||||
'step9': 'step10_watercolor',
|
|
||||||
'step10': 'step4_sampling',
|
|
||||||
'step11_ml': 'step9_ml_predict',
|
|
||||||
'step11': 'step11_map',
|
|
||||||
'step14': 'step11_map',
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class PipelineExecutor(QObject):
|
class PipelineExecutor(QObject):
|
||||||
"""Pipeline 执行器 —— 纯逻辑层,零 UI 直接操作。"""
|
"""Pipeline 执行器 —— 纯逻辑层,零 UI 直接操作。"""
|
||||||
|
|
||||||
|
|||||||
@ -34,23 +34,6 @@ from PyQt5.QtWidgets import QMessageBox, QFileDialog
|
|||||||
from src.gui.core.event_bus import global_event_bus
|
from src.gui.core.event_bus import global_event_bus
|
||||||
from src.core.workspace_manager import WorkspaceManager
|
from src.core.workspace_manager import WorkspaceManager
|
||||||
|
|
||||||
# pipeline step_id → panel step_id 映射
|
|
||||||
PIPELINE_TO_PANEL_STEP = {
|
|
||||||
'step1': 'step1',
|
|
||||||
'step2': 'step2',
|
|
||||||
'step3': 'step3',
|
|
||||||
'step4': 'step5_clean',
|
|
||||||
'step5': 'step6_feature',
|
|
||||||
'step7': 'step7_index',
|
|
||||||
'step8': 'step8_ml_train',
|
|
||||||
'step9': 'step10_watercolor',
|
|
||||||
'step10': 'step4_sampling',
|
|
||||||
'step11_ml': 'step9_ml_predict',
|
|
||||||
'step11': 'step11_map',
|
|
||||||
'step14': 'step11_map',
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class WorkspaceInitializer(QObject):
|
class WorkspaceInitializer(QObject):
|
||||||
"""工作空间初始化器 —— 纯逻辑层,零 UI 直接操作。"""
|
"""工作空间初始化器 —— 纯逻辑层,零 UI 直接操作。"""
|
||||||
|
|
||||||
@ -66,7 +49,10 @@ class WorkspaceInitializer(QObject):
|
|||||||
|
|
||||||
# 工作空间管理器(文件扫描、路径发现)
|
# 工作空间管理器(文件扫描、路径发现)
|
||||||
self._workspace_manager = WorkspaceManager()
|
self._workspace_manager = WorkspaceManager()
|
||||||
self._workspace_manager.set_step_id_mapping(PIPELINE_TO_PANEL_STEP)
|
|
||||||
|
# 全局输入缓存:{step_id: {output_type: path}}
|
||||||
|
# 存储非步骤产出但下游面板依赖的参数(如 Step1 的 reference_img)
|
||||||
|
self._global_inputs = {}
|
||||||
|
|
||||||
# 订阅 StepCompleted 事件 → 自动扫描产物
|
# 订阅 StepCompleted 事件 → 自动扫描产物
|
||||||
global_event_bus.subscribe('StepCompleted', self._on_step_completed)
|
global_event_bus.subscribe('StepCompleted', self._on_step_completed)
|
||||||
@ -89,6 +75,28 @@ class WorkspaceInitializer(QObject):
|
|||||||
def workspace_manager(self) -> WorkspaceManager:
|
def workspace_manager(self) -> WorkspaceManager:
|
||||||
return self._workspace_manager
|
return self._workspace_manager
|
||||||
|
|
||||||
|
def cache_global_input(self, step_id: str, output_type: str, path: str):
|
||||||
|
"""缓存全局输入参数。
|
||||||
|
|
||||||
|
全局输入不是任何步骤的产出,但下游面板依赖它。
|
||||||
|
典型例子:Step1 的 reference_img(用户选择的输入影像),
|
||||||
|
Step2/Step3 等下游面板需要它来预填"影像文件"字段。
|
||||||
|
|
||||||
|
Args:
|
||||||
|
step_id: 源步骤 ID(如 'step1')
|
||||||
|
output_type: 输出类型标识(如 'reference_img')
|
||||||
|
path: 文件路径字符串
|
||||||
|
"""
|
||||||
|
if not path:
|
||||||
|
return
|
||||||
|
if step_id not in self._global_inputs:
|
||||||
|
self._global_inputs[step_id] = {}
|
||||||
|
self._global_inputs[step_id][output_type] = path
|
||||||
|
|
||||||
|
def get_global_inputs(self):
|
||||||
|
"""返回所有已缓存的全局输入 {step_id: {output_type: path}}。"""
|
||||||
|
return dict(self._global_inputs)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
"""启动时工作目录选择对话框(由 QTimer.singleShot 延迟调用)。
|
"""启动时工作目录选择对话框(由 QTimer.singleShot 延迟调用)。
|
||||||
|
|
||||||
@ -183,12 +191,23 @@ class WorkspaceInitializer(QObject):
|
|||||||
if step1_panel and hasattr(step1_panel, 'img_file'):
|
if step1_panel and hasattr(step1_panel, 'img_file'):
|
||||||
ref_img = step1_panel.img_file.get_path()
|
ref_img = step1_panel.img_file.get_path()
|
||||||
if ref_img:
|
if ref_img:
|
||||||
|
# 缓存到全局输入,供后续懒加载面板苏醒时回放
|
||||||
|
self.cache_global_input('step1', 'reference_img', ref_img)
|
||||||
global_event_bus.publish('OutputUpdated', {
|
global_event_bus.publish('OutputUpdated', {
|
||||||
'step_id': 'step1',
|
'step_id': 'step1',
|
||||||
'output_type': 'reference_img',
|
'output_type': 'reference_img',
|
||||||
'path': ref_img,
|
'path': ref_img,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
# 兜底:遍历已加载面板调用 update_from_config,重建内存级参数和默认输出路径
|
||||||
|
for step_id, panel in self._panel_factory.get_loaded_panels().items():
|
||||||
|
if not hasattr(panel, 'update_from_config'):
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
panel.update_from_config(work_dir=work_dir, pipeline=None)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
global_event_bus.publish('LogMessage', {
|
global_event_bus.publish('LogMessage', {
|
||||||
'message': '✓ 工作目录扫描完成,事件总线已通知所有面板自动填充',
|
'message': '✓ 工作目录扫描完成,事件总线已通知所有面板自动填充',
|
||||||
'level': 'info',
|
'level': 'info',
|
||||||
|
|||||||
@ -157,23 +157,6 @@ from src.gui.core.pipeline_mode_dialog import PipelineModeDialog
|
|||||||
from src.gui.core.viz_thread import VisualizationWorkerThread, _viz_training_spectra_csv_path
|
from src.gui.core.viz_thread import VisualizationWorkerThread, _viz_training_spectra_csv_path
|
||||||
from src.core.workspace_manager import WorkspaceManager
|
from src.core.workspace_manager import WorkspaceManager
|
||||||
|
|
||||||
# pipeline step_id → panel step_id 映射(pipeline 内部编号与 GUI 面板编号不同)
|
|
||||||
PIPELINE_TO_PANEL_STEP = {
|
|
||||||
'step1': 'step1',
|
|
||||||
'step2': 'step2',
|
|
||||||
'step3': 'step3',
|
|
||||||
'step4': 'step5_clean',
|
|
||||||
'step5': 'step6_feature',
|
|
||||||
'step7': 'step7_index',
|
|
||||||
'step8': 'step8_ml_train',
|
|
||||||
'step9': 'step10_watercolor',
|
|
||||||
'step10': 'step4_sampling',
|
|
||||||
'step11_ml': 'step9_ml_predict',
|
|
||||||
'step11': 'step11_map',
|
|
||||||
'step14': 'step11_map',
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class WaterQualityGUI(QMainWindow):
|
class WaterQualityGUI(QMainWindow):
|
||||||
"""水质参数反演分析系统主窗口"""
|
"""水质参数反演分析系统主窗口"""
|
||||||
|
|
||||||
@ -199,7 +182,6 @@ class WaterQualityGUI(QMainWindow):
|
|||||||
|
|
||||||
# 工作空间管理器(文件扫描、路径发现、配置裁剪)
|
# 工作空间管理器(文件扫描、路径发现、配置裁剪)
|
||||||
self.workspace_manager = WorkspaceManager()
|
self.workspace_manager = WorkspaceManager()
|
||||||
self.workspace_manager.set_step_id_mapping(PIPELINE_TO_PANEL_STEP)
|
|
||||||
|
|
||||||
# 面板实例字典(step_id → panel instance),由 create_content_area 填充
|
# 面板实例字典(step_id → panel instance),由 create_content_area 填充
|
||||||
self._panels = {}
|
self._panels = {}
|
||||||
|
|||||||
Reference in New Issue
Block a user