Amandeep01 commited on
Commit
b38cd3d
·
verified ·
1 Parent(s): fdb17bb

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +1121 -0
app.py ADDED
@@ -0,0 +1,1121 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import numpy as np
3
+ import pytesseract
4
+ from PIL import Image, ImageEnhance, ImageFilter
5
+ import pandas as pd
6
+ import re
7
+ import json
8
+ import os
9
+ import torch
10
+ from transformers import pipeline, AutoTokenizer, AutoModelForSequenceClassification
11
+ import requests
12
+ from io import BytesIO
13
+ import csv
14
+ import matplotlib.pyplot as plt
15
+ import seaborn as sns
16
+ from datetime import datetime
17
+ import base64
18
+ from pathlib import Path
19
+ import cv2
20
+ import html
21
+
22
+ # Initialize models and data storage
23
+ class NutritionAnalyzer:
24
+ def __init__(self):
25
+ self.nutrition_data = self.load_nutrition_data()
26
+ self.history = []
27
+ self.initialize_models()
28
+
29
+ def initialize_models(self):
30
+ try:
31
+ # Initialize food classifier model
32
+ self.food_classifier = pipeline("zero-shot-classification",
33
+ model="facebook/bart-large-mnli",
34
+ device=0 if torch.cuda.is_available() else -1)
35
+
36
+ # Add sentiment analysis for feedback
37
+ self.sentiment_analyzer = pipeline("sentiment-analysis",
38
+ model="distilbert-base-uncased-finetuned-sst-2-english",
39
+ device=0 if torch.cuda.is_available() else -1)
40
+ print("NLP models loaded successfully")
41
+ except Exception as e:
42
+ print(f"Error loading NLP models: {e}")
43
+ self.food_classifier = None
44
+ self.sentiment_analyzer = None
45
+
46
+ def load_nutrition_data(self):
47
+ """Load nutrition data from CSV if available, otherwise use default database"""
48
+ try:
49
+ if os.path.exists('nutrition_database.csv'):
50
+ df = pd.read_csv('nutrition_database.csv')
51
+ nutrition_data = {}
52
+ for _, row in df.iterrows():
53
+ food_name = row['food_name'].lower()
54
+ nutrition_data[food_name] = {
55
+ "calories": row['calories'],
56
+ "fat": row['fat'],
57
+ "carbs": row['carbs'],
58
+ "protein": row['protein'],
59
+ "sugar": row['sugar'] if 'sugar' in row else 0,
60
+ "fiber": row['fiber'] if 'fiber' in row else 0,
61
+ "category": row['category']
62
+ }
63
+ print(f"Loaded {len(nutrition_data)} items from nutrition database")
64
+ return nutrition_data
65
+ else:
66
+ return self.create_default_nutrition_database()
67
+ except Exception as e:
68
+ print(f"Error loading nutrition data: {e}")
69
+ return self.create_default_nutrition_database()
70
+
71
+ def create_default_nutrition_database(self):
72
+ """Create a default nutrition database with extended food items"""
73
+ print("Creating default nutrition database")
74
+ food_data = {
75
+ # Fast food
76
+ "pizza": {"calories": 285, "fat": 10, "carbs": 36, "protein": 12, "sugar": 3, "fiber": 2, "category": "junk"},
77
+ "burger": {"calories": 354, "fat": 17, "carbs": 40, "protein": 15, "sugar": 8, "fiber": 1, "category": "junk"},
78
+ "fries": {"calories": 312, "fat": 15, "carbs": 41, "protein": 3, "sugar": 0, "fiber": 3, "category": "junk"},
79
+ "chicken sandwich": {"calories": 450, "fat": 19, "carbs": 45, "protein": 28, "sugar": 5, "fiber": 2, "category": "junk"},
80
+ "hot dog": {"calories": 290, "fat": 16, "carbs": 18, "protein": 10, "sugar": 2, "fiber": 1, "category": "junk"},
81
+ "nachos": {"calories": 600, "fat": 39, "carbs": 56, "protein": 15, "sugar": 2, "fiber": 7, "category": "junk"},
82
+ "onion rings": {"calories": 320, "fat": 16, "carbs": 40, "protein": 5, "sugar": 6, "fiber": 3, "category": "junk"},
83
+
84
+ # Beverages
85
+ "soda": {"calories": 140, "fat": 0, "carbs": 39, "protein": 0, "sugar": 39, "fiber": 0, "category": "junk"},
86
+ "diet soda": {"calories": 0, "fat": 0, "carbs": 0, "protein": 0, "sugar": 0, "fiber": 0, "category": "neutral"},
87
+ "juice": {"calories": 110, "fat": 0, "carbs": 26, "protein": 0, "sugar": 22, "fiber": 0, "category": "neutral"},
88
+ "water": {"calories": 0, "fat": 0, "carbs": 0, "protein": 0, "sugar": 0, "fiber": 0, "category": "healthy"},
89
+ "coffee": {"calories": 5, "fat": 0, "carbs": 0, "protein": 0, "sugar": 0, "fiber": 0, "category": "healthy"},
90
+ "tea": {"calories": 2, "fat": 0, "carbs": 0, "protein": 0, "sugar": 0, "fiber": 0, "category": "healthy"},
91
+ "milkshake": {"calories": 300, "fat": 10, "carbs": 50, "protein": 9, "sugar": 40, "fiber": 0, "category": "junk"},
92
+ "smoothie": {"calories": 170, "fat": 2, "carbs": 35, "protein": 5, "sugar": 25, "fiber": 3, "category": "neutral"},
93
+ "lemonade": {"calories": 120, "fat": 0, "carbs": 32, "protein": 0, "sugar": 28, "fiber": 0, "category": "junk"},
94
+ "beer": {"calories": 154, "fat": 0, "carbs": 13, "protein": 1, "sugar": 0, "fiber": 0, "category": "junk"},
95
+ "wine": {"calories": 125, "fat": 0, "carbs": 4, "protein": 0, "sugar": 1, "fiber": 0, "category": "neutral"},
96
+ "cocktail": {"calories": 210, "fat": 0, "carbs": 22, "protein": 0, "sugar": 20, "fiber": 0, "category": "junk"},
97
+ "espresso": {"calories": 3, "fat": 0, "carbs": 0, "protein": 0, "sugar": 0, "fiber": 0, "category": "healthy"},
98
+ "cappuccino": {"calories": 80, "fat": 4, "carbs": 8, "protein": 4, "sugar": 6, "fiber": 0, "category": "neutral"},
99
+ "latte": {"calories": 120, "fat": 6, "carbs": 10, "protein": 6, "sugar": 8, "fiber": 0, "category": "neutral"},
100
+
101
+ # Main dishes
102
+ "salad": {"calories": 100, "fat": 7, "carbs": 5, "protein": 2, "sugar": 2, "fiber": 2, "category": "healthy"},
103
+ "pasta": {"calories": 200, "fat": 2, "carbs": 42, "protein": 7, "sugar": 2, "fiber": 2, "category": "neutral"},
104
+ "steak": {"calories": 300, "fat": 15, "carbs": 0, "protein": 30, "sugar": 0, "fiber": 0, "category": "protein"},
105
+ "chicken": {"calories": 220, "fat": 8, "carbs": 0, "protein": 40, "sugar": 0, "fiber": 0, "category": "protein"},
106
+ "fish": {"calories": 180, "fat": 5, "carbs": 0, "protein": 30, "sugar": 0, "fiber": 0, "category": "healthy"},
107
+ "rice": {"calories": 130, "fat": 0, "carbs": 28, "protein": 3, "sugar": 0, "fiber": 0, "category": "neutral"},
108
+ "noodles": {"calories": 190, "fat": 2, "carbs": 40, "protein": 7, "sugar": 1, "fiber": 2, "category": "neutral"},
109
+ "sandwich": {"calories": 250, "fat": 8, "carbs": 30, "protein": 15, "sugar": 4, "fiber": 3, "category": "neutral"},
110
+ "soup": {"calories": 120, "fat": 3, "carbs": 12, "protein": 10, "sugar": 2, "fiber": 2, "category": "healthy"},
111
+ "burrito": {"calories": 350, "fat": 12, "carbs": 50, "protein": 15, "sugar": 3, "fiber": 6, "category": "neutral"},
112
+ "taco": {"calories": 210, "fat": 10, "carbs": 22, "protein": 12, "sugar": 2, "fiber": 3, "category": "neutral"},
113
+ "wrap": {"calories": 220, "fat": 5, "carbs": 30, "protein": 13, "sugar": 2, "fiber": 2, "category": "neutral"},
114
+ "sushi": {"calories": 350, "fat": 10, "carbs": 60, "protein": 24, "sugar": 4, "fiber": 2, "category": "healthy"},
115
+ "curry": {"calories": 400, "fat": 20, "carbs": 35, "protein": 20, "sugar": 6, "fiber": 5, "category": "neutral"},
116
+ "stir fry": {"calories": 300, "fat": 10, "carbs": 30, "protein": 20, "sugar": 8, "fiber": 5, "category": "healthy"},
117
+ "lasagna": {"calories": 330, "fat": 15, "carbs": 33, "protein": 18, "sugar": 7, "fiber": 3, "category": "neutral"},
118
+ "risotto": {"calories": 310, "fat": 10, "carbs": 45, "protein": 8, "sugar": 1, "fiber": 2, "category": "neutral"},
119
+ "ramen": {"calories": 400, "fat": 15, "carbs": 60, "protein": 10, "sugar": 2, "fiber": 1, "category": "neutral"},
120
+ "pho": {"calories": 300, "fat": 5, "carbs": 45, "protein": 20, "sugar": 2, "fiber": 2, "category": "healthy"},
121
+
122
+ # Appetizers and sides
123
+ "appetizer": {"calories": 200, "fat": 12, "carbs": 15, "protein": 8, "sugar": 2, "fiber": 1, "category": "neutral"},
124
+ "bread": {"calories": 80, "fat": 1, "carbs": 15, "protein": 3, "sugar": 1, "fiber": 1, "category": "neutral"},
125
+ "garlic bread": {"calories": 150, "fat": 7, "carbs": 18, "protein": 4, "sugar": 1, "fiber": 1, "category": "neutral"},
126
+ "mashed potatoes": {"calories": 160, "fat": 8, "carbs": 21, "protein": 2, "sugar": 1, "fiber": 2, "category": "neutral"},
127
+ "baked potato": {"calories": 160, "fat": 0, "carbs": 37, "protein": 4, "sugar": 2, "fiber": 4, "category": "healthy"},
128
+ "coleslaw": {"calories": 120, "fat": 8, "carbs": 12, "protein": 1, "sugar": 8, "fiber": 2, "category": "neutral"},
129
+ "corn on the cob": {"calories": 90, "fat": 2, "carbs": 21, "protein": 3, "sugar": 6, "fiber": 2, "category": "healthy"},
130
+ "green beans": {"calories": 35, "fat": 0, "carbs": 8, "protein": 2, "sugar": 2, "fiber": 4, "category": "healthy"},
131
+ "brussels sprouts": {"calories": 60, "fat": 2, "carbs": 10, "protein": 4, "sugar": 2, "fiber": 4, "category": "healthy"},
132
+ "mac and cheese": {"calories": 300, "fat": 15, "carbs": 30, "protein": 12, "sugar": 5, "fiber": 1, "category": "junk"},
133
+
134
+ # Breakfast
135
+ "eggs": {"calories": 140, "fat": 10, "carbs": 1, "protein": 12, "sugar": 0, "fiber": 0, "category": "protein"},
136
+ "bacon": {"calories": 150, "fat": 12, "carbs": 0, "protein": 10, "sugar": 0, "fiber": 0, "category": "protein"},
137
+ "pancakes": {"calories": 250, "fat": 8, "carbs": 40, "protein": 6, "sugar": 12, "fiber": 1, "category": "neutral"},
138
+ "waffles": {"calories": 280, "fat": 10, "carbs": 42, "protein": 8, "sugar": 15, "fiber": 1, "category": "neutral"},
139
+ "french toast": {"calories": 260, "fat": 10, "carbs": 30, "protein": 8, "sugar": 10, "fiber": 1, "category": "neutral"},
140
+ "oatmeal": {"calories": 150, "fat": 3, "carbs": 27, "protein": 5, "sugar": 1, "fiber": 4, "category": "healthy"},
141
+ "cereal": {"calories": 200, "fat": 2, "carbs": 42, "protein": 5, "sugar": 12, "fiber": 3, "category": "neutral"},
142
+ "bagel": {"calories": 260, "fat": 2, "carbs": 51, "protein": 10, "sugar": 6, "fiber": 2, "category": "neutral"},
143
+ "croissant": {"calories": 270, "fat": 14, "carbs": 31, "protein": 5, "sugar": 7, "fiber": 1, "category": "junk"},
144
+ "muffin": {"calories": 340, "fat": 15, "carbs": 48, "protein": 5, "sugar": 24, "fiber": 2, "category": "junk"},
145
+
146
+ # Desserts
147
+ "cake": {"calories": 350, "fat": 18, "carbs": 45, "protein": 4, "sugar": 30, "fiber": 1, "category": "junk"},
148
+ "ice cream": {"calories": 207, "fat": 11, "carbs": 24, "protein": 4, "sugar": 20, "fiber": 0, "category": "junk"},
149
+ "chocolate": {"calories": 200, "fat": 12, "carbs": 20, "protein": 2, "sugar": 18, "fiber": 1, "category": "junk"},
150
+ "dessert": {"calories": 280, "fat": 14, "carbs": 35, "protein": 5, "sugar": 25, "fiber": 1, "category": "junk"},
151
+ "cheesecake": {"calories": 320, "fat": 20, "carbs": 28, "protein": 6, "sugar": 21, "fiber": 0, "category": "junk"},
152
+ "pie": {"calories": 300, "fat": 14, "carbs": 40, "protein": 3, "sugar": 20, "fiber": 2, "category": "junk"},
153
+ "brownie": {"calories": 250, "fat": 12, "carbs": 35, "protein": 3, "sugar": 25, "fiber": 1, "category": "junk"},
154
+ "cookie": {"calories": 150, "fat": 7, "carbs": 20, "protein": 2, "sugar": 12, "fiber": 1, "category": "junk"},
155
+ "tiramisu": {"calories": 240, "fat": 15, "carbs": 24, "protein": 3, "sugar": 14, "fiber": 0, "category": "junk"},
156
+ "sorbet": {"calories": 120, "fat": 0, "carbs": 30, "protein": 0, "sugar": 28, "fiber": 0, "category": "junk"},
157
+
158
+ # International
159
+ "pad thai": {"calories": 400, "fat": 17, "carbs": 50, "protein": 15, "sugar": 8, "fiber": 3, "category": "neutral"},
160
+ "fried rice": {"calories": 350, "fat": 12, "carbs": 45, "protein": 12, "sugar": 2, "fiber": 2, "category": "neutral"},
161
+ "biryani": {"calories": 400, "fat": 15, "carbs": 50, "protein": 20, "sugar": 2, "fiber": 3, "category": "neutral"},
162
+ "falafel": {"calories": 330, "fat": 18, "carbs": 32, "protein": 13, "sugar": 0, "fiber": 5, "category": "healthy"},
163
+ "hummus": {"calories": 170, "fat": 10, "carbs": 14, "protein": 8, "sugar": 0, "fiber": 6, "category": "healthy"},
164
+ "gyro": {"calories": 430, "fat": 22, "carbs": 33, "protein": 29, "sugar": 4, "fiber": 2, "category": "neutral"},
165
+ "enchiladas": {"calories": 380, "fat": 20, "carbs": 32, "protein": 18, "sugar": 3, "fiber": 5, "category": "neutral"},
166
+ "tandoori chicken": {"calories": 260, "fat": 8, "carbs": 2, "protein": 43, "sugar": 0, "fiber": 0, "category": "protein"},
167
+ "schnitzel": {"calories": 400, "fat": 22, "carbs": 20, "protein": 30, "sugar": 0, "fiber": 1, "category": "neutral"},
168
+ "shawarma": {"calories": 450, "fat": 30, "carbs": 26, "protein": 24, "sugar": 2, "fiber": 3, "category": "neutral"},
169
+
170
+ # Common additions
171
+ "ketchup": {"calories": 15, "fat": 0, "carbs": 4, "protein": 0, "sugar": 3, "fiber": 0, "category": "junk"},
172
+ "mayonnaise": {"calories": 90, "fat": 10, "carbs": 0, "protein": 0, "sugar": 0, "fiber": 0, "category": "junk"},
173
+ "mustard": {"calories": 5, "fat": 0, "carbs": 0, "protein": 0, "sugar": 0, "fiber": 0, "category": "neutral"},
174
+ "butter": {"calories": 100, "fat": 11, "carbs": 0, "protein": 0, "sugar": 0, "fiber": 0, "category": "neutral"},
175
+ "olive oil": {"calories": 120, "fat": 14, "carbs": 0, "protein": 0, "sugar": 0, "fiber": 0, "category": "healthy"},
176
+ "dressing": {"calories": 150, "fat": 16, "carbs": 2, "protein": 0, "sugar": 1, "fiber": 0, "category": "junk"},
177
+ "guacamole": {"calories": 100, "fat": 9, "carbs": 6, "protein": 1, "sugar": 0, "fiber": 4, "category": "healthy"},
178
+ "cheese": {"calories": 110, "fat": 9, "carbs": 1, "protein": 7, "sugar": 0, "fiber": 0, "category": "neutral"},
179
+ "sauce": {"calories": 50, "fat": 2, "carbs": 8, "protein": 1, "sugar": 6, "fiber": 0, "category": "neutral"}
180
+ }
181
+
182
+ # Save the default database to CSV for future use
183
+ try:
184
+ df = pd.DataFrame(columns=['food_name', 'calories', 'fat', 'carbs', 'protein', 'sugar', 'fiber', 'category'])
185
+ for food_name, nutrition in food_data.items():
186
+ df = df.append({
187
+ 'food_name': food_name,
188
+ 'calories': nutrition['calories'],
189
+ 'fat': nutrition['fat'],
190
+ 'carbs': nutrition['carbs'],
191
+ 'protein': nutrition['protein'],
192
+ 'sugar': nutrition['sugar'],
193
+ 'fiber': nutrition['fiber'],
194
+ 'category': nutrition['category']
195
+ }, ignore_index=True)
196
+ df.to_csv('nutrition_database.csv', index=False)
197
+ print("Default nutrition database saved to CSV")
198
+ except Exception as e:
199
+ print(f"Warning: Could not save default database to CSV: {e}")
200
+
201
+ return food_data
202
+
203
+ def preprocess_image(self, image):
204
+ """Preprocess image to improve OCR results"""
205
+ try:
206
+ if isinstance(image, str) and (image.startswith('http://') or image.startswith('https://')):
207
+ response = requests.get(image)
208
+ img = Image.open(BytesIO(response.content))
209
+ else:
210
+ img = Image.fromarray(image) if isinstance(image, np.ndarray) else image
211
+
212
+ # Convert to grayscale
213
+ img = img.convert('L')
214
+
215
+ # Increase contrast
216
+ enhancer = ImageEnhance.Contrast(img)
217
+ img = enhancer.enhance(2.0)
218
+
219
+ # Apply noise reduction
220
+ img = img.filter(ImageFilter.MedianFilter(size=3))
221
+
222
+ # Binarize (convert to black and white)
223
+ threshold = 150
224
+ img = img.point(lambda p: 255 if p > threshold else 0)
225
+
226
+ return img
227
+ except Exception as e:
228
+ print(f"Image preprocessing error: {e}")
229
+ return image
230
+
231
+ def extract_text_from_image(self, image):
232
+ """Extract text from image using OCR with enhanced preprocessing"""
233
+ try:
234
+ # Preprocess image
235
+ img = self.preprocess_image(image)
236
+
237
+ # Run OCR with optimized configuration
238
+ custom_config = r'--oem 3 --psm 6 -c preserve_interword_spaces=1'
239
+ text = pytesseract.image_to_string(img, config=custom_config)
240
+
241
+ return text
242
+ except Exception as e:
243
+ return f"Error extracting text: {str(e)}"
244
+
245
+ def extract_food_items(self, text):
246
+ """Extract food items from OCR text with enhanced NLP capabilities"""
247
+ # Split into lines and preprocess
248
+ lines = text.split('\n')
249
+ food_items = []
250
+
251
+ # Regular patterns for food items in bills
252
+ price_pattern = r'\$?\d+\.?\d{0,2}'
253
+ quantity_pattern = r'^\d+\s*[xX]?\s*'
254
+
255
+ # First pass: Look for items with prices
256
+ for line in lines:
257
+ line = line.strip()
258
+ if not line:
259
+ continue
260
+
261
+ # Skip lines that look like totals, headers, or payment info
262
+ if re.search(r'(total|subtotal|tax|gratuity|tip|service|amount|due|change|cash|credit|card|payment|date|time|server|check|table|guest|invoice|receipt|bill|order|terminal|merchant|transaction|approved|auth|method|reference|account)',
263
+ line.lower()):
264
+ continue
265
+
266
+ # If line contains a price, it's likely a food item
267
+ if re.search(price_pattern, line):
268
+ # Extract the item name (everything before the price)
269
+ item_parts = re.split(price_pattern, line)
270
+ if item_parts and len(item_parts[0].strip()) > 1:
271
+ item_name = item_parts[0].strip()
272
+
273
+ # Clean up the item name
274
+ cleaned_item = re.sub(quantity_pattern, '', item_name) # Remove quantities
275
+ cleaned_item = re.sub(r'\d+\s*(oz|ml|g|lb|kg)\s*', '', cleaned_item) # Remove sizes
276
+ cleaned_item = re.sub(r'\(\w+\)', '', cleaned_item) # Remove parentheses
277
+
278
+ # Add to food items if it looks valid
279
+ if len(cleaned_item.strip()) > 1:
280
+ food_items.append(cleaned_item.strip().lower())
281
+
282
+ # Second pass: Use NLP if we couldn't find many food items
283
+ if len(food_items) < 2 and self.food_classifier:
284
+ # Extract potential food words from text
285
+ common_words = []
286
+ for line in lines:
287
+ # Extract words that might be food items
288
+ words = re.findall(r'\b[a-zA-Z]{3,}\b', line.lower())
289
+ # Filter out common non-food words
290
+ words = [w for w in words if w not in ['total', 'tax', 'subtotal', 'amount', 'cash', 'credit', 'visa', 'card']]
291
+ common_words.extend(words)
292
+
293
+ # Use NLP to identify food items
294
+ if common_words:
295
+ try:
296
+ candidate_foods = list(set(common_words))
297
+ food_categories = ["food", "drink", "meal", "dish", "beverage"]
298
+
299
+ for item in candidate_foods:
300
+ # Skip very short items
301
+ if len(item) < 3:
302
+ continue
303
+
304
+ # Direct match in our database
305
+ if item in self.nutrition_data:
306
+ if item not in food_items:
307
+ food_items.append(item)
308
+ continue
309
+
310
+ # Use classifier to determine if it's food
311
+ result = self.food_classifier(item, food_categories)
312
+ if result["scores"][0] > 0.7: # High confidence
313
+ food_items.append(item)
314
+ except Exception as e:
315
+ print(f"Error in NLP food identification: {e}")
316
+
317
+ # Remove duplicates and return
318
+ return list(set(food_items))
319
+
320
+ def match_food_to_nutrition(self, food_items):
321
+ """Match extracted food items to nutrition database with improved fuzzy matching"""
322
+ matched_items = []
323
+ unmatched_items = []
324
+
325
+ for item in food_items:
326
+ # 1. Direct match
327
+ if item in self.nutrition_data:
328
+ matched_items.append({"name": item, "nutrition": self.nutrition_data[item]})
329
+ continue
330
+
331
+ # 2. Check for exact word matches
332
+ item_words = set(item.split())
333
+ exact_match = False
334
+
335
+ for db_food in self.nutrition_data:
336
+ db_words = set(db_food.split())
337
+ # If all words in database item are in our item
338
+ if db_words.issubset(item_words) and len(db_words) > 0:
339
+ matched_items.append({"name": item, "matched_as": db_food, "nutrition": self.nutrition_data[db_food]})
340
+ exact_match = True
341
+ break
342
+
343
+ if exact_match:
344
+ continue
345
+
346
+ # 3. Partial fuzzy matching
347
+ best_match = None
348
+ max_score = 0
349
+
350
+ for db_food in self.nutrition_data:
351
+ # Check if one is contained in the other
352
+ if db_food in item or item in db_food:
353
+ # Calculate word overlap score
354
+ db_words = set(db_food.split())
355
+ item_words = set(item.split())
356
+ intersection = len(item_words.intersection(db_words))
357
+
358
+ # Score based on word overlap and length similarity
359
+ overlap = intersection / max(len(item_words), len(db_words))
360
+ length_sim = min(len(item), len(db_food)) / max(len(item), len(db_food))
361
+ score = (overlap * 0.7) + (length_sim * 0.3)
362
+
363
+ if score > max_score and score > 0.3: # Threshold for match quality
364
+ max_score = score
365
+ best_match = db_food
366
+
367
+ if best_match:
368
+ matched_items.append({"name": item, "matched_as": best_match, "nutrition": self.nutrition_data[best_match], "confidence": max_score})
369
+ else:
370
+ unmatched_items.append(item)
371
+
372
+ # Return both matched and unmatched for potential manual entry
373
+ return matched_items, unmatched_items
374
+
375
+ def calculate_nutrition_and_health_score(self, matched_items):
376
+ """Calculate nutritional totals and health score with improved metrics"""
377
+ if not matched_items:
378
+ return {
379
+ "total_calories": 0,
380
+ "total_fat": 0,
381
+ "total_carbs": 0,
382
+ "total_protein": 0,
383
+ "total_sugar": 0,
384
+ "total_fiber": 0,
385
+ "health_score": 0,
386
+ "health_assessment": "No food items detected",
387
+ "items": []
388
+ }
389
+
390
+ # Calculate totals
391
+ total_calories = sum(item["nutrition"]["calories"] for item in matched_items)
392
+ total_fat = sum(item["nutrition"]["fat"] for item in matched_items)
393
+ total_carbs = sum(item["nutrition"]["carbs"] for item in matched_items)
394
+ total_protein = sum(item["nutrition"]["protein"] for item in matched_items)
395
+ total_sugar = sum(item["nutrition"]["sugar"] for item in matched_items)
396
+ total_fiber = sum(item["nutrition"]["fiber"] for item in matched_items)
397
+
398
+ # Count categories
399
+ categories = [item["nutrition"]["category"] for item in matched_items]
400
+ category_counts = {
401
+ "healthy": categories.count("healthy"),
402
+ "protein": categories.count("protein"),
403
+ "neutral": categories.count("neutral"),
404
+ "junk": categories.count("junk")
405
+ }
406
+
407
+ # Calculate health score (0-100)
408
+ total_items = len(matched_items)
409
+
410
+ # Base health score from food categories
411
+ health_score = 0
412
+ if total_items > 0:
413
+ health_score += (category_counts["healthy"] / total_items) * 35
414
+ health_score += (category_counts["protein"] / total_items) * 30
415
+ health_score += (category_counts["neutral"] / total_items) * 20
416
+
417
+ # Adjust for macronutrient balance
418
+ if total_calories > 0:
419
+ # Protein should be ~25% of calories (4 calories per gram)
420
+ protein_calories = total_protein * 4
421
+ protein_percentage = protein_calories / total_calories
422
+ if protein_percentage > 0.2:
423
+ health_score += 10
424
+
425
+ # Fat shouldn't be more than 35% of calories (9 calories per gram)
426
+ fat_calories = total_fat * 9
427
+ fat_percentage = fat_calories / total_calories
428
+ if fat_percentage < 0.35:
429
+ health_score += 8
430
+ else:
431
+ health_score -= 10
432
+
433
+ # Sugar should be limited
434
+ sugar_calories = total_sugar * 4
435
+ sugar_percentage = sugar_calories / total_calories
436
+ if sugar_percentage < 0.1: # Less than 10% from sugar
437
+ health_score += 7
438
+ else:
439
+ health_score -= 7
440
+
441
+ # Fiber is good
442
+ if total_fiber > 5:
443
+ health_score += 5
444
+
445
+ # Apply a balanced meal bonus if there's variety
446
+ if len(set(categories)) >= 3 and category_counts["junk"] < total_items / 2:
447
+ health_score += 10
448
+
449
+ # Cap between 0-100
450
+ health_score = max(0, min(100, health_score))
451
+
452
+ # Generate health assessment
453
+ if health_score > 80:
454
+ assessment = f"Excellent! Your meal of {total_calories} calories is very well-balanced with great nutritional choices."
455
+ elif health_score > 60:
456
+ assessment = f"Good job! Your meal of {total_calories} calories is generally balanced with decent nutritional value."
457
+ elif health_score > 40:
458
+ assessment = f"Average meal. Your {total_calories} calories could be better balanced for optimal nutrition."
459
+ elif health_score > 20:
460
+ assessment = f"Caution - you consumed approximately {total_calories} calories with too much emphasis on less healthy options."
461
+ else:
462
+ assessment = f"Warning - your meal totaling {total_calories} calories is primarily composed of unhealthy items. Consider healthier choices next time."
463
+
464
+ # Prepare detailed items list
465
+ items_details = []
466
+ for item in matched_items:
467
+ name = item["name"]
468
+ if "matched_as" in item:
469
+ confidence = item.get("confidence", 1.0)
470
+ confidence_str = f" - {int(confidence*100)}% match" if confidence < 0.95 else ""
471
+ name = f"{name} (matched as {item['matched_as']}{confidence_str})"
472
+
473
+ items_details.append({
474
+ "name": name,
475
+ "calories": item["nutrition"]["calories"],
476
+ "fat": item["nutrition"]["fat"],
477
+ "carbs": item["nutrition"]["carbs"],
478
+ "protein": item["nutrition"]["protein"],
479
+ "sugar": item["nutrition"].get("sugar", 0),
480
+ "fiber": item["nutrition"].get("fiber", 0),
481
+ "category": item["nutrition"]["category"]
482
+ })
483
+
484
+ # Return comprehensive results
485
+ return {
486
+ "total_calories": total_calories,
487
+ "total_fat": total_fat,
488
+ "total_carbs": total_carbs,
489
+ "total_protein": total_protein,
490
+ "total_sugar": total_sugar,
491
+ "total_fiber": total_fiber,
492
+ "health_score": round(health_score, 1),
493
+ "health_assessment": assessment,
494
+ "items": items_details
495
+ }
496
+
497
+ def generate_nutritional_charts(self, nutrition_results):
498
+ """Generate charts for nutritional breakdown"""
499
+ try:
500
+ # Create a figure with subplots
501
+ fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))
502
+
503
+ # Macronutrient distribution pie chart
504
+ if nutrition_results["total_calories"] > 0:
505
+ # Calculate macronutrient percentages
506
+ protein_cals = nutrition_results["total_protein"] * 4
507
+ carbs_cals = nutrition_results["total_carbs"] * 4
508
+ fat_cals = nutrition_results["total_fat"] * 9
509
+
510
+ labels = ['Protein', 'Carbs', 'Fat']
511
+ sizes = [protein_cals, carbs_cals, fat_cals]
512
+ colors = ['#66c2a5', '#fc8d62', '#8da0cb']
513
+
514
+ ax1.pie(sizes, labels=labels, colors=colors, autopct='%1.1f%%', startangle=90)
515
+ ax1.set_title('Macronutrient Distribution')
516
+
517
+ # Category distribution bar chart
518
+ categories = {}
519
+ for item in nutrition_results["items"]:
520
+ cat = item["category"].title()
521
+ categories[cat] = categories.get(cat, 0) + item["calories"]
522
+
523
+ cat_names = list(categories.keys())
524
+ cat_values = list(categories.values())
525
+
526
+ colors = {'Healthy': '#5cb85c', 'Protein': '#5bc0de', 'Neutral': '#f0ad4e', 'Junk': '#d9534f'}
527
+ bar_colors = [colors.get(cat, '#777777') for cat in cat_names]
528
+
529
+ ax2.bar(cat_names, cat_values, color=bar_colors)
530
+ ax2.set_title('Calories by Food Category')
531
+ ax2.set_ylabel('Calories')
532
+ plt.xticks(rotation=45)
533
+
534
+ plt.tight_layout()
535
+
536
+ # Save chart to bytes
537
+ chart_bytes = BytesIO()
538
+ plt.savefig(chart_bytes, format='png', dpi=100)
539
+ plt.close(fig)
540
+ chart_bytes.seek(0)
541
+
542
+ # Convert to base64 for embedding in HTML
543
+ chart_base64 = base64.b64encode(chart_bytes.read()).decode('utf-8')
544
+
545
+ return chart_base64
546
+ except Exception as e:
547
+ print(f"Error generating charts: {e}")
548
+ return None
549
+
550
+ def format_results(self, nutrition_results, unmatched_items=None, chart_base64=None):
551
+ """Format results into a readable summary with enhanced design"""
552
+ if not nutrition_results["items"]:
553
+ return "No food items were detected in the bill. Please try a clearer image or manually enter food items."
554
+
555
+ # Start with CSS for better styling
556
+ result = """
557
+ <style>
558
+ .nutrition-container {
559
+ font-family: Arial, sans-serif;
560
+ max-width: 900px;
561
+ margin: 0 auto;
562
+ padding: 20px;
563
+ border-radius: 10px;
564
+ box-shadow: 0 0 10px rgba(0,0,0,0.1);
565
+ background-color: #f9f9f9;
566
+ }
567
+ .score-container {
568
+ text-align: center;
569
+ margin-bottom: 20px;
570
+ }
571
+ .score-circle {
572
+ display: inline-block;
573
+ width: 100px;
574
+ height: 100px;
575
+ line-height: 100px;
576
+ border-radius: 50%;
577
+ font-size: 32px;
578
+ color: white;
579
+ text-align: center;
580
+ margin: 20px;
581
+ font-weight: bold;
582
+ }
583
+ .assessment {
584
+ font-size: 18px;
585
+ margin: 15px 0;
586
+ padding: 10px;
587
+ border-radius: 5px;
588
+ background-color: #f0f0f0;
589
+ }
590
+ .macro-section {
591
+ display: flex;
592
+ justify-content: space-around;
593
+ flex-wrap: wrap;
594
+ margin: 20px 0;
595
+ }
596
+ .macro-box {
597
+ text-align: center;
598
+ width: 18%;
599
+ min-width: 120px;
600
+ margin: 10px;
601
+ padding: 15px;
602
+ border-radius: 8px;
603
+ box-shadow: 0 0 5px rgba(0,0,0,0.1);
604
+ }
605
+ .macro-title {
606
+ font-size: 14px;
607
+ text-transform: uppercase;
608
+ color: #666;
609
+ }
610
+ .macro-value {
611
+ font-size: 24px;
612
+ font-weight: bold;
613
+ }
614
+ .items-table {
615
+ width: 100%;
616
+ border-collapse: collapse;
617
+ margin: 20px 0;
618
+ }
619
+ .items-table th, .items-table td {
620
+ padding: 10px;
621
+ text-align: left;
622
+ border-bottom: 1px solid #ddd;
623
+ }
624
+ .items-table th {
625
+ background-color: #f2f2f2;
626
+ }
627
+ .category-healthy {
628
+ color: #5cb85c;
629
+ }
630
+ .category-protein {
631
+ color: #5bc0de;
632
+ }
633
+ .category-neutral {
634
+ color: #f0ad4e;
635
+ }
636
+ .category-junk {
637
+ color: #d9534f;
638
+ }
639
+ .chart-container {
640
+ margin: 20px 0;
641
+ text-align: center;
642
+ }
643
+ .recommendations {
644
+ background-color: #e9f7ef;
645
+ padding: 15px;
646
+ border-left: 5px solid #27ae60;
647
+ margin: 20px 0;
648
+ }
649
+ </style>
650
+ """
651
+
652
+ # Determine health score color
653
+ score = nutrition_results["health_score"]
654
+ if score > 80:
655
+ score_color = "#5cb85c" # Green
656
+ elif score > 60:
657
+ score_color = "#5bc0de" # Blue
658
+ elif score > 40:
659
+ score_color = "#f0ad4e" # Orange
660
+ elif score > 20:
661
+ score_color = "#ff7f0e" # Dark orange
662
+ else:
663
+ score_color = "#d9534f" # Red
664
+
665
+ # Main container
666
+ result += '<div class="nutrition-container">'
667
+
668
+ # Header
669
+ result += '<h1 style="text-align:center;">Nutritional Analysis Report</h1>'
670
+
671
+ # Health score
672
+ result += f'''
673
+ <div class="score-container">
674
+ <h2>Health Score</h2>
675
+ <div class="score-circle" style="background-color: {score_color};">{nutrition_results["health_score"]}</div>
676
+ <div class="assessment">{nutrition_results["health_assessment"]}</div>
677
+ </div>
678
+ '''
679
+
680
+ # Insert chart if available
681
+ if chart_base64:
682
+ result += f'''
683
+ <div class="chart-container">
684
+ <h2>Nutritional Breakdown</h2>
685
+ <img src="data:image/png;base64,{chart_base64}" style="max-width:100%; height:auto;" alt="Nutritional Chart">
686
+ </div>
687
+ '''
688
+
689
+ # Macronutrient summary
690
+ result += '''
691
+ <h2>Nutritional Summary</h2>
692
+ <div class="macro-section">
693
+ '''
694
+
695
+ # Add macro boxes
696
+ result += f'''
697
+ <div class="macro-box" style="background-color: #f9f0ff;">
698
+ <div class="macro-title">Calories</div>
699
+ <div class="macro-value">{nutrition_results["total_calories"]}</div>
700
+ <div>kcal</div>
701
+ </div>
702
+ <div class="macro-box" style="background-color: #fff0f0;">
703
+ <div class="macro-title">Fat</div>
704
+ <div class="macro-value">{nutrition_results["total_fat"]}</div>
705
+ <div>grams</div>
706
+ </div>
707
+ <div class="macro-box" style="background-color: #fff9f0;">
708
+ <div class="macro-title">Carbs</div>
709
+ <div class="macro-value">{nutrition_results["total_carbs"]}</div>
710
+ <div>grams</div>
711
+ </div>
712
+ <div class="macro-box" style="background-color: #f0f9ff;">
713
+ <div class="macro-title">Protein</div>
714
+ <div class="macro-value">{nutrition_results["total_protein"]}</div>
715
+ <div>grams</div>
716
+ </div>
717
+ <div class="macro-box" style="background-color: #f0fff9;">
718
+ <div class="macro-title">Sugar</div>
719
+ <div class="macro-value">{nutrition_results["total_sugar"]}</div>
720
+ <div>grams</div>
721
+ </div>
722
+ </div>
723
+ '''
724
+
725
+ # Food items table
726
+ result += '''
727
+ <h2>Detected Food Items</h2>
728
+ <div style="overflow-x:auto;">
729
+ <table class="items-table">
730
+ <tr>
731
+ <th>Item</th>
732
+ <th>Calories</th>
733
+ <th>Fat (g)</th>
734
+ <th>Carbs (g)</th>
735
+ <th>Protein (g)</th>
736
+ <th>Category</th>
737
+ </tr>
738
+ '''
739
+
740
+ # Add items to table
741
+ for item in nutrition_results["items"]:
742
+ category_class = f"category-{item['category']}"
743
+ result += f'''
744
+ <tr>
745
+ <td>{html.escape(item['name']).title()}</td>
746
+ <td>{item['calories']}</td>
747
+ <td>{item['fat']}</td>
748
+ <td>{item['carbs']}</td>
749
+ <td>{item['protein']}</td>
750
+ <td class="{category_class}">{item['category'].title()}</td>
751
+ </tr>
752
+ '''
753
+
754
+ result += '</table></div>'
755
+
756
+ # Show unmatched items if any
757
+ if unmatched_items and len(unmatched_items) > 0:
758
+ result += f'''
759
+ <div style="margin: 20px 0; padding: 10px; background-color: #fff3cd; border-left: 5px solid #ffc107;">
760
+ <h3>Unrecognized Items</h3>
761
+ <p>The following items could not be matched to our nutrition database:</p>
762
+ <ul>{"".join([f"<li>{item}</li>" for item in unmatched_items])}</ul>
763
+ </div>
764
+ '''
765
+
766
+ # Recommendations
767
+ result += '<div class="recommendations"><h2>Recommendations</h2><ul>'
768
+
769
+ # Calculate macronutrient percentages
770
+ total_calories = nutrition_results['total_calories']
771
+ if total_calories > 0:
772
+ fat_percentage = (nutrition_results['total_fat'] * 9 / total_calories) * 100
773
+ carbs_percentage = (nutrition_results['total_carbs'] * 4 / total_calories) * 100
774
+ protein_percentage = (nutrition_results['total_protein'] * 4 / total_calories) * 100
775
+ sugar_percentage = (nutrition_results['total_sugar'] * 4 / total_calories) * 100
776
+
777
+ # Make recommendations based on the composition
778
+ if protein_percentage < 15:
779
+ result += "<li>Your meal is low in protein. Consider adding lean protein sources like chicken, fish, tofu, or legumes to your next meal.</li>"
780
+ if fat_percentage > 35:
781
+ result += "<li>Your meal is high in fat. Try reducing fried foods and opting for leaner protein sources and cooking methods.</li>"
782
+ if carbs_percentage > 60:
783
+ result += "<li>Your meal is high in carbohydrates. Consider balancing with more vegetables and protein in your next meal.</li>"
784
+ if sugar_percentage > 15:
785
+ result += "<li>Your meal contains a high amount of sugar. Try to reduce sugary drinks and desserts.</li>"
786
+ if nutrition_results['total_fiber'] < 5:
787
+ result += "<li>Consider adding more fiber to your diet through whole grains, fruits, and vegetables.</li>"
788
+
789
+ # Check total calories
790
+ if total_calories > 1000 and len(nutrition_results["items"]) <= 2:
791
+ result += "<li>This meal is high in calories for the number of items. Consider portion control or lighter options.</li>"
792
+
793
+ # If no specific recommendations, add a general one
794
+ if "<li>" not in result:
795
+ result += "<li>Your meal is well-balanced! Keep up the good choices and maintain this eating pattern.</li>"
796
+
797
+ result += '</ul></div>'
798
+
799
+ # Close container
800
+ result += '</div>'
801
+
802
+ return result
803
+
804
+ def analyze_restaurant_bill(self, image, manual_items=None):
805
+ """Main function to process the bill image and return nutritional analysis"""
806
+ start_time = datetime.now()
807
+
808
+ # Initialize result
809
+ result = {"status": "processing"}
810
+ food_items = []
811
+
812
+ # Process image if provided
813
+ if image is not None:
814
+ # Extract text from image using OCR
815
+ text = self.extract_text_from_image(image)
816
+ if text.startswith("Error"):
817
+ return f"OCR failed: {text}"
818
+
819
+ # Extract food items from the text
820
+ food_items = self.extract_food_items(text)
821
+
822
+ # Add manually entered items if provided
823
+ if manual_items and isinstance(manual_items, str) and len(manual_items.strip()) > 0:
824
+ manual_list = [item.strip().lower() for item in manual_items.split(',')]
825
+ food_items.extend(manual_list)
826
+
827
+ # Remove duplicates
828
+ food_items = list(set(food_items))
829
+
830
+ if not food_items:
831
+ return "No food items detected in the bill. Please try a clearer image or manually enter food items."
832
+
833
+ # Match food items to nutrition database
834
+ matched_items, unmatched_items = self.match_food_to_nutrition(food_items)
835
+
836
+ # Calculate nutritional information and health score
837
+ nutrition_results = self.calculate_nutrition_and_health_score(matched_items)
838
+
839
+ # Generate charts
840
+ chart_base64 = self.generate_nutritional_charts(nutrition_results)
841
+
842
+ # Format the results
843
+ formatted_results = self.format_results(nutrition_results, unmatched_items, chart_base64)
844
+
845
+ # Add to history (keep last 10 analyses)
846
+ self.history.append({
847
+ 'timestamp': datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
848
+ 'food_items': food_items,
849
+ 'nutrition_results': nutrition_results
850
+ })
851
+ if len(self.history) > 10:
852
+ self.history.pop(0)
853
+
854
+ # Log processing time
855
+ processing_time = (datetime.now() - start_time).total_seconds()
856
+ print(f"Analysis completed in {processing_time:.2f} seconds")
857
+
858
+ return formatted_results
859
+
860
+ def save_to_csv(self, file_path="nutrition_history.csv"):
861
+ """Save analysis history to CSV"""
862
+ try:
863
+ with open(file_path, 'w', newline='') as csvfile:
864
+ fieldnames = ['timestamp', 'total_calories', 'total_fat', 'total_carbs', 'total_protein', 'health_score', 'items']
865
+ writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
866
+ writer.writeheader()
867
+
868
+ for entry in self.history:
869
+ writer.writerow({
870
+ 'timestamp': entry['timestamp'],
871
+ 'total_calories': entry['nutrition_results']['total_calories'],
872
+ 'total_fat': entry['nutrition_results']['total_fat'],
873
+ 'total_carbs': entry['nutrition_results']['total_carbs'],
874
+ 'total_protein': entry['nutrition_results']['total_protein'],
875
+ 'health_score': entry['nutrition_results']['health_score'],
876
+ 'items': ', '.join([item['name'] for item in entry['nutrition_results']['items']])
877
+ })
878
+ return True
879
+ except Exception as e:
880
+ print(f"Error saving to CSV: {e}")
881
+ return False
882
+
883
+ # Initialize the analyzer
884
+ analyzer = NutritionAnalyzer()
885
+
886
+ # Define Gradio interface
887
+ with gr.Blocks(theme=gr.themes.Soft()) as demo:
888
+ gr.Markdown("""
889
+ # 🧾 Advanced Restaurant Bill Nutritional Analyzer 🍽️
890
+
891
+ **Upload a photo of your restaurant bill to analyze the nutritional content and get a detailed health score.**
892
+
893
+ This AI-powered tool uses OCR to read your bill, identifies food items, and provides nutritional analysis and recommendations.
894
+ """)
895
+
896
+ with gr.Tabs():
897
+ with gr.TabItem("Bill Analysis"):
898
+ with gr.Row():
899
+ with gr.Column(scale=1):
900
+ input_image = gr.Image(type="pil", label="Upload Bill Image")
901
+ manual_items = gr.Textbox(label="Manually Add Items (comma separated)", placeholder="burger, fries, soda")
902
+ analyze_button = gr.Button("Analyze Nutrition", variant="primary")
903
+
904
+ with gr.Accordion("Advanced Options", open=False):
905
+ feedback = gr.Textbox(label="Give Feedback About Results", placeholder="The analysis was accurate and helpful.")
906
+ submit_feedback = gr.Button("Submit Feedback")
907
+
908
+ with gr.Column(scale=2):
909
+ output_html = gr.HTML(label="Nutritional Analysis")
910
+
911
+ with gr.Row():
912
+ save_button = gr.Button("Save Analysis")
913
+ clear_button = gr.Button("Clear")
914
+
915
+ with gr.TabItem("History"):
916
+ history_html = gr.HTML("<p>Your analysis history will appear here.</p>")
917
+ refresh_history = gr.Button("Refresh History")
918
+
919
+ with gr.TabItem("Help"):
920
+ gr.Markdown("""
921
+ ## How to use this tool
922
+
923
+ 1. **Upload a photo** of your restaurant bill or receipt
924
+ 2. Optionally **manually add food items** that might be missed
925
+ 3. Click **Analyze Nutrition** to get your results
926
+ 4. Save your analysis if you want to track it
927
+
928
+ ## Tips for best results
929
+
930
+ - Ensure your bill is clearly visible and well-lit
931
+ - Take the photo straight-on with minimal glare
932
+ - Make sure food item names are readable
933
+ - Crop unnecessary parts of the image
934
+ - If items are missed, add them manually in the text box
935
+
936
+ ## What you'll get
937
+
938
+ - Detailed health score and assessment
939
+ - Complete nutritional breakdown (calories, macros, etc.)
940
+ - Visual charts showing your meal composition
941
+ - Personalized recommendations for better nutrition
942
+ - History tracking for your previous analyses
943
+
944
+ ## About the food categories
945
+
946
+ - **Healthy**: Nutrient-dense foods with health benefits
947
+ - **Protein**: High-protein foods that support muscle maintenance
948
+ - **Neutral**: Balanced foods that can fit in a healthy diet
949
+ - **Junk**: High-calorie, low-nutrient foods to limit
950
+
951
+ ## Disclaimer
952
+
953
+ This tool provides an estimate only. Actual nutritional content may vary based on specific recipes and preparation methods. It is not intended as medical advice.
954
+ """)
955
+
956
+ # Handle button clicks
957
+ analyze_button.click(
958
+ fn=analyzer.analyze_restaurant_bill,
959
+ inputs=[input_image, manual_items],
960
+ outputs=[output_html]
961
+ )
962
+
963
+ clear_button.click(
964
+ fn=lambda: (None, ""),
965
+ inputs=[],
966
+ outputs=[input_image, manual_items]
967
+ )
968
+
969
+ def process_feedback(feedback_text):
970
+ """Process user feedback"""
971
+ if analyzer.sentiment_analyzer and feedback_text:
972
+ try:
973
+ sentiment = analyzer.sentiment_analyzer(feedback_text)
974
+ feedback_type = sentiment[0]['label']
975
+ confidence = sentiment[0]['score']
976
+
977
+ # Log feedback
978
+ print(f"Feedback received: {feedback_text}")
979
+ print(f"Sentiment: {feedback_type} (confidence: {confidence:.2f})")
980
+
981
+ return f"Thank you for your feedback! We've recorded your {feedback_type.lower()} comments."
982
+ except Exception as e:
983
+ print(f"Error processing feedback: {e}")
984
+ return "Thank you for your feedback!"
985
+ return "Thank you for your feedback!"
986
+
987
+ submit_feedback.click(
988
+ fn=process_feedback,
989
+ inputs=[feedback],
990
+ outputs=[gr.Textbox(label="Feedback Status")]
991
+ )
992
+
993
+ def format_history():
994
+ """Format history as HTML"""
995
+ if not analyzer.history:
996
+ return "<p>No analysis history found.</p>"
997
+
998
+ html = """
999
+ <style>
1000
+ .history-table {
1001
+ width: 100%;
1002
+ border-collapse: collapse;
1003
+ margin-top: 20px;
1004
+ }
1005
+ .history-table th, .history-table td {
1006
+ border: 1px solid #ddd;
1007
+ padding: 8px;
1008
+ text-align: left;
1009
+ }
1010
+ .history-table th {
1011
+ background-color: #f2f2f2;
1012
+ }
1013
+ .history-table tr:nth-child(even) {
1014
+ background-color: #f9f9f9;
1015
+ }
1016
+ .history-table tr:hover {
1017
+ background-color: #f1f1f1;
1018
+ }
1019
+ </style>
1020
+ <h2>Analysis History</h2>
1021
+ <table class="history-table">
1022
+ <tr>
1023
+ <th>Date & Time</th>
1024
+ <th>Calories</th>
1025
+ <th>Fat (g)</th>
1026
+ <th>Carbs (g)</th>
1027
+ <th>Protein (g)</th>
1028
+ <th>Health Score</th>
1029
+ <th>Items</th>
1030
+ </tr>
1031
+ """
1032
+
1033
+ for entry in reversed(analyzer.history):
1034
+ results = entry['nutrition_results']
1035
+ items_text = ", ".join([item['name'] for item in results['items']])
1036
+
1037
+ html += f"""
1038
+ <tr>
1039
+ <td>{entry['timestamp']}</td>
1040
+ <td>{results['total_calories']}</td>
1041
+ <td>{results['total_fat']}</td>
1042
+ <td>{results['total_carbs']}</td>
1043
+ <td>{results['total_protein']}</td>
1044
+ <td>{results['health_score']}</td>
1045
+ <td>{items_text}</td>
1046
+ </tr>
1047
+ """
1048
+
1049
+ html += "</table>"
1050
+
1051
+ # Add a simple chart of health scores over time
1052
+ if len(analyzer.history) > 1:
1053
+ html += """
1054
+ <h3>Health Score Trend</h3>
1055
+ <div style="padding: 20px; background-color: #f9f9f9; border-radius: 5px;">
1056
+ <svg width="600" height="200" style="background-color: white; border: 1px solid #ddd;">
1057
+ """
1058
+
1059
+ # Extract dates and scores
1060
+ dates = [entry['timestamp'].split()[0] for entry in analyzer.history]
1061
+ scores = [entry['nutrition_results']['health_score'] for entry in analyzer.history]
1062
+
1063
+ # Simple SVG line chart
1064
+ max_score = 100
1065
+ width = 580
1066
+ height = 180
1067
+ margin = 40
1068
+
1069
+ # X axis
1070
+ x_step = (width - 2 * margin) / (len(dates) - 1) if len(dates) > 1 else 0
1071
+
1072
+ # Create points for the polyline
1073
+ points = []
1074
+ for i, score in enumerate(scores):
1075
+ x = margin + i * x_step
1076
+ y = height - margin - (score / max_score * (height - 2 * margin))
1077
+ points.append(f"{x},{y}")
1078
+
1079
+ # Draw x and y axes
1080
+ html += f'<line x1="{margin}" y1="{height-margin}" x2="{width-margin}" y2="{height-margin}" stroke="black" />'
1081
+ html += f'<line x1="{margin}" y1="{margin}" x2="{margin}" y2="{height-margin}" stroke="black" />'
1082
+
1083
+ # Draw the line
1084
+ html += f'<polyline points="{" ".join(points)}" fill="none" stroke="#5bc0de" stroke-width="2" />'
1085
+
1086
+ # Add dots at data points
1087
+ for i, point in enumerate(points):
1088
+ x, y = point.split(',')
1089
+ html += f'<circle cx="{x}" cy="{y}" r="4" fill="#5bc0de" />'
1090
+ # Add tooltip
1091
+ html += f'<title>Date: {dates[i]}, Score: {scores[i]}</title>'
1092
+
1093
+ html += """
1094
+ </svg>
1095
+ </div>
1096
+ """
1097
+
1098
+ return html
1099
+
1100
+ refresh_history.click(
1101
+ fn=format_history,
1102
+ inputs=[],
1103
+ outputs=[history_html]
1104
+ )
1105
+
1106
+ save_button.click(
1107
+ fn=lambda: "Analysis saved to history" if analyzer.history else "No analysis to save",
1108
+ inputs=[],
1109
+ outputs=[gr.Textbox(label="Save Status")]
1110
+ )
1111
+
1112
+ # Launch the app
1113
+ if __name__ == "__main__":
1114
+ try:
1115
+ # Load the pre-trained models
1116
+ print("Initializing the application...")
1117
+
1118
+ # Launch Gradio app
1119
+ demo.launch()
1120
+ except Exception as e:
1121
+ print(f"Error launching application: {e}")