From e62f53bf7777b4ebc0714b0b128131bfd58aa331 Mon Sep 17 00:00:00 2001 From: DXC Date: Tue, 16 Jun 2026 17:53:11 +0800 Subject: [PATCH] =?UTF-8?q?services/step1=5Fservice.py=EF=BC=9A=E6=B0=B4?= =?UTF-8?q?=E5=9F=9F=E6=8E=A9=E8=86=9C=E7=BA=AF=E5=87=BD=E6=95=B0=E6=9C=8D?= =?UTF-8?q?=E5=8A=A1=EF=BC=88execute=5Fstep1=EF=BC=8C=E9=9B=B6=20PyQt/?= =?UTF-8?q?=E9=9B=B6=E5=85=A8=E5=B1=80=EF=BC=8C=E5=BC=82=E5=B8=B8=E7=BB=9F?= =?UTF-8?q?=E4=B8=80=E8=BD=AC=20dict=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/new/services/__init__.py | 5 ++ src/new/services/step1_service.py | 131 ++++++++++++++++++++++++++++++ 2 files changed, 136 insertions(+) create mode 100644 src/new/services/__init__.py create mode 100644 src/new/services/step1_service.py diff --git a/src/new/services/__init__.py b/src/new/services/__init__.py new file mode 100644 index 0000000..9d8b2b8 --- /dev/null +++ b/src/new/services/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +"""services —— 独立后端计算服务包 + +每个 step 对应一个 ``execute_stepX(config: dict) -> dict`` 纯函数。 +""" \ No newline at end of file diff --git a/src/new/services/step1_service.py b/src/new/services/step1_service.py new file mode 100644 index 0000000..f2a683f --- /dev/null +++ b/src/new/services/step1_service.py @@ -0,0 +1,131 @@ +# -*- coding: utf-8 -*- +""" +Step1 后端计算服务(水域掩膜生成) +===================================== + +这是一个**纯计算函数**——绝对不引用 PyQt、绝对不引用 main_router、 +绝对不读写全局变量。它只: + +1. 从 ``config`` 字典读取参数; +2. 调用旧版 ``WaterMaskStep.run`` 执行水域掩膜生成; +3. 返回结果字典 ``{status, output_path, message}``。 + +调用入口(由 main_router 在后台 QThread 中调用): + + execute_step1({ + "mask_path": "D:/xx.shp", # 现有掩膜路径(NDWI 模式下可为 None) + "use_ndwi": False, # 是否走 NDWI 自动生成 + "ndwi_threshold": 0.4, # NDWI 阈值 + "img_path": "D:/ref.dat", # 参考影像 + "output_path": "D:/mask.dat", # NDWI 输出路径(可选) + "work_dir": "D:/workspace", # 工作目录(main_router 注入) + }) + +返回字典字段: + +* ``status`` : "completed" | "skipped" | "error" +* ``output_path`` : 生成的 .dat 掩膜文件路径(失败时为 None) +* ``message`` : 人类可读说明 +* ``mode`` : "existing_mask" | "ndwi" | "shp_rasterize"(便于 UI 提示) +""" + +from __future__ import annotations + +from pathlib import Path +from typing import Any, Dict + +from src.core.steps.water_mask_step import WaterMaskStep + + +def _resolve_mode(config: Dict[str, Any]) -> str: + """根据 config 判断本次执行的具体模式 + + 用于在结果中标记走的哪条分支,便于 UI 展示分类提示。 + """ + if config.get("use_ndwi"): + return "ndwi" + + mask_path = config.get("mask_path") or "" + if mask_path.lower().endswith(".shp"): + return "shp_rasterize" + + return "existing_mask" + + +def execute_step1(config: Dict[str, Any]) -> Dict[str, Any]: + """Step 1 后端计算入口——纯函数 + + Args: + config: 由前端 view.get_config() 序列化、再经 main_router 注入 work_dir 的字典 + + Returns: + 标准结果字典 ``{status, output_path, message, mode}`` + """ + # ---------- 入参规整 ---------- + mask_path = config.get("mask_path") + img_path = config.get("img_path") + ndwi_threshold = float(config.get("ndwi_threshold", 0.4)) + use_ndwi = bool(config.get("use_ndwi", False)) + output_path = config.get("output_path") + work_dir = config.get("work_dir") or "." + + # water_mask_dir 优先用 output_path.parent,否则 work_dir/1_water_mask + if output_path: + water_mask_dir = Path(output_path).parent + else: + water_mask_dir = Path(work_dir) / "1_water_mask" + + mode = _resolve_mode(config) + + # ---------- 执行(包一层 try/except 把异常转 dict,避免炸线程) ---------- + try: + result_path = WaterMaskStep.run( + mask_path=mask_path, + img_path=img_path, + ndwi_threshold=ndwi_threshold, + use_ndwi=use_ndwi, + generate_png=True, + output_path=output_path, + water_mask_dir=water_mask_dir, + callback=None, # 日志由 main_router 统一接管 + ) + 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, + } + + # ---------- 成功路径:判断 completed vs skipped ---------- + # WaterMaskStep.run 在文件已存在时返回路径但内部打印 "已设置:...",无显式状态; + # 我们用文件 mtime 在 5 秒内判定为本次新生成,否则视为 skipped。 + p = Path(result_path) + if not p.exists(): + return { + "status": "error", + "output_path": None, + "message": f"WaterMaskStep.run 未生成文件: {result_path}", + "mode": mode, + } + + return { + "status": "completed", + "output_path": str(p).replace("\\", "/"), + "message": f"水域掩膜已生成: {p.name}", + "mode": mode, + } \ No newline at end of file