Neylton commited on
Commit
bea5a24
·
1 Parent(s): f0f70ba

Add ConvNeXt Large model and app files

Browse files
allergen_data.json ADDED
@@ -0,0 +1,103 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "Apple Pie": [],
3
+ "Baby Back Ribs": [],
4
+ "Baklava": ["Gluten", "Nuts"],
5
+ "Beef Carpaccio": [],
6
+ "Beef Tartare": ["Egg"],
7
+ "Beet Salad": [],
8
+ "Beignets": ["Gluten", "Dairy"],
9
+ "Bibimbap": ["Soy", "Egg"],
10
+ "Bread Pudding": ["Gluten", "Dairy", "Egg"],
11
+ "Breakfast Burrito": ["Gluten", "Dairy", "Egg"],
12
+ "Bruschetta": ["Gluten"],
13
+ "Caesar Salad": ["Dairy", "Fish"],
14
+ "Cannoli": ["Gluten", "Dairy"],
15
+ "Caprese Salad": ["Dairy"],
16
+ "Carrot Cake": ["Gluten", "Dairy", "Egg", "Nuts"],
17
+ "Ceviche": ["Fish", "Shellfish"],
18
+ "Cheesecake": ["Dairy", "Gluten", "Egg"],
19
+ "Cheese Plate": ["Dairy"],
20
+ "Chicken Curry": [],
21
+ "Chicken Quesadilla": ["Gluten", "Dairy"],
22
+ "Chicken Wings": [],
23
+ "Chocolate Cake": ["Gluten", "Dairy", "Egg"],
24
+ "Chocolate Mousse": ["Dairy", "Egg"],
25
+ "Churros": ["Gluten", "Dairy"],
26
+ "Clam Chowder": ["Shellfish", "Dairy", "Gluten"],
27
+ "Club Sandwich": ["Gluten", "Dairy", "Egg"],
28
+ "Crab Cakes": ["Shellfish", "Gluten", "Egg"],
29
+ "Creme Brulee": ["Dairy", "Egg"],
30
+ "Croque Madame": ["Gluten", "Dairy", "Egg"],
31
+ "Cup Cakes": ["Gluten", "Dairy", "Egg"],
32
+ "Deviled Eggs": ["Egg"],
33
+ "Donuts": ["Gluten", "Dairy", "Egg"],
34
+ "Dumplings": ["Gluten", "Soy"],
35
+ "Edamame": ["Soy"],
36
+ "Eggs Benedict": ["Gluten", "Dairy", "Egg"],
37
+ "Escargots": ["Shellfish", "Dairy"],
38
+ "Falafel": ["Gluten"],
39
+ "Filet Mignon": [],
40
+ "Fish And Chips": ["Fish", "Gluten"],
41
+ "Foie Gras": [],
42
+ "French Fries": [],
43
+ "French Onion Soup": ["Gluten", "Dairy"],
44
+ "French Toast": ["Gluten", "Dairy", "Egg"],
45
+ "Fried Calamari": ["Shellfish", "Gluten"],
46
+ "Fried Rice": ["Soy", "Egg"],
47
+ "Frozen Yogurt": ["Dairy"],
48
+ "Garlic Bread": ["Gluten", "Dairy"],
49
+ "Gnocchi": ["Gluten", "Dairy", "Egg"],
50
+ "Greek Salad": ["Dairy"],
51
+ "Grilled Cheese Sandwich": ["Gluten", "Dairy"],
52
+ "Grilled Salmon": ["Fish"],
53
+ "Guacamole": [],
54
+ "Gyoza": ["Gluten", "Soy"],
55
+ "Hamburger": ["Gluten"],
56
+ "Hot And Sour Soup": ["Soy", "Egg"],
57
+ "Hot Dog": ["Gluten"],
58
+ "Huevos Rancheros": ["Egg"],
59
+ "Hummus": ["Sesame"],
60
+ "Ice Cream": ["Dairy"],
61
+ "Lasagna": ["Gluten", "Dairy", "Egg"],
62
+ "Lobster Bisque": ["Shellfish", "Dairy"],
63
+ "Lobster Roll Sandwich": ["Shellfish", "Gluten", "Dairy"],
64
+ "Macaroni And Cheese": ["Gluten", "Dairy"],
65
+ "Macarons": ["Nuts", "Egg", "Dairy"],
66
+ "Miso Soup": ["Soy", "Fish"],
67
+ "Mussels": ["Shellfish"],
68
+ "Nachos": ["Dairy"],
69
+ "Omelette": ["Egg", "Dairy"],
70
+ "Onion Rings": ["Gluten"],
71
+ "Oysters": ["Shellfish"],
72
+ "Pad Thai": ["Peanuts", "Soy", "Egg", "Shellfish"],
73
+ "Paella": ["Shellfish", "Fish"],
74
+ "Pancakes": ["Gluten", "Dairy", "Egg"],
75
+ "Panna Cotta": ["Dairy"],
76
+ "Peking Duck": ["Gluten", "Soy"],
77
+ "Pho": [],
78
+ "Pizza": ["Gluten", "Dairy"],
79
+ "Pork Chop": [],
80
+ "Poutine": ["Dairy"],
81
+ "Prime Rib": [],
82
+ "Pulled Pork Sandwich": ["Gluten"],
83
+ "Ramen": ["Gluten", "Soy", "Egg"],
84
+ "Ravioli": ["Gluten", "Dairy", "Egg"],
85
+ "Red Velvet Cake": ["Gluten", "Dairy", "Egg"],
86
+ "Risotto": ["Dairy"],
87
+ "Samosa": ["Gluten"],
88
+ "Sashimi": ["Fish"],
89
+ "Scallops": ["Shellfish"],
90
+ "Seaweed Salad": ["Sesame"],
91
+ "Shrimp And Grits": ["Shellfish", "Dairy"],
92
+ "Spaghetti Bolognese": ["Gluten", "Dairy"],
93
+ "Spaghetti Carbonara": ["Gluten", "Dairy", "Egg"],
94
+ "Spring Rolls": ["Gluten", "Soy"],
95
+ "Steak": [],
96
+ "Strawberry Shortcake": ["Gluten", "Dairy"],
97
+ "Sushi": ["Fish", "Soy"],
98
+ "Tacos": [],
99
+ "Takoyaki": ["Gluten", "Shellfish", "Egg"],
100
+ "Tiramisu": ["Gluten", "Dairy", "Egg"],
101
+ "Tuna Tartare": ["Fish", "Soy"],
102
+ "Waffles": ["Gluten", "Dairy", "Egg"]
103
+ }
app.py ADDED
@@ -0,0 +1,581 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
13
+ # --- Page Configuration ---
14
+ st.set_page_config(page_title="EatSmart Pro", page_icon="🍽️", layout="wide")
15
+
16
+ # Mobile menu indicator
17
+ st.markdown("""
18
+ <div style="display: block; background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
19
+ padding: 8px 15px; border-radius: 8px; margin-bottom: 15px; text-align: center;">
20
+ <p style="color: white; margin: 0; font-size: 14px;">
21
+ 📱 <strong>Mobile Users:</strong> Click the <strong>></strong> arrow (top-left) to open preferences menu
22
+ </p>
23
+ </div>
24
+ """, unsafe_allow_html=True)
25
+
26
+ # ==============================================================================
27
+ # The correct, full list of 101 class names from your training script.
28
+ # ==============================================================================
29
+ CLASS_NAMES = [
30
+ 'apple_pie', 'baby_back_ribs', 'baklava', 'beef_carpaccio', 'beef_tartare',
31
+ 'beet_salad', 'beignets', 'bibimbap', 'bread_pudding', 'breakfast_burrito',
32
+ 'bruschetta', 'caesar_salad', 'cannoli', 'caprese_salad', 'carrot_cake',
33
+ 'ceviche', 'cheesecake', 'cheese_plate', 'chicken_curry', 'chicken_quesadilla',
34
+ 'chicken_wings', 'chocolate_cake', 'chocolate_mousse', 'churros', 'clam_chowder',
35
+ 'club_sandwich', 'crab_cakes', 'creme_brulee', 'croque_madame', 'cup_cakes',
36
+ 'deviled_eggs', 'donuts', 'dumplings', 'edamame', 'eggs_benedict',
37
+ 'escargots', 'falafel', 'filet_mignon', 'fish_and_chips', 'foie_gras',
38
+ 'french_fries', 'french_onion_soup', 'french_toast', 'fried_calamari', 'fried_rice',
39
+ 'frozen_yogurt', 'garlic_bread', 'gnocchi', 'greek_salad', 'grilled_cheese_sandwich',
40
+ 'grilled_salmon', 'guacamole', 'gyoza', 'hamburger', 'hot_and_sour_soup',
41
+ 'hot_dog', 'huevos_rancheros', 'hummus', 'ice_cream', 'lasagna',
42
+ 'lobster_bisque', 'lobster_roll_sandwich', 'macaroni_and_cheese', 'macarons', 'miso_soup',
43
+ 'mussels', 'nachos', 'omelette', 'onion_rings', 'oysters',
44
+ 'pad_thai', 'paella', 'pancakes', 'panna_cotta', 'peking_duck',
45
+ 'pho', 'pizza', 'pork_chop', 'poutine', 'prime_rib',
46
+ 'pulled_pork_sandwich', 'ramen', 'ravioli', 'red_velvet_cake', 'risotto',
47
+ 'samosa', 'sashimi', 'scallops', 'seaweed_salad', 'shrimp_and_grits',
48
+ 'spaghetti_bolognese', 'spaghetti_carbonara', 'spring_rolls', 'steak', 'strawberry_shortcake',
49
+ 'sushi', 'tacos', 'takoyaki', 'tiramisu', 'tuna_tartare',
50
+ 'waffles'
51
+ ]
52
+
53
+ # ==============================================================================
54
+ # ALL HELPER FUNCTIONS ARE NOW INSIDE A SINGLE APP.PY
55
+ # ==============================================================================
56
+
57
+ def get_convnext_model(num_classes):
58
+ """
59
+ Creates the ConvNeXt Large model architecture,
60
+ matching the training script for maximum accuracy.
61
+ """
62
+ print(f"🚀 Loading ConvNeXt Large model for inference...")
63
+ print(f"📊 Model: ConvNeXt Large (197M parameters)")
64
+
65
+ # Create ConvNeXt Large model (same as training script)
66
+ model = timm.create_model('convnext_large.fb_in22k_ft_in1k', pretrained=True, num_classes=num_classes)
67
+
68
+ # Model statistics for user feedback
69
+ total_params = sum(p.numel() for p in model.parameters())
70
+ print(f"✅ Model architecture loaded:")
71
+ print(f" Total parameters: {total_params:,}")
72
+ print(f" Model size: ~{total_params * 4 / 1024**2:.1f} MB")
73
+
74
+ return model
75
+
76
+ def get_efficientnet_model(num_classes):
77
+ """
78
+ Creates the old EfficientNet-V2-S model architecture for fallback.
79
+ """
80
+ print(f"⚠️ Loading EfficientNet-V2-S model (fallback)...")
81
+ model = models.efficientnet_v2_s(weights=None)
82
+ num_features = model.classifier[1].in_features
83
+ model.classifier = nn.Sequential(
84
+ nn.Dropout(p=0.2),
85
+ nn.Linear(num_features, 256),
86
+ nn.ReLU(),
87
+ nn.Dropout(p=0.1),
88
+ nn.Linear(256, num_classes)
89
+ )
90
+ return model
91
+
92
+ def load_json_data(path):
93
+ if not os.path.exists(path): return {}
94
+ try:
95
+ with open(path, 'r') as f: return json.load(f)
96
+ except (FileNotFoundError, json.JSONDecodeError): return {}
97
+
98
+ def get_health_info(food_name, health_data):
99
+ """Enhanced health information display with comprehensive nutritional data"""
100
+ food_name_key = food_name.replace('_', ' ').title()
101
+
102
+ # Try to get from the nested nutrition_info structure first
103
+ nutrition_info = health_data.get("nutrition_info", {})
104
+ info = nutrition_info.get(food_name_key) or nutrition_info.get(food_name.lower())
105
+
106
+ # Try direct lookup if nested lookup fails
107
+ if not info:
108
+ info = health_data.get(food_name_key)
109
+
110
+ if not info:
111
+ return "<p>No specific health information available for this dish.</p>"
112
+
113
+ # Get health score
114
+ health_scores = health_data.get("health_scores", {})
115
+ health_score = health_scores.get(food_name.lower(), {})
116
+ score = health_score.get("score", 75)
117
+ score_explanation = health_score.get("explanation", "Good")
118
+
119
+ # Determine score color
120
+ if score >= 80:
121
+ score_color = "#28a745" # Green
122
+ score_bg = "#d4edda"
123
+ elif score >= 60:
124
+ score_color = "#ffc107" # Yellow
125
+ score_bg = "#fff3cd"
126
+ else:
127
+ score_color = "#dc3545" # Red
128
+ score_bg = "#f8d7da"
129
+
130
+ # Build nutritional information
131
+ nutrition_html = f"""
132
+ <div style="background-color: #f8f9fa; padding: 15px; border-radius: 8px; margin: 10px 0;">
133
+ <h4 style="color: #495057; margin-top: 0;">📊 Nutritional Information</h4>
134
+ <div style="display: grid; grid-template-columns: repeat(4, 1fr); gap: 15px; text-align: center;">
135
+ <div>
136
+ <div style="font-size: 14px; color: #6c757d;">Calories</div>
137
+ <div style="font-size: 24px; font-weight: bold; color: #495057;">{info.get("calories", "N/A")}</div>
138
+ <div style="font-size: 12px; color: #6c757d;">kcal</div>
139
+ </div>
140
+ <div>
141
+ <div style="font-size: 14px; color: #6c757d;">Protein</div>
142
+ <div style="font-size: 24px; font-weight: bold; color: #495057;">{info.get("protein", "N/A")}</div>
143
+ <div style="font-size: 12px; color: #6c757d;">g</div>
144
+ </div>
145
+ <div>
146
+ <div style="font-size: 14px; color: #6c757d;">Carbs</div>
147
+ <div style="font-size: 24px; font-weight: bold; color: #495057;">{info.get("carbs", "N/A")}</div>
148
+ <div style="font-size: 12px; color: #6c757d;">g</div>
149
+ </div>
150
+ <div>
151
+ <div style="font-size: 14px; color: #6c757d;">Fat</div>
152
+ <div style="font-size: 24px; font-weight: bold; color: #495057;">{info.get("fat", "N/A")}</div>
153
+ <div style="font-size: 12px; color: #6c757d;">g</div>
154
+ </div>
155
+ </div>
156
+ </div>
157
+ """
158
+
159
+ # Health Score
160
+ score_html = f"""
161
+ <div style="background-color: {score_bg}; border: 1px solid {score_color}; border-radius: 8px; padding: 15px; margin: 10px 0;">
162
+ <h4 style="color: {score_color}; margin-top: 0;">🏥 Health Assessment</h4>
163
+ <div style="background-color: {score_color}; color: white; padding: 10px; border-radius: 5px; text-align: center; font-weight: bold;">
164
+ Health Score: {score}/100 ({score_explanation})
165
+ </div>
166
+ </div>
167
+ """
168
+
169
+ # Health Benefits - Display using Streamlit components instead of HTML
170
+ benefits_section = ""
171
+ if info.get("benefits"):
172
+ # We'll handle benefits separately using st.markdown with proper formatting
173
+ benefits_section = "BENEFITS_SECTION" # Placeholder to indicate benefits exist
174
+
175
+ return nutrition_html + score_html + benefits_section
176
+
177
+ def get_allergen_info(food_name, allergen_data):
178
+ """Enhanced allergen information using sidebar preferences"""
179
+ food_name_key = food_name.replace('_', ' ').title()
180
+ allergens = allergen_data.get(food_name_key, [])
181
+
182
+ # Display allergen information for the detected food
183
+ if allergens:
184
+ # Check for user-specified allergen matches
185
+ user_allergen_matches = [a for a in allergens if a in st.session_state.user_allergens]
186
+
187
+ if user_allergen_matches:
188
+ # High priority alert for user's allergens
189
+ st.error(f"🚨 **CRITICAL ALLERGEN ALERT**: This dish contains **{', '.join(user_allergen_matches)}** which you've marked as allergens to avoid!")
190
+
191
+ # Display allergens in a more user-friendly way
192
+ st.markdown("### ⚠️ Allergens Detected in This Food")
193
+
194
+ # Create columns for allergen badges
195
+ cols = st.columns(min(len(allergens), 4))
196
+ for i, allergen in enumerate(allergens):
197
+ with cols[i % len(cols)]:
198
+ if allergen in st.session_state.user_allergens:
199
+ # User's marked allergens - show as error
200
+ st.error(f"🚨 {allergen}")
201
+ else:
202
+ # Other allergens - show as info
203
+ st.info(f"ℹ️ {allergen}")
204
+
205
+ # Add explanation
206
+ if user_allergen_matches:
207
+ st.markdown("---")
208
+ st.markdown("🔴 **Red alerts** are for allergens you've marked in your preferences")
209
+ else:
210
+ st.markdown("---")
211
+ st.markdown("💙 **Blue badges** show allergens present in this food")
212
+
213
+ else:
214
+ st.success("✅ No common allergens typically found in this dish.")
215
+
216
+ def get_trans_fat_analysis(food_name, health_data):
217
+ """Enhanced trans fat analysis with user preferences"""
218
+ food_name_lower = food_name.lower().replace('_', ' ')
219
+
220
+ # Get trans fat ingredients from health data
221
+ trans_fat_ingredients = health_data.get("trans_fat_ingredients", [
222
+ "hydrogenated oil", "partially hydrogenated oil", "margarine", "shortening"
223
+ ])
224
+
225
+ # Foods that commonly contain trans fats
226
+ high_trans_fat_foods = [
227
+ "donuts", "french_fries", "fried", "margarine", "shortening",
228
+ "processed", "packaged", "fast food", "baked goods", "cookies", "crackers"
229
+ ]
230
+
231
+ # Check if food likely contains trans fats
232
+ likely_trans_fat = any(ingredient in food_name_lower for ingredient in high_trans_fat_foods)
233
+
234
+ if likely_trans_fat:
235
+ alert_level = "HIGH RISK" if st.session_state.avoid_trans_fat else "POTENTIAL RISK"
236
+ if st.session_state.avoid_trans_fat:
237
+ st.error(f"🚨 **TRANS FAT ALERT**: You've enabled trans fat warnings and this food may contain trans fats!")
238
+
239
+ trans_fat_html = f"""
240
+ <div style="background: linear-gradient(135deg, #f8d7da 0%, #f5c6cb 100%); border: 2px solid #dc3545; border-radius: 12px; padding: 20px; margin: 15px 0; box-shadow: 0 4px 6px rgba(0,0,0,0.1);">
241
+ <h4 style="color: #721c24; margin-top: 0; text-align: center;">🧪 Trans Fat Analysis - {alert_level}</h4>
242
+ <div style="color: #721c24; text-align: center;">
243
+ <p><strong>⚠️ This food may contain trans fats from:</strong></p>
244
+ <div style="display: flex; flex-wrap: wrap; justify-content: center; gap: 10px; margin: 15px 0;">
245
+ {"".join([f'<span style="background-color: #dc3545; color: white; padding: 5px 10px; border-radius: 15px; font-size: 0.9em;">{ingredient.title()}</span>' for ingredient in trans_fat_ingredients])}
246
+ </div>
247
+ <p style="font-weight: bold; margin-top: 15px;">💡 <em>Recommendation: Check ingredient labels and consider healthier alternatives</em></p>
248
+ </div>
249
+ </div>
250
+ """
251
+ else:
252
+ trans_fat_html = f"""
253
+ <div style="background: linear-gradient(135deg, #d4edda 0%, #c3e6cb 100%); border: 2px solid #28a745; border-radius: 12px; padding: 20px; margin: 15px 0; box-shadow: 0 4px 6px rgba(0,0,0,0.1);">
254
+ <h4 style="color: #155724; margin-top: 0; text-align: center;">🧪 Trans Fat Analysis - LOW RISK</h4>
255
+ <div style="color: #155724; text-align: center;">
256
+ <p><strong>✅ Great Choice!</strong></p>
257
+ <p>This food typically contains minimal or no artificial trans fats.</p>
258
+ <p style="font-style: italic; margin-top: 10px;">Keep enjoying healthy foods like this! 🌟</p>
259
+ </div>
260
+ </div>
261
+ """
262
+
263
+ return trans_fat_html
264
+
265
+ def generate_recipe(food_name):
266
+ """Enhanced recipe generation based on detected ingredients and user preferences"""
267
+ try:
268
+ api_key = st.secrets.get("OPENAI_API_KEY")
269
+ if not api_key:
270
+ return "Error: OPENAI_API_KEY not found."
271
+
272
+ openai.api_key = api_key
273
+
274
+ # Build prompt based on user preferences
275
+ dietary_restrictions = ""
276
+ if st.session_state.dietary_preferences:
277
+ dietary_restrictions = f"Make it {', '.join(st.session_state.dietary_preferences).lower()}. "
278
+
279
+ allergen_restrictions = ""
280
+ if st.session_state.user_allergens:
281
+ allergen_restrictions = f"Avoid using {', '.join(st.session_state.user_allergens).lower()}. "
282
+
283
+ trans_fat_note = ""
284
+ if st.session_state.avoid_trans_fat:
285
+ trans_fat_note = "Use healthy oils and avoid trans fats. "
286
+
287
+ prompt = f"""Create a recipe for {food_name}. {dietary_restrictions}{allergen_restrictions}{trans_fat_note}
288
+
289
+ Format the response with these exact headings:
290
+ Ingredients:
291
+ Instructions:
292
+ Chef's Tips:
293
+
294
+ Make it practical for home cooking and include nutritional benefits."""
295
+
296
+ response = openai.chat.completions.create(
297
+ model="gpt-3.5-turbo",
298
+ messages=[
299
+ {"role": "system", "content": "You are a professional chef who creates healthy, personalized recipes based on dietary preferences and restrictions."},
300
+ {"role": "user", "content": prompt}
301
+ ],
302
+ temperature=0.7,
303
+ max_tokens=600
304
+ )
305
+
306
+ recipe_text = response.choices[0].message.content
307
+ return recipe_text.replace("**Ingredients:**", "Ingredients:").replace("**Instructions:**", "Instructions:").replace("**Chef's Tips:**", "Chef's Tips:")
308
+ except Exception as e:
309
+ return f"The AI Chef is busy right now. Error: {str(e)}"
310
+
311
+ @st.cache_resource
312
+ def load_model_resources():
313
+ try:
314
+ num_classes = len(CLASS_NAMES)
315
+
316
+ # Look for the new ConvNeXt Large model first, fallback to old models
317
+ convnext_model_path = 'models/food_classifier_convnext_large_cpu_full.pth'
318
+ efficientnet_model_path = 'models/food101_efficientnet_best.pth'
319
+ old_model_path = 'models/final_model.pth'
320
+
321
+ if os.path.exists(convnext_model_path):
322
+ model_path = convnext_model_path
323
+ model = get_convnext_model(num_classes=num_classes)
324
+ print(f"🎯 Using NEW ConvNeXt Large model: {model_path}")
325
+ elif os.path.exists(efficientnet_model_path):
326
+ model_path = efficientnet_model_path
327
+ model = get_efficientnet_model(num_classes=num_classes)
328
+ print(f"⚠️ Using EfficientNet model: {model_path}")
329
+ elif os.path.exists(old_model_path):
330
+ model_path = old_model_path
331
+ model = get_efficientnet_model(num_classes=num_classes)
332
+ print(f"⚠️ Using old fallback model: {model_path}")
333
+ else:
334
+ st.error(f"FATAL: No model file found. Looking for:\n- {convnext_model_path}\n- {efficientnet_model_path}\n- {old_model_path}")
335
+ return None, None, None, None
336
+
337
+ device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
338
+ checkpoint = torch.load(model_path, map_location=device)
339
+ model.load_state_dict(checkpoint['model_state_dict'])
340
+ model.to(device)
341
+ model.eval()
342
+
343
+ # Store model info for UI display
344
+ model_info = {
345
+ 'path': model_path,
346
+ 'type': 'ConvNeXt Large' if 'convnext' in model_path else 'EfficientNet-V2-S',
347
+ 'accuracy': checkpoint.get('accuracy', 'Unknown')
348
+ }
349
+
350
+ health_data = load_json_data('health_data.json')
351
+ allergen_data = load_json_data('allergen_data.json')
352
+ return model, health_data, allergen_data, model_info
353
+ except Exception as e:
354
+ st.error(f"A critical error occurred while loading the model: {e}")
355
+ return None, None, None, None
356
+
357
+ model, health_data, allergen_data, model_info = load_model_resources()
358
+
359
+ def transform_image(image_bytes):
360
+ transform = transforms.Compose([
361
+ transforms.Resize((224, 224)), transforms.ToTensor(),
362
+ transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])])
363
+ image = Image.open(io.BytesIO(image_bytes)).convert("RGB")
364
+ return transform(image).unsqueeze(0)
365
+
366
+ def get_prediction(image_tensor):
367
+ if model is None: return "Error: Model not loaded", 0.0
368
+ with torch.no_grad():
369
+ outputs = model(image_tensor.to(torch.device('cuda' if torch.cuda.is_available() else 'cpu')))
370
+ probabilities = torch.nn.functional.softmax(outputs, dim=1)
371
+ confidence, predicted_idx = torch.max(probabilities, 1)
372
+ predicted_idx_item = predicted_idx.item()
373
+ if predicted_idx_item >= len(CLASS_NAMES):
374
+ return "Prediction Error: Index out of bounds.", 0.0
375
+ predicted_class = CLASS_NAMES[predicted_idx_item].replace('_', ' ').title()
376
+ return predicted_class, confidence.item()
377
+
378
+ # --- UI Layout ---
379
+ st.markdown("""
380
+ <div style="text-align: center; padding: 20px 0;">
381
+ <h1 style="font-size: 3em; margin: 0;">
382
+ 🍽️ <span style="color: #28a745;">Eat</span><span style="color: #dc3545;">Smart</span>
383
+ <span style="color: #17a2b8;">Pro</span>
384
+ </h1>
385
+ <p style="font-size: 1.2em; color: #6c757d; margin: 10px 0;">
386
+ 🌟 Your <span style="color: #28a745;">AI-Powered</span>
387
+ <span style="color: #dc3545;">Nutrition</span> Assistant 🌟
388
+ </p>
389
+ <div style="display: flex; justify-content: center; gap: 10px; margin: 15px 0;">
390
+ <span style="background: linear-gradient(45deg, #28a745, #20c997); color: white; padding: 5px 15px; border-radius: 20px; font-size: 0.9em;">
391
+ 🥗 Healthy Analysis
392
+ </span>
393
+ <span style="background: linear-gradient(45deg, #dc3545, #fd7e14); color: white; padding: 5px 15px; border-radius: 20px; font-size: 0.9em;">
394
+ ⚠️ Allergen Alerts
395
+ </span>
396
+ <span style="background: linear-gradient(45deg, #17a2b8, #6f42c1); color: white; padding: 5px 15px; border-radius: 20px; font-size: 0.9em;">
397
+ 🍳 Smart Recipes
398
+ </span>
399
+ </div>
400
+ </div>
401
+ """, unsafe_allow_html=True)
402
+
403
+ # Model Status Indicator
404
+ if model_info:
405
+ model_type = model_info['type']
406
+ model_accuracy = model_info['accuracy']
407
+
408
+ if 'ConvNeXt' in model_type:
409
+ status_color = "#28a745" # Green for new model
410
+ status_bg = "#d4edda"
411
+ status_icon = "🚀"
412
+ status_text = f"HIGH ACCURACY MODEL ACTIVE"
413
+ else:
414
+ status_color = "#ffc107" # Yellow for old model
415
+ status_bg = "#fff3cd"
416
+ status_icon = "⚠️"
417
+ status_text = f"FALLBACK MODEL (Training in Progress)"
418
+
419
+ st.markdown(f"""
420
+ <div style="background-color: {status_bg}; border: 2px solid {status_color}; border-radius: 10px; padding: 15px; margin: 15px 0; text-align: center;">
421
+ <div style="color: {status_color}; font-size: 1.2em; font-weight: bold;">
422
+ {status_icon} {status_text}
423
+ </div>
424
+ <div style="color: #495057; font-size: 0.9em; margin-top: 5px;">
425
+ Model: {model_type} | Validation Accuracy: {model_accuracy}
426
+ </div>
427
+ </div>
428
+ """, unsafe_allow_html=True)
429
+
430
+ # User Preferences Sidebar
431
+ with st.sidebar:
432
+ st.markdown("""
433
+ <div style="text-align: center; padding: 15px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 10px; margin-bottom: 20px;">
434
+ <h3 style="color: white; margin: 0;">⚙️ Your Preferences</h3>
435
+ <p style="color: #f8f9fa; margin: 5px 0; font-size: 0.9em;">Set your dietary needs</p>
436
+ </div>
437
+ """, unsafe_allow_html=True)
438
+
439
+ # Initialize session state for preferences
440
+ if 'user_allergens' not in st.session_state:
441
+ st.session_state.user_allergens = []
442
+ if 'avoid_trans_fat' not in st.session_state:
443
+ st.session_state.avoid_trans_fat = False
444
+ if 'dietary_preferences' not in st.session_state:
445
+ st.session_state.dietary_preferences = []
446
+
447
+ # Allergen Preferences
448
+ st.markdown("### 🚨 Allergen Alerts")
449
+ st.markdown("*Select allergens you want to be warned about:*")
450
+
451
+ common_allergens = ["Gluten", "Dairy", "Egg", "Fish", "Shellfish", "Nuts", "Peanuts", "Soy", "Sesame"]
452
+
453
+ for allergen in common_allergens:
454
+ if st.checkbox(f"🛡️ {allergen}", key=f"allergen_{allergen}",
455
+ value=allergen in st.session_state.user_allergens):
456
+ if allergen not in st.session_state.user_allergens:
457
+ st.session_state.user_allergens.append(allergen)
458
+ else:
459
+ if allergen in st.session_state.user_allergens:
460
+ st.session_state.user_allergens.remove(allergen)
461
+
462
+ # Trans Fat Preference
463
+ st.markdown("### 🧪 Trans Fat Settings")
464
+ st.session_state.avoid_trans_fat = st.checkbox(
465
+ "⚠️ Alert me about trans fats",
466
+ value=st.session_state.avoid_trans_fat,
467
+ help="Get warnings about foods that may contain trans fats"
468
+ )
469
+
470
+ # Dietary Preferences
471
+ st.markdown("### 🌱 Dietary Preferences")
472
+ dietary_options = ["Vegetarian", "Vegan", "Keto", "Low-Carb", "High-Protein", "Gluten-Free"]
473
+
474
+ for diet in dietary_options:
475
+ if st.checkbox(f"🥬 {diet}", key=f"diet_{diet}",
476
+ value=diet in st.session_state.dietary_preferences):
477
+ if diet not in st.session_state.dietary_preferences:
478
+ st.session_state.dietary_preferences.append(diet)
479
+ else:
480
+ if diet in st.session_state.dietary_preferences:
481
+ st.session_state.dietary_preferences.remove(diet)
482
+
483
+ # Display current preferences summary
484
+ if st.session_state.user_allergens or st.session_state.dietary_preferences or st.session_state.avoid_trans_fat:
485
+ st.markdown("---")
486
+ st.markdown("### 📋 Active Preferences")
487
+ if st.session_state.user_allergens:
488
+ st.markdown(f"🚨 **Allergen Alerts:** {', '.join(st.session_state.user_allergens)}")
489
+ if st.session_state.dietary_preferences:
490
+ st.markdown(f"🌱 **Diet:** {', '.join(st.session_state.dietary_preferences)}")
491
+ if st.session_state.avoid_trans_fat:
492
+ st.markdown("🧪 **Trans Fat Alerts:** Enabled")
493
+
494
+ if 'image_buffer' not in st.session_state: st.session_state.image_buffer = None
495
+ if 'prediction_result' not in st.session_state: st.session_state.prediction_result = None
496
+ if 'last_image_buffer' not in st.session_state: st.session_state.last_image_buffer = None
497
+
498
+ col1, col2 = st.columns([1, 1.2])
499
+ with col1:
500
+ st.markdown("""
501
+ <div style="text-align: center; padding: 15px; background: linear-gradient(135deg, #74b9ff 0%, #0984e3 100%); border-radius: 10px; margin-bottom: 20px;">
502
+ <h3 style="color: white; margin: 0;">📸 Upload Food Image</h3>
503
+ <p style="color: #ddd; margin: 5px 0; font-size: 0.9em;">Drag & drop or browse to analyze</p>
504
+ </div>
505
+ """, unsafe_allow_html=True)
506
+
507
+ uploaded_file = st.file_uploader("Choose your food image", type=["jpg", "jpeg", "png"], label_visibility="collapsed")
508
+
509
+ # Set the image buffer based on the file uploader's state.
510
+ if uploaded_file is not None:
511
+ st.session_state.image_buffer = uploaded_file.getvalue()
512
+ else:
513
+ st.session_state.image_buffer = None
514
+
515
+ # This code block displays the image after it is uploaded.
516
+ if st.session_state.image_buffer is not None:
517
+ st.image(st.session_state.image_buffer, caption='🍽️ Your Food Image', use_column_width=True)
518
+
519
+ with col2:
520
+ st.markdown("""
521
+ <div style="text-align: center; padding: 15px; background: linear-gradient(135deg, #00b894 0%, #00a085 100%); border-radius: 10px; margin-bottom: 20px;">
522
+ <h3 style="color: white; margin: 0;">🔬 Smart Analysis & Recipes</h3>
523
+ <p style="color: #ddd; margin: 5px 0; font-size: 0.9em;">AI-powered nutrition insights</p>
524
+ </div>
525
+ """, unsafe_allow_html=True)
526
+
527
+ if model and st.session_state.image_buffer:
528
+ if st.session_state.image_buffer != st.session_state.last_image_buffer:
529
+ st.session_state.last_image_buffer = st.session_state.image_buffer
530
+ with st.spinner('Analyzing image...'):
531
+ image_tensor = transform_image(st.session_state.image_buffer)
532
+ st.session_state.prediction_result = get_prediction(image_tensor)
533
+ if 'recipe' in st.session_state: del st.session_state.recipe
534
+ if st.session_state.prediction_result:
535
+ food_name, confidence = st.session_state.prediction_result
536
+ st.metric(label="Predicted Food", value=food_name)
537
+ st.progress(confidence, text=f"Confidence: {confidence:.2%}")
538
+ tab1, tab2, tab3, tab4 = st.tabs(["Health Info", "Allergen Alert", "Trans Fat Analysis", "AI Recipes"])
539
+ with tab1:
540
+ health_info_html = get_health_info(food_name, health_data)
541
+ if "BENEFITS_SECTION" in health_info_html:
542
+ # Display HTML parts
543
+ st.markdown(health_info_html.replace("BENEFITS_SECTION", ""), unsafe_allow_html=True)
544
+
545
+ # Display benefits using Streamlit components for proper formatting
546
+ nutrition_info = health_data.get("nutrition_info", {})
547
+ food_info = nutrition_info.get(food_name.replace('_', ' ').title()) or nutrition_info.get(food_name.lower()) or health_data.get(food_name.replace('_', ' ').title())
548
+
549
+ if food_info and food_info.get("benefits"):
550
+ st.markdown("### ✨ Health Benefits")
551
+ for benefit in food_info.get("benefits", []):
552
+ st.markdown(f"• {benefit}")
553
+ else:
554
+ st.markdown(health_info_html, unsafe_allow_html=True)
555
+ with tab2:
556
+ get_allergen_info(food_name, allergen_data)
557
+ with tab3:
558
+ st.markdown(get_trans_fat_analysis(food_name, health_data), unsafe_allow_html=True)
559
+ with tab4:
560
+ st.subheader(f"AI-Generated Recipe for {food_name}")
561
+ if 'recipe' not in st.session_state or st.session_state.get('recipe_food') != food_name:
562
+ with st.spinner("Chef AI is thinking of a recipe..."):
563
+ st.session_state.recipe = generate_recipe(food_name)
564
+ st.session_state.recipe_food = food_name
565
+ if 'recipe' in st.session_state:
566
+ recipe_text = st.session_state.recipe
567
+ sections = {"ingredients": [], "instructions": [], "tips": []}
568
+ current_section_key = None
569
+ for line in recipe_text.split('\n'):
570
+ line_lower = line.strip().lower()
571
+ if line_lower.startswith("ingredients"): current_section_key = "ingredients"
572
+ elif line_lower.startswith("instructions"): current_section_key = "instructions"
573
+ elif line_lower.startswith("tips") or line_lower.startswith("chef's tips"): current_section_key = "tips"
574
+ elif line.strip() and current_section_key: sections[current_section_key].append(line.strip().lstrip('*- '))
575
+ with st.expander("Ingredients", expanded=True): st.markdown("\n".join(f"- {item}" for item in sections["ingredients"]) or "No ingredients listed.")
576
+ with st.expander("Instructions", expanded=True): st.markdown("\n".join(f"{i+1}. {item}" for i, item in enumerate(sections["instructions"])) or "No instructions provided.")
577
+ with st.expander("Chef's Tips"): st.markdown("\n".join(f"- {item}" for item in sections["tips"]) or "No special tips provided.")
578
+ elif not model:
579
+ st.error("Application has failed to start. Please check the logs for errors.")
580
+ else:
581
+ st.info("Upload an image to get started.")
food_classifier_convnext_large_cpu_full.pth ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:9f23f0343f88bdcb948d9c4773943c2d4fdd841d745e8f2e6ec55d89848f755e
3
+ size 2357079991
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
+ }