Amandeep01 commited on
Commit
89691b4
Β·
verified Β·
1 Parent(s): f59b381

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +728 -254
app.py CHANGED
@@ -7,191 +7,91 @@ import re
7
  import json
8
  import os
9
  import random
10
- from datetime import datetime
11
- import matplotlib.pyplot as plt
12
- import io
13
- import base64
14
  from transformers import pipeline
15
  import requests
16
  from io import BytesIO
 
 
 
17
 
18
  # Load nutrition database
19
  def load_nutrition_data():
20
- # Enhanced food database with nutritional information
 
21
  food_data = {
22
- "pizza": {"calories": 285, "fat": 10, "carbs": 36, "protein": 12, "category": "junk", "diet_type": ["vegetarian"], "health_index": 30},
23
- "burger": {"calories": 354, "fat": 17, "carbs": 40, "protein": 15, "category": "junk", "diet_type": ["non-vegetarian"], "health_index": 25},
24
- "fries": {"calories": 312, "fat": 15, "carbs": 41, "protein": 3, "category": "junk", "diet_type": ["vegetarian", "vegan"], "health_index": 20},
25
- "salad": {"calories": 100, "fat": 7, "carbs": 5, "protein": 2, "category": "healthy", "diet_type": ["vegetarian", "vegan"], "health_index": 90},
26
- "soda": {"calories": 140, "fat": 0, "carbs": 39, "protein": 0, "category": "junk", "diet_type": ["vegetarian", "vegan"], "health_index": 10},
27
- "juice": {"calories": 110, "fat": 0, "carbs": 26, "protein": 0, "category": "neutral", "diet_type": ["vegetarian", "vegan"], "health_index": 50},
28
- "water": {"calories": 0, "fat": 0, "carbs": 0, "protein": 0, "category": "healthy", "diet_type": ["vegetarian", "vegan"], "health_index": 100},
29
- "pasta": {"calories": 200, "fat": 2, "carbs": 42, "protein": 7, "category": "neutral", "diet_type": ["vegetarian"], "health_index": 60},
30
- "steak": {"calories": 300, "fat": 15, "carbs": 0, "protein": 30, "category": "protein", "diet_type": ["non-vegetarian"], "health_index": 65},
31
- "chicken": {"calories": 220, "fat": 8, "carbs": 0, "protein": 40, "category": "protein", "diet_type": ["non-vegetarian"], "health_index": 75},
32
- "fish": {"calories": 180, "fat": 5, "carbs": 0, "protein": 30, "category": "healthy", "diet_type": ["non-vegetarian"], "health_index": 85},
33
- "rice": {"calories": 130, "fat": 0, "carbs": 28, "protein": 3, "category": "neutral", "diet_type": ["vegetarian", "vegan"], "health_index": 65},
34
- "beer": {"calories": 154, "fat": 0, "carbs": 13, "protein": 1, "category": "junk", "diet_type": ["vegetarian", "vegan"], "health_index": 20},
35
- "wine": {"calories": 125, "fat": 0, "carbs": 4, "protein": 0, "category": "neutral", "diet_type": ["vegetarian", "vegan"], "health_index": 40},
36
- "ice cream": {"calories": 207, "fat": 11, "carbs": 24, "protein": 4, "category": "junk", "diet_type": ["vegetarian"], "health_index": 25},
37
- "coffee": {"calories": 5, "fat": 0, "carbs": 0, "protein": 0, "category": "healthy", "diet_type": ["vegetarian", "vegan"], "health_index": 80},
38
- "sandwich": {"calories": 250, "fat": 8, "carbs": 30, "protein": 15, "category": "neutral", "diet_type": ["vegetarian"], "health_index": 60},
39
- "soup": {"calories": 120, "fat": 3, "carbs": 12, "protein": 10, "category": "healthy", "diet_type": ["vegetarian"], "health_index": 80},
40
- "cake": {"calories": 350, "fat": 18, "carbs": 45, "protein": 4, "category": "junk", "diet_type": ["vegetarian"], "health_index": 15},
41
- "bread": {"calories": 80, "fat": 1, "carbs": 15, "protein": 3, "category": "neutral", "diet_type": ["vegetarian"], "health_index": 50},
42
- "chocolate": {"calories": 200, "fat": 12, "carbs": 20, "protein": 2, "category": "junk", "diet_type": ["vegetarian"], "health_index": 20},
43
- "milkshake": {"calories": 300, "fat": 10, "carbs": 50, "protein": 9, "category": "junk", "diet_type": ["vegetarian"], "health_index": 20},
44
- "dessert": {"calories": 280, "fat": 14, "carbs": 35, "protein": 5, "category": "junk", "diet_type": ["vegetarian"], "health_index": 15},
45
- "smoothie": {"calories": 170, "fat": 2, "carbs": 35, "protein": 5, "category": "neutral", "diet_type": ["vegetarian", "vegan"], "health_index": 70},
46
- "tea": {"calories": 2, "fat": 0, "carbs": 0, "protein": 0, "category": "healthy", "diet_type": ["vegetarian", "vegan"], "health_index": 85},
47
- "appetizer": {"calories": 200, "fat": 12, "carbs": 15, "protein": 8, "category": "neutral", "diet_type": ["vegetarian"], "health_index": 45},
48
- "noodles": {"calories": 190, "fat": 2, "carbs": 40, "protein": 7, "category": "neutral", "diet_type": ["vegetarian"], "health_index": 50},
49
- "taco": {"calories": 210, "fat": 10, "carbs": 22, "protein": 12, "category": "neutral", "diet_type": ["non-vegetarian"], "health_index": 55},
50
- "burrito": {"calories": 350, "fat": 12, "carbs": 50, "protein": 15, "category": "neutral", "diet_type": ["non-vegetarian"], "health_index": 50},
51
- "wrap": {"calories": 220, "fat": 5, "carbs": 30, "protein": 13, "category": "neutral", "diet_type": ["vegetarian"], "health_index": 65},
52
- "sushi": {"calories": 300, "fat": 7, "carbs": 40, "protein": 20, "category": "healthy", "diet_type": ["non-vegetarian"], "health_index": 80},
53
- "vegetables": {"calories": 50, "fat": 0, "carbs": 10, "protein": 2, "category": "healthy", "diet_type": ["vegetarian", "vegan"], "health_index": 95},
54
- "fruits": {"calories": 60, "fat": 0, "carbs": 15, "protein": 1, "category": "healthy", "diet_type": ["vegetarian", "vegan"], "health_index": 90},
55
- "oatmeal": {"calories": 150, "fat": 3, "carbs": 27, "protein": 5, "category": "healthy", "diet_type": ["vegetarian", "vegan"], "health_index": 85},
56
- "eggs": {"calories": 155, "fat": 11, "carbs": 1, "protein": 13, "category": "protein", "diet_type": ["vegetarian"], "health_index": 75},
57
- "tofu": {"calories": 144, "fat": 8, "carbs": 4, "protein": 16, "category": "protein", "diet_type": ["vegetarian", "vegan"], "health_index": 85},
58
- "yogurt": {"calories": 120, "fat": 5, "carbs": 9, "protein": 12, "category": "healthy", "diet_type": ["vegetarian"], "health_index": 75},
59
- "nuts": {"calories": 170, "fat": 15, "carbs": 7, "protein": 6, "category": "healthy", "diet_type": ["vegetarian", "vegan"], "health_index": 80},
60
- "chips": {"calories": 150, "fat": 10, "carbs": 15, "protein": 2, "category": "junk", "diet_type": ["vegetarian", "vegan"], "health_index": 20},
61
- "cookies": {"calories": 200, "fat": 10, "carbs": 25, "protein": 3, "category": "junk", "diet_type": ["vegetarian"], "health_index": 15},
62
- "pancakes": {"calories": 175, "fat": 7, "carbs": 22, "protein": 5, "category": "neutral", "diet_type": ["vegetarian"], "health_index": 40},
63
- "waffles": {"calories": 220, "fat": 11, "carbs": 26, "protein": 6, "category": "neutral", "diet_type": ["vegetarian"], "health_index": 35},
64
- "french toast": {"calories": 250, "fat": 10, "carbs": 30, "protein": 8, "category": "neutral", "diet_type": ["vegetarian"], "health_index": 40},
65
- "bacon": {"calories": 180, "fat": 14, "carbs": 0, "protein": 12, "category": "protein", "diet_type": ["non-vegetarian"], "health_index": 30},
66
- "sausage": {"calories": 230, "fat": 20, "carbs": 1, "protein": 14, "category": "protein", "diet_type": ["non-vegetarian"], "health_index": 25},
67
- "ham": {"calories": 140, "fat": 5, "carbs": 1, "protein": 21, "category": "protein", "diet_type": ["non-vegetarian"], "health_index": 60},
68
- "cheese": {"calories": 110, "fat": 9, "carbs": 1, "protein": 7, "category": "protein", "diet_type": ["vegetarian"], "health_index": 50},
69
- "butter": {"calories": 100, "fat": 11, "carbs": 0, "protein": 0, "category": "junk", "diet_type": ["vegetarian"], "health_index": 20},
70
- "oil": {"calories": 120, "fat": 14, "carbs": 0, "protein": 0, "category": "junk", "diet_type": ["vegetarian", "vegan"], "health_index": 20},
71
- "honey": {"calories": 64, "fat": 0, "carbs": 17, "protein": 0, "category": "neutral", "diet_type": ["vegetarian", "vegan"], "health_index": 55},
72
- "syrup": {"calories": 210, "fat": 0, "carbs": 53, "protein": 0, "category": "junk", "diet_type": ["vegetarian", "vegan"], "health_index": 15},
73
- "jam": {"calories": 50, "fat": 0, "carbs": 13, "protein": 0, "category": "neutral", "diet_type": ["vegetarian", "vegan"], "health_index": 40},
74
- "peanut butter": {"calories": 190, "fat": 16, "carbs": 7, "protein": 8, "category": "protein", "diet_type": ["vegetarian", "vegan"], "health_index": 60},
75
- "hummus": {"calories": 166, "fat": 10, "carbs": 14, "protein": 8, "category": "healthy", "diet_type": ["vegetarian", "vegan"], "health_index": 80},
76
- "avocado": {"calories": 160, "fat": 15, "carbs": 9, "protein": 2, "category": "healthy", "diet_type": ["vegetarian", "vegan"], "health_index": 85},
77
- "beans": {"calories": 120, "fat": 0, "carbs": 22, "protein": 8, "category": "healthy", "diet_type": ["vegetarian", "vegan"], "health_index": 90},
78
- "lentils": {"calories": 115, "fat": 0, "carbs": 20, "protein": 9, "category": "healthy", "diet_type": ["vegetarian", "vegan"], "health_index": 90},
79
- "quinoa": {"calories": 120, "fat": 2, "carbs": 21, "protein": 4, "category": "healthy", "diet_type": ["vegetarian", "vegan"], "health_index": 85},
80
- "muffin": {"calories": 210, "fat": 8, "carbs": 33, "protein": 4, "category": "junk", "diet_type": ["vegetarian"], "health_index": 30},
81
- "croissant": {"calories": 230, "fat": 12, "carbs": 26, "protein": 5, "category": "junk", "diet_type": ["vegetarian"], "health_index": 25},
82
- "bagel": {"calories": 245, "fat": 1, "carbs": 48, "protein": 10, "category": "neutral", "diet_type": ["vegetarian"], "health_index": 45},
83
- "donut": {"calories": 195, "fat": 11, "carbs": 22, "protein": 2, "category": "junk", "diet_type": ["vegetarian"], "health_index": 10},
84
- "alcohol": {"calories": 150, "fat": 0, "carbs": 5, "protein": 0, "category": "junk", "diet_type": ["vegetarian", "vegan"], "health_index": 10},
85
- "cocktail": {"calories": 200, "fat": 0, "carbs": 15, "protein": 0, "category": "junk", "diet_type": ["vegetarian", "vegan"], "health_index": 10},
86
- "mango": {"calories": 60, "fat": 0, "carbs": 15, "protein": 1, "category": "healthy", "diet_type": ["vegetarian", "vegan"], "health_index": 85},
87
- "berries": {"calories": 40, "fat": 0, "carbs": 10, "protein": 1, "category": "healthy", "diet_type": ["vegetarian", "vegan"], "health_index": 95},
88
- "banana": {"calories": 105, "fat": 0, "carbs": 27, "protein": 1, "category": "healthy", "diet_type": ["vegetarian", "vegan"], "health_index": 80},
89
- "orange": {"calories": 45, "fat": 0, "carbs": 11, "protein": 1, "category": "healthy", "diet_type": ["vegetarian", "vegan"], "health_index": 90},
90
- "apple": {"calories": 95, "fat": 0, "carbs": 25, "protein": 0, "category": "healthy", "diet_type": ["vegetarian", "vegan"], "health_index": 85},
91
- "grapes": {"calories": 65, "fat": 0, "carbs": 17, "protein": 1, "category": "healthy", "diet_type": ["vegetarian", "vegan"], "health_index": 80},
92
- "salmon": {"calories": 175, "fat": 11, "carbs": 0, "protein": 19, "category": "healthy", "diet_type": ["non-vegetarian"], "health_index": 90},
93
- "tuna": {"calories": 120, "fat": 1, "carbs": 0, "protein": 26, "category": "healthy", "diet_type": ["non-vegetarian"], "health_index": 85},
94
- "shrimp": {"calories": 85, "fat": 1, "carbs": 0, "protein": 18, "category": "healthy", "diet_type": ["non-vegetarian"], "health_index": 85},
95
- "lobster": {"calories": 89, "fat": 1, "carbs": 0, "protein": 17, "category": "healthy", "diet_type": ["non-vegetarian"], "health_index": 85},
96
- "crab": {"calories": 83, "fat": 1, "carbs": 0, "protein": 16, "category": "healthy", "diet_type": ["non-vegetarian"], "health_index": 85},
97
- "lamb": {"calories": 250, "fat": 20, "carbs": 0, "protein": 20, "category": "protein", "diet_type": ["non-vegetarian"], "health_index": 60},
98
- "pork": {"calories": 210, "fat": 13, "carbs": 0, "protein": 22, "category": "protein", "diet_type": ["non-vegetarian"], "health_index": 60},
99
- "turkey": {"calories": 175, "fat": 7, "carbs": 0, "protein": 25, "category": "protein", "diet_type": ["non-vegetarian"], "health_index": 75},
100
- "duck": {"calories": 280, "fat": 24, "carbs": 0, "protein": 19, "category": "protein", "diet_type": ["non-vegetarian"], "health_index": 55},
101
- "curry": {"calories": 300, "fat": 15, "carbs": 20, "protein": 25, "category": "neutral", "diet_type": ["non-vegetarian"], "health_index": 60},
102
- "stir fry": {"calories": 250, "fat": 10, "carbs": 15, "protein": 20, "category": "neutral", "diet_type": ["non-vegetarian"], "health_index": 70},
103
- "lasagna": {"calories": 330, "fat": 16, "carbs": 30, "protein": 18, "category": "neutral", "diet_type": ["non-vegetarian"], "health_index": 45},
104
- "risotto": {"calories": 310, "fat": 12, "carbs": 40, "protein": 8, "category": "neutral", "diet_type": ["vegetarian"], "health_index": 55},
105
- "paella": {"calories": 320, "fat": 12, "carbs": 38, "protein": 16, "category": "neutral", "diet_type": ["non-vegetarian"], "health_index": 60},
106
- "chips and salsa": {"calories": 170, "fat": 8, "carbs": 18, "protein": 2, "category": "junk", "diet_type": ["vegetarian", "vegan"], "health_index": 35},
107
- "nachos": {"calories": 350, "fat": 19, "carbs": 34, "protein": 8, "category": "junk", "diet_type": ["vegetarian"], "health_index": 20},
108
- "hot dog": {"calories": 290, "fat": 17, "carbs": 18, "protein": 11, "category": "junk", "diet_type": ["non-vegetarian"], "health_index": 15},
109
- "ice tea": {"calories": 90, "fat": 0, "carbs": 22, "protein": 0, "category": "neutral", "diet_type": ["vegetarian", "vegan"], "health_index": 50},
110
- "lemonade": {"calories": 120, "fat": 0, "carbs": 30, "protein": 0, "category": "neutral", "diet_type": ["vegetarian", "vegan"], "health_index": 40},
111
- "milk": {"calories": 120, "fat": 5, "carbs": 12, "protein": 8, "category": "neutral", "diet_type": ["vegetarian"], "health_index": 70},
112
- "almond milk": {"calories": 40, "fat": 3, "carbs": 1, "protein": 1, "category": "healthy", "diet_type": ["vegetarian", "vegan"], "health_index": 80},
113
- "coconut water": {"calories": 45, "fat": 0, "carbs": 10, "protein": 0, "category": "healthy", "diet_type": ["vegetarian", "vegan"], "health_index": 75},
114
- "energy drink": {"calories": 160, "fat": 0, "carbs": 40, "protein": 0, "category": "junk", "diet_type": ["vegetarian", "vegan"], "health_index": 5},
115
- "protein shake": {"calories": 170, "fat": 3, "carbs": 15, "protein": 25, "category": "protein", "diet_type": ["vegetarian"], "health_index": 75},
116
  }
