shuishen
6 days ago deac984180e54dcb904f415c8f2e095b8b1661a7
scripts/serve_workbench_console.py
@@ -78,6 +78,10 @@
POINTCLOUD_CLASS_CODES = {1, 2, 5, 6, 15, 16}
POINTCLOUD_CPU_ENVIRONMENT = "05-3d-pointcloud"
POINTCLOUD_GPU_ENVIRONMENT = "05-3d-pointcloud-gpu"
OBJECT_DETECTION_CPU_ENVIRONMENT = "01-object-detection"
OBJECT_DETECTION_GPU_ENVIRONMENT = "01-object-detection-cuda"
CHANGE_DETECTION_CPU_ENVIRONMENT = "00-change-detection"
CHANGE_DETECTION_GPU_ENVIRONMENT = "00-change-detection-cuda"
class ApiError(ValueError):
@@ -165,6 +169,54 @@
    return {"python": str(cpu_python), "device": "cpu", "environment": POINTCLOUD_CPU_ENVIRONMENT, "torchVersion": "unknown"}
def object_detection_execution_environment(root: Path) -> dict[str, str]:
    """Choose the fixed object-detection CUDA environment only after probing it."""
    cpu_python = root / ".venvs" / OBJECT_DETECTION_CPU_ENVIRONMENT / "Scripts" / "python.exe"
    gpu_python = root / ".venvs" / OBJECT_DETECTION_GPU_ENVIRONMENT / "Scripts" / "python.exe"
    if gpu_python.is_file():
        try:
            probe = subprocess.run(
                [str(gpu_python), "-c", "import json, torch; print(json.dumps({'cuda': bool(torch.cuda.is_available()), 'torch': torch.__version__}))"],
                cwd=root,
                capture_output=True,
                text=True,
                timeout=20,
                check=False,
            )
            payload = json.loads(probe.stdout.strip().splitlines()[-1]) if probe.returncode == 0 and probe.stdout.strip() else {}
            if payload.get("cuda") is True and isinstance(payload.get("torch"), str):
                return {"python": str(gpu_python), "device": "cuda", "environment": OBJECT_DETECTION_GPU_ENVIRONMENT, "torchVersion": payload["torch"]}
        except (OSError, subprocess.SubprocessError, json.JSONDecodeError, IndexError):
            pass
    if not cpu_python.is_file():
        raise ApiError("Object-detection CPU virtual environment is unavailable. Run the capability setup first.")
    return {"python": str(cpu_python), "device": "cpu", "environment": OBJECT_DETECTION_CPU_ENVIRONMENT, "torchVersion": "unknown"}
def change_detection_execution_environment(root: Path) -> dict[str, str]:
    """Choose the fixed ChangeStar CUDA environment only after probing it."""
    cpu_python = root / ".venvs" / CHANGE_DETECTION_CPU_ENVIRONMENT / "Scripts" / "python.exe"
    gpu_python = root / ".venvs" / CHANGE_DETECTION_GPU_ENVIRONMENT / "Scripts" / "python.exe"
    if gpu_python.is_file():
        try:
            probe = subprocess.run(
                [str(gpu_python), "-c", "import json, torch; print(json.dumps({'cuda': bool(torch.cuda.is_available()), 'torch': torch.__version__}))"],
                cwd=root,
                capture_output=True,
                text=True,
                timeout=20,
                check=False,
            )
            payload = json.loads(probe.stdout.strip().splitlines()[-1]) if probe.returncode == 0 and probe.stdout.strip() else {}
            if payload.get("cuda") is True and isinstance(payload.get("torch"), str):
                return {"python": str(gpu_python), "device": "cuda", "environment": CHANGE_DETECTION_GPU_ENVIRONMENT, "torchVersion": payload["torch"]}
        except (OSError, subprocess.SubprocessError, json.JSONDecodeError, IndexError):
            pass
    if not cpu_python.is_file():
        raise ApiError("Change-detection CPU virtual environment is unavailable. Run the capability setup first.")
    return {"python": str(cpu_python), "device": "cpu", "environment": CHANGE_DETECTION_CPU_ENVIRONMENT, "torchVersion": "unknown"}
def trajectory_runs(root: Path) -> list[dict[str, Any]]:
    output_root = root / "shared" / "outputs" / "15-trajectory-analysis"
    records: list[dict[str, Any]] = []
