Files
WQ_GUI/src/new/services/step10_service.py

217 lines
8.4 KiB
Python
Raw Normal View History

# -*- coding: utf-8 -*-
"""
Step10 后端计算服务(水色指数反演)
====================================
纯计算函数——绝对不引用 PyQt、绝对不引用 main_view、绝对不读写全局变量。它只:
1. 从 ``config`` 字典读取参数;
2. 调用 ``WaterIndexProcessor.run_inversion`` 用 ``waterindex.csv`` 中的
公式直接处理去耀斑 BSQ 影像,输出各水质参数指数的 GeoTIFF;
3. 返回结果字典 ``{status, output_path, message, mode}``。
调用入口(由 main_view 在后台 QThread 中调用):
execute_step10({
"bsq_path": "D:/deglint_output.bsq", # 去耀斑 BSQ 影像(必填)
"deglint_img_path": "D:/deglint_output.bsq", # 同上(兼容旧 panel 字段)
"hdr_path": "D:/deglint_output.hdr", # ENVI 头文件(可省,自动 .bsq→.hdr 推断)
"selected_formulas": ["NDCI", "BGA_Am09KBBI"], # 要处理的公式名列表(空 → 全部)
"formula_csv_path": "D:/waterindex.csv", # waterindex.csv 路径(可省,自动探测)
"water_mask_path": "D:/water_mask.dat", # 水域掩膜路径(可省)
"nodata_value": -9999.0, # NoData 标记值
"output_dir": "D:/10_WaterIndex_Images", # 输出目录(可省 → work_dir/10_WaterIndex_Images)
"enabled": True,
"work_dir": "D:/workspace", # 工作目录(main_view 注入)
})
返回字典字段:
* ``status`` : "completed" | "skipped" | "error"
* ``output_path`` : 输出目录路径(失败时为 None)
* ``message`` : 人类可读说明
* ``mode`` : "watercolor_inversion"(便于 UI 提示)
"""
from __future__ import annotations
from pathlib import Path
from typing import Any, Dict, List, Optional
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
from src.new.services._output_resolver import get_user_output_path, is_user_specified, resolve_output_dir
def _resolve_waterindex_csv(formula_csv_path: Optional[str], work_dir: str) -> str:
"""解析 waterindex.csv 路径(与 WaterIndexProcessor.__init__ 默认逻辑保持一致)"""
if formula_csv_path and Path(formula_csv_path).is_file():
return formula_csv_path
candidates = [
Path(work_dir) / "waterindex.csv",
Path(work_dir) / "model" / "waterindex.csv",
Path("src/gui/model/waterindex.csv"),
]
for c in candidates:
if c.is_file():
return str(c)
return formula_csv_path or ""
def _resolve_water_mask_path(water_mask_path: Optional[str], work_dir: str) -> Optional[str]:
"""解析水域掩膜路径(缺省时尝试从 work_dir/1_water_mask 自动扫盘)"""
if water_mask_path and Path(water_mask_path).is_file():
return water_mask_path
if not work_dir:
return None
mask_dir = Path(work_dir) / "1_water_mask"
if not mask_dir.is_dir():
return None
for pat in ("*.tif", "*.TIF", "*.dat", "*.DT"):
cands = sorted(mask_dir.glob(pat))
if cands:
return str(cands[0])
return None
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
def _resolve_output_dir(config: Dict[str, Any], work_dir: str) -> tuple[Path, str]:
"""根据 output_dir / work_dir 计算水色指数反演结果输出目录
使用共享解析器强制执行"用户优先"规则——用户指定 output_dir 时直接用其值
(step10 的 output_dir 本身就是一个目录),否则用 work_dir/10_WaterIndex_Images 默认。
注意:step10 与其他步骤不同——output_dir 直接表示目录而非文件路径,
所以使用 Path(user_path) 而非 .parent。
"""
user_path = get_user_output_path(config, "output_dir", "output_path")
if user_path:
return Path(user_path), "user"
return Path(work_dir) / "10_WaterIndex_Images", "default"
def execute_step10(config: Dict[str, Any]) -> Dict[str, Any]:
"""Step 10 后端计算入口——纯函数
Args:
config: 由前端 view.get_config() 序列化、再经 main_view 注入 work_dir 的字典
Returns:
标准结果字典 ``{status, output_path, message, mode}``
"""
# ---------- 入参规整 ----------
bsq_path: str = config.get("bsq_path") or config.get("deglint_img_path") or ""
hdr_path: str = config.get("hdr_path") or ""
selected_formulas: List[str] = config.get("selected_formulas") or []
formula_csv_path: str = config.get("formula_csv_path") or ""
water_mask_path: Optional[str] = config.get("water_mask_path")
nodata_value: float = float(config.get("nodata_value", -9999.0))
output_dir: str = config.get("output_dir") or ""
enabled: bool = bool(config.get("enabled", True))
work_dir: str = config.get("work_dir") or "."
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
output_path, _source = _resolve_output_dir(config, work_dir)
mode = "watercolor_inversion"
# ---------- 提前失败检查 ----------
if not enabled:
return {
"status": "skipped",
"output_path": None,
"message": "用户禁用此步骤(enabled=False)",
"mode": mode,
}
if not bsq_path:
return {
"status": "error",
"output_path": None,
"message": "未提供 BSQ 影像路径(bsq_path / deglint_img_path)",
"mode": mode,
}
if not Path(bsq_path).is_file():
return {
"status": "error",
"output_path": None,
"message": f"BSQ 影像不存在: {bsq_path}",
"mode": mode,
}
if not hdr_path:
# 自动探测 .hdr
hdr_path = str(Path(bsq_path).with_suffix(".hdr"))
if not Path(hdr_path).is_file():
hdr_alt = str(Path(bsq_path).with_suffix(".HDR"))
if Path(hdr_alt).is_file():
hdr_path = hdr_alt
else:
hdr_path = ""
if not hdr_path or not Path(hdr_path).is_file():
return {
"status": "error",
"output_path": None,
"message": f"未找到 ENVI 头文件(与 BSQ 同名 .hdr): {bsq_path}",
"mode": mode,
}
# ---------- 解析 waterindex.csv ----------
resolved_formula_csv = _resolve_waterindex_csv(formula_csv_path, work_dir)
if not resolved_formula_csv:
return {
"status": "error",
"output_path": None,
"message": "未提供 formula_csv_path 且默认位置均找不到 waterindex.csv",
"mode": mode,
}
# ---------- 解析水域掩膜(可选) ----------
resolved_water_mask = _resolve_water_mask_path(water_mask_path, work_dir)
# ---------- 执行(包一层 try/except 把异常转 dict,避免炸线程) ----------
try:
from src.core.algorithms.waterindex_inversion import WaterIndexProcessor
print(f"[Step10 Service] 水色指数反演: bsq={bsq_path}")
print(f"[Step10 Service] hdr={hdr_path}")
print(f"[Step10 Service] formula_csv={resolved_formula_csv}")
print(f"[Step10 Service] selected_formulas={selected_formulas or '全部'}")
if resolved_water_mask:
print(f"[Step10 Service] water_mask={resolved_water_mask}")
processor = WaterIndexProcessor(resolved_formula_csv)
results = processor.run_inversion(
deglint_img_path=bsq_path,
work_dir=work_dir,
formula_csv_path=resolved_formula_csv,
selected_formulas=selected_formulas or None,
water_mask_path=resolved_water_mask,
nodata_value=nodata_value,
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(output_path)
n_results = len(results) if isinstance(results, dict) else 0
return {
"status": "completed",
"output_path": str(p).replace("\\", "/"),
"message": f"水色指数反演完成,共生成 {n_results} 个指数 GeoTIFF",
"mode": mode,
}