From 91e2fe47a57cd39b612d54177b548e4ec3432783 Mon Sep 17 00:00:00 2001
From: shuishen <1109946754@qq.com>
Date: Mon, 17 Aug 2026 14:50:48 +0800
Subject: [PATCH] feat:语义分割demo处理

---
 scripts/serve_workbench_console.py |   82 +++++++++++++++++++++++++++++++++++++++++
 1 files changed, 82 insertions(+), 0 deletions(-)

diff --git a/scripts/serve_workbench_console.py b/scripts/serve_workbench_console.py
index 302eb37..08b333e 100644
--- a/scripts/serve_workbench_console.py
+++ b/scripts/serve_workbench_console.py
@@ -24,10 +24,12 @@
 MAX_REQUEST_BYTES = 128 * 1024 * 1024
 MAX_FILE_BYTES = 96 * 1024 * 1024
 MAX_IMAGES_PER_RUN = 12
+MAX_SEGMENTATION_IMAGES_PER_RUN = 6
 ALLOWED_PATH_PREFIXES = (
     "apps/workbench-console",
     "shared/outputs",
     "shared/data/raw/01-object-detection",
+    "shared/data/raw/02-semantic-mapping",
 )
 SAFE_FILE_NAME = re.compile(r"[^A-Za-z0-9._-]+")
 RUN_LOCK = threading.Lock()
@@ -129,6 +131,37 @@
     return sorted(records, key=lambda item: (item["createdAt"], item["id"]), reverse=True)
 
 
+def semantic_runs(root: Path) -> list[dict[str, Any]]:
+    output_root = root / "shared" / "outputs" / "02-semantic-mapping"
+    records: list[dict[str, Any]] = []
+    for metadata_path in output_root.rglob("run_metadata.json"):
+        artifact = metadata_path.parent
+        metadata = load_json(metadata_path)
+        if metadata.get("capability") != "02-semantic-mapping" or not isinstance(metadata.get("images"), list):
+            continue
+        run_id = artifact.name if artifact != output_root else "baseline"
+        records.append(
+            {
+                "id": run_id,
+                "label": "语义分割基线" if run_id == "baseline" else run_id,
+                "note": f"{metadata.get('task_name') or '通用颜色规则基线'},输出栅格掩膜与 GeoAI 矢量结果。",
+                "artifactRoot": relative_path(root, artifact),
+                "inputRoot": str(metadata.get("input_dir") or "shared/data/processed/02-semantic-mapping"),
+                "rawInputRoot": str(metadata.get("raw_input_dir") or "shared/data/raw/02-semantic-mapping"),
+                "createdAt": str(metadata.get("created_at") or ""),
+            }
+        )
+    return sorted(records, key=lambda item: (item["createdAt"], item["id"]), reverse=True)
+
+
+def semantic_tasks(root: Path) -> list[dict[str, Any]]:
+    catalog = load_json(root / "capabilities" / "02-semantic-mapping" / "configs" / "task-catalog.json")
+    tasks = catalog.get("tasks")
+    if not isinstance(tasks, list):
+        return []
+    return [item for item in tasks if isinstance(item, dict) and isinstance(item.get("id"), str)]
+
+
 class WorkbenchConsoleHandler(SimpleHTTPRequestHandler):
     """Static UI plus fixed, local-only ingestion and experiment commands."""
 
@@ -146,6 +179,12 @@
         if path == "/api/object-detection/runs":
             self.send_json(HTTPStatus.OK, {"runs": detection_runs(self.root)})
             return
+        if path == "/api/semantic-mapping/runs":
+            self.send_json(HTTPStatus.OK, {"runs": semantic_runs(self.root)})
+            return
+        if path == "/api/semantic-mapping/tasks":
+            self.send_json(HTTPStatus.OK, {"tasks": semantic_tasks(self.root)})
+            return
         if path == "/":
             self.send_response(HTTPStatus.FOUND)
             self.send_header("Location", "/apps/workbench-console/")
