Files
feedmill-auto-label/backend/api/review.py
T
asus 5c7c122105 feat: add counting bench, triage, and dataset modules
This commit includes major additions and updates to the frontend and backend architectures, introducing new dataset management, live counting features, batch processing, and triage logic. Includes new UI pages, components, and API routes.
2026-08-14 16:28:52 +07:00

115 lines
3.5 KiB
Python

"""Annotation and review-state routes (REQ-040…045)."""
from typing import Any, Dict, List, Optional
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel
from backend import batches as batch_store
from backend import review as review_store
router = APIRouter(tags=["review"])
class AnnotationRequest(BaseModel):
class_id: int = 0
geometry: Dict[str, Any]
class AnnotationPatch(BaseModel):
class_id: Optional[int] = None
geometry: Optional[Dict[str, Any]] = None
class BulkRequest(BaseModel):
annotation_ids: List[int]
class BulkReclassRequest(BaseModel):
annotation_ids: List[int]
class_id: int
class StatusRequest(BaseModel):
status: str
class AssistRequest(BaseModel):
box: List[float]
class_id: int = 0
threshold: float = 0.5
@router.get("/api/frames/{frame_id}/annotations")
def list_annotations(frame_id: int) -> dict:
target = review_store.frame(frame_id)
if target is None:
raise HTTPException(404, "No such frame")
return {
"frame": {
"id": target["id"], "batch_id": target["batch_id"], "idx": target["idx"],
"width": target["width"], "height": target["height"],
"review_status": target["review_status"], "label_type": target["label_type"],
},
"annotations": review_store.listing(frame_id),
}
@router.post("/api/frames/{frame_id}/annotations")
def add_annotation(frame_id: int, request: AnnotationRequest) -> dict:
try:
return review_store.add(frame_id, request.class_id, request.geometry)
except review_store.ReviewError as exc:
raise HTTPException(400, str(exc))
@router.patch("/api/annotations/{annotation_id}")
def patch_annotation(annotation_id: int, request: AnnotationPatch) -> dict:
try:
return review_store.update(annotation_id, request.class_id, request.geometry)
except review_store.ReviewError as exc:
raise HTTPException(400, str(exc))
@router.delete("/api/annotations/{annotation_id}")
def delete_annotation(annotation_id: int) -> dict:
return {"deleted": review_store.delete(annotation_id)}
@router.post("/api/annotations/bulk-delete")
def bulk_delete(request: BulkRequest) -> dict:
return {"deleted": review_store.delete_many(request.annotation_ids)}
@router.post("/api/annotations/bulk-reclass")
def bulk_reclass(request: BulkReclassRequest) -> dict:
try:
return {"updated": review_store.reclass_many(request.annotation_ids,
request.class_id)}
except review_store.ReviewError as exc:
raise HTTPException(400, str(exc))
@router.post("/api/frames/{frame_id}/assist")
def assist(frame_id: int, request: AssistRequest) -> dict:
try:
return review_store.assist(frame_id, request.box, request.class_id,
request.threshold)
except review_store.ReviewError as exc:
raise HTTPException(400, str(exc))
@router.post("/api/frames/{frame_id}/status")
def set_status(frame_id: int, request: StatusRequest) -> dict:
try:
return review_store.set_status(frame_id, request.status)
except review_store.ReviewError as exc:
raise HTTPException(400, str(exc))
@router.get("/api/batches/{batch_id}/next-pending")
def next_pending(batch_id: int, after_idx: int = -1) -> dict:
if batch_store.get(batch_id) is None:
raise HTTPException(404, "No such batch")
return {"frame_id": review_store.next_pending(batch_id, after_idx)}