CoolFace
Apppublic

nick-localhost/Sign-language-detection

sourceHugging Faceupdated 11mo agoView on Hugging Face
0likes
api.py153 linesDownload Raw Back to src
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