Spaces:
Sleeping
Sleeping
| import os | |
| import io | |
| import numpy as np | |
| from fastapi import FastAPI, UploadFile, File, HTTPException | |
| from fastapi.middleware.cors import CORSMiddleware | |
| from PIL import Image | |
| import tensorflow as tf | |
| # Patch Keras Layer initialization to pop quantization_config (avoiding deserialize issues in some Keras versions) | |
| try: | |
| import keras | |
| original_layer_init = keras.layers.Layer.__init__ | |
| def patched_layer_init(self, *args, **kwargs): | |
| kwargs.pop("quantization_config", None) | |
| original_layer_init(self, *args, **kwargs) | |
| keras.layers.Layer.__init__ = patched_layer_init | |
| except Exception as e: | |
| pass | |
| try: | |
| original_tf_layer_init = tf.keras.layers.Layer.__init__ | |
| def patched_tf_layer_init(self, *args, **kwargs): | |
| kwargs.pop("quantization_config", None) | |
| original_tf_layer_init(self, *args, **kwargs) | |
| tf.keras.layers.Layer.__init__ = patched_tf_layer_init | |
| except Exception as e: | |
| pass | |
| app = FastAPI(title="Parkinson's Detection API", description="API to classify Parkinson's from images") | |
| # Setup CORS to allow requests from the Cloudflare frontend | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=["*"], # Adjust this in production to match your Cloudflare Pages URL | |
| allow_credentials=True, | |
| allow_methods=["*"], | |
| allow_headers=["*"], | |
| ) | |
| # Global variables to hold the model and status/error details | |
| MODEL = None | |
| MODEL_LOAD_STATUS = "Not started" | |
| async def load_model(): | |
| global MODEL, MODEL_LOAD_STATUS | |
| model_path = "final_parkinson_model.keras" | |
| MODEL_LOAD_STATUS = f"Checking path: {os.path.abspath(model_path)}" | |
| if os.path.exists(model_path): | |
| try: | |
| MODEL_LOAD_STATUS = "Model file exists. Attempting to load..." | |
| MODEL = tf.keras.models.load_model(model_path) | |
| MODEL_LOAD_STATUS = "Loaded successfully" | |
| print("Model loaded successfully.") | |
| except Exception as e: | |
| import traceback | |
| error_msg = f"Error loading model: {str(e)}\n{traceback.format_exc()}" | |
| MODEL_LOAD_STATUS = error_msg | |
| print(error_msg) | |
| else: | |
| error_msg = f"Model file {model_path} not found. Files in current directory: {os.listdir('.')}" | |
| MODEL_LOAD_STATUS = error_msg | |
| print(error_msg) | |
| def preprocess_image(image_bytes): | |
| try: | |
| # Load image, convert to grayscale (L) to match training's cv2.IMREAD_GRAYSCALE, | |
| # then convert to RGB to duplicate the grayscale channel to 3 channels. | |
| image = Image.open(io.BytesIO(image_bytes)) | |
| image = image.convert("L") | |
| image = image.resize((128, 128)) | |
| image = image.convert("RGB") | |
| # Convert to numpy array and apply MobileNetV2 preprocessing (scales to [-1, 1]) | |
| # This matches the training pipeline which used tf.keras.applications.mobilenet_v2.preprocess_input | |
| img_array = np.array(image, dtype=np.float32) | |
| img_array = tf.keras.applications.mobilenet_v2.preprocess_input(img_array) | |
| # Expand dimensions to create a batch of 1: (1, 128, 128, 3) | |
| img_array = np.expand_dims(img_array, axis=0) | |
| return img_array | |
| except Exception as e: | |
| raise ValueError(f"Invalid image file: {e}") | |
| def read_root(): | |
| return { | |
| "status": "healthy", | |
| "model_loaded": MODEL is not None, | |
| "model_load_status": MODEL_LOAD_STATUS | |
| } | |
| async def predict(file: UploadFile = File(...)): | |
| if MODEL is None: | |
| raise HTTPException(status_code=503, detail="Model is not loaded.") | |
| if not file.content_type.startswith("image/"): | |
| raise HTTPException(status_code=400, detail="File must be an image.") | |
| try: | |
| image_bytes = await file.read() | |
| processed_image = preprocess_image(image_bytes) | |
| # Make prediction | |
| predictions = MODEL.predict(processed_image) | |
| # Based on the notebook, it's a binary classification with 2 dense output units. | |
| # Or a single unit. Let's return the raw predictions and class. | |
| # Determine class 0 = healthy, 1 = parkinson (based on standard alphabetical ordering of classes, but could vary) | |
| class_idx = int(np.argmax(predictions[0])) | |
| confidence = float(predictions[0][class_idx]) | |
| # We assume output layer has 2 nodes: e.g., index 0 = healthy, index 1 = parkinson. | |
| classes = ["Healthy", "Parkinson"] | |
| result_class = classes[class_idx] if class_idx < len(classes) else "Unknown" | |
| return { | |
| "prediction": result_class, | |
| "confidence": confidence, | |
| "raw_scores": predictions[0].tolist() | |
| } | |
| except Exception as e: | |
| raise HTTPException(status_code=500, detail=str(e)) | |
| if __name__ == "__main__": | |
| import uvicorn | |
| uvicorn.run(app, host="0.0.0.0", port=7860) | |