@@ -162,6 +201,9 @@
                 return
             if path == "/api/object-detection/runs":
                 self.send_json(HTTPStatus.CREATED, {"run": self.create_detection_run(payload)})
+                return
+            if path == "/api/semantic-mapping/runs":
+                self.send_json(HTTPStatus.CREATED, {"run": self.create_semantic_run(payload)})
                 return
             self.send_json(HTTPStatus.NOT_FOUND, {"error": "Unknown local API endpoint."})
         except ApiError as exc:
@@ -257,6 +299,46 @@
             raise ApiError("Detection script finished without the expected result metadata.")
         return next(item for item in detection_runs(self.root) if item["id"] == run_id)
 
+    def create_semantic_run(self, payload: dict[str, Any]) -> dict[str, Any]:
+        task_id = str(payload.get("taskId") or "color_baseline")
+        task = next((item for item in semantic_tasks(self.root) if item["id"] == task_id), None)
+        if task is None:
+            raise ApiError(f"Unknown semantic-mapping task: {task_id}.")
+        if task.get("selectable") is not True:
+            raise ApiError(f"Semantic-mapping task is not runnable yet: {task_id}.")
+        uploads = payload.get("images")
+        if not isinstance(uploads, list) or not uploads:
+            raise ApiError("Semantic-mapping request must include at least one image.")
+        if len(uploads) > MAX_SEGMENTATION_IMAGES_PER_RUN:
+            raise ApiError(f"A semantic-mapping run accepts at most {MAX_SEGMENTATION_IMAGES_PER_RUN} images.")
+        decoded = [decode_upload(item, {".jpg", ".jpeg", ".png", ".tif", ".tiff"}) for item in uploads]
+        if len({name.casefold() for name, _ in decoded}) != len(decoded):
+            raise ApiError("Uploaded image names must be unique within one run.")
+        run_id = make_run_id("semantic")
+        raw_root = self.root / "shared" / "data" / "raw" / "02-semantic-mapping" / "runs" / run_id
+        processed_root = self.root / "shared" / "data" / "processed" / "02-semantic-mapping" / run_id
+        raw_root.mkdir(parents=True, exist_ok=False)
+        processed_root.mkdir(parents=True, exist_ok=False)
+        for name, content in decoded:
+            (raw_root / name).write_bytes(content)
+            (processed_root / name).write_bytes(content)
+        output = self.root / "shared" / "outputs" / "02-semantic-mapping" / "runs" / run_id
+        python = self.root / ".venvs" / "02-semantic-mapping" / "Scripts" / "python.exe"
+        if not python.is_file():
+            raise ApiError("Semantic-mapping virtual environment is unavailable. Run the capability setup first.")
+        with RUN_LOCK:
+            self.run_command([str(python), str(self.root / "capabilities" / "02-semantic-mapping" / "run_semantic_segmentation.py"), "--input", str(processed_root), "--output", str(output)], 900)
+        metadata_path = output / "run_metadata.json"
+        if not metadata_path.is_file():
+            raise ApiError("Semantic-mapping script finished without the expected result metadata.")
+        metadata = load_json(metadata_path)
+        metadata["task_id"] = task_id
+        metadata["task_name"] = str(task.get("name") or task_id)
+        metadata["input_dir"] = relative_path(self.root, processed_root)
+        metadata["raw_input_dir"] = relative_path(self.root, raw_root)
+        metadata_path.write_text(json.dumps(metadata, ensure_ascii=False, indent=2), encoding="utf-8")
+        return next(item for item in semantic_runs(self.root) if item["id"] == run_id)
+
     def send_json(self, status: HTTPStatus, payload: dict[str, Any]) -> None:
         body = json.dumps(payload, ensure_ascii=False).encode("utf-8")
         self.send_response(status)

--
Gitblit v1.9.3