# -*- 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 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 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 "." 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, }