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

@ -186,6 +186,70 @@ class WaterQualityInversionPipeline:
print(f"工作目录已创建: {self.work_dir}")
# ---- 步骤输出目录查找接口(归一化所有 panel 的路径访问)----
# 用户口语编号 → 权威子目录对象 的映射
# 同时支持 "stepN"、"stepN_alias"、"subdir名" 三种 key 形式查找
_STEP_OUTPUT_DIR_MAP = None # 延迟到首次访问时构造
def _ensure_step_dir_map(self):
"""延迟构造 step_name → 目录对象 映射表(首次访问时执行)"""
if WaterQualityInversionPipeline._STEP_OUTPUT_DIR_MAP is not None:
return WaterQualityInversionPipeline._STEP_OUTPUT_DIR_MAP
wp = self.work_dir
m = {
# 基础步骤
"step1": wp / "1_water_mask",
"step2": wp / "2_Glint_Detection",
"step3": wp / "3_deglint",
"step4_sampling": wp / "4_sampling",
"step5_clean": wp / "5_Data_Cleaning",
"step6_feature": wp / "6_Spectral_Feature_Extraction",
"step7_index": wp / "7_Water_Quality_Indices",
"step8_ml_train": wp / "8_Supervised_Model_Training",
"step9_ml_predict": wp / "8_Non_Empirical_Regression",
"step10_watercolor": wp / "10_WaterIndex_Images",
"step11_map": wp / "14_visualization",
"step12_viz": wp / "14_visualization",
"step13_report": wp / "14_visualization",
# 合并目录(提供单一访问点,避免分散硬编码)
"step11_predictions": wp / "11_12_13_predictions",
"step12_predictions": wp / "11_12_13_predictions",
"step13_predictions": wp / "11_12_13_predictions",
"custom_regression": wp / "13_Custom_Regression",
"prediction_dir": wp / "11_12_13_predictions",
"visualization": wp / "14_visualization",
"reports": wp / "reports",
# 兼容主流程 step_id数字+短名)
"step8": wp / "8_Supervised_Model_Training",
"step9": wp / "8_Non_Empirical_Regression",
"step10": wp / "10_WaterIndex_Images",
"step11": wp / "11_12_13_predictions",
"step12": wp / "13_Custom_Regression",
"step13": wp / "reports",
"step14": wp / "14_visualization",
}
WaterQualityInversionPipeline._STEP_OUTPUT_DIR_MAP = m
return m
def get_step_output_dir(self, step_name: str):
"""根据步骤名称返回权威输出目录 Path 对象。
这是 panel 端访问子目录的**唯一**入口。接收以下任意形式 key:
- 完整 panel 属性名: "step11_map", "step12_viz", "step8_ml_train"
- 主流程 step_id: "step8"~"step14"
- 业务别名: "prediction_dir", "visualization", "reports", "custom_regression"
- 兼容口语: "step11_predictions" (=11_12_13_predictions)
未知 key 一律回退到 work_dir 本身,并打 warning。
"""
mapping = self._ensure_step_dir_map()
key = (step_name or "").strip()
if key in mapping:
return mapping[key]
print(f"[pipeline.get_step_output_dir] 未知 step_name={key!r},回退到 work_dir")
return self.work_dir
def set_callback(self, callback):
"""
设置回调函数用于向GUI报告进度

View File

@ -0,0 +1,186 @@
# -*- coding: utf-8 -*-
"""
Step 路径解析器——统一消灭 panel 端的硬编码路径与"张冠李戴"跨面板引用。
提供三个公共 API
- resolve_step_widget(main_window, step_key)
- get_step_output_path(main_window, step_key, work_dir=None)
- STEP_DATA_SOURCE 映射表
典型使用:
from src.gui.panels._step_path_resolver import (
resolve_step_widget, get_step_output_path
)
# 替换前getattr(main_window.step11_panel, 'output_file', None) # 死代码
# 替换后:
widget = resolve_step_widget(main_window, 'step11_predictions') # 找到正确 widget
if widget: ...
"""
from pathlib import Path
from typing import Optional, Union
# 用户口语编号 / 业务别名 → main_window 上真实属性名的映射
# 这是"张冠李戴"修复的核心——之前代码写的 step11_panel 实际不存在,
# 真实存在的属性见 water_quality_gui.py:1891-1928
STEP_DATA_SOURCE = {
# 数据流 step 编号(用户口语) → main_window 真实属性
'step5_clean_output': 'step5_clean_panel',
'step7_index_output': 'step7_index_panel',
'step8_ml_train_output': 'step8_ml_train_panel',
'step8_5_non_empirical': 'step8_non_empirical_panel', # 之前写错成 step11_panel
'step9_ml_predict_output': 'step9_ml_predict_panel',
'step10_watercolor_output': 'step10_watercolor_panel',
'step11_ml_prediction': 'step9_ml_predict_panel', # 主流程 step11 = ML 预测
'step12_regression_prediction': 'step8_non_empirical_panel', # 主流程 step12 = 非经验预测
'step13_custom_regression': 'step13_report_panel', # 占位(自定义回归本身没有专属 panel
'sampling_csv': 'step4_sampling_panel',
'training_spectra_csv': 'step5_clean_panel',
'indices_csv': 'step7_index_panel',
'models_dir': 'step8_ml_train_panel',
'watercolor_dir': 'step10_watercolor_panel',
'prediction_csv_dir': 'step9_ml_predict_panel', # 默认从 ML 预测读
}
def _get_widget(main_window, attr_name: str, widget_attr: str = 'output_file'):
"""从 main_window.<attr_name> 取出指定子组件,失败时返回 None。"""
if main_window is None:
return None
panel = getattr(main_window, attr_name, None)
if panel is None:
return None
return getattr(panel, widget_attr, None)
def _read_widget_path(widget) -> str:
"""统一从 widget 读 path兼容 FileSelectWidget / QLineEdit / 字符串)。"""
if widget is None:
return ""
if hasattr(widget, 'get_path'):
try:
return str(widget.get_path() or "").strip()
except Exception:
return ""
if hasattr(widget, 'text'):
try:
return str(widget.text() or "").strip()
except Exception:
return ""
if isinstance(widget, str):
return widget.strip()
return ""
def resolve_step_widget(main_window, step_key: str, widget_attr: str = 'output_file'):
"""根据业务 step_key 解析出正确的 widget消除张冠李戴
Returns:
widget 对象 or None找不到时返回 None调用方需自行兜底
"""
attr_name = STEP_DATA_SOURCE.get(step_key)
if attr_name is None:
return None
return _get_widget(main_window, attr_name, widget_attr)
_FALLBACK_DIR_TABLE = {
# pipeline key与 _ensure_step_dir_map 对齐)→ 子目录名
'step1': '1_water_mask',
'step2': '2_Glint_Detection',
'step3': '3_deglint',
'step4_sampling': '4_sampling',
'step5_clean': '5_Data_Cleaning',
'step6_feature': '6_Spectral_Feature_Extraction',
'step7_index': '7_Water_Quality_Indices',
'step8_ml_train': '8_Supervised_Model_Training',
'step8': '8_Supervised_Model_Training',
'step9_ml_predict': '8_Non_Empirical_Regression',
'step9': '8_Non_Empirical_Regression',
'step10_watercolor': '10_WaterIndex_Images',
'step10': '10_WaterIndex_Images',
'step11_map': '14_visualization',
'step11': '11_12_13_predictions',
'step11_predictions': '11_12_13_predictions',
'step12': '13_Custom_Regression',
'step12_predictions': '11_12_13_predictions',
'step13': 'reports',
'step13_predictions': '11_12_13_predictions',
'step14': '14_visualization',
'prediction_dir': '11_12_13_predictions',
'visualization': '14_visualization',
'reports': 'reports',
'custom_regression': '13_Custom_Regression',
# 扩展:覆盖 panel 内部使用的子目录别名
'water_mask': '1_water_mask',
'glint_detection': '2_Glint_Detection',
'deglint': '3_deglint',
'sampling': '4_sampling',
'data_cleaning': '5_Data_Cleaning',
'spectral_feature': '6_Spectral_Feature_Extraction',
'indices': '7_Water_Quality_Indices',
'supervised_models': '8_Supervised_Model_Training',
'non_empirical': '8_Non_Empirical_Regression',
'qaa_inversion': '8_QAA_Inversion',
'regression_modeling': '8_Regression_Modeling',
'watercolor': '10_WaterIndex_Images',
'ml_prediction': '9_ML_Prediction',
'sampling_csv_path': '4_sampling/sampling_spectra.csv',
}
def get_step_output_path(
main_window,
step_key: str,
work_dir: Optional[Union[str, Path]] = None,
widget_attr: str = 'output_file',
fallback_key: Optional[str] = None,
) -> str:
"""获取 step_key 指向的输出路径(带 main_window 解析 + 兜底路径)。
解析顺序:
1. STEP_DATA_SOURCE[step_key] 找到对应 panel从 widget 读用户填的 path
2. 若为空字符串,用 _FALLBACK_DIR_TABLE[fallback_key or step_key] + work_dir 拼兜底
3. 全失败返回 str(work_dir)
注意:不创建 pipeline 实例(避免触发 osgeo 导入),用本地子目录字典兜底。
"""
wd = str(work_dir) if work_dir else ""
widget = resolve_step_widget(main_window, step_key, widget_attr)
p = _read_widget_path(widget)
if p:
if not Path(p).is_absolute() and wd:
p = str(Path(wd) / p).replace('\\', '/')
return p
# 兜底:本地子目录字典(与 pipeline._ensure_step_dir_map 一致)
key = fallback_key or step_key
sub = _FALLBACK_DIR_TABLE.get(key)
if sub and wd:
return str(Path(wd) / sub).replace('\\', '/')
return wd
def resolve_subdir(work_dir, subdir_key: str) -> str:
"""纯子目录拼装:把 pipeline key 解析为 work_dir 下的子目录路径。
用法resolve_subdir(self.work_dir, 'visualization')
'<work_dir>/14_visualization'
与 pipeline.get_step_output_dir 同源(都查同一份 _FALLBACK_DIR_TABLE 子集)。
"""
wd = str(work_dir) if work_dir else ""
sub = _FALLBACK_DIR_TABLE.get(subdir_key)
if sub and wd:
return str(Path(wd) / sub).replace('\\', '/')
return wd
__all__ = [
'STEP_DATA_SOURCE',
'resolve_step_widget',
'get_step_output_path',
'resolve_subdir',
]

View File

@ -8,10 +8,17 @@ Step10 面板 - 水色指数反演(直接处理去耀斑 BSQ 影像)
"""
import os
import sys
import traceback
from pathlib import Path
from typing import Dict, 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 resolve_subdir
from PyQt5.QtWidgets import (
QWidget, QVBoxLayout, QHBoxLayout, QGridLayout, QFormLayout,
QGroupBox, QLabel, QLineEdit, QComboBox, QCheckBox, QPushButton,
@ -451,7 +458,7 @@ class Step10WatercolorPanel(QWidget):
# 3. 终极回退:智能扫描 3_deglint 目录,取最新的 .bsq 或 .dat 文件
if not deglint_path and self.work_dir:
deglint_dir = os.path.join(self.work_dir, "3_deglint")
deglint_dir = resolve_subdir(self.work_dir, 'deglint')
if os.path.isdir(deglint_dir):
import glob
candidates = glob.glob(os.path.join(deglint_dir, "*.bsq")) + glob.glob(os.path.join(deglint_dir, "*.dat"))
@ -472,7 +479,7 @@ class Step10WatercolorPanel(QWidget):
# 自动填入输出目录
if self.work_dir:
out_dir = os.path.join(self.work_dir, "10_WaterIndex_Images").replace('\\', '/')
out_dir = resolve_subdir(self.work_dir, 'watercolor')
os.makedirs(out_dir, exist_ok=True)
if not self.output_dir.get_path():
self.output_dir.set_path(out_dir)
@ -503,7 +510,7 @@ class Step10WatercolorPanel(QWidget):
return
if not output_dir:
work_dir = self._get_default_work_dir()
output_dir = os.path.join(work_dir, "10_WaterIndex_Images").replace('\\', '/')
output_dir = resolve_subdir(work_dir, 'watercolor')
os.makedirs(output_dir, exist_ok=True)
self.output_dir.set_path(output_dir)
@ -518,7 +525,7 @@ class Step10WatercolorPanel(QWidget):
# ── 自动扫描工作目录下的水域掩膜文件 ────────────────────────────
work_dir = self.work_dir or str(Path(bsq_path).parent)
mask_dir = os.path.join(work_dir, "1_water_mask")
mask_dir = resolve_subdir(work_dir, 'water_mask')
water_mask_path: Optional[str] = None
if os.path.isdir(mask_dir):
# ★★★ glob 智能扫描:取任意 .dat 或 .tif 文件 ★★★

View File

@ -5,10 +5,17 @@ Step10 面板 - 专题图生成
"""
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 resolve_subdir
from PyQt5.QtCore import Qt, QThread, pyqtSignal
from PyQt5.QtWidgets import (
QWidget, QVBoxLayout, QGroupBox, QFormLayout, QHBoxLayout,
@ -372,7 +379,7 @@ class Step11MapPanel(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 Step11MapPanel(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,36 @@ class Step11MapPanel(QWidget):
if not main_window:
return
# 1. 尝试从 Step8 界面读取机器学习预测输出目录(最优先)
# 1. 优先:从 Step9机器学习预测)读输出目录9_ML_Prediction 子目录
# 修复张冠李戴:原 main_window.step11_prediction_panel 不存在,真实属性是 step9_ml_predict_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 ""
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:
# 若为相对路径,使用 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 ""
# 2. 备选:从 Step8非经验预测)读输出目录
# 修复张冠李戴:原 main_window.step11_panel 不存在,真实属性是 step8_non_empirical_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 不存在;自定义回归 panel 是 step13_panel 类main_window 上无此名)
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 +560,7 @@ class Step11MapPanel(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 +606,7 @@ class Step11MapPanel(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 +698,7 @@ class Step11MapPanel(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 +754,7 @@ class Step11MapPanel(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)

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():

View File

@ -5,6 +5,14 @@ Step12 面板 - 自定义回归预测
"""
import os
import sys
from pathlib import Path
# 路径归一化 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.QtWidgets import (
QWidget, QVBoxLayout, QGroupBox,
@ -94,37 +102,24 @@ class Step12Panel(QWidget):
main_window = self.window()
# 1. 尝试从 Step7 界面读取全湖采样点 CSV 路径
if main_window and hasattr(main_window, 'step7_panel'):
step7_widget = getattr(main_window.step7_panel, 'output_file', None)
step7_output_path = ""
if hasattr(step7_widget, 'get_path'):
step7_output_path = step7_widget.get_path() or ""
elif hasattr(step7_widget, 'text'):
step7_output_path = step7_widget.text() or ""
# 1. 尝试从 Step7(水质光谱指数)界面读取全湖采样点 CSV 路径
# 修复张冠李戴:原 main_window.step7_panel 不存在,真实属性是 step7_index_panel
step7_output_path = get_step_output_path(
main_window, 'sampling_csv', work_dir=self.work_dir,
widget_attr='output_file', fallback_key='step7_index',
)
if step7_output_path:
# 若为相对路径,使用 work_dir 合成为绝对路径
if not os.path.isabs(step7_output_path):
step7_output_path = os.path.join(self.work_dir or '', step7_output_path).replace('\\', '/')
existing = self.sampling_csv_file.get_path()
if not existing or not existing.strip():
self.sampling_csv_file.set_path(step7_output_path)
# 2. 尝试从 Step9 界面读取自定义回归模型目录
if main_window and hasattr(main_window, 'step12_panel'):
step9_widget = getattr(main_window.step9_panel, 'output_dir', None)
step9_models_dir = ""
if hasattr(step9_widget, 'get_path'):
step9_models_dir = step9_widget.get_path() or ""
elif hasattr(step9_widget, 'text'):
step9_models_dir = step9_widget.text() or ""
step9_models_dir = step9_models_dir.strip()
# 2. 尝试从 Step8非经验回归/自定义回归源)读取模型目录
# 修复张冠李戴:原 main_window.step12_panel 不存在;按代码原意是 step9 的 output_dir
step9_models_dir = get_step_output_path(
main_window, 'models_dir', work_dir=self.work_dir,
widget_attr='output_dir', fallback_key='step8_ml_train',
)
if step9_models_dir:
# 若为相对路径,使用 work_dir 合成为绝对路径
if not os.path.isabs(step9_models_dir):
step9_models_dir = os.path.join(self.work_dir or '', step9_models_dir).replace('\\', '/')
existing_models = self.regression_models_dir.get_path()
if not existing_models or not existing_models.strip():
self.regression_models_dir.set_path(step9_models_dir)
@ -133,12 +128,12 @@ class Step12Panel(QWidget):
if self.work_dir:
models_dir = self.regression_models_dir.get_path().strip()
if not models_dir:
default_models_dir = os.path.join(self.work_dir, "13_Custom_Regression").replace('\\', '/')
default_models_dir = resolve_subdir(self.work_dir, 'custom_regression')
self.regression_models_dir.set_path(default_models_dir)
# 4. 自动填充输出目录(自定义回归预测目录)
if self.work_dir:
output_dir = os.path.join(self.work_dir, "13_Custom_Regression/Custom_Regression_Prediction")
output_dir = os.path.join(resolve_subdir(self.work_dir, 'custom_regression'), "Custom_Regression_Prediction")
os.makedirs(output_dir, exist_ok=True)
existing_out = self.output_dir_widget.get_path()
if not existing_out or not existing_out.strip():
@ -161,7 +156,7 @@ class Step12Panel(QWidget):
"""浏览回归模型目录"""
default = self._get_default_work_dir()
if default:
default = os.path.join(default, "13_Custom_Regression")
default = resolve_subdir(default, 'custom_regression')
dir_path = QFileDialog.getExistingDirectory(self, "选择回归模型目录", default)
if dir_path:
self.regression_models_dir.set_path(dir_path)

View File

@ -5,10 +5,17 @@ ReportGenerationPanel - Word 分析报告生成面板
"""
import os
import sys
import traceback
from pathlib import Path
from typing import 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 resolve_subdir
from PyQt5.QtCore import Qt, QThread, pyqtSignal, QSettings
from PyQt5.QtWidgets import (
QWidget, QVBoxLayout, QHBoxLayout, QGroupBox, QFormLayout,
@ -206,7 +213,7 @@ class Step13ReportPanel(QWidget):
def browse_output_dir(self):
default = self._get_default_work_dir()
if default:
default = os.path.join(default, "14_visualization")
default = resolve_subdir(default, 'visualization')
d = QFileDialog.getExistingDirectory(self, "选择报告输出目录", default)
if d:
self.output_dir_edit.setText(d)

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 ""
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:
# 若为相对路径,使用 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 ""
# 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)

View File

@ -5,6 +5,14 @@ Step1 面板 - 水域掩膜生成
"""
import os
import sys
from pathlib import Path
# 路径归一化 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 resolve_subdir
from PyQt5.QtWidgets import (
QWidget, QVBoxLayout, QHBoxLayout, QGroupBox, QLabel,
@ -197,8 +205,8 @@ class Step1Panel(QWidget):
if not hasattr(self, 'work_dir') or not self.work_dir:
return
# 生成输出掩膜的完整路径
output_dir = os.path.join(self.work_dir, "1_water_mask")
# 生成输出掩膜的完整路径(用 resolve_subdir 归一化,消除硬编码)
output_dir = resolve_subdir(self.work_dir, 'water_mask')
os.makedirs(output_dir, exist_ok=True) # 确保目录存在
# 统一使用正斜杠,避免 \ 和 / 混用

View File

@ -5,6 +5,14 @@ Step2 面板 - 耀斑区域识别
"""
import os
import sys
from pathlib import Path
# 路径归一化 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 resolve_subdir
from PyQt5.QtWidgets import (
QWidget, QVBoxLayout, QGroupBox, QFormLayout,
@ -187,7 +195,7 @@ class Step2Panel(QWidget):
# 3. 自动填充输出路径(基于工作目录)
if self.work_dir:
# 生成输出耀斑掩膜的标准路径workspace/2_Glint_Detection/severe_glint_area.dat
output_dir = os.path.join(self.work_dir, "2_Glint_Detection")
output_dir = resolve_subdir(self.work_dir, 'glint_detection')
os.makedirs(output_dir, exist_ok=True)
default_output_path = os.path.join(output_dir, "severe_glint_area.dat").replace('\\', '/')
self.output_file.set_path(default_output_path)

View File

@ -5,6 +5,14 @@ Step3 面板 - 耀斑去除
"""
import os
import sys
from pathlib import Path
# 路径归一化 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 resolve_subdir
from PyQt5.QtWidgets import (
QWidget, QVBoxLayout, QGroupBox, QFormLayout,
@ -299,7 +307,7 @@ class Step3Panel(QWidget):
# 自动填充输出路径(基于工作目录)
if self.work_dir:
output_dir = os.path.join(self.work_dir, "3_deglint")
output_dir = resolve_subdir(self.work_dir, 'deglint')
os.makedirs(output_dir, exist_ok=True)
default_output_path = os.path.join(output_dir, "deglint_image.bsq").replace('\\', '/')
self.output_file.set_path(default_output_path)

View File

@ -5,6 +5,14 @@ Step4 面板 - 采样点布设
"""
import os
import sys
from pathlib import Path
# 路径归一化 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 resolve_subdir
from PyQt5.QtCore import QTimer
from PyQt5.QtWidgets import (
@ -186,7 +194,7 @@ class Step4SamplingPanel(QWidget):
water_mask_path = main_window.step1_panel.output_file.get_path()
# 备选:扫描 1_water_mask 目录下的 .dat 文件
if not water_mask_path and self.work_dir:
mask_dir = os.path.join(self.work_dir, "1_water_mask")
mask_dir = resolve_subdir(self.work_dir, 'water_mask')
if os.path.isdir(mask_dir):
dat_files = [f for f in os.listdir(mask_dir) if f.lower().endswith('.dat')]
if dat_files:
@ -213,7 +221,7 @@ class Step4SamplingPanel(QWidget):
# 3. 自动填充输出路径(绝对路径)
if self.work_dir:
output_path = os.path.join(self.work_dir, "4_sampling", "sampling_spectra.csv")
output_path = resolve_subdir(self.work_dir, 'sampling_csv_path')
os.makedirs(os.path.dirname(output_path), exist_ok=True)
self.output_file.set_path(output_path.replace('\\', '/'))

View File

@ -5,6 +5,14 @@ Step4 面板 - 数据预处理
"""
import os
import sys
from pathlib import Path
# 路径归一化 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 resolve_subdir
import pandas as pd
from PyQt5.QtWidgets import (
@ -127,7 +135,7 @@ class Step5CleanPanel(QWidget):
self.work_dir = None
if self.work_dir:
output_dir = os.path.join(self.work_dir, "5_Data_Cleaning")
output_dir = resolve_subdir(self.work_dir, 'data_cleaning')
os.makedirs(output_dir, exist_ok=True)
default_output_path = os.path.join(output_dir, "processed_data.csv").replace('\\', '/')
self.output_file.set_path(default_output_path)

View File

@ -5,6 +5,14 @@ Step6 面板 - 光谱特征提取
"""
import os
import sys
from pathlib import Path
# 路径归一化 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 resolve_subdir
from PyQt5.QtWidgets import (
QWidget, QVBoxLayout, QGroupBox, QFormLayout, QLabel,
@ -193,7 +201,7 @@ class Step6FeaturePanel(QWidget):
# 4. 自动填充输出路径(基于工作目录)
if self.work_dir:
output_dir = os.path.join(self.work_dir, "6_Spectral_Feature_Extraction")
output_dir = resolve_subdir(self.work_dir, 'spectral_feature')
os.makedirs(output_dir, exist_ok=True)
default_output_path = os.path.join(output_dir, "training_spectra.csv").replace('\\', '/')
self.output_file.set_path(default_output_path)

View File

@ -10,6 +10,12 @@ import pandas as pd
from pathlib import Path
from typing import Dict, 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 resolve_subdir
from PyQt5.QtWidgets import (
QWidget, QVBoxLayout, QGroupBox, QGridLayout,
QHBoxLayout, QLabel, QCheckBox, QPushButton, QMessageBox,
@ -261,7 +267,7 @@ class Step7IndexPanel(QWidget):
work_dir = self._get_work_dir()
if work_dir:
track_a_dir = os.path.join(work_dir, "7_Water_Quality_Indices")
track_a_dir = resolve_subdir(work_dir, 'indices')
os.makedirs(track_a_dir, exist_ok=True)
config['output_file'] = os.path.join(track_a_dir, "training_spectra_indices.csv").replace('\\', '/')

View File

@ -5,6 +5,14 @@ Step8 面板 - 机器学习建模
"""
import os
import sys
from pathlib import Path
# 路径归一化 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.QtWidgets import (
QWidget, QVBoxLayout, QGroupBox, QFormLayout, QGridLayout,
@ -271,7 +279,7 @@ class Step8MlTrainPanel(QWidget):
if not initial_dir or not os.path.isdir(initial_dir):
# 默认定位到 indices 目录
work_dir = self._get_default_work_dir()
initial_dir = os.path.join(work_dir, "7_Water_Quality_Indices") if work_dir else ""
initial_dir = resolve_subdir(work_dir, 'indices') if work_dir else ""
if initial_dir and not os.path.isdir(initial_dir):
os.makedirs(initial_dir, exist_ok=True)
@ -350,18 +358,19 @@ class Step8MlTrainPanel(QWidget):
self.work_dir = None
# 1. 尝试从 Step5 界面读取训练数据路径,并确保为绝对路径
# 修复张冠李戴:原代码 main_window.step5_panel 不存在,正确属性是 step5_clean_panel
main_window = self.window()
if hasattr(main_window, 'step5_panel'):
# 优先直接从 Step5 的输出 widget 读取
step5_output = main_window.step5_panel.output_file.get_path()
step5_output = get_step_output_path(
main_window, 'training_spectra_csv', work_dir=self.work_dir,
widget_attr='output_file', fallback_key='step6_feature',
)
if step5_output:
# 若为相对路径,使用 work_dir 合成为绝对路径
if not os.path.isabs(step5_output):
step5_output = os.path.join(self.work_dir or '', step5_output).replace('\\', '/')
self.training_csv_file.set_path(step5_output)
elif hasattr(main_window, 'step5_panel') and hasattr(main_window.step5_panel, 'get_config'):
else:
# 回退:从 Step5 的 config 字典中查找可能的键名
step5_cfg = main_window.step5_panel.get_config()
step5_panel = getattr(main_window, 'step5_clean_panel', None)
if step5_panel and hasattr(step5_panel, 'get_config'):
step5_cfg = step5_panel.get_config()
step5_csv = (
step5_cfg.get('training_csv_path')
or step5_cfg.get('output_file')
@ -369,7 +378,6 @@ class Step8MlTrainPanel(QWidget):
or step5_cfg.get('output_csv')
)
if step5_csv:
# 若为相对路径,使用 work_dir 合成为绝对路径
if not os.path.isabs(step5_csv):
step5_csv = os.path.join(self.work_dir or '', step5_csv).replace('\\', '/')
self.training_csv_file.set_path(step5_csv)
@ -378,7 +386,7 @@ class Step8MlTrainPanel(QWidget):
# 输入是 training_spectra.csv → 输出 {work_dir}/7_Water_Quality_Indices/training_spectra_indices.csv
# 输入是 sampling_spectra.csv → 输出 {work_dir}/7_Water_Quality_Indices/sampling_spectra_indices.csv
if self.work_dir:
indices_dir = os.path.join(self.work_dir, "7_Water_Quality_Indices")
indices_dir = resolve_subdir(self.work_dir, 'indices')
os.makedirs(indices_dir, exist_ok=True)
training_csv = self.training_csv_file.get_path()
if training_csv:

View File

@ -5,8 +5,15 @@ Step8 面板 - 非经验统计回归建模
"""
import os
import sys
from pathlib import Path
# 路径归一化 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.QtWidgets import (
QWidget, QVBoxLayout, QGroupBox, QFormLayout, QGridLayout,
QHBoxLayout, QLabel, QCheckBox, QSpinBox, QPushButton,
@ -231,25 +238,19 @@ class Step8NonEmpiricalPanel(QWidget):
# 借用父组件的 window() 方法,安全绕过当前类的命名冲突
parent_widget = self.parentWidget()
main_window = parent_widget.window() if parent_widget else None
if main_window and hasattr(main_window, 'step5_panel'):
step5_widget = getattr(main_window.step5_panel, 'output_file', None)
step5_output_path = ""
if hasattr(step5_widget, 'get_path'):
step5_output_path = step5_widget.get_path() or ""
elif hasattr(step5_widget, 'text'):
step5_output_path = step5_widget.text() or ""
# 1. 尝试从 Step5数据清洗读取训练光谱 CSV修复张冠李戴原 main_window.step5_panel 不存在)
step5_output_path = get_step_output_path(
main_window, 'training_spectra_csv', work_dir=self.work_dir,
widget_attr='output_file', fallback_key='step6_feature',
)
if step5_output_path:
# 若为相对路径,使用 work_dir 合成为绝对路径
if not os.path.isabs(step5_output_path):
step5_output_path = os.path.join(self.work_dir or '', step5_output_path).replace('\\', '/')
existing = self.training_csv_file.get_path()
if not existing or not existing.strip():
self.training_csv_file.set_path(step5_output_path)
# 2. 自动填充输出目录8_Regression_Modeling
if self.work_dir:
output_dir = os.path.join(self.work_dir, "8_Regression_Modeling")
output_dir = resolve_subdir(self.work_dir, 'regression_modeling')
os.makedirs(output_dir, exist_ok=True)
existing_out = self.output_dir.get_path()
if not existing_out or not existing_out.strip():
@ -274,7 +275,7 @@ class Step8NonEmpiricalPanel(QWidget):
"""浏览输出目录"""
default = self._get_default_work_dir()
if default:
default = os.path.join(default, "8_Regression_Modeling")
default = resolve_subdir(default, 'regression_modeling')
dir_path = QFileDialog.getExistingDirectory(self, "选择输出模型目录", default)
if dir_path:
self.output_dir.set_path(dir_path)

View File

@ -5,6 +5,13 @@ Step8 面板 - QAA 物理反演(非经验模型)
"""
import os
import sys
# 路径归一化 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 resolve_subdir
from PyQt5.QtWidgets import (
QWidget, QVBoxLayout, QGroupBox, QFormLayout, QGridLayout,
@ -143,7 +150,7 @@ class Step8QAAPanel(QWidget):
if not initial_dir or not os.path.isdir(initial_dir):
work_dir = self._get_default_work_dir()
initial_dir = os.path.join(work_dir, "8_QAA_Inversion") if work_dir else ""
initial_dir = resolve_subdir(work_dir, 'qaa_inversion') if work_dir else ""
if initial_dir and not os.path.isdir(initial_dir):
os.makedirs(initial_dir, exist_ok=True)
@ -198,7 +205,7 @@ class Step8QAAPanel(QWidget):
self.spectrum_csv_file.set_path(step5_output)
if self.work_dir:
qaa_dir = os.path.join(self.work_dir, "8_QAA_Inversion")
qaa_dir = resolve_subdir(self.work_dir, 'qaa_inversion')
os.makedirs(qaa_dir, exist_ok=True)
output_path = os.path.join(qaa_dir, "a_lambda_results.csv").replace('\\', '/')
self.output_path.set_path(output_path)
@ -228,7 +235,7 @@ class Step8QAAPanel(QWidget):
if not output_path:
work_dir = self._get_default_work_dir()
qaa_dir = os.path.join(work_dir, "8_QAA_Inversion") if work_dir else ""
qaa_dir = resolve_subdir(work_dir, 'qaa_inversion') if work_dir else ""
if qaa_dir and not os.path.isdir(qaa_dir):
os.makedirs(qaa_dir, exist_ok=True)
output_path = os.path.join(qaa_dir, "a_lambda_results.csv").replace('\\', '/')

View File

@ -5,8 +5,15 @@ Step11 面板 - 机器学习预测
"""
import os
import sys
from pathlib import Path
# 路径归一化 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.QtWidgets import (
QWidget, QVBoxLayout, QGroupBox, QFormLayout,
QPushButton, QCheckBox, QComboBox, QLineEdit, QMessageBox,
@ -190,7 +197,7 @@ class Step9MlPredictPanel(QWidget):
"""浏览模型母文件夹,自动扫描子目录中的 .joblib 文件"""
default = self._get_default_work_dir()
if default:
default = os.path.join(default, "9_ML_Prediction")
default = resolve_subdir(default, 'ml_prediction')
dir_path = QFileDialog.getExistingDirectory(
self,
"选择模型母文件夹",
@ -334,25 +341,20 @@ class Step9MlPredictPanel(QWidget):
if not existing or not existing.strip():
self.sampling_csv_file.set_path(step4_output_path)
# 2. 尝试从 Step9(监督建模)读取模型目录
if main_window and hasattr(main_window, 'step9_panel'):
step9_widget = getattr(main_window.step9_panel, 'output_dir', None)
step9_models_dir = ""
if hasattr(step9_widget, 'get_path'):
step9_models_dir = step9_widget.get_path() or ""
elif hasattr(step9_widget, 'text'):
step9_models_dir = step9_widget.text() or ""
if step9_models_dir:
if not os.path.isabs(step9_models_dir):
step9_models_dir = os.path.join(self.work_dir or '', step9_models_dir).replace('\\', '/')
# 2. 尝试从 Step8(监督建模)读取模型目录(修复张冠李戴:原代码 main_window.step9_panel 不存在)
step8_models_dir = get_step_output_path(
main_window, 'models_dir', work_dir=self.work_dir,
widget_attr='output_dir', fallback_key='step8_ml_train',
)
if step8_models_dir:
existing_models = self.models_dir_file.get_path()
if not existing_models or not existing_models.strip():
self.models_dir_file.set_path(step9_models_dir)
self.models_dir_file.set_path(step8_models_dir)
# 3. 自动填充输出路径(机器学习预测目录)
# 3. 自动填充输出路径(机器学习预测目录,归属 step9 → 9_ML_Prediction
# 注9_ML_Prediction 是 prediction_dir 的子目录,用本地约定
if self.work_dir:
output_dir = os.path.join(self.work_dir, "9_ML_Prediction")
output_dir = resolve_subdir(self.work_dir, 'ml_prediction')
os.makedirs(output_dir, exist_ok=True)
existing_out = self.output_file.get_path()
if not existing_out or not existing_out.strip():
@ -375,7 +377,7 @@ class Step9MlPredictPanel(QWidget):
"""浏览模型目录"""
default = self._get_default_work_dir()
if default:
default = os.path.join(default, "9_ML_Prediction")
default = resolve_subdir(default, 'ml_prediction')
dir_path = QFileDialog.getExistingDirectory(self, "选择模型目录", default)
if dir_path:
self.models_dir_file.set_path(dir_path)