NeuroHealth / app.py
Mikecode123's picture
Update app.py
559e735 verified
Raw
History Blame Contribute Delete
6.48 kB
import torch
import torch.nn as nn
import torchvision.models as models
from fastapi import FastAPI, UploadFile, File
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
from PIL import Image
import io
import torchvision.transforms as transforms
# =========================
# APP INIT
# =========================
app = FastAPI(title="Alzheimer Ensemble API", version="1.0")
# =========================
# CORS
# =========================
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# =========================
# DEVICE
# =========================
DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print("Using device:", DEVICE)
# =========================
# CLASS LABELS (FIXED)
# =========================
CLASSES = [
"Mild Demented",
"Moderate Demented",
"Non Demented",
"Very Mild Demented"
]
# =========================
# IMAGE TRANSFORM
# =========================
transform = transforms.Compose([
transforms.Resize((224, 224)),
transforms.ToTensor(),
transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5])
])
# =========================
# MODEL BUILDER
# =========================
def build_model(version="121"):
if version == "121":
model = models.densenet121(weights=None)
in_features = 1024
elif version == "169":
model = models.densenet169(weights=None)
in_features = 1664
else:
model = models.densenet201(weights=None)
in_features = 1920
model.classifier = nn.Sequential(
nn.Dropout(0.4),
nn.Linear(in_features, len(CLASSES))
)
return model
# =========================
# SAFE MODEL LOADER
# =========================
def load_model(path, version):
model = build_model(version)
try:
checkpoint = torch.load(path, map_location=DEVICE)
if isinstance(checkpoint, dict):
if "state_dict" in checkpoint:
checkpoint = checkpoint["state_dict"]
elif "model_state_dict" in checkpoint:
checkpoint = checkpoint["model_state_dict"]
model.load_state_dict(checkpoint, strict=False)
print(f"Loaded: {path}")
except Exception as e:
print(f"Failed loading {path}: {e}")
model.to(DEVICE)
model.eval()
return model
# =========================
# LOAD MODELS
# =========================
model_121 = load_model("alzheimers_densenet121.pth", "121")
model_169 = load_model("alzheimers_densenet169.pth", "169")
model_201 = load_model("alzheimers_densenet201.pth", "201")
# =========================
# IMAGE PROCESSING
# =========================
def process_image(image_bytes):
img = Image.open(io.BytesIO(image_bytes)).convert("RGB")
img = transform(img).unsqueeze(0).to(DEVICE)
return img
# =========================
# PREDICTION FUNCTION
# All confidence values are returned as floats in range [0.0, 1.0]
# =========================
def predict(model, x):
with torch.no_grad():
output = model(x)
probs = torch.softmax(output, dim=1)[0]
conf, cls = torch.max(probs, 0)
cls = int(cls.item())
return {
"prediction": CLASSES[cls],
"class_id": cls,
# Confidence as 0.0–1.0 decimal
"confidence": float(conf.item()),
"probabilities": {
CLASSES[i]: float(probs[i].item())
for i in range(len(CLASSES))
}
}
# =========================
# HEALTH ENDPOINT
# =========================
@app.get("/health")
def health():
return {
"status": "running",
"service": "Alzheimer MRI Ensemble API",
"models_loaded": {
"densenet121": True,
"densenet169": True,
"densenet201": True,
},
"device": str(DEVICE),
"classes": CLASSES,
}
# =========================
# ROOT ENDPOINT
# =========================
@app.get("/")
def home():
return {
"status": "running",
"models": ["121", "169", "201"],
"classes": CLASSES,
"endpoints": [
"/health",
"/predict/121",
"/predict/169",
"/predict/201",
"/predict/ensemble"
]
}
# =========================
# SINGLE MODEL PREDICTIONS
# =========================
@app.post("/predict/121")
async def predict_121(file: UploadFile = File(...)):
img = process_image(await file.read())
result = predict(model_121, img)
result["model"] = "densenet121"
return JSONResponse(result)
@app.post("/predict/169")
async def predict_169(file: UploadFile = File(...)):
img = process_image(await file.read())
result = predict(model_169, img)
result["model"] = "densenet169"
return JSONResponse(result)
@app.post("/predict/201")
async def predict_201(file: UploadFile = File(...)):
img = process_image(await file.read())
result = predict(model_201, img)
result["model"] = "densenet201"
return JSONResponse(result)
# =========================
# ENSEMBLE PREDICTION
# Returns ensemble result with individual model results
# All confidence values are 0.0-1.0
# =========================
@app.post("/predict/ensemble")
async def ensemble(file: UploadFile = File(...)):
img = process_image(await file.read())
r1 = predict(model_121, img)
r2 = predict(model_169, img)
r3 = predict(model_201, img)
# Average probabilities (all already 0.0-1.0)
avg_probs = {}
for c in CLASSES:
avg_probs[c] = (
r1["probabilities"][c] +
r2["probabilities"][c] +
r3["probabilities"][c]
) / 3
final_class = max(avg_probs, key=avg_probs.get)
# Ensemble confidence = max averaged probability (0.0-1.0)
ensemble_confidence = avg_probs[final_class]
return JSONResponse({
"prediction": final_class,
# confidence as 0.0-1.0
"confidence": ensemble_confidence,
"probabilities": avg_probs,
"individual_models": {
"densenet121": r1,
"densenet169": r2,
"densenet201": r3,
}
})