feat: setup dataset enrichment app codebase and scripts
This commit is contained in:
1 parent
b5c28cc98a
commit
d07578462e
72 files changed
+11370
No files matched your search
@@ -0,0 +1,152 @@
|
||||
"""Batch, frame and auto-annotation routes (REQ-020…034)."""
|
||||
|
||||
import os
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Response
|
||||
from fastapi.responses import FileResponse
|
||||
from pydantic import BaseModel
|
||||
|
||||
from backend import autolabel, dataset, library
|
||||
from backend import batches as batch_store
|
||||
from backend import review as review_store
|
||||
from backend.api.common import project_or_404, thumbnail
|
||||
|
||||
router = APIRouter(tags=["batches"])
|
||||
|
||||
|
||||
class BatchRequest(BaseModel):
|
||||
rel: str
|
||||
start_sec: float = 0.0
|
||||
end_sec: float
|
||||
fps: float = 1.0
|
||||
|
||||
|
||||
class AutolabelRequest(BaseModel):
|
||||
engine: str = "sam3"
|
||||
engines: Optional[list[str]] = None
|
||||
class_ids: Optional[list[int]] = None
|
||||
engine_classes: Optional[dict[str, list[str]]] = None
|
||||
threshold: float = autolabel.DEFAULT_THRESHOLD
|
||||
iou_threshold: float = autolabel.DEFAULT_IOU
|
||||
min_box_frac: float = 0.0
|
||||
resume: bool = False
|
||||
|
||||
|
||||
@router.post("/api/projects/{project_id}/batches")
|
||||
def create_batch(project_id: int, request: BatchRequest) -> dict:
|
||||
try:
|
||||
return batch_store.create(project_id, request.rel, request.start_sec,
|
||||
request.end_sec, request.fps)
|
||||
except (batch_store.BatchError, library.LibraryError) as exc:
|
||||
raise HTTPException(400, str(exc))
|
||||
|
||||
|
||||
@router.get("/api/projects/{project_id}/batches")
|
||||
def list_batches(project_id: int) -> dict:
|
||||
project_or_404(project_id)
|
||||
return {"batches": batch_store.listing(project_id)}
|
||||
|
||||
|
||||
class BatchPatch(BaseModel):
|
||||
batch_label: Optional[str] = None
|
||||
date_label: Optional[str] = None
|
||||
status: Optional[str] = None
|
||||
|
||||
|
||||
@router.get("/api/batches/{batch_id}")
|
||||
def read_batch(batch_id: int) -> dict:
|
||||
batch = batch_store.get(batch_id)
|
||||
if batch is None:
|
||||
raise HTTPException(404, "No such batch")
|
||||
return batch
|
||||
|
||||
|
||||
@router.patch("/api/batches/{batch_id}")
|
||||
def update_batch(batch_id: int, request: BatchPatch) -> dict:
|
||||
try:
|
||||
return batch_store.update(batch_id, request.model_dump(exclude_unset=True))
|
||||
except batch_store.BatchError as exc:
|
||||
raise HTTPException(400, str(exc))
|
||||
|
||||
|
||||
@router.delete("/api/batches/{batch_id}")
|
||||
def delete_batch(batch_id: int) -> dict:
|
||||
if not batch_store.delete(batch_id):
|
||||
raise HTTPException(404, "No such batch")
|
||||
return {"deleted": True}
|
||||
|
||||
|
||||
|
||||
@router.get("/api/batches/{batch_id}/frames")
|
||||
def list_frames(batch_id: int) -> dict:
|
||||
if batch_store.get(batch_id) is None:
|
||||
raise HTTPException(404, "No such batch")
|
||||
return {"frames": batch_store.frames(batch_id)}
|
||||
|
||||
|
||||
@router.post("/api/batches/{batch_id}/autolabel")
|
||||
def start_autolabel(batch_id: int, request: AutolabelRequest) -> dict:
|
||||
try:
|
||||
engine_list = request.engines if (request.engines and len(request.engines) > 0) else [request.engine]
|
||||
return autolabel.start(batch_id, request.threshold, request.iou_threshold,
|
||||
request.min_box_frac, resume=request.resume,
|
||||
engines=engine_list, class_ids=request.class_ids,
|
||||
engine_classes=request.engine_classes)
|
||||
except batch_store.BatchError as exc:
|
||||
raise HTTPException(400, str(exc))
|
||||
|
||||
|
||||
|
||||
@router.post("/api/batches/{batch_id}/approve-all")
|
||||
def approve_all_batch_frames(batch_id: int) -> dict:
|
||||
if batch_store.get(batch_id) is None:
|
||||
raise HTTPException(404, "No such batch")
|
||||
updated = batch_store.approve_all_frames(batch_id)
|
||||
return {"approved_count": updated}
|
||||
|
||||
|
||||
@router.post("/api/batches/{batch_id}/approve")
|
||||
def approve_batch(batch_id: int) -> dict:
|
||||
try:
|
||||
return dataset.approve(batch_id)
|
||||
except dataset.DatasetError as exc:
|
||||
raise HTTPException(400, str(exc))
|
||||
|
||||
|
||||
@router.get("/api/projects/{project_id}/dataset")
|
||||
def dataset_summary(project_id: int) -> dict:
|
||||
project_or_404(project_id)
|
||||
return dataset.summary(project_id)
|
||||
|
||||
|
||||
@router.get("/api/projects/{project_id}/dataset/download")
|
||||
def dataset_download(project_id: int):
|
||||
project = project_or_404(project_id)
|
||||
try:
|
||||
path = dataset.zip_path(project)
|
||||
except dataset.DatasetError as exc:
|
||||
raise HTTPException(400, str(exc))
|
||||
return FileResponse(path, media_type="application/zip",
|
||||
filename=f"{project['slug']}-dataset.zip")
|
||||
|
||||
|
||||
@router.get("/api/frames/{frame_id}/image")
|
||||
def frame_image(frame_id: int, w: int = 0):
|
||||
path = batch_store.frame_path(frame_id)
|
||||
if path is None or not os.path.isfile(path):
|
||||
raise HTTPException(404, "No such frame")
|
||||
if w and 16 <= w <= 2048:
|
||||
return Response(content=thumbnail(path, w), media_type="image/jpeg",
|
||||
headers={"Cache-Control": "public, max-age=3600"})
|
||||
return FileResponse(path, media_type="image/jpeg",
|
||||
headers={"Cache-Control": "public, max-age=3600"})
|
||||
|
||||
|
||||
@router.delete("/api/batches/{batch_id}/classes/{class_id}/annotations")
|
||||
def clear_batch_class_annotations(batch_id: int, class_id: int) -> dict:
|
||||
if batch_store.get(batch_id) is None:
|
||||
raise HTTPException(404, "No such batch")
|
||||
deleted = review_store.clear_batch_class_annotations(batch_id, class_id)
|
||||
return {"deleted": deleted}
|
||||
|
||||
Reference in new issue
Block a user