Spaces:
Runtime error
Runtime error
| # ------------------------------------------------------------ | |
| # FastAPI service exposing BinhQuocNguyen/food-recognition-model | |
| # ------------------------------------------------------------ | |
| from fastapi import FastAPI, HTTPException | |
| from pydantic import BaseModel | |
| import base64, io | |
| from PIL import Image | |
| import torch | |
| import numpy as np | |
| # Transformers imports | |
| from transformers import AutoModel, AutoImageProcessor | |
| # ------------------------------------------------------------------- | |
| # 1️⃣ Load the model & processor (once, at import time) | |
| # ------------------------------------------------------------------- | |
| MODEL_NAME = "BinhQuocNguyen/food-recognition-model" | |
| # AutoModel knows the custom architecture (food_recognition) because | |
| # the repository ships a proper `config.json`. | |
| model = AutoModel.from_pretrained(MODEL_NAME) | |
| processor = AutoImageProcessor.from_pretrained(MODEL_NAME) | |
| # Put the model on CPU – the Space has no GPU. | |
| device = torch.device("cpu") | |
| model.to(device) | |
| model.eval() | |
| # Mapping from class index → readable label (comes from the config) | |
| id2label = model.config.id2label # dict[int, str] | |
| # ------------------------------------------------------------------- | |
| # 2️⃣ Minimal nutrient lookup table (extend as you like) | |
| # ------------------------------------------------------------------- | |
| nutrient_db = { | |
| "Apple": {"calories_per_100g": 52, "portion_g": 182}, | |
| "Banana": {"calories_per_100g": 89, "portion_g": 118}, | |
| "Orange": {"calories_per_100g": 43, "portion_g": 131}, | |
| "Pizza": {"calories_per_100g": 266, "portion_g": 200}, | |
| "Bread": {"calories_per_100g": 265, "portion_g": 30}, | |
| # Add the rest of the 101 categories if you need them | |
| } | |
| # ------------------------------------------------------------------- | |
| # 3️⃣ Pydantic model for the incoming JSON payload | |
| # ------------------------------------------------------------------- | |
| class ImageRequest(BaseModel): | |
| image: str # base64‑encoded JPEG/PNG | |
| app = FastAPI() | |
| # ------------------------------------------------------------ | |
| # Health‑check endpoint (optional) | |
| # ------------------------------------------------------------ | |
| def health(): | |
| return {"message": "Food‑Recognition API is up"} | |
| # ------------------------------------------------------------ | |
| # 4️⃣ Main inference endpoint | |
| # ------------------------------------------------------------ | |
| def analyze(request: ImageRequest): | |
| # ---- 4.1 decode the base64 image --------------------------------- | |
| try: | |
| raw = base64.b64decode(request.image) | |
| pil_img = Image.open(io.BytesIO(raw)).convert("RGB") | |
| except Exception: | |
| raise HTTPException(status_code=400, detail="Invalid base64 image") | |
| # ---- 4.2 preprocess ------------------------------------------------ | |
| inputs = processor(images=pil_img, return_tensors="pt") | |
| inputs = {k: v.to(device) for k, v in inputs.items()} | |
| # ---- 4.3 forward pass --------------------------------------------- | |
| with torch.no_grad(): | |
| outputs = model(**inputs) | |
| # The model returns logits (shape [1, num_classes]) | |
| logits = outputs.logits.squeeze(0) # [num_classes] | |
| probs = torch.nn.functional.softmax(logits, dim=-1) | |
| # ---- 4.4 get top‑1 prediction -------------------------------------- | |
| top_idx = int(probs.argmax().item()) | |
| confidence = float(probs[top_idx].item()) | |
| label = id2label.get(top_idx, "unknown") | |
| # ---- 4.5 lookup nutrition ----------------------------------------- | |
| nutrition = nutrient_db.get(label, {"calories_per_100g": 0, "portion_g": 100}) | |
| calories_per_100g = nutrition["calories_per_100g"] | |
| portion_g = nutrition["portion_g"] | |
| estimated_calories = calories_per_100g * (portion_g / 100.0) | |
| # ---- 4.6 build JSON response --------------------------------------- | |
| return { | |
| "label": label, | |
| "confidence": confidence, | |
| "estimated_portion_g": portion_g, | |
| "calories_per_100g": calories_per_100g, | |
| "estimated_calories": round(estimated_calories, 2) | |
| } | |