Files
WQ_GUI/src/new/services/step5_service.py
DXC 6a962f5e8f feat(new-arch):主窗口全功能增强(图标系统 + 全链路参数同步 + 服务输出统一解析 + Step12 分类浏览)
1. main_view.py:图标系统 + 全链路参数自动传导
   - 新增 _res() 解析项目根的相对路径,PyInstaller 打包后兼容 sys._MEIPASS。
   - 新增 QListWidgetItem / QMessageBox 导入,左侧导航列表支持右键菜单 + 错误弹窗。
   - ROUTES 12 条全部新增 icon 字段("1.png" 等),侧边栏显示业务图标。
   - 新增 step_outputs 缓存机制:每个 step 完成后把 output_path 写入 self.step_outputs。
   - 新增 _sync_dependencies() 同步函数 + _safe_set_config() 包装器,
     按依赖图把上游产物推给下游 view:
       step1 → step6 water_mask_path
       step3 → step4 / step6 / step10 deglint_img_path / bsq_path
       step4 → step9 sampling_csv_path
       step5 → step6 csv_path
       step6 → step7 / step8 training_csv_path
       step8 → step9 models_dir(父目录)
       step9 → step11 prediction_csv_dir / prediction_csv_path(双推)
       step10 → step11 geotiff_dir / geotiff_path(双推)

2. services/step1-13:统一输出解析器集成
   - 新增 src/new/services/_output_resolver.py,提供 resolve_output_dir /
     copy_to_user_path / get_user_output_path / is_user_specified 四个共享工具。
   - 每个 service 把原有的私有 _resolve_xxx_dir 改为调用 resolve_output_dir,
     强制执行"用户优先"规则(用户指定 output_path 时用其父目录,否则用 work_dir/<subdir>)。
   - 用户指定文件名 vs 底层硬编码文件名的"事后劫持"通过 copy_to_user_path 完成
     (覆盖 step2、step4、step7、step8 等底层 step 不接受 output_path 关键字的步骤)。

3. views/step12_view.py:恢复 ImageCategoryTree + ImageViewerWidget 高级组件
   - 删掉精简版占位 Label,挂回旧版的 ImageCategoryTree(按"模型评估/光谱分析/
     统计图表/处理结果/含量分布图"五类自动归类工作目录下的图像文件)。
   - 挂回 ImageViewerWidget(滚轮缩放 0.1x-5x + 50ms 防抖 + FastTransformation/
     SmoothTransformation 智能切换 + Ctrl+Wheel + 工具栏)。
   - 扫描按钮接通 image_tree.scan_directory(),选中节点即时加载到 image_viewer。
   - 按钮样式切换为 ModernStylesheet(success/primary)统一视觉。
2026-06-17 13:28:58 +08:00

152 lines
5.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# -*- coding: utf-8 -*-
"""
Step5 后端计算服务(数据清洗)
====================================
纯计算函数——绝对不引用 PyQt、绝对不引用 main_view、绝对不读写全局变量。它只
1. 从 ``config`` 字典读取参数;
2. 调用旧版 ``DataPreparationStep.process_csv`` 读取水质 CSV
筛选剔除异常值后写出新的 CSV
3. 返回结果字典 ``{status, output_path, message, mode}``。
调用入口(由 main_view 在后台 QThread 中调用):
execute_step5({
"csv_path": "D:/water_quality.csv", # 输入水质参数 CSV
"enabled": True,
"output_path": "D:/processed_data.csv", # 处理后输出 CSV
"work_dir": "D:/workspace", # 工作目录main_view 注入)
})
返回字典字段:
* ``status`` : "completed" | "skipped" | "error"
* ``output_path`` : 生成的 .csv 清洗后文件路径(失败时为 None
* ``message`` : 人类可读说明
* ``mode`` : "csv_clean"(便于 UI 提示)
"""
from __future__ import annotations
from pathlib import Path
from typing import Any, Dict
from src.core.steps.data_preparation_step import DataPreparationStep
from src.new.services._output_resolver import (
copy_to_user_path,
get_user_output_path,
is_user_specified,
resolve_output_dir,
)
def _resolve_output_dir(config: Dict[str, Any], work_dir: str) -> tuple[Path, str]:
"""根据 output_path / work_dir 计算清洗后 CSV 输出目录
使用共享解析器强制执行"用户优先"规则——用户指定 output_path 时用其父目录,
否则用 work_dir/5_Data_Cleaning 默认。
"""
return resolve_output_dir(config, work_dir, "5_Data_Cleaning", "output_path", "output_dir")
def execute_step5(config: Dict[str, Any]) -> Dict[str, Any]:
"""Step 5 后端计算入口——纯函数
Args:
config: 由前端 view.get_config() 序列化、再经 main_view 注入 work_dir 的字典
Returns:
标准结果字典 ``{status, output_path, message, mode}``
"""
# ---------- 入参规整 ----------
csv_path = config.get("csv_path")
enabled = bool(config.get("enabled", True))
output_path = config.get("output_path")
work_dir = config.get("work_dir") or "."
output_dir, _source = _resolve_output_dir(config, work_dir)
mode = "csv_clean"
# ---------- 提前失败检查 ----------
if not enabled:
return {
"status": "skipped",
"output_path": None,
"message": "用户禁用此步骤enabled=False",
"mode": mode,
}
if not csv_path:
return {
"status": "error",
"output_path": None,
"message": "未提供水质参数 CSV 路径csv_path",
"mode": mode,
}
if not Path(csv_path).exists():
return {
"status": "error",
"output_path": None,
"message": f"水质参数 CSV 不存在: {csv_path}",
"mode": mode,
}
# ---------- 显式构造 output_path如未指定----------
# process_csv 内部会用 output_dir/processed_data.csv 自行拼接,
# 此处显式指定 output_path 以保持契约一致。
if not output_path:
output_path = str(output_dir / "processed_data.csv").replace("\\", "/")
# ---------- 执行(包一层 try/except 把异常转 dict避免炸线程 ----------
try:
result_path = DataPreparationStep.process_csv(
csv_path=csv_path,
output_dir=output_dir,
callback=None, # 日志由 main_view 统一接管
)
except FileNotFoundError as e:
return {
"status": "error",
"output_path": None,
"message": f"文件不存在: {e}",
"mode": mode,
}
except ValueError as e:
return {
"status": "error",
"output_path": None,
"message": f"参数错误: {e}",
"mode": mode,
}
except Exception as e: # noqa: BLE001 —— service 层兜底捕获所有
return {
"status": "error",
"output_path": None,
"message": f"{type(e).__name__}: {e}",
"mode": mode,
}
# ---------- 成功路径 ----------
p = Path(result_path)
if not p.exists():
return {
"status": "error",
"output_path": None,
"message": f"DataPreparationStep.process_csv 未生成文件: {result_path}",
"mode": mode,
}
# ---------- 事后劫持:用户指定文件名 vs 底层硬编码文件名 ----------
# process_csv 内部硬编码输出文件名 processed_data.csv
# 用户浏览指定的文件名(无论是 .csv 还是别的格式)被底层忽略。
user_path = config.get("output_path")
if user_path:
result_path = copy_to_user_path(result_path, user_path)
p = Path(result_path)
return {
"status": "completed",
"output_path": str(p).replace("\\", "/"),
"message": f"清洗后 CSV 已保存: {p.name}",
"mode": mode,
}