Spaces:
Sleeping
Sleeping
| import io | |
| import numpy as np | |
| import tensorflow as tf | |
| from tensorflow import keras | |
| from keras import layers | |
| from fastapi import FastAPI, File, UploadFile, Form, HTTPException | |
| from PIL import Image | |
| app = FastAPI(title="DyslexiaLens Prediction API") | |
| # βββ RE-REGISTER CUSTOM ARCHITECTURE COMPONENTS βββ | |
| # CHANGED: Swapped .saving to .utils to match the tf.keras wrapper ecosystem | |
| class AdaptiveContrastNorm(layers.Layer): | |
| def __init__(self, epsilon: float = 1e-6, **kwargs): | |
| super().__init__(**kwargs) | |
| self.epsilon = epsilon | |
| def build(self, input_shape): | |
| channels = input_shape[-1] | |
| self.gamma = self.add_weight(name='gamma', shape=(1, 1, 1, channels), initializer='ones', trainable=True) | |
| self.beta = self.add_weight(name='beta', shape=(1, 1, 1, channels), initializer='zeros', trainable=True) | |
| super().build(input_shape) | |
| def call(self, x, training=None): | |
| axes = [1, 2] | |
| mu = tf.reduce_mean(x, axis=axes, keepdims=True) | |
| sigma = tf.math.reduce_std(x, axis=axes, keepdims=True) + self.epsilon | |
| return self.gamma * ((x - mu) / sigma) + self.beta | |
| def get_config(self): | |
| config = super().get_config() | |
| config.update({'epsilon': self.epsilon}) | |
| return config | |
| # CHANGED: Swapped .saving to .utils here as well | |
| class MaskedHuberLoss(keras.losses.Loss): | |
| def __init__(self, delta: float = 0.5, **kwargs): | |
| super().__init__(**kwargs) | |
| self.delta = delta | |
| self._huber_fn = keras.losses.Huber(delta=delta, reduction='none') | |
| def call(self, y_true, y_pred): | |
| y_true = tf.cast(tf.reshape(y_true, [-1, 1]), tf.float32) | |
| y_pred = tf.cast(tf.reshape(y_pred, [-1, 1]), tf.float32) | |
| mask = tf.cast(y_true > 0.0, tf.float32) | |
| per_sample = self._huber_fn(y_true, y_pred) | |
| masked = per_sample * tf.squeeze(mask, axis=-1) | |
| return tf.reduce_sum(masked) / (tf.reduce_sum(mask) + 1e-8) | |
| def get_config(self): | |
| config = super().get_config() | |
| config.update({'delta': self.delta}) | |
| return config | |
| # βββ GLOBAL WEIGHT LOADING βββ | |
| MODEL_PATH = "dyslexialens_model.keras" | |
| model = None | |
| def load_model(): | |
| global model | |
| try: | |
| model = keras.models.load_model( | |
| MODEL_PATH, | |
| custom_objects={ | |
| 'AdaptiveContrastNorm': AdaptiveContrastNorm, | |
| 'MaskedHuberLoss': MaskedHuberLoss | |
| } | |
| ) | |
| print("Model successfully loaded onto CPU context.") | |
| except Exception as e: | |
| print(f"Error loading Keras model: {str(e)}") | |
| def health_check(): | |
| return {"status": "online", "model": "DyslexiaLens Late Fusion Pipeline ready"} | |
| async def predict( | |
| stroke_density: float = Form(...), | |
| center_of_mass_x: float = Form(...), | |
| center_of_mass_y: float = Form(...), | |
| bounding_box_ratio: float = Form(...), | |
| stroke_transitions: float = Form(...), | |
| horizontal_symmetry: float = Form(...), | |
| file: UploadFile = File(...) | |
| ): | |
| if model is None: | |
| raise HTTPException(status_code=503, detail="Model is loading or uninitialized.") | |
| try: | |
| contents = await file.read() | |
| image = Image.open(io.BytesIO(contents)).convert('L') | |
| image = image.resize((128, 128), Image.BILINEAR) | |
| img_array = np.array(image, dtype=np.float32) / 255.0 | |
| img_tensor = np.expand_dims(img_array, axis=(0, -1)) | |
| feature_vector = np.array([ | |
| stroke_density, center_of_mass_x, center_of_mass_y, | |
| bounding_box_ratio, stroke_transitions, horizontal_symmetry | |
| ], dtype=np.float32).reshape(1, 6) | |
| predictions = model.predict({ | |
| 'image_input': img_tensor, | |
| 'feature_input': feature_vector | |
| }) | |
| clf_probability = float(predictions[0][0][0]) | |
| severity_score = float(predictions[1][0][0]) | |
| is_dyslexia = clf_probability >= 0.40 | |
| return { | |
| "status": "success", | |
| "prediction": { | |
| "has_dyslexia": is_dyslexia, | |
| "dyslexia_probability": round(clf_probability, 4), | |
| "severity_score": round(severity_score, 4) | |
| } | |
| } | |
| except Exception as e: | |
| raise HTTPException(status_code=500, detail=f"Inference Failure: {str(e)}") |