fix: 全局UX修复与Step4交互可视化重构

=== 自动填入过于激进(幽灵路径级联)===
- 所有面板 update_from_config 移除 os.makedirs(),目录创建留给 pipeline 执行
- 输出路径仅 widget 为空时填入默认值,不覆盖用户已选
- 输入路径从上游读取后添加 os.path.exists() 检查,阻断幽灵路径级联
- panel_factory._replay_live_panel_inputs 广播前校验文件确实存在
- step10 update_from_config 添加 os.path.isfile() 存在性检查
- 清理 step8/9/11 中冗余局部 import os(修复 UnboundLocalError)

=== 输出目录缺失 ===
- step7 新增 output_file FileSelectWidget,默认路径 7_Water_Quality_Indices/
- step9 output_file 从文件模式改为目录模式 (Directories)

=== 空目录自动创建 ===
- step12 _setup_prediction_output_dirs 移除 mkdir() 调用,改为只读日志

=== 过期依赖与缺失 import ===
- panel_registry Step10 依赖 bsq_file→sampling_csv_file(匹配 CSV 模式重构)
- step7 添加缺失的 import pandas as pd(修复 NameError)

=== 导航与 UI 一致性 ===
- water_quality_gui_v2 新增 _select_first_nav_item(),启动时默认选中第一项
- step1 输出卡片对齐 step8 风格
- step8 补充缺失的样式表和统一边距

=== Step4 交互式光谱探针重构 ===
- 左右分栏 QSplitter 布局:左侧控制区 + 右侧 Matplotlib 视图
- 1x2 子图:ax1 散点图 + ax2 光谱曲线
- Hover 悬停 Annotation 显示坐标,Click 点击高亮+绘制光谱
- NavigationToolbar2QT 工具栏(保存/缩放/平移)
- 自动检测坐标列和波段列,完善异常处理
This commit is contained in:
duxin
2026-06-30 09:38:27 +08:00
parent e337f01312
commit 48d17ef0ca
20 changed files with 910 additions and 201 deletions

View File

@ -183,26 +183,35 @@ class WorkspaceManager:
for file_path in subdir_path.rglob('*'):
if file_path.is_file():
file_name = file_path.name.lower()
file_stem = file_path.stem.lower()
for step_id in step_ids:
if step_id not in discovered_outputs:
discovered_outputs[step_id] = {}
if 'water_mask' in file_name and step_id == 'step1':
if self._is_scientific_mask(file_path):
# 2026-06-30 加强匹配:用更精确的边界匹配替代简单的子串包含
# water_mask 匹配:必须是独立文件名的一部分(如 water_mask_out.dat, water_mask_from_ndwi.dat)
if step_id == 'step1' and self._is_scientific_mask(file_path):
if ('water_mask' in file_stem
and 'glint' not in file_stem):
discovered_outputs[step_id]['water_mask'] = str(file_path)
elif 'glint' in file_name and 'mask' in file_name and step_id == 'step2':
if self._is_scientific_mask(file_path):
# glint_mask 匹配:必须同时含 glint 且文件名主体以 mask 或 area 结尾
elif step_id == 'step2' and self._is_scientific_mask(file_path):
if ('glint' in file_stem and
(file_stem.endswith('mask') or file_stem.endswith('area')
or 'severe_glint' in file_stem)):
discovered_outputs[step_id]['glint_mask'] = str(file_path)
elif 'deglint' in file_name and step_id == 'step3':
elif 'deglint' in file_stem and step_id == 'step3':
discovered_outputs[step_id]['deglint_image'] = str(file_path)
elif 'processed_data' in file_name and step_id == 'step4_sampling':
elif file_name == 'processed_data.csv' and step_id == 'step5_clean':
discovered_outputs[step_id]['processed_data'] = str(file_path)
elif 'training_spectra' in file_name and step_id == 'step5_clean':
elif ('training_spectra' in file_stem and file_path.suffix == '.csv'
and step_id == 'step6_feature'):
discovered_outputs[step_id]['training_spectra'] = str(file_path)
elif 'water_quality_indices' in file_name and step_id == 'step6_feature':
elif ('water_quality_indices' in file_stem and file_path.suffix == '.csv'
and step_id == 'step7_index'):
discovered_outputs[step_id]['water_indices'] = str(file_path)
elif 'sampling_spectra' in file_name and step_id == 'step4_sampling':
elif file_name == 'sampling_spectra.csv' and step_id == 'step4_sampling':
discovered_outputs[step_id]['sampling_points'] = str(file_path)
elif file_name.endswith('.csv') and step_id in ['step9_ml_predict', 'step11_map', 'step12_viz']:
discovered_outputs[step_id]['predictions'] = str(file_path)
@ -255,13 +264,16 @@ class WorkspaceManager:
def prune_config_for_prediction_mode(config: dict) -> dict:
"""Prediction-only 模式:禁用训练相关步骤,保留预测和成图步骤。
2026-06-30 修复:步骤 ID 从旧的 PIPELINE_STEPS 体系改为 PANEL_REGISTRY 体系,
确保与 get_current_config() 返回的 key 一致,避免 enabled: False 写入无效 key。
被禁用的 step dict 中统一写入 'enabled': False,
这些配置最终传给 PipelineRunner,Runner 会跳过它们。
同时,被跳过的步骤的 required_input_files 在 build_missing_items
中不会被检查,从而自然规避了"CSV 缺失"等训练模式下的误报。
Args:
config: 完整配置字典(来自 get_current_config)
config: 完整配置字典(来自 get_current_config,key 为 PANEL_REGISTRY step_id)
Returns:
裁剪后的 config(深拷贝,原 config 不被修改)
@ -269,12 +281,11 @@ class WorkspaceManager:
cfg = copy.deepcopy(config)
training_steps = [
"step4",
"step5",
"step7",
"step6",
"step8_non_empirical_modeling",
"step9",
"step4_sampling",
"step5_clean",
"step6_feature",
"step7_index",
"step8_ml_train",
]
for step_id in training_steps:
step_cfg = cfg.setdefault(step_id, {})