AdeogunTechno commited on
Commit
c2fa88d
·
verified ·
1 Parent(s): fc4ac27

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +64 -61
app.py CHANGED
@@ -1,100 +1,103 @@
1
- """
2
- FastAPI service exposing BinhQuocNguyen/food‑recognition‑model.
3
- """
4
-
5
  # ------------------------------------------------------------
6
- # 1️⃣ Imports
7
  # ------------------------------------------------------------
8
  from fastapi import FastAPI, HTTPException
9
  from pydantic import BaseModel
10
- import base64
11
- import io
12
-
13
  from PIL import Image
14
- from transformers import pipeline
 
15
 
16
- # ------------------------------------------------------------
17
- # 2️⃣ Load the model once at startup (fast, no re‑load per request)
18
- # ------------------------------------------------------------
19
- classifier = pipeline(
20
- "image-classification",
21
- model="BinhQuocNguyen/food-recognition-model"
22
- )
23
 
24
- # ------------------------------------------------------------
25
- # 3️⃣ Simple in‑memory “nutritional DB”
26
- # (calories per 100 g, average portion weight in grams)
27
- # ------------------------------------------------------------
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  nutrient_db = {
29
  "Apple": {"calories_per_100g": 52, "portion_g": 182},
30
  "Banana": {"calories_per_100g": 89, "portion_g": 118},
31
  "Orange": {"calories_per_100g": 43, "portion_g": 131},
32
  "Pizza": {"calories_per_100g": 266, "portion_g": 200},
33
  "Bread": {"calories_per_100g": 265, "portion_g": 30},
34
- # add the rest of your favourite foods here
35
  }
36
 
37
- # ------------------------------------------------------------
38
- # 4️⃣ Pydantic model for the incoming JSON payload
39
- # ------------------------------------------------------------
40
  class ImageRequest(BaseModel):
41
- """Base64‑encoded image sent by the client."""
42
- image: str
43
 
44
-
45
- # ------------------------------------------------------------
46
- # 5️⃣ FastAPI app & health‑check endpoint
47
- # ------------------------------------------------------------
48
  app = FastAPI()
49
 
50
 
 
 
 
51
  @app.get("/")
52
- def root():
53
- """Simple health‑check."""
54
  return {"message": "Food‑Recognition API is up"}
55
 
56
 
57
  # ------------------------------------------------------------
58
- # 6️⃣ Main inference endpoint
59
  # ------------------------------------------------------------
60
  @app.post("/analyze")
61
  def analyze(request: ImageRequest):
62
- """
63
- 1️⃣ Decode the base64 image.
64
- 2️⃣ Run the classifier.
65
- 3️⃣ Look up (or fall back to) nutritional information.
66
- 4️⃣ Return a JSON response.
67
- """
68
- # ---- 1️⃣ decode the base64 image ---------------------------------
69
  try:
70
- raw_bytes = base64.b64decode(request.image)
71
- pil_img = Image.open(io.BytesIO(raw_bytes)).convert("RGB")
72
- except Exception as e: # pragma: no cover
73
- raise HTTPException(status_code=400, detail="Invalid base64 image") from e
74
-
75
- # ---- 2️⃣ run the classifier --------------------------------------
76
- results = classifier(pil_img)
77
- if not results:
78
- raise HTTPException(status_code=500, detail="Model returned no results")
79
-
80
- top = results[0]
81
- label = top.get("label")
82
- confidence = top.get("score")
83
-
84
- # ---- 3️⃣ look up nutrition data ----------------------------------
85
- # Fallback values if the label isn’t in the DB
 
 
 
 
 
 
 
86
  nutrition = nutrient_db.get(label, {"calories_per_100g": 0, "portion_g": 100})
87
  calories_per_100g = nutrition["calories_per_100g"]
88
  portion_g = nutrition["portion_g"]
 
89
 
90
- # ---- 4️⃣ estimated calories for the default portion size ----------
91
- est_calories = calories_per_100g * (portion_g / 100.0)
92
-
93
- # ---- 5️⃣ build the JSON response --------------------------------
94
  return {
95
  "label": label,
96
  "confidence": confidence,
97
  "estimated_portion_g": portion_g,
98
  "calories_per_100g": calories_per_100g,
99
- "estimated_calories": round(est_calories, 2),
100
  }
 
 
 
 
 
1
  # ------------------------------------------------------------
2
+ # FastAPI service exposing BinhQuocNguyen/food-recognition-model
3
  # ------------------------------------------------------------
4
  from fastapi import FastAPI, HTTPException