117
  return food_data
118
 
119
- # Motivational quotes based on health score
120
- def get_motivational_quote(health_score, items):
121
- # Categories for quotes
122
- high_health_quotes = [
123
- "Your choices today reflect your health tomorrow! Keep making those smart food choices.",
124
- "Nutrition isn't about eating less; it's about eating right. You're doing great!",
125
- "Health is wealth, and you're investing wisely!",
126
- "Your body is a temple, and you're taking good care of it!",
127
- "The greatest wealth is health. You're rich in smart choices!",
128
- "Eating well is a form of self-respect. You're clearly loving yourself!",
129
- "You are what you eat - today, you're choosing to be vibrant and healthy!",
130
- "Let food be thy medicine. You're practicing this wisdom!"
131
- ]
132
-
133
- medium_health_quotes = [
134
- "Balance is not something you find, it's something you create. Keep working on your food choices!",
135
- "Small changes lead to big results. Keep making those mindful choices!",
136
- "Progress, not perfection. You're on the right track with your nutrition!",
137
- "Every healthy choice is a step in the right direction. Keep walking!",
138
- "Your diet doesn't have to be perfect to be healthy. Keep finding that balance!",
139
- "Healthy eating is a journey, not a destination. You're making progress!"
140
- ]
141
-
142
- low_health_quotes = [
143
- "Tomorrow is a new opportunity to nourish your body better.",
144
- "Every meal is a chance to make a healthier choice. Your next one can be better!",
145
- "Your body deserves the best. Consider what fuels you optimally!",
146
- "Small, consistent changes in your diet can transform your health over time.",
147
- "Eating well is loving yourself in action. Start with your very next meal!",
148
- "It's never too late to make a healthy choice. Your body will thank you!"
149
- ]
150
-
151
- # Check for specific food types to personalize quotes
152
- has_vegetables = any("vegetable" in item["name"].lower() for item in items)
153
- has_fruits = any("fruit" in item["name"].lower() or item["name"].lower() in ["apple", "banana", "orange", "berries"] for item in items)
154
- has_protein = any(item["nutrition"]["category"] == "protein" for item in items)
155
- has_water = any(item["name"].lower() == "water" for item in items)
156
- high_fat = sum(item["nutrition"]["fat"] for item in items) > 50
157
- high_sugar = any(item["name"].lower() in ["soda", "cake", "ice cream", "dessert", "cookies", "donut"] for item in items)
158
-
159
- # Personalized quotes based on specific food choices
160
- personalized_quotes = []
161
-
162
- if has_vegetables:
163
- personalized_quotes.append("Loving those vegetables! They're nature's multivitamin package.")
164
- if has_fruits:
165
- personalized_quotes.append("Fruits are nature's candy - sweet and nutritious. Great choice!")
166
- if has_protein and health_score > 60:
167
- personalized_quotes.append("Good job balancing your proteins - they're the building blocks of a healthy body!")
168
- if has_water:
169
- personalized_quotes.append("Staying hydrated is crucial for overall health. Water is always a smart choice!")
170
- if high_fat and health_score < 40:
171
- personalized_quotes.append("Consider foods with healthier fats like avocados, nuts, and olive oil next time.")
172
- if high_sugar and health_score < 40:
173
- personalized_quotes.append("Natural sugars from fruits can satisfy your sweet tooth while providing nutrients!")
174
-
175
- # Select quote based on health score
176
- if health_score >= 70:
177
- quotes = high_health_quotes
178
- elif health_score >= 40:
179
- quotes = medium_health_quotes
180
- else:
181
- quotes = low_health_quotes
182
-
183
- # If we have personalized quotes, mix them in
184
- if personalized_quotes:
185
- quotes.extend(personalized_quotes)
186
-
187
- return random.choice(quotes)
188
 
