refactor(pipeline): 路径直接传输 — 统一 ctx 字段名/panel key/step 形参名

This commit is contained in:
DXC
2026-06-03 17:29:41 +08:00
parent 517bb28611
commit 343e316799
99 changed files with 9127 additions and 91 deletions

View File

@ -0,0 +1,206 @@
---
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`(优先)<br>若文件不存在则 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 / step7step5 → 下游训练
```
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` 作为产物路径