5
  from pydantic import BaseModel
6
+ import base64, io
 
 
7
  from PIL import Image
8
+ import torch
9
+ import numpy as np
10
 
11
+ # Transformers imports
12
+ from transformers import AutoModel, AutoImageProcessor
 
 
 
 
 
13
 
14
+ # -------------------------------------------------------------------
15
+ # 1️⃣ Load the model & processor (once, at import time)
16
+ # -------------------------------------------------------------------
17
+ MODEL_NAME = "BinhQuocNguyen/food-recognition-model"
18
+
19
+ # AutoModel knows the custom architecture (food_recognition) because
20
+ # the repository ships a proper `config.json`.
21
+ model = AutoModel.from_pretrained(MODEL_NAME)
22
+ processor = AutoImageProcessor.from_pretrained(MODEL_NAME)
23
+
24
+ # Put the model on CPU – the Space has no GPU.
25
+ device = torch.device("cpu")
26
+ model.to(device)
27
+ model.eval()
28
+
29
+ # Mapping from class index → readable label (comes from the config)
30
+ id2label = model.config.id2label # dict[int, str]
31
+
32
+ # -------------------------------------------------------------------
33
+ # 2️⃣ Minimal nutrient lookup table (extend as you like)
34
+ # -------------------------------------------------------------------
35
  nutrient_db = {
36
  "Apple": {"calories_per_100g": 52, "portion_g": 182},
37
  "Banana": {"calories_per_100g": 89, "portion_g": 118},
38
  "Orange": {"calories_per_100g": 43, "portion_g": 131},
39
  "Pizza": {"calories_per_100g": 266, "portion_g": 200},
40
  "Bread": {"calories_per_100g": 265, "portion_g": 30},
41
+ # Add the rest of the 101 categories if you need them
42
  }
43
 
44
+ # -------------------------------------------------------------------
45
+ # 3️⃣ Pydantic model for the incoming JSON payload
46
+ # -------------------------------------------------------------------
47
  class ImageRequest(BaseModel):
48
+ image: str # base64‑encoded JPEG/PNG
 
49
 
 
 
 
 
50
  app = FastAPI()
51
 
52
 
53
+ # ------------------------------------------------------------
54
+ # Health‑check endpoint (optional)
55
+ # ------------------------------------------------------------
56
  @app.get("/")
57
+ def health():
 
58
  return {"message": "Food‑Recognition API is up"}
59
 
60
 
61
  # ------------------------------------------------------------
62
+ # 4️⃣ Main inference endpoint
63
  # ------------------------------------------------------------
64
  @app.post("/analyze")
65
  def analyze(request: ImageRequest):
66
+ # ---- 4.1 decode the base64 image ---------------------------------
 
 
 
 
 
 
67
  try:
68
+ raw = base64.b64decode(request.image)
69
+ pil_img = Image.open(io.BytesIO(raw)).convert("RGB")
70
+ except Exception:
71
+ raise HTTPException(status_code=400, detail="Invalid base64 image")
72
+
73
+ # ---- 4.2 preprocess ------------------------------------------------
74
+ inputs = processor(images=pil_img, return_tensors="pt")
75
+ inputs = {k: v.to(device) for k, v in inputs.items()}
76
+
77
+ # ---- 4.3 forward pass ---------------------------------------------
78
+ with torch.no_grad():
79
+ outputs = model(**inputs)
80
+
81
+ # The model returns logits (shape [1, num_classes])
82
+ logits = outputs.logits.squeeze(0) # [num_classes]
83
+ probs = torch.nn.functional.softmax(logits, dim=-1)
84
+
85
+ # ---- 4.4 get top‑1 prediction --------------------------------------
86
+ top_idx = int(probs.argmax().item())
87
+ confidence = float(probs[top_idx].item())
88
+ label = id2label.get(top_idx, "unknown")
89
+
90
+ # ---- 4.5 lookup nutrition -----------------------------------------
91
  nutrition = nutrient_db.get(label, {"calories_per_100g": 0, "portion_g": 100})
92
  calories_per_100g = nutrition["calories_per_100g"]
93
  portion_g = nutrition["portion_g"]
94
+ estimated_calories = calories_per_100g * (portion_g / 100.0)
95
 
96
+ # ---- 4.6 build JSON response ---------------------------------------
 
 
 
97
  return {
98
  "label": label,
99
  "confidence": confidence,
100
  "estimated_portion_g": portion_g,
101
  "calories_per_100g": calories_per_100g,
102
+ "estimated_calories": round(estimated_calories, 2)
103
  }