services/step2-5:打通前四个预处理步骤的真实后端独立服务

新增 src/new/services/{step2,step3,step4,step5}_service.py 四个独立后端服务:
This commit is contained in:
DXC
2026-06-17 09:15:22 +08:00
parent ef3de632d3
commit f8d5ea2eb8
6 changed files with 672 additions and 13 deletions

View File

@ -0,0 +1,207 @@
# -*- coding: utf-8 -*-
"""
Step3 后端计算服务(耀斑去除)
====================================
纯计算函数——绝对不引用 PyQt、绝对不引用 main_view、绝对不读写全局变量。它只:
1. 从 ``config`` 字典读取参数;
2. 调用旧版 ``GlintRemovalStep.run`` 执行去耀斑(4 种方法:Goodman/Kutser/Hedley/SUGAR);
3. 返回结果字典 ``{status, output_path, message, mode}``。
调用入口(由 main_view 在后台 QThread 中调用):
execute_step3({
"img_path": "D:/ref.bsq", # 输入影像(去耀斑前)
"method": "goodman", # goodman/kutser/hedley/sugar
"enabled": True,
"interpolate_zeros": False, # 是否先做 0 值像素插值
"interpolation_method": "bilinear",
"water_mask_path": "D:/mask.dat", # 水域掩膜(可选)
"output_path": "D:/deglint_image.bsq",
# 方法专属参数(按 method 任选一组)
"nir_lower": 65, "nir_upper": 91, "goodman_A": 1.9e-5, "goodman_B": 0.1, # goodman
"oxy_band": 38, "lower_oxy": 36, "upper_oxy": 49, "nir_band": 47, # kutser
"hedley_nir_band": 47, # hedley
"sugar_iter": 3, "sugar_sigma": 1.0, "sugar_estimate_background": True,
"sugar_glint_mask_method": "cdf", "sugar_termination_thresh": 20.0,
"sugar_bounds": [(1, 2)], # sugar
"work_dir": "D:/workspace", # 工作目录(main_view 注入)
})
返回字典字段:
* ``status`` : "completed" | "skipped" | "error"
* ``output_path`` : 生成的 .bsq 去耀斑影像路径(失败时为 None)
* ``message`` : 人类可读说明
* ``mode`` : 实际调用的去耀斑方法名,便于 UI 提示
"""
from __future__ import annotations
from pathlib import Path
from typing import Any, Dict
from src.core.steps.glint_removal_step import GlintRemovalStep
def _resolve_dirs(output_path: str | None, work_dir: str) -> tuple[Path, Path]:
"""根据 output_path / work_dir 计算 (deglint_dir, water_mask_dir)"""
if output_path:
deglint_dir = Path(output_path).parent
else:
deglint_dir = Path(work_dir) / "3_Deglint"
water_mask_dir = Path(work_dir) / "1_water_mask"
return deglint_dir, water_mask_dir
def _normalize_method(method: str) -> str:
"""方法名标准化(与 GlintRemovalStep.run 内部规则保持一致)"""
raw = str(method).lower()
if "kutser" in raw:
return "kutser"
if "goodman" in raw:
return "goodman"
if "hedley" in raw:
return "hedley"
if "sugar" in raw:
return "sugar"
return raw
def _build_method_kwargs(method: str, config: Dict[str, Any]) -> Dict[str, Any]:
"""按 method 从 config 中抽取对应的方法专属参数"""
if method == "goodman":
return {
"nir_lower": int(config.get("nir_lower", 65)),
"nir_upper": int(config.get("nir_upper", 91)),
"goodman_A": float(config.get("goodman_A", 0.000019)),
"goodman_B": float(config.get("goodman_B", 0.1)),
}
if method == "kutser":
return {
"oxy_band": int(config.get("oxy_band", 38)),
"lower_oxy": int(config.get("lower_oxy", 36)),
"upper_oxy": int(config.get("upper_oxy", 49)),
"nir_band": int(config.get("nir_band", 47)),
}
if method == "hedley":
return {
"hedley_nir_band": int(config.get("hedley_nir_band", 47)),
}
if method == "sugar":
bounds = config.get("sugar_bounds")
if bounds is None or not isinstance(bounds, (list, tuple)):
bounds = [(1, 2)]
return {
"sugar_iter": int(config.get("sugar_iter", 3)),
"sugar_sigma": float(config.get("sugar_sigma", 1.0)),
"sugar_estimate_background": bool(config.get("sugar_estimate_background", True)),
"sugar_glint_mask_method": str(config.get("sugar_glint_mask_method", "cdf")),
"sugar_termination_thresh": float(config.get("sugar_termination_thresh", 20.0)),
"sugar_bounds": bounds,
}
return {}
def execute_step3(config: Dict[str, Any]) -> Dict[str, Any]:
"""Step 3 后端计算入口——纯函数
Args:
config: 由前端 view.get_config() 序列化、再经 main_view 注入 work_dir 的字典
Returns:
标准结果字典 ``{status, output_path, message, mode}``
"""
# ---------- 入参规整 ----------
img_path = config.get("img_path")
raw_method = config.get("method", "goodman")
method = _normalize_method(raw_method)
enabled = bool(config.get("enabled", True))
interpolate_zeros = bool(config.get("interpolate_zeros", False))
interpolation_method = str(config.get("interpolation_method", "bilinear"))
water_mask_path = config.get("water_mask_path")
output_path = config.get("output_path")
work_dir = config.get("work_dir") or "."
deglint_dir, water_mask_dir = _resolve_dirs(output_path, work_dir)
# ---------- 提前失败检查 ----------
if not enabled:
return {
"status": "skipped",
"output_path": img_path,
"message": "用户禁用此步骤(enabled=False),保留原始影像",
"mode": method,
}
if not img_path:
return {
"status": "error",
"output_path": None,
"message": "未提供输入影像路径(img_path)",
"mode": method,
}
if not Path(img_path).exists():
return {
"status": "error",
"output_path": None,
"message": f"输入影像不存在: {img_path}",
"mode": method,
}
# ---------- 构建底层 kwargs ----------
method_kwargs = _build_method_kwargs(method, config)
kwargs: Dict[str, Any] = {
"img_path": img_path,
"method": method,
"water_mask": water_mask_path,
"interpolate_zeros": interpolate_zeros,
"interpolation_method": interpolation_method,
"deglint_dir": deglint_dir,
"water_mask_dir": water_mask_dir,
"output_path": output_path,
"callback": None, # 日志由 main_view 统一接管
}
kwargs.update(method_kwargs)
# ---------- 执行(包一层 try/except 把异常转 dict,避免炸线程) ----------
try:
result_path = GlintRemovalStep.run(**kwargs)
except FileNotFoundError as e:
return {
"status": "error",
"output_path": None,
"message": f"文件不存在: {e}",
"mode": method,
}
except ValueError as e:
return {
"status": "error",
"output_path": None,
"message": f"参数错误: {e}",
"mode": method,
}
except Exception as e: # noqa: BLE001 —— service 层兜底捕获所有
return {
"status": "error",
"output_path": None,
"message": f"{type(e).__name__}: {e}",
"mode": method,
}
# ---------- 成功路径 ----------
p = Path(result_path)
if not p.exists():
return {
"status": "error",
"output_path": None,
"message": f"GlintRemovalStep.run 未生成文件: {result_path}",
"mode": method,
}
return {
"status": "completed",
"output_path": str(p).replace("\\", "/"),
"message": f"去耀斑影像已生成: {p.name}",
"mode": method,
}