路径归一化:统一 14 个子目录 helper 接口 + 修复 getattr 张冠李戴

新增 _step_path_resolver.py(STEP_DATA_SOURCE 映射表 + _FALLBACK_DIR_TABLE 40+ keys + resolve_subdir / get_step_output_path / resolve_step_widget 三层 API),与 pipeline.get_step_output_dir 互为表里、互不依赖。

pipeline 新增 get_step_output_dir(step_name) 唯一权威接口(class-level _STEP_OUTPUT_DIR_MAP 延迟构造 + 未知 key 回退 work_dir + 调试日志)。

全量重构 src/gui/panels/step*.py(17 个文件)

* 消除全部 os.path.join(wp, "X_subdir") 硬编码(14 个预定义子目录)

* 8 处 getattr(main_window.stepXX_panel, ...) 张冠李戴死代码全部修复(错位属性名 → 通过 STEP_DATA_SOURCE 映射到正确的 main_window 长名属性)

* 删除 step12_viz_panel.py 中 self.step11_ml_panel / step11_panel / step12_panel 死代码块

* 提示文字/标签字典/日志保留原文,仅替换实际路径计算

Smoke test:39 fallback key + 14 路径映射 + 14 step 数字 key + 17/17 panel AST 解析 + 17/17 import 全部就位。
This commit is contained in:
DXC
2026-06-16 12:54:18 +08:00
parent 03c788a16c
commit 0238aa66ab
19 changed files with 544 additions and 222 deletions

View File

