Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import numpy as np | |
| import pytesseract | |
| from PIL import Image, ImageEnhance, ImageFilter | |
| import pandas as pd | |
| import re | |
| import json | |
| import os | |
| import random | |
| import datetime | |
| import matplotlib.pyplot as plt | |
| import seaborn as sns | |
| import cv2 | |
| import requests | |
| from io import BytesIO | |
| # Load nutrition database with expanded items | |
| def load_nutrition_data(): | |
| # Enhanced food database with more items and categories | |
| food_data = { | |
| # Fast food and restaurant items | |
| "pizza": {"calories": 285, "fat": 10, "carbs": 36, "protein": 12, "category": "junk"}, | |
| "burger": {"calories": 354, "fat": 17, "carbs": 40, "protein": 15, "category": "junk"}, | |
| "cheeseburger": {"calories": 400, "fat": 20, "carbs": 40, "protein": 15, "category": "junk"}, | |
| "hamburger": {"calories": 350, "fat": 15, "carbs": 40, "protein": 15, "category": "junk"}, | |
| "fries": {"calories": 312, "fat": 15, "carbs": 41, "protein": 3, "category": "junk"}, | |
| "french fries": {"calories": 312, "fat": 15, "carbs": 41, "protein": 3, "category": "junk"}, | |
| "salad": {"calories": 100, "fat": 7, "carbs": 5, "protein": 2, "category": "healthy"}, | |
| "caesar salad": {"calories": 150, "fat": 10, "carbs": 5, "protein": 3, "category": "healthy"}, | |
| "garden salad": {"calories": 80, "fat": 5, "carbs": 5, "protein": 2, "category": "healthy"}, | |
| "soda": {"calories": 140, "fat": 0, "carbs": 39, "protein": 0, "category": "junk"}, | |
| "coke": {"calories": 140, "fat": 0, "carbs": 39, "protein": 0, "category": "junk"}, | |
| "pepsi": {"calories": 150, "fat": 0, "carbs": 41, "protein": 0, "category": "junk"}, | |
| "sprite": {"calories": 140, "fat": 0, "carbs": 38, "protein": 0, "category": "junk"}, | |
| "cola": {"calories": 140, "fat": 0, "carbs": 39, "protein": 0, "category": "junk"}, | |
| "diet coke": {"calories": 0, "fat": 0, "carbs": 0, "protein": 0, "category": "neutral"}, | |
| "juice": {"calories": 110, "fat": 0, "carbs": 26, "protein": 0, "category": "neutral"}, | |
| "orange juice": {"calories": 110, "fat": 0, "carbs": 26, "protein": 0, "category": "neutral"}, | |
| "apple juice": {"calories": 115, "fat": 0, "carbs": 28, "protein": 0, "category": "neutral"}, | |
| "water": {"calories": 0, "fat": 0, "carbs": 0, "protein": 0, "category": "healthy"}, | |
| "sparkling water": {"calories": 0, "fat": 0, "carbs": 0, "protein": 0, "category": "healthy"}, | |
| "pasta": {"calories": 200, "fat": 2, "carbs": 42, "protein": 7, "category": "neutral"}, | |
| "spaghetti": {"calories": 220, "fat": 2, "carbs": 43, "protein": 8, "category": "neutral"}, | |
| "pasta carbonara": {"calories": 380, "fat": 18, "carbs": 43, "protein": 14, "category": "neutral"}, | |
| "fettuccine": {"calories": 220, "fat": 2, "carbs": 43, "protein": 8, "category": "neutral"}, | |
| "lasagna": {"calories": 360, "fat": 12, "carbs": 37, "protein": 25, "category": "neutral"}, | |
| "mac and cheese": {"calories": 350, "fat": 15, "carbs": 45, "protein": 15, "category": "neutral"}, | |
| "macaroni": {"calories": 200, "fat": 2, "carbs": 42, "protein": 7, "category": "neutral"}, | |
| "steak": {"calories": 300, "fat": 15, "carbs": 0, "protein": 30, "category": "protein"}, | |
| "ribeye": {"calories": 330, "fat": 25, "carbs": 0, "protein": 30, "category": "protein"}, | |
| "filet mignon": {"calories": 320, "fat": 20, "carbs": 0, "protein": 35, "category": "protein"}, | |
| "sirloin": {"calories": 270, "fat": 12, "carbs": 0, "protein": 32, "category": "protein"}, | |
| "chicken": {"calories": 220, "fat": 8, "carbs": 0, "protein": 40, "category": "protein"}, | |
| "chicken wings": {"calories": 350, "fat": 18, "carbs": 5, "protein": 33, "category": "protein"}, | |
| "chicken tenders": {"calories": 380, "fat": 20, "carbs": 20, "protein": 30, "category": "protein"}, | |
| "grilled chicken": {"calories": 220, "fat": 8, "carbs": 0, "protein": 40, "category": "protein"}, | |
| "fried chicken": {"calories": 320, "fat": 16, "carbs": 12, "protein": 28, "category": "protein"}, | |
| "fish": {"calories": 180, "fat": 5, "carbs": 0, "protein": 30, "category": "healthy"}, | |
| "salmon": {"calories": 200, "fat": 10, "carbs": 0, "protein": 25, "category": "healthy"}, | |
| "tuna": {"calories": 160, "fat": 3, "carbs": 0, "protein": 33, "category": "healthy"}, | |
| "cod": {"calories": 150, "fat": 2, "carbs": 0, "protein": 28, "category": "healthy"}, | |
| "rice": {"calories": 130, "fat": 0, "carbs": 28, "protein": 3, "category": "neutral"}, | |
| "brown rice": {"calories": 110, "fat": 1, "carbs": 22, "protein": 3, "category": "healthy"}, | |
| "white rice": {"calories": 130, "fat": 0, "carbs": 28, "protein": 3, "category": "neutral"}, | |
| "fried rice": {"calories": 230, "fat": 10, "carbs": 28, "protein": 8, "category": "neutral"}, | |
| #Indian Food Items | |
| "butter chicken": {"calories": 450, "fat": 28, "carbs": 14, "protein": 32, "category": "protein"}, | |
| "chole bhature": {"calories": 550, "fat": 30, "carbs": 50, "protein": 14, "category": "junk"}, | |
| "dal makhani": {"calories": 320, "fat": 18, "carbs": 24, "protein": 14, "category": "neutral"}, | |
| "rajma chawal": {"calories": 410, "fat": 10, "carbs": 60, "protein": 15, "category": "neutral"}, | |
| "paneer butter masala": {"calories": 430, "fat": 30, "carbs": 20, "protein": 18, "category": "protein"}, | |
| "tandoori chicken": {"calories": 290, "fat": 13, "carbs": 4, "protein": 35, "category": "protein"}, | |
| "biryani": {"calories": 480, "fat": 20, "carbs": 55, "protein": 18, "category": "neutral"}, | |
| "veg biryani": {"calories": 400, "fat": 15, "carbs": 50, "protein": 10, "category": "neutral"}, | |
| "chicken biryani": {"calories": 500, "fat": 20, "carbs": 55, "protein": 22, "category": "protein"}, | |
| "aloo paratha": {"calories": 300, "fat": 12, "carbs": 40, "protein": 7, "category": "neutral"}, | |
| "samosa": {"calories": 260, "fat": 15, "carbs": 25, "protein": 5, "category": "junk"}, | |
| "masala dosa": {"calories": 390, "fat": 15, "carbs": 50, "protein": 8, "category": "neutral"}, | |
| "idli sambar": {"calories": 270, "fat": 6, "carbs": 45, "protein": 10, "category": "healthy"}, | |
| "pav bhaji": {"calories": 420, "fat": 22, "carbs": 40, "protein": 8, "category": "junk"}, | |
| "poha": {"calories": 250, "fat": 10, "carbs": 35, "protein": 5, "category": "healthy"}, | |
| "upma": {"calories": 240, "fat": 8, "carbs": 34, "protein": 6, "category": "healthy"}, | |
| "chana masala": {"calories": 280, "fat": 12, "carbs": 30, "protein": 12, "category": "protein"}, | |
| "fish curry": {"calories": 310, "fat": 18, "carbs": 12, "protein": 25, "category": "protein"}, | |
| "mutton curry": {"calories": 500, "fat": 35, "carbs": 10, "protein": 30, "category": "protein"}, | |
| "kadai paneer": {"calories": 380, "fat": 25, "carbs": 18, "protein": 15, "category": "protein"}, | |
| "malai kofta": {"calories": 440, "fat": 30, "carbs": 25, "protein": 12, "category": "junk"}, | |
| "dal tadka": {"calories": 280, "fat": 10, "carbs": 30, "protein": 12, "category": "healthy"}, | |
| "aloo methi": {"calories": 200, "fat": 10, "carbs": 18, "protein": 6, "category": "healthy"}, | |
| "phulka": {"calories": 90, "fat": 2, "carbs": 18, "protein": 3, "category": "healthy"}, | |
| "butter naan": {"calories": 300, "fat": 10, "carbs": 40, "protein": 6, "category": "junk"}, | |
| "tandoori roti": {"calories": 120, "fat": 2, "carbs": 22, "protein": 4, "category": "healthy"}, | |
| "mineral water": {"calories": 0, "fat": 0, "carbs": 0, "protein": 0, "category": "healthy"}, | |
| "Chicken Tikka Masala": {"calories": 480, "fat": 28, "carbs": 16, "protein": 35, "category": "protein"}, | |
| "mutton biryani": {"calories": 550, "fat": 25, "carbs": 50, "protein": 30, "category": "protein"}, | |
| "garlic naan": {"calories": 160, "fat": 5, "carbs": 28, "protein": 4, "category": "neutral"}, | |
| "garlic naan (2 pcs)": {"calories": 320, "fat": 10, "carbs": 56, "protein": 8, "category": "neutral"}, | |
| "butter naan (2 pcs)": {"calories": 600, "fat": 20, "carbs": 80, "protein": 12, "category": "junk"}, | |
| "jeera rice": {"calories": 210, "fat": 5, "carbs": 35, "protein": 4, "category": "neutral"}, | |
| "papadum": {"calories": 40, "fat": 2, "carbs": 4, "protein": 1, "category": "neutral"}, | |
| "papadum": {"calories": 160, "fat": 8, "carbs": 16, "protein": 4, "category": "neutral"}, | |
| "mixed raita": {"calories": 100, "fat": 5, "carbs": 10, "protein": 3, "category": "healthy"}, | |
| "gulab jamun": {"calories": 150, "fat": 7, "carbs": 25, "protein": 3, "category": "junk"}, | |
| "gulab jamun 4 pcs": {"calories": 600, "fat": 28, "carbs": 100, "protein": 12, "category": "junk"}, | |
| "masala chai": {"calories": 80, "fat": 3, "carbs": 10, "protein": 2, "category": "neutral"}, | |
| "masala chai 4 cups": {"calories": 320, "fat": 12, "carbs": 40, "protein": 8, "category": "neutral"}, | |
| "pongal": {"calories": 320, "fat": 12, "carbs": 40, "protein": 7, "category": "neutral"}, | |
| "medu vadai": {"calories": 150, "fat": 8, "carbs": 15, "protein": 4, "category": "junk"}, | |
| "sambhar idly": {"calories": 270, "fat": 6, "carbs": 40, "protein": 10, "category": "healthy"}, | |
| "poori": {"calories": 150, "fat": 8, "carbs": 18, "protein": 3, "category": "junk"}, | |
| "ghee roast": {"calories": 450, "fat": 25, "carbs": 45, "protein": 7, "category": "junk"}, | |
| "tea": {"calories": 50, "fat": 2, "carbs": 5, "protein": 1, "category": "neutral"}, | |
| "dal makhani": {"calories": 320, "fat": 18, "carbs": 24, "protein": 14, "category": "neutral"}, | |
| "mixed raita": {"calories": 100, "fat": 5, "carbs": 10, "protein": 3, "category": "healthy"}, | |
| "gulab jamun": {"calories": 150, "fat": 7, "carbs": 25, "protein": 3, "category": "junk"}, | |
| "masala chai": {"calories": 80, "fat": 3, "carbs": 10, "protein": 2, "category": "neutral"}, | |
| "mutton biryani": {"calories": 550, "fat": 25, "carbs": 50, "protein": 30, "category": "protein"}, | |
| # Drinks | |
| "beer": {"calories": 154, "fat": 0, "carbs": 13, "protein": 1, "category": "junk"}, | |
| "wine": {"calories": 125, "fat": 0, "carbs": 4, "protein": 0, "category": "neutral"}, | |
| "red wine": {"calories": 125, "fat": 0, "carbs": 4, "protein": 0, "category": "neutral"}, | |
| "white wine": {"calories": 120, "fat": 0, "carbs": 4, "protein": 0, "category": "neutral"}, | |
| "cocktail": {"calories": 180, "fat": 0, "carbs": 20, "protein": 0, "category": "junk"}, | |
| "margarita": {"calories": 200, "fat": 0, "carbs": 25, "protein": 0, "category": "junk"}, | |
| "daiquiri": {"calories": 180, "fat": 0, "carbs": 20, "protein": 0, "category": "junk"}, | |
| "mojito": {"calories": 160, "fat": 0, "carbs": 18, "protein": 0, "category": "junk"}, | |
| "martini": {"calories": 120, "fat": 0, "carbs": 3, "protein": 0, "category": "neutral"}, | |
| "coffee": {"calories": 5, "fat": 0, "carbs": 0, "protein": 0, "category": "healthy"}, | |
| "latte": {"calories": 120, "fat": 4, "carbs": 10, "protein": 8, "category": "neutral"}, | |
| "cappuccino": {"calories": 110, "fat": 4, "carbs": 8, "protein": 6, "category": "neutral"}, | |
| "espresso": {"calories": 5, "fat": 0, "carbs": 0, "protein": 0, "category": "healthy"}, | |
| # Desserts | |
| "ice cream": {"calories": 207, "fat": 11, "carbs": 24, "protein": 4, "category": "junk"}, | |
| "cake": {"calories": 350, "fat": 18, "carbs": 45, "protein": 4, "category": "junk"}, | |
| "chocolate cake": {"calories": 370, "fat": 19, "carbs": 48, "protein": 5, "category": "junk"}, | |
| "cheesecake": {"calories": 400, "fat": 25, "carbs": 35, "protein": 7, "category": "junk"}, | |
| "tiramisu": {"calories": 380, "fat": 20, "carbs": 40, "protein": 5, "category": "junk"}, | |
| "brownie": {"calories": 300, "fat": 15, "carbs": 40, "protein": 3, "category": "junk"}, | |
| "cookie": {"calories": 180, "fat": 9, "carbs": 22, "protein": 2, "category": "junk"}, | |
| "chocolate": {"calories": 200, "fat": 12, "carbs": 20, "protein": 2, "category": "junk"}, | |
| "pie": {"calories": 300, "fat": 14, "carbs": 38, "protein": 3, "category": "junk"}, | |
| "apple pie": {"calories": 290, "fat": 14, "carbs": 40, "protein": 3, "category": "junk"}, | |
| "pudding": {"calories": 150, "fat": 4, "carbs": 25, "protein": 3, "category": "junk"}, | |
| # Other common items | |
| "sandwich": {"calories": 250, "fat": 8, "carbs": 30, "protein": 15, "category": "neutral"}, | |
| "wrap": {"calories": 220, "fat": 5, "carbs": 30, "protein": 13, "category": "neutral"}, | |
| "soup": {"calories": 120, "fat": 3, "carbs": 12, "protein": 10, "category": "healthy"}, | |
| "bread": {"calories": 80, "fat": 1, "carbs": 15, "protein": 3, "category": "neutral"}, | |
| "garlic bread": {"calories": 150, "fat": 6, "carbs": 18, "protein": 4, "category": "neutral"}, | |
| "roll": {"calories": 80, "fat": 1, "carbs": 15, "protein": 3, "category": "neutral"}, | |
| "milkshake": {"calories": 300, "fat": 10, "carbs": 50, "protein": 9, "category": "junk"}, | |
| "dessert": {"calories": 280, "fat": 14, "carbs": 35, "protein": 5, "category": "junk"}, | |
| "smoothie": {"calories": 170, "fat": 2, "carbs": 35, "protein": 5, "category": "neutral"}, | |
| "tea": {"calories": 2, "fat": 0, "carbs": 0, "protein": 0, "category": "healthy"}, | |
| "appetizer": {"calories": 200, "fat": 12, "carbs": 15, "protein": 8, "category": "neutral"}, | |
| "noodles": {"calories": 190, "fat": 2, "carbs": 40, "protein": 7, "category": "neutral"}, | |
| "taco": {"calories": 210, "fat": 10, "carbs": 22, "protein": 12, "category": "neutral"}, | |
| "burrito": {"calories": 350, "fat": 12, "carbs": 50, "protein": 15, "category": "neutral"}, | |
| "nachos": {"calories": 600, "fat": 35, "carbs": 58, "protein": 20, "category": "junk"}, | |
| "fajitas": {"calories": 290, "fat": 10, "carbs": 30, "protein": 25, "category": "neutral"}, | |
| "quesadilla": {"calories": 400, "fat": 22, "carbs": 35, "protein": 18, "category": "neutral"}, | |
| "eggs": {"calories": 140, "fat": 10, "carbs": 1, "protein": 12, "category": "protein"}, | |
| "omelette": {"calories": 220, "fat": 16, "carbs": 2, "protein": 16, "category": "protein"}, | |
| "pancakes": {"calories": 380, "fat": 12, "carbs": 60, "protein": 10, "category": "neutral"}, | |
| "waffles": {"calories": 370, "fat": 14, "carbs": 55, "protein": 8, "category": "neutral"}, | |
| "toast": {"calories": 80, "fat": 1, "carbs": 15, "protein": 3, "category": "neutral"}, | |
| "muffin": {"calories": 210, "fat": 10, "carbs": 30, "protein": 3, "category": "junk"}, | |
| "croissant": {"calories": 230, "fat": 12, "carbs": 26, "protein": 5, "category": "neutral"}, | |
| "doughnut": {"calories": 250, "fat": 12, "carbs": 30, "protein": 4, "category": "junk"}, | |
| "donut": {"calories": 250, "fat": 12, "carbs": 30, "protein": 4, "category": "junk"}, | |
| "bagel": {"calories": 245, "fat": 1, "carbs": 48, "protein": 10, "category": "neutral"}, | |
| "scone": {"calories": 230, "fat": 12, "carbs": 28, "protein": 4, "category": "neutral"}, | |
| # Side dishes | |
| "onion rings": {"calories": 320, "fat": 18, "carbs": 35, "protein": 5, "category": "junk"}, | |
| "mashed potatoes": {"calories": 150, "fat": 4, "carbs": 25, "protein": 3, "category": "neutral"}, | |
| "baked potato": {"calories": 130, "fat": 0, "carbs": 30, "protein": 3, "category": "neutral"}, | |
| "coleslaw": {"calories": 120, "fat": 8, "carbs": 10, "protein": 1, "category": "neutral"}, | |
| "corn": {"calories": 90, "fat": 1, "carbs": 20, "protein": 3, "category": "healthy"}, | |
| "broccoli": {"calories": 40, "fat": 0, "carbs": 8, "protein": 4, "category": "healthy"}, | |
| "veggies": {"calories": 50, "fat": 0, "carbs": 10, "protein": 2, "category": "healthy"}, | |
| "vegetables": {"calories": 50, "fat": 0, "carbs": 10, "protein": 2, "category": "healthy"}, | |
| "chips": {"calories": 300, "fat": 15, "carbs": 35, "protein": 3, "category": "junk"}, | |
| } | |
| return food_data | |
| # Load nutrition database | |
| nutrition_data = load_nutrition_data() | |
| # Load motivational quotes based on health score ranges | |
| def load_motivational_quotes(): | |
| quotes = { | |
| "excellent": [ | |
| "You're making excellent food choices! Your body thanks you for the premium fuel.", | |
| "Fantastic choices! You're investing in your long-term health with every bite.", | |
| "Your healthy eating habits today are building your stronger body for tomorrow.", | |
| "Impressive meal choices! You're mastering the art of nutritious eating.", | |
| "You're a nutrition champion! These balanced choices will energize your day." | |
| ], | |
| "good": [ | |
| "Good job balancing nutrition! Small improvements can take you to the next level.", | |
| "You're on the right track with your food choices. Keep building those healthy habits!", | |
| "Nice work choosing a fairly balanced meal. Your body appreciates the consideration.", | |
| "Your meal choices show you care about your health. Keep that momentum going!", | |
| "Good balance of nutrients in this meal. Remember: consistency is key to health." | |
| ], | |
| "moderate": [ | |
| "This meal has some nutritional bright spots. Consider adding more protein next time.", | |
| "Balance is a journey. Try adding more vegetables to your next meal.", | |
| "Everyone indulges sometimes. Tomorrow is a new opportunity for nourishing choices.", | |
| "Consider this meal a starting point. Small improvements add up to big health benefits.", | |
| "Moderation is key. Try balancing this meal with healthier choices later today." | |
| ], | |
| "poor": [ | |
| "Your body deserves premium fuel. Consider more nutrient-dense options next time.", | |
| "One meal doesn't define your health journey. Your next choice can be a healthier one.", | |
| "We all have indulgences. Balance this meal with nutritious choices for your next one.", | |
| "Small steps lead to big changes. Consider adding vegetables to your next meal.", | |
| "Remember: food is fuel. Choose options that will energize rather than drain you." | |
| ] | |
| } | |
| return quotes | |
| # Initialize motivational quotes | |
| motivational_quotes = load_motivational_quotes() | |
| # Helper function to preprocess the image for better OCR results | |
| def preprocess_image(image): | |
| # Convert to numpy array if needed | |
| if not isinstance(image, np.ndarray): | |
| image = np.array(image) | |
| try: | |
| # Ensure the image is in RGB format (3 channels) | |
| if len(image.shape) == 2: # Grayscale | |
| image = cv2.cvtColor(image, cv2.COLOR_GRAY2RGB) | |
| elif len(image.shape) == 3 and image.shape[2] == 4: # RGBA | |
| image = cv2.cvtColor(image, cv2.COLOR_RGBA2RGB) | |
| # Convert to grayscale | |
| gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY) | |
| # Apply multiple preprocessing techniques and keep the best result | |
| results = [] | |
| # Technique 1: Adaptive thresholding | |
| thresh = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, | |
| cv2.THRESH_BINARY, 11, 2) | |
| results.append(thresh) | |
| # Technique 2: Otsu's thresholding after Gaussian filtering | |
| blur = cv2.GaussianBlur(gray, (5, 5), 0) | |
| _, otsu = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) | |
| results.append(otsu) | |
| # Technique 3: Histogram equalization | |
| equalized = cv2.equalizeHist(gray) | |
| results.append(equalized) | |
| # Technique 4: Original grayscale | |
| results.append(gray) | |
| # Convert all results to PIL images | |
| pil_images = [Image.fromarray(img) for img in results] | |
| return pil_images | |
| except Exception as e: | |
| print(f"Error preprocessing image: {e}") | |
| # If preprocessing fails, return the original image as a list | |
| return [Image.fromarray(image) if isinstance(image, np.ndarray) else image] | |
| # OCR function to extract text from bill image with enhanced image processing | |
| def extract_text_from_image(image): | |
| try: | |
| # If image is a URL, download it | |
| if isinstance(image, str) and (image.startswith('http://') or image.startswith('https://')): | |
| response = requests.get(image) | |
| img = Image.open(BytesIO(response.content)) | |
| else: | |
| img = Image.fromarray(image) if isinstance(image, np.ndarray) else image | |
| # Create a copy for display | |
| display_img = img.copy() if hasattr(img, 'copy') else img | |
| # Preprocess the image to get multiple versions | |
| preprocessed_images = preprocess_image(img) | |
| # Try OCR on each preprocessed image | |
| best_text = "" | |
| # Custom configs to try | |
| configs = [ | |
| r'--oem 3 --psm 6 -l eng', # Assume a single uniform block of text | |
| r'--oem 3 --psm 4 -l eng', # Assume a single column of text | |
| r'--oem 3 --psm 3 -l eng', # Fully automatic page segmentation | |
| r'--oem 3 --psm 11 -l eng', # Sparse text - no specific structure | |
| r'--oem 3 --psm 12 -l eng', # Sparse text with OSD | |
| ] | |
| for img_version in preprocessed_images: | |
| for config in configs: | |
| try: | |
| text = pytesseract.image_to_string(img_version, config=config) | |
| # Keep the longest text as it likely contains more information | |
| if len(text.strip()) > len(best_text.strip()): | |
| best_text = text | |
| except Exception as e: | |
| print(f"OCR error with specific config: {str(e)}") | |
| continue | |
| # If all attempts failed or returned very little text | |
| if len(best_text.strip()) < 10: | |
| # Try one last attempt with default settings | |
| try: | |
| best_text = pytesseract.image_to_string(img) | |
| except Exception as e: | |
| print(f"Final OCR attempt error: {str(e)}") | |
| # Debug output | |
| print(f"OCR extracted text of length: {len(best_text)}") | |
| return best_text | |
| except Exception as e: | |
| print(f"Error extracting text: {str(e)}") | |
| return f"Error extracting text: {str(e)}" | |
| # Extract food items from the OCR text with improved pattern recognition | |
| def extract_food_items(text): | |
| # Improved algorithm to detect food items in bill text | |
| lines = text.split('\n') | |
| food_items = [] | |
| # Debug info | |
| print(f"Processing {len(lines)} lines of text") | |
| # Clean and normalize all lines first | |
| cleaned_lines = [] | |
| for line in lines: | |
| # Remove common non-food text | |
| line = re.sub(r'thank you|receipt|invoice|order|table|server', '', line.lower(), flags=re.IGNORECASE) | |
| cleaned_lines.append(line.strip()) | |
| # Regular patterns for food items in bills | |
| # More comprehensive price pattern to catch various formats | |
| price_pattern = r'(\$?\d+\.\d{2}|\$?\d+\,\d{2}|\$?\d+)' | |
| for line in cleaned_lines: | |
| if not line: | |
| continue | |
| # Skip lines that look like totals or headers | |
| skip_keywords = [ | |
| 'total', 'subtotal', 'tax', 'gratuity', 'tip', 'service', 'amount', 'due', 'change', | |
| 'cash', 'credit', 'card', 'payment', 'date', 'time', 'check', 'table', | |
| 'guest', 'invoice', 'receipt', 'bill', 'order', 'tel', 'phone', 'address', | |
| 'thank you', 'restaurant', 'cafe', 'bar', 'grill', 'kitchen', 'www', 'http' | |
| ] | |
| if any(keyword in line.lower() for keyword in skip_keywords): | |
| continue | |
| # Debug line | |
| print(f"Processing line: '{line}'") | |
| # If line contains a price, extract the item name (everything before the price) | |
| if re.search(price_pattern, line): | |
| # Split based on number patterns (likely price) | |
| item_parts = re.split(price_pattern, line) | |
| if item_parts and len(item_parts) > 1: | |
| item_match = item_parts[0].strip() | |
| if item_match and len(item_match) > 1: # Ensure it's not just whitespace | |
| # Clean up the item name (remove quantities, etc.) | |
| cleaned_item = re.sub(r'^\d+\s*[xX]?\s*', '', item_match) # Remove quantities like "2 x" or "2" | |
| cleaned_item = re.sub(r'\d+\s*oz\s*', '', cleaned_item) # Remove sizes like "12oz" | |
| cleaned_item = re.sub(r'\(\w+\)', '', cleaned_item) # Remove parentheses | |
| # Filter out very short items that are likely not food | |
| if len(cleaned_item.strip()) > 2: | |
| food_items.append(cleaned_item.strip().lower()) | |
| print(f"Found item with price: '{cleaned_item.strip().lower()}'") | |
| # If not enough items found, try alternate methods | |
| if len(food_items) < 2: | |
| # Look for menu-like patterns | |
| for line in cleaned_lines: | |
| # Try to find numbered items (e.g., "1. Burger" or "#1 Burger") | |
| numbered_pattern = r'(?:^|\s)(?:\d+\.|\#\d+)\s+(.+?)(?:\s+\$|\s+\d|\s*$)' | |
| match = re.search(numbered_pattern, line) | |
| if match: | |
| item = match.group(1).strip().lower() | |
| if len(item) > 2 and item not in food_items: | |
| food_items.append(item) | |
| print(f"Found numbered item: '{item}'") | |
| # Simple heuristic: look for capitalized words that might be menu items | |
| # This is a fallback when we're struggling to find items | |
| if len(line) > 3 and not any(char.isdigit() for char in line) and not any(skip in line for skip in skip_keywords): | |
| potential_item = re.sub(r'\W+', ' ', line).strip().lower() | |
| # Check if the line contains any known food items | |
| for food in nutrition_data.keys(): | |
| if food in potential_item: | |
| if potential_item not in food_items: | |
| food_items.append(potential_item) | |
| print(f"Found potential food item: '{potential_item}'") | |
| break | |
| # If we still have no items, use a more aggressive approach to find any words | |
| # that match our food database | |
| if len(food_items) < 2: | |
| print("Using aggressive food item detection...") | |
| # Flatten all text and clean it | |
| all_text = ' '.join(cleaned_lines).lower() | |
| # Filter out non-alphanumeric characters | |
| all_text = re.sub(r'[^\w\s]', ' ', all_text) | |
| # Get all words | |
| words = all_text.split() | |
| # Look for any word or pair of words that matches our food database | |
| for i in range(len(words)): | |
| # Single word match | |
| if words[i] in nutrition_data: | |
| food_items.append(words[i]) | |
| print(f"Found direct food match: '{words[i]}'") | |
| # Two-word match | |
| if i < len(words) - 1: | |
| two_words = words[i] + ' ' + words[i+1] | |
| if two_words in nutrition_data: | |
| food_items.append(two_words) | |
| print(f"Found direct two-word food match: '{two_words}'") | |
| # If we've exhausted all options but still have no items, try to find words | |
| # that are similar to our food database | |
| if len(food_items) < 2: | |
| print("Using similarity-based food item detection...") | |
| all_text = ' '.join(cleaned_lines).lower() | |
| words = re.findall(r'\b[a-z]{3,}\b', all_text) # Find all words with at least 3 letters | |
| for word in words: | |
| # Skip very common words | |
| if word in ['the', 'and', 'for', 'with', 'that', 'have', 'this', 'from']: | |
| continue | |
| # Check if the word is a substring of any food in our database | |
| for food in nutrition_data.keys(): | |
| if word in food: | |
| food_items.append(food) | |
| print(f"Found similar food item: '{food}' from '{word}'") | |
| break | |
| # Remove duplicates and limit to reasonable number | |
| food_items = list(set(food_items))[:10] | |
| print(f"Final food items extracted: {food_items}") | |
| return food_items | |
| # Match extracted food items to our nutrition database with improved fuzzy matching | |
| def match_food_to_nutrition(food_items): | |
| matched_items = [] | |
| for item in food_items: | |
| # Direct match | |
| if item in nutrition_data: | |
| matched_items.append({"name": item, "nutrition": nutrition_data[item]}) | |
| continue | |
| # Improved matching logic - word-based matching and ngram similarity | |
| best_match = None | |
| max_score = 0 | |
| # Split the item into words for better matching | |
| item_words = set(item.split()) | |
| for db_food in nutrition_data: | |
| # Calculate word overlap | |
| db_food_words = set(db_food.split()) | |
| if item_words and db_food_words: | |
| overlap = len(item_words.intersection(db_food_words)) | |
| score = overlap / max(len(item_words), len(db_food_words)) | |
| # Boost score if one string contains the other | |
| if db_food in item or item in db_food: | |
| score += 0.3 | |
| if score > max_score: | |
| max_score = score | |
| best_match = db_food | |
| # Only match if the score is reasonably high | |
| if best_match and max_score > 0.3: | |
| matched_items.append({"name": item, "matched_as": best_match, "nutrition": nutrition_data[best_match]}) | |
| # Remove duplicates (based on matched_as) | |
| unique_matches = [] | |
| seen_matches = set() | |
| for item in matched_items: | |
| match_key = item.get("matched_as", item["name"]) | |
| if match_key not in seen_matches: | |
| unique_matches.append(item) | |
| seen_matches.add(match_key) | |
| return unique_matches | |
| # Calculate nutritional totals and health | |
| # Calculate nutritional totals and health score | |
| def calculate_meal_health(matched_items): | |
| if not matched_items: | |
| return None, None, "No food items detected" | |
| # Calculate total nutrition | |
| total_calories = sum(item["nutrition"]["calories"] for item in matched_items) | |
| total_fat = sum(item["nutrition"]["fat"] for item in matched_items) | |
| total_carbs = sum(item["nutrition"]["carbs"] for item in matched_items) | |
| total_protein = sum(item["nutrition"]["protein"] for item in matched_items) | |
| # Count items by category | |
| category_counts = {"healthy": 0, "neutral": 0, "protein": 0, "junk": 0} | |
| for item in matched_items: | |
| category = item["nutrition"]["category"] | |
| category_counts[category] = category_counts.get(category, 0) + 1 | |
| # Calculate health score (0-100) | |
| total_items = len(matched_items) | |
| health_score = 0 | |
| # Point system: | |
| # - Healthy items: +25 points each | |
| # - Protein items: +15 points each | |
| # - Neutral items: +5 points each | |
| # - Junk items: -10 points each | |
| # Base score of 50 | |
| health_score = 50 | |
| health_score += category_counts["healthy"] * 25 | |
| health_score += category_counts["protein"] * 15 | |
| health_score += category_counts["neutral"] * 5 | |
| health_score -= category_counts["junk"] * 10 | |
| # Adjust based on macros | |
| if total_calories > 0: | |
| # Protein is good | |
| protein_ratio = (total_protein * 4) / total_calories | |
| if protein_ratio > 0.25: # >25% protein is good | |
| health_score += 10 | |
| # Too much fat is not ideal | |
| fat_ratio = (total_fat * 9) / total_calories | |
| if fat_ratio > 0.4: # >40% calories from fat | |
| health_score -= 10 | |
| # Clamp score between 0-100 | |
| health_score = max(0, min(100, health_score)) | |
| # Determine feedback category | |
| if health_score >= 80: | |
| category = "excellent" | |
| elif health_score >= 60: | |
| category = "good" | |
| elif health_score >= 40: | |
| category = "moderate" | |
| else: | |
| category = "poor" | |
| # Get a random motivational quote for the category | |
| quote = random.choice(motivational_quotes[category]) | |
| # Create nutrition data | |
| nutrition_data = { | |
| "calories": total_calories, | |
| "fat": total_fat, | |
| "carbs": total_carbs, | |
| "protein": total_protein, | |
| "health_score": health_score, | |
| "category": category, | |
| "message": quote | |
| } | |
| # Create summary | |
| dominant_macro = "" | |
| if total_calories > 0: | |
| fat_percentage = (total_fat * 9) / total_calories * 100 | |
| carbs_percentage = (total_carbs * 4) / total_calories * 100 | |
| protein_percentage = (total_protein * 4) / total_calories * 100 | |
| if max(fat_percentage, carbs_percentage, protein_percentage) == fat_percentage: | |
| dominant_macro = "fat" | |
| elif max(fat_percentage, carbs_percentage, protein_percentage) == carbs_percentage: | |
| dominant_macro = "carbs" | |
| else: | |
| dominant_macro = "protein" | |
| summary = f"You consumed approximately {total_calories} calories — mostly {dominant_macro}." | |
| if health_score >= 70: | |
| summary += " Great choices today!" | |
| elif health_score >= 50: | |
| summary += " Consider more balanced options next time." | |
| else: | |
| summary += " Try to make healthier choices next time." | |
| return nutrition_data, summary, "" | |
| # Generate detailed analysis with visualization | |
| def generate_analysis(matched_items, nutrition_data): | |
| if not matched_items or not nutrition_data: | |
| return None | |
| # Create DataFrame for the items | |
| items_data = [] | |
| for item in matched_items: | |
| name = item["name"] | |
| if "matched_as" in item: | |
| name = f"{name} (matched as {item['matched_as']})" | |
| items_data.append({ | |
| "Item": name, | |
| "Calories": item["nutrition"]["calories"], | |
| "Fat (g)": item["nutrition"]["fat"], | |
| "Carbs (g)": item["nutrition"]["carbs"], | |
| "Protein (g)": item["nutrition"]["protein"], | |
| "Category": item["nutrition"]["category"].capitalize() | |
| }) | |
| df = pd.DataFrame(items_data) | |
| # Get the current date and time | |
| now = datetime.datetime.now() | |
| date_str = now.strftime("%Y-%m-%d") | |
| time_str = now.strftime("%H:%M:%S") | |
| # Create visualization plots | |
| fig, axs = plt.subplots(2, 2, figsize=(12, 10)) | |
| # Plot 1: Calories by item (horizontal bar) | |
| df_sorted = df.sort_values('Calories', ascending=True) | |
| sns.barplot(x='Calories', y='Item', data=df_sorted, ax=axs[0, 0], palette='viridis') | |
| axs[0, 0].set_title('Calories by Item') | |
| axs[0, 0].set_xlabel('Calories') | |
| axs[0, 0].set_ylabel('Food Item') | |
| # Plot 2: Macronutrient breakdown (pie chart) | |
| total_calories = nutrition_data["calories"] | |
| if total_calories > 0: | |
| fat_cals = nutrition_data["fat"] * 9 | |
| carb_cals = nutrition_data["carbs"] * 4 | |
| protein_cals = nutrition_data["protein"] * 4 | |
| macro_data = [fat_cals, carb_cals, protein_cals] | |
| macro_labels = [f'Fat ({fat_cals:.0f} cal)', f'Carbs ({carb_cals:.0f} cal)', f'Protein ({protein_cals:.0f} cal)'] | |
| colors = ['#FF9999', '#66B2FF', '#99FF99'] | |
| axs[0, 1].pie(macro_data, labels=macro_labels, colors=colors, autopct='%1.1f%%', startangle=90) | |
| axs[0, 1].set_title('Calorie Sources') | |
| else: | |
| axs[0, 1].text(0.5, 0.5, 'No calorie data available', ha='center', va='center') | |
| axs[0, 1].axis('off') | |
| # Plot 3: Health score gauge | |
| health_score = nutrition_data["health_score"] | |
| # Create a gauge chart using a pie chart | |
| size = 0.3 | |
| vals = [health_score, 100-health_score] | |
| # Create color based on score | |
| if health_score >= 80: | |
| color = '#00CC66' # Green | |
| elif health_score >= 60: | |
| color = '#CCCC00' # Yellow | |
| elif health_score >= 40: | |
| color = '#FF9900' # Orange | |
| else: | |
| color = '#FF3333' # Red | |
| cmap = [color, '#f0f0f0'] | |
| axs[1, 0].pie(vals, radius=1, colors=cmap, startangle=90, counterclock=False) | |
| axs[1, 0].pie([1], radius=1-size, colors=['white']) | |
| axs[1, 0].text(0, 0, f"{health_score:.0f}", fontsize=32, ha='center', va='center') | |
| axs[1, 0].text(0, -0.2, "Health Score", fontsize=12, ha='center', va='center') | |
| axs[1, 0].set_title('Meal Health Score') | |
| # Plot 4: Food category breakdown | |
| category_counts = df['Category'].value_counts() | |
| sns.barplot(x=category_counts.index, y=category_counts.values, ax=axs[1, 1], palette='viridis') | |
| axs[1, 1].set_title('Food Categories') | |
| axs[1, 1].set_xlabel('Category') | |
| axs[1, 1].set_ylabel('Count') | |
| plt.tight_layout() | |
| # Save the plots to a file | |
| analysis_img_path = "analysis_temp.png" | |
| plt.savefig(analysis_img_path, dpi=150, bbox_inches='tight') | |
| plt.close() | |
| # Create a table of the analyzed items | |
| items_table = df.to_html(index=False, classes='table table-striped') | |
| # Create analysis summary | |
| total_fat = nutrition_data["fat"] | |
| total_carbs = nutrition_data["carbs"] | |
| total_protein = nutrition_data["protein"] | |
| analysis_summary = f""" | |
| <h2>Meal Nutrition Analysis</h2> | |
| <p><strong>Date:</strong> {date_str} <strong>Time:</strong> {time_str}</p> | |
| <h3>Summary</h3> | |
| <p> | |
| Total Calories: <strong>{total_calories:.0f}</strong><br> | |
| Total Fat: <strong>{total_fat:.1f}g</strong> ({(total_fat * 9 / total_calories * 100):.1f}% of calories)<br> | |
| Total Carbs: <strong>{total_carbs:.1f}g</strong> ({(total_carbs * 4 / total_calories * 100):.1f}% of calories)<br> | |
| Total Protein: <strong>{total_protein:.1f}g</strong> ({(total_protein * 4 / total_calories * 100):.1f}% of calories)<br> | |
| Health Score: <strong>{health_score:.0f}/100</strong> ({nutrition_data["category"].capitalize()}) | |
| </p> | |
| <h3>Feedback</h3> | |
| <p>{nutrition_data["message"]}</p> | |
| <h3>Analyzed Items</h3> | |
| {items_table} | |
| <h3>Recommendations</h3> | |
| """ | |
| # Add custom recommendations based on the nutritional analysis | |
| if total_protein < 20: | |
| analysis_summary += "<p>✅ <strong>Add more protein</strong> to your meals. Good sources include lean meats, fish, eggs, tofu, or legumes.</p>" | |
| if (total_fat * 9 / total_calories) > 0.4: | |
| analysis_summary += "<p>✅ <strong>Consider reducing fat intake</strong>, especially from fried foods and processed items.</p>" | |
| category_counts_dict = df['Category'].value_counts().to_dict() | |
| junk_count = category_counts_dict.get('Junk', 0) | |
| healthy_count = category_counts_dict.get('Healthy', 0) | |
| if junk_count > healthy_count: | |
| analysis_summary += "<p>✅ <strong>Try to include more fruits and vegetables</strong> in your meals for better nutrition.</p>" | |
| if health_score < 50: | |
| analysis_summary += "<p>✅ <strong>Balance your plate</strong> with 1/2 vegetables, 1/4 protein, and 1/4 whole grains for improved nutrition.</p>" | |
| return analysis_img_path, analysis_summary | |
| # Function to process the bill image with enhanced error handling | |
| def process_bill_image(image): | |
| try: | |
| display_img = None | |
| ocr_text = "" | |
| food_items = [] | |
| matched_items = [] | |
| nutrition_data = None | |
| summary = "" | |
| error_message = "" | |
| # Process the image if it's valid | |
| if image is not None: | |
| # Extract text using OCR | |
| ocr_text = extract_text_from_image(image) | |
| if ocr_text: | |
| # Extract food items from the OCR text | |
| food_items = extract_food_items(ocr_text) | |
| if food_items: | |
| # Match food items to nutrition database | |
| matched_items = match_food_to_nutrition(food_items) | |
| if matched_items: | |
| # Calculate health score and nutrition data | |
| nutrition_data, summary, error_message = calculate_meal_health(matched_items) | |
| else: | |
| error_message = "No matching food items found in our database. Please try another image." | |
| else: | |
| error_message = "No food items detected. Please try another image or check the image clarity." | |
| else: | |
| error_message = "No text could be extracted from the image. Please try a clearer image." | |
| else: | |
| error_message = "Please upload an image to analyze." | |
| # Generate the food items section | |
| food_items_html = "<p>No food items detected</p>" | |
| if matched_items: | |
| food_items_html = "<ul>" | |
| for item in matched_items: | |
| item_name = item["name"] | |
| if "matched_as" in item: | |
| item_name = f"{item_name} (recognized as {item['matched_as']})" | |
| cals = item["nutrition"]["calories"] | |
| cat = item["nutrition"]["category"].capitalize() | |
| # Choose color based on category | |
| color = "#000000" | |
| if item["nutrition"]["category"] == "healthy": | |
| color = "#007700" # Green | |
| elif item["nutrition"]["category"] == "junk": | |
| color = "#CC0000" # Red | |
| elif item["nutrition"]["category"] == "protein": | |
| color = "#0000CC" # Blue | |
| food_items_html += f'<li style="color:{color};"><strong>{item_name}</strong>: {cals} calories ({cat})</li>' | |
| food_items_html += "</ul>" | |
| # Generate detailed analysis if we have data | |
| analysis_img = None | |
| analysis_html = "<p>No analysis available</p>" | |
| if matched_items and nutrition_data: | |
| try: | |
| analysis_img, analysis_html = generate_analysis(matched_items, nutrition_data) | |
| except Exception as e: | |
| print(f"Error generating analysis: {str(e)}") | |
| analysis_html = f"<p>Error generating analysis: {str(e)}</p>" | |
| # Return the results | |
| return ( | |
| ocr_text, | |
| food_items_html, | |
| summary if summary else error_message, | |
| analysis_img if analysis_img else None, | |
| analysis_html | |
| ) | |
| except Exception as e: | |
| error_msg = f"An error occurred: {str(e)}" | |
| print(error_msg) | |
| return ( | |
| "", | |
| "<p>No food items detected</p>", | |
| error_msg, | |
| None, | |
| "<p>Analysis not available due to an error</p>" | |
| ) | |
| # Function to process direct text input (instead of an image) | |
| def process_text_input(text_input): | |
| try: | |
| if not text_input: | |
| return "<p>No text provided</p>", "Please enter some text to analyze", None, "<p>Analysis not available</p>" | |
| # Extract food items from the text | |
| food_items = extract_food_items(text_input) | |
| if not food_items: | |
| return "<p>No food items detected in your text</p>", "No food items found. Try being more specific about what you ate.", None, "<p>Analysis not available</p>" | |
| # Match food items to nutrition database | |
| matched_items = match_food_to_nutrition(food_items) | |
| if not matched_items: | |
| return "<p>No matching food items found in our database</p>", "Your food items couldn't be matched to our database. Try different foods or descriptions.", None, "<p>Analysis not available</p>" | |
| # Calculate health score and nutrition data | |
| nutrition_data, summary, error_message = calculate_meal_health(matched_items) | |
| if error_message: | |
| return "<p>No food items detected</p>", error_message, None, "<p>Analysis not available</p>" | |
| # Generate the food items section | |
| food_items_html = "<ul>" | |
| for item in matched_items: | |
| item_name = item["name"] | |
| if "matched_as" in item: | |
| item_name = f"{item_name} (recognized as {item['matched_as']})" | |
| cals = item["nutrition"]["calories"] | |
| cat = item["nutrition"]["category"].capitalize() | |
| # Choose color based on category | |
| color = "#000000" | |
| if item["nutrition"]["category"] == "healthy": | |
| color = "#007700" # Green | |
| elif item["nutrition"]["category"] == "junk": | |
| color = "#CC0000" # Red | |
| elif item["nutrition"]["category"] == "protein": | |
| color = "#0000CC" # Blue | |
| food_items_html += f'<li style="color:{color};"><strong>{item_name}</strong>: {cals} calories ({cat})</li>' | |
| food_items_html += "</ul>" | |
| # Generate detailed analysis | |
| analysis_img, analysis_html = generate_analysis(matched_items, nutrition_data) | |
| return food_items_html, summary, analysis_img, analysis_html | |
| except Exception as e: | |
| error_msg = f"An error occurred: {str(e)}" | |
| print(error_msg) | |
| return "<p>No food items detected</p>", error_msg, None, "<p>Analysis not available due to an error</p>" | |
| # Create the Gradio interface | |
| def create_gradio_interface(): | |
| # Define CSS for the interface | |
| custom_css = """ | |
| body { | |
| font-family: 'Arial', sans-serif; | |
| } | |
| h1 { | |
| color: #4a4a4a; | |
| text-align: center; | |
| } | |
| .footer { | |
| text-align: center; | |
| margin-top: 20px; | |
| font-size: 0.8em; | |
| color: #666; | |
| } | |
| .container { | |
| margin: 0 auto; | |
| max-width: 1200px; | |
| } | |
| .tab-content { | |
| padding: 15px; | |
| border: 1px solid #ddd; | |
| border-top: none; | |
| border-radius: 0 0 5px 5px; | |
| } | |
| .nutrition-summary { | |
| background-color: #f9f9f9; | |
| padding: 15px; | |
| border-radius: 5px; | |
| margin-top: 15px; | |
| } | |
| .footer-note { | |
| font-size: 0.9em; | |
| font-style: italic; | |
| margin-top: 30px; | |
| text-align: center; | |
| color: #777; | |
| } | |
| table.table-striped { | |
| width: 100%; | |
| border-collapse: collapse; | |
| } | |
| table.table-striped th, table.table-striped td { | |
| border: 1px solid #ddd; | |
| padding: 8px; | |
| text-align: left; | |
| } | |
| table.table-striped tr:nth-child(even) { | |
| background-color: #f2f2f2; | |
| } | |
| table.table-striped th { | |
| padding-top: 12px; | |
| padding-bottom: 12px; | |
| background-color: #4CAF50; | |
| color: white; | |
| } | |
| """ | |
| # Define theme | |
| theme = gr.themes.Soft( | |
| primary_hue="green", | |
| secondary_hue="blue", | |
| ).set( | |
| body_text_color="#333333", | |
| block_title_text_weight="600", | |
| block_border_width="1px", | |
| block_shadow="0px 5px 10px rgba(0, 0, 0, 0.1)", | |
| button_primary_background_fill="#4CAF50", | |
| button_primary_background_fill_hover="#45a049", | |
| ) | |
| # Create Gradio blocks | |
| with gr.Blocks(css=custom_css, theme=theme) as demo: | |
| # Header | |
| gr.HTML(""" | |
| <div style="text-align: center; max-width: 850px; margin: 0 auto;"> | |
| <h1>🧾 Restaurant Bill Nutritional Analyzer 🍔</h1> | |
| <p>Upload a photo of your restaurant bill or receipt, and this tool will analyze what you ate, estimate the nutritional content, and provide a health score.</p> | |
| <p><em>Note: This tool works best with clear images of English-language bills and menus.</em></p> | |
| </div> | |
| """) | |
| # Main content | |
| with gr.Tabs(): | |
| # Image upload tab | |
| with gr.TabItem("Upload Receipt Image"): | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| # Input components | |
| image_input = gr.Image(label="Upload a photo of your restaurant bill") | |
| analyze_btn = gr.Button("Analyze Receipt", variant="primary") | |
| with gr.Column(scale=1): | |
| # Output components | |
| ocr_output = gr.Textbox(label="Extracted Text (OCR)", lines=5) | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| food_items_output = gr.HTML(label="Detected Food Items") | |
| with gr.Column(scale=1): | |
| nutrition_summary = gr.Textbox(label="Nutrition Summary", lines=4) | |
| with gr.Row(): | |
| gr.HTML("<h3>Detailed Nutritional Analysis</h3>") | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| analysis_chart = gr.Image(label="Analysis Chart") | |
| with gr.Column(scale=1): | |
| analysis_details = gr.HTML(label="Analysis Details") | |
| # Set up the button click event | |
| analyze_btn.click( | |
| fn=process_bill_image, | |
| inputs=[image_input], | |
| outputs=[ocr_output, food_items_output, nutrition_summary, analysis_chart, analysis_details] | |
| ) | |
| # Manual text input tab | |
| with gr.TabItem("Enter Food Items Manually"): | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| text_input = gr.Textbox( | |
| label="Enter what you ate (e.g., 'burger, fries, and a soda')", | |
| lines=3, | |
| placeholder="Example: I had a cheeseburger with fries and a coke for lunch." | |
| ) | |
| analyze_text_btn = gr.Button("Analyze Food Items", variant="primary") | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| text_food_items = gr.HTML(label="Detected Food Items") | |
| with gr.Column(scale=1): | |
| text_summary = gr.Textbox(label="Nutrition Summary", lines=4) | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| text_analysis_chart = gr.Image(label="Analysis Chart") | |
| with gr.Column(scale=1): | |
| text_analysis_details = gr.HTML(label="Analysis Details") | |
| # Examples for text input | |
| gr.Examples( | |
| examples=[ | |
| "I had a burger, fries and a coke", | |
| "For dinner I ordered pizza and ice cream", | |
| "Grilled chicken salad with water", | |
| "Steak, baked potato and broccoli with red wine" | |
| ], | |
| inputs=text_input | |
| ) | |
| # Set up the button click event | |
| analyze_text_btn.click( | |
| fn=process_text_input, | |
| inputs=[text_input], | |
| outputs=[text_food_items, text_summary, text_analysis_chart, text_analysis_details] | |
| ) | |
| # About tab | |
| with gr.TabItem("About"): | |
| gr.HTML(""" | |
| <div style="text-align: left; max-width: 850px; margin: 0 auto;"> | |
| <h2>About This Tool</h2> | |
| <p>This nutritional analyzer uses OCR (Optical Character Recognition) to extract text from restaurant bills and receipts. | |
| It then uses natural language processing techniques to identify food items and match them to a nutrition database.</p> | |
| <h3>How It Works</h3> | |
| <ol> | |
| <li><strong>Image Processing:</strong> Your uploaded image is enhanced for better text recognition</li> | |
| <li><strong>Text Extraction:</strong> OCR technology reads the text from the image</li> | |
| <li><strong>Food Detection:</strong> NLP algorithms identify food items in the text</li> | |
| <li><strong>Nutrition Matching:</strong> Food items are matched to a nutrition database</li> | |
| <li><strong>Analysis:</strong> Nutritional totals are calculated and a health score is assigned</li> | |
| </ol> | |
| <h3>Limitations</h3> | |
| <p>Please note the following limitations:</p> | |
| <ul> | |
| <li>Works best with clear, well-lit images</li> | |
| <li>Designed primarily for English-language bills and receipts</li> | |
| <li>May not recognize all specialized or regional dishes</li> | |
| <li>Nutritional estimates are approximate and based on standard portions</li> | |
| <li>Health scores are relative indicators and not medical advice</li> | |
| </ul> | |
| <h3>Privacy Notice</h3> | |
| <p>Images uploaded to this tool are processed for the sole purpose of extracting food information. | |
| Images and extracted data are not permanently stored.</p> | |
| <div class="footer-note"> | |
| <p>This tool is intended for informational purposes only and is not a substitute for professional nutritional or medical advice.</p> | |
| </div> | |
| </div> | |
| """) | |
| # Footer | |
| gr.HTML(""" | |
| <div class="footer"> | |
| <p>🍽️ Restaurant Bill Nutritional Analyzer | Built with Gradio and Hugging Face | 2023</p> | |
| </div> | |
| """) | |
| return demo | |
| # Create and launch the app | |
| demo = create_gradio_interface() | |
| # Run the app | |
| if __name__ == "__main__": | |
| demo.launch() |