Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import os | |
| import json | |
| import requests | |
| from PIL import Image | |
| from difflib import get_close_matches | |
| from functools import lru_cache | |
| # ===================== | |
| # ENV | |
| # ===================== | |
| ROBOFLOW_API_KEY = os.getenv("ROBOFLOW_API_KEY") | |
| # ===================== | |
| # LOAD DB | |
| # ===================== | |
| with open("nutrition_db.json", "r") as f: | |
| NUTRITION_DB = json.load(f) | |
| print("β Loaded DB:", len(NUTRITION_DB)) | |
| # ===================== | |
| # NORMALIZATION | |
| # ===================== | |
| def normalize_food_name(name): | |
| name = name.lower() | |
| mapping = { | |
| "chapati": "wheat", | |
| "roti": "wheat", | |
| "naan": "wheat", | |
| "paratha": "wheat", | |
| "omelette": "egg", | |
| "omellete": "egg", | |
| "fried rice": "rice", | |
| "plain rice": "rice" | |
| } | |
| return mapping.get(name, name) | |
| # ===================== | |
| # FIND MATCH | |
| # ===================== | |
| def find_food(name): | |
| if name in NUTRITION_DB: | |
| return name | |
| matches = get_close_matches(name, NUTRITION_DB.keys(), n=1, cutoff=0.6) | |
| return matches[0] if matches else None | |
| # ===================== | |
| # FALLBACK | |
| # ===================== | |
| def estimate_unknown_food(grams): | |
| return { | |
| "calories": round(1.5 * grams, 2), | |
| "protein": round(0.05 * grams, 2), | |
| "carbs": round(0.2 * grams, 2), | |
| "fat": round(0.05 * grams, 2), | |
| } | |
| # ===================== | |
| # CACHE | |
| # ===================== | |
| def compute_nutrition_cached(food_key, grams): | |
| base = NUTRITION_DB[food_key] | |
| factor = grams / 100 | |
| return { | |
| "calories": round(base["calories"] * factor, 2), | |
| "protein": round(base["protein"] * factor, 2), | |
| "carbs": round(base["carbs"] * factor, 2), | |
| "fat": round(base["fat"] * factor, 2), | |
| } | |
| # ===================== | |
| # GET NUTRITION | |
| # ===================== | |
| def get_nutrition(dish, grams): | |
| dish = normalize_food_name(dish) | |
| food_key = find_food(dish) | |
| if not food_key: | |
| return estimate_unknown_food(grams) | |
| return compute_nutrition_cached(food_key, grams) | |
| # ===================== | |
| # QUANTITY | |
| # ===================== | |
| def estimate_quantity(pred): | |
| width = pred.get("width", 0) | |
| height = pred.get("height", 0) | |
| area = width * height | |
| ratio = area / (640 * 640) | |
| grams = 150 + (ratio * 300) | |
| return round(grams, 1) | |
| # ===================== | |
| # ROBOFLOW (HTTP) | |
| # ===================== | |
| import requests | |
| import os | |
| def detect(image_path): | |
| api_key = os.getenv("ROBOFLOW_API_KEY") | |
| if not api_key: | |
| return "β API KEY NOT FOUND" | |
| # model_id = "almost-final/1" | |
| url = "https://detect.roboflow.com/almost-final/1" | |
| try: | |
| with open(image_path, "rb") as f: | |
| response = requests.post( | |
| url, | |
| files={"file": f}, # β correct format | |
| params={"api_key": api_key}, | |
| timeout=15 | |
| ) | |
| print("STATUS:", response.status_code) | |
| print("RESPONSE:", response.text) | |
| if response.status_code != 200: | |
| return f"β Roboflow Error: {response.text}" | |
| data = response.json() | |
| return data.get("predictions", []) | |
| except Exception as e: | |
| return f"β Request Failed: {str(e)}" | |
| # ===================== | |
| # MAIN FUNCTION | |
| # ===================== | |
| def analyze_image(image): | |
| if image is None: | |
| return "Please upload an image" | |
| path = "temp.jpg" | |
| # PIL image save | |
| image.save(path) | |
| preds = detect(path) | |
| if isinstance(preds, str): | |
| return preds | |
| if len(preds) == 0: | |
| return "β No food detected" | |
| output = "" | |
| total = {"calories": 0, "protein": 0, "carbs": 0, "fat": 0} | |
| for pred in preds: | |
| dish = pred.get("class", "unknown") | |
| grams = estimate_quantity(pred) | |
| nutrition = get_nutrition(dish, grams) | |
| output += f"π½οΈ {dish}\n" | |
| output += f"π {grams} g\n" | |
| output += f"π₯ {nutrition['calories']} kcal\n" | |
| output += f"πͺ Protein: {nutrition['protein']} g\n" | |
| output += f"π Carbs: {nutrition['carbs']} g\n" | |
| output += f"π§ Fat: {nutrition['fat']} g\n" | |
| output += "-"*30 + "\n" | |
| for k in total: | |
| total[k] += nutrition[k] | |
| output += "\nπ§Ύ TOTAL:\n" | |
| output += f"π₯ Calories: {round(total['calories'],2)}\n" | |
| output += f"πͺ Protein: {round(total['protein'],2)} g\n" | |
| output += f"π Carbs: {round(total['carbs'],2)} g\n" | |
| output += f"π§ Fat: {round(total['fat'],2)} g\n" | |
| return output | |
| # ===================== | |
| # UI | |
| # ===================== | |
| demo = gr.Interface( | |
| fn=analyze_image, | |
| inputs=gr.Image(type="pil"), # β FIXED | |
| outputs="text", | |
| title="π½οΈ AI Nutritionist", | |
| description="Upload food image to get calories & macros" | |
| ) | |
| # ===================== | |
| # RUN | |
| # ===================== | |
| if __name__ == "__main__": | |
| demo.launch(ssr_mode=False) |