路径归一化:统一 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

@ -5,10 +5,17 @@ Step14 面板 - 分布图生成
"""
import os
import sys
import traceback
from pathlib import Path
from typing import List, Optional
# 路径归一化 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
from PyQt5.QtCore import Qt, QThread, pyqtSignal
from PyQt5.QtWidgets import (
QWidget, QVBoxLayout, QGroupBox, QFormLayout, QHBoxLayout,
@ -372,7 +379,7 @@ class Step14Panel(QWidget):
def browse_prediction_csv_dir(self):
default = self._get_default_work_dir()
if default:
default = os.path.join(default, "11_12_13_predictions")
default = resolve_subdir(default, 'prediction_dir')
d = QFileDialog.getExistingDirectory(self, "选择预测结果 CSV 所在文件夹", default)
if d:
self.prediction_csv_dir_edit.setText(d)
@ -392,7 +399,7 @@ class Step14Panel(QWidget):
"""浏览 GeoTIFF 文件夹(批量模式)"""
default = self._get_default_work_dir()
if default:
default = os.path.join(default, "10_WaterIndex_Images")
default = resolve_subdir(default, 'watercolor')
d = QFileDialog.getExistingDirectory(
self, "选择水色指数 GeoTIFF 文件夹", default
)
@ -510,49 +517,35 @@ class Step14Panel(QWidget):
if not main_window:
return
# 1. 尝试从 Step8 界面读取机器学习预测输出目录(最优先)
# 1. 优先:从 Step9机器学习预测)读输出目录9_ML_Prediction 子目录
# 修复张冠李戴:原 main_window.step11_prediction_panel 不存在
pred_dir = None
if hasattr(main_window, 'step11_prediction_panel'):
step8_widget = getattr(main_window.step11_prediction_panel, 'output_file', None)
step10_output = ""
if hasattr(step8_widget, 'get_path'):
step10_output = step8_widget.get_path() or ""
elif hasattr(step8_widget, 'text'):
step10_output = step8_widget.text() or ""
if step10_output:
# 若为相对路径,使用 work_dir 合成为绝对路径
if not os.path.isabs(step10_output):
step10_output = os.path.join(self.work_dir or '', step10_output).replace('\\', '/')
# 提取父目录后追加 9_ML_Prediction最底层真实子目录
base_pred_dir = str(Path(step10_output).parent)
ml_pred_dir = Path(base_pred_dir) / "9_ML_Prediction"
pred_dir = str(ml_pred_dir) if ml_pred_dir.exists() else base_pred_dir
# 2. 备选:从 Step11 界面读取非经验预测输出目录
if not pred_dir and hasattr(main_window, 'step11_panel'):
step8_5_widget = getattr(main_window.step11_panel, 'output_file', None)
step8_5_output = ""
if hasattr(step8_5_widget, 'get_path'):
step8_5_output = step8_5_widget.get_path() or ""
elif hasattr(step8_5_widget, 'text'):
step8_5_output = step8_5_widget.text() or ""
step10_output = get_step_output_path(
main_window, 'step11_ml_prediction', work_dir=self.work_dir,
widget_attr='output_file', fallback_key='step9_ml_predict',
)
if step10_output:
base_pred_dir = str(Path(step10_output).parent)
ml_pred_dir = Path(base_pred_dir) / "9_ML_Prediction"
pred_dir = str(ml_pred_dir) if ml_pred_dir.exists() else base_pred_dir
# 2. 备选:从 Step8非经验预测读输出目录
# 修复张冠李戴:原 main_window.step11_panel 不存在
if not pred_dir:
step8_5_output = get_step_output_path(
main_window, 'step12_regression_prediction', work_dir=self.work_dir,
widget_attr='output_file', fallback_key='step8_ml_train',
)
if step8_5_output:
# 若为相对路径,使用 work_dir 合成为绝对路径
if not os.path.isabs(step8_5_output):
step8_5_output = os.path.join(self.work_dir or '', step8_5_output).replace('\\', '/')
pred_dir = str(Path(step8_5_output).parent)
# 3. 备选:从 Step12 界面读取自定义回归预测输出目录
if not pred_dir and hasattr(main_window, 'step12_panel'):
step8_75_widget = getattr(main_window.step12_panel, 'output_dir_widget', None)
step8_75_output = ""
if hasattr(step8_75_widget, 'get_path'):
step8_75_output = step8_75_widget.get_path() or ""
elif hasattr(step8_75_widget, 'text'):
step8_75_output = step8_75_widget.text() or ""
# 3. 备选:从 Step13 panel自定义回归)读输出目录
# 修复张冠李戴:原 main_window.step12_panel 不存在
if not pred_dir:
step8_75_output = get_step_output_path(
main_window, 'step13_custom_regression', work_dir=self.work_dir,
widget_attr='output_dir_widget', fallback_key='custom_regression',
)
if step8_75_output:
pred_dir = step8_75_output
@ -566,7 +559,7 @@ class Step14Panel(QWidget):
# 4. 自动填充输出目录14_visualization
if self.work_dir:
output_dir = os.path.join(self.work_dir, "14_visualization")
output_dir = resolve_subdir(self.work_dir, 'visualization')
os.makedirs(output_dir, exist_ok=True)
existing_out = self.output_dir.get_path()
if not existing_out or not existing_out.strip():
@ -612,7 +605,7 @@ class Step14Panel(QWidget):
"""浏览输出目录"""
default = self._get_default_work_dir()
if default:
default = os.path.join(default, "14_visualization")
default = resolve_subdir(default, 'visualization')
dir_path = QFileDialog.getExistingDirectory(self, "选择输出分布图目录", default)
if dir_path:
self.output_dir.set_path(dir_path)
@ -704,7 +697,7 @@ class Step14Panel(QWidget):
out_dir = (self.output_dir.get_path() or "").strip()
if not out_dir:
out_dir = os.path.join(self._get_default_work_dir(), "14_visualization")
out_dir = resolve_subdir(self._get_default_work_dir(), 'visualization')
os.makedirs(out_dir, exist_ok=True)
self.run_button.setEnabled(False)
@ -760,7 +753,7 @@ class Step14Panel(QWidget):
# 构造输出路径
out_dir = (self.output_dir.get_path() or "").strip()
if not out_dir:
out_dir = os.path.join(self._get_default_work_dir(), "14_visualization")
out_dir = resolve_subdir(self._get_default_work_dir(), 'visualization')
os.makedirs(out_dir, exist_ok=True)
tif_stem = Path(geotiff_path).stem
chinese_name = mapper._get_chinese_title(tif_stem)