Spaces:
Running
Running
| # 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) βββββββββββββββββββββββββββββββββββββββββββ | |
| 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 ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| 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/<user_id>/<camera_name>/<camera_name>_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, | |
| } | |
| } |