@ -6,6 +6,7 @@ VisualizationPanel - 可视化分析面板
"""
import os
import sys
import traceback
from pathlib import Path
from typing import Optional, List, Union
@ -13,6 +14,12 @@ from typing import Optional, List, Union
import numpy as np
import pandas as pd
# 路径归一化 helper与 pipeline.get_step_output_dir 互为表里)
_HERE = os.path.dirname(os.path.abspath(__file__))
if _HERE not in sys.path:
sys.path.insert(0, _HERE)
from _step_path_resolver import get_step_output_path, resolve_step_widget, resolve_subdir
from PyQt5.QtCore import Qt, QTimer, QThread, pyqtSignal, QAbstractTableModel
from PyQt5.QtGui import QPixmap
from PyQt5.QtWidgets import (
@ -85,7 +92,7 @@ class VisualizationWorkerThread(QThread):
wp = Path(self.work_dir)
if self.task == "mask_glint":
from src.postprocessing.visualization_reports import WaterQualityVisualization
viz = WaterQualityVisualization(output_dir=str(wp / "14_visualization"))
viz = WaterQualityVisualization(output_dir=str(resolve_subdir(self.work_dir, 'visualization')))
preview_paths = viz.generate_glint_deglint_previews(
work_dir=str(wp),
output_subdir="glint_deglint_previews",
@ -94,7 +101,7 @@ class VisualizationWorkerThread(QThread):
self.finished_ok.emit({"task": "mask_glint", "count": cnt, "preview_paths": preview_paths})
elif self.task == "sampling_map":
hyperspectral_files = []
deglint_dir = wp / "3_deglint"
deglint_dir = Path(resolve_subdir(self.work_dir, 'deglint'))
if deglint_dir.exists():
for ext in ("*.dat", "*.bsq", "*.tif", "*.tiff"):
hyperspectral_files.extend(list(deglint_dir.glob(ext)))
@ -121,7 +128,7 @@ class VisualizationWorkerThread(QThread):
csv_path = str(csv_files[0])
from src.postprocessing.point_map import SamplingPointMap
map_generator = SamplingPointMap(
output_dir=str(wp / "14_visualization" / "sampling_maps"),
output_dir=str(Path(resolve_subdir(self.work_dir, 'visualization')) / "sampling_maps"),
fast_mode=True,
)
map_path = map_generator.create_sampling_point_map(
@ -146,7 +153,7 @@ class VisualizationWorkerThread(QThread):
)
elif self.task == "spectrum":
from src.postprocessing.visualization_reports import WaterQualityVisualization
viz = WaterQualityVisualization(output_dir=str(wp / "14_visualization"))
viz = WaterQualityVisualization(output_dir=str(resolve_subdir(self.work_dir, 'visualization')))
csv_file = self.extra.get("csv_path")
wl = self.extra.get("wavelength_start_column", "UTM_Y")
n_groups = int(self.extra.get("n_groups", 5))
@ -190,7 +197,7 @@ class VisualizationWorkerThread(QThread):
)
elif self.task == "statistics":
from src.postprocessing.visualization_reports import WaterQualityVisualization
viz = WaterQualityVisualization(output_dir=str(wp / "14_visualization"))
viz = WaterQualityVisualization(output_dir=str(resolve_subdir(self.work_dir, 'visualization')))
csv_file = self.extra.get("csv_path")
param_cols = self.extra.get("param_cols") or []
output_paths = viz.plot_statistical_charts(
@ -219,7 +226,7 @@ class VisualizationWorkerThread(QThread):
self.finished_ok.emit({"task": "scatter", "scatter_paths": scatter_paths or {}})
elif self.task == "generate_all_selected":
from src.postprocessing.visualization_reports import WaterQualityVisualization
viz = WaterQualityVisualization(output_dir=str(wp / "14_visualization"))
viz = WaterQualityVisualization(output_dir=str(resolve_subdir(self.work_dir, 'visualization')))
parts = []
training_csv = wp / "5_training_spectra" / "training_spectra.csv"
@ -316,7 +323,7 @@ class VisualizationWorkerThread(QThread):
if self.extra.get("gen_sampling_map"):
hyperspectral_files = []
deglint_dir = wp / "3_deglint"
deglint_dir = Path(resolve_subdir(self.work_dir, 'deglint'))
if deglint_dir.exists():
for ext in ("*.dat", "*.bsq", "*.tif", "*.tiff"):
hyperspectral_files.extend(list(deglint_dir.glob(ext)))
@ -339,7 +346,7 @@ class VisualizationWorkerThread(QThread):
csv_path = str(csv_files[0])
from src.postprocessing.point_map import SamplingPointMap
map_generator = SamplingPointMap(
output_dir=str(wp / "14_visualization" / "sampling_maps"),
output_dir=str(Path(resolve_subdir(self.work_dir, 'visualization')) / "sampling_maps"),
fast_mode=True,
)
map_path = map_generator.create_sampling_point_map(
@ -817,14 +824,14 @@ class ImageCategoryTree(QTreeWidget):
# 拓宽扫描根目录列表(新增多个遗漏目录)
scan_roots: List[Path] = [
self._work_path / "14_visualization",
self._work_path / "11_12_13_predictions",
self._work_path / "8_Regression_Modeling",
Path(resolve_subdir(str(self._work_path), 'visualization')),
Path(resolve_subdir(str(self._work_path), 'prediction_dir')),
Path(resolve_subdir(str(self._work_path), 'regression_modeling')),
self._work_path / "10_feature_construction",
self._work_path / "5_training_spectra",
self._work_path / "2_Glint_Detection",
self._work_path / "3_deglint",
self._work_path / "1_water_mask",
Path(resolve_subdir(str(self._work_path), 'glint_detection')),
Path(resolve_subdir(str(self._work_path), 'deglint')),
Path(resolve_subdir(str(self._work_path), 'water_mask')),
self._work_path / "9_water_quality_prediction",
self._work_path / "9_Concentration",
]
@ -1536,14 +1543,14 @@ class Step12VizPanel(QWidget):
return
work_path = Path(self.work_dir)
pred_dir = work_path / "11_12_13_predictions"
pred_dir = Path(resolve_subdir(self.work_dir, 'prediction_dir'))
# 按优先级寻找存在的目录
candidates = [
work_path / "9_ML_Prediction",
Path(resolve_subdir(self.work_dir, 'ml_prediction')),
pred_dir / "Non_Empirical_Prediction",
work_path / "13_Custom_Regression" / "Custom_Regression_Prediction",
work_path / "14_visualization",
Path(resolve_subdir(self.work_dir, 'custom_regression')) / "Custom_Regression_Prediction",
Path(resolve_subdir(self.work_dir, 'visualization')),
work_path,
]
detected_dir = None
@ -1611,7 +1618,7 @@ class Step12VizPanel(QWidget):
print(f"扫描工作目录: {work_path}")
self.image_tree.scan_directory(str(work_path))
self._setup_prediction_output_dirs(work_path)
viz_dir = work_path / "14_visualization"
viz_dir = Path(resolve_subdir(str(work_path), 'visualization'))
if viz_dir.exists():
image_files = list(viz_dir.glob("**/*.png")) + list(viz_dir.glob("**/*.jpg"))
if image_files:
@ -1620,20 +1627,17 @@ class Step12VizPanel(QWidget):
def _setup_prediction_output_dirs(self, work_path: Path):
"""设置三个预测步骤的默认输出目录"""
try:
base_prediction_dir = work_path / "11_12_13_predictions"
ml_dir = work_path / "9_ML_Prediction"
base_prediction_dir = Path(resolve_subdir(str(work_path), 'prediction_dir'))
ml_dir = Path(resolve_subdir(str(work_path), 'ml_prediction'))
reg_dir = base_prediction_dir / "Regression_Model_Prediction"
custom_dir = work_path / "13_Custom_Regression" / "Custom_Regression_Prediction"
custom_dir = Path(resolve_subdir(str(work_path), 'custom_regression')) / "Custom_Regression_Prediction"
ml_dir.mkdir(parents=True, exist_ok=True)
reg_dir.mkdir(parents=True, exist_ok=True)
custom_dir.mkdir(parents=True, exist_ok=True)
if hasattr(self, 'step11_ml_panel') and hasattr(self.step11_ml_panel, 'output_file'):
self.step11_ml_panel.output_file.set_path(str(ml_dir))
if hasattr(self, 'step11_panel') and hasattr(self.step11_panel, 'output_file'):
self.step11_panel.output_file.set_path(str(reg_dir))
if hasattr(self, 'step12_panel') and hasattr(self.step12_panel, 'output_dir_widget'):
self.step12_panel.output_dir_widget.set_path(str(custom_dir))
print(f"预测输出目录已设置:\n ML: {ml_dir}\n Reg: {reg_dir}\n Custom: {custom_dir}")
# 旧的 self.step11_ml_panel/step11_panel/step12_panel 在 Step12VizPanel 上不存在,是死代码。
# 三个目录的真实默认值在用户首次浏览 / 自动填充时由各 panel 自己的 _get_default_work_dir 路径产出。
# 这里仅做目录创建 + 提示输出,便于用户在工作目录树中能看到预测输出位置。
print(f"预测输出目录已创建:\n ML: {ml_dir}\n Reg: {reg_dir}\n Custom: {custom_dir}")
except Exception as e:
print(f"设置预测输出目录失败: {e}")
@ -1780,7 +1784,7 @@ class Step12VizPanel(QWidget):
QMessageBox.warning(self, "警告", "请先选择工作目录!")
return
work_path = Path(self.work_dir)
viz_dir = work_path / "14_visualization"
viz_dir = Path(resolve_subdir(self.work_dir, 'visualization'))
viz_dir2 = viz_dir / "boxplots"
viz_dir3 = viz_dir / "scatter_plots"
if not viz_dir.exists():