nutrivision-app / model.py
Omarsy2's picture
Upload 6 files
d182af3 verified
Raw
History Blame Contribute Delete
5.17 kB
from pathlib import Path
import io
from typing import Any, Dict, List
from PIL import Image
import torch
from torchvision import models, transforms
# Define path configurations
MODEL_PATH = Path(__file__).resolve().parent / "model_resnet.pth"
# List of all food class names supported by the model
CLASS_NAMES = [
"apple_pie", "baby_back_ribs", "baklava", "beef_carpaccio", "beef_tartare",
"beet_salad", "beignets", "bibimbap", "bread_pudding", "breakfast_burrito",
"bruschetta", "caesar_salad", "cannoli", "caprese_salad", "carrot_cake",
"ceviche", "cheesecake", "cheese_plate", "chicken_curry", "chicken_quesadilla",
"chicken_wings", "chocolate_cake", "chocolate_mousse", "churros", "clam_chowder",
"club_sandwich", "crab_cakes", "creme_brulee", "croque_madame", "cup_cakes"
]
# Mapping classes to human-readable labels and emojis
CLASS_DISPLAY_NAMES = {
"apple_pie": {"label": "Apple Pie", "icon": "๐Ÿฅง"},
"baby_back_ribs": {"label": "Baby Back Ribs", "icon": "๐Ÿ–"},
"baklava": {"label": "Baklava", "icon": "๐Ÿฏ"},
"beef_carpaccio": {"label": "Beef Carpaccio", "icon": "๐Ÿฅฉ"},
"beef_tartare": {"label": "Beef Tartare", "icon": "๐Ÿฅฉ"},
"beet_salad": {"label": "Beet Salad", "icon": "๐Ÿฅ—"},
"beignets": {"label": "Beignets", "icon": "๐Ÿฉ"},
"bibimbap": {"label": "Bibimbap", "icon": "๐Ÿš"},
"bread_pudding": {"label": "Bread Pudding", "icon": "๐Ÿฎ"},
"breakfast_burrito": {"label": "Breakfast Burrito", "icon": "๐ŸŒฏ"},
"bruschetta": {"label": "Bruschetta", "icon": "๐Ÿ…"},
"caesar_salad": {"label": "Caesar Salad", "icon": "๐Ÿฅ—"},
"cannoli": {"label": "Cannoli", "icon": "๐Ÿฅ"},
"caprese_salad": {"label": "Caprese Salad", "icon": "๐Ÿ…"},
"carrot_cake": {"label": "Carrot Cake", "icon": "๐ŸŽ‚"},
"ceviche": {"label": "Ceviche", "icon": "๐Ÿค"},
"cheesecake": {"label": "Cheesecake", "icon": "๐Ÿฐ"},
"cheese_plate": {"label": "Cheese Plate", "icon": "๐Ÿง€"},
"chicken_curry": {"label": "Chicken Curry", "icon": "๐Ÿ›"},
"chicken_quesadilla": {"label": "Chicken Quesadilla", "icon": "๐ŸŒฏ"},
"chicken_wings": {"label": "Chicken Wings", "icon": "๐Ÿ—"},
"chocolate_cake": {"label": "Chocolate Cake", "icon": "๐Ÿซ"},
"chocolate_mousse": {"label": "Chocolate Mousse", "icon": "๐Ÿฎ"},
"churros": {"label": "Churros", "icon": "๐Ÿง‹"},
"clam_chowder": {"label": "Clam Chowder", "icon": "๐Ÿฅฃ"},
"club_sandwich": {"label": "Club Sandwich", "icon": "๐Ÿฅช"},
"crab_cakes": {"label": "Crab Cakes", "icon": "๐Ÿฆ€"},
"creme_brulee": {"label": "Crรจme Brรปlรฉe", "icon": "๐Ÿฎ"},
"croque_madame": {"label": "Croque Madame", "icon": "๐Ÿฅช"},
"cup_cakes": {"label": "Cupcakes", "icon": "๐Ÿง"},
}
NUM_CLASSES = len(CLASS_NAMES)
# Determine hardware device (GPU or CPU)
def get_device() -> torch.device:
return torch.device("cuda" if torch.cuda.is_available() else "cpu")
# Define standard transformations for input images
def get_transform() -> transforms.Compose:
return transforms.Compose([
transforms.Resize((512, 512)),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])
# Convert uploaded image bytes to PIL format
def load_image(image_bytes: bytes) -> Image.Image:
return Image.open(io.BytesIO(image_bytes)).convert("RGB")
# Load the model architecture and pretrained weights
def build_model(device: torch.device) -> torch.nn.Module:
if not MODEL_PATH.exists():
raise FileNotFoundError(f"Model file not found: {MODEL_PATH}")
model = models.resnet50(weights=None)
in_features = model.fc.in_features
# Customize the final fully connected layer
model.fc = torch.nn.Sequential(
torch.nn.Dropout(p=0.3),
torch.nn.Linear(in_features=in_features, out_features=NUM_CLASSES)
)
state_dict = torch.load(MODEL_PATH, map_location=device)
model.load_state_dict(state_dict)
model.to(device)
model.eval()
return model
# Perform inference on the provided image
def predict(image: Image.Image, model: torch.nn.Module, device: torch.device, top_k: int = 5) -> List[Dict[str, Any]]:
transform = get_transform()
tensor = transform(image).unsqueeze(0).to(device)
# Disable gradient calculation for inference
with torch.no_grad():
outputs = model(tensor)
probabilities = torch.softmax(outputs[0], dim=0)
# Get the top K highest probability classes
top_probs, top_idxs = torch.topk(probabilities, k=min(top_k, NUM_CLASSES))
predictions = []
for score, idx in zip(top_probs.tolist(), top_idxs.tolist()):
class_name = CLASS_NAMES[idx]
display_info = CLASS_DISPLAY_NAMES.get(class_name, {"label": class_name.replace("_", " ").title(), "icon": "๐Ÿฝ๏ธ"})
predictions.append({
"label": display_info["label"],
"icon": display_info["icon"],
"confidence": float(score * 100),
"index": int(idx)
})
return predictions