189
- # Keep track of user's food history
190
- user_history = {
191
- "meals": [],
192
- "health_scores": [],
193
- "timestamps": []
194
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
195
 
196
  # Initialize NLP model for food item recognition
197
  try:
@@ -200,7 +100,36 @@ except Exception as e:
200
  print(f"Error loading NLP model: {e}")
201
  food_classifier = None
202
 
203
- # Enhanced OCR function with image preprocessing
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
204
  def extract_text_from_image(image):
205
  try:
206
  # If image is a URL, download it
@@ -210,52 +139,31 @@ def extract_text_from_image(image):
210
  else:
211
  img = Image.fromarray(image) if isinstance(image, np.ndarray) else image
212
 
213
- # Apply image preprocessing to enhance OCR quality
214
- # Convert to grayscale
215
- img_gray = img.convert('L')
216
-
217
- # Increase contrast
218
- enhancer = ImageEnhance.Contrast(img_gray)
219
- img_contrast = enhancer.enhance(2.0)
220
 
221
- # Apply slight blur to reduce noise
222
- img_blur = img_contrast.filter(ImageFilter.GaussianBlur(1))
 
223
 
224
- # Apply threshold to make text stand out
225
- threshold = 150
226
- img_binary = img_blur.point(lambda p: p > threshold and 255)
227
 
228
- # Run OCR on both processed and original images and combine results
229
- text_processed = pytesseract.image_to_string(img_binary)
230
- text_original = pytesseract.image_to_string(img)
231
-
232
- # Combine results (processed image might be better for some cases, original for others)
233
- combined_text = text_processed if len(text_processed) > len(text_original) else text_original
234
-
235
- # Try different OCR configurations if results are poor
236
- if len(combined_text.strip()) < 20: # Arbitrary threshold for "poor results"
237
- # Try page segmentation mode 4 (assume a single column of text)
238
- text_alt = pytesseract.image_to_string(img, config='--psm 4')
239
- if len(text_alt.strip()) > len(combined_text.strip()):
240
- combined_text = text_alt
241
-
242
- # Try page segmentation mode 6 (assume a single uniform block of text)
243
- text_alt = pytesseract.image_to_string(img, config='--psm 6')
244
- if len(text_alt.strip()) > len(combined_text.strip()):
245
- combined_text = text_alt
246
-
247
- return combined_text
248
  except Exception as e:
249
  return f"Error extracting text: {str(e)}"
250
 
251
- # Enhanced extraction of food items from the OCR text
252
  def extract_food_items(text):
253
  # Common patterns found in restaurant bills
 
254
  lines = text.split('\n')
255
  food_items = []
256
 
257
  # Regular patterns for food items in bills
258
- price_pattern = r'\$?\d+\.\d{2}'
 
259
 
260
  for line in lines:
261
  line = line.strip()
@@ -263,56 +171,622 @@ def extract_food_items(text):
263
  continue
264
 
265
  # Skip lines that look like totals or headers
266
- 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|#)',
267
- line.lower()):
 
 
 
 
 
 
268
  continue
