refactor: 重构所有面板 update_from_config 为文件系统扫描模式

=== 核心变更 ===
- _step_path_resolver 新增 scan_work_dir_for_input() 统一文件扫描工具
- 基于 _SCAN_TABLE 映射表,按 output_type 自动扫描 work_dir 子目录
- 支持扩展名匹配(.dat/.tif/.bsq/.csv 等)和文件名关键词匹配
- 按 mtime 排序,返回最新匹配文件

=== 各面板重构 ===
- step2/3/4/6: 废弃 main_window.stepX_panel 跨面板读取
  统一改为 scan_work_dir_for_input(work_dir, 'water_mask/glint_mask/deglint_image')
- step6: 移除对 step1/2/3/5 panel 的4处跨面板依赖
- step8: 替换 _resolve_training_csv_from_workdir 为 scan_work_dir_for_input
  优先级:training_spectra_indices -> training_spectra
- step9: 替换 _resolve_latest_wqi_test_csv 为 scan_work_dir_for_input
  废弃 factory.get_panel('step4_sampling') 和 get_panel('step8_ml_train')
- step10: 替换3层回退链为 scan_work_dir_for_input('sampling_points')
- step11: 替换 factory.get_panel('step9')/('step1') 为文件扫描

=== 删除的冗余方法 ===
- step8._resolve_training_csv_from_workdir (~55行)
- step9._resolve_latest_wqi_test_csv (~56行)

=== 数据流原则 ===
1. pipeline context 为第一顺位(执行时内存状态)
2. 文件系统扫描为回退(仅信任硬盘上真实存在的文件)
3. 彻底禁止面板间 UI 控件互相读取
This commit is contained in:
duxin
2026-06-30 09:53:23 +08:00
parent 1bdd623fe7
commit 73cb019a4a
9 changed files with 194 additions and 312 deletions

View File

@ -12,7 +12,7 @@ from pathlib import Path
_HERE = os.path.dirname(os.path.abspath(__file__))
if _HERE not in sys.path:
sys.path.insert(0, _HERE)
from src.gui.panels._step_path_resolver import resolve_subdir
from src.gui.panels._step_path_resolver import resolve_subdir, scan_work_dir_for_input
from PyQt5.QtWidgets import (
QWidget, QVBoxLayout, QHBoxLayout, QGroupBox, QFormLayout,
@ -218,33 +218,22 @@ class Step6FeaturePanel(QWidget):
else:
self.work_dir = None
# 1. 水体掩膜:优先 pipeline,回退文件系统扫描
mask_path = None
if pipeline and hasattr(pipeline, 'water_mask_path') and pipeline.water_mask_path:
mask_path = pipeline.water_mask_path
if not mask_path and self.work_dir:
mask_path = scan_work_dir_for_input(self.work_dir, 'water_mask')
if mask_path and os.path.exists(str(mask_path)):
self.water_mask_file.set_path(str(mask_path))
main_window = self.window()
if not mask_path and hasattr(main_window, 'step1_panel'):
if main_window.step1_panel.use_ndwi_radio.isChecked():
mask_path = main_window.step1_panel.output_file.get_path()
else:
mask_path = main_window.step1_panel.mask_file.get_path()
if mask_path and not os.path.isabs(mask_path):
mask_path = os.path.join(self.work_dir or '', mask_path).replace('\\', '/')
if mask_path:
if not os.path.isabs(mask_path):
mask_path = os.path.join(self.work_dir or '', mask_path).replace('\\', '/')
if os.path.exists(mask_path):
self.water_mask_file.set_path(mask_path)
if hasattr(main_window, 'step2_panel'):
glint_path = main_window.step2_panel.output_file.get_path()
if glint_path:
if not os.path.isabs(glint_path):
glint_path = os.path.join(self.work_dir or '', glint_path).replace('\\', '/')
if os.path.exists(glint_path):
self.glint_mask_file.set_path(glint_path)
# 2. 耀斑掩膜:文件系统扫描
if self.work_dir:
glint_path = scan_work_dir_for_input(self.work_dir, 'glint_mask')
if glint_path and os.path.exists(str(glint_path)):
self.glint_mask_file.set_path(str(glint_path))
# 3. 去耀斑影像:优先 pipeline.step_outputs,回退文件系统扫描
deglint_path = None
if pipeline and hasattr(pipeline, 'step_outputs'):
step3_outputs = getattr(pipeline, 'step_outputs', {}).get('step3', {})
@ -254,30 +243,22 @@ class Step6FeaturePanel(QWidget):
or step3_outputs.get('output_file')
or step3_outputs.get('deglint_img_path')
)
if not deglint_path and hasattr(main_window, 'step3_panel'):
step3_widget = getattr(main_window.step3_panel, 'output_file', None)
if step3_widget is not None and hasattr(step3_widget, 'get_path'):
deglint_path = step3_widget.get_path() or ""
if not deglint_path and self.work_dir:
deglint_dir = resolve_subdir(self.work_dir, 'deglint')
if os.path.isdir(deglint_dir):
bsq_files = [
f for f in os.listdir(deglint_dir)
if f.lower().endswith('.bsq')
]
bsq_files.sort(key=lambda n: (0 if 'goodman' in n.lower() else 1, n))
if bsq_files:
deglint_path = os.path.join(deglint_dir, bsq_files[0]).replace('\\', '/')
if deglint_path:
if not os.path.isabs(deglint_path):
deglint_path = os.path.join(self.work_dir or '', deglint_path).replace('\\', '/')
deglint_path = scan_work_dir_for_input(self.work_dir, 'deglint_image')
if deglint_path and os.path.exists(str(deglint_path)):
existing_deglint = self.deglint_img_file.get_path()
if (not existing_deglint or not existing_deglint.strip()) and os.path.exists(deglint_path):
self.deglint_img_file.set_path(deglint_path)
if (not existing_deglint or not existing_deglint.strip()):
self.deglint_img_file.set_path(str(deglint_path))
# 4. 处理后 CSV:文件系统扫描
if self.work_dir:
csv_path = scan_work_dir_for_input(self.work_dir, 'processed_data')
if csv_path and os.path.exists(str(csv_path)):
existing_csv = self.csv_file.get_path()
if (not existing_csv or not existing_csv.strip()):
self.csv_file.set_path(str(csv_path))
# 5. 输出路径
if self.work_dir and not self.output_file.get_path():
output_dir = resolve_subdir(self.work_dir, 'spectral_feature')
default_output_path = os.path.join(output_dir, "training_spectra.csv").replace('\\', '/')
@ -285,17 +266,6 @@ class Step6FeaturePanel(QWidget):
elif not self.work_dir:
self.output_file.set_path("")
if main_window and hasattr(main_window, 'step5_clean_panel'):
step5_clean_output_path = main_window.step5_clean_panel.output_file.get_path()
if step5_clean_output_path:
if not os.path.isabs(step5_clean_output_path):
step5_clean_output_path = os.path.join(
self.work_dir or '', step5_clean_output_path
).replace('\\', '/')
existing_csv = self.csv_file.get_path()
if (not existing_csv or not existing_csv.strip()) and os.path.exists(step5_clean_output_path):
self.csv_file.set_path(step5_clean_output_path)
def _on_run_single_clicked(self):
from src.gui.core.event_bus import global_event_bus