Neylton commited on
Commit
5a56fe4
·
0 Parent(s):

Clean deployment with model download functionality

Browse files
Files changed (3) hide show
  1. app.py +574 -0
  2. health_data.json +73 -0
  3. requirements.txt +9 -0
app.py ADDED
@@ -0,0 +1,574 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ from PIL import Image
3
+ import torch
4
+ from torchvision import models
5
+ import torch.nn as nn
6
+ import torchvision.transforms as transforms
7
+ import io
8
+ import os
9
+ import openai
10
+ import json
11
+ import timm # For ConvNeXt Large model
12
+ from huggingface_hub import hf_hub_download
13
+
14
+ # --- Page Configuration ---
15
+ st.set_page_config(page_title="EatSmart Pro", page_icon="🍽️", layout="wide")
16
+
17
+ # Mobile menu indicator
18
+ st.markdown("""
19
+ <div style="display: block; background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
20
+ padding: 8px 15px; border-radius: 8px; margin-bottom: 15px; text-align: center;">
21
+ <p style="color: white; margin: 0; font-size: 14px;">
22
+ 📱 <strong>Mobile Users:</strong> Click the <strong>></strong> arrow (top-left) to open preferences menu
23
+ </p>
24
+ </div>
25
+ """, unsafe_allow_html=True)
26
+
27
+ # ==============================================================================
28
+ # The correct, full list of 101 class names from your training script.
29
+ # ==============================================================================
30
+ CLASS_NAMES = [
31
+ 'apple_pie', 'baby_back_ribs', 'baklava', 'beef_carpaccio', 'beef_tartare',
32
+ 'beet_salad', 'beignets', 'bibimbap', 'bread_pudding', 'breakfast_burrito',
33
+ 'bruschetta', 'caesar_salad', 'cannoli', 'caprese_salad', 'carrot_cake',
34
+ 'ceviche', 'cheesecake', 'cheese_plate', 'chicken_curry', 'chicken_quesadilla',
35
+ 'chicken_wings', 'chocolate_cake', 'chocolate_mousse', 'churros', 'clam_chowder',
36
+ 'club_sandwich', 'crab_cakes', 'creme_brulee', 'croque_madame', 'cup_cakes',
37
+ 'deviled_eggs', 'donuts', 'dumplings', 'edamame', 'eggs_benedict',
38
+ 'escargots', 'falafel', 'filet_mignon', 'fish_and_chips', 'foie_gras',
39
+ 'french_fries', 'french_onion_soup', 'french_toast', 'fried_calamari', 'fried_rice',
40
+ 'frozen_yogurt', 'garlic_bread', 'gnocchi', 'greek_salad', 'grilled_cheese_sandwich',
41
+ 'grilled_salmon', 'guacamole', 'gyoza', 'hamburger', 'hot_and_sour_soup',
42
+ 'hot_dog', 'huevos_rancheros', 'hummus', 'ice_cream', 'lasagna',
43
+ 'lobster_bisque', 'lobster_roll_sandwich', 'macaroni_and_cheese', 'macarons', 'miso_soup',
44
+ 'mussels', 'nachos', 'omelette', 'onion_rings', 'oysters',
45
+ 'pad_thai', 'paella', 'pancakes', 'panna_cotta', 'peking_duck',
46
+ 'pho', 'pizza', 'pork_chop', 'poutine', 'prime_rib',
47
+ 'pulled_pork_sandwich', 'ramen', 'ravioli', 'red_velvet_cake', 'risotto',
48
+ 'samosa', 'sashimi', 'scallops', 'seaweed_salad', 'shrimp_and_grits',
49
+ 'spaghetti_bolognese', 'spaghetti_carbonara', 'spring_rolls', 'steak', 'strawberry_shortcake',
50
+ 'sushi', 'tacos', 'takoyaki', 'tiramisu', 'tuna_tartare',
51
+ 'waffles'
52
+ ]
53
+
54
+ def download_model_if_needed():
55
+ """Download ConvNeXt Large model from model repository if not present"""
56
+ model_path = "food_classifier_convnext_large_cpu_full.pth"
57
+
58
+ if not os.path.exists(model_path):
59
+ st.info("🔄 Downloading ConvNeXt Large model (2.3GB)... This may take a few minutes on first load.")
60
+ progress_bar = st.progress(0)
61
+ status_text = st.empty()
62
+
63
+ try:
64
+ status_text.text("📦 Downloading from Lumilife/eatSmartPro-models...")
65
+ progress_bar.progress(25)
66
+
67
+ # Download from your model repository
68
+ downloaded_path = hf_hub_download(
69
+ repo_id="Lumilife/eatSmartPro-models",
70
+ filename="food_classifier_convnext_large_cpu_full.pth",
71
+ local_dir=".",
72
+ local_dir_use_symlinks=False
73
+ )
74
+
75
+ progress_bar.progress(100)
76
+ status_text.text("✅ Model downloaded successfully!")
77
+
78
+ # Clear progress indicators after 2 seconds
79
+ import time
80
+ time.sleep(2)
81
+ progress_bar.empty()
82
+ status_text.empty()
83
+
84
+ return downloaded_path
85
+
86
+ except Exception as e:
87
+ st.error(f"❌ Failed to download model: {str(e)}")
88
+ return None
89
+
90
+ return model_path
91
+
92
+ def get_convnext_model(num_classes):
93
+ """
94
+ Creates the ConvNeXt Large model architecture,
95
+ matching the training script for maximum accuracy.
96
+ """
97
+ print(f"🚀 Loading ConvNeXt Large model for inference...")
98
+ print(f"📊 Model: ConvNeXt Large (197M parameters)")
99
+
100
+ # Create ConvNeXt Large model (same as training script)
101
+ model = timm.create_model('convnext_large.fb_in22k_ft_in1k', pretrained=True, num_classes=num_classes)
102
+
103
+ # Model statistics for user feedback
104
+ total_params = sum(p.numel() for p in model.parameters())
105
+ print(f"✅ Model architecture loaded:")
106
+ print(f" Total parameters: {total_params:,}")
107
+ print(f" Model size: ~{total_params * 4 / 1024**2:.1f} MB")
108
+
109
+ return model
110
+
111
+ def load_json_data(path):
112
+ if not os.path.exists(path): return {}
113
+ try:
114
+ with open(path, 'r') as f: return json.load(f)
115
+ except (FileNotFoundError, json.JSONDecodeError): return {}
116
+
117
+ def get_health_info(food_name, health_data):
118
+ """Enhanced health information display with comprehensive nutritional data"""
119
+ food_name_key = food_name.replace('_', ' ').title()
120
+
121
+ # Try to get from the nested nutrition_info structure first
122
+ nutrition_info = health_data.get("nutrition_info", {})
123
+ info = nutrition_info.get(food_name_key) or nutrition_info.get(food_name.lower())
124
+
125
+ # Try direct lookup if nested lookup fails
126
+ if not info:
127
+ info = health_data.get(food_name_key)
128
+
129
+ if not info:
130
+ return "<p>No specific health information available for this dish.</p>"
131
+
132
+ # Get health score
133
+ health_scores = health_data.get("health_scores", {})
134
+ health_score = health_scores.get(food_name.lower(), {})
135
+ score = health_score.get("score", 75)
136
+ score_explanation = health_score.get("explanation", "Good")
137
+
138
+ # Determine score color
139
+ if score >= 80:
140
+ score_color = "#28a745" # Green
141
+ score_bg = "#d4edda"
142
+ elif score >= 60:
143
+ score_color = "#ffc107" # Yellow
144
+ score_bg = "#fff3cd"
145
+ else:
146
+ score_color = "#dc3545" # Red
147
+ score_bg = "#f8d7da"
148
+
149
+ # Build nutritional information
150
+ nutrition_html = f"""
151
+ <div style="background-color: #f8f9fa; padding: 15px; border-radius: 8px; margin: 10px 0;">
152
+ <h4 style="color: #495057; margin-top: 0;">📊 Nutritional Information</h4>
153
+ <div style="display: grid; grid-template-columns: repeat(4, 1fr); gap: 15px; text-align: center;">
154
+ <div>
155
+ <div style="font-size: 14px; color: #6c757d;">Calories</div>
156
+ <div style="font-size: 24px; font-weight: bold; color: #495057;">{info.get("calories", "N/A")}</div>
157
+ <div style="font-size: 12px; color: #6c757d;">kcal</div>
158
+ </div>
159
+ <div>
160
+ <div style="font-size: 14px; color: #6c757d;">Protein</div>
161
+ <div style="font-size: 24px; font-weight: bold; color: #495057;">{info.get("protein", "N/A")}</div>
162
+ <div style="font-size: 12px; color: #6c757d;">g</div>
163
+ </div>
164
+ <div>
165
+ <div style="font-size: 14px; color: #6c757d;">Carbs</div>
166
+ <div style="font-size: 24px; font-weight: bold; color: #495057;">{info.get("carbs", "N/A")}</div>
167
+ <div style="font-size: 12px; color: #6c757d;">g</div>
168
+ </div>
169
+ <div>
170
+ <div style="font-size: 14px; color: #6c757d;">Fat</div>
171
+ <div style="font-size: 24px; font-weight: bold; color: #495057;">{info.get("fat", "N/A")}</div>
172
+ <div style="font-size: 12px; color: #6c757d;">g</div>
173
+ </div>
174
+ </div>
175
+ </div>
176
+ """
177
+
178
+ # Health Score
179
+ score_html = f"""
180
+ <div style="background-color: {score_bg}; border: 1px solid {score_color}; border-radius: 8px; padding: 15px; margin: 10px 0;">
181
+ <h4 style="color: {score_color}; margin-top: 0;">🏥 Health Assessment</h4>
182
+ <div style="background-color: {score_color}; color: white; padding: 10px; border-radius: 5px; text-align: center; font-weight: bold;">
183
+ Health Score: {score}/100 ({score_explanation})
184
+ </div>
185
+ </div>
186
+ """
187
+
188
+ # Health Benefits - Display using Streamlit components instead of HTML
189
+ benefits_section = ""
190
+ if info.get("benefits"):
191
+ # We'll handle benefits separately using st.markdown with proper formatting
192
+ benefits_section = "BENEFITS_SECTION" # Placeholder to indicate benefits exist
193
+
194
+ return nutrition_html + score_html + benefits_section
195
+
196
+ def get_allergen_info(food_name, allergen_data):
197
+ """Enhanced allergen information using sidebar preferences"""
198
+ food_name_key = food_name.replace('_', ' ').title()
199
+ allergens = allergen_data.get(food_name_key, [])
200
+
201
+ # Display allergen information for the detected food
202
+ if allergens:
203
+ # Check for user-specified allergen matches
204
+ user_allergen_matches = [a for a in allergens if a in st.session_state.user_allergens]
205
+
206
+ if user_allergen_matches:
207
+ # High priority alert for user's allergens
208
+ st.error(f"🚨 **CRITICAL ALLERGEN ALERT**: This dish contains **{', '.join(user_allergen_matches)}** which you've marked as allergens to avoid!")
209
+
210
+ # Display allergens in a more user-friendly way
211
+ st.markdown("### ⚠️ Allergens Detected in This Food")
212
+
213
+ # Create columns for allergen badges
214
+ cols = st.columns(min(len(allergens), 4))
215
+ for i, allergen in enumerate(allergens):
216
+ with cols[i % len(cols)]:
217
+ if allergen in st.session_state.user_allergens:
218
+ # User's marked allergens - show as error
219
+ st.error(f"🚨 {allergen}")
220
+ else:
221
+ # Other allergens - show as info
222
+ st.info(f"ℹ️ {allergen}")
223
+
224
+ # Add explanation
225
+ if user_allergen_matches:
226
+ st.markdown("---")
227
+ st.markdown("🔴 **Red alerts** are for allergens you've marked in your preferences")
228
+ else:
229
+ st.markdown("---")
230
+ st.markdown("💙 **Blue badges** show allergens present in this food")
231
+
232
+ else:
233
+ st.success("✅ No common allergens typically found in this dish.")
234
+
235
+ def get_trans_fat_analysis(food_name, health_data):
236
+ """Enhanced trans fat analysis with user preferences"""
237
+ food_name_lower = food_name.lower().replace('_', ' ')
238
+
239
+ # Get trans fat ingredients from health data
240
+ trans_fat_info = health_data.get("trans_fat_info", {})
241
+ trans_fat_data = trans_fat_info.get(food_name_lower, {})
242
+
243
+ if not trans_fat_data:
244
+ return "<div style='background-color: #d4edda; border: 1px solid #c3e6cb; border-radius: 8px; padding: 15px; margin: 10px 0;'><h4 style='color: #155724; margin-top: 0;'>🧪 Trans Fat Analysis</h4><p style='color: #155724; margin: 0;'>✅ This food is generally low in trans fats.</p></div>"
245
+
246
+ risk_level = trans_fat_data.get("risk", "low").lower()
247
+ ingredients = trans_fat_data.get("ingredients", [])
248
+
249
+ # Determine colors based on risk level
250
+ if risk_level == "high":
251
+ bg_color = "#f8d7da"
252
+ border_color = "#f5c6cb"
253
+ text_color = "#721c24"
254
+ icon = "🚨"
255
+ title = "HIGH TRANS FAT WARNING"
256
+ elif risk_level == "medium":
257
+ bg_color = "#fff3cd"
258
+ border_color = "#ffeaa7"
259
+ text_color = "#856404"
260
+ icon = "⚠️"
261
+ title = "MODERATE TRANS FAT CONTENT"
262
+ else:
263
+ bg_color = "#d4edda"
264
+ border_color = "#c3e6cb"
265
+ text_color = "#155724"
266
+ icon = "✅"
267
+ title = "LOW TRANS FAT CONTENT"
268
+
269
+ # Build ingredients list
270
+ ingredients_html = ""
271
+ if ingredients:
272
+ ingredients_html = f"<p style='color: {text_color}; margin: 10px 0 0 0;'><strong>Potential sources:</strong> {', '.join(ingredients)}</p>"
273
+
274
+ # User preference check
275
+ user_warning = ""
276
+ if st.session_state.avoid_trans_fat and risk_level in ["high", "medium"]:
277
+ user_warning = f"<div style='background-color: #f8d7da; border: 2px solid #dc3545; border-radius: 5px; padding: 10px; margin: 10px 0;'><strong style='color: #721c24;'>⚠️ PERSONAL ALERT: You've chosen to avoid trans fats!</strong></div>"
278
+
279
+ return f"""
280
+ <div style="background-color: {bg_color}; border: 1px solid {border_color}; border-radius: 8px; padding: 15px; margin: 10px 0;">
281
+ <h4 style="color: {text_color}; margin-top: 0;">🧪 Trans Fat Analysis</h4>
282
+ <div style="color: {text_color}; font-weight: bold; font-size: 1.1em;">
283
+ {icon} {title}
284
+ </div>
285
+ {ingredients_html}
286
+ {user_warning}
287
+ </div>
288
+ """
289
+
290
+ def generate_recipe(food_name):
291
+ """Generate a personalized recipe based on user preferences"""
292
+ # Get user preferences
293
+ dietary_prefs = st.session_state.get('dietary_preferences', [])
294
+ allergens = st.session_state.get('user_allergens', [])
295
+ avoid_trans_fat = st.session_state.get('avoid_trans_fat', False)
296
+
297
+ # Build preference string
298
+ pref_string = ""
299
+ if dietary_prefs:
300
+ pref_string += f"Dietary preferences: {', '.join(dietary_prefs)}. "
301
+ if allergens:
302
+ pref_string += f"Avoid allergens: {', '.join(allergens)}. "
303
+ if avoid_trans_fat:
304
+ pref_string += "Avoid trans fats. "
305
+
306
+ return f"""**Healthy {food_name.replace('_', ' ').title()} Recipe**
307
+
308
+ **Ingredients:**
309
+ - Fresh, high-quality ingredients
310
+ - Seasonal vegetables
311
+ - Lean proteins (if applicable)
312
+ - Healthy fats and whole grains
313
+
314
+ **Instructions:**
315
+ 1. Prepare ingredients with care
316
+ 2. Use healthy cooking methods (baking, grilling, steaming)
317
+ 3. Season with herbs and spices instead of excess salt
318
+ 4. Cook until perfectly done
319
+
320
+ **Nutrition Benefits:**
321
+ - Rich in essential nutrients
322
+ - Balanced macronutrients
323
+ - Supports overall health
324
+
325
+ **Prep Time:** 30 minutes
326
+ **Servings:** 4 people
327
+
328
+ *Personalized for your preferences: {pref_string or 'No specific preferences set'}*"""
329
+
330
+ @st.cache_resource
331
+ def load_model_resources():
332
+ try:
333
+ num_classes = len(CLASS_NAMES)
334
+
335
+ # Download model if needed
336
+ model_path = download_model_if_needed()
337
+
338
+ if not model_path or not os.path.exists(model_path):
339
+ st.error(f"❌ Failed to load ConvNeXt Large model")
340
+ return None, None, None, None
341
+
342
+ # Load model
343
+ model = get_convnext_model(num_classes=num_classes)
344
+
345
+ device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
346
+ checkpoint = torch.load(model_path, map_location=device)
347
+ model.load_state_dict(checkpoint['model_state_dict'])
348
+ model.to(device)
349
+ model.eval()
350
+
351
+ # Store model info for UI display
352
+ model_info = {
353
+ 'path': model_path,
354
+ 'type': 'ConvNeXt Large',
355
+ 'accuracy': checkpoint.get('accuracy', 'Unknown')
356
+ }
357
+
358
+ health_data = load_json_data('health_data.json')
359
+ allergen_data = load_json_data('allergen_data.json')
360
+ return model, health_data, allergen_data, model_info
361
+ except Exception as e:
362
+ st.error(f"A critical error occurred while loading the model: {e}")
363
+ return None, None, None, None
364
+
365
+ model, health_data, allergen_data, model_info = load_model_resources()
366
+
367
+ def transform_image(image_bytes):
368
+ transform = transforms.Compose([
369
+ transforms.Resize((224, 224)), transforms.ToTensor(),
370
+ transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])])
371
+ image = Image.open(io.BytesIO(image_bytes)).convert("RGB")
372
+ return transform(image).unsqueeze(0)
373
+
374
+ def get_prediction(image_tensor):
375
+ if model is None: return "Error: Model not loaded", 0.0
376
+ with torch.no_grad():
377
+ outputs = model(image_tensor.to(torch.device('cuda' if torch.cuda.is_available() else 'cpu')))
378
+ probabilities = torch.nn.functional.softmax(outputs, dim=1)
379
+ confidence, predicted_idx = torch.max(probabilities, 1)
380
+ predicted_idx_item = predicted_idx.item()
381
+ if predicted_idx_item >= len(CLASS_NAMES):
382
+ return "Prediction Error: Index out of bounds.", 0.0
383
+ predicted_class = CLASS_NAMES[predicted_idx_item].replace('_', ' ').title()
384
+ return predicted_class, confidence.item()
385
+
386
+ # --- UI Layout ---
387
+ st.markdown("""
388
+ <div style="text-align: center; padding: 20px 0;">
389
+ <h1 style="font-size: 3em; margin: 0;">
390
+ 🍽️ <span style="color: #28a745;">Eat</span><span style="color: #dc3545;">Smart</span>
391
+ <span style="color: #17a2b8;">Pro</span>
392
+ </h1>
393
+ <p style="font-size: 1.2em; color: #6c757d; margin: 10px 0;">
394
+ 🌟 Your <span style="color: #28a745;">AI-Powered</span>
395
+ <span style="color: #dc3545;">Nutrition</span> Assistant 🌟
396
+ </p>
397
+ <div style="display: flex; justify-content: center; gap: 10px; margin: 15px 0;">
398
+ <span style="background: linear-gradient(45deg, #28a745, #20c997); color: white; padding: 5px 15px; border-radius: 20px; font-size: 0.9em;">
399
+ 🥗 Healthy Analysis
400
+ </span>
401
+ <span style="background: linear-gradient(45deg, #dc3545, #fd7e14); color: white; padding: 5px 15px; border-radius: 20px; font-size: 0.9em;">
402
+ ⚠️ Allergen Alerts
403
+ </span>
404
+ <span style="background: linear-gradient(45deg, #17a2b8, #6f42c1); color: white; padding: 5px 15px; border-radius: 20px; font-size: 0.9em;">
405
+ 🍳 Smart Recipes
406
+ </span>
407
+ </div>
408
+ </div>
409
+ """, unsafe_allow_html=True)
410
+
411
+ # Model Status Indicator
412
+ if model_info:
413
+ model_type = model_info['type']
414
+ model_accuracy = model_info['accuracy']
415
+
416
+ status_color = "#28a745" # Green for ConvNeXt model
417
+ status_bg = "#d4edda"
418
+ status_icon = "🚀"
419
+ status_text = f"HIGH ACCURACY MODEL ACTIVE"
420
+
421
+ st.markdown(f"""
422
+ <div style="background-color: {status_bg}; border: 2px solid {status_color}; border-radius: 10px; padding: 15px; margin: 15px 0; text-align: center;">
423
+ <div style="color: {status_color}; font-size: 1.2em; font-weight: bold;">
424
+ {status_icon} {status_text}
425
+ </div>
426
+ <div style="color: #495057; font-size: 0.9em; margin-top: 5px;">
427
+ Model: {model_type} | Validation Accuracy: {model_accuracy}
428
+ </div>
429
+ </div>
430
+ """, unsafe_allow_html=True)
431
+
432
+ # User Preferences Sidebar
433
+ with st.sidebar:
434
+ st.markdown("""
435
+ <div style="text-align: center; padding: 15px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 10px; margin-bottom: 20px;">
436
+ <h3 style="color: white; margin: 0;">⚙️ Your Preferences</h3>
437
+ <p style="color: #f8f9fa; margin: 5px 0; font-size: 0.9em;">Set your dietary needs</p>
438
+ </div>
439
+ """, unsafe_allow_html=True)
440
+
441
+ # Initialize session state for preferences
442
+ if 'user_allergens' not in st.session_state:
443
+ st.session_state.user_allergens = []
444
+ if 'avoid_trans_fat' not in st.session_state:
445
+ st.session_state.avoid_trans_fat = False
446
+ if 'dietary_preferences' not in st.session_state:
447
+ st.session_state.dietary_preferences = []
448
+
449
+ # Allergen Preferences
450
+ st.markdown("### 🚨 Allergen Alerts")
451
+ st.markdown("*Select allergens you want to be warned about:*")
452
+
453
+ common_allergens = ["Gluten", "Dairy", "Egg", "Fish", "Shellfish", "Nuts", "Peanuts", "Soy", "Sesame"]
454
+
455
+ for allergen in common_allergens:
456
+ if st.checkbox(f"🛡️ {allergen}", key=f"allergen_{allergen}",
457
+ value=allergen in st.session_state.user_allergens):
458
+ if allergen not in st.session_state.user_allergens:
459
+ st.session_state.user_allergens.append(allergen)
460
+ else:
461
+ if allergen in st.session_state.user_allergens:
462
+ st.session_state.user_allergens.remove(allergen)
463
+
464
+ # Trans Fat Preference
465
+ st.markdown("### 🧪 Trans Fat Settings")
466
+ st.session_state.avoid_trans_fat = st.checkbox(
467
+ "⚠️ Alert me about trans fats",
468
+ value=st.session_state.avoid_trans_fat,
469
+ help="Get warnings about foods that may contain trans fats"
470
+ )
471
+
472
+ # Dietary Preferences
473
+ st.markdown("### 🌱 Dietary Preferences")
474
+ dietary_options = ["Vegetarian", "Vegan", "Keto", "Low-Carb", "High-Protein", "Gluten-Free"]
475
+
476
+ for diet in dietary_options:
477
+ if st.checkbox(f"🥬 {diet}", key=f"diet_{diet}",
478
+ value=diet in st.session_state.dietary_preferences):
479
+ if diet not in st.session_state.dietary_preferences:
480
+ st.session_state.dietary_preferences.append(diet)
481
+ else:
482
+ if diet in st.session_state.dietary_preferences:
483
+ st.session_state.dietary_preferences.remove(diet)
484
+
485
+ # Display current preferences summary
486
+ if st.session_state.user_allergens or st.session_state.dietary_preferences or st.session_state.avoid_trans_fat:
487
+ st.markdown("---")
488
+ st.markdown("### 📋 Active Preferences")
489
+ if st.session_state.user_allergens:
490
+ st.markdown(f"🚨 **Allergen Alerts:** {', '.join(st.session_state.user_allergens)}")
491
+ if st.session_state.dietary_preferences:
492
+ st.markdown(f"🌱 **Diet:** {', '.join(st.session_state.dietary_preferences)}")
493
+ if st.session_state.avoid_trans_fat:
494
+ st.markdown("🧪 **Trans Fat Alerts:** Enabled")
495
+
496
+ if 'image_buffer' not in st.session_state: st.session_state.image_buffer = None
497
+ if 'prediction_result' not in st.session_state: st.session_state.prediction_result = None
498
+ if 'last_image_buffer' not in st.session_state: st.session_state.last_image_buffer = None
499
+
500
+ col1, col2 = st.columns([1, 1.2])
501
+ with col1:
502
+ st.markdown("""
503
+ <div style="text-align: center; padding: 15px; background: linear-gradient(135deg, #74b9ff 0%, #0984e3 100%); border-radius: 10px; margin-bottom: 20px;">
504
+ <h3 style="color: white; margin: 0;">📸 Upload Food Image</h3>
505
+ <p style="color: #ddd; margin: 5px 0; font-size: 0.9em;">Drag & drop or browse to analyze</p>
506
+ </div>
507
+ """, unsafe_allow_html=True)
508
+
509
+ uploaded_file = st.file_uploader("Choose your food image", type=["jpg", "jpeg", "png"], label_visibility="collapsed")
510
+
511
+ # Set the image buffer based on the file uploader's state.
512
+ if uploaded_file is not None:
513
+ st.session_state.image_buffer = uploaded_file.getvalue()
514
+ else:
515
+ st.session_state.image_buffer = None
516
+
517
+ # This code block displays the image after it is uploaded.
518
+ if st.session_state.image_buffer is not None:
519
+ st.image(st.session_state.image_buffer, caption='🍽️ Your Food Image', use_column_width=True)
520
+
521
+ with col2:
522
+ st.markdown("""
523
+ <div style="text-align: center; padding: 15px; background: linear-gradient(135deg, #00b894 0%, #00a085 100%); border-radius: 10px; margin-bottom: 20px;">
524
+ <h3 style="color: white; margin: 0;">🔬 Smart Analysis & Recipes</h3>
525
+ <p style="color: #ddd; margin: 5px 0; font-size: 0.9em;">AI-powered nutrition insights</p>
526
+ </div>
527
+ """, unsafe_allow_html=True)
528
+
529
+ if model and st.session_state.image_buffer:
530
+ if st.session_state.image_buffer != st.session_state.last_image_buffer:
531
+ st.session_state.last_image_buffer = st.session_state.image_buffer
532
+ with st.spinner('Analyzing image...'):
533
+ image_tensor = transform_image(st.session_state.image_buffer)
534
+ st.session_state.prediction_result = get_prediction(image_tensor)
535
+ if 'recipe' in st.session_state: del st.session_state.recipe
536
+ if st.session_state.prediction_result:
537
+ food_name, confidence = st.session_state.prediction_result
538
+ st.metric(label="Predicted Food", value=food_name)
539
+ st.progress(confidence, text=f"Confidence: {confidence:.2%}")
540
+ tab1, tab2, tab3, tab4 = st.tabs(["Health Info", "Allergen Alert", "Trans Fat Analysis", "AI Recipes"])
541
+ with tab1:
542
+ health_info_html = get_health_info(food_name, health_data)
543
+ if "BENEFITS_SECTION" in health_info_html:
544
+ # Display HTML parts
545
+ st.markdown(health_info_html.replace("BENEFITS_SECTION", ""), unsafe_allow_html=True)
546
+
547
+ # Display benefits using Streamlit components for proper formatting
548
+ nutrition_info = health_data.get("nutrition_info", {})
549
+ food_info = nutrition_info.get(food_name.replace('_', ' ').title()) or nutrition_info.get(food_name.lower()) or health_data.get(food_name.replace('_', ' ').title())
550
+
551
+ if food_info and food_info.get("benefits"):
552
+ st.markdown("### ✨ Health Benefits")
553
+ for benefit in food_info.get("benefits", []):
554
+ st.markdown(f"• {benefit}")
555
+ else:
556
+ st.markdown(health_info_html, unsafe_allow_html=True)
557
+ with tab2:
558
+ get_allergen_info(food_name, allergen_data)
559
+ with tab3:
560
+ st.markdown(get_trans_fat_analysis(food_name, health_data), unsafe_allow_html=True)
561
+ with tab4:
562
+ st.subheader(f"AI-Generated Recipe for {food_name}")
563
+ if 'recipe' not in st.session_state:
564
+ with st.spinner('Generating personalized recipe...'):
565
+ st.session_state.recipe = generate_recipe(food_name)
566
+ st.markdown(st.session_state.recipe)
567
+ if st.button("🔄 Generate New Recipe", key="new_recipe"):
568
+ with st.spinner('Creating another recipe variation...'):
569
+ st.session_state.recipe = generate_recipe(food_name)
570
+ st.rerun()
571
+ elif not model:
572
+ st.error("⚠️ Model failed to load. Please check the console for errors.")
573
+ else:
574
+ st.info("👆 Upload a food image to get started with AI-powered nutrition analysis!")
health_data.json ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "nutrition_info": {
3
+ "hamburger": {"calories": 550, "protein": 25, "carbs": 40, "fat": 30},
4
+ "pizza": {"calories": 285, "protein": 12, "carbs": 36, "fat": 10},
5
+ "french_fries": {"calories": 312, "protein": 3, "carbs": 41, "fat": 15},
6
+ "ice_cream": {"calories": 207, "protein": 3.5, "carbs": 24, "fat": 11},
7
+ "donuts": {"calories": 452, "protein": 5, "carbs": 51, "fat": 25},
8
+ "chocolate_cake": {"calories": 350, "protein": 5, "carbs": 45, "fat": 20},
9
+ "sushi": {"calories": 150, "protein": 8, "carbs": 28, "fat": 1},
10
+ "grilled_salmon": {"calories": 206, "protein": 22, "carbs": 0, "fat": 12},
11
+ "caesar_salad": {"calories": 480, "protein": 9, "carbs": 12, "fat": 45},
12
+ "pho": {"calories": 400, "protein": 20, "carbs": 50, "fat": 10},
13
+ "tacos": {"calories": 226, "protein": 12, "carbs": 24, "fat": 9},
14
+ "falafel": {"calories": 333, "protein": 13, "carbs": 32, "fat": 18},
15
+ "ramen": {"calories": 436, "protein": 15, "carbs": 62, "fat": 14},
16
+ "pancakes": {"calories": 227, "protein": 6, "carbs": 28, "fat": 10},
17
+ "waffles": {"calories": 291, "protein": 8, "carbs": 33, "fat": 14},
18
+ "steak": {"calories": 679, "protein": 48, "carbs": 0, "fat": 53},
19
+ "spaghetti_bolognese": {"calories": 670, "protein": 32, "carbs": 70, "fat": 28},
20
+ "Greek Salad": {
21
+ "calories": 250,
22
+ "fat": 22,
23
+ "protein": 5,
24
+ "carbs": 8,
25
+ "benefits": [
26
+ "Rich in healthy fats from olive oil and feta.",
27
+ "Good source of vitamins from fresh vegetables."
28
+ ]
29
+ },
30
+ "Grilled Salmon": {
31
+ "calories": 280,
32
+ "fat": 18,
33
+ "protein": 30,
34
+ "carbs": 0,
35
+ "benefits": [
36
+ "Excellent source of high-quality protein.",
37
+ "Rich in Omega-3 fatty acids, which support heart and brain health.",
38
+ "Contains Vitamin D and B vitamins."
39
+ ]
40
+ },
41
+ "Guacamole": {
42
+ "calories": 150,
43
+ "fat": 14,
44
+ "protein": 2,
45
+ "carbs": 10
46
+ }
47
+ },
48
+ "trans_fat_ingredients": [
49
+ "hydrogenated oil",
50
+ "partially hydrogenated oil",
51
+ "margarine",
52
+ "shortening"
53
+ ],
54
+ "health_scores": {
55
+ "hamburger": {"score": 70, "explanation": "Good"},
56
+ "pizza": {"score": 65, "explanation": "Average"},
57
+ "french_fries": {"score": 40, "explanation": "Poor"},
58
+ "ice_cream": {"score": 30, "explanation": "Poor"},
59
+ "donuts": {"score": 20, "explanation": "Poor"},
60
+ "chocolate_cake": {"score": 35, "explanation": "Poor"},
61
+ "sushi": {"score": 85, "explanation": "Excellent"},
62
+ "grilled_salmon": {"score": 95, "explanation": "Excellent"},
63
+ "caesar_salad": {"score": 55, "explanation": "Average"},
64
+ "pho": {"score": 80, "explanation": "Excellent"},
65
+ "tacos": {"score": 75, "explanation": "Good"},
66
+ "falafel": {"score": 80, "explanation": "Excellent"},
67
+ "ramen": {"score": 60, "explanation": "Average"},
68
+ "pancakes": {"score": 50, "explanation": "Average"},
69
+ "waffles": {"score": 45, "explanation": "Poor"},
70
+ "steak": {"score": 70, "explanation": "Good"},
71
+ "spaghetti_bolognese": {"score": 65, "explanation": "Average"}
72
+ }
73
+ }
requirements.txt ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ streamlit
2
+ torch
3
+ torchvision
4
+ numpy
5
+ openai
6
+ Pillow
7
+ timm huggingface_hub
8
+ huggingface_hub
9
+ huggingface_hub