269
 
270
  # If line contains a price, it's likely a food item
271
  if re.search(price_pattern, line):
272
  # Extract the item name (everything before the price)
273
- item_match = re.split(price_pattern, line)[0].strip()
274
- if item_match and len(item_match) > 1: # Ensure it's not just whitespace
275
- # Clean up the item name (remove quantities, etc.)
276
- cleaned_item = re.sub(r'^\d+\s*[xX]?\s*', '', item_match) # Remove quantities like "2 x" or "2"
277
- cleaned_item = re.sub(r'\d+\s*oz\s*', '', cleaned_item) # Remove sizes like "12oz"
278
- cleaned_item = re.sub(r'\(\w+\)', '', cleaned_item) # Remove parentheses
279
- food_items.append(cleaned_item.strip().lower())
280
-
281
- # If we couldn't find food items using the price pattern, try to extract words that might be food
 
 
 
 
 
 
 
 
 
 
 
 
 
 
282
  if not food_items:
283
- # Build a potential list of food words
284
- potential_foods = []
285
  for line in lines:
286
- # Clean and extract potential food names
287
- cleaned_line = re.sub(r'\d+\.*\d*', ' ', line) # Remove numbers
288
- words = re.findall(r'\b[a-zA-Z]{3,}\b', cleaned_line.lower())
289
-
290
- # Add words that could be food items
291
- for word in words:
292
- if word not in ["total", "subtotal", "tax", "tip", "amount", "due", "cash", "credit",
293
- "card", "date", "time", "server", "table", "guest", "check", "receipt"]:
294
- potential_foods.append(word)
295
-
296
- # Use NLP to identify which words are likely food items
297
- if potential_foods and food_classifier:
298
  try:
299
- # Process words in small batches to avoid overloading the model
300
- batch_size = 10
301
- for i in range(0, len(potential_foods), batch_size):
302
- batch = potential_foods[i:i+batch_size]
303
- for word in batch:
304
- # Check if word is directly in our database
305
- if word in nutrition_data:
306
- food_items.append(word)
307
- continue
308
-
309
- # Use zero-shot classification
310
- result = food_classifier(word, ["food item", "not food"])
311
- if result["scores"][0] > 0.7 and result["labels"][0] == "food item":
312
- food_items.append(word)
313
-
314
- # Look for multi-word food items
315
- for i in range(len(lines)):
316
- line = lines[i].lower()
317
- for food_name in nutrition_data:
318
- if " " in food_name and food_name
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
  import json
8
  import os
9
  import random
10
+ import datetime
 
 
 
11
  from transformers import pipeline
12
  import requests
13
  from io import BytesIO
14
+ import matplotlib.pyplot as plt
15
+ import seaborn as sns
16
+ import cv2
17
 
18
  # Load nutrition database
19
  def load_nutrition_data():
20
+ # Create a basic food database with nutritional information
21
+ # In a production environment, you might want to use a more comprehensive database
22
  food_data = {
23
+ "pizza": {"calories": 285, "fat": 10, "carbs": 36, "protein": 12, "category": "junk"},
24
+ "burger": {"calories": 354, "fat": 17, "carbs": 40, "protein": 15, "category": "junk"},
25
+ "fries": {"calories": 312, "fat": 15, "carbs": 41, "protein": 3, "category": "junk"},
26
+ "salad": {"calories": 100, "fat": 7, "carbs": 5, "protein": 2, "category": "healthy"},
27
+ "soda": {"calories": 140, "fat": 0, "carbs": 39, "protein": 0, "category": "junk"},
28
+ "juice": {"calories": 110, "fat": 0, "carbs": 26, "protein": 0, "category": "neutral"},
29
+ "water": {"calories": 0, "fat": 0, "carbs": 0, "protein": 0, "category": "healthy"},
30
+ "pasta": {"calories": 200, "fat": 2, "carbs": 42, "protein": 7, "category": "neutral"},
31
+ "steak": {"calories": 300, "fat": 15, "carbs": 0, "protein": 30, "category": "protein"},
32
+ "chicken": {"calories": 220, "fat": 8, "carbs": 0, "protein": 40, "category": "protein"},
33
+ "fish": {"calories": 180, "fat": 5, "carbs": 0, "protein": 30, "category": "healthy"},
34
+ "rice": {"calories": 130, "fat": 0, "carbs": 28, "protein": 3, "category": "neutral"},
35
+ "beer": {"calories": 154, "fat": 0, "carbs": 13, "protein": 1, "category": "junk"},
36
+ "wine": {"calories": 125, "fat": 0, "carbs": 4, "protein": 0, "category": "neutral"},
37
+ "ice cream": {"calories": 207, "fat": 11, "carbs": 24, "protein": 4, "category": "junk"},
38
+ "coffee": {"calories": 5, "fat": 0, "carbs": 0, "protein": 0, "category": "healthy"},
39
+ "sandwich": {"calories": 250, "fat": 8, "carbs": 30, "protein": 15, "category": "neutral"},
40
+ "soup": {"calories": 120, "fat": 3, "carbs": 12, "protein": 10, "category": "healthy"},
41
+ "cake": {"calories": 350, "fat": 18, "carbs": 45, "protein": 4, "category": "junk"},
42
+ "bread": {"calories": 80, "fat": 1, "carbs": 15, "protein": 3, "category": "neutral"},
43
+ "chocolate": {"calories": 200, "fat": 12, "carbs": 20, "protein": 2, "category": "junk"},
44
+ "milkshake": {"calories": 300, "fat": 10, "carbs": 50, "protein": 9, "category": "junk"},
45
+ "dessert": {"calories": 280, "fat": 14, "carbs": 35, "protein": 5, "category": "junk"},
46
+ "smoothie": {"calories": 170, "fat": 2, "carbs": 35, "protein": 5, "category": "neutral"},
47
+ "tea": {"calories": 2, "fat": 0, "carbs": 0, "protein": 0, "category": "healthy"},
48
+ "appetizer": {"calories": 200, "fat": 12, "carbs": 15, "protein": 8, "category": "neutral"},
49
+ "noodles": {"calories": 190, "fat": 2, "carbs": 40, "protein": 7, "category": "neutral"},
50
+ "taco": {"calories": 210, "fat": 10, "carbs": 22, "protein": 12, "category": "neutral"},
51
+ "burrito": {"calories": 350, "fat": 12, "carbs": 50, "protein": 15, "category": "neutral"},
52
+ "wrap": {"calories": 220, "fat": 5, "carbs": 30, "protein": 13, "category": "neutral"},
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
  }
54
  return food_data
55
 
56
+ # Initialize nutrition data
57
+ nutrition_data = load_nutrition_data()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
58
 
