feat: setup dataset enrichment app codebase and scripts

This commit is contained in:
asus committed 2026-08-05 11:52:27 +07:00
1 parent b5c28cc98a
commit d07578462e
72 files changed
+11370

No files matched your search

+152
View File
@@ -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}