Amandeep01 commited on
Commit
7976d35
·
verified ·
1 Parent(s): 47eb28e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +806 -524
app.py CHANGED
@@ -8,39 +8,97 @@ 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"},
@@ -49,11 +107,35 @@ def load_nutrition_data():
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
@@ -93,13 +175,6 @@ def load_motivational_quotes():
93
  # Initialize motivational quotes
94
  motivational_quotes = load_motivational_quotes()
95
 
96
- # Initialize NLP model for food item recognition
97
- try:
98
- food_classifier = pipeline("zero-shot-classification", model="facebook/bart-large-mnli")
99
- except Exception as e:
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
@@ -107,27 +182,43 @@ def preprocess_image(image):
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):
@@ -139,112 +230,181 @@ def extract_text_from_image(image):
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()
170
  if not line:
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
@@ -283,510 +443,632 @@ def match_food_to_nutrition(food_items):
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()
 
8
  import os
9
  import random
10
  import datetime
 
 
 
11
  import matplotlib.pyplot as plt
12
  import seaborn as sns
13
  import cv2
14
+ import requests
15
+ from io import BytesIO
16
 
17
+ # Load nutrition database with expanded items
18
  def load_nutrition_data():
19
+ # Enhanced food database with more items and categories
 
20
  food_data = {
21
+ # Fast food and restaurant items
22
  "pizza": {"calories": 285, "fat": 10, "carbs": 36, "protein": 12, "category": "junk"},
23
  "burger": {"calories": 354, "fat": 17, "carbs": 40, "protein": 15, "category": "junk"},
24
+ "cheeseburger": {"calories": 400, "fat": 20, "carbs": 40, "protein": 15, "category": "junk"},
25
+ "hamburger": {"calories": 350, "fat": 15, "carbs": 40, "protein": 15, "category": "junk"},
26
  "fries": {"calories": 312, "fat": 15, "carbs": 41, "protein": 3, "category": "junk"},
27
+ "french fries": {"calories": 312, "fat": 15, "carbs": 41, "protein": 3, "category": "junk"},
28
  "salad": {"calories": 100, "fat": 7, "carbs": 5, "protein": 2, "category": "healthy"},
29
+ "caesar salad": {"calories": 150, "fat": 10, "carbs": 5, "protein": 3, "category": "healthy"},
30
+ "garden salad": {"calories": 80, "fat": 5, "carbs": 5, "protein": 2, "category": "healthy"},
31
  "soda": {"calories": 140, "fat": 0, "carbs": 39, "protein": 0, "category": "junk"},
32
+ "coke": {"calories": 140, "fat": 0, "carbs": 39, "protein": 0, "category": "junk"},
33
+ "pepsi": {"calories": 150, "fat": 0, "carbs": 41, "protein": 0, "category": "junk"},
34
+ "sprite": {"calories": 140, "fat": 0, "carbs": 38, "protein": 0, "category": "junk"},
35
+ "cola": {"calories": 140, "fat": 0, "carbs": 39, "protein": 0, "category": "junk"},
36
+ "diet coke": {"calories": 0, "fat": 0, "carbs": 0, "protein": 0, "category": "neutral"},
37
  "juice": {"calories": 110, "fat": 0, "carbs": 26, "protein": 0, "category": "neutral"},
38
+ "orange juice": {"calories": 110, "fat": 0, "carbs": 26, "protein": 0, "category": "neutral"},
39
+ "apple juice": {"calories": 115, "fat": 0, "carbs": 28, "protein": 0, "category": "neutral"},
40
  "water": {"calories": 0, "fat": 0, "carbs": 0, "protein": 0, "category": "healthy"},
41
+ "sparkling water": {"calories": 0, "fat": 0, "carbs": 0, "protein": 0, "category": "healthy"},
42
  "pasta": {"calories": 200, "fat": 2, "carbs": 42, "protein": 7, "category": "neutral"},
43
+ "spaghetti": {"calories": 220, "fat": 2, "carbs": 43, "protein": 8, "category": "neutral"},
44
+ "pasta carbonara": {"calories": 380, "fat": 18, "carbs": 43, "protein": 14, "category": "neutral"},
45
+ "fettuccine": {"calories": 220, "fat": 2, "carbs": 43, "protein": 8, "category": "neutral"},
46
+ "lasagna": {"calories": 360, "fat": 12, "carbs": 37, "protein": 25, "category": "neutral"},
47
+ "mac and cheese": {"calories": 350, "fat": 15, "carbs": 45, "protein": 15, "category": "neutral"},
48
+ "macaroni": {"calories": 200, "fat": 2, "carbs": 42, "protein": 7, "category": "neutral"},
49
  "steak": {"calories": 300, "fat": 15, "carbs": 0, "protein": 30, "category": "protein"},
50
+ "ribeye": {"calories": 330, "fat": 25, "carbs": 0, "protein": 30, "category": "protein"},
51
+ "filet mignon": {"calories": 320, "fat": 20, "carbs": 0, "protein": 35, "category": "protein"},
52
+ "sirloin": {"calories": 270, "fat": 12, "carbs": 0, "protein": 32, "category": "protein"},
53
  "chicken": {"calories": 220, "fat": 8, "carbs": 0, "protein": 40, "category": "protein"},
54
+ "chicken wings": {"calories": 350, "fat": 18, "carbs": 5, "protein": 33, "category": "protein"},
55
+ "chicken tenders": {"calories": 380, "fat": 20, "carbs": 20, "protein": 30, "category": "protein"},
56
+ "grilled chicken": {"calories": 220, "fat": 8, "carbs": 0, "protein": 40, "category": "protein"},
57
+ "fried chicken": {"calories": 320, "fat": 16, "carbs": 12, "protein": 28, "category": "protein"},
58
  "fish": {"calories": 180, "fat": 5, "carbs": 0, "protein": 30, "category": "healthy"},
59
+ "salmon": {"calories": 200, "fat": 10, "carbs": 0, "protein": 25, "category": "healthy"},
60
+ "tuna": {"calories": 160, "fat": 3, "carbs": 0, "protein": 33, "category": "healthy"},
61
+ "cod": {"calories": 150, "fat": 2, "carbs": 0, "protein": 28, "category": "healthy"},
62
  "rice": {"calories": 130, "fat": 0, "carbs": 28, "protein": 3, "category": "neutral"},
63
+ "brown rice": {"calories": 110, "fat": 1, "carbs": 22, "protein": 3, "category": "healthy"},
64
+ "white rice": {"calories": 130, "fat": 0, "carbs": 28, "protein": 3, "category": "neutral"},
65
+ "fried rice": {"calories": 230, "fat": 10, "carbs": 28, "protein": 8, "category": "neutral"},
66
+
67
+ # Drinks
68
  "beer": {"calories": 154, "fat": 0, "carbs": 13, "protein": 1, "category": "junk"},
69
  "wine": {"calories": 125, "fat": 0, "carbs": 4, "protein": 0, "category": "neutral"},
70
+ "red wine": {"calories": 125, "fat": 0, "carbs": 4, "protein": 0, "category": "neutral"},
71
+ "white wine": {"calories": 120, "fat": 0, "carbs": 4, "protein": 0, "category": "neutral"},
72
+ "cocktail": {"calories": 180, "fat": 0, "carbs": 20, "protein": 0, "category": "junk"},
73
+ "margarita": {"calories": 200, "fat": 0, "carbs": 25, "protein": 0, "category": "junk"},
74
+ "daiquiri": {"calories": 180, "fat": 0, "carbs": 20, "protein": 0, "category": "junk"},
75
+ "mojito": {"calories": 160, "fat": 0, "carbs": 18, "protein": 0, "category": "junk"},
76
+ "martini": {"calories": 120, "fat": 0, "carbs": 3, "protein": 0, "category": "neutral"},
77
  "coffee": {"calories": 5, "fat": 0, "carbs": 0, "protein": 0, "category": "healthy"},
78
+ "latte": {"calories": 120, "fat": 4, "carbs": 10, "protein": 8, "category": "neutral"},
79
+ "cappuccino": {"calories": 110, "fat": 4, "carbs": 8, "protein": 6, "category": "neutral"},
80
+ "espresso": {"calories": 5, "fat": 0, "carbs": 0, "protein": 0, "category": "healthy"},
81
+
82
+ # Desserts
83
+ "ice cream": {"calories": 207, "fat": 11, "carbs": 24, "protein": 4, "category": "junk"},
84
+ "cake": {"calories": 350, "fat": 18, "carbs": 45, "protein": 4, "category": "junk"},
85
+ "chocolate cake": {"calories": 370, "fat": 19, "carbs": 48, "protein": 5, "category": "junk"},
86
+ "cheesecake": {"calories": 400, "fat": 25, "carbs": 35, "protein": 7, "category": "junk"},
87
+ "tiramisu": {"calories": 380, "fat": 20, "carbs": 40, "protein": 5, "category": "junk"},
88
+ "brownie": {"calories": 300, "fat": 15, "carbs": 40, "protein": 3, "category": "junk"},
89
+ "cookie": {"calories": 180, "fat": 9, "carbs": 22, "protein": 2, "category": "junk"},
90
+ "chocolate": {"calories": 200, "fat": 12, "carbs": 20, "protein": 2, "category": "junk"},
91
+ "pie": {"calories": 300, "fat": 14, "carbs": 38, "protein": 3, "category": "junk"},
92
+ "apple pie": {"calories": 290, "fat": 14, "carbs": 40, "protein": 3, "category": "junk"},
93
+ "pudding": {"calories": 150, "fat": 4, "carbs": 25, "protein": 3, "category": "junk"},
94
+
95
+ # Other common items
96
  "sandwich": {"calories": 250, "fat": 8, "carbs": 30, "protein": 15, "category": "neutral"},
97
+ "wrap": {"calories": 220, "fat": 5, "carbs": 30, "protein": 13, "category": "neutral"},
98
  "soup": {"calories": 120, "fat": 3, "carbs": 12, "protein": 10, "category": "healthy"},
 
99
  "bread": {"calories": 80, "fat": 1, "carbs": 15, "protein": 3, "category": "neutral"},
100
+ "garlic bread": {"calories": 150, "fat": 6, "carbs": 18, "protein": 4, "category": "neutral"},
101
+ "roll": {"calories": 80, "fat": 1, "carbs": 15, "protein": 3, "category": "neutral"},
102
  "milkshake": {"calories": 300, "fat": 10, "carbs": 50, "protein": 9, "category": "junk"},
103
  "dessert": {"calories": 280, "fat": 14, "carbs": 35, "protein": 5, "category": "junk"},
104
  "smoothie": {"calories": 170, "fat": 2, "carbs": 35, "protein": 5, "category": "neutral"},
 
107
  "noodles": {"calories": 190, "fat": 2, "carbs": 40, "protein": 7, "category": "neutral"},
108
  "taco": {"calories": 210, "fat": 10, "carbs": 22, "protein": 12, "category": "neutral"},
109
  "burrito": {"calories": 350, "fat": 12, "carbs": 50, "protein": 15, "category": "neutral"},
110
+ "nachos": {"calories": 600, "fat": 35, "carbs": 58, "protein": 20, "category": "junk"},
111
+ "fajitas": {"calories": 290, "fat": 10, "carbs": 30, "protein": 25, "category": "neutral"},
112
+ "quesadilla": {"calories": 400, "fat": 22, "carbs": 35, "protein": 18, "category": "neutral"},
113
+ "eggs": {"calories": 140, "fat": 10, "carbs": 1, "protein": 12, "category": "protein"},
114
+ "omelette": {"calories": 220, "fat": 16, "carbs": 2, "protein": 16, "category": "protein"},
115
+ "pancakes": {"calories": 380, "fat": 12, "carbs": 60, "protein": 10, "category": "neutral"},
116
+ "waffles": {"calories": 370, "fat": 14, "carbs": 55, "protein": 8, "category": "neutral"},
117
+ "toast": {"calories": 80, "fat": 1, "carbs": 15, "protein": 3, "category": "neutral"},
118
+ "muffin": {"calories": 210, "fat": 10, "carbs": 30, "protein": 3, "category": "junk"},
119
+ "croissant": {"calories": 230, "fat": 12, "carbs": 26, "protein": 5, "category": "neutral"},
120
+ "doughnut": {"calories": 250, "fat": 12, "carbs": 30, "protein": 4, "category": "junk"},
121
+ "donut": {"calories": 250, "fat": 12, "carbs": 30, "protein": 4, "category": "junk"},
122
+ "bagel": {"calories": 245, "fat": 1, "carbs": 48, "protein": 10, "category": "neutral"},
123
+ "scone": {"calories": 230, "fat": 12, "carbs": 28, "protein": 4, "category": "neutral"},
124
+
125
+ # Side dishes
126
+ "onion rings": {"calories": 320, "fat": 18, "carbs": 35, "protein": 5, "category": "junk"},
127
+ "mashed potatoes": {"calories": 150, "fat": 4, "carbs": 25, "protein": 3, "category": "neutral"},
128
+ "baked potato": {"calories": 130, "fat": 0, "carbs": 30, "protein": 3, "category": "neutral"},
129
+ "coleslaw": {"calories": 120, "fat": 8, "carbs": 10, "protein": 1, "category": "neutral"},
130
+ "corn": {"calories": 90, "fat": 1, "carbs": 20, "protein": 3, "category": "healthy"},
131
+ "broccoli": {"calories": 40, "fat": 0, "carbs": 8, "protein": 4, "category": "healthy"},
132
+ "veggies": {"calories": 50, "fat": 0, "carbs": 10, "protein": 2, "category": "healthy"},
133
+ "vegetables": {"calories": 50, "fat": 0, "carbs": 10, "protein": 2, "category": "healthy"},
134
+ "chips": {"calories": 300, "fat": 15, "carbs": 35, "protein": 3, "category": "junk"},
135
  }
