Upgrade async GPU analysis and workbench UX

This commit is contained in:
Jens
2026-08-23 21:50:11 +02:00
parent 4040cbca7b
commit b996986d20
59 changed files with 3999 additions and 274 deletions
+57 -7
View File
@@ -2,9 +2,14 @@ from __future__ import annotations
from uuid import UUID
from fastapi import APIRouter, Depends, Query
from fastapi import APIRouter, Depends, Query, Request
from sqlalchemy.orm import Session
from app.api.guest_scope import (
assert_guest_project_scope,
guest_project_scope,
guest_scoped_project_filter,
)
from app.db.session import get_db
from app.schemas import (
AnalysisQaResponse,
@@ -25,6 +30,7 @@ from app.schemas import (
YoloPreflightResponse,
)
from app.services.detection_comparison_service import DetectionComparisonService
from app.services.dataset_service import DatasetService
from app.services.detection_service import DetectionService
from app.services.model_asset_catalog_service import ModelAssetCatalogService
from app.services.model_registry_service import ModelRegistryService
@@ -60,7 +66,12 @@ def get_yolo_preflight(
@router.post("/run", response_model=Envelope[DetectionRunResponse])
def run_detection(payload: DetectionRunRequest, db: Session = Depends(get_db)) -> dict:
def run_detection(
payload: DetectionRunRequest,
request: Request,
db: Session = Depends(get_db),
) -> dict:
assert_guest_project_scope(request, payload.project_id)
result = DetectionService.run_detection(
db=db,
project_id=payload.project_id,
@@ -76,7 +87,11 @@ def run_detection(payload: DetectionRunRequest, db: Session = Depends(get_db)) -
@router.post("/run-async", response_model=Envelope[JobRead])
def queue_detection(payload: DetectionRunRequest, db: Session = Depends(get_db)) -> dict:
def queue_detection(
payload: DetectionRunRequest,
request: Request,
db: Session = Depends(get_db),
) -> dict:
"""Queue a detection run for the background worker.
Tiled GPU inference takes minutes; ``POST /detection/run`` performs it
@@ -84,6 +99,7 @@ def queue_detection(payload: DetectionRunRequest, db: Session = Depends(get_db))
``GET /jobs/{id}`` for the queued run instead.
"""
assert_guest_project_scope(request, payload.project_id)
job = DetectionService.enqueue_detection(
db=db,
project_id=payload.project_id,
@@ -100,12 +116,14 @@ def queue_detection(payload: DetectionRunRequest, db: Session = Depends(get_db))
@router.get("/runs", response_model=Envelope[DetectionRunListResponse])
def list_detection_runs(
request: Request,
project_id: UUID | None = None,
dataset_id: UUID | None = None,
limit: int = Query(default=DetectionService.DEFAULT_RUN_LIST_LIMIT, ge=0, le=5_000),
offset: int = Query(default=0, ge=0),
db: Session = Depends(get_db),
) -> dict:
project_id = guest_scoped_project_filter(request, project_id)
return envelope(
DetectionService.list_runs(
db, project_id=project_id, dataset_id=dataset_id, limit=limit, offset=offset
@@ -114,8 +132,14 @@ def list_detection_runs(
@router.get("/runs/{analysis_run_id}", response_model=Envelope[DetectionRunRead])
def get_detection_run(analysis_run_id: UUID, db: Session = Depends(get_db)) -> dict:
return envelope(DetectionService.get_run(db, analysis_run_id).model_dump())
def get_detection_run(
analysis_run_id: UUID,
request: Request,
db: Session = Depends(get_db),
) -> dict:
run = DetectionService.get_run(db, analysis_run_id)
assert_guest_project_scope(request, run.project_id)
return envelope(run.model_dump())
@router.get(
@@ -124,6 +148,7 @@ def get_detection_run(analysis_run_id: UUID, db: Session = Depends(get_db)) -> d
)
def list_detection_run_detections(
analysis_run_id: UUID,
request: Request,
dataset_id: UUID | None = None,
class_name: str | None = None,
min_confidence: float | None = None,
@@ -136,6 +161,9 @@ def list_detection_run_detections(
offset: int = Query(default=0, ge=0),
db: Session = Depends(get_db),
) -> dict:
if guest_project_scope(request) is not None:
run = DetectionService.get_run(db, analysis_run_id)
assert_guest_project_scope(request, run.project_id)
return envelope(
DetectionService.list_detections(
db,
@@ -155,6 +183,7 @@ def list_detection_run_detections(
)
def list_dataset_detections(
dataset_id: UUID,
request: Request,
analysis_run_id: UUID | None = None,
class_name: str | None = None,
min_confidence: float | None = None,
@@ -167,6 +196,9 @@ def list_dataset_detections(
offset: int = Query(default=0, ge=0),
db: Session = Depends(get_db),
) -> dict:
if guest_project_scope(request) is not None:
dataset = DatasetService.get_dataset(db, dataset_id)
assert_guest_project_scope(request, dataset.project_id)
return envelope(
DetectionService.list_detections(
db,
@@ -181,8 +213,14 @@ def list_dataset_detections(
@router.get("/detections/{detection_id}", response_model=Envelope[DetectionRead])
def get_detection(detection_id: UUID, db: Session = Depends(get_db)) -> dict:
return envelope(DetectionService.get_detection(db, detection_id).model_dump())
def get_detection(
detection_id: UUID,
request: Request,
db: Session = Depends(get_db),
) -> dict:
detection = DetectionService.get_detection(db, detection_id)
assert_guest_project_scope(request, detection.project_id)
return envelope(detection.model_dump())
@router.get(
@@ -191,6 +229,7 @@ def get_detection(detection_id: UUID, db: Session = Depends(get_db)) -> dict:
)
def get_detection_run_geojson(
analysis_run_id: UUID,
request: Request,
class_name: str | None = None,
min_confidence: float | None = None,
limit: int = Query(
@@ -201,6 +240,9 @@ def get_detection_run_geojson(
),
db: Session = Depends(get_db),
) -> dict:
if guest_project_scope(request) is not None:
run = DetectionService.get_run(db, analysis_run_id)
assert_guest_project_scope(request, run.project_id)
return envelope(
DetectionService.detections_to_geojson(
db,
@@ -218,6 +260,7 @@ def get_detection_run_geojson(
)
def get_dataset_detection_geojson(
dataset_id: UUID,
request: Request,
analysis_run_id: UUID | None = None,
class_name: str | None = None,
min_confidence: float | None = None,
@@ -229,6 +272,9 @@ def get_dataset_detection_geojson(
),
db: Session = Depends(get_db),
) -> dict:
if guest_project_scope(request) is not None:
dataset = DatasetService.get_dataset(db, dataset_id)
assert_guest_project_scope(request, dataset.project_id)
return envelope(
DetectionService.detections_to_geojson(
db,
@@ -269,8 +315,12 @@ def compare_detection_runs(payload: DetectionComparisonRequest, db: Session = De
def compare_detection_run_with_reference(
analysis_run_id: UUID,
payload: DetectionQaRequest,
request: Request,
db: Session = Depends(get_db),
) -> dict:
if guest_project_scope(request) is not None:
run = DetectionService.get_run(db, analysis_run_id)
assert_guest_project_scope(request, run.project_id)
return envelope(
DetectionService.compare_detections_with_reference(
db=db,