File size: 4,079 Bytes
e626858
c2fa88d
e626858
 
 
c2fa88d
e626858
c2fa88d
 
e626858
c2fa88d
 
e626858
c2fa88d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e626858
 
 
 
 
 
c2fa88d
e626858
 
c2fa88d
 
 
e626858
c2fa88d
e626858
 
 
 
c2fa88d
 
 
e626858
c2fa88d
e626858
 
 
 
c2fa88d
e626858
 
 
c2fa88d
e626858
c2fa88d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e626858
 
 
c2fa88d
e626858
c2fa88d
e626858
 
 
 
 
c2fa88d
e626858
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# ------------------------------------------------------------
# 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)
    }