136
  return food_data
137
 
138
+ # Load nutrition database
139
  nutrition_data = load_nutrition_data()
140
 
141
  # Load motivational quotes based on health score ranges
 
175
  # Initialize motivational quotes
176
  motivational_quotes = load_motivational_quotes()
177
 
 
 
 
 
 
 
 
178
  # Helper function to preprocess the image for better OCR results
179
  def preprocess_image(image):
180
  # Convert to numpy array if needed
 
182
  image = np.array(image)
183
 
184
  try:
185
+ # Ensure the image is in RGB format (3 channels)
186
+ if len(image.shape) == 2: # Grayscale
187
+ image = cv2.cvtColor(image, cv2.COLOR_GRAY2RGB)
188
+ elif len(image.shape) == 3 and image.shape[2] == 4: # RGBA
189
+ image = cv2.cvtColor(image, cv2.COLOR_RGBA2RGB)
190
+
191
  # Convert to grayscale
192
+ gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
 
 
 
193
 
194
+ # Apply multiple preprocessing techniques and keep the best result
195
+ results = []
196
+
197
+ # Technique 1: Adaptive thresholding
198
  thresh = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
199
  cv2.THRESH_BINARY, 11, 2)
200
+ results.append(thresh)
201
+
202
+ # Technique 2: Otsu's thresholding after Gaussian filtering
203
+ blur = cv2.GaussianBlur(gray, (5, 5), 0)
204
+ _, otsu = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
205
+ results.append(otsu)
206
+
207
+ # Technique 3: Histogram equalization
208
+ equalized = cv2.equalizeHist(gray)
209
+ results.append(equalized)
210
 