@@ -204,11 +256,13 @@
        except ValueError:
            continue
        run_id = artifact.name if artifact != output_root else "baseline"
        device = str(metadata.get("device") or "cpu").lower()
        execution = "GPU" if device.startswith("cuda") else "CPU"
        records.append(
            {
                "id": run_id,
                "label": "既有基线结果" if run_id == "baseline" else run_id,
                "note": "CPU 基线:人员与常见车辆;树木不在当前模型有效类别内。",
                "note": f"{execution} 基线:人员与常见车辆;树木不在当前模型有效类别内。",
                "artifactRoot": relative_path(root, artifact),
                "inputRoot": input_root,
                "createdAt": str(metadata.get("created_at") or ""),
@@ -250,10 +304,12 @@
        registered_before_path = artifact / registered_before_name if registered_before_name else None
        registered_after_path = artifact / registered_after_name if registered_after_name else None
        run_id = artifact.name
        device = str(metadata.get("device") or "cpu").lower()
        execution = "GPU" if device.startswith("cuda") else "CPU"
        record = {
                "id": run_id,
                "label": run_id,
                "note": "ChangeStar CPU 变化栅格与 GeoAI 像素坐标图斑;结果需人工复核。",
                "note": f"ChangeStar {execution} 变化栅格与 GeoAI 像素坐标图斑;结果需人工复核。",
                "artifactRoot": relative_path(root, artifact),
                "beforeImage": relative_path(root, before_path),
                "afterImage": relative_path(root, after_path),
@@ -1165,11 +1221,9 @@
        for name, content in decoded:
            (raw_root / name).write_bytes(content)
        output = self.root / "shared" / "outputs" / "01-object-detection" / "runs" / run_id
        python = self.root / ".venvs" / "01-object-detection" / "Scripts" / "python.exe"
        if not python.is_file():
            raise ApiError("Object-detection virtual environment is unavailable. Run the capability setup first.")
        execution = object_detection_execution_environment(self.root)
        with RUN_LOCK:
            self.run_command([str(python), str(self.root / "capabilities" / "01-object-detection" / "run_detection.py"), "--input", str(raw_root), "--output", str(output)], 1200)
            self.run_command([execution["python"], str(self.root / "capabilities" / "01-object-detection" / "run_detection.py"), "--input", str(raw_root), "--output", str(output), "--device", execution["device"]], 1200)
        if not (output / "run_metadata.json").is_file():
            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)
@@ -1219,19 +1273,18 @@
            after_path.write_bytes(decoded_after[1])
        processed_root = self.root / "shared" / "data" / "processed" / "00-change-detection" / run_id
        output = self.root / "shared" / "outputs" / "00-change-detection" / "runs" / run_id
        python = self.root / ".venvs" / "00-change-detection" / "Scripts" / "python.exe"
        if not python.is_file():
            raise ApiError("Change-detection virtual environment is unavailable. Run the capability setup first.")
        execution = change_detection_execution_environment(self.root)
        with RUN_LOCK:
            self.run_command(
                [
                    str(python),
                    execution["python"],
                    str(self.root / "capabilities" / "00-change-detection" / "run_change_detection.py"),
                    "--before", str(before_path),
                    "--after", str(after_path),
                    "--threshold", f"{threshold:.4f}",
                    "--max-dimension", str(max_dimension),
                    "--processing-mode", processing_mode,
                    "--device", execution["device"],
                    "--processed-output", str(processed_root),
                    "--output", str(output),
                ],
@@ -1428,21 +1481,21 @@
        processing_mode: str,
        max_dimension: int,
    ) -> None:
        python = self.root / ".venvs" / "00-change-detection" / "Scripts" / "python.exe"
        execution = change_detection_execution_environment(self.root)
        python = execution["python"]
        try:
            if not python.is_file():
                raise ApiError("Change-detection virtual environment is unavailable. Run the capability setup first.")
            self._update_scan_job(run_id, status="running", phase="inference")
            self._update_scan_job(run_id, status="running", phase="inference", device=execution["device"], environment=execution["environment"], torchVersion=execution["torchVersion"])
            with RUN_LOCK:
                self.run_command(
                    [
                        str(python),
                        python,
                        str(self.root / "capabilities" / "00-change-detection" / "run_change_detection.py"),
                        "--before", str(before_path),
                        "--after", str(after_path),
                        "--threshold", "0.5000",
                        "--max-dimension", str(max_dimension),
                        "--processing-mode", processing_mode,
                        "--device", execution["device"],
                        "--processed-output", str(processed_root),
                        "--output", str(inference_output),
                    ],
@@ -1455,7 +1508,7 @@
                inference_metadata_path.write_text(json.dumps(inference_metadata, ensure_ascii=False, indent=2), encoding="utf-8")
                self._update_scan_job(run_id, phase="parameter-scan")
                command = [
                    str(python),
                    python,
                    str(self.root / "capabilities" / "00-change-detection" / "scan_change_detection_parameters.py"),
                    "--run-dir", str(inference_output),
                    "--output", str(scan_output),
@@ -1467,7 +1520,7 @@
                self.run_command(command, SCAN_JOB_TIMEOUT)
                self._update_scan_job(run_id, phase="vectorization")
                vector_command = [
                    str(python),
                    python,
                    str(self.root / "capabilities" / "00-change-detection" / "materialize_parameter_scan_candidates.py"),
                    "--scan-dir", str(scan_output),
                ]
@@ -1484,6 +1537,9 @@
                "minimum_areas": areas,
                "processing_mode": processing_mode,
                "max_dimension": max_dimension,
                "device": execution["device"],
                "environment": execution["environment"],
                "torch_version": execution["torchVersion"],
                "raw_input_dir": relative_path(self.root, before_path.parent.parent),
                "processed_input_dir": relative_path(self.root, processed_root),
            }