Spaces:
Sleeping
Sleeping
| import os | |
| import io | |
| import numpy as np | |
| from PIL import Image | |
| import tensorflow as tf | |
| from tensorflow import keras | |
| from fastapi import FastAPI, UploadFile, File, HTTPException | |
| from fastapi.middleware.cors import CORSMiddleware | |
| # Initialize FastAPI app | |
| app = FastAPI( | |
| title="PneuVision AI API", | |
| description="Backend API for MobileNetV2 Pneumonia Detection model", | |
| version="1.0.0" | |
| ) | |
| # Enable CORS so frontend deployed on Cloudflare can access this API | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=["*"], | |
| allow_credentials=True, | |
| allow_methods=["*"], | |
| allow_headers=["*"], | |
| ) | |
| MODEL_PATH = "MobileNetV2_final.keras" | |
| model = None | |
| def load_model(): | |
| global model | |
| if os.path.exists(MODEL_PATH): | |
| try: | |
| # Load the Keras model. Keras 3 format handles both weights and architecture. | |
| model = keras.models.load_model(MODEL_PATH) | |
| print("[BACKEND] Model loaded successfully.") | |
| except Exception as e: | |
| print(f"[BACKEND] Error loading model: {e}") | |
| else: | |
| print(f"[BACKEND] Model file not found at {MODEL_PATH}") | |
| def health_check(): | |
| """Health check endpoint to see if API and model are active.""" | |
| return { | |
| "status": "ready" if model is not None else "model_not_loaded", | |
| "model_architecture": "MobileNetV2", | |
| "input_shape": [224, 224, 3], | |
| "classes": ["NORMAL", "PNEUMONIA"] | |
| } | |
| async def predict(file: UploadFile = File(...)): | |
| """Receives a chest X-ray image and predicts the probability of pneumonia.""" | |
| global model | |
| if model is None: | |
| raise HTTPException( | |
| status_code=503, | |
| detail="Model is not initialized on the server. Please check server logs." | |
| ) | |
| if not file.content_type.startswith("image/"): | |
| raise HTTPException( | |
| status_code=400, | |
| detail="Invalid file format. Please upload an image (JPEG/PNG)." | |
| ) | |
| try: | |
| # Read file contents and open image | |
| contents = await file.read() | |
| image = Image.open(io.BytesIO(contents)).convert("RGB") | |
| # Preprocessing: | |
| # 1. Resize image to 224x224 using Bilinear interpolation (matching Colab training setup) | |
| image = image.resize((224, 224), Image.Resampling.BILINEAR) | |
| img_array = np.array(image, dtype=np.float32) | |
| # 2. Add batch dimension: shape (1, 224, 224, 3) | |
| img_array = np.expand_dims(img_array, axis=0) | |
| # Note: Rescaling (1.0/255.0) and Normalization (ImageNet mean/std) | |
| # are defined inside the Keras model as layers, so we don't apply them here. | |
| # Running model inference (returns shape [1, 2]) | |
| predictions = model.predict(img_array) | |
| # Class 0: NORMAL, Class 1: PNEUMONIA | |
| prob_normal = float(predictions[0][0]) | |
| prob_pneumonia = float(predictions[0][1]) | |
| predicted_class = "PNEUMONIA" if prob_pneumonia > prob_normal else "NORMAL" | |
| confidence = max(prob_normal, prob_pneumonia) | |
| return { | |
| "prediction": predicted_class, | |
| "confidence": confidence, | |
| "probabilities": { | |
| "NORMAL": prob_normal, | |
| "PNEUMONIA": prob_pneumonia | |
| }, | |
| "info": { | |
| "model": "MobileNetV2", | |
| "processed_shape": [224, 224, 3] | |
| } | |
| } | |
| except Exception as e: | |
| raise HTTPException( | |
| status_code=500, | |
| detail=f"Inference error: {str(e)}" | |
| ) | |