--- name: WQ_GUI 数据流转架构 description: WQ_GUI ProjectSession 事件总线驱动的步骤间数据传递机制(完整重构版) source: auto-skill extracted_at: '2026-05-28T09:07:34.967Z' --- # WQ_GUI 数据流转架构 ## 核心结论 整个系统是**基于文件路径驱动**的管道,所有数据存储在本地磁盘。重构后通过 `ProjectSession` 事件总线实现 Panel 间完全解耦。 --- ## 1. 旧架构(旧代码中已删除) 主窗口通过 `self.step_outputs` 字典 + `step_dependencies` 配置 + `auto_populate_*` 系列方法管理步骤间路径填充。存在高度耦合问题: ```python # 已废弃并删除 self.step_outputs = {} self._init_step_dependencies() self.update_step_outputs(step_name, work_path) self.auto_populate_dependent_steps(completed_step) self.auto_populate_step_inputs(step_id) self.find_step_output(work_path, step_id, output_type) self.add_auto_fill_buttons_to_panels() self.scan_work_directory_for_files(work_path) ``` --- ## 2. 新架构:ProjectSession 事件总线 ### Session 核心 API(`src/core/project_session.py`) ```python class ProjectSession(QObject): path_updated = pyqtSignal(str, str, str) # step, out_type, path step_outputs_ready = pyqtSignal(str, str) # step, out_type def update_output(step, out_type, path): """Panel 完成后广播输出路径""" def update_outputs(step, {out_type: path, ...}): """Panel 完成后批量广播多个输出路径""" def get_output(step, out_type): """Panel 可主动查询上游路径(用于自动填充)""" def get_step_outputs(step): """返回该 step 的全部输出字典""" def scan_work_directory(): """主窗口 on_step_completed 末尾调用,扫描并广播所有已知路径""" ``` ### Panel 重构模板 ```python class StepXPanel(QWidget): def __init__(self, session=None, parent=None): super().__init__(parent) self.session = session self.work_dir = None self.init_ui() self._bind_session_signals() def _bind_session_signals(self): if not self.session: return self.session.path_updated.connect( self._on_session_path_updated, Qt.QueuedConnection ) @pyqtSlot(str, str, str) def _on_session_path_updated(self, step_name, output_type, path): print(f"[StepX Debug] 收到广播: step={step_name}, type={output_type}, path={path}") if step_name == 'step1': if output_type == 'reference_img': if not self.img_file.get_path().strip(): self.img_file.set_path(path) print(f"[StepX] 自动填充参考影像: {path}") elif output_type == 'water_mask': if not self.water_mask_file.get_path().strip(): self.water_mask_file.set_path(path) print(f"[StepX] 自动填充水域掩膜: {path}") # ... def on_step_finished(self, success, message): """由主窗口 on_step_completed 通过 getattr 动态调用""" if not success: return if self.session: outputs = {} path = self.output_widget.get_path().strip() if path: outputs['output_type'] = path if outputs: self.session.update_outputs('stepX', outputs) ``` ### 主窗口两处改动 ```python # 1. __init__ 中注入 session(所有 Panel 统一注入) self.step1_panel = Step1Panel(session=self.session) self.step2_panel = Step2Panel(session=self.session) self.step3_panel = Step3Panel(session=self.session) self.step4_panel = Step4Panel(session=self.session) self.step5_panel = Step5Panel(session=self.session) self.step5_5_panel = Step5_5Panel(session=self.session) self.step6_panel = Step6Panel(session=self.session) self.step6_5_panel = Step6_5Panel(session=self.session) self.step6_75_panel = Step6_75Panel(session=self.session) self.step7_panel = Step7Panel(session=self.session) self.step8_panel = Step8Panel(session=self.session) self.step8_5_panel = Step8_5Panel(session=self.session) self.step8_75_panel = Step8_75Panel(session=self.session) self.step9_panel = Step9Panel(session=self.session) # 2. on_step_completed(通用动态获取,无需维护字典) def on_step_completed(self, step_name, success, message): if not success: return if hasattr(self, 'session') and self.session: self.session.scan_work_directory() panel = getattr(self, f"{step_name}_panel", None) if panel and hasattr(panel, 'on_step_finished'): panel.on_step_finished(success, message) ``` --- ## 3. 全链路事件流 ### step1 → step2 / step3 路径(通过 Shapefile 栅格化产物) | 场景 | 广播的 water_mask 路径 | |------|----------------------| | NDWI 模式 | `output_file` 用户指定路径 | | Shapefile 模式 | `{work_dir}/1_water_mask/water_mask_from_shp.dat`(优先)
若文件不存在则 fallback 回 `mask_file.get_path()` | ``` step1 完成 → step1_panel.on_step_finished() → session.update_outputs('step1', { 'reference_img': img_path, 'water_mask': mask_path # 可能是 .dat 或 .shp(见上表) }) → step2_panel._on_session_path_updated() → step3_panel._on_session_path_updated() ``` ### step3 → step5 / step7;step5 → 下游训练 ``` step3.deglint_image ──┬─→ step5.deglint_image(填充 img_file) └─→ step7.deglint_image(填充 img_file) step5.training_spectra ──┬─→ step5_5.index_features ├─→ step6.models_dir ──→ step8.predictions ├─→ step6_5.models_dir ──→ step8_5.predictions └─→ step6_75.models_dir ──→ step8_75.predictions step7.sampling_points ──┬─→ step8 ├─→ step8_5 └─→ step8_75 step8/8_5/8_75.predictions ──→ step9.distribution_map ``` ### 各 Panel 监听/发布对照表(完整版) | Panel | 监听 | 发布 | |-------|------|------| | step1 | — | `reference_img`, `water_mask` | | step2 | `step1.reference_img`, `step1.water_mask` | `glint_mask` | | step3 | `step1.reference_img`, `step1.water_mask`, `step2.glint_mask` | `deglint_image` | | step4 | — | `processed_data` | | step5 | `step3.deglint_image`, `step4.processed_data`, `step2.glint_mask` | `training_spectra` | | step5_5 | `step5.training_spectra` | `index_features` | | step6 | `step5.training_spectra` | `models_dir` | | step6_5 | `step5.training_spectra` | `models_dir` | | step6_75 | `step5.training_spectra` | `models_dir` | | step7 | `step3.deglint_image`, `step1.water_mask`, `step2.glint_mask` | `sampling_points` | | step8 | `step7.sampling_points`, `step6.models_dir` | `predictions` | | step8_5 | `step7.sampling_points`, `step6_5.models_dir` | `predictions` | | step8_75 | `step7.sampling_points`, `step6_75.models_dir` | `predictions` | | step9 | `step8.predictions`, `step8_5.predictions`, `step8_75.predictions` | `distribution_map` | --- ## 4. 关键约束 - `__init__` 参数 `session=None`(向后兼容,主窗口可继续不传) - 所有 Panel 的 `init_ui / get_config / set_config / update_from_config` 完整保留 - 删除所有 `self.window().stepX_panel` 跨界访问 - 使用 `self.session.get_output()` 替代直接读取其他 panel 的 widget - 监听使用 `Qt.QueuedConnection` 确保跨线程安全 - 仅在 field 为空时自动填充(`not widget.get_path().strip()`) - `update_from_config` 中优先从 Session 获取路径,再用 Session 广播 - 主窗口 `on_step_completed` 中使用 `getattr(self, f"{step_name}_panel", None)` 实现通用动态获取,无需维护硬编码字典 - `step1` Shapefile 模式下,**不能**直接广播 `.shp` 输入文件,必须拼接 `{work_dir}/1_water_mask/water_mask_from_shp.dat` 作为产物路径