59
+ # Load motivational quotes based on health score ranges
60
+ def load_motivational_quotes():
61
+ quotes = {
62
+ "excellent": [
63
+ "You're making excellent food choices! Your body thanks you for the premium fuel.",
64
+ "Fantastic choices! You're investing in your long-term health with every bite.",
65
+ "Your healthy eating habits today are building your stronger body for tomorrow.",
66
+ "Impressive meal choices! You're mastering the art of nutritious eating.",
67
+ "You're a nutrition champion! These balanced choices will energize your day."
68
+ ],
69
+ "good": [
70
+ "Good job balancing nutrition! Small improvements can take you to the next level.",
71
+ "You're on the right track with your food choices. Keep building those healthy habits!",
72
+ "Nice work choosing a fairly balanced meal. Your body appreciates the consideration.",
73
+ "Your meal choices show you care about your health. Keep that momentum going!",
74
+ "Good balance of nutrients in this meal. Remember: consistency is key to health."
75
+ ],
76
+ "moderate": [
77
+ "This meal has some nutritional bright spots. Consider adding more protein next time.",
78
+ "Balance is a journey. Try adding more vegetables to your next meal.",
79
+ "Everyone indulges sometimes. Tomorrow is a new opportunity for nourishing choices.",
80
+ "Consider this meal a starting point. Small improvements add up to big health benefits.",
81
+ "Moderation is key. Try balancing this meal with healthier choices later today."
82
+ ],
83
+ "poor": [
84
+ "Your body deserves premium fuel. Consider more nutrient-dense options next time.",
85
+ "One meal doesn't define your health journey. Your next choice can be a healthier one.",
86
+ "We all have indulgences. Balance this meal with nutritious choices for your next one.",
87
+ "Small steps lead to big changes. Consider adding vegetables to your next meal.",
88
+ "Remember: food is fuel. Choose options that will energize rather than drain you."
89
+ ]
90
+ }
91
+ return quotes
92
+
93
+ # Initialize motivational quotes
94
+ motivational_quotes = load_motivational_quotes()
95
 
96
  # Initialize NLP model for food item recognition
97
  try:
 
100
  print(f"Error loading NLP model: {e}")
101
  food_classifier = None
102
 
103
+ # Helper function to preprocess the image for better OCR results
104
+ def preprocess_image(image):
105
+ # Convert to numpy array if needed
106
+ if not isinstance(image, np.ndarray):
107
+ image = np.array(image)
108
+
109
+ try:
110
+ # Convert to grayscale
111
+ if len(image.shape) == 3 and image.shape[2] == 3:
112
+ gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
113
+ else:
114
+ gray = image
115
+
116
+ # Apply adaptive thresholding
117
+ thresh = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
118
+ cv2.THRESH_BINARY, 11, 2)
119
+
120
+ # Denoise
121
+ denoised = cv2.fastNlMeansDenoising(thresh, None, 10, 7, 21)
122
+
123
+ # Convert back to PIL image for tesseract
124
+ enhanced_img = Image.fromarray(denoised)
125
+
126
+ return enhanced_img
127
+ except Exception as e:
128
+ print(f"Error preprocessing image: {e}")
129
+ # If preprocessing fails, return the original image
130
+ return Image.fromarray(image) if isinstance(image, np.ndarray) else image
131
+
132
+ # OCR function to extract text from bill image with enhanced image processing
133
  def extract_text_from_image(image):
134
  try:
135
  # If image is a URL, download it
 
139
  else:
140
  img = Image.fromarray(image) if isinstance(image, np.ndarray) else image
141
 
142
+ # Preprocess the image for better OCR results
143
+ preprocessed_img = preprocess_image(img)
 
 
 
 
 
144
 
145
+ # Use tesseract with optimized configuration
146
+ custom_config = r'--oem 3 --psm 6 -l eng'
147
+ text = pytesseract.image_to_string(preprocessed_img, config=custom_config)
148
 
149
+ # Fallback to original image if the result is too short
150
+ if len(text.strip()) < 20:
151
+ text = pytesseract.image_to_string(img, config=custom_config)
152
 
153
+ return text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
154
  except Exception as e:
155
  return f"Error extracting text: {str(e)}"
156
 
157
+ # Extract food items from the OCR text with improved pattern recognition
158
  def extract_food_items(text):
159
  # Common patterns found in restaurant bills
160
+ # Look for items with prices
161
  lines = text.split('\n')
162
  food_items = []
163
 
164
  # Regular patterns for food items in bills
165
+ # More comprehensive price pattern to catch various formats
166
+ price_pattern = r'(\$?\d+\.\d{2}|\$?\d+\,\d{2}|\$?\d+)'
167
 
168
  for line in lines:
169
  line = line.strip()
 
171
  continue
172
 
173
  # Skip lines that look like totals or headers
174
+ skip_keywords = [
175
+ 'total', 'subtotal', 'tax', 'gratuity', 'tip', 'service', 'amount', 'due', 'change',
176
+ 'cash', 'credit', 'card', 'payment', 'date', 'time', 'server', 'check', 'table',
177
+ 'guest', 'invoice', 'receipt', 'bill', 'order', 'tel', 'phone', 'address',
178
+ 'thank you', 'restaurant', 'cafe', 'bar', 'grill', 'kitchen'
179
+ ]
180
+
181
+ if any(keyword in line.lower() for keyword in skip_keywords):
182
  continue
183
 
184
  # If line contains a price, it's likely a food item
185
  if re.search(price_pattern, line):
186
  # Extract the item name (everything before the price)
187
+ item_parts = re.split(price_pattern, line)
188
+ if item_parts and len(item_parts) > 1:
189
+ item_match = item_parts[0].strip()
190
+ if item_match and len(item_match) > 1: # Ensure it's not just whitespace
191
+ # Clean up the item name (remove quantities, etc.)
192
+ cleaned_item = re.sub(r'^\d+\s*[xX]?\s*', '', item_match) # Remove quantities like "2 x" or "2"
193
+ cleaned_item = re.sub(r'\d+\s*oz\s*', '', cleaned_item) # Remove sizes like "12oz"
194
+ cleaned_item = re.sub(r'\(\w+\)', '', cleaned_item) # Remove parentheses
195
+
196
+ # Filter out very short items that are likely not food
197
+ if len(cleaned_item.strip()) > 2:
198
+ food_items.append(cleaned_item.strip().lower())
199
+
200
+ # Enhanced approach: look for menu item formats (even without prices)
201
+ item_pattern = r'^\s*\d+\.\s+(.+)$' # Matches numbered items like "1. Burger"
202
+ for line in lines:
203
+ match = re.search(item_pattern, line)
204
+ if match:
205
+ food_item = match.group(1).strip().lower()
206
+ if len(food_item) > 2 and food_item not in food_items:
207
+ food_items.append(food_item)
208
+
209
+ # If we couldn't find food items using patterns, try to extract words that might be food
210
  if not food_items:
211
+ # Use NLP to identify potential food items
212
+ common_words = []
213
  for line in lines:
214
+ words = re.findall(r'\b[a-zA-Z]{3,}\b', line.lower())
215
+ common_words.extend(words)
216
+
217
+ # Filter by known food terms if we have enough words
218
+ if common_words and food_classifier:
 
 
 
 
 
 
 
219
  try:
