Buck_Tracker / api /detection.py
codewithRiz's picture
edit tage button added
3c474bb
# from fastapi import APIRouter, UploadFile, File, Form, HTTPException
# from pathlib import Path
# import cv2
# import numpy as np
# from .config import UPLOAD_DIR
# from .utils import (
# validate_form,
# process_image,
# save_image,
# load_json,
# save_json,
# validate_user_and_camera,extract_metadata
# )
# router = APIRouter()
# @router.post("/predict")
# async def predict(
# user_id: str = Form(...),
# camera_name: str = Form(...),
# images: list[UploadFile] = File(...)
# ):
# images = validate_form(user_id, camera_name, images)
# validate_user_and_camera(user_id, camera_name)
# base = Path(UPLOAD_DIR) / user_id / camera_name
# base.mkdir(parents=True, exist_ok=True)
# json_path = base / f"{camera_name}_detections.json"
# data = load_json(json_path)
# new_results = []
# for file in images:
# raw = await file.read()
# metadata = extract_metadata(raw)
# nparr = np.frombuffer(raw, np.uint8)
# img = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
# if img is None:
# raise HTTPException(400, f"Invalid image: {file.filename}")
# detections = process_image(img)
# url = save_image(user_id, camera_name, file.filename, raw)
# record = {
# "filename": file.filename,
# "image_url": url,
# "detections": detections,
# "metadata": metadata
# }
# data.append(record)
# new_results.append(record)
# save_json(json_path, data)
# return {
# "message": "Images processed successfully",
# "camera": camera_name,
# "results": new_results
# }
from fastapi import APIRouter, UploadFile, File, Form, HTTPException
from pydantic import BaseModel
from pathlib import Path
from typing import Optional, List, Literal
import cv2
import numpy as np
import logging
from .config import UPLOAD_DIR
from .utils import (
validate_form,
process_image,
save_image,
load_json,
save_json,
validate_user_and_camera,
extract_metadata
)
router = APIRouter()
logger = logging.getLogger(__name__)
# ─── existing endpoint
@router.post("/predict")
async def predict(
user_id: str = Form(...),
camera_name: str = Form(...),
images: list[UploadFile] = File(...)
):
images = validate_form(user_id, camera_name, images)
validate_user_and_camera(user_id, camera_name)
base = Path(UPLOAD_DIR) / user_id / camera_name
base.mkdir(parents=True, exist_ok=True)
json_path = base / f"{camera_name}_detections.json"
data = load_json(json_path)
new_results = []
for file in images:
raw = await file.read()
metadata = extract_metadata(raw)
nparr = np.frombuffer(raw, np.uint8)
img = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
if img is None:
raise HTTPException(400, f"Invalid image: {file.filename}")
detections = process_image(img)
url = save_image(user_id, camera_name, file.filename, raw)
record = {
"filename": file.filename,
"image_url": url,
"detections": detections,
"metadata": metadata
}
data.append(record)
new_results.append(record)
save_json(json_path, data)
return {
"message": "Images processed successfully",
"camera": camera_name,
"results": new_results
}
# ─────────────────
# Request Models
# ─────────────────
class DetectionOperation(BaseModel):
action: Literal["add", "update", "delete"]
detection_index: Optional[int] = None
label: Optional[str] = None
bbox: Optional[List[float]] = None # [x1, y1, x2, y2]
class MultiUpdateRequest(BaseModel):
user_id: str
camera_name: str
image_url: str
operations: List[DetectionOperation]
# ─────────────────
# Endpoint
# ─────────────────
@router.post("/modify_detections")
async def modify_detections(req: MultiUpdateRequest):
"""
Add, update, and delete detections (tags) for a given image.
Supports multiple operations in a single request.
"""
# Validate user
user_path = Path(UPLOAD_DIR) / req.user_id
if not user_path.exists() or not user_path.is_dir():
raise HTTPException(status_code=404, detail="user not found")
# Validate camera
camera_path = user_path / req.camera_name
if not camera_path.exists() or not camera_path.is_dir():
raise HTTPException(status_code=404, detail="camera not found")
# Validate JSON file
json_path = camera_path / f"{req.camera_name}_detections.json"
if not json_path.exists():
raise HTTPException(status_code=404, detail="detections file not found")
# Load data
data = load_json(json_path)
# Find image record
target_filename = req.image_url.split("/")[-1].split("?")[0]
record = None
for item in data:
stored = item.get("image_url", item.get("filename", ""))
stored_filename = stored.split("/")[-1].split("?")[0]
if stored_filename == target_filename:
record = item
break
if record is None:
raise HTTPException(status_code=404, detail="image not found")
# ── 6. Ensure detections list exists
if "detections" not in record or not isinstance(record["detections"], list):
record["detections"] = []
dets = record["detections"]
# ── 7. Apply operations safely ──────
# NOTE: Reverse delete operations to avoid index shifting issues
delete_ops = [op for op in req.operations if op.action == "delete"]
other_ops = [op for op in req.operations if op.action != "delete"]
# Handle DELETE (reverse order)
for op in sorted(delete_ops, key=lambda x: x.detection_index or -1, reverse=True):
if op.detection_index is None or op.detection_index >= len(dets):
raise HTTPException(
status_code=400,
detail=f"Invalid delete index {op.detection_index}"
)
dets.pop(op.detection_index)
# Handle ADD + UPDATE
for op in other_ops:
# ADD
if op.action == "add":
dets.append({
"label": op.label or "Unknown",
"confidence": 1.0,
"bbox": op.bbox or [],
"manually_edited": True
})
# UPDATE
elif op.action == "update":
if op.detection_index is None or op.detection_index >= len(dets):
raise HTTPException(
status_code=400,
detail=f"Invalid update index {op.detection_index}"
)
if op.label is not None:
dets[op.detection_index]["label"] = op.label
if op.bbox is not None:
dets[op.detection_index]["bbox"] = op.bbox
dets[op.detection_index]["manually_edited"] = True
# ── 8. Save back ────────────────────
save_json(json_path, data)
logger.info(
"Detections modified | user=%s camera=%s file=%s ops=%d final_count=%d",
req.user_id,
req.camera_name,
target_filename,
len(req.operations),
len(dets)
)
# ── 9. Response ─────────────────────
return {
"success": True,
"message": "Detections modified successfully",
"filename": target_filename,
"total_detections": len(dets),
"detections": dets
}