nick-localhost/Sign-language-detection
0
1from fastapi.middleware.cors import CORSMiddleware2from fastapi import FastAPI, UploadFile, File3import torch4import cv25import numpy as np6from model import DETR7from utils.setup import get_classes8from typing import List, Dict9import os, requests10# from dotenv import load_dotenv11 12# load_dotenv() # reads .env file13 14 15app = FastAPI()16 17origins = [18 "https://sign-detection.vercel.app", # frontend origin19 # you can add more allowed origins here20]21 22# env = os.getenv("ENV", "development")23# frontend_url = os.getenv("FRONTEND_URL", "http://localhost:5173")24 25# if env == "development":26# origins = [frontend_url]27# else:28# # In production (Render), allow the deployed frontend29# origins = [frontend_url]30 31 32app.add_middleware(33 CORSMiddleware,34 allow_origins=origins, 35 allow_credentials=True,36 allow_methods=["*"], # GET, POST, etc.37 allow_headers=["*"], # headers like Content-Type38)39 40model = DETR(num_classes=3)41model.eval()42# model.load_pretrained('pretrained/4426_model.pt')43 44 45MODEL_PATH = "pretrained/4426_model.pt"46MODEL_URL = "https://huggingface.co/nick-localhost/sign-detect-model/resolve/main/4426_model.pt"47 48 49# Ensure model directory exists50os.makedirs(os.path.dirname(MODEL_PATH), exist_ok=True)51 52# Download model file if missing53if not os.path.exists(MODEL_PATH):54 print(f"Model file not found. Downloading from {MODEL_URL} ...")55 response = requests.get(MODEL_URL, stream=True)56 with open(MODEL_PATH, "wb") as f:57 for chunk in response.iter_content(chunk_size=8192):58 if chunk:59 f.write(chunk)60 print("Model downloaded successfully.")61 62# Now load it63model = DETR(num_classes=3)64model.eval()65model.load_pretrained(MODEL_PATH)66 67 68 69CLASSES = get_classes()70 71# Adjustable inference parameters72SCORE_THRESHOLD = 0.90 # confidence threshold for keeping detections73MAX_DETECTIONS = 5 # safety cap74 75MEAN = torch.tensor([0.485, 0.456, 0.406]).view(3, 1, 1)76STD = torch.tensor([0.229, 0.224, 0.225]).view(3, 1, 1)77 78def run_inference(frame: np.ndarray) -> List[Dict]:79 """Run model inference on a BGR frame and return filtered detections.80 81 Returns a list of dicts: {label, score, bbox:[x1,y1,x2,y2]}82 Bboxes are pixel coordinates in the original frame space.83 """84 orig_h, orig_w = frame.shape[:2]85 86 # Preprocess (resize + normalize like training)87 rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)88 rgb = cv2.resize(rgb, (224, 224))89 img = torch.tensor(rgb, dtype=torch.float32).permute(2, 0, 1) / 255.090 img = (img - MEAN) / STD91 img = img.unsqueeze(0)92 93 with torch.no_grad():94 outputs = model(img)95 96 logits = outputs['pred_logits'][0] # (num_queries, num_classes+1)97 boxes = outputs['pred_boxes'][0] # (num_queries, 4) in cx,cy,w,h normalized98 99 probs = logits.softmax(-1)100 scores, labels = probs.max(-1)101 102 detections: List[Dict] = []103 for i in range(scores.shape[0]):104 cls_id = labels[i].item()105 # Skip background / no-object class (DETR usually has an extra one at the end)106 if cls_id >= len(CLASSES):107 continue108 score = scores[i].item()109 if score < SCORE_THRESHOLD:110 continue111 112 # Convert bbox from normalized cx,cy,w,h -> pixel x1,y1,x2,y2113 cx, cy, w, h = boxes[i].tolist()114 x1 = (cx - w / 2) * orig_w115 y1 = (cy - h / 2) * orig_h116 x2 = (cx + w / 2) * orig_w117 y2 = (cy + h / 2) * orig_h118 119 # Clamp to image bounds120 x1 = max(0, min(orig_w - 1, x1))121 y1 = max(0, min(orig_h - 1, y1))122 x2 = max(0, min(orig_w - 1, x2))123 y2 = max(0, min(orig_h - 1, y2))124 125 detections.append({126 "label": CLASSES[cls_id],127 "score": float(score),128 "bbox": [float(x1), float(y1), float(x2), float(y2)]129 })130 131 # Sort by score desc and cap132 detections.sort(key=lambda d: d["score"], reverse=True)133 if len(detections) > MAX_DETECTIONS:134 detections = detections[:MAX_DETECTIONS]135 136 return detections137 138 139 140@app.post("/detect")141async def detect(file: UploadFile = File(...)):142 """Receive an uploaded frame and return gesture detections.143 If no gestures are confidently detected, returns an empty list instead of a repeated label.144 """145 img_bytes = await file.read()146 npimg = np.frombuffer(img_bytes, np.uint8)147 frame = cv2.imdecode(npimg, cv2.IMREAD_COLOR)148 if frame is None:149 return {"detections": []}150 detections = run_inference(frame)151 return {"detections": detections}152 153 