| | |
| | | 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): |
| | |
| | | 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]] = [] |
| | |
| | | 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 ""), |
| | |
| | | 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), |
| | |
| | | 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) |
| | |
| | | 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), |
| | | ], |
| | |
| | | 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), |
| | | ], |
| | |
| | | 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), |
| | |
| | | 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), |
| | | ] |
| | |
| | | "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), |
| | | } |