| from typing import Dict, List, Any |
| import tensorflow as tf |
| import numpy as np |
| from PIL import Image |
| import io |
| import base64 |
| import os |
|
|
|
|
| class EndpointHandler(): |
| def __init__(self, path=""): |
| """ |
| Initialize the handler by loading the model and class labels. |
| |
| Args: |
| path: Path to the model directory (provided by Hugging Face) |
| """ |
| |
| model_path = os.path.join(path, "food_cassifier.h5") |
| self.model = tf.keras.models.load_model(model_path) |
|
|
| |
| classes_path = os.path.join(path, "classes.txt") |
| with open(classes_path, 'r') as f: |
| self.class_names = [line.strip() for line in f.readlines()] |
|
|
| print(f"Model loaded successfully with {len(self.class_names)} classes") |
|
|
| def __call__(self, data: Dict[str, Any]) -> List[Dict[str, Any]]: |
| """ |
| Process the inference request. |
| |
| Args: |
| data: Dictionary containing the input data |
| Expected format: {"inputs": "<base64_encoded_image>"} |
| or {"inputs": {"image": "<base64_encoded_image>"}} |
| |
| Returns: |
| List of predictions with class labels and confidence scores |
| """ |
| |
| inputs = data.get("inputs", "") |
|
|
| |
| if isinstance(inputs, dict): |
| image_data = inputs.get("image", "") |
| else: |
| image_data = inputs |
|
|
| |
| try: |
| |
| if isinstance(image_data, str) and "base64," in image_data: |
| image_data = image_data.split("base64,")[1] |
|
|
| |
| image_bytes = base64.b64decode(image_data) |
| image = Image.open(io.BytesIO(image_bytes)) |
| except Exception as e: |
| return [{"error": f"Failed to decode image: {str(e)}"}] |
|
|
| |
| try: |
| preprocessed_image = self.preprocess_image(image) |
| except Exception as e: |
| return [{"error": f"Failed to preprocess image: {str(e)}"}] |
|
|
| |
| try: |
| prediction = self.model.predict(preprocessed_image) |
| except Exception as e: |
| return [{"error": f"Failed to make prediction: {str(e)}"}] |
|
|
| |
| results = self.postprocess_prediction(prediction) |
|
|
| return results |
|
|
| def preprocess_image(self, image: Image.Image) -> np.ndarray: |
| """ |
| Preprocess the input image for the model. |
| |
| Args: |
| image: PIL Image object |
| |
| Returns: |
| Preprocessed numpy array of shape (1, 224, 224, 3) |
| """ |
| |
| if image.mode != 'RGB': |
| image = image.convert('RGB') |
|
|
| |
| img_array = tf.keras.preprocessing.image.img_to_array(image) |
|
|
| |
| img_array = tf.image.resize(img_array, (224, 224)) |
|
|
| |
| img_array = np.expand_dims(img_array, axis=0) |
|
|
| |
| img_array = tf.keras.applications.mobilenet_v2.preprocess_input(img_array) |
|
|
| return img_array |
|
|
| def postprocess_prediction(self, prediction: np.ndarray) -> List[Dict[str, Any]]: |
| """ |
| Postprocess the model prediction to return human-readable results. |
| |
| Args: |
| prediction: Model output array of shape (1, 101) |
| |
| Returns: |
| List containing prediction results with top predictions |
| """ |
| |
| predicted_idx = int(np.argmax(prediction[0])) |
| confidence = float(prediction[0][predicted_idx]) |
| predicted_label = self.class_names[predicted_idx] |
|
|
| |
| top_5_indices = np.argsort(prediction[0])[-5:][::-1] |
| top_5_predictions = [ |
| { |
| "label": self.class_names[int(idx)], |
| "score": float(prediction[0][idx]) |
| } |
| for idx in top_5_indices |
| ] |
|
|
| |
| return [ |
| { |
| "predicted_class": predicted_label, |
| "predicted_index": predicted_idx, |
| "confidence": confidence, |
| "top_5_predictions": top_5_predictions |
| } |
| ] |
|
|