220
+ candidate_foods = list(set(common_words))
221
+ if candidate_foods:
222
+ food_items = identify_food_items_with_nlp(candidate_foods)
223
+ except Exception as e:
224
+ print(f"Error identifying food items with NLP: {e}")
225
+
226
+ return food_items
227
+
228
+ # Use NLP to identify food items from candidate text with improved confidence threshold
229
+ def identify_food_items_with_nlp(candidate_items, threshold=0.65):
230
+ food_items = []
231
+
232
+ # List of candidate food categories
233
+ food_categories = ["food", "drink", "meal", "dish", "beverage", "dessert", "snack"]
234
+
235
+ for item in candidate_items:
236
+ if item in nutrition_data: # Directly in our database
237
+ food_items.append(item)
238
+ continue
239
+
240
+ # Use zero-shot classification to check if the item is a food
241
+ try:
242
+ result = food_classifier(item, food_categories)
243
+ if result["scores"][0] > threshold:
244
+ food_items.append(item)
245
+ except Exception as e:
246
+ print(f"Error classifying {item}: {e}")
247
+
248
+ return food_items
249
+
250
+ # Match extracted food items to our nutrition database with improved fuzzy matching
251
+ def match_food_to_nutrition(food_items):
252
+ matched_items = []
253
+
254
+ for item in food_items:
255
+ # Direct match
256
+ if item in nutrition_data:
257
+ matched_items.append({"name": item, "nutrition": nutrition_data[item]})
258
+ continue
259
+
260
+ # Improved matching logic - word-based matching and ngram similarity
261
+ best_match = None
262
+ max_score = 0
263
+
264
+ # Split the item into words for better matching
265
+ item_words = set(item.split())
266
+
267
+ for db_food in nutrition_data:
268
+ # Calculate word overlap
269
+ db_food_words = set(db_food.split())
270
+ if item_words and db_food_words:
271
+ overlap = len(item_words.intersection(db_food_words))
272
+ score = overlap / max(len(item_words), len(db_food_words))
273
+
274
+ # Boost score if one string contains the other
275
+ if db_food in item or item in db_food:
276
+ score += 0.3
277
+
278
+ if score > max_score:
279
+ max_score = score
280
+ best_match = db_food
281
+
282
+ # Only match if the score is reasonably high
283
+ if best_match and max_score > 0.3:
284
+ matched_items.append({"name": item, "matched_as": best_match, "nutrition": nutrition_data[best_match]})
285
+
286
+ return matched_items
287
+
288
+ # Calculate nutritional totals and health score with more sophisticated scoring
289
+ def calculate_nutrition_and_health_score(matched_items):
290
+ if not matched_items:
291
+ return {
292
+ "total_calories": 0,
293
+ "total_fat": 0,
294
+ "total_carbs": 0,
295
+ "total_protein": 0,
296
+ "health_score": 0,
297
+ "health_assessment": "No food items detected",
298
+ "items": [],
299
+ "macronutrient_ratios": {"protein": 0, "fat": 0, "carbs": 0}
300
+ }
301
+
302
+ # Calculate totals
303
+ total_calories = sum(item["nutrition"]["calories"] for item in matched_items)
304
+ total_fat = sum(item["nutrition"]["fat"] for item in matched_items)
305
+ total_carbs = sum(item["nutrition"]["carbs"] for item in matched_items)
306
+ total_protein = sum(item["nutrition"]["protein"] for item in matched_items)
307
+
308
+ # Calculate macronutrient ratios
309
+ total_nutrient_calories = (total_protein * 4) + (total_carbs * 4) + (total_fat * 9)
310
+ if total_nutrient_calories > 0:
311
+ protein_ratio = (total_protein * 4) / total_nutrient_calories
312
+ carbs_ratio = (total_carbs * 4) / total_nutrient_calories
313
+ fat_ratio = (total_fat * 9) / total_nutrient_calories
314
+ else:
315
+ protein_ratio = carbs_ratio = fat_ratio = 0
316
+
317
+ macronutrient_ratios = {
318
+ "protein": round(protein_ratio * 100, 1),
319
+ "fat": round(fat_ratio * 100, 1),
320
+ "carbs": round(carbs_ratio * 100, 1)
321
+ }
322
+
323
+ # Count categories
324
+ categories = [item["nutrition"]["category"] for item in matched_items]
325
+ category_counts = {
326
+ "healthy": categories.count("healthy"),
327
+ "protein": categories.count("protein"),
328
+ "neutral": categories.count("neutral"),
329
+ "junk": categories.count("junk")
330
+ }
331
+
332
+ # Calculate health score (0-100) with more sophisticated algorithm
333
+ health_score = 0
334
+ total_items = len(matched_items)
335
+
336
+ # Base score from category distribution (50% of total score)
337
+ if total_items > 0:
338
+ health_score += (category_counts["healthy"] / total_items) * 25
339
+ health_score += (category_counts["protein"] / total_items) * 20
340
+ health_score += (category_counts["neutral"] / total_items) * 10
341
+
342
+ # Macronutrient balance (50% of total score)
343
+ if total_nutrient_calories > 0:
344
+ # Protein score (ideal: 20-30%)
345
+ if protein_ratio >= 0.2 and protein_ratio <= 0.3:
346
+ health_score += 15
347
+ elif protein_ratio > 0.15 and protein_ratio < 0.35:
348
+ health_score += 10
349
+ elif protein_ratio > 0.1:
350
+ health_score += 5
351
+
352
+ # Fat score (ideal: 20-35%)
353
+ if fat_ratio >= 0.2 and fat_ratio <= 0.35:
354
+ health_score += 15
355
+ elif fat_ratio > 0.15 and fat_ratio < 0.4:
356
+ health_score += 10
357
+ elif fat_ratio < 0.45:
358
+ health_score += 5
359
+
360
+ # Carb score (ideal: 45-65%)
361
+ if carbs_ratio >= 0.45 and carbs_ratio <= 0.65:
362
+ health_score += 15
363
+ elif carbs_ratio > 0.35 and carbs_ratio < 0.7:
364
+ health_score += 10
365
+ elif carbs_ratio > 0.25 and carbs_ratio < 0.75:
366
+ health_score += 5
367
+
368
+ # Cap between 0-100
369
+ health_score = max(0, min(100, health_score))
370
+
371
+ # Get appropriate motivational quote based on health score
372
+ motivational_quote = get_motivational_quote(health_score)
373
+
374
+ # Generate detailed health assessment
375
+ if health_score > 75:
376
+ assessment = f"Excellent! Your meal of {total_calories} calories shows thoughtful, balanced choices."
377
+ assessment_category = "excellent"
378
+ elif health_score > 50:
379
+ assessment = f"Good job! Your meal of {total_calories} calories has decent nutritional balance."
380
+ assessment_category = "good"
381
+ elif health_score > 25:
382
+ assessment = f"This {total_calories}-calorie meal has some nutritional gaps. Consider more balance next time."
383
+ assessment_category = "moderate"
384
+ else:
385
+ assessment = f"Your {total_calories}-calorie meal is primarily composed of less nutritious options. Try incorporating more whole foods."
386
+ assessment_category = "poor"
387
+
388
+ # Prepare detailed items list
389
+ items_details = []
390
+ for item in matched_items:
391
+ name = item["name"]
392
+ if "matched_as" in item:
393
+ name = f"{name} (matched as {item['matched_as']})"
394
+
395
+ items_details.append({
396
+ "name": name,
397
+ "calories": item["nutrition"]["calories"],
398
+ "fat": item["nutrition"]["fat"],
399
+ "carbs": item["nutrition"]["carbs"],
400
+ "protein": item["nutrition"]["protein"],
401
+ "category": item["nutrition"]["category"]
402
+ })
403
+
404
+ # Calculate the timestamp for meal tracking
405
+ timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
406
+
407
+ # Return comprehensive results
408
+ return {
409
+ "total_calories": total_calories,
410
+ "total_fat": total_fat,
411
+ "total_carbs": total_carbs,
412
+ "total_protein": total_protein,
413
+ "health_score": round(health_score, 1),
414
+ "health_assessment": assessment,
415
+ "assessment_category": assessment_category,
416
+ "motivational_quote": motivational_quote,
417
+ "items": items_details,
418
+ "macronutrient_ratios": macronutrient_ratios,
419
+ "timestamp": timestamp
420
+ }
421
+
422
+ # Get a motivational quote based on health score
423
+ def get_motivational_quote(health_score):
424
+ if health_score > 75:
425
+ category = "excellent"
426
+ elif health_score > 50:
427
+ category = "good"
428
+ elif health_score > 25:
429
+ category = "moderate"
430
+ else:
431
+ category = "poor"
432
+
433
+ return random.choice(motivational_quotes[category])
434
+
435
+ # Generate visualizations based on nutritional analysis
436
+ def generate_visualizations(nutrition_results):
437
+ if not nutrition_results["items"]:
438
+ return None, None, None
439
+
440
+ try:
441
+ # Create figures with white background for better visibility
442
+ plt.style.use('default')
443
+
444
+ # Macronutrient distribution pie chart
445
+ fig1, ax1 = plt.subplots(figsize=(6, 4), facecolor='white')
446
+ labels = ['Protein', 'Carbs', 'Fat']
447
+ sizes = [
448
+ nutrition_results['macronutrient_ratios']['protein'],
449
+ nutrition_results['macronutrient_ratios']['carbs'],
450
+ nutrition_results['macronutrient_ratios']['fat']
451
+ ]
452
+ colors = ['#4CAF50', '#2196F3', '#FFC107']
453
+ explode = (0.1, 0, 0) # explode the protein slice
454
+
455
+ ax1.pie(sizes, explode=explode, labels=labels, colors=colors, autopct='%1.1f%%',
456
+ shadow=True, startangle=90)
457
+ ax1.axis('equal') # Equal aspect ratio ensures that pie is drawn as a circle
458
+ plt.title('Macronutrient Distribution')
459
+ plt.tight_layout()
460
+
461
+ # Item comparison bar chart
462
+ fig2, ax2 = plt.subplots(figsize=(8, 5), facecolor='white')
463
+
464
+ # Limit to top 6 items for readability
465
+ items = nutrition_results['items'][:6] if len(nutrition_results['items']) > 6 else nutrition_results['items']
466
+
467
+ # Prepare data
468
+ item_names = [item['name'].split(' (matched')[0] for item in items] # Just use the first part
469
+ item_names = [name[:12] + '...' if len(name) > 15 else name for name in item_names] # Truncate long names
470
+ calories = [item['calories'] for item in items]
471
+
472
+ # Color based on category
473
+ category_colors = {
474
+ 'healthy': '#4CAF50', # Green
475
+ 'protein': '#2196F3', # Blue
476
+ 'neutral': '#9E9E9E', # Grey
477
+ 'junk': '#F44336' # Red
478
+ }
479
+ bar_colors = [category_colors[item['category']] for item in items]
480
+
481
+ # Create bars
482
+ bars = ax2.barh(range(len(item_names)), calories, color=bar_colors)
483
+ ax2.set_yticks(range(len(item_names)))
484
+ ax2.set_yticklabels(item_names)
485
+ ax2.set_xlabel('Calories')
486
+ ax2.set_title('Calorie Content by Food Item')
487
+
488
+ # Add value labels
489
+ for i, bar in enumerate(bars):
490
+ ax2.text(bar.get_width() + 5, bar.get_y() + bar.get_height()/2,
491
+ str(calories[i]) + ' cal',
492
+ va='center', fontsize=8)
493
+
494
+ plt.tight_layout()
495
+
496
+ # Health score gauge chart
497
+ fig3, ax3 = plt.subplots(figsize=(6, 3), facecolor='white')
498
+
499
+ # Create health score gauge
500
+ score = nutrition_results['health_score']
501
+
502
+ # Create a horizontal bar for the gauge
503
+ cmap = plt.cm.RdYlGn # Red-Yellow-Green colormap
504
+ norm = plt.Normalize(0, 100)
505
+
506
+ # Create gradient background
507
+ for i in range(100):
508
+ ax3.barh(0, 1, left=i, height=0.5, color=cmap(norm(i)), alpha=0.7)
509
+
510
+ # Add marker for the score
511
+ ax3.barh(0, 3, left=score-1.5, height=0.7, color='black')
512
+
513
+ # Remove axes and add labels
514
+ ax3.set_yticks([])
515
+ ax3.set_xticks([0, 25, 50, 75, 100])
516
+ ax3.set_xlim(0, 100)
517
+ ax3.set_title(f'Health Score: {score}/100')
518
+
519
+ plt.tight_layout()
520
+
521
+ # Convert figures to images
522
+ macros_chart = fig_to_image(fig1)
523
+ items_chart = fig_to_image(fig2)
524
+ score_chart = fig_to_image(fig3)
525
+
526
+ plt.close(fig1)
527
+ plt.close(fig2)
528
+ plt.close(fig3)
529
+
530
+ return macros_chart, items_chart, score_chart
531
+
532
+ except Exception as e:
533
+ print(f"Error generating visualizations: {e}")
534
+ return None, None, None
535
+
536
+ # Helper function to convert matplotlib figure to image
537
+ def fig_to_image(fig):
538
+ from io import BytesIO
539
+
540
+ buf = BytesIO()
541
+ fig.savefig(buf, format='png', dpi=100)
542
+ buf.seek(0)
543
+ return buf
544
+
545
+ # Format nutritional analysis results with enhanced styling
546
+ def format_results(nutrition_results):
547
+ if not nutrition_results["items"]:
548
+ return "No food items were detected in the bill. Please try a clearer image or check that the image shows food items clearly."
549
+
550
+ # Basic summary
551
+ result = f"## Nutrition Summary\n\n"
552
+
553
+ # Health score & assessment
554
+ result += f"**Health Score:** {nutrition_results['health_score']}/100\n\n"
555
+ result += f"**Assessment:** {nutrition_results['health_assessment']}\n\n"
556
+
557
+ # Motivational quote
558
+ result += f"**πŸ’ͺ Motivation:** {nutrition_results['motivational_quote']}\n\n"
559
+
560
+ # Macronutrient breakdown
561
+ result += "### Nutritional Totals\n"
562
+ result += f"- **Calories:** {nutrition_results['total_calories']} kcal\n"
563
+ result += f"- **Protein:** {nutrition_results['total_protein']}g ({nutrition_results['macronutrient_ratios']['protein']}%)\n"
564
+ result += f"- **Carbs:** {nutrition_results['total_carbs']}g ({nutrition_results['macronutrient_ratios']['carbs']}%)\n"
565
+ result += f"- **Fat:** {nutrition_results['total_fat']}g ({nutrition_results['macronutrient_ratios']['fat']}%)\n\n"
566
+
567
+ # Item breakdown
568
+ result += "### Detected Food Items\n"
569
+ for item in nutrition_results["items"]:
570
+ # Emoji based on food category
571
+ emoji = "πŸ₯¦" if item['category'] == "healthy" else "πŸ—" if item['category'] == "protein" else "βš–οΈ" if item['category'] == "neutral" else "🍰"
572
+
573
+ result += f"- {emoji} **{item['name'].title()}**: {item['calories']} kcal ({item['fat']}g fat, {item['carbs']}g carbs, {item['protein']}g protein)\n"
574
+
575
+ # Recommendations
576
+ result += "\n### Recommendations\n"
577
+
578
+ # Targeted recommendations based on nutrient analysis
579
+ if nutrition_results['macronutrient_ratios']['protein'] < 15:
580
+ result += "- πŸ₯© **Protein Boost Needed:** Consider adding more protein sources like chicken, fish, tofu, or legumes.\n"
581
+
582
+ if nutrition_results['macronutrient_ratios']['fat'] > 35:
583
+ result += "- πŸ₯‘ **Fat Watch:** This meal is high in fat. Try reducing fried foods and choosing leaner options.\n"
584
+
585
+ if nutrition_results['macronutrient_ratios']['carbs'] > 60:
586
+ result += "- 🍞 **Carb Heavy:** Consider reducing refined carbs and choosing more vegetables and proteins.\n"
587
+
588
+ # Check total calories
589
+ if nutrition_results["total_calories"] > 900 and len(nutrition_results["items"]) <= 2:
590
+ result += "- πŸ“Š **Portion Alert:** This meal contains high calories in few items. Consider portion control.\n"
591
+
592
+ # If no specific recommendations, add a general one
593
+ if "Consider" not in result:
594
+ result += "- 🌟 **Keep it Up:** Your meal is well-balanced! Maintain this approach to nutrition.\n"
595
+
596
+ return result
597
+
598
+ # Main function to process the image and return analysis
599
+ def analyze_restaurant_bill(image):
600
+ if image is None:
601
+ return "Please upload an image of your restaurant bill to analyze.", None, None, None
602
+
603
+ # Extract text from image using OCR
604
+ text = extract_text_from_image(image)
605
+ if text.startswith("Error"):
606
+ return f"OCR failed: {text}", None, None, None
607
+
608
+ # Extract food items from the text
609
+ food_items = extract_food_items(text)
610
+ if not food_items:
611
+ return "No food items detected in the bill. Please try a clearer image or manually enter food items.", None, None, None
612
+
613
+ # Match food items to nutrition database
614
+ matched_items = match_food_to_nutrition(food_items)
615
+
616
+ # Calculate nutritional information and health score
617
+ nutrition_results = calculate_nutrition_and_health_score(matched_items)
618
+
619
+ # Format results as a readable string
620
+ formatted_results = format_results(nutrition_results)
621
+
622
+ # Generate visualizations
623
+ macro_chart, items_chart, score_chart = generate_visualizations(nutrition_results)
624
+
625
+ return formatted_results, macro_chart, items_chart, score_chart
626
+
627
+ # Function to process manually entered food items
628
+ def analyze_manual_food_items(food_items_text):
629
+ if not food_items_text.strip():
630
+ return "Please enter some food items to analyze.", None, None, None
631
+
632
+ # Parse the text into a list of food items
633
+ food_items = [item.strip().lower() for item in food_items_text.split(',') if item.strip()]
634
+
635
+ # Match food items to nutrition database
636
+ matched_items = match_food_to_nutrition(food_items)
637
+
638
+ if not matched_items:
639
+ return "None of the entered items could be matched to our nutrition database. Please try different foods or check spelling.", None, None, None
640
+
641
+ # Calculate nutritional information and health score
642
+ nutrition_results = calculate_nutrition_and_health_score(matched_items)
643
+
644
+ # Format results as a readable string
645
+ formatted_results = format_results(nutrition_results)
646
+
647
+ # Generate visualizations
648
+ macro_chart, items_chart, score_chart = generate_visualizations(nutrition_results)
649
+
650
+ return formatted_results, macro_chart, items_chart, score_chart
651
+
652
+ # Save meal data to file
653
+ def save_meal_data(nutrition_results, log_file="meal_log.json"):
654
+ if not nutrition_results or not nutrition_results.get("items"):
655
+ return False
656
+
657
+ # Create simplified data for logging
658
+ log_entry = {
659
+ "timestamp": nutrition_results["timestamp"],
660
+ "total_calories": nutrition_results["total_calories"],
661
+ "health_score": nutrition_results["health_score"],
662
+ "macros": {
663
+ "protein": nutrition_results["total_protein"],
664
+ "carbs": nutrition_results["total_carbs"],
665
+ "fat": nutrition_results["total_fat"]
666
+ },
667
+ "items": [item["name"] for item in nutrition_results["items"]]
668
+ }
669
+
670
+ # Load existing log if available
671
+ try:
672
+ if os.path.exists(log_file):
673
+ with open(log_file, 'r') as f:
674
+ log_data = json.load(f)
675
+ else:
676
+ log_data = []
677
+ except Exception:
678
+ log_data = []
679
+
680
+ # Add new entry
681
+ log_data.append(log_entry)
682
+
683
+ # Save updated log
684
+ try:
685
+ with open(log_file, 'w') as f:
686
+ json.dump(log_data, f, indent=2)
687
+ return True
688
+ except Exception:
689
+ return False
690
+
691
+ # Setup Gradio interface with improved UI and layout
692
+ def create_interface():
693
+ with gr.Blocks(theme=gr.themes.Soft(primary_hue="green"), title="Restaurant Bill Nutrition Analyzer") as demo:
694
+ gr.Markdown(
695
+ """
696
+ # 🧾 Restaurant Bill Nutrition Analyzer πŸ₯—
697
+
698
+ Upload a photo of your restaurant bill or receipt, and this app will:
699
+
700
+ 1. πŸ” Extract food items from the image using OCR
701
+ 2. πŸ“Š Match them to a nutrition database
702
+ 3. πŸ“‹ Calculate total calories, macronutrients, and a health score
703
+ 4. πŸ“ˆ Provide visualizations and personalized recommendations
704
+
705
+ Alternatively, you can manually enter food items separated by commas.
706
+ """
707
+ )
708
+
709
+ with gr.Tabs():
710
+ with gr.TabItem("πŸ“Έ Analyze Restaurant Bill"):
711
+ with gr.Row():
712
+ with gr.Column(scale=1):
713
+ upload_image = gr.Image(
714
+ label="Upload Restaurant Bill Image",
715
+ type="numpy",
716
+ height=300
717
+ )
718
+ analyze_button = gr.Button("Analyze Bill", variant="primary")
719
+
720
+ with gr.Column(scale=2):
721
+ output_text = gr.Markdown(label="Analysis Results")
722
+
723
+ with gr.Row():
724
+ with gr.Column():
725
+ macros_chart = gr.Image(label="Macronutrient Distribution")
726
+ with gr.Column():
727
+ score_chart = gr.Image(label="Health Score")
728
+ with gr.Column():
729
+ items_chart = gr.Image(label="Calories by Item")
730
+
731
+ with gr.TabItem("✍️ Manual Food Entry"):
732
+ with gr.Row():
733
+ with gr.Column(scale=1):
734
+ food_input = gr.Textbox(
735
+ label="Enter Food Items (separate with commas)",
736
+ placeholder="e.g., pizza, salad, soda, chicken"
737
+ )
738
+ manual_analyze_button = gr.Button("Analyze Foods", variant="primary")
739
+
740
+ with gr.Column(scale=2):
741
+ manual_output_text = gr.Markdown(label="Analysis Results")
742
+
743
+ with gr.Row():
744
+ with gr.Column():
745
+ manual_macros_chart = gr.Image(label="Macronutrient Distribution")
746
+ with gr.Column():
747
+ manual_score_chart = gr.Image(label="Health Score")
748
+ with gr.Column():
749
+ manual_items_chart = gr.Image(label="Calories by Item")
750
+
751
+ gr.Markdown(
752
+ """
753
+ ### How it works
754
+
755
+ This app uses Optical Character Recognition (OCR) to read text from your bill image,
756
+ then applies Natural Language Processing to identify food items and match them to a
757
+ nutrition database. The results include total calories, macronutrient breakdown, and
758
+ a health score from 0-100.
759
+
760
+ ### Limitations
761
+
762
+ - OCR accuracy depends on image quality - clear, well-lit photos work best
763
+ - Limited nutrition database - some foods may not be recognized
764
+ - Calorie estimates are approximations based on general serving sizes
765
+
766
+ ### Tips
767
+
768
+ - Take clear, straight-on photos of your bill in good lighting
769
+ - If the app misses items, try the manual entry option
770
+ - For best results, ensure food items are clearly listed on the receipt
771
+ """
772
+ )
773
+
774
+ # Set up event handlers
775
+ analyze_button.click(
776
+ fn=analyze_restaurant_bill,
777
+ inputs=upload_image,
778
+ outputs=[output_text, macros_chart, score_chart, items_chart]
779
+ )
780
+
781
+ manual_analyze_button.click(
782
+ fn=analyze_manual_food_items,
783
+ inputs=food_input,
784
+ outputs=[manual_output_text, manual_macros_chart, manual_score_chart, manual_items_chart]
785
+ )
786
+
787
+ return demo
788
+
789
+ # Launch the application
790
+ if __name__ == "__main__":
791
+ demo = create_interface()
792
+ demo.launch()