A-Laabid's picture
Update app.py
41503c0 verified
Raw
History Blame Contribute Delete
6.53 kB
"""
Pneumonia Detection API
FastAPI backend for dual-model pneumonia classification from chest X-rays
"""
from fastapi import FastAPI, File, UploadFile, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
import uvicorn
import numpy as np
import time
import io
from PIL import Image
import cv2
# Import preprocessing utilities
from utils.preprocess_svm import preprocess_for_svm, extract_svm_features
from utils.preprocess_cnn import preprocess_for_cnn
from utils.model_loader import load_models
# Initialize FastAPI app
app = FastAPI(
title="Pneumonia Detection API",
description="Dual-model (SVM + DenseNet121) pneumonia classification API",
version="1.0.0"
)
# Enable CORS for GitHub Pages frontend
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # In production, replace with specific domain
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Global model storage
MODELS = {}
@app.on_event("startup")
async def startup_event():
"""Load models on server startup"""
global MODELS
print("Loading models...")
MODELS = load_models()
print("✅ Models loaded successfully!")
print(f" - SVM model: {type(MODELS['svm']).__name__}")
print(f" - CNN model: {type(MODELS['cnn']).__name__}")
print(f" - Device: {MODELS['device']}")
@app.get("/")
async def root():
"""Health check endpoint"""
return {
"status": "healthy",
"message": "Pneumonia Detection API is running",
"models_loaded": len(MODELS) > 0,
"version": "1.0.0"
}
@app.get("/health")
async def health_check():
"""Detailed health check"""
return {
"status": "healthy",
"models": {
"svm": MODELS.get("svm") is not None,
"cnn": MODELS.get("cnn") is not None,
},
"device": str(MODELS.get("device", "unknown"))
}
@app.post("/predict")
async def predict_pneumonia(file: UploadFile = File(...)):
"""
Predict pneumonia from chest X-ray image
Args:
file: Uploaded image file (JPG, JPEG, PNG)
Returns:
JSON with predictions from both models
"""
# Validate file type
if not file.content_type.startswith("image/"):
raise HTTPException(
status_code=400,
detail="Invalid file type. Please upload an image (JPG, JPEG, PNG)"
)
try:
# Read image bytes
contents = await file.read()
image = Image.open(io.BytesIO(contents))
# Convert PIL Image to numpy array (RGB)
image_np = np.array(image.convert('RGB'))
# Convert RGB to BGR for OpenCV compatibility
image_bgr = cv2.cvtColor(image_np, cv2.COLOR_RGB2BGR)
# ============================================================
# SVM PREDICTION
# ============================================================
start_svm = time.time()
# Preprocess for SVM (CLAHE + resize to 224x224)
svm_preprocessed = preprocess_for_svm(image_bgr)
# Extract features (LBP + HOG)
svm_features = extract_svm_features(svm_preprocessed)
# Reshape for sklearn
svm_features = svm_features.reshape(1, -1)
# Predict
svm_prediction = MODELS['svm'].predict(svm_features)[0]
svm_proba = MODELS['svm'].predict_proba(svm_features)[0]
svm_time = time.time() - start_svm
# ============================================================
# CNN PREDICTION (DenseNet121)
# ============================================================
start_cnn = time.time()
# Preprocess for CNN (normalize, resize, tensor)
cnn_input = preprocess_for_cnn(image_np)
cnn_input = cnn_input.unsqueeze(0).to(MODELS['device'])
# Predict
MODELS['cnn'].eval()
import torch
with torch.no_grad():
outputs = MODELS['cnn'](cnn_input)
probabilities = torch.softmax(outputs, dim=1)
cnn_prediction = outputs.argmax(dim=1).item()
cnn_confidence = probabilities[0][cnn_prediction].item()
cnn_time = time.time() - start_cnn
# ============================================================
# PREPARE RESPONSE
# ============================================================
class_names = ['NORMAL', 'PNEUMONIA']
response = {
"success": True,
"predictions": {
"svm": {
"class": class_names[svm_prediction],
"class_id": int(svm_prediction),
"probabilities": {
"NORMAL": float(svm_proba[0]),
"PNEUMONIA": float(svm_proba[1])
},
"confidence": float(svm_proba[svm_prediction]),
"inference_time_ms": round(svm_time * 1000, 2)
},
"cnn": {
"class": class_names[cnn_prediction],
"class_id": int(cnn_prediction),
"confidence": float(cnn_confidence),
"probabilities": {
"NORMAL": float(probabilities[0][0]),
"PNEUMONIA": float(probabilities[0][1])
},
"inference_time_ms": round(cnn_time * 1000, 2)
}
},
"consensus": {
"agreement": bool(svm_prediction == cnn_prediction), # ✅ FIX: Convert numpy.bool_ to Python bool
"predicted_class": class_names[cnn_prediction] if svm_prediction == cnn_prediction else "DISAGREEMENT"
},
"total_time_ms": round((svm_time + cnn_time) * 1000, 2),
"image_info": {
"filename": file.filename,
"original_size": image.size,
"format": image.format
}
}
return JSONResponse(content=response)
except Exception as e:
print(f"Error during prediction: {str(e)}")
raise HTTPException(
status_code=500,
detail=f"Prediction failed: {str(e)}"
)
if __name__ == "__main__":
# Run the server
uvicorn.run(
"app:app",
host="0.0.0.0",
port=7860,
reload=True,
log_level="info"
)