211
+ # Technique 4: Original grayscale
212
+ results.append(gray)
213
 
214
+ # Convert all results to PIL images
215
+ pil_images = [Image.fromarray(img) for img in results]
216
 
217
+ return pil_images
218
  except Exception as e:
219
  print(f"Error preprocessing image: {e}")
220
+ # If preprocessing fails, return the original image as a list
221
+ return [Image.fromarray(image) if isinstance(image, np.ndarray) else image]
222
 
223
  # OCR function to extract text from bill image with enhanced image processing
224
  def extract_text_from_image(image):
 
230
  else:
231
  img = Image.fromarray(image) if isinstance(image, np.ndarray) else image
232
 
233
+ # Create a copy for display
234
+ display_img = img.copy() if hasattr(img, 'copy') else img
235
 
236
+ # Preprocess the image to get multiple versions
237
+ preprocessed_images = preprocess_image(img)
 
238
 
239
+ # Try OCR on each preprocessed image
240
+ best_text = ""
 
241
 
242
+ # Custom configs to try
243
+ configs = [
244
+ r'--oem 3 --psm 6 -l eng', # Assume a single uniform block of text
245
+ r'--oem 3 --psm 4 -l eng', # Assume a single column of text
246
+ r'--oem 3 --psm 3 -l eng', # Fully automatic page segmentation
247
+ r'--oem 3 --psm 11 -l eng', # Sparse text - no specific structure
248
+ r'--oem 3 --psm 12 -l eng', # Sparse text with OSD
249
+ ]
250
+
251
+ for img_version in preprocessed_images:
252
+ for config in configs:
253
+ try:
254
+ text = pytesseract.image_to_string(img_version, config=config)
255
+ # Keep the longest text as it likely contains more information
256
+ if len(text.strip()) > len(best_text.strip()):
257
+ best_text = text
258
+ except Exception as e:
259
+ print(f"OCR error with specific config: {str(e)}")
260
+ continue
261
+
262
+ # If all attempts failed or returned very little text
263
+ if len(best_text.strip()) < 10:
264
+ # Try one last attempt with default settings
265
+ try:
266
+ best_text = pytesseract.image_to_string(img)
267
+ except Exception as e:
268
+ print(f"Final OCR attempt error: {str(e)}")
269
+
270
+ # Debug output
271
+ print(f"OCR extracted text of length: {len(best_text)}")
272
+
273
+ return best_text
274
  except Exception as e:
275
+ print(f"Error extracting text: {str(e)}")
276
  return f"Error extracting text: {str(e)}"
277
 
278
  # Extract food items from the OCR text with improved pattern recognition
279
  def extract_food_items(text):
280
+ # Improved algorithm to detect food items in bill text
 
281
  lines = text.split('\n')
282
  food_items = []
283
 
284
+ # Debug info
285
+ print(f"Processing {len(lines)} lines of text")
286
+
287
+ # Clean and normalize all lines first
288
+ cleaned_lines = []
289
+ for line in lines:
290
+ # Remove common non-food text
291
+ line = re.sub(r'thank you|receipt|invoice|order|table|server', '', line.lower(), flags=re.IGNORECASE)
292
+ cleaned_lines.append(line.strip())
293
+
294
  # Regular patterns for food items in bills
295
  # More comprehensive price pattern to catch various formats
296
  price_pattern = r'(\$?\d+\.\d{2}|\$?\d+\,\d{2}|\$?\d+)'
297
 
298
+ for line in cleaned_lines:
 
299
  if not line:
300
  continue
301
+
302
  # Skip lines that look like totals or headers
