Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import torch | |
| import torch.nn as nn | |
| from torchvision import models, transforms | |
| from PIL import Image | |
| import numpy as np | |
| from torchvision.models import ResNet50_Weights | |
| # ----------------------------- | |
| # Device | |
| # ----------------------------- | |
| device = torch.device("cuda" if torch.cuda.is_available() else "cpu") | |
| print(f"Using device: {device}") | |
| # ----------------------------- | |
| # Model definition | |
| # ----------------------------- | |
| class FoodIngredientClassifier(nn.Module): | |
| def __init__(self, num_classes): | |
| super().__init__() | |
| self.backbone = models.resnet50(weights=models.ResNet50_Weights.IMAGENET1K_V1) | |
| num_features = self.backbone.fc.in_features | |
| self.backbone.fc = nn.Sequential( | |
| nn.Dropout(0.5), | |
| nn.Linear(num_features, 512), | |
| nn.ReLU(), | |
| nn.Dropout(0.3), | |
| nn.Linear(512, num_classes) | |
| ) | |
| def forward(self, x): | |
| return self.backbone(x) | |
| # ----------------------------- | |
| # Load checkpoint | |
| # ----------------------------- | |
| checkpoint = torch.load( | |
| "best_model.pth", | |
| map_location=device, | |
| weights_only=False | |
| ) | |
| mlb = checkpoint["mlb"] | |
| class_names = mlb.classes_ | |
| num_classes = len(class_names) | |
| model = FoodIngredientClassifier(num_classes) | |
| model.load_state_dict(checkpoint["model_state_dict"]) | |
| model.to(device) | |
| model.eval() | |
| print(f"Loaded model with {num_classes} ingredient classes") | |
| # ----------------------------- | |
| # Image transforms | |
| # ----------------------------- | |
| transform = transforms.Compose([ | |
| transforms.Resize((224, 224)), | |
| transforms.ToTensor(), | |
| transforms.Normalize( | |
| [0.485, 0.456, 0.406], | |
| [0.229, 0.224, 0.225] | |
| ) | |
| ]) | |
| # ----------------------------- | |
| # Utility | |
| # ----------------------------- | |
| def clean_name(name): | |
| return name.replace("_", " ").title() | |
| # ----------------------------- | |
| # Prediction function | |
| # ----------------------------- | |
| def predict(image, threshold): | |
| if image is None: | |
| return {"error": "No image provided"} | |
| if not isinstance(image, Image.Image): | |
| image = Image.fromarray(image) | |
| image = image.convert("RGB") | |
| input_tensor = transform(image).unsqueeze(0).to(device) | |
| with torch.no_grad(): | |
| logits = model(input_tensor) | |
| probs = torch.sigmoid(logits).cpu().numpy()[0] | |
| # Threshold-based results | |
| results = { | |
| clean_name(class_names[i]): float(probs[i]) | |
| for i in range(len(probs)) | |
| if probs[i] >= threshold | |
| } | |
| # Fallback: always return top 5 | |
| if not results: | |
| top_idx = np.argsort(probs)[-5:][::-1] | |
| results = { | |
| clean_name(class_names[i]): float(probs[i]) | |
| for i in top_idx | |
| } | |
| return dict(sorted(results.items(), key=lambda x: x[1], reverse=True)) | |
| # ----------------------------- | |
| # Gradio Interface (NO deprecated args) | |
| # ----------------------------- | |
| iface = gr.Interface( | |
| fn=predict, | |
| inputs=[ | |
| gr.Image(type="pil", label="Upload Food Image"), | |
| gr.Slider( | |
| minimum=0.00001, | |
| maximum=0.5, | |
| value=0.05, | |
| step=0.01, | |
| label="Confidence Threshold" | |
| ) | |
| ], | |
| outputs=gr.JSON(label="Detected Ingredients"), | |
| title="Food Ingredient Detection (Multi-Label)", | |
| description="Upload a food image to detect multiple ingredients using a ResNet-50 model." | |
| ) | |
| # ----------------------------- | |
| # Launch (Gradio 6.x style) | |
| # ----------------------------- | |
| if __name__ == "__main__": | |
| iface.launch(theme=gr.themes.Soft()) | |