AdeogunTechno's picture
Update app.py
c2fa88d verified
Raw
History Blame Contribute Delete
4.08 kB
# ------------------------------------------------------------
# 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)
# ------------------------------------------------------------
@app.get("/")
def health():
return {"message": "Food‑Recognition API is up"}
# ------------------------------------------------------------
# 4️⃣ Main inference endpoint
# ------------------------------------------------------------
@app.post("/analyze")
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)
}