| from __future__ import annotations | |
| import io | |
| from typing import Any, Dict, List | |
| import numpy as np | |
| from PIL import Image | |
| import tensorflow as tf | |
| LABELS: List[str] = [ | |
| "Heart", | |
| "Oblong", | |
| "Oval", | |
| "Round", | |
| "Square", | |
| ] | |
| TARGET_SIZE = 244 | |
| def _load_image(image_bytes: bytes) -> Image.Image: | |
| image = Image.open(io.BytesIO(image_bytes)) | |
| if image.mode != "RGB": | |
| image = image.convert("RGB") | |
| return image | |
| def _ensure_three_channels(array: np.ndarray) -> np.ndarray: | |
| if array.ndim == 2: | |
| array = np.stack([array] * 3, axis=-1) | |
| elif array.ndim == 3: | |
| if array.shape[-1] == 1: | |
| array = np.repeat(array, 3, axis=-1) | |
| elif array.shape[-1] > 3: | |
| array = array[..., :3] | |
| return array | |
| def _preprocess(image_bytes: bytes) -> np.ndarray: | |
| image = _load_image(image_bytes) | |
| resized = image.resize((TARGET_SIZE, TARGET_SIZE), Image.BILINEAR) | |
| array = np.asarray(resized, dtype="float32") | |
| array = _ensure_three_channels(array) | |
| array /= 255.0 | |
| return np.expand_dims(array, axis=0) | |
| class PreTrainedModel: | |
| def __init__(self, model_path: str = "model/best_model.keras") -> None: | |
| self.model = tf.keras.models.load_model(model_path) | |
| def predict(self, inputs: bytes) -> List[Dict[str, Any]]: | |
| x = _preprocess(inputs) | |
| preds = self.model.predict(x, verbose=0) | |
| if isinstance(preds, (list, tuple)): | |
| preds = preds[0] | |
| probs = np.asarray(preds).squeeze().tolist() | |
| idx = int(np.argmax(probs)) | |
| return [ | |
| {"label": LABELS[idx], "score": float(probs[idx])}, | |
| ] | |
| def load_model(model_dir: str = ".") -> PreTrainedModel: | |
| return PreTrainedModel(model_path=f"{model_dir}/model/best_model.keras") | |