import torch import io from pathlib import Path from fastapi import FastAPI, File, UploadFile, Form from fastapi.responses import HTMLResponse from PIL import Image from torchvision import transforms import uvicorn from train import BoneAgeModel app = FastAPI(title="Bone Age Assessment") device = torch.device("cpu") # CPU only for Render free tier model = None use_fp16 = False # Try fp16 model first (smaller memory), fallback to full model for model_file in ["best_model_fp16.pth", "best_model.pth"]: model_path = Path(model_file) if model_path.exists(): m = BoneAgeModel() if model_file == "best_model_fp16.pth": m = m.half() use_fp16 = True m.load_state_dict(torch.load(model_path, map_location="cpu")) m.eval() model = m print(f"Model loaded: {model_file}") break if model is None: print("No trained model found") transform = transforms.Compose([ transforms.Resize((256, 256)), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]) def predict_bone_age(image: Image.Image, is_male: bool) -> float: img_tensor = transform(image.convert("RGB")).unsqueeze(0).to(device) gender_tensor = torch.tensor([1.0 if is_male else 0.0]).to(device) if use_fp16: img_tensor = img_tensor.half() gender_tensor = gender_tensor.half() with torch.no_grad(): prediction = model(img_tensor, gender_tensor) return prediction.item() @app.get("/", response_class=HTMLResponse) async def home(): return """ Bone Age Assessment

Bone Age Assessment

Upload a hand X-ray to estimate skeletal maturity

Developed by Dr. Prashanth Mamidipalli

Estimated Bone Age
⚠️ For research and educational use only.
Not intended for clinical diagnosis or patient management.
""" @app.post("/assess") async def assess( file: UploadFile = File(...), sex: str = Form(...) ): if model is None: return {"error": "Model not loaded. Please train the model first."} contents = await file.read() # Basic validation if len(contents) < 10 * 1024: # less than 10KB return {"error": "File too small. Please upload a valid hand X-ray image."} image = Image.open(io.BytesIO(contents)) # Check if image is grayscale-ish (X-rays have low color saturation) img_rgb = image.convert("RGB") import numpy as np arr = np.array(img_rgb).astype(float) r, g, b = arr[:,:,0], arr[:,:,1], arr[:,:,2] color_diff = np.mean(np.abs(r - g) + np.abs(g - b) + np.abs(r - b)) if color_diff > 30: return {"error": "Image appears to be a color photo, not an X-ray. Please upload a hand X-ray image."} is_male = sex.lower() == "male" predicted_months = predict_bone_age(image, is_male) years = int(predicted_months // 12) months = int(predicted_months % 12) years_decimal = round(predicted_months / 12, 2) return { "bone_age": f"{years} yrs {months} mo", "predicted_months": round(predicted_months, 1), "predicted_years": years_decimal, "predicted_formatted": f"{years} years {months} months", "total_months": round(predicted_months, 1) } if __name__ == "__main__": uvicorn.run(app, host="0.0.0.0", port=8000)