Spaces:
Sleeping
Sleeping
| from __future__ import annotations | |
| import io | |
| import os | |
| from pathlib import Path | |
| from typing import Dict, List | |
| import gradio as gr | |
| import numpy as np | |
| import tensorflow as tf | |
| from PIL import Image | |
| from tensorflow.keras.applications import EfficientNetB2 | |
| # Label wajah sesuai backend Flask | |
| CLASS_NAMES: List[str] = ["Heart", "Oblong", "Oval", "Round", "Square"] | |
| IMG_SIZE = 244 | |
| NUM_CLASSES = len(CLASS_NAMES) | |
| def _load_image_to_rgb(image: Image.Image) -> np.ndarray: | |
| if image.mode != "RGB": | |
| image = image.convert("RGB") | |
| return np.asarray(image) | |
| def _resize_to_model(img_rgb: np.ndarray) -> np.ndarray: | |
| im = Image.fromarray(img_rgb) | |
| im = im.resize((IMG_SIZE, IMG_SIZE), Image.NEAREST) | |
| return np.asarray(im) | |
| def _preprocess(image: Image.Image) -> np.ndarray: | |
| rgb = _load_image_to_rgb(image) | |
| resized = _resize_to_model(rgb) | |
| arr = resized.astype("float32") | |
| return np.expand_dims(arr, axis=0) # [1, IMG_SIZE, IMG_SIZE, 3] | |
| def _create_model(num_classes: int): | |
| base_model = EfficientNetB2( | |
| weights=None, | |
| include_top=False, | |
| pooling="avg", | |
| input_shape=(IMG_SIZE, IMG_SIZE, 3), | |
| ) | |
| model = tf.keras.Sequential( | |
| [ | |
| base_model, | |
| tf.keras.layers.Dropout(0.2), | |
| tf.keras.layers.Dense(num_classes, activation="softmax"), | |
| ] | |
| ) | |
| return model | |
| def _resolve_model_path() -> Path: | |
| base_dir = Path(__file__).resolve().parent | |
| candidates = [ | |
| base_dir / "best_model.keras", | |
| ] | |
| for p in candidates: | |
| if p.exists(): | |
| return p | |
| # default fallback | |
| return candidates[0] | |
| class PreTrainedModel: | |
| def __init__(self) -> None: | |
| self.model_path = _resolve_model_path() | |
| self.model = _create_model(NUM_CLASSES) | |
| if self.model_path.exists(): | |
| # Memuat bobot yang sama seperti backend Flask | |
| self.model.load_weights(self.model_path) | |
| print(f"Loaded weights from: {self.model_path}") | |
| else: | |
| print(f"Warning: Model file not found at: {self.model_path}") | |
| def predict_image(self, image: Image.Image) -> Dict[str, float]: | |
| x = _preprocess(image) | |
| preds = self.model.predict(x) | |
| if isinstance(preds, (list, tuple)): | |
| preds = preds[0] | |
| probs = np.asarray(preds).squeeze().tolist() | |
| return {label: float(score) for label, score in zip(CLASS_NAMES, probs)} | |
| model = PreTrainedModel() | |
| def predict(image: Image.Image): | |
| predictions = model.predict_image(image) | |
| probs_percent = {label: round(p * 100, 2) | |
| for label, p in predictions.items()} | |
| max_label = max(probs_percent, key=probs_percent.get) | |
| return { | |
| "label": max_label, | |
| "percentage": probs_percent[max_label], | |
| "probabilities": probs_percent, | |
| } | |
| iface = gr.Interface( | |
| fn=predict, | |
| inputs=gr.Image(type="pil"), | |
| outputs=gr.JSON(), | |
| title="Face Shape Classification", | |
| description="Upload an image to classify face shape (Heart, Oblong, Oval, Round, Square).", | |
| ) | |
| if __name__ == "__main__": | |
| iface.launch() | |