From deac984180e54dcb904f415c8f2e095b8b1661a7 Mon Sep 17 00:00:00 2001
From: shuishen <1109946754@qq.com>
Date: Tue, 25 Aug 2026 14:56:08 +0800
Subject: [PATCH] feat(console): auto-select cuda for detection workflows
---
scripts/serve_workbench_console.py | 90 ++++++++++++++++++++++++++++++++++++--------
1 files changed, 73 insertions(+), 17 deletions(-)
diff --git a/scripts/serve_workbench_console.py b/scripts/serve_workbench_console.py
index b1d12ca..6a97f99 100644
--- a/scripts/serve_workbench_console.py
+++ b/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),
}
--
Gitblit v1.9.3