sparshagra51's picture
Deploy 5-stage traffic violation pipeline with FastAPI
1b4ae87
Raw
History Blame Contribute Delete
4.1 kB
"""
app.py β€” FastAPI wrapper for the traffic violation detection pipeline.
Deployed on HuggingFace Spaces (CPU Docker) at port 7860.
Endpoints:
GET /health β€” liveness check
POST /predict β€” upload image, get JSON violations + all_vehicles + summary
"""
import os
from contextlib import asynccontextmanager
from pathlib import Path
import cv2
import numpy as np
from fastapi import FastAPI, File, HTTPException, UploadFile
from fastapi.responses import JSONResponse
from ultralytics import YOLO
import uvicorn
# ── ensure all model paths resolve from the app's working directory ───────────
os.chdir(Path(__file__).parent)
from enhanced_pipeline import (
COCO_MODEL, S1_LOCAL, S3_LOCAL, S4_LOCAL,
load_depth_model, run_pipeline,
)
# ── global model store (loaded once at startup) ───────────────────────────────
_models: dict = {}
@asynccontextmanager
async def lifespan(app: FastAPI):
"""Load all models on startup; release on shutdown."""
print("[*] Loading depth model (DepthAnythingV2)...")
load_depth_model()
print("[*] Loading YOLO models...")
_models["coco"] = YOLO(COCO_MODEL)
_models["s1"] = YOLO(str(S1_LOCAL))
_models["helmet"] = YOLO(str(S3_LOCAL))
_models["plate"] = YOLO(str(S4_LOCAL))
print("[βœ“] All models ready. API is live.\n")
yield # app runs here
_models.clear()
print("[*] Models released.")
# ── app ───────────────────────────────────────────────────────────────────────
app = FastAPI(
title="Traffic Violation Detection API",
description=(
"5-stage cascaded pipeline: person/bike detection β†’ depth-filtered "
"spatial association β†’ helmet classification β†’ license plate OCR. "
"Returns per-vehicle violation data in JSON."
),
version="1.0.0",
lifespan=lifespan,
)
@app.get("/health")
def health():
"""Liveness check β€” returns ok once models are loaded."""
if len(_models) < 4:
raise HTTPException(503, "Models not ready yet")
return {"status": "ok", "models_loaded": list(_models.keys())}
@app.post("/predict")
async def predict(file: UploadFile = File(...)):
"""
Upload an image (jpg/png) and receive a full violation report.
Response shape:
{
"violations": [
{ "num_riders": int, "helmet_violations": int, "license_plate": str|null }
],
"all_vehicles": [
{
"bike_bbox": [x1,y1,x2,y2],
"num_riders": int,
"with_helmet": int,
"without_helmet": int,
"triple_riding": bool,
"helmet_violation": bool,
"license_plate": str|null,
"plate_bbox": [x1,y1,x2,y2]|null,
"riders": [
{ "person_bbox":[x1,y1,x2,y2], "head_bbox":[x1,y1,x2,y2], "helmet": str }
],
"is_violation": bool
}
],
"summary": { "total_bikes": int, "total_violations": int }
}
"""
# Validate content type
if not file.content_type.startswith("image/"):
raise HTTPException(400, f"Expected an image file, got: {file.content_type}")
# Decode uploaded bytes β†’ numpy BGR image
raw = await file.read()
arr = np.frombuffer(raw, np.uint8)
img = cv2.imdecode(arr, cv2.IMREAD_COLOR)
if img is None:
raise HTTPException(400, "Could not decode image β€” ensure it is a valid JPEG/PNG.")
# Run full pipeline (CPU, no save/display)
result = run_pipeline(
img,
_models["coco"],
_models["s1"],
_models["helmet"],
_models["plate"],
save=False,
debug=False,
)
if "error" in result:
raise HTTPException(500, result["error"])
return JSONResponse(content=result)
if __name__ == "__main__":
uvicorn.run("app:app", host="0.0.0.0", port=7860, log_level="info")