Unnatrathi commited on
Commit
92da58c
Β·
verified Β·
1 Parent(s): eb499ce

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +60 -6
app.py CHANGED
@@ -40,6 +40,8 @@ from fastapi import FastAPI, HTTPException, Header, Depends
40
  from fastapi.middleware.cors import CORSMiddleware
41
  from pydantic import BaseModel
42
  from PIL import Image
 
 
43
 
44
  # ── Lazy imports for heavy ML deps ──────────────────────────────────────────
45
  # Imported inside lifespan so the Space starts quickly and fails clearly
@@ -208,10 +210,50 @@ def _run_inference(image: Image.Image, max_new_tokens: int) -> str:
208
  return _processor.batch_decode(new_tokens, skip_special_tokens=True)[0].strip()
209
 
210
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
211
  def _parse_response(raw: str) -> dict:
212
  result = {"ingredients": "", "portion_notes": "", "raw_text": raw}
213
 
214
- # Try the CaLoRAify reasoning loop format first
215
  if "Ingredients detected:" in raw:
216
  ing_start = raw.index("Ingredients detected:") + len("Ingredients detected:")
217
  ing_end = raw.index(".", ing_start) if "." in raw[ing_start:] else len(raw)
@@ -223,8 +265,8 @@ def _parse_response(raw: str) -> dict:
223
  result["portion_notes"] = raw[pa_start:pa_end].strip()
224
 
225
  if "JSON Summary:" in raw:
226
- json_start = raw.index("JSON Summary:") + len("JSON Summary:")
227
- json_str = raw[json_start:].strip()
228
  brace_start = json_str.find("{")
229
  brace_end = json_str.rfind("}") + 1
230
  if brace_start != -1 and brace_end > brace_start:
@@ -240,9 +282,21 @@ def _parse_response(raw: str) -> dict:
240
  except json.JSONDecodeError:
241
  pass
242
 
243
- # ── FALLBACK: if model didn't follow format, use raw text as ingredients
244
- if not result["ingredients"] and raw.strip():
245
- result["ingredients"] = raw.strip()[:300] # show raw output so you can see what model said
 
 
 
 
 
 
 
 
 
 
 
 
246
 
247
  return result
248
 
 
40
  from fastapi.middleware.cors import CORSMiddleware
41
  from pydantic import BaseModel
42
  from PIL import Image
43
+ import re
44
+ import requests as req_lib
45
 
46
  # ── Lazy imports for heavy ML deps ──────────────────────────────────────────
47
  # Imported inside lifespan so the Space starts quickly and fails clearly
 
210
  return _processor.batch_decode(new_tokens, skip_special_tokens=True)[0].strip()
211
 
212
 
213
+ import re
214
+ import requests as req_lib
215
+
216
+ def _get_nutrition_from_api(ingredients_text: str) -> dict:
217
+ """Use Open Food Facts search β€” no API key needed."""
218
+ try:
219
+ # Take the first identified food item
220
+ food_query = ingredients_text.split(",")[0].strip()
221
+
222
+ response = req_lib.get(
223
+ "https://world.openfoodfacts.org/cgi/search.pl",
224
+ params={
225
+ "search_terms": food_query,
226
+ "search_simple": 1,
227
+ "action": "process",
228
+ "json": 1,
229
+ "page_size": 1,
230
+ },
231
+ timeout=10,
232
+ )
233
+ response.raise_for_status()
234
+ data = response.json()
235
+ products = data.get("products", [])
236
+
237
+ if not products:
238
+ return {}
239
+
240
+ nutriments = products[0].get("nutriments", {})
241
+ return {
242
+ "calories": round(nutriments.get("energy-kcal_100g", 0) or 0, 1),
243
+ "protein_g": round(nutriments.get("proteins_100g", 0) or 0, 1),
244
+ "carbs_g": round(nutriments.get("carbohydrates_100g", 0) or 0, 1),
245
+ "fat_g": round(nutriments.get("fat_100g", 0) or 0, 1),
246
+ "fibre_g": round(nutriments.get("fiber_100g", 0) or 0, 1),
247
+ }
248
+ except Exception as e:
249
+ logger.warning(f"Open Food Facts API failed: {e}")
250
+ return {}
251
+
252
+
253
  def _parse_response(raw: str) -> dict:
254
  result = {"ingredients": "", "portion_notes": "", "raw_text": raw}
255
 
256
+ # ── Try structured CaLoRAify format first ─────────────────────────────
257
  if "Ingredients detected:" in raw:
258
  ing_start = raw.index("Ingredients detected:") + len("Ingredients detected:")
259
  ing_end = raw.index(".", ing_start) if "." in raw[ing_start:] else len(raw)
 
265
  result["portion_notes"] = raw[pa_start:pa_end].strip()
266
 
267
  if "JSON Summary:" in raw:
268
+ json_start = raw.index("JSON Summary:") + len("JSON Summary:")
269
+ json_str = raw[json_start:].strip()
270
  brace_start = json_str.find("{")
271
  brace_end = json_str.rfind("}") + 1
272
  if brace_start != -1 and brace_end > brace_start:
 
282
  except json.JSONDecodeError:
283
  pass
284
 
285
+ # ── Fallback: model gave plain description ─────────────────────────────
286
+ if not result["ingredients"]:
287
+ # Extract food nouns from the raw description
288
+ result["ingredients"] = raw.strip()[:200]
289
+ result["portion_notes"] = "Portion estimated from image."
290
+
291
+ # ── If we still have no calories, call Nutritionix ────────────────────
292
+ if result["calories"] is None and result["ingredients"]:
293
+ logger.info(f"Calling Nutritionix for: {result['ingredients'][:80]}")
294
+ nutrition = _get_nutrition_from_api(result["ingredients"])
295
+ if nutrition:
296
+ result.update(nutrition)
297
+ logger.info(f"Nutritionix returned: {nutrition}")
298
+ else:
299
+ logger.warning("Nutritionix returned nothing β€” calories will be None")
300
 
301
  return result
302