dermaAI / app /backend /main.py
yashvardhanshah
fix predict result unpacking
d74fa1e
Raw
History Blame Contribute Delete
4.6 kB
import sys, os
# ── Path fix: allow "python -m uvicorn" from project root ──────────────────
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..")))
from fastapi import FastAPI, UploadFile, File, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from contextlib import asynccontextmanager
from typing import List
from backend.model import load_model, is_model_loaded, predict_image
from backend.schemas import (
PredictResponse,
PredictionResult,
DiseaseListItem,
DiseaseDetail,
HealthResponse,
)
# Load disease info directly by path (avoids package import issues)
import importlib.util
_spec = importlib.util.spec_from_file_location(
"disease_info",
os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "data", "disease_info.py"))
)
_mod = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(_mod)
DISEASE_INFO = _mod.DISEASE_INFO
ALLOWED_TYPES = {"image/jpeg", "image/png", "image/webp", "image/bmp"}
MODEL_PATH = os.path.abspath(
os.path.join(os.path.dirname(__file__), "..", "..", "skin_disease_classifier.pkl")
)
@asynccontextmanager
async def lifespan(app: FastAPI):
result = load_model(MODEL_PATH)
print(f"STARTUP: load_model returned {result}", flush=True)
print(f"STARTUP: model_loaded={is_model_loaded()}", flush=True)
yield
app = FastAPI(
title="DermAI β€” Skin Disease Classifier API",
description="FastAPI backend powering the DermAI skin disease diagnosis web app.",
version="1.0.0",
lifespan=lifespan,
)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_methods=["*"],
allow_headers=["*"],
)
# ── Health check ────────────────────────────────────────────────────────────
@app.get("/health", response_model=HealthResponse, tags=["Utility"])
async def health_check():
return HealthResponse(status="ok", model_loaded=is_model_loaded())
# ── Prediction ──────────────────────────────────────────────────────────────
import asyncio
from concurrent.futures import ThreadPoolExecutor
_executor = ThreadPoolExecutor(max_workers=1)
@app.post("/predict", response_model=PredictResponse, tags=["Inference"])
async def predict(file: UploadFile = File(...)):
if file.content_type not in ALLOWED_TYPES:
raise HTTPException(status_code=415, detail=f"Unsupported format '{file.content_type}'.")
raw = await file.read()
if len(raw) > 15 * 1024 * 1024:
raise HTTPException(status_code=413, detail="Image too large. Maximum 15 MB.")
loop = asyncio.get_event_loop()
try:
top3 = await loop.run_in_executor(_executor, predict_image, raw)
except Exception as exc:
raise HTTPException(status_code=500, detail=f"Inference error: {str(exc)}")
if top3 is None:
raise HTTPException(status_code=500, detail="Prediction failed.")
results = [
PredictionResult(
label=p["medical_name"],
friendly_name=p["friendly_name"],
confidence=p["confidence"],
)
for p in top3
]
return PredictResponse(top3=results)
# ── Disease encyclopedia ────────────────────────────────────────────────────
@app.get("/diseases", response_model=List[DiseaseListItem], tags=["Encyclopedia"])
async def list_diseases():
"""Summary info for all 21 classifiable skin conditions."""
return [
DiseaseListItem(
key=key,
friendly_name=info["friendly_name"],
category=info["category"],
severity=info["severity"],
emoji=info["emoji"],
)
for key, info in DISEASE_INFO.items()
]
@app.get("/disease/{disease_key}", response_model=DiseaseDetail, tags=["Encyclopedia"])
async def get_disease(disease_key: str):
"""Full detail for a single disease by its key name."""
info = DISEASE_INFO.get(disease_key)
if info is None:
for k, v in DISEASE_INFO.items():
if k.lower() == disease_key.lower():
disease_key, info = k, v
break
if info is None:
raise HTTPException(status_code=404, detail=f"Disease '{disease_key}' not found.")
return DiseaseDetail(key=disease_key, **info)