# 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 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 (unchanged) ─────────────────────────────────────────── @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 } # ─── NEW: request model ─────────────────────────────────────────────────────── class UpdateDetectionRequest(BaseModel): user_id: str camera_name: str image_url: str # used to locate the record detection_index: int = 0 # which detection inside the image to edit new_label: str # e.g. "Buck", "Doe", "Unknown" new_bbox: Optional[List[float]] = None # [x1,y1,x2,y2] in natural px, or null # ─── NEW: endpoint ──────────────────────────────────────────────────────────── @router.post("/update_detection") async def update_detection(req: UpdateDetectionRequest): """ Edit the label and/or bounding box of one detection on an already-processed image. Writes the change back into user_data///_detections.json """ # ── 1. First check if user directory exists ───────────────────────── 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" ) # ── 2. Check if camera directory exists ──────────────────────────── 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" ) # ── 3. Check if detections JSON file exists ───────────────────────── json_path = camera_path / f"{req.camera_name}_detections.json" if not json_path.exists(): raise HTTPException( status_code=404, detail="camera not found" # Camera ke hisaab se JSON file nahi hai ) # ── 4. Load the JSON data ─────────────────────────────────────────── data = load_json(json_path) # ── 5. Find the record by matching the filename ───────────────────── 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. Apply the edit ─────────────────────────────────────────────── dets = record.get("detections", []) if not dets: # Image had zero detections before — create the first one manually record["detections"] = [{ "label": req.new_label, "confidence": 1.0, "bbox": req.new_bbox or [], "manually_edited": True }] elif req.detection_index < len(dets): # Normal case: update the detection at the requested index dets[req.detection_index]["label"] = req.new_label dets[req.detection_index]["manually_edited"] = True if req.new_bbox is not None: dets[req.detection_index]["bbox"] = req.new_bbox else: raise HTTPException( status_code=400, detail=f"detection_index {req.detection_index} is out of range " f"(image has {len(dets)} detection(s))" ) # ── 7. Save back ──────────────────────────────────────────────────── save_json(json_path, data) logger.info( "Detection updated | user=%s camera=%s file=%s idx=%d label=%s bbox=%s", req.user_id, req.camera_name, target_filename, req.detection_index, req.new_label, req.new_bbox ) return { "success": True, "message": "Detection updated successfully", "updated": { "filename": target_filename, "detection_index": req.detection_index, "new_label": req.new_label, "new_bbox": req.new_bbox, } }