|
|
| from __future__ import annotations |
| import uuid |
| from contextlib import asynccontextmanager |
| from fastapi import FastAPI, Request |
| from fastapi.middleware.cors import CORSMiddleware |
| from fastapi.responses import HTMLResponse, JSONResponse |
| from slowapi import Limiter, _rate_limit_exceeded_handler |
| from slowapi.errors import RateLimitExceeded |
| from slowapi.util import get_remote_address |
| from api.inference import pipeline |
| from api.routes import health, predict |
| |
| |
| |
| limiter = Limiter(key_func=get_remote_address, default_limits=["10/minute"]) |
| |
| |
| |
| @asynccontextmanager |
| async def lifespan(app: FastAPI): |
| print("[ChestAI] Loading model...") |
| await pipeline.load() |
| print("[ChestAI] Ready.") |
| yield |
| |
| if pipeline.gradcam: |
| pipeline.gradcam.remove_hooks() |
| print("[ChestAI] Shutdown complete.") |
| |
| |
| |
| app = FastAPI( |
| title="ChestAI", |
| description=( |
| "Uncertainty-aware multi-label chest X-ray diagnostic API. " |
| "Analyzes 14 pathology classes with MC Dropout uncertainty estimation, " |
| "GradCAM explainability, and auto-generated radiology reports." |
| ), |
| version="1.0.0", |
| lifespan=lifespan, |
| docs_url="/docs", |
| redoc_url="/redoc", |
| ) |
| |
| app.state.limiter = limiter |
| app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler) |
| |
| |
| |
| app.add_middleware( |
| CORSMiddleware, |
| allow_origins=[ |
| "http://localhost:3000", |
| "http://localhost:3001", |
| "https://thorax-tho.vercel.app", |
| ], |
| allow_origin_regex=r"https://.*\.vercel\.app", |
| allow_credentials=True, |
| allow_methods=["GET", "POST"], |
| allow_headers=["*"], |
| ) |
| |
| |
| |
| @app.middleware("http") |
| async def add_request_id(request: Request, call_next): |
| request_id = str(uuid.uuid4())[:8] |
| request.state.request_id = request_id |
| response = await call_next(request) |
| response.headers["X-Request-ID"] = request_id |
| return response |
| |
| |
| |
| @app.exception_handler(Exception) |
| async def global_exception_handler(request: Request, exc: Exception): |
| return JSONResponse( |
| status_code=500, |
| content={"detail": "Internal server error.", "type": type(exc).__name__}, |
| ) |
| |
| |
| |
| |
| LANDING_HTML = """<!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="utf-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1"> |
| <title>ThoraxNet — AI Chest X-Ray Diagnosis</title> |
| </head> |
| <body style="margin:0;font-family:system-ui,-apple-system,sans-serif;background:#0b1220;color:#e8edf4;text-align:center;padding:8vh 1.5rem 3rem"> |
| <h1 style="font-size:2.4rem;margin-bottom:0.4rem">ThoraxNet</h1> |
| <p style="color:#9fb0c3;max-width:36rem;margin:0 auto 2.2rem;line-height:1.6"> |
| AI chest X-ray diagnosis — detects <b>14 thoracic pathologies</b> with |
| MC Dropout uncertainty estimates, GradCAM heatmaps and auto-generated |
| radiology reports. Mean AUC <b>0.8215</b> on NIH ChestX-ray14. |
| </p> |
| <a href="https://thorax-tho.vercel.app" target="_blank" rel="noopener" style="display:inline-block;background:#22c55e;color:#04120a;font-weight:700;padding:0.9rem 2.4rem;border-radius:10px;text-decoration:none;font-size:1.15rem"> |
| Open Live Demo |
| </a> |
| <p style="margin-top:2.2rem"> |
| <a href="/docs" style="color:#7cc4ff;text-decoration:none">API Docs</a> |
| · |
| <a href="https://github.com/Sowaiba-01/ThoraxNet" target="_blank" rel="noopener" style="color:#7cc4ff;text-decoration:none">GitHub</a> |
| · |
| <a href="https://huggingface.co/Sowaiba01/chestai-model" target="_blank" rel="noopener" style="color:#7cc4ff;text-decoration:none">Model</a> |
| </p> |
| <p style="color:#5b6b7d;font-size:0.8rem;margin-top:3rem"> |
| For research use only. Not FDA cleared. Not a substitute for clinical radiologist interpretation. |
| </p> |
| </body> |
| </html>""" |
|
|
|
|
| @app.get("/", response_class=HTMLResponse, include_in_schema=False) |
| async def root() -> HTMLResponse: |
| return HTMLResponse(content=LANDING_HTML) |
| |
| |
| |
| app.include_router(health.router) |
| app.include_router(predict.router) |
|
|