303
  skip_keywords = [
304
  'total', 'subtotal', 'tax', 'gratuity', 'tip', 'service', 'amount', 'due', 'change',
305
+ 'cash', 'credit', 'card', 'payment', 'date', 'time', 'check', 'table',
306
  'guest', 'invoice', 'receipt', 'bill', 'order', 'tel', 'phone', 'address',
307
+ 'thank you', 'restaurant', 'cafe', 'bar', 'grill', 'kitchen', 'www', 'http'
308
  ]
309
 
310
  if any(keyword in line.lower() for keyword in skip_keywords):
311
  continue
312
+
313
+ # Debug line
314
+ print(f"Processing line: '{line}'")
315
+
316
+ # If line contains a price, extract the item name (everything before the price)
317
  if re.search(price_pattern, line):
318
+ # Split based on number patterns (likely price)
319
  item_parts = re.split(price_pattern, line)
320
  if item_parts and len(item_parts) > 1:
321
  item_match = item_parts[0].strip()
322
  if item_match and len(item_match) > 1: # Ensure it's not just whitespace
323
  # Clean up the item name (remove quantities, etc.)
324
  cleaned_item = re.sub(r'^\d+\s*[xX]?\s*', '', item_match) # Remove quantities like "2 x" or "2"
325
+ cleaned_item = re.sub(r'\d+\s*oz\s*', '', cleaned_item) # Remove sizes like "12oz"
326
+ cleaned_item = re.sub(r'\(\w+\)', '', cleaned_item) # Remove parentheses
327
 
328
  # Filter out very short items that are likely not food
329
  if len(cleaned_item.strip()) > 2:
330
  food_items.append(cleaned_item.strip().lower())
331
+ print(f"Found item with price: '{cleaned_item.strip().lower()}'")
332
 
333
+ # If not enough items found, try alternate methods
334
+ if len(food_items) < 2:
335
+ # Look for menu-like patterns
336
+ for line in cleaned_lines:
337
+ # Try to find numbered items (e.g., "1. Burger" or "#1 Burger")
338
+ numbered_pattern = r'(?:^|\s)(?:\d+\.|\#\d+)\s+(.+?)(?:\s+\$|\s+\d|\s*$)'
339
+ match = re.search(numbered_pattern, line)
340
+ if match:
341
+ item = match.group(1).strip().lower()
342
+ if len(item) > 2 and item not in food_items:
343
+ food_items.append(item)
344
+ print(f"Found numbered item: '{item}'")
345
+
346
+ # Simple heuristic: look for capitalized words that might be menu items
347
+ # This is a fallback when we're struggling to find items
348
+ if len(line) > 3 and not any(char.isdigit() for char in line) and not any(skip in line for skip in skip_keywords):
349
+ potential_item = re.sub(r'\W+', ' ', line).strip().lower()
350
+
351
+ # Check if the line contains any known food items
352
+ for food in nutrition_data.keys():
353
+ if food in potential_item:
354
+ if potential_item not in food_items:
355
+ food_items.append(potential_item)
356
+ print(f"Found potential food item: '{potential_item}'")
357
+ break
 
 
 
 
 
 
 
 
 
358
 
359
+ # If we still have no items, use a more aggressive approach to find any words
360
+ # that match our food database
361
+ if len(food_items) < 2:
362
+ print("Using aggressive food item detection...")
363
+ # Flatten all text and clean it
364
+ all_text = ' '.join(cleaned_lines).lower()
365
+
366
+ # Filter out non-alphanumeric characters
367
+ all_text = re.sub(r'[^\w\s]', ' ', all_text)
368
+
369
+ # Get all words
370
+ words = all_text.split()
371
+
372
+ # Look for any word or pair of words that matches our food database
373
+ for i in range(len(words)):
374
+ # Single word match
375
+ if words[i] in nutrition_data:
376
+ food_items.append(words[i])
377
+ print(f"Found direct food match: '{words[i]}'")
378
 
379
+ # Two-word match
380
+ if i < len(words) - 1:
381
+ two_words = words[i] + ' ' + words[i+1]
382
+ if two_words in nutrition_data:
383
+ food_items.append(two_words)
384
+ print(f"Found direct two-word food match: '{two_words}'")
 
385
 
386
+ # If we've exhausted all options but still have no items, try to find words
387
+ # that are similar to our food database
388
+ if len(food_items) < 2:
389
+ print("Using similarity-based food item detection...")
390
+ all_text = ' '.join(cleaned_lines).lower()
391
+ words = re.findall(r'\b[a-z]{3,}\b', all_text) # Find all words with at least 3 letters
392
+
393
+ for word in words:
394
+ # Skip very common words
395
+ if word in ['the', 'and', 'for', 'with', 'that', 'have', 'this', 'from']:
396
+ continue
397
+
398
+ # Check if the word is a substring of any food in our database
399
+ for food in nutrition_data.keys():
400
+ if word in food:
401
+ food_items.append(food)
402
+ print(f"Found similar food item: '{food}' from '{word}'")
403
+ break
404
+
405
+ # Remove duplicates and limit to reasonable number
406
+ food_items = list(set(food_items))[:10]
407
+ print(f"Final food items extracted: {food_items}")
408
  return food_items
409
 
410
  # Match extracted food items to our nutrition database with improved fuzzy matching
 
443
  if best_match and max_score > 0.3:
444
  matched_items.append({"name": item, "matched_as": best_match, "nutrition": nutrition_data[best_match]})
445
 
446
+ # Remove duplicates (based on matched_as)
447
+ unique_matches = []
448
+ seen_matches = set()
449
+
450
+ for item in matched_items:
451
+ match_key = item.get("matched_as", item["name"])
452
+ if match_key not in seen_matches:
453
+ unique_matches.append(item)
454
+ seen_matches.add(match_key)
455
+
456
+ return unique_matches
457
 
458
+ # Calculate nutritional totals and health
459
+ # Calculate nutritional totals and health score
460
+ def calculate_meal_health(matched_items):
461
  if not matched_items:
462
+ return None, None, "No food items detected"
463
+
464
+ # Calculate total nutrition
 
 
 
 
 
 
 
 
 
465
  total_calories = sum(item["nutrition"]["calories"] for item in matched_items)
466
  total_fat = sum(item["nutrition"]["fat"] for item in matched_items)
467
  total_carbs = sum(item["nutrition"]["carbs"] for item in matched_items)
468
  total_protein = sum(item["nutrition"]["protein"] for item in matched_items)
469
 
470
+ # Count items by category
471
+ category_counts = {"healthy": 0, "neutral": 0, "protein": 0, "junk": 0}
472
+ for item in matched_items:
473
+ category = item["nutrition"]["category"]
474
+ category_counts[category] = category_counts.get(category, 0) + 1
 
 
 
