Surface YOLO preflight in Detection Lab
This commit is contained in:
@@ -19,6 +19,7 @@
|
|||||||
- Hardened the AI Docker runtime with OpenCV native libraries required by Ultralytics and made YOLO dependency detection use real imports instead of optimistic module discovery.
|
- Hardened the AI Docker runtime with OpenCV native libraries required by Ultralytics and made YOLO dependency detection use real imports instead of optimistic module discovery.
|
||||||
- Added a writable `YOLO_CONFIG_DIR` default under application storage so Ultralytics does not fall back to root user config paths in Docker/Unraid.
|
- Added a writable `YOLO_CONFIG_DIR` default under application storage so Ultralytics does not fall back to root user config paths in Docker/Unraid.
|
||||||
- Added YOLO preflight runtime diagnostics for dependency assumption state, model directory, `YOLO_CONFIG_DIR`, installed `torch`/`ultralytics` versions and CUDA availability without running inference or downloading weights.
|
- Added YOLO preflight runtime diagnostics for dependency assumption state, model directory, `YOLO_CONFIG_DIR`, installed `torch`/`ultralytics` versions and CUDA availability without running inference or downloading weights.
|
||||||
|
- Added a canonical `GET /api/v1/detection/yolo/preflight` endpoint and Detection Lab panel so operators can inspect live YOLO runtime readiness from the web UI.
|
||||||
- Added static regression coverage for the road basemap, attribution, basemap policy notice, database layer selector and persisted operational GIS workflow wiring.
|
- Added static regression coverage for the road basemap, attribution, basemap policy notice, database layer selector and persisted operational GIS workflow wiring.
|
||||||
|
|
||||||
## Sprint 115 QA/QC and Exports usability layout pass (2026-07-04)
|
## Sprint 115 QA/QC and Exports usability layout pass (2026-07-04)
|
||||||
|
|||||||
@@ -301,6 +301,12 @@ python scripts/yolo_preflight.py --model-path /absolute/path/to/local-model.pt -
|
|||||||
|
|
||||||
The preflight checks configuration, dependency availability, local model file existence, tile manifest validity, tile count and referenced tile paths. JSON output also includes runtime diagnostics for the model directory, `YOLO_CONFIG_DIR`, installed `torch`/`ultralytics` versions and CUDA availability when dependency checks pass. It does not load a YOLO model, run inference or download weights.
|
The preflight checks configuration, dependency availability, local model file existence, tile manifest validity, tile count and referenced tile paths. JSON output also includes runtime diagnostics for the model directory, `YOLO_CONFIG_DIR`, installed `torch`/`ultralytics` versions and CUDA availability when dependency checks pass. It does not load a YOLO model, run inference or download weights.
|
||||||
|
|
||||||
|
The same read-only status is available through the API and Detection Lab UI:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl http://localhost:1202/api/v1/detection/yolo/preflight
|
||||||
|
```
|
||||||
|
|
||||||
### Run backend
|
### Run backend
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ from app.db.session import get_db
|
|||||||
from app.schemas import DetectionQaRequest, DetectionRunRequest
|
from app.schemas import DetectionQaRequest, DetectionRunRequest
|
||||||
from app.services.detection_service import DetectionService
|
from app.services.detection_service import DetectionService
|
||||||
from app.services.model_registry_service import ModelRegistryService
|
from app.services.model_registry_service import ModelRegistryService
|
||||||
|
from app.services.yolo_preflight_service import YoloPreflightService
|
||||||
from app.utils.response import envelope
|
from app.utils.response import envelope
|
||||||
|
|
||||||
router = APIRouter(prefix="/detection", tags=["detection"])
|
router = APIRouter(prefix="/detection", tags=["detection"])
|
||||||
@@ -19,6 +20,16 @@ def list_detection_models() -> dict:
|
|||||||
return envelope({"models": [model.model_dump() for model in ModelRegistryService.list_model_capabilities()]})
|
return envelope({"models": [model.model_dump() for model in ModelRegistryService.list_model_capabilities()]})
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/yolo/preflight", response_model=dict)
|
||||||
|
def get_yolo_preflight(tile_manifest_path: str | None = None, check_model_load: bool = False) -> dict:
|
||||||
|
return envelope(
|
||||||
|
YoloPreflightService.run(
|
||||||
|
tile_manifest_path=tile_manifest_path,
|
||||||
|
check_model_load=check_model_load,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/run", response_model=dict)
|
@router.post("/run", response_model=dict)
|
||||||
def run_detection(payload: DetectionRunRequest, db: Session = Depends(get_db)) -> dict:
|
def run_detection(payload: DetectionRunRequest, db: Session = Depends(get_db)) -> dict:
|
||||||
result = DetectionService.run_detection(
|
result = DetectionService.run_detection(
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[2]
|
||||||
|
|
||||||
|
|
||||||
|
def test_detection_lab_surfaces_yolo_runtime_preflight() -> None:
|
||||||
|
lab = (ROOT / "frontend" / "src" / "components" / "detection" / "DetectionLab.tsx").read_text(
|
||||||
|
encoding="utf-8"
|
||||||
|
)
|
||||||
|
hook = (ROOT / "frontend" / "src" / "hooks" / "useDetectionWorkflow.ts").read_text(encoding="utf-8")
|
||||||
|
api = (ROOT / "frontend" / "src" / "services" / "api" / "detection.ts").read_text(encoding="utf-8")
|
||||||
|
types = (ROOT / "frontend" / "src" / "types.ts").read_text(encoding="utf-8")
|
||||||
|
app = (ROOT / "frontend" / "src" / "App.tsx").read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
assert "YOLO runtime preflight" in lab
|
||||||
|
assert "torch_version" in lab
|
||||||
|
assert "ultralytics_version" in lab
|
||||||
|
assert "cuda_available" in lab
|
||||||
|
assert "onRefreshYoloPreflight" in lab
|
||||||
|
assert "loadYoloPreflight" in hook
|
||||||
|
assert "getYoloPreflight" in api
|
||||||
|
assert "/api/v1/detection/yolo/preflight" in api
|
||||||
|
assert "interface YoloPreflightResponse" in types
|
||||||
|
assert "yoloPreflight={yoloPreflight}" in app
|
||||||
@@ -5,7 +5,10 @@ import subprocess
|
|||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
from fastapi.testclient import TestClient
|
||||||
|
|
||||||
from app.core.config import Settings
|
from app.core.config import Settings
|
||||||
|
from app.main import app
|
||||||
from app.services.yolo_preflight_service import YoloPreflightService
|
from app.services.yolo_preflight_service import YoloPreflightService
|
||||||
|
|
||||||
|
|
||||||
@@ -213,3 +216,21 @@ def test_yolo_preflight_script_rejects_assumed_dependencies_for_model_load(tmp_p
|
|||||||
|
|
||||||
assert result.returncode != 0
|
assert result.returncode != 0
|
||||||
assert "--check-model-load cannot be combined with --assume-dependencies" in result.stderr
|
assert "--check-model-load cannot be combined with --assume-dependencies" in result.stderr
|
||||||
|
|
||||||
|
|
||||||
|
def test_yolo_preflight_api_returns_canonical_envelope(monkeypatch, tmp_path: Path) -> None:
|
||||||
|
monkeypatch.setenv("YOLO_ENABLED", "false")
|
||||||
|
monkeypatch.setenv("YOLO_MODEL_PATH", str(tmp_path / "missing.pt"))
|
||||||
|
monkeypatch.setenv("YOLO_CONFIG_DIR", str(tmp_path / "ultralytics"))
|
||||||
|
|
||||||
|
response = TestClient(app).get("/api/v1/detection/yolo/preflight")
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
payload = response.json()
|
||||||
|
assert set(payload) == {"data"}
|
||||||
|
assert payload["data"]["status"] == "not_configured"
|
||||||
|
assert payload["data"]["checks"]["enabled"] is False
|
||||||
|
assert payload["data"]["runtime"]["model_directory"] == str(tmp_path)
|
||||||
|
assert payload["data"]["runtime"]["yolo_config_dir"] == str(tmp_path / "ultralytics")
|
||||||
|
assert payload["data"]["will_download_models"] is False
|
||||||
|
assert payload["data"]["will_run_inference"] is False
|
||||||
|
|||||||
@@ -636,6 +636,52 @@ Returns object-detection model capability descriptors.
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### GET `/api/v1/detection/yolo/preflight`
|
||||||
|
|
||||||
|
Returns a canonical envelope with read-only configured-YOLO runtime preflight
|
||||||
|
state. Optional query parameters:
|
||||||
|
|
||||||
|
- `tile_manifest_path`: existing raster tile manifest path to validate.
|
||||||
|
- `check_model_load`: default `false`; when `true`, explicitly loads only the
|
||||||
|
configured local model file for compatibility smoke. It never downloads
|
||||||
|
weights and never runs inference.
|
||||||
|
|
||||||
|
Response data:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"model_id": "yolo-configured",
|
||||||
|
"model_path": null,
|
||||||
|
"tile_manifest_path": null,
|
||||||
|
"status": "not_configured",
|
||||||
|
"message": "YOLO_MODEL_PATH is not set. GeoIntel will not download model weights automatically.",
|
||||||
|
"checks": {
|
||||||
|
"enabled": true,
|
||||||
|
"dependencies_available": true,
|
||||||
|
"model_path_set": false,
|
||||||
|
"model_file_exists": null,
|
||||||
|
"model_load_requested": false,
|
||||||
|
"model_load_ok": null,
|
||||||
|
"manifest_path_set": null,
|
||||||
|
"manifest_valid": null,
|
||||||
|
"tile_paths_exist": null,
|
||||||
|
"tile_limit_ok": null
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"dependencies_assumed": false,
|
||||||
|
"model_directory": null,
|
||||||
|
"yolo_config_dir": "/app/storage/ultralytics",
|
||||||
|
"torch_version": "2.12.1",
|
||||||
|
"ultralytics_version": "8.4.88",
|
||||||
|
"cuda_available": false
|
||||||
|
},
|
||||||
|
"tile_count": 0,
|
||||||
|
"max_tiles": 100,
|
||||||
|
"will_download_models": false,
|
||||||
|
"will_run_inference": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### POST `/api/v1/detection/run`
|
### POST `/api/v1/detection/run`
|
||||||
|
|
||||||
Creates a detection job and detection analysis run. If the requested model is unavailable, the job and analysis run are marked `failed` with `DETECTION_MODEL_UNAVAILABLE` or `DETECTION_DEPENDENCY_UNAVAILABLE`.
|
Creates a detection job and detection analysis run. If the requested model is unavailable, the job and analysis run are marked `failed` with `DETECTION_MODEL_UNAVAILABLE` or `DETECTION_DEPENDENCY_UNAVAILABLE`.
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ Changed:
|
|||||||
- Hardened the AI image path after Tower validation showed `torch` imported but `ultralytics` failed on a missing OpenCV native library. The Dockerfiles now include the required OpenCV runtime shared libraries and YOLO dependency detection performs real imports instead of `find_spec` checks.
|
- Hardened the AI image path after Tower validation showed `torch` imported but `ultralytics` failed on a missing OpenCV native library. The Dockerfiles now include the required OpenCV runtime shared libraries and YOLO dependency detection performs real imports instead of `find_spec` checks.
|
||||||
- Added a writable `YOLO_CONFIG_DIR` default under application storage after Tower validation showed Ultralytics otherwise falls back to `/tmp` because root config is not writable in the container.
|
- Added a writable `YOLO_CONFIG_DIR` default under application storage after Tower validation showed Ultralytics otherwise falls back to `/tmp` because root config is not writable in the container.
|
||||||
- Added YOLO preflight runtime diagnostics so operators can see dependency assumption state, model directory, `YOLO_CONFIG_DIR`, installed `torch`/`ultralytics` package versions and CUDA availability without loading a model, running inference or downloading weights.
|
- Added YOLO preflight runtime diagnostics so operators can see dependency assumption state, model directory, `YOLO_CONFIG_DIR`, installed `torch`/`ultralytics` package versions and CUDA availability without loading a model, running inference or downloading weights.
|
||||||
|
- Added a read-only `GET /api/v1/detection/yolo/preflight` endpoint and Detection Lab YOLO runtime preflight panel so browser operators can inspect live AI runtime readiness without loading a model, running inference or downloading weights.
|
||||||
- Updated `.env.example`, `backend/README.md`, `frontend/README.md`, `scripts/README.md`, `docs/AI_PIPELINES.md`, `docs/TODO.md` and `CHANGELOG.md`.
|
- Updated `.env.example`, `backend/README.md`, `frontend/README.md`, `scripts/README.md`, `docs/AI_PIPELINES.md`, `docs/TODO.md` and `CHANGELOG.md`.
|
||||||
- Added regression coverage in `backend/tests/test_sprint116_operational_gis_map_workflow.py`, `backend/tests/test_sprint8b_yolo_foundation.py` and `backend/tests/test_docker_runtime_config.py`.
|
- Added regression coverage in `backend/tests/test_sprint116_operational_gis_map_workflow.py`, `backend/tests/test_sprint8b_yolo_foundation.py` and `backend/tests/test_docker_runtime_config.py`.
|
||||||
|
|
||||||
@@ -54,6 +55,17 @@ Validation:
|
|||||||
- Deploy-time live migration smoke passed on Tower; PostGIS reported `3.6 USE_GEOS=1 USE_PROJ=1 USE_STATS=1` and Alembic head was `202606120900`.
|
- Deploy-time live migration smoke passed on Tower; PostGIS reported `3.6 USE_GEOS=1 USE_PROJ=1 USE_STATS=1` and Alembic head was `202606120900`.
|
||||||
- Deploy-time browser runtime verification passed for `http://192.168.10.150:1202`, API proxy and icon.
|
- Deploy-time browser runtime verification passed for `http://192.168.10.150:1202`, API proxy and icon.
|
||||||
- Tower container check passed: remote checkout is `7a29e78`, `geointel` is healthy on `0.0.0.0:1202->80/tcp`, and `scripts/yolo_preflight.py --enabled --json` reports `dependencies_available=true`, `torch_version=2.12.1`, `ultralytics_version=8.4.88`, `cuda_available=false`, `yolo_config_dir=/app/storage/ultralytics`, `status=not_configured`, `will_download_models=false` and `will_run_inference=false`.
|
- Tower container check passed: remote checkout is `7a29e78`, `geointel` is healthy on `0.0.0.0:1202->80/tcp`, and `scripts/yolo_preflight.py --enabled --json` reports `dependencies_available=true`, `torch_version=2.12.1`, `ultralytics_version=8.4.88`, `cuda_available=false`, `yolo_config_dir=/app/storage/ultralytics`, `status=not_configured`, `will_download_models=false` and `will_run_inference=false`.
|
||||||
|
- RED: `python -m pytest backend\tests\test_sprint13_yolo_preflight.py::test_yolo_preflight_api_returns_canonical_envelope backend\tests\test_sprint118_yolo_preflight_ui.py -q` failed before implementation because `/api/v1/detection/yolo/preflight` returned 404 and the Detection Lab did not surface a YOLO runtime preflight panel.
|
||||||
|
- `python -m pytest backend\tests\test_sprint13_yolo_preflight.py::test_yolo_preflight_api_returns_canonical_envelope backend\tests\test_sprint118_yolo_preflight_ui.py -q` passed: 2 tests.
|
||||||
|
- `cd frontend && npm run typecheck` passed after adding the preflight API client and Detection Lab panel.
|
||||||
|
- `python -m pytest backend\tests\test_sprint13_yolo_preflight.py backend\tests\test_sprint118_yolo_preflight_ui.py backend\tests\test_sprint48_api_contract_audit.py -q` passed: 13 tests.
|
||||||
|
- `python -m compileall backend/app` passed.
|
||||||
|
- `cd backend && python -m pytest -q` passed: 371 tests.
|
||||||
|
- `cd frontend && npm run build` passed.
|
||||||
|
- `bash scripts/run_readiness_check.sh` passed: 371 backend tests plus frontend typecheck/build and API contract audit for 80 documented routes.
|
||||||
|
- `cd backend && python -m alembic heads` passed: `202606120900 (head)`.
|
||||||
|
- `cd backend && python -m alembic upgrade head --sql` passed.
|
||||||
|
- `bash -n scripts/live_migration_smoke.sh` and `bash -n scripts/deploy_tower.sh` passed.
|
||||||
|
|
||||||
Limitations:
|
Limitations:
|
||||||
- `GEOINTEL_INSTALL_AI=true` installs optional PyTorch/Ultralytics dependencies but still requires a user-provided local model file; GeoIntel does not download weights.
|
- `GEOINTEL_INSTALL_AI=true` installs optional PyTorch/Ultralytics dependencies but still requires a user-provided local model file; GeoIntel does not download weights.
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ This file now starts with the current implementation status. Older preparation/b
|
|||||||
- [x] Add basemap policy notice and guided GIS query-to-QA/export workflow in the Map workspace.
|
- [x] Add basemap policy notice and guided GIS query-to-QA/export workflow in the Map workspace.
|
||||||
- [x] Add reusable latest-result mode for repeated Map QA/QC runs without duplicate derived artifacts.
|
- [x] Add reusable latest-result mode for repeated Map QA/QC runs without duplicate derived artifacts.
|
||||||
- [x] Add opt-in Docker/Unraid AI build/runtime path for local PyTorch/Ultralytics YOLO operation.
|
- [x] Add opt-in Docker/Unraid AI build/runtime path for local PyTorch/Ultralytics YOLO operation.
|
||||||
|
- [x] Surface configured-YOLO runtime preflight status through the API and Detection Lab UI.
|
||||||
- [x] Add one-click full GIS workflow action for query, derived dataset, QA/QC and export handoff.
|
- [x] Add one-click full GIS workflow action for query, derived dataset, QA/QC and export handoff.
|
||||||
- [x] Add QA/QC workspace result hierarchy and filter density polish.
|
- [x] Add QA/QC workspace result hierarchy and filter density polish.
|
||||||
- [x] Add Change Detection panel hierarchy and analysis workspace density polish.
|
- [x] Add Change Detection panel hierarchy and analysis workspace density polish.
|
||||||
|
|||||||
@@ -120,6 +120,7 @@ AI Lab run controls explicitly explain when no raster dataset is available, inst
|
|||||||
## Sprint 8B additions
|
## Sprint 8B additions
|
||||||
- Detection Lab now exposes the `yolo-configured` capability reported by the backend.
|
- Detection Lab now exposes the `yolo-configured` capability reported by the backend.
|
||||||
- When `yolo-configured` is selected, users can provide an existing raster tile manifest path.
|
- When `yolo-configured` is selected, users can provide an existing raster tile manifest path.
|
||||||
|
- Detection Lab includes a read-only YOLO runtime preflight panel with backend status, dependency visibility, local model configuration, `torch`/`ultralytics` versions, CUDA state and `YOLO_CONFIG_DIR`.
|
||||||
- The UI still does not download models or create fake detections; backend status and error codes remain the source of truth.
|
- The UI still does not download models or create fake detections; backend status and error codes remain the source of truth.
|
||||||
|
|
||||||
## Sprint 8C additions
|
## Sprint 8C additions
|
||||||
|
|||||||
@@ -242,7 +242,11 @@ function App(): JSX.Element {
|
|||||||
detectionQaResult,
|
detectionQaResult,
|
||||||
detectionQaError,
|
detectionQaError,
|
||||||
runningDetectionQa,
|
runningDetectionQa,
|
||||||
|
yoloPreflight,
|
||||||
|
loadingYoloPreflight,
|
||||||
|
yoloPreflightError,
|
||||||
loadDetectionModels,
|
loadDetectionModels,
|
||||||
|
loadYoloPreflight,
|
||||||
loadDetectionRuns,
|
loadDetectionRuns,
|
||||||
loadDetectionResults,
|
loadDetectionResults,
|
||||||
runDetection,
|
runDetection,
|
||||||
@@ -945,10 +949,14 @@ function App(): JSX.Element {
|
|||||||
detectionQaResult={detectionQaResult}
|
detectionQaResult={detectionQaResult}
|
||||||
detectionQaError={detectionQaError}
|
detectionQaError={detectionQaError}
|
||||||
runningDetectionQa={runningDetectionQa}
|
runningDetectionQa={runningDetectionQa}
|
||||||
|
yoloPreflight={yoloPreflight}
|
||||||
|
loadingYoloPreflight={loadingYoloPreflight}
|
||||||
|
yoloPreflightError={yoloPreflightError}
|
||||||
selectedProjectId={selectedProjectId}
|
selectedProjectId={selectedProjectId}
|
||||||
rasterDatasets={rasterDatasets}
|
rasterDatasets={rasterDatasets}
|
||||||
referenceDatasets={referenceDatasets}
|
referenceDatasets={referenceDatasets}
|
||||||
onLoadModels={loadDetectionModels}
|
onLoadModels={loadDetectionModels}
|
||||||
|
onRefreshYoloPreflight={() => loadYoloPreflight()}
|
||||||
onSelectDataset={setSelectedDetectionDatasetId}
|
onSelectDataset={setSelectedDetectionDatasetId}
|
||||||
onSelectModel={setSelectedDetectionModelId}
|
onSelectModel={setSelectedDetectionModelId}
|
||||||
onSetConfidenceThreshold={setDetectionConfidenceThreshold}
|
onSetConfidenceThreshold={setDetectionConfidenceThreshold}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import type {
|
|||||||
DetectionRead,
|
DetectionRead,
|
||||||
DetectionRunRead,
|
DetectionRunRead,
|
||||||
DetectionRunResponse,
|
DetectionRunResponse,
|
||||||
|
YoloPreflightResponse,
|
||||||
} from '../../types'
|
} from '../../types'
|
||||||
|
|
||||||
interface DetectionLabProps {
|
interface DetectionLabProps {
|
||||||
@@ -28,10 +29,14 @@ interface DetectionLabProps {
|
|||||||
detectionQaResult: DetectionQaResult | null
|
detectionQaResult: DetectionQaResult | null
|
||||||
detectionQaError: string | null
|
detectionQaError: string | null
|
||||||
runningDetectionQa: boolean
|
runningDetectionQa: boolean
|
||||||
|
yoloPreflight: YoloPreflightResponse | null
|
||||||
|
loadingYoloPreflight: boolean
|
||||||
|
yoloPreflightError: string | null
|
||||||
selectedProjectId: string | null
|
selectedProjectId: string | null
|
||||||
rasterDatasets: DatasetCreateResponse[]
|
rasterDatasets: DatasetCreateResponse[]
|
||||||
referenceDatasets: DatasetCreateResponse[]
|
referenceDatasets: DatasetCreateResponse[]
|
||||||
onLoadModels: () => void
|
onLoadModels: () => void
|
||||||
|
onRefreshYoloPreflight: () => void
|
||||||
onSelectDataset: (datasetId: string) => void
|
onSelectDataset: (datasetId: string) => void
|
||||||
onSelectModel: (modelId: string) => void
|
onSelectModel: (modelId: string) => void
|
||||||
onSetConfidenceThreshold: (value: number) => void
|
onSetConfidenceThreshold: (value: number) => void
|
||||||
@@ -67,10 +72,14 @@ export function DetectionLab({
|
|||||||
detectionQaResult,
|
detectionQaResult,
|
||||||
detectionQaError,
|
detectionQaError,
|
||||||
runningDetectionQa,
|
runningDetectionQa,
|
||||||
|
yoloPreflight,
|
||||||
|
loadingYoloPreflight,
|
||||||
|
yoloPreflightError,
|
||||||
selectedProjectId,
|
selectedProjectId,
|
||||||
rasterDatasets,
|
rasterDatasets,
|
||||||
referenceDatasets,
|
referenceDatasets,
|
||||||
onLoadModels,
|
onLoadModels,
|
||||||
|
onRefreshYoloPreflight,
|
||||||
onSelectDataset,
|
onSelectDataset,
|
||||||
onSelectModel,
|
onSelectModel,
|
||||||
onSetConfidenceThreshold,
|
onSetConfidenceThreshold,
|
||||||
@@ -164,6 +173,76 @@ export function DetectionLab({
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="ai-lab-model-surface" aria-label="YOLO runtime preflight">
|
||||||
|
<div className="ai-lab-section-header">
|
||||||
|
<div>
|
||||||
|
<h3>YOLO runtime preflight</h3>
|
||||||
|
<p>Read-only runtime status. This does not load a model, run inference or download weights.</p>
|
||||||
|
</div>
|
||||||
|
<button className="secondary-action" type="button" onClick={onRefreshYoloPreflight} disabled={loadingYoloPreflight}>
|
||||||
|
Refresh preflight
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="ai-lab-state-stack">
|
||||||
|
{loadingYoloPreflight ? (
|
||||||
|
<div className="result-state result-state-loading">
|
||||||
|
<strong>Loading YOLO preflight.</strong>
|
||||||
|
<p>Checking backend runtime configuration and optional dependency visibility.</p>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
{yoloPreflightError ? (
|
||||||
|
<div className="result-state result-state-error">
|
||||||
|
<strong>YOLO preflight unavailable.</strong>
|
||||||
|
<p>{yoloPreflightError}</p>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
{!yoloPreflight && !loadingYoloPreflight && !yoloPreflightError ? (
|
||||||
|
<div className="result-state result-state-empty">
|
||||||
|
<strong>No YOLO preflight loaded.</strong>
|
||||||
|
<p>Refresh preflight to inspect the live backend AI runtime before running configured YOLO.</p>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
{yoloPreflight ? (
|
||||||
|
<div className={yoloPreflight.status === 'ready' ? 'lab-readiness-panel lab-readiness-panel-ready' : 'lab-readiness-panel'}>
|
||||||
|
<div className="ai-lab-section-header">
|
||||||
|
<div>
|
||||||
|
<h3>Status: {yoloPreflight.status}</h3>
|
||||||
|
<p>{yoloPreflight.message}</p>
|
||||||
|
</div>
|
||||||
|
<span className={yoloPreflight.status === 'ready' ? 'status-badge status-badge-ready' : 'status-badge'}>
|
||||||
|
{yoloPreflight.checks.dependencies_available ? 'dependencies visible' : 'not ready'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="lab-readiness-grid">
|
||||||
|
<div className={yoloPreflight.checks.enabled ? 'lab-readiness-item lab-readiness-item-ready' : 'lab-readiness-item'}>
|
||||||
|
<span>YOLO enabled</span>
|
||||||
|
<strong>{yoloPreflight.checks.enabled ? 'true' : 'false'}</strong>
|
||||||
|
</div>
|
||||||
|
<div className={yoloPreflight.checks.dependencies_available ? 'lab-readiness-item lab-readiness-item-ready' : 'lab-readiness-item'}>
|
||||||
|
<span>Dependencies</span>
|
||||||
|
<strong>{yoloPreflight.checks.dependencies_available === true ? 'available' : yoloPreflight.checks.dependencies_available === false ? 'unavailable' : 'not checked'}</strong>
|
||||||
|
</div>
|
||||||
|
<div className={yoloPreflight.checks.model_file_exists ? 'lab-readiness-item lab-readiness-item-ready' : 'lab-readiness-item'}>
|
||||||
|
<span>Local model file</span>
|
||||||
|
<strong>{yoloPreflight.checks.model_file_exists === true ? 'found' : yoloPreflight.checks.model_path_set ? 'missing' : 'not configured'}</strong>
|
||||||
|
</div>
|
||||||
|
<div className={yoloPreflight.runtime.cuda_available ? 'lab-readiness-item lab-readiness-item-ready' : 'lab-readiness-item'}>
|
||||||
|
<span>CUDA</span>
|
||||||
|
<strong>{yoloPreflight.runtime.cuda_available === true ? 'available' : yoloPreflight.runtime.cuda_available === false ? 'not available' : 'not checked'}</strong>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="entity-meta">
|
||||||
|
<span>torch_version: {yoloPreflight.runtime.torch_version ?? 'n/a'}</span>
|
||||||
|
<span>ultralytics_version: {yoloPreflight.runtime.ultralytics_version ?? 'n/a'}</span>
|
||||||
|
<span>cuda_available: {String(yoloPreflight.runtime.cuda_available ?? 'unknown')}</span>
|
||||||
|
<span>YOLO_CONFIG_DIR: {yoloPreflight.runtime.yolo_config_dir ?? 'n/a'}</span>
|
||||||
|
<span>model directory: {yoloPreflight.runtime.model_directory ?? 'n/a'}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="lab-block">
|
<div className="lab-block">
|
||||||
<div className="ai-lab-run-surface" aria-label="Detection run controls">
|
<div className="ai-lab-run-surface" aria-label="Detection run controls">
|
||||||
<h3>Run detection</h3>
|
<h3>Run detection</h3>
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import type {
|
|||||||
DetectionRunRead,
|
DetectionRunRead,
|
||||||
DetectionRunResponse,
|
DetectionRunResponse,
|
||||||
QualityCheckRead,
|
QualityCheckRead,
|
||||||
|
YoloPreflightResponse,
|
||||||
} from '../types'
|
} from '../types'
|
||||||
import { formatError } from '../lib/formatError'
|
import { formatError } from '../lib/formatError'
|
||||||
|
|
||||||
@@ -47,6 +48,9 @@ export function useDetectionWorkflow({
|
|||||||
const [detectionQaResult, setDetectionQaResult] = useState<DetectionQaResult | null>(null)
|
const [detectionQaResult, setDetectionQaResult] = useState<DetectionQaResult | null>(null)
|
||||||
const [detectionQaError, setDetectionQaError] = useState<string | null>(null)
|
const [detectionQaError, setDetectionQaError] = useState<string | null>(null)
|
||||||
const [runningDetectionQa, setRunningDetectionQa] = useState(false)
|
const [runningDetectionQa, setRunningDetectionQa] = useState(false)
|
||||||
|
const [yoloPreflight, setYoloPreflight] = useState<YoloPreflightResponse | null>(null)
|
||||||
|
const [loadingYoloPreflight, setLoadingYoloPreflight] = useState(false)
|
||||||
|
const [yoloPreflightError, setYoloPreflightError] = useState<string | null>(null)
|
||||||
|
|
||||||
const loadDetectionModels = async () => {
|
const loadDetectionModels = async () => {
|
||||||
setLoadingDetectionModels(true)
|
setLoadingDetectionModels(true)
|
||||||
@@ -64,6 +68,21 @@ export function useDetectionWorkflow({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const loadYoloPreflight = async (tileManifestPath = detectionTileManifestPath) => {
|
||||||
|
setLoadingYoloPreflight(true)
|
||||||
|
setYoloPreflightError(null)
|
||||||
|
try {
|
||||||
|
const response = await detectionApi.getYoloPreflight({
|
||||||
|
tile_manifest_path: tileManifestPath.trim() || null,
|
||||||
|
})
|
||||||
|
setYoloPreflight(response)
|
||||||
|
} catch (error) {
|
||||||
|
setYoloPreflightError(formatError(error, 'Failed to load YOLO preflight status'))
|
||||||
|
} finally {
|
||||||
|
setLoadingYoloPreflight(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const loadDetectionRuns = async (projectId = selectedProjectId) => {
|
const loadDetectionRuns = async (projectId = selectedProjectId) => {
|
||||||
if (!projectId) {
|
if (!projectId) {
|
||||||
setDetectionRuns([])
|
setDetectionRuns([])
|
||||||
@@ -199,7 +218,11 @@ export function useDetectionWorkflow({
|
|||||||
detectionQaResult,
|
detectionQaResult,
|
||||||
detectionQaError,
|
detectionQaError,
|
||||||
runningDetectionQa,
|
runningDetectionQa,
|
||||||
|
yoloPreflight,
|
||||||
|
loadingYoloPreflight,
|
||||||
|
yoloPreflightError,
|
||||||
loadDetectionModels,
|
loadDetectionModels,
|
||||||
|
loadYoloPreflight,
|
||||||
loadDetectionRuns,
|
loadDetectionRuns,
|
||||||
loadDetectionResults,
|
loadDetectionResults,
|
||||||
runDetection,
|
runDetection,
|
||||||
|
|||||||
@@ -8,9 +8,10 @@ import type {
|
|||||||
DetectionRunRead,
|
DetectionRunRead,
|
||||||
DetectionRunRequest,
|
DetectionRunRequest,
|
||||||
DetectionRunResponse,
|
DetectionRunResponse,
|
||||||
|
YoloPreflightResponse,
|
||||||
} from '../../types'
|
} from '../../types'
|
||||||
|
|
||||||
function queryString(params: Record<string, string | number | null | undefined>): string {
|
function queryString(params: Record<string, string | number | boolean | null | undefined>): string {
|
||||||
const searchParams = new URLSearchParams()
|
const searchParams = new URLSearchParams()
|
||||||
Object.entries(params).forEach(([key, value]) => {
|
Object.entries(params).forEach(([key, value]) => {
|
||||||
if (value !== null && value !== undefined && value !== '') {
|
if (value !== null && value !== undefined && value !== '') {
|
||||||
@@ -23,6 +24,8 @@ function queryString(params: Record<string, string | number | null | undefined>)
|
|||||||
|
|
||||||
export const detectionApi = {
|
export const detectionApi = {
|
||||||
listModels: (): Promise<DetectionModelsResponse> => apiGet<DetectionModelsResponse>('/api/v1/detection/models'),
|
listModels: (): Promise<DetectionModelsResponse> => apiGet<DetectionModelsResponse>('/api/v1/detection/models'),
|
||||||
|
getYoloPreflight: (params: { tile_manifest_path?: string | null; check_model_load?: boolean | null } = {}): Promise<YoloPreflightResponse> =>
|
||||||
|
apiGet<YoloPreflightResponse>(`/api/v1/detection/yolo/preflight${queryString(params)}`),
|
||||||
run: (payload: DetectionRunRequest): Promise<DetectionRunResponse> =>
|
run: (payload: DetectionRunRequest): Promise<DetectionRunResponse> =>
|
||||||
apiPost<DetectionRunResponse>('/api/v1/detection/run', payload),
|
apiPost<DetectionRunResponse>('/api/v1/detection/run', payload),
|
||||||
listRuns: (params: { project_id?: string | null; dataset_id?: string | null } = {}): Promise<DetectionRunListResponse> =>
|
listRuns: (params: { project_id?: string | null; dataset_id?: string | null } = {}): Promise<DetectionRunListResponse> =>
|
||||||
|
|||||||
@@ -404,6 +404,43 @@ export interface DetectionModelsResponse {
|
|||||||
models: DetectionModelCapability[]
|
models: DetectionModelCapability[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface YoloPreflightChecks {
|
||||||
|
enabled: boolean
|
||||||
|
dependencies_available?: boolean | null
|
||||||
|
model_path_set?: boolean | null
|
||||||
|
model_file_exists?: boolean | null
|
||||||
|
model_load_requested: boolean
|
||||||
|
model_load_ok?: boolean | null
|
||||||
|
manifest_path_set?: boolean | null
|
||||||
|
manifest_valid?: boolean | null
|
||||||
|
tile_paths_exist?: boolean | null
|
||||||
|
tile_limit_ok?: boolean | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface YoloPreflightRuntime {
|
||||||
|
dependencies_assumed: boolean
|
||||||
|
model_directory?: string | null
|
||||||
|
yolo_config_dir?: string | null
|
||||||
|
torch_version?: string | null
|
||||||
|
ultralytics_version?: string | null
|
||||||
|
cuda_available?: boolean | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface YoloPreflightResponse {
|
||||||
|
model_id: string
|
||||||
|
model_path?: string | null
|
||||||
|
tile_manifest_path?: string | null
|
||||||
|
status: string
|
||||||
|
message: string
|
||||||
|
checks: YoloPreflightChecks
|
||||||
|
runtime: YoloPreflightRuntime
|
||||||
|
tile_count: number
|
||||||
|
max_tiles: number
|
||||||
|
will_download_models: boolean
|
||||||
|
will_run_inference: boolean
|
||||||
|
error_code?: string | null
|
||||||
|
}
|
||||||
|
|
||||||
export interface DetectionRunRequest {
|
export interface DetectionRunRequest {
|
||||||
project_id: string
|
project_id: string
|
||||||
dataset_id: string
|
dataset_id: string
|
||||||
|
|||||||
Reference in New Issue
Block a user