Surface YOLO preflight in Detection Lab
GeoIntel CI / docs-smoke (push) Has been cancelled
GeoIntel CI / contract-smoke (push) Has been cancelled

This commit is contained in:
Codex
2026-07-06 10:52:26 +02:00
parent 58608383cd
commit 7aa9382c9e
14 changed files with 275 additions and 1 deletions
+1
View File
@@ -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)
+6
View File
@@ -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
+11
View File
@@ -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
+46
View File
@@ -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`.
+12
View File
@@ -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.
+1
View File
@@ -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.
+1
View File
@@ -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
+8
View File
@@ -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,
+4 -1
View File
@@ -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> =>
+37
View File
@@ -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