475
 
476
+ # Calculate health score (0-100)
477
+ total_items = len(matched_items)
478
+ health_score = 0
 
 
479
 
480
+ # Point system:
481
+ # - Healthy items: +25 points each
482
+ # - Protein items: +15 points each
483
+ # - Neutral items: +5 points each
484
+ # - Junk items: -10 points each
 
 
 
485
 
486
+ # Base score of 50
487
+ health_score = 50
488
+ health_score += category_counts["healthy"] * 25
489
+ health_score += category_counts["protein"] * 15
490
+ health_score += category_counts["neutral"] * 5
491
+ health_score -= category_counts["junk"] * 10
492
 
493
+ # Adjust based on macros
494
+ if total_calories > 0:
495
+ # Protein is good
496
+ protein_ratio = (total_protein * 4) / total_calories
497
+ if protein_ratio > 0.25: # >25% protein is good
 
 
 
 
 
 
 
498
  health_score += 10
 
 
499
 
500
+ # Too much fat is not ideal
501
+ fat_ratio = (total_fat * 9) / total_calories
502
+ if fat_ratio > 0.4: # >40% calories from fat
503
+ health_score -= 10
 
 
 
 
 
 
 
 
 
 
 
504
 
505
+ # Clamp score between 0-100
506
  health_score = max(0, min(100, health_score))
507
 
508
+ # Determine feedback category
509
+ if health_score >= 80:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
510
  category = "excellent"
511
+ elif health_score >= 60:
512
  category = "good"
513
+ elif health_score >= 40:
514
  category = "moderate"
515
  else:
516
  category = "poor"
517
 
518
+ # Get a random motivational quote for the category
519
+ quote = random.choice(motivational_quotes[category])
 
 
 
 
520
 
