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

232 lines
8.7 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 -*-
"""
Step10 后端计算服务(水色指数反演 · 散点 CSV 模式)
====================================================
纯计算函数——绝对不引用 PyQt、绝对不引用 main_view、绝对不读写全局变量。它只:
1. 从 ``config`` 字典读取参数;
2. 调用 ``WaterIndexCsvProcessor.compute_indices_from_csv``
读取 Step 4 输出的 ``sampling_spectra.csv`` 散点,对每行采样点
套用 ``waterindex.csv`` 中勾选的公式,输出每公式一个 CSV;
3. 返回结果字典 ``{status, output_path, message, mode, ...}``。
调用入口(由 main_view 在后台 QThread 中调用):
execute_step10({
"sampling_csv_path": "D:/4_sampling/sampling_spectra.csv", # 必填
"selected_formulas": ["NDCI", "BGA_Am09KBBI"], # 勾选公式;空 → 全部
"formula_csv_path": "D:/waterindex.csv", # waterindex.csv 路径
"output_dir": "D:/10_WaterIndex_CSV", # 输出目录;可省
"enabled": True,
"work_dir": "D:/workspace", # 主窗口注入
})
返回字典字段:
* ``status`` : "completed" | "skipped" | "error"
* ``output_path`` : 输出目录路径(失败时为 None)
* ``output_files`` : {公式名: 公式 CSV 路径}(失败时为空 dict)
* ``message`` : 人类可读说明
* ``mode`` : "watercolor_inversion_csv"(便于 UI 提示)
设计要点
========
- 与 Step 9 (ML 预测) 完全对称的"散点处理模式":输入 CSV、输出 CSV,
坐标列重命名为 longitude/latitude,公式值以列形式追加。
- 旧"读 BSQ 全图 → 输出 GeoTIFF"模式已废弃(科学上误差大且与 GIS 栅格计算器重复)。
- 兼容调用方可能仍传旧键(bsq_path / hdr_path / deglint_img_path),检测到时
静默忽略并回退到 sampling_spectra.csv 路径解析(避免破坏已有 pipeline 配置)。
"""
from __future__ import annotations
from pathlib import Path
from typing import Any, Dict, List, Optional
from src.new.services._output_resolver import get_user_output_path
def _resolve_sampling_csv_path(
sampling_csv_path: Optional[str],
work_dir: str,
) -> str:
"""解析采样点 CSV 路径
解析顺序:
1. 显式传入的 ``sampling_csv_path``
2. ``{work_dir}/4_sampling/sampling_spectra.csv``
3. ``{work_dir}/4_sampling/`` 下任意 ``.csv`` (取最新)
"""
if sampling_csv_path and Path(sampling_csv_path).is_file():
return sampling_csv_path
if not work_dir:
return sampling_csv_path or ""
primary = Path(work_dir) / "4_sampling" / "sampling_spectra.csv"
if primary.is_file():
return str(primary).replace("\\", "/")
sample_dir = Path(work_dir) / "4_sampling"
if sample_dir.is_dir():
cands = sorted(sample_dir.glob("*.csv"), key=lambda p: p.stat().st_mtime, reverse=True)
if cands:
return str(cands[0]).replace("\\", "/")
return sampling_csv_path or ""
def _resolve_waterindex_csv(formula_csv_path: Optional[str], work_dir: str) -> str:
"""解析 waterindex.csv 路径(与 WaterIndexCsvProcessor.__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).replace("\\", "/")
return formula_csv_path or ""
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_CSV`` 默认。
"""
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_CSV", "default"
def execute_step10(config: Dict[str, Any]) -> Dict[str, Any]:
"""Step 10 后端计算入口——纯函数(散点 CSV 模式)
Args:
config: 由前端 view.get_config() 序列化、再经 main_view 注入 work_dir 的字典
Returns:
标准结果字典 ``{status, output_path, output_files, message, mode}``
"""
# ---------- 入参规整 ----------
sampling_csv_path: str = (
config.get("sampling_csv_path")
or config.get("spectrum_csv_path") # 兼容旧字段
or ""
)
selected_formulas: List[str] = config.get("selected_formulas") or []
formula_csv_path: str = config.get("formula_csv_path") or ""
output_dir: str = config.get("output_dir") or ""
enabled: bool = bool(config.get("enabled", True))
work_dir: str = config.get("work_dir") or "."
output_path, _source = _resolve_output_dir(config, work_dir)
mode = "watercolor_inversion_csv"
# ---------- 提前失败检查 ----------
if not enabled:
return {
"status": "skipped",
"output_path": None,
"output_files": {},
"message": "用户禁用此步骤(enabled=False)",
"mode": mode,
}
# 解析采样点 CSV 路径
resolved_sampling_csv = _resolve_sampling_csv_path(sampling_csv_path, work_dir)
if not resolved_sampling_csv:
return {
"status": "error",
"output_path": None,
"output_files": {},
"message": "未提供 sampling_csv_path 且默认位置均找不到 sampling_spectra.csv",
"mode": mode,
}
if not Path(resolved_sampling_csv).is_file():
return {
"status": "error",
"output_path": None,
"output_files": {},
"message": f"采样点 CSV 不存在: {resolved_sampling_csv}",
"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,
"output_files": {},
"message": "未提供 formula_csv_path 且默认位置均找不到 waterindex.csv",
"mode": mode,
}
if not Path(resolved_formula_csv).is_file():
return {
"status": "error",
"output_path": None,
"output_files": {},
"message": f"waterindex.csv 不存在: {resolved_formula_csv}",
"mode": mode,
}
# ---------- 执行(包一层 try/except 把异常转 dict,避免炸线程) ----------
try:
from src.core.algorithms.waterindex_inversion import (
WaterIndexCsvProcessor,
)
print(f"[Step10 Service] 水色指数反演(散点模式): sampling_csv={resolved_sampling_csv}")
print(f"[Step10 Service] formula_csv={resolved_formula_csv}")
print(f"[Step10 Service] selected_formulas={selected_formulas or '全部'}")
print(f"[Step10 Service] output_dir={output_path}")
processor = WaterIndexCsvProcessor(resolved_formula_csv)
out_files = processor.compute_indices_from_csv(
sampling_csv_path=resolved_sampling_csv,
output_dir=str(output_path).replace("\\", "/"),
selected_formulas=selected_formulas or None,
progress_callback=None, # 日志由 main_view 统一接管
)
except FileNotFoundError as e:
return {
"status": "error",
"output_path": None,
"output_files": {},
"message": f"文件不存在: {e}",
"mode": mode,
}
except ValueError as e:
return {
"status": "error",
"output_path": None,
"output_files": {},
"message": f"参数错误: {e}",
"mode": mode,
}
except Exception as e: # noqa: BLE001 —— service 层兜底捕获所有
return {
"status": "error",
"output_path": None,
"output_files": {},
"message": f"{type(e).__name__}: {e}",
"mode": mode,
}
# ---------- 成功路径 ----------
p = Path(output_path)
n_results = len(out_files) if isinstance(out_files, dict) else 0
return {
"status": "completed",
"output_path": str(p).replace("\\", "/"),
"output_files": out_files,
"message": f"水色指数反演完成,共生成 {n_results} 个指数 CSV",
"mode": mode,
}