521
+ # Create nutrition data
522
+ nutrition_data = {
523
+ "calories": total_calories,
524
+ "fat": total_fat,
525
+ "carbs": total_carbs,
526
+ "protein": total_protein,
527
+ "health_score": health_score,
528
+ "category": category,
529
+ "message": quote
530
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
531
 
532
+ # Create summary
533
+ dominant_macro = ""
534
+ if total_calories > 0:
535
+ fat_percentage = (total_fat * 9) / total_calories * 100
536
+ carbs_percentage = (total_carbs * 4) / total_calories * 100
537
+ protein_percentage = (total_protein * 4) / total_calories * 100
538
+
539
+ if max(fat_percentage, carbs_percentage, protein_percentage) == fat_percentage:
540
+ dominant_macro = "fat"
541
+ elif max(fat_percentage, carbs_percentage, protein_percentage) == carbs_percentage:
542
+ dominant_macro = "carbs"
543
+ else:
544
+ dominant_macro = "protein"
545
 
546
+ summary = f"You consumed approximately {total_calories} calories — mostly {dominant_macro}."
 
 
547
 
548
+ if health_score >= 70:
549
+ summary += " Great choices today!"
550
+ elif health_score >= 50:
551
+ summary += " Consider more balanced options next time."
552
+ else:
553
+ summary += " Try to make healthier choices next time."
554
 
555
+ return nutrition_data, summary, ""
556
+
557
+ # Generate detailed analysis with visualization
558
+ def generate_analysis(matched_items, nutrition_data):
559
+ if not matched_items or not nutrition_data:
560
+ return None
561
 
562
+ # Create DataFrame for the items
563
+ items_data = []
564
+ for item in matched_items:
565
+ name = item["name"]
566
+ if "matched_as" in item:
567
+ name = f"{name} (matched as {item['matched_as']})"
568
 
569
+ items_data.append({
570
+ "Item": name,
571
+ "Calories": item["nutrition"]["calories"],
572
+ "Fat (g)": item["nutrition"]["fat"],
573
+ "Carbs (g)": item["nutrition"]["carbs"],
574
+ "Protein (g)": item["nutrition"]["protein"],
575
+ "Category": item["nutrition"]["category"].capitalize()
576
+ })
577
 
578
+ df = pd.DataFrame(items_data)
 
579
 
580
+ # Get the current date and time
581
+ now = datetime.datetime.now()
582
+ date_str = now.strftime("%Y-%m-%d")
583
+ time_str = now.strftime("%H:%M:%S")
584
 
585
+ # Create visualization plots
586
+ fig, axs = plt.subplots(2, 2, figsize=(12, 10))
587
 
588
+ # Plot 1: Calories by item (horizontal bar)
589
+ df_sorted = df.sort_values('Calories', ascending=True)
590
+ sns.barplot(x='Calories', y='Item', data=df_sorted, ax=axs[0, 0], palette='viridis')
591
+ axs[0, 0].set_title('Calories by Item')
592
+ axs[0, 0].set_xlabel('Calories')
593
+ axs[0, 0].set_ylabel('Food Item')
594
 
595
+ # Plot 2: Macronutrient breakdown (pie chart)
596
+ total_calories = nutrition_data["calories"]
597
+ if total_calories > 0:
598
+ fat_cals = nutrition_data["fat"] * 9
599
+ carb_cals = nutrition_data["carbs"] * 4
600
+ protein_cals = nutrition_data["protein"] * 4
601
+
602
+ macro_data = [fat_cals, carb_cals, protein_cals]
603
+ macro_labels = [f'Fat ({fat_cals:.0f} cal)', f'Carbs ({carb_cals:.0f} cal)', f'Protein ({protein_cals:.0f} cal)']
604
+ colors = ['#FF9999', '#66B2FF', '#99FF99']
605
+
606
+ axs[0, 1].pie(macro_data, labels=macro_labels, colors=colors, autopct='%1.1f%%', startangle=90)
607
+ axs[0, 1].set_title('Calorie Sources')
608
+ else:
609
+ axs[0, 1].text(0.5, 0.5, 'No calorie data available', ha='center', va='center')
610
+ axs[0, 1].axis('off')
611
 
612
+ # Plot 3: Health score gauge
613
+ health_score = nutrition_data["health_score"]
 
614
 
615
+ # Create a gauge chart using a pie chart
616
+ size = 0.3
617
+ vals = [health_score, 100-health_score]
 
 
 
618
 
619
+ # Create color based on score
620
+ if health_score >= 80:
621
+ color = '#00CC66' # Green
622
+ elif health_score >= 60:
623
+ color = '#CCCC00' # Yellow
624
+ elif health_score >= 40:
625
+ color = '#FF9900' # Orange
626
+ else:
627
+ color = '#FF3333' # Red
628
 
629
+ cmap = [color, '#f0f0f0']
630
+ axs[1, 0].pie(vals, radius=1, colors=cmap, startangle=90, counterclock=False)
631
+ axs[1, 0].pie([1], radius=1-size, colors=['white'])
632
+ axs[1, 0].text(0, 0, f"{health_score:.0f}", fontsize=32, ha='center', va='center')
633
+ axs[1, 0].text(0, -0.2, "Health Score", fontsize=12, ha='center', va='center')
634
+ axs[1, 0].set_title('Meal Health Score')
635
 
636
+ # Plot 4: Food category breakdown
637
+ category_counts = df['Category'].value_counts()
638
+ sns.barplot(x=category_counts.index, y=category_counts.values, ax=axs[1, 1], palette='viridis')
639
+ axs[1, 1].set_title('Food Categories')
640
+ axs[1, 1].set_xlabel('Category')
641
+ axs[1, 1].set_ylabel('Count')
642
 
643
+ plt.tight_layout()
 
644
 
645
+ # Save the plots to a file
646
+ analysis_img_path = "analysis_temp.png"
647
+ plt.savefig(analysis_img_path, dpi=150, bbox_inches='tight')
648
+ plt.close()
649
 
650
+ # Create a table of the analyzed items
651
+ items_table = df.to_html(index=False, classes='table table-striped')
652
 
653
+ # Create analysis summary
654
+ total_fat = nutrition_data["fat"]
655
+ total_carbs = nutrition_data["carbs"]
656
+ total_protein = nutrition_data["protein"]
 
 
657
 
658
+ analysis_summary = f"""
659
+ <h2>Meal Nutrition Analysis</h2>
660
+ <p><strong>Date:</strong> {date_str} &nbsp; <strong>Time:</strong> {time_str}</p>
661
 
662
+ <h3>Summary</h3>
663
+ <p>
664
+ Total Calories: <strong>{total_calories:.0f}</strong><br>
665
+ Total Fat: <strong>{total_fat:.1f}g</strong> ({(total_fat * 9 / total_calories * 100):.1f}% of calories)<br>
666
+ Total Carbs: <strong>{total_carbs:.1f}g</strong> ({(total_carbs * 4 / total_calories * 100):.1f}% of calories)<br>
667
+ Total Protein: <strong>{total_protein:.1f}g</strong> ({(total_protein * 4 / total_calories * 100):.1f}% of calories)<br>
668
+ Health Score: <strong>{health_score:.0f}/100</strong> ({nutrition_data["category"].capitalize()})
669
+ </p>
670
 
671
+ <h3>Feedback</h3>
672
+ <p>{nutrition_data["message"]}</p>
673
 
674
+ <h3>Analyzed Items</h3>
675
+ {items_table}
676
 
677
+ <h3>Recommendations</h3>
678
+ """
679
 
680
+ # Add custom recommendations based on the nutritional analysis
681
+ if total_protein < 20:
682
+ analysis_summary += "<p>✅ <strong>Add more protein</strong> to your meals. Good sources include lean meats, fish, eggs, tofu, or legumes.</p>"
683
 
684
+ if (total_fat * 9 / total_calories) > 0.4:
685
+ analysis_summary += "<p>✅ <strong>Consider reducing fat intake</strong>, especially from fried foods and processed items.</p>"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
686
 
687
+ category_counts_dict = df['Category'].value_counts().to_dict()
688
+ junk_count = category_counts_dict.get('Junk', 0)
689
+ healthy_count = category_counts_dict.get('Healthy', 0)
 
 
 
 
 
 
690
 
691
+ if junk_count > healthy_count:
692
+ analysis_summary += "<p>✅ <strong>Try to include more fruits and vegetables</strong> in your meals for better nutrition.</p>"
693
 
694
+ if health_score < 50:
695
+ analysis_summary += "<p>✅ <strong>Balance your plate</strong> with 1/2 vegetables, 1/4 protein, and 1/4 whole grains for improved nutrition.</p>"
696
+
697
+ return analysis_img_path, analysis_summary
698
+
699
+ # Function to process the bill image with enhanced error handling
700
+ def process_bill_image(image):
701
  try:
702
+ display_img = None
703
+ ocr_text = ""
704
+ food_items = []
705
+ matched_items = []
706
+ nutrition_data = None
707
+ summary = ""
708
+ error_message = ""
709
+
710
+ # Process the image if it's valid
711
+ if image is not None:
712
+ # Extract text using OCR
713
+ ocr_text = extract_text_from_image(image)
714
+
715
+ if ocr_text:
716
+ # Extract food items from the OCR text
717
+ food_items = extract_food_items(ocr_text)
718
+
719
+ if food_items:
720
+ # Match food items to nutrition database
721
+ matched_items = match_food_to_nutrition(food_items)
722
+
723
+ if matched_items:
724
+ # Calculate health score and nutrition data
725
+ nutrition_data, summary, error_message = calculate_meal_health(matched_items)
726
+ else:
727
+ error_message = "No matching food items found in our database. Please try another image."
728
+ else:
729
+ error_message = "No food items detected. Please try another image or check the image clarity."
730
+ else:
731
+ error_message = "No text could be extracted from the image. Please try a clearer image."
732
+ else:
733
+ error_message = "Please upload an image to analyze."
734
+
735
+ # Generate the food items section
736
+ food_items_html = "<p>No food items detected</p>"
737
+ if matched_items:
738
+ food_items_html = "<ul>"
739
+ for item in matched_items:
740
+ item_name = item["name"]
741
+ if "matched_as" in item:
742
+ item_name = f"{item_name} (recognized as {item['matched_as']})"
743
+
744
+ cals = item["nutrition"]["calories"]
745
+ cat = item["nutrition"]["category"].capitalize()
746
+
747
+ # Choose color based on category
748
+ color = "#000000"
749
+ if item["nutrition"]["category"] == "healthy":
750
+ color = "#007700" # Green
751
+ elif item["nutrition"]["category"] == "junk":
752
+ color = "#CC0000" # Red
753
+ elif item["nutrition"]["category"] == "protein":
754
+ color = "#0000CC" # Blue
755
+
756
+ food_items_html += f'<li style="color:{color};"><strong>{item_name}</strong>: {cals} calories ({cat})</li>'
757
+ food_items_html += "</ul>"
758
+
759
+ # Generate detailed analysis if we have data
760
+ analysis_img = None
761
+ analysis_html = "<p>No analysis available</p>"
762
+
763
+ if matched_items and nutrition_data:
764
+ try:
765
+ analysis_img, analysis_html = generate_analysis(matched_items, nutrition_data)
766
+ except Exception as e:
767
+ print(f"Error generating analysis: {str(e)}")
768
+ analysis_html = f"<p>Error generating analysis: {str(e)}</p>"
769
+
770
+ # Return the results
771
+ return (
772
+ ocr_text,
773
+ food_items_html,
774
+ summary if summary else error_message,
775
+ analysis_img if analysis_img else None,
776
+ analysis_html
777
+ )
778
+ except Exception as e:
779
+ error_msg = f"An error occurred: {str(e)}"
780
+ print(error_msg)
781
+ return (
782
+ "",
783
+ "<p>No food items detected</p>",
784
+ error_msg,
785
+ None,
786
+ "<p>Analysis not available due to an error</p>"
787
+ )
788
 
789
+ # Function to process direct text input (instead of an image)
790
+ def process_text_input(text_input):
791
+ try:
792
+ if not text_input:
793
+ return "<p>No text provided</p>", "Please enter some text to analyze", None, "<p>Analysis not available</p>"
794
+
795
+ # Extract food items from the text
796
+ food_items = extract_food_items(text_input)
797
+
798
+ if not food_items:
799
+ return "<p>No food items detected in your text</p>", "No food items found. Try being more specific about what you ate.", None, "<p>Analysis not available</p>"
800
+
801
+ # Match food items to nutrition database
802
+ matched_items = match_food_to_nutrition(food_items)
803
+
804
+ if not matched_items:
805
+ return "<p>No matching food items found in our database</p>", "Your food items couldn't be matched to our database. Try different foods or descriptions.", None, "<p>Analysis not available</p>"
806
+
807
+ # Calculate health score and nutrition data
808
+ nutrition_data, summary, error_message = calculate_meal_health(matched_items)
809
+
810
+ if error_message:
811
+ return "<p>No food items detected</p>", error_message, None, "<p>Analysis not available</p>"
812
+
813
+ # Generate the food items section
814
+ food_items_html = "<ul>"
815
+ for item in matched_items:
816
+ item_name = item["name"]
817
+ if "matched_as" in item:
818
+ item_name = f"{item_name} (recognized as {item['matched_as']})"
819
 
820
+ cals = item["nutrition"]["calories"]
821
+ cat = item["nutrition"]["category"].capitalize()
822
 
823
+ # Choose color based on category
824
+ color = "#000000"
825
+ if item["nutrition"]["category"] == "healthy":
826
+ color = "#007700" # Green
827
+ elif item["nutrition"]["category"] == "junk":
828
+ color = "#CC0000" # Red
829
+ elif item["nutrition"]["category"] == "protein":
830
+ color = "#0000CC" # Blue
831
 
832
+ food_items_html += f'<li style="color:{color};"><strong>{item_name}</strong>: {cals} calories ({cat})</li>'
833
+ food_items_html += "</ul>"
834
+
835
+ # Generate detailed analysis
836
+ analysis_img, analysis_html = generate_analysis(matched_items, nutrition_data)
837
+
838
+ return food_items_html, summary, analysis_img, analysis_html
839
+ except Exception as e:
840
+ error_msg = f"An error occurred: {str(e)}"
841
+ print(error_msg)
842
+ return "<p>No food items detected</p>", error_msg, None, "<p>Analysis not available due to an error</p>"
843
+
844
+ # Example images for the demo
845
+ def get_example_images():
846
+ example_urls = [
847
+ "https://huggingface.co/datasets/huggingface-tools/default-examples/resolve/main/images/restaurant_bill1.jpg",
848
+ "https://huggingface.co/datasets/huggingface-tools/default-examples/resolve/main/images/restaurant_bill2.jpg"
849
+ ]
850
+ return example_urls
851
+
852
+ # Create the Gradio interface
853
+ def create_gradio_interface():
854
+ # Define CSS for the interface
855
+ custom_css = """
856
+ body {
857
+ font-family: 'Arial', sans-serif;
858
+ }
859
+ h1 {
860
+ color: #4a4a4a;
861
+ text-align: center;
862
+ }
863
+ .footer {
864
+ text-align: center;
865
+ margin-top: 20px;
866
+ font-size: 0.8em;
867
+ color: #666;
868
+ }
869
+ .container {
870
+ margin: 0 auto;
871
+ max-width: 1200px;
872
+ }
873
+ .tab-content {
874
+ padding: 15px;
875
+ border: 1px solid #ddd;
876
+ border-top: none;
877
+ border-radius: 0 0 5px 5px;
878
+ }
879
+ .nutrition-summary {
880
+ background-color: #f9f9f9;
881
+ padding: 15px;
882
+ border-radius: 5px;
883
+ margin-top: 15px;
884
+ }
885
+ .footer-note {
886
+ font-size: 0.9em;
887
+ font-style: italic;
888
+ margin-top: 30px;
889
+ text-align: center;
890
+ color: #777;
891
+ }
892
+ table.table-striped {
893
+ width: 100%;
894
+ border-collapse: collapse;
895
+ }
896
+ table.table-striped th, table.table-striped td {
897
+ border: 1px solid #ddd;
898
+ padding: 8px;
899
+ text-align: left;
900
+ }
901
+ table.table-striped tr:nth-child(even) {
902
+ background-color: #f2f2f2;
903
+ }
904
+ table.table-striped th {
905
+ padding-top: 12px;
906
+ padding-bottom: 12px;
907
+ background-color: #4CAF50;
908
+ color: white;
909
+ }
910
+ """
911
+
912
+ # Define theme
913
+ theme = gr.themes.Soft(
914
+ primary_hue="green",
915
+ secondary_hue="blue",
916
+ ).set(
917
+ body_text_color="#333333",
918
+ block_title_text_weight="600",
919
+ block_border_width="1px",
920
+ block_shadow="0px 5px 10px rgba(0, 0, 0, 0.1)",
921
+ button_primary_background_fill="#4CAF50",
922
+ button_primary_background_fill_hover="#45a049",
923
+ )
924
+
925
+ # Create Gradio blocks
926
+ with gr.Blocks(css=custom_css, theme=theme) as demo:
927
+ # Header
928
+ gr.HTML("""
929
+ <div style="text-align: center; max-width: 850px; margin: 0 auto;">
930
+ <h1>🧾 Restaurant Bill Nutritional Analyzer 🍔</h1>
931
+ <p>Upload a photo of your restaurant bill or receipt, and this tool will analyze what you ate, estimate the nutritional content, and provide a health score.</p>
932
+ <p><em>Note: This tool works best with clear images of English-language bills and menus.</em></p>
933
+ </div>
934
+ """)
935
 
936
+ # Main content
937
  with gr.Tabs():
938
+ # Image upload tab
939
+ with gr.TabItem("Upload Receipt Image"):
940
  with gr.Row():
941
  with gr.Column(scale=1):
942
+ # Input components
943
+ image_input = gr.Image(label="Upload a photo of your restaurant bill")
944
+ analyze_btn = gr.Button("Analyze Receipt", variant="primary")
945
+
946
+ with gr.Column(scale=1):
947
+ # Output components
948
+ ocr_output = gr.Textbox(label="Extracted Text (OCR)", lines=5)
949
+
950
+ with gr.Row():
951
+ with gr.Column(scale=1):
952
+ food_items_output = gr.HTML(label="Detected Food Items")
953
 
954
+ with gr.Column(scale=1):
955
+ nutrition_summary = gr.Textbox(label="Nutrition Summary", lines=4)
956
+
957
+ with gr.Row():
958
+ gr.HTML("<h3>Detailed Nutritional Analysis</h3>")
959
 
960
  with gr.Row():
961
+ with gr.Column(scale=1):
962
+ analysis_chart = gr.Image(label="Analysis Chart")
963
+
964
+ with gr.Column(scale=1):
965
+ analysis_details = gr.HTML(label="Analysis Details")
966
+
967
+ # Example selector
968
+ example_images = get_example_images()
969
+ gr.Examples(examples=example_images, inputs=image_input, outputs=[
970
+ ocr_output, food_items_output, nutrition_summary, analysis_chart, analysis_details
971
+ ], fn=process_bill_image, examples_per_page=2)
972
+
973
+ # Set up the button click event
974
+ analyze_btn.click(
975
+ fn=process_bill_image,
976
+ inputs=[image_input],
977
+ outputs=[ocr_output, food_items_output, nutrition_summary, analysis_chart, analysis_details]
978
+ )
979
 
980
+ # Manual text input tab
981
+ with gr.TabItem("Enter Food Items Manually"):
982
  with gr.Row():
983
  with gr.Column(scale=1):
984
+ text_input = gr.Textbox(
985
+ label="Enter what you ate (e.g., 'burger, fries, and a soda')",
986
+ lines=3,
987
+ placeholder="Example: I had a cheeseburger with fries and a coke for lunch."
988
  )
989
+ analyze_text_btn = gr.Button("Analyze Food Items", variant="primary")
990
+
991
+ with gr.Row():
992
+ with gr.Column(scale=1):
993
+ text_food_items = gr.HTML(label="Detected Food Items")
994
 
995
+ with gr.Column(scale=1):
996
+ text_summary = gr.Textbox(label="Nutrition Summary", lines=4)
997
 
998
  with gr.Row():
999
+ with gr.Column(scale=1):
1000
+ text_analysis_chart = gr.Image(label="Analysis Chart")
1001
+
1002
+ with gr.Column(scale=1):
1003
+ text_analysis_details = gr.HTML(label="Analysis Details")
1004
+
1005
+ # Examples for text input
1006
+ gr.Examples(
1007
+ examples=[
1008
+ "I had a burger, fries and a coke",
1009
+ "For dinner I ordered pizza and ice cream",
1010
+ "Grilled chicken salad with water",
1011
+ "Steak, baked potato and broccoli with red wine"
1012
+ ],
1013
+ inputs=text_input
1014
+ )
1015
+
1016
+ # Set up the button click event
1017
+ analyze_text_btn.click(
1018
+ fn=process_text_input,
1019
+ inputs=[text_input],
1020
+ outputs=[text_food_items, text_summary, text_analysis_chart, text_analysis_details]
1021
+ )
1022
 
1023
+ # About tab
1024
+ with gr.TabItem("About"):
1025
+ gr.HTML("""
1026
+ <div style="text-align: left; max-width: 850px; margin: 0 auto;">
1027
+ <h2>About This Tool</h2>
1028
+ <p>This nutritional analyzer uses OCR (Optical Character Recognition) to extract text from restaurant bills and receipts.
1029
+ It then uses natural language processing techniques to identify food items and match them to a nutrition database.</p>
1030
+
1031
+ <h3>How It Works</h3>
1032
+ <ol>
1033
+ <li><strong>Image Processing:</strong> Your uploaded image is enhanced for better text recognition</li>
1034
+ <li><strong>Text Extraction:</strong> OCR technology reads the text from the image</li>
1035
+ <li><strong>Food Detection:</strong> NLP algorithms identify food items in the text</li>
1036
+ <li><strong>Nutrition Matching:</strong> Food items are matched to a nutrition database</li>
1037
+ <li><strong>Analysis:</strong> Nutritional totals are calculated and a health score is assigned</li>
1038
+ </ol>
1039
+
1040
+ <h3>Limitations</h3>
1041
+ <p>Please note the following limitations:</p>
1042
+ <ul>
1043
+ <li>Works best with clear, well-lit images</li>
1044
+ <li>Designed primarily for English-language bills and receipts</li>
1045
+ <li>May not recognize all specialized or regional dishes</li>
1046
+ <li>Nutritional estimates are approximate and based on standard portions</li>
1047
+ <li>Health scores are relative indicators and not medical advice</li>
1048
+ </ul>
1049
+
1050
+ <h3>Privacy Notice</h3>
1051
+ <p>Images uploaded to this tool are processed for the sole purpose of extracting food information.
1052
+ Images and extracted data are not permanently stored.</p>
1053
+
1054
+ <div class="footer-note">
1055
+ <p>This tool is intended for informational purposes only and is not a substitute for professional nutritional or medical advice.</p>
1056
+ </div>
1057
+ </div>
1058
+ """)
1059
 
1060
+ # Footer
1061
+ gr.HTML("""
1062
+ <div class="footer">
1063
+ <p>🍽️ Restaurant Bill Nutritional Analyzer | Built with Gradio and Hugging Face | 2023</p>
1064
+ </div>
1065
+ """)
1066
 
1067
  return demo
1068
 
1069
+ # Create and launch the app
1070
+ demo = create_gradio_interface()
1071
+
1072
+ # Run the app
1073
  if __name__ == "__main__":
 
1074
  demo.launch()