Spaces:
Sleeping
Sleeping
🚀 Major Update: 89.82% Accuracy Model + Professional UI
Browse files- allergen_info.json +118 -0
- app.py +675 -279
- health_info.json +157 -0
allergen_info.json
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"grilled_salmon": {
|
| 3 |
+
"common_allergens": ["Fish"],
|
| 4 |
+
"may_contain": []
|
| 5 |
+
},
|
| 6 |
+
"pizza": {
|
| 7 |
+
"common_allergens": ["Gluten", "Dairy", "Wheat"],
|
| 8 |
+
"may_contain": ["Egg", "Soy"]
|
| 9 |
+
},
|
| 10 |
+
"chicken_curry": {
|
| 11 |
+
"common_allergens": [],
|
| 12 |
+
"may_contain": ["Dairy", "Nuts"]
|
| 13 |
+
},
|
| 14 |
+
"caesar_salad": {
|
| 15 |
+
"common_allergens": ["Dairy", "Egg"],
|
| 16 |
+
"may_contain": ["Gluten", "Wheat"]
|
| 17 |
+
},
|
| 18 |
+
"hamburger": {
|
| 19 |
+
"common_allergens": ["Gluten", "Dairy", "Wheat"],
|
| 20 |
+
"may_contain": ["Egg", "Soy", "Sesame"]
|
| 21 |
+
},
|
| 22 |
+
"sushi": {
|
| 23 |
+
"common_allergens": ["Fish"],
|
| 24 |
+
"may_contain": ["Shellfish", "Soy", "Sesame"]
|
| 25 |
+
},
|
| 26 |
+
"chocolate_cake": {
|
| 27 |
+
"common_allergens": ["Gluten", "Dairy", "Egg", "Wheat"],
|
| 28 |
+
"may_contain": ["Nuts", "Soy"]
|
| 29 |
+
},
|
| 30 |
+
"french_fries": {
|
| 31 |
+
"common_allergens": [],
|
| 32 |
+
"may_contain": ["Gluten", "Wheat"]
|
| 33 |
+
},
|
| 34 |
+
"ice_cream": {
|
| 35 |
+
"common_allergens": ["Dairy"],
|
| 36 |
+
"may_contain": ["Egg", "Nuts", "Soy"]
|
| 37 |
+
},
|
| 38 |
+
"tacos": {
|
| 39 |
+
"common_allergens": ["Gluten", "Dairy", "Wheat"],
|
| 40 |
+
"may_contain": ["Soy"]
|
| 41 |
+
},
|
| 42 |
+
"steak": {
|
| 43 |
+
"common_allergens": [],
|
| 44 |
+
"may_contain": []
|
| 45 |
+
},
|
| 46 |
+
"fish_and_chips": {
|
| 47 |
+
"common_allergens": ["Fish", "Gluten", "Wheat"],
|
| 48 |
+
"may_contain": ["Egg"]
|
| 49 |
+
},
|
| 50 |
+
"shrimp_and_grits": {
|
| 51 |
+
"common_allergens": ["Shellfish", "Dairy"],
|
| 52 |
+
"may_contain": ["Gluten", "Wheat"]
|
| 53 |
+
},
|
| 54 |
+
"lobster_bisque": {
|
| 55 |
+
"common_allergens": ["Shellfish", "Dairy"],
|
| 56 |
+
"may_contain": ["Gluten", "Wheat"]
|
| 57 |
+
},
|
| 58 |
+
"crab_cakes": {
|
| 59 |
+
"common_allergens": ["Shellfish", "Egg", "Gluten", "Wheat"],
|
| 60 |
+
"may_contain": ["Dairy"]
|
| 61 |
+
},
|
| 62 |
+
"oysters": {
|
| 63 |
+
"common_allergens": ["Shellfish"],
|
| 64 |
+
"may_contain": []
|
| 65 |
+
},
|
| 66 |
+
"mussels": {
|
| 67 |
+
"common_allergens": ["Shellfish"],
|
| 68 |
+
"may_contain": []
|
| 69 |
+
},
|
| 70 |
+
"scallops": {
|
| 71 |
+
"common_allergens": ["Shellfish"],
|
| 72 |
+
"may_contain": []
|
| 73 |
+
},
|
| 74 |
+
"fried_calamari": {
|
| 75 |
+
"common_allergens": ["Shellfish", "Gluten", "Wheat"],
|
| 76 |
+
"may_contain": ["Egg"]
|
| 77 |
+
},
|
| 78 |
+
"sashimi": {
|
| 79 |
+
"common_allergens": ["Fish"],
|
| 80 |
+
"may_contain": []
|
| 81 |
+
},
|
| 82 |
+
"tuna_tartare": {
|
| 83 |
+
"common_allergens": ["Fish"],
|
| 84 |
+
"may_contain": ["Egg", "Soy"]
|
| 85 |
+
},
|
| 86 |
+
"cheese_plate": {
|
| 87 |
+
"common_allergens": ["Dairy"],
|
| 88 |
+
"may_contain": ["Nuts"]
|
| 89 |
+
},
|
| 90 |
+
"macaroni_and_cheese": {
|
| 91 |
+
"common_allergens": ["Gluten", "Dairy", "Wheat"],
|
| 92 |
+
"may_contain": ["Egg"]
|
| 93 |
+
},
|
| 94 |
+
"eggs_benedict": {
|
| 95 |
+
"common_allergens": ["Egg", "Dairy", "Gluten", "Wheat"],
|
| 96 |
+
"may_contain": []
|
| 97 |
+
},
|
| 98 |
+
"omelette": {
|
| 99 |
+
"common_allergens": ["Egg", "Dairy"],
|
| 100 |
+
"may_contain": []
|
| 101 |
+
},
|
| 102 |
+
"deviled_eggs": {
|
| 103 |
+
"common_allergens": ["Egg"],
|
| 104 |
+
"may_contain": ["Soy"]
|
| 105 |
+
},
|
| 106 |
+
"donuts": {
|
| 107 |
+
"common_allergens": ["Gluten", "Dairy", "Egg", "Wheat"],
|
| 108 |
+
"may_contain": ["Soy", "Nuts"]
|
| 109 |
+
},
|
| 110 |
+
"churros": {
|
| 111 |
+
"common_allergens": ["Gluten", "Wheat"],
|
| 112 |
+
"may_contain": ["Dairy", "Egg"]
|
| 113 |
+
},
|
| 114 |
+
"onion_rings": {
|
| 115 |
+
"common_allergens": ["Gluten", "Wheat"],
|
| 116 |
+
"may_contain": ["Dairy", "Egg"]
|
| 117 |
+
}
|
| 118 |
+
}
|
app.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
|
|
| 1 |
import streamlit as st
|
| 2 |
from PIL import Image
|
| 3 |
import torch
|
|
@@ -10,18 +11,26 @@ import requests
|
|
| 10 |
import json
|
| 11 |
import timm
|
| 12 |
import numpy as np
|
| 13 |
-
from huggingface_hub import hf_hub_download
|
| 14 |
-
import time
|
| 15 |
|
| 16 |
# Page Configuration
|
| 17 |
st.set_page_config(
|
| 18 |
page_title="🍽️ EatSmart Pro - AI Food Analysis",
|
| 19 |
page_icon="🍽️",
|
| 20 |
layout="wide",
|
| 21 |
-
initial_sidebar_state="
|
| 22 |
)
|
| 23 |
|
| 24 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
st.markdown("""
|
| 26 |
<style>
|
| 27 |
.stApp {
|
|
@@ -30,12 +39,12 @@ st.markdown("""
|
|
| 30 |
}
|
| 31 |
|
| 32 |
.main-header {
|
| 33 |
-
background: linear-gradient(90deg, #
|
| 34 |
padding: 20px;
|
| 35 |
border-radius: 15px;
|
| 36 |
text-align: center;
|
| 37 |
margin-bottom: 20px;
|
| 38 |
-
box-shadow: 0 8px 32px rgba(0,0,0,0.
|
| 39 |
}
|
| 40 |
|
| 41 |
.main-header h1 {
|
|
@@ -51,45 +60,45 @@ st.markdown("""
|
|
| 51 |
margin: 10px 0 0 0;
|
| 52 |
}
|
| 53 |
|
| 54 |
-
.
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
border-radius:
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
background: rgba(255,255,255,0.9);
|
| 63 |
-
border-radius: 8px;
|
| 64 |
-
color: #333333;
|
| 65 |
-
font-weight: bold;
|
| 66 |
-
padding: 10px 20px;
|
| 67 |
}
|
| 68 |
|
| 69 |
-
.
|
| 70 |
-
background: linear-gradient(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 71 |
color: white;
|
|
|
|
| 72 |
}
|
| 73 |
|
| 74 |
.info-card {
|
| 75 |
-
background: rgba(255,255,255,0.
|
| 76 |
backdrop-filter: blur(10px);
|
| 77 |
border-radius: 15px;
|
| 78 |
padding: 20px;
|
| 79 |
margin: 10px 0;
|
| 80 |
border: 1px solid rgba(0,0,0,0.1);
|
| 81 |
-
box-shadow: 0
|
| 82 |
color: #333333;
|
| 83 |
}
|
| 84 |
|
| 85 |
.metric-card {
|
| 86 |
-
background: linear-gradient(135deg, #
|
| 87 |
padding: 15px;
|
| 88 |
border-radius: 10px;
|
| 89 |
text-align: center;
|
| 90 |
color: white;
|
| 91 |
margin: 5px;
|
| 92 |
-
box-shadow: 0 4px 15px rgba(
|
| 93 |
}
|
| 94 |
|
| 95 |
.metric-value {
|
|
@@ -100,80 +109,105 @@ st.markdown("""
|
|
| 100 |
|
| 101 |
.metric-label {
|
| 102 |
font-size: 0.9rem;
|
| 103 |
-
opacity: 0.
|
| 104 |
}
|
| 105 |
|
| 106 |
.health-score-excellent {
|
| 107 |
-
background: linear-gradient(135deg, #
|
| 108 |
color: white;
|
| 109 |
padding: 15px;
|
| 110 |
border-radius: 10px;
|
| 111 |
text-align: center;
|
| 112 |
font-weight: bold;
|
| 113 |
margin: 10px 0;
|
|
|
|
| 114 |
}
|
| 115 |
|
| 116 |
.health-score-good {
|
| 117 |
-
background: linear-gradient(135deg, #
|
| 118 |
color: white;
|
| 119 |
padding: 15px;
|
| 120 |
border-radius: 10px;
|
| 121 |
text-align: center;
|
| 122 |
font-weight: bold;
|
| 123 |
margin: 10px 0;
|
|
|
|
| 124 |
}
|
| 125 |
|
| 126 |
.health-score-poor {
|
| 127 |
-
background: linear-gradient(135deg, #
|
| 128 |
color: white;
|
| 129 |
padding: 15px;
|
| 130 |
border-radius: 10px;
|
| 131 |
text-align: center;
|
| 132 |
font-weight: bold;
|
| 133 |
margin: 10px 0;
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
.stButton > button {
|
| 137 |
-
background: linear-gradient(45deg, #ff6b6b, #4ecdc4);
|
| 138 |
-
color: white;
|
| 139 |
-
border: none;
|
| 140 |
-
border-radius: 25px;
|
| 141 |
-
padding: 10px 20px;
|
| 142 |
-
font-weight: bold;
|
| 143 |
-
transition: all 0.3s ease;
|
| 144 |
-
}
|
| 145 |
-
|
| 146 |
-
.stButton > button:hover {
|
| 147 |
-
transform: translateY(-2px);
|
| 148 |
-
box-shadow: 0 5px 15px rgba(0,0,0,0.3);
|
| 149 |
}
|
| 150 |
|
| 151 |
.allergen-alert {
|
| 152 |
-
background: linear-gradient(135deg, #
|
| 153 |
color: white;
|
| 154 |
padding: 15px;
|
| 155 |
border-radius: 10px;
|
| 156 |
margin: 10px 0;
|
| 157 |
-
border-left: 5px solid #
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 158 |
}
|
| 159 |
|
| 160 |
.allergen-info {
|
| 161 |
-
background: linear-gradient(135deg, #
|
| 162 |
color: white;
|
| 163 |
padding: 15px;
|
| 164 |
border-radius: 10px;
|
| 165 |
margin: 10px 0;
|
| 166 |
-
border-left: 5px solid #
|
|
|
|
| 167 |
}
|
| 168 |
|
| 169 |
.recipe-section {
|
| 170 |
-
background: rgba(255,255,255,0.
|
| 171 |
-
border-radius:
|
| 172 |
-
padding:
|
| 173 |
margin: 10px 0;
|
| 174 |
-
border-left: 4px solid #
|
| 175 |
color: #333333;
|
| 176 |
border: 1px solid rgba(0,0,0,0.1);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 177 |
}
|
| 178 |
</style>
|
| 179 |
""", unsafe_allow_html=True)
|
|
@@ -217,45 +251,6 @@ if 'prediction_result' not in st.session_state:
|
|
| 217 |
st.session_state.prediction_result = None
|
| 218 |
|
| 219 |
# Helper Functions
|
| 220 |
-
def download_model_with_progress():
|
| 221 |
-
"""Download model from HF Hub with progress tracking"""
|
| 222 |
-
try:
|
| 223 |
-
# Show download progress
|
| 224 |
-
progress_bar = st.progress(0)
|
| 225 |
-
status_text = st.empty()
|
| 226 |
-
|
| 227 |
-
status_text.text("🔄 Initializing model download...")
|
| 228 |
-
progress_bar.progress(20)
|
| 229 |
-
time.sleep(0.3)
|
| 230 |
-
|
| 231 |
-
status_text.text("📥 Downloading ConvNeXt Large model (2.3GB)...")
|
| 232 |
-
progress_bar.progress(40)
|
| 233 |
-
|
| 234 |
-
# Download the model file
|
| 235 |
-
model_path = hf_hub_download(
|
| 236 |
-
repo_id="Lumilife/eatSmartPro-models",
|
| 237 |
-
filename="food_classifier_convnext_large_cpu_full.pth",
|
| 238 |
-
cache_dir="./models"
|
| 239 |
-
)
|
| 240 |
-
|
| 241 |
-
progress_bar.progress(80)
|
| 242 |
-
status_text.text("✅ Model downloaded successfully!")
|
| 243 |
-
time.sleep(0.3)
|
| 244 |
-
|
| 245 |
-
progress_bar.progress(100)
|
| 246 |
-
status_text.text("🚀 Loading model into memory...")
|
| 247 |
-
time.sleep(0.3)
|
| 248 |
-
|
| 249 |
-
# Clear progress indicators
|
| 250 |
-
progress_bar.empty()
|
| 251 |
-
status_text.empty()
|
| 252 |
-
|
| 253 |
-
return model_path
|
| 254 |
-
|
| 255 |
-
except Exception as e:
|
| 256 |
-
st.error(f"❌ Failed to download model: {str(e)}")
|
| 257 |
-
return None
|
| 258 |
-
|
| 259 |
def get_convnext_model(num_classes):
|
| 260 |
"""Creates the ConvNeXt Large model architecture"""
|
| 261 |
print(f"🚀 Loading ConvNeXt Large model for inference...")
|
|
@@ -264,53 +259,80 @@ def get_convnext_model(num_classes):
|
|
| 264 |
print(f"✅ Model loaded: {total_params:,} parameters (~{total_params * 4 / 1024**2:.1f} MB)")
|
| 265 |
return model
|
| 266 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 267 |
@st.cache_resource
|
| 268 |
def load_model_resources():
|
| 269 |
-
"""Load model with
|
| 270 |
num_classes = len(CLASS_NAMES)
|
| 271 |
|
| 272 |
-
#
|
| 273 |
-
|
|
|
|
| 274 |
|
| 275 |
-
|
| 276 |
-
|
| 277 |
-
model_path = download_model_with_progress()
|
| 278 |
-
if not model_path:
|
| 279 |
-
st.error("❌ Failed to download model. Please refresh the page.")
|
| 280 |
-
return None, None
|
| 281 |
-
else:
|
| 282 |
-
model_path = local_model_path
|
| 283 |
|
| 284 |
-
|
| 285 |
-
|
| 286 |
-
|
| 287 |
-
|
| 288 |
-
|
| 289 |
-
|
| 290 |
-
|
| 291 |
-
|
| 292 |
-
|
| 293 |
-
|
| 294 |
-
|
| 295 |
-
|
| 296 |
-
|
| 297 |
-
|
| 298 |
-
|
| 299 |
-
|
|
|
|
|
|
|
| 300 |
else:
|
| 301 |
model.load_state_dict(checkpoint, strict=False)
|
| 302 |
model_info = {"name": "ConvNeXt Large", "params": "197M", "accuracy": "89.8%"}
|
| 303 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 304 |
model.load_state_dict(checkpoint, strict=False)
|
| 305 |
-
|
| 306 |
-
|
| 307 |
-
|
| 308 |
-
|
| 309 |
-
|
| 310 |
-
|
| 311 |
-
|
| 312 |
-
|
|
|
|
| 313 |
return None, None
|
|
|
|
|
|
|
| 314 |
|
| 315 |
def load_json_data(path):
|
| 316 |
"""Load JSON data with error handling"""
|
|
@@ -376,25 +398,30 @@ def get_health_score(food_name, health_data):
|
|
| 376 |
return nutrition.get('health_score', 'Good')
|
| 377 |
|
| 378 |
def get_allergen_info(food_name, allergen_data):
|
| 379 |
-
"""Get allergen information"""
|
| 380 |
-
|
| 381 |
'common_allergens': [],
|
| 382 |
'may_contain': []
|
| 383 |
})
|
|
|
|
|
|
|
| 384 |
|
| 385 |
def get_common_ingredients(food_name):
|
| 386 |
-
"""Get common ingredients for a food item"""
|
| 387 |
ingredients_db = {
|
| 388 |
-
'grilled_salmon': ['salmon fillet', 'olive oil', 'lemon', 'herbs', 'salt', 'pepper'],
|
| 389 |
-
'chicken_curry': ['chicken', 'curry spices', 'coconut milk', 'onions', 'garlic', 'ginger'],
|
| 390 |
-
'caesar_salad': ['romaine lettuce', 'parmesan cheese', 'croutons', 'caesar dressing'],
|
| 391 |
-
'hamburger': ['ground beef', 'burger bun', 'lettuce', 'tomato', 'onion', 'pickles'],
|
| 392 |
-
'pizza': ['pizza dough', 'tomato sauce', 'mozzarella cheese', 'various toppings'],
|
| 393 |
-
'sushi': ['sushi rice', 'nori seaweed', 'fresh fish', 'wasabi', 'soy sauce'],
|
| 394 |
-
'chocolate_cake': ['flour', 'cocoa powder', 'sugar', 'eggs', 'butter', 'vanilla'],
|
| 395 |
'french_fries': ['potatoes', 'oil for frying', 'salt'],
|
| 396 |
-
'ice_cream': ['milk', 'cream', 'sugar', 'eggs', 'flavorings'],
|
| 397 |
-
'tacos': ['tortillas', 'meat or beans', 'lettuce', 'tomatoes', 'cheese', 'salsa']
|
|
|
|
|
|
|
|
|
|
| 398 |
}
|
| 399 |
|
| 400 |
return ingredients_db.get(food_name, [
|
|
@@ -405,8 +432,7 @@ def get_common_ingredients(food_name):
|
|
| 405 |
])
|
| 406 |
|
| 407 |
def generate_recipe(food_name):
|
| 408 |
-
"""Generate personalized recipe using
|
| 409 |
-
# For now, use fallback recipe system since HF inference can be unreliable
|
| 410 |
food_display = food_name.replace('_', ' ').title()
|
| 411 |
|
| 412 |
dietary_restrictions = ""
|
|
@@ -424,63 +450,75 @@ def generate_recipe(food_name):
|
|
| 424 |
return generate_fallback_recipe(food_display, dietary_restrictions, allergen_restrictions, trans_fat_note)
|
| 425 |
|
| 426 |
def generate_fallback_recipe(food_display, dietary_restrictions, allergen_restrictions, trans_fat_note):
|
| 427 |
-
"""Generate a structured
|
| 428 |
|
| 429 |
-
#
|
| 430 |
recipe_templates = {
|
| 431 |
'grilled_salmon': {
|
| 432 |
-
'ingredients': ['4 salmon fillets', '2 tbsp olive oil', '1 lemon (juiced)', '
|
| 433 |
-
'instructions': ['Preheat grill to medium-high', '
|
| 434 |
'prep_time': '15 minutes',
|
| 435 |
-
'servings': '4'
|
|
|
|
| 436 |
},
|
| 437 |
'chicken_curry': {
|
| 438 |
-
'ingredients': ['1 lb chicken breast (cubed)', '1 onion (diced)', '
|
| 439 |
-
'instructions': ['Sauté onion and garlic', 'Add chicken and curry powder', 'Pour in coconut milk', 'Add vegetables
|
| 440 |
'prep_time': '30 minutes',
|
| 441 |
-
'servings': '4'
|
|
|
|
| 442 |
},
|
| 443 |
'caesar_salad': {
|
| 444 |
-
'ingredients': ['1 head romaine lettuce', '1/
|
| 445 |
-
'instructions': ['Wash and chop lettuce', 'Make dressing with oil and
|
| 446 |
'prep_time': '10 minutes',
|
| 447 |
-
'servings': '2'
|
|
|
|
| 448 |
},
|
| 449 |
'hamburger': {
|
| 450 |
-
'ingredients': ['1 lb ground beef', '4 burger buns', 'lettuce', '
|
| 451 |
-
'instructions': ['Form beef into patties', 'Season with salt and pepper', 'Grill or pan-
|
| 452 |
'prep_time': '20 minutes',
|
| 453 |
-
'servings': '4'
|
|
|
|
| 454 |
},
|
| 455 |
'pizza': {
|
| 456 |
-
'ingredients': ['pizza dough', 'tomato sauce', '
|
| 457 |
-
'instructions': ['Preheat oven to 450°F', 'Roll out dough', 'Spread sauce evenly', 'Add cheese and toppings', 'Bake 12-15 minutes until golden'],
|
| 458 |
'prep_time': '25 minutes',
|
| 459 |
-
'servings': '4'
|
|
|
|
| 460 |
}
|
| 461 |
}
|
| 462 |
|
| 463 |
# Get template or create generic one
|
| 464 |
food_key = food_display.lower().replace(' ', '_')
|
| 465 |
template = recipe_templates.get(food_key, {
|
| 466 |
-
'ingredients': [f'Main
|
| 467 |
-
'instructions': ['Prepare all ingredients', 'Use healthy cooking methods', 'Season
|
| 468 |
'prep_time': '20-30 minutes',
|
| 469 |
-
'servings': '2-4'
|
|
|
|
| 470 |
})
|
| 471 |
|
| 472 |
# Apply dietary modifications
|
| 473 |
modifications = []
|
| 474 |
if 'vegan' in dietary_restrictions.lower():
|
| 475 |
-
modifications.append("🌱
|
|
|
|
|
|
|
| 476 |
if 'keto' in dietary_restrictions.lower():
|
| 477 |
-
modifications.append("🥑 Keep carbs under 5g per serving")
|
|
|
|
|
|
|
| 478 |
if 'gluten' in allergen_restrictions.lower():
|
| 479 |
-
modifications.append("⚠️ Use gluten-free alternatives")
|
|
|
|
|
|
|
| 480 |
if trans_fat_note:
|
| 481 |
-
modifications.append("💚 Use healthy oils
|
| 482 |
|
| 483 |
-
recipe = f"""**🍽️
|
| 484 |
|
| 485 |
**Ingredients:**
|
| 486 |
{chr(10).join(f'• {ingredient}' for ingredient in template['ingredients'])}
|
|
@@ -488,20 +526,244 @@ def generate_fallback_recipe(food_display, dietary_restrictions, allergen_restri
|
|
| 488 |
**Instructions:**
|
| 489 |
{chr(10).join(f'{i+1}. {instruction}' for i, instruction in enumerate(template['instructions']))}
|
| 490 |
|
| 491 |
-
**
|
| 492 |
-
•
|
| 493 |
-
•
|
| 494 |
-
•
|
| 495 |
-
• Control portion sizes for balanced eating
|
| 496 |
|
| 497 |
-
**
|
| 498 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 499 |
|
| 500 |
if modifications:
|
| 501 |
recipe += f"\n\n**🎯 Your Personalized Modifications:**\n{chr(10).join(f'• {mod}' for mod in modifications)}"
|
| 502 |
|
| 503 |
-
recipe += "\n\n*💡 Recipe
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 504 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 505 |
return recipe
|
| 506 |
|
| 507 |
# Main App
|
|
@@ -510,7 +772,7 @@ def main():
|
|
| 510 |
st.markdown("""
|
| 511 |
<div class="main-header">
|
| 512 |
<h1>🍽️ EatSmart Pro</h1>
|
| 513 |
-
<p>AI-Powered
|
| 514 |
</div>
|
| 515 |
""", unsafe_allow_html=True)
|
| 516 |
|
|
@@ -519,62 +781,82 @@ def main():
|
|
| 519 |
health_data, allergen_data = load_data()
|
| 520 |
|
| 521 |
if not model:
|
| 522 |
-
st.error("❌ Failed to load AI model. Please
|
| 523 |
return
|
| 524 |
|
| 525 |
-
# Model status
|
| 526 |
-
|
|
|
|
|
|
|
|
|
|
| 527 |
|
| 528 |
-
# Sidebar -
|
| 529 |
with st.sidebar:
|
| 530 |
-
st.markdown("
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 531 |
|
| 532 |
-
# Allergen preferences
|
| 533 |
-
st.markdown("###
|
| 534 |
-
|
| 535 |
-
"Gluten", "Dairy", "Egg", "Fish", "Shellfish",
|
| 536 |
-
"Nuts", "Peanuts", "Soy", "Sesame"
|
| 537 |
-
]
|
| 538 |
|
| 539 |
-
|
| 540 |
-
"Select allergens to avoid:",
|
| 541 |
-
allergen_options,
|
| 542 |
-
default=st.session_state.user_allergens,
|
| 543 |
-
help="Get alerts when these allergens are detected"
|
| 544 |
-
)
|
| 545 |
|
| 546 |
-
#
|
| 547 |
-
|
| 548 |
-
|
| 549 |
-
|
| 550 |
-
|
| 551 |
-
|
| 552 |
-
|
| 553 |
-
|
| 554 |
-
|
| 555 |
-
dietary_options,
|
| 556 |
-
default=st.session_state.dietary_preferences,
|
| 557 |
-
help="Personalize recipes and recommendations"
|
| 558 |
-
)
|
| 559 |
|
| 560 |
# Trans fat settings
|
| 561 |
st.markdown("### 🧪 Trans Fat Settings")
|
| 562 |
st.session_state.avoid_trans_fat = st.checkbox(
|
| 563 |
-
"Alert me about trans fats",
|
| 564 |
value=st.session_state.avoid_trans_fat,
|
| 565 |
-
help="Get warnings about
|
| 566 |
)
|
| 567 |
|
| 568 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 569 |
st.markdown("---")
|
| 570 |
-
st.markdown("### 🚀 Features")
|
| 571 |
st.markdown("""
|
| 572 |
✅ **High-Accuracy AI Recognition**
|
| 573 |
✅ **Comprehensive Nutrition Analysis**
|
| 574 |
✅ **Personalized Allergen Alerts**
|
| 575 |
-
✅ **
|
| 576 |
✅ **Trans Fat Detection**
|
| 577 |
-
✅ **
|
| 578 |
✅ **Health Score Assessment**
|
| 579 |
""")
|
| 580 |
|
|
@@ -582,47 +864,47 @@ def main():
|
|
| 582 |
col1, col2 = st.columns([1, 1])
|
| 583 |
|
| 584 |
with col1:
|
|
|
|
| 585 |
st.markdown("""
|
| 586 |
-
<div
|
| 587 |
-
<h3 style="
|
| 588 |
-
<p style="
|
| 589 |
</div>
|
| 590 |
""", unsafe_allow_html=True)
|
| 591 |
|
| 592 |
-
#
|
| 593 |
-
|
| 594 |
-
|
| 595 |
-
|
| 596 |
-
|
|
|
|
| 597 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 598 |
|
| 599 |
-
|
| 600 |
-
|
| 601 |
-
|
| 602 |
-
|
| 603 |
-
|
| 604 |
-
|
| 605 |
-
|
| 606 |
-
)
|
| 607 |
-
if uploaded_file:
|
| 608 |
-
try:
|
| 609 |
-
uploaded_image = uploaded_file.getvalue()
|
| 610 |
-
st.session_state.image_buffer = uploaded_image
|
| 611 |
-
st.success("✅ Image uploaded successfully!")
|
| 612 |
-
except Exception as e:
|
| 613 |
-
st.error(f"❌ Error uploading image: {str(e)}")
|
| 614 |
-
st.info("Please try a different image format (JPG, JPEG, or PNG).")
|
| 615 |
|
| 616 |
-
|
| 617 |
-
|
| 618 |
-
|
| 619 |
-
|
| 620 |
-
|
| 621 |
-
|
| 622 |
-
|
| 623 |
-
|
| 624 |
-
|
| 625 |
-
st.info("Please try taking the photo again.")
|
| 626 |
|
| 627 |
# Display uploaded image and auto-analyze
|
| 628 |
if st.session_state.image_buffer:
|
|
@@ -645,7 +927,7 @@ def main():
|
|
| 645 |
st.session_state.prediction_result = predictions
|
| 646 |
st.session_state.last_image_buffer = st.session_state.image_buffer
|
| 647 |
st.session_state.last_analyzed_buffer = st.session_state.image_buffer
|
| 648 |
-
st.success("✅ Analysis complete!
|
| 649 |
except Exception as e:
|
| 650 |
st.error(f"❌ Error analyzing image: {str(e)}")
|
| 651 |
st.info("Please try uploading a different image or refresh the page.")
|
|
@@ -655,23 +937,24 @@ def main():
|
|
| 655 |
st.info("The uploaded file may be corrupted. Please try uploading again.")
|
| 656 |
|
| 657 |
with col2:
|
|
|
|
| 658 |
st.markdown("""
|
| 659 |
-
<div
|
| 660 |
-
<h3 style="
|
| 661 |
-
<p style="
|
| 662 |
</div>
|
| 663 |
""", unsafe_allow_html=True)
|
| 664 |
|
| 665 |
# Show instruction if no image uploaded
|
| 666 |
if not st.session_state.image_buffer:
|
| 667 |
-
st.info("👆 Upload an image above to start the analysis!")
|
| 668 |
|
| 669 |
# Display results
|
| 670 |
if st.session_state.prediction_result and st.session_state.last_image_buffer == st.session_state.image_buffer:
|
| 671 |
predictions = st.session_state.prediction_result
|
| 672 |
top_prediction = predictions[0]
|
| 673 |
|
| 674 |
-
# Main prediction
|
| 675 |
st.markdown(f"""
|
| 676 |
<div class="info-card">
|
| 677 |
<h3>🎯 Food Identified</h3>
|
|
@@ -686,16 +969,16 @@ def main():
|
|
| 686 |
for pred in predictions[1:]:
|
| 687 |
st.write(f"• {pred['display_name']} ({pred['confidence']:.1f}%)")
|
| 688 |
|
| 689 |
-
#
|
| 690 |
-
tab1, tab2, tab3 = st.tabs(["🏥 Health
|
| 691 |
|
| 692 |
with tab1:
|
| 693 |
-
#
|
| 694 |
food_name = top_prediction['class']
|
| 695 |
nutrition = get_nutrition_info(food_name, health_data)
|
| 696 |
health_score = get_health_score(food_name, health_data)
|
| 697 |
|
| 698 |
-
#
|
| 699 |
score_class = f"health-score-{health_score.lower()}"
|
| 700 |
st.markdown(f"""
|
| 701 |
<div class="{score_class}">
|
|
@@ -703,7 +986,7 @@ def main():
|
|
| 703 |
</div>
|
| 704 |
""", unsafe_allow_html=True)
|
| 705 |
|
| 706 |
-
#
|
| 707 |
col1, col2, col3, col4 = st.columns(4)
|
| 708 |
|
| 709 |
with col1:
|
|
@@ -744,17 +1027,17 @@ def main():
|
|
| 744 |
for benefit in benefits:
|
| 745 |
st.markdown(f"• {benefit}")
|
| 746 |
|
| 747 |
-
#
|
| 748 |
st.markdown("### 🥘 Common Ingredients")
|
| 749 |
ingredients = get_common_ingredients(food_name)
|
| 750 |
for ingredient in ingredients:
|
| 751 |
st.markdown(f"• {ingredient}")
|
| 752 |
|
| 753 |
with tab2:
|
| 754 |
-
#
|
| 755 |
allergen_info = get_allergen_info(food_name, allergen_data)
|
| 756 |
|
| 757 |
-
# User-specific allergen alerts
|
| 758 |
user_allergens_lower = [allergen.lower() for allergen in st.session_state.user_allergens]
|
| 759 |
common_allergens_lower = [allergen.lower() for allergen in allergen_info.get('common_allergens', [])]
|
| 760 |
may_contain_lower = [allergen.lower() for allergen in allergen_info.get('may_contain', [])]
|
|
@@ -769,11 +1052,11 @@ def main():
|
|
| 769 |
elif user_allergen in may_contain_lower:
|
| 770 |
may_contain_matches.append(user_allergen.title())
|
| 771 |
|
| 772 |
-
#
|
| 773 |
if allergen_matches:
|
| 774 |
st.markdown(f"""
|
| 775 |
<div class="allergen-alert">
|
| 776 |
-
<h3>
|
| 777 |
<p>This food contains: <strong>{', '.join(allergen_matches)}</strong></p>
|
| 778 |
<p>You have marked these as allergens to avoid!</p>
|
| 779 |
</div>
|
|
@@ -782,7 +1065,7 @@ def main():
|
|
| 782 |
if may_contain_matches:
|
| 783 |
st.markdown(f"""
|
| 784 |
<div class="allergen-info">
|
| 785 |
-
<h3>⚠️ May Contain</h3>
|
| 786 |
<p>This food may contain: <strong>{', '.join(may_contain_matches)}</strong></p>
|
| 787 |
<p>Please check ingredients carefully.</p>
|
| 788 |
</div>
|
|
@@ -805,33 +1088,146 @@ def main():
|
|
| 805 |
for allergen in allergen_info['may_contain']:
|
| 806 |
st.markdown(f"• {allergen}")
|
| 807 |
|
| 808 |
-
#
|
| 809 |
if st.session_state.avoid_trans_fat:
|
| 810 |
-
trans_fat_foods = ['french_fries', 'donuts', 'fried_calamari', 'onion_rings']
|
| 811 |
if food_name in trans_fat_foods:
|
| 812 |
st.markdown("""
|
| 813 |
<div class="allergen-alert">
|
| 814 |
-
<h3>
|
| 815 |
-
<p>This food may contain trans fats from frying oils
|
| 816 |
-
<p>
|
|
|
|
| 817 |
</div>
|
| 818 |
""", unsafe_allow_html=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 819 |
|
| 820 |
with tab3:
|
| 821 |
-
#
|
| 822 |
-
st.markdown("### 👨🍳
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 823 |
|
| 824 |
-
if
|
| 825 |
-
|
| 826 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 827 |
|
| 828 |
-
|
| 829 |
-
|
| 830 |
-
|
| 831 |
-
|
| 832 |
-
|
| 833 |
-
|
| 834 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 835 |
|
| 836 |
if __name__ == "__main__":
|
| 837 |
main()
|
|
|
|
| 1 |
+
# EatSmart Pro - Professional Version with All Features
|
| 2 |
import streamlit as st
|
| 3 |
from PIL import Image
|
| 4 |
import torch
|
|
|
|
| 11 |
import json
|
| 12 |
import timm
|
| 13 |
import numpy as np
|
|
|
|
|
|
|
| 14 |
|
| 15 |
# Page Configuration
|
| 16 |
st.set_page_config(
|
| 17 |
page_title="🍽️ EatSmart Pro - AI Food Analysis",
|
| 18 |
page_icon="🍽️",
|
| 19 |
layout="wide",
|
| 20 |
+
initial_sidebar_state="collapsed" # Start with sidebar closed
|
| 21 |
)
|
| 22 |
|
| 23 |
+
# Mobile menu indicator - restored from history
|
| 24 |
+
st.markdown("""
|
| 25 |
+
<div style="display: block; background: linear-gradient(90deg, #28a745 0%, #fd7e14 50%, #007bff 100%);
|
| 26 |
+
padding: 8px 15px; border-radius: 8px; margin-bottom: 15px; text-align: center;">
|
| 27 |
+
<p style="color: white; margin: 0; font-size: 14px;">
|
| 28 |
+
📱 <strong>Mobile Users:</strong> Click the <strong>></strong> arrow (top-left) to set dietary preferences
|
| 29 |
+
</p>
|
| 30 |
+
</div>
|
| 31 |
+
""", unsafe_allow_html=True)
|
| 32 |
+
|
| 33 |
+
# Professional CSS with better colors - green, orange, light blue theme
|
| 34 |
st.markdown("""
|
| 35 |
<style>
|
| 36 |
.stApp {
|
|
|
|
| 39 |
}
|
| 40 |
|
| 41 |
.main-header {
|
| 42 |
+
background: linear-gradient(90deg, #28a745 0%, #fd7e14 50%, #007bff 100%);
|
| 43 |
padding: 20px;
|
| 44 |
border-radius: 15px;
|
| 45 |
text-align: center;
|
| 46 |
margin-bottom: 20px;
|
| 47 |
+
box-shadow: 0 8px 32px rgba(0,0,0,0.1);
|
| 48 |
}
|
| 49 |
|
| 50 |
.main-header h1 {
|
|
|
|
| 60 |
margin: 10px 0 0 0;
|
| 61 |
}
|
| 62 |
|
| 63 |
+
.upload-section {
|
| 64 |
+
background: linear-gradient(135deg, #28a745 0%, #20c997 100%);
|
| 65 |
+
padding: 20px;
|
| 66 |
+
border-radius: 15px;
|
| 67 |
+
text-align: center;
|
| 68 |
+
margin-bottom: 20px;
|
| 69 |
+
color: white;
|
| 70 |
+
box-shadow: 0 4px 15px rgba(40, 167, 69, 0.3);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 71 |
}
|
| 72 |
|
| 73 |
+
.analysis-section {
|
| 74 |
+
background: linear-gradient(135deg, #007bff 0%, #0056b3 100%);
|
| 75 |
+
padding: 20px;
|
| 76 |
+
border-radius: 15px;
|
| 77 |
+
text-align: center;
|
| 78 |
+
margin-bottom: 20px;
|
| 79 |
color: white;
|
| 80 |
+
box-shadow: 0 4px 15px rgba(0, 123, 255, 0.3);
|
| 81 |
}
|
| 82 |
|
| 83 |
.info-card {
|
| 84 |
+
background: rgba(255,255,255,0.95);
|
| 85 |
backdrop-filter: blur(10px);
|
| 86 |
border-radius: 15px;
|
| 87 |
padding: 20px;
|
| 88 |
margin: 10px 0;
|
| 89 |
border: 1px solid rgba(0,0,0,0.1);
|
| 90 |
+
box-shadow: 0 4px 20px rgba(0,0,0,0.1);
|
| 91 |
color: #333333;
|
| 92 |
}
|
| 93 |
|
| 94 |
.metric-card {
|
| 95 |
+
background: linear-gradient(135deg, #20c997 0%, #17a2b8 100%);
|
| 96 |
padding: 15px;
|
| 97 |
border-radius: 10px;
|
| 98 |
text-align: center;
|
| 99 |
color: white;
|
| 100 |
margin: 5px;
|
| 101 |
+
box-shadow: 0 4px 15px rgba(32, 201, 151, 0.3);
|
| 102 |
}
|
| 103 |
|
| 104 |
.metric-value {
|
|
|
|
| 109 |
|
| 110 |
.metric-label {
|
| 111 |
font-size: 0.9rem;
|
| 112 |
+
opacity: 0.9;
|
| 113 |
}
|
| 114 |
|
| 115 |
.health-score-excellent {
|
| 116 |
+
background: linear-gradient(135deg, #28a745 0%, #20c997 100%);
|
| 117 |
color: white;
|
| 118 |
padding: 15px;
|
| 119 |
border-radius: 10px;
|
| 120 |
text-align: center;
|
| 121 |
font-weight: bold;
|
| 122 |
margin: 10px 0;
|
| 123 |
+
box-shadow: 0 4px 15px rgba(40, 167, 69, 0.3);
|
| 124 |
}
|
| 125 |
|
| 126 |
.health-score-good {
|
| 127 |
+
background: linear-gradient(135deg, #17a2b8 0%, #20c997 100%);
|
| 128 |
color: white;
|
| 129 |
padding: 15px;
|
| 130 |
border-radius: 10px;
|
| 131 |
text-align: center;
|
| 132 |
font-weight: bold;
|
| 133 |
margin: 10px 0;
|
| 134 |
+
box-shadow: 0 4px 15px rgba(23, 162, 184, 0.3);
|
| 135 |
}
|
| 136 |
|
| 137 |
.health-score-poor {
|
| 138 |
+
background: linear-gradient(135deg, #dc3545 0%, #c82333 100%);
|
| 139 |
color: white;
|
| 140 |
padding: 15px;
|
| 141 |
border-radius: 10px;
|
| 142 |
text-align: center;
|
| 143 |
font-weight: bold;
|
| 144 |
margin: 10px 0;
|
| 145 |
+
box-shadow: 0 4px 15px rgba(220, 53, 69, 0.3);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 146 |
}
|
| 147 |
|
| 148 |
.allergen-alert {
|
| 149 |
+
background: linear-gradient(135deg, #dc3545 0%, #c82333 100%);
|
| 150 |
color: white;
|
| 151 |
padding: 15px;
|
| 152 |
border-radius: 10px;
|
| 153 |
margin: 10px 0;
|
| 154 |
+
border-left: 5px solid #a71e2a;
|
| 155 |
+
box-shadow: 0 4px 15px rgba(220, 53, 69, 0.3);
|
| 156 |
+
animation: pulse 2s infinite;
|
| 157 |
+
}
|
| 158 |
+
|
| 159 |
+
@keyframes pulse {
|
| 160 |
+
0% { box-shadow: 0 4px 15px rgba(220, 53, 69, 0.3); }
|
| 161 |
+
50% { box-shadow: 0 4px 25px rgba(220, 53, 69, 0.6); }
|
| 162 |
+
100% { box-shadow: 0 4px 15px rgba(220, 53, 69, 0.3); }
|
| 163 |
}
|
| 164 |
|
| 165 |
.allergen-info {
|
| 166 |
+
background: linear-gradient(135deg, #007bff 0%, #0056b3 100%);
|
| 167 |
color: white;
|
| 168 |
padding: 15px;
|
| 169 |
border-radius: 10px;
|
| 170 |
margin: 10px 0;
|
| 171 |
+
border-left: 5px solid #004085;
|
| 172 |
+
box-shadow: 0 4px 15px rgba(0, 123, 255, 0.3);
|
| 173 |
}
|
| 174 |
|
| 175 |
.recipe-section {
|
| 176 |
+
background: rgba(255,255,255,0.95);
|
| 177 |
+
border-radius: 15px;
|
| 178 |
+
padding: 20px;
|
| 179 |
margin: 10px 0;
|
| 180 |
+
border-left: 4px solid #28a745;
|
| 181 |
color: #333333;
|
| 182 |
border: 1px solid rgba(0,0,0,0.1);
|
| 183 |
+
box-shadow: 0 4px 20px rgba(0,0,0,0.1);
|
| 184 |
+
}
|
| 185 |
+
|
| 186 |
+
.stButton > button {
|
| 187 |
+
background: linear-gradient(45deg, #28a745, #007bff);
|
| 188 |
+
color: white;
|
| 189 |
+
border: none;
|
| 190 |
+
border-radius: 25px;
|
| 191 |
+
padding: 12px 24px;
|
| 192 |
+
font-weight: bold;
|
| 193 |
+
font-size: 1.1rem;
|
| 194 |
+
transition: all 0.3s ease;
|
| 195 |
+
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
|
| 196 |
+
}
|
| 197 |
+
|
| 198 |
+
.stButton > button:hover {
|
| 199 |
+
transform: translateY(-2px);
|
| 200 |
+
box-shadow: 0 6px 20px rgba(0,0,0,0.3);
|
| 201 |
+
}
|
| 202 |
+
|
| 203 |
+
.camera-section {
|
| 204 |
+
background: linear-gradient(135deg, #17a2b8 0%, #20c997 100%);
|
| 205 |
+
padding: 15px;
|
| 206 |
+
border-radius: 10px;
|
| 207 |
+
margin: 10px 0;
|
| 208 |
+
text-align: center;
|
| 209 |
+
color: white;
|
| 210 |
+
box-shadow: 0 4px 15px rgba(23, 162, 184, 0.3);
|
| 211 |
}
|
| 212 |
</style>
|
| 213 |
""", unsafe_allow_html=True)
|
|
|
|
| 251 |
st.session_state.prediction_result = None
|
| 252 |
|
| 253 |
# Helper Functions
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 254 |
def get_convnext_model(num_classes):
|
| 255 |
"""Creates the ConvNeXt Large model architecture"""
|
| 256 |
print(f"🚀 Loading ConvNeXt Large model for inference...")
|
|
|
|
| 259 |
print(f"✅ Model loaded: {total_params:,} parameters (~{total_params * 4 / 1024**2:.1f} MB)")
|
| 260 |
return model
|
| 261 |
|
| 262 |
+
def get_efficientnet_model(num_classes):
|
| 263 |
+
"""Creates EfficientNet-V2-S model for fallback"""
|
| 264 |
+
print(f"⚠️ Loading EfficientNet-V2-S model (fallback)...")
|
| 265 |
+
model = models.efficientnet_v2_s(weights=None)
|
| 266 |
+
num_features = model.classifier[1].in_features
|
| 267 |
+
model.classifier = nn.Sequential(
|
| 268 |
+
nn.Dropout(p=0.2),
|
| 269 |
+
nn.Linear(num_features, 256),
|
| 270 |
+
nn.ReLU(),
|
| 271 |
+
nn.Dropout(p=0.1),
|
| 272 |
+
nn.Linear(256, num_classes)
|
| 273 |
+
)
|
| 274 |
+
return model
|
| 275 |
+
|
| 276 |
@st.cache_resource
|
| 277 |
def load_model_resources():
|
| 278 |
+
"""Load model with smart detection"""
|
| 279 |
num_classes = len(CLASS_NAMES)
|
| 280 |
|
| 281 |
+
# Try to load ConvNeXt Large model first
|
| 282 |
+
convnext_path = "models/food_classifier_convnext_large_cpu_full.pth"
|
| 283 |
+
efficientnet_path = "models/food101_efficientnet_best.pth"
|
| 284 |
|
| 285 |
+
model = None
|
| 286 |
+
model_info = {"name": "Unknown", "params": 0, "accuracy": "Unknown"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 287 |
|
| 288 |
+
if os.path.exists(convnext_path):
|
| 289 |
+
try:
|
| 290 |
+
print("🎯 Loading ConvNeXt Large model...")
|
| 291 |
+
model = get_convnext_model(num_classes)
|
| 292 |
+
checkpoint = torch.load(convnext_path, map_location='cpu')
|
| 293 |
+
|
| 294 |
+
# Handle different checkpoint formats
|
| 295 |
+
if isinstance(checkpoint, dict):
|
| 296 |
+
if 'model_state_dict' in checkpoint:
|
| 297 |
+
model.load_state_dict(checkpoint['model_state_dict'], strict=False)
|
| 298 |
+
model_info = {
|
| 299 |
+
"name": "ConvNeXt Large",
|
| 300 |
+
"params": "197M",
|
| 301 |
+
"accuracy": f"{checkpoint.get('best_acc', 89.8):.1f}%"
|
| 302 |
+
}
|
| 303 |
+
else:
|
| 304 |
+
model.load_state_dict(checkpoint, strict=False)
|
| 305 |
+
model_info = {"name": "ConvNeXt Large", "params": "197M", "accuracy": "89.8%"}
|
| 306 |
else:
|
| 307 |
model.load_state_dict(checkpoint, strict=False)
|
| 308 |
model_info = {"name": "ConvNeXt Large", "params": "197M", "accuracy": "89.8%"}
|
| 309 |
+
|
| 310 |
+
model.eval()
|
| 311 |
+
print("✅ ConvNeXt Large model loaded successfully!")
|
| 312 |
+
|
| 313 |
+
except Exception as e:
|
| 314 |
+
print(f"❌ Failed to load ConvNeXt model: {e}")
|
| 315 |
+
model = None
|
| 316 |
+
|
| 317 |
+
# Fallback to EfficientNet if ConvNeXt fails
|
| 318 |
+
if model is None and os.path.exists(efficientnet_path):
|
| 319 |
+
try:
|
| 320 |
+
print("🔄 Loading EfficientNet-V2-S model (fallback)...")
|
| 321 |
+
model = get_efficientnet_model(num_classes)
|
| 322 |
+
checkpoint = torch.load(efficientnet_path, map_location='cpu')
|
| 323 |
model.load_state_dict(checkpoint, strict=False)
|
| 324 |
+
model.eval()
|
| 325 |
+
model_info = {"name": "EfficientNet-V2-S", "params": "21M", "accuracy": "85.2%"}
|
| 326 |
+
print("✅ EfficientNet model loaded successfully!")
|
| 327 |
+
except Exception as e:
|
| 328 |
+
print(f"❌ Failed to load EfficientNet model: {e}")
|
| 329 |
+
model = None
|
| 330 |
+
|
| 331 |
+
if model is None:
|
| 332 |
+
st.error("❌ No model could be loaded. Please check model files.")
|
| 333 |
return None, None
|
| 334 |
+
|
| 335 |
+
return model, model_info
|
| 336 |
|
| 337 |
def load_json_data(path):
|
| 338 |
"""Load JSON data with error handling"""
|
|
|
|
| 398 |
return nutrition.get('health_score', 'Good')
|
| 399 |
|
| 400 |
def get_allergen_info(food_name, allergen_data):
|
| 401 |
+
"""Get allergen information with enhanced user-specific alerts"""
|
| 402 |
+
allergen_info = allergen_data.get(food_name, {
|
| 403 |
'common_allergens': [],
|
| 404 |
'may_contain': []
|
| 405 |
})
|
| 406 |
+
|
| 407 |
+
return allergen_info
|
| 408 |
|
| 409 |
def get_common_ingredients(food_name):
|
| 410 |
+
"""Get common ingredients for a food item with enhanced protein focus"""
|
| 411 |
ingredients_db = {
|
| 412 |
+
'grilled_salmon': ['salmon fillet (high protein)', 'olive oil', 'lemon', 'herbs', 'salt', 'pepper'],
|
| 413 |
+
'chicken_curry': ['chicken breast (high protein)', 'curry spices', 'coconut milk', 'onions', 'garlic', 'ginger'],
|
| 414 |
+
'caesar_salad': ['romaine lettuce', 'parmesan cheese (protein)', 'croutons', 'caesar dressing'],
|
| 415 |
+
'hamburger': ['ground beef (high protein)', 'burger bun', 'lettuce', 'tomato', 'onion', 'pickles'],
|
| 416 |
+
'pizza': ['pizza dough', 'tomato sauce', 'mozzarella cheese (protein)', 'various toppings'],
|
| 417 |
+
'sushi': ['sushi rice', 'nori seaweed', 'fresh fish (high protein)', 'wasabi', 'soy sauce'],
|
| 418 |
+
'chocolate_cake': ['flour', 'cocoa powder', 'sugar', 'eggs (protein)', 'butter', 'vanilla'],
|
| 419 |
'french_fries': ['potatoes', 'oil for frying', 'salt'],
|
| 420 |
+
'ice_cream': ['milk (protein)', 'cream', 'sugar', 'eggs (protein)', 'flavorings'],
|
| 421 |
+
'tacos': ['tortillas', 'meat or beans (protein)', 'lettuce', 'tomatoes', 'cheese (protein)', 'salsa'],
|
| 422 |
+
'steak': ['beef steak (very high protein)', 'seasonings', 'cooking oil'],
|
| 423 |
+
'eggs_benedict': ['eggs (high protein)', 'english muffin', 'canadian bacon (protein)', 'hollandaise sauce'],
|
| 424 |
+
'greek_salad': ['lettuce', 'tomatoes', 'feta cheese (protein)', 'olives', 'olive oil', 'herbs']
|
| 425 |
}
|
| 426 |
|
| 427 |
return ingredients_db.get(food_name, [
|
|
|
|
| 432 |
])
|
| 433 |
|
| 434 |
def generate_recipe(food_name):
|
| 435 |
+
"""Generate personalized recipe using built-in database"""
|
|
|
|
| 436 |
food_display = food_name.replace('_', ' ').title()
|
| 437 |
|
| 438 |
dietary_restrictions = ""
|
|
|
|
| 450 |
return generate_fallback_recipe(food_display, dietary_restrictions, allergen_restrictions, trans_fat_note)
|
| 451 |
|
| 452 |
def generate_fallback_recipe(food_display, dietary_restrictions, allergen_restrictions, trans_fat_note):
|
| 453 |
+
"""Generate a structured recipe with professional formatting"""
|
| 454 |
|
| 455 |
+
# Enhanced recipes database with more variety
|
| 456 |
recipe_templates = {
|
| 457 |
'grilled_salmon': {
|
| 458 |
+
'ingredients': ['4 salmon fillets (6 oz each)', '2 tbsp extra virgin olive oil', '1 lemon (juiced)', 'fresh dill or parsley', 'sea salt and black pepper', 'garlic powder'],
|
| 459 |
+
'instructions': ['Preheat grill to medium-high heat', 'Pat salmon dry and brush with olive oil', 'Season generously with salt, pepper, and garlic powder', 'Grill 4-5 minutes per side until flakes easily', 'Garnish with fresh herbs and lemon juice'],
|
| 460 |
'prep_time': '15 minutes',
|
| 461 |
+
'servings': '4',
|
| 462 |
+
'protein_content': 'Very High (35g per serving)'
|
| 463 |
},
|
| 464 |
'chicken_curry': {
|
| 465 |
+
'ingredients': ['1 lb chicken breast (cubed)', '1 large onion (diced)', '3 cloves garlic (minced)', '1 tbsp curry powder', '1 can coconut milk', '1 cup mixed vegetables', 'ginger', 'cilantro'],
|
| 466 |
+
'instructions': ['Heat oil in large pan over medium heat', 'Sauté onion and garlic until fragrant', 'Add chicken and curry powder, cook until browned', 'Pour in coconut milk and simmer', 'Add vegetables and cook 15-20 minutes until tender'],
|
| 467 |
'prep_time': '30 minutes',
|
| 468 |
+
'servings': '4',
|
| 469 |
+
'protein_content': 'High (28g per serving)'
|
| 470 |
},
|
| 471 |
'caesar_salad': {
|
| 472 |
+
'ingredients': ['1 large head romaine lettuce', '1/3 cup parmesan cheese (grated)', '2 tbsp olive oil', '1 tbsp lemon juice', 'whole grain croutons', 'black pepper', 'caesar dressing'],
|
| 473 |
+
'instructions': ['Wash and chop lettuce into bite-sized pieces', 'Make dressing with oil, lemon, and seasonings', 'Toss lettuce with dressing until well coated', 'Top with parmesan cheese and croutons', 'Season with fresh black pepper'],
|
| 474 |
'prep_time': '10 minutes',
|
| 475 |
+
'servings': '2-3',
|
| 476 |
+
'protein_content': 'Moderate (8g per serving)'
|
| 477 |
},
|
| 478 |
'hamburger': {
|
| 479 |
+
'ingredients': ['1 lb lean ground beef (80/20)', '4 whole grain burger buns', 'lettuce leaves', '2 tomatoes (sliced)', '1 onion (sliced)', 'pickles', 'low-fat cheese (optional)'],
|
| 480 |
+
'instructions': ['Form beef into 4 equal patties', 'Season with salt and pepper', 'Grill or pan-cook 4-5 minutes per side', 'Toast buns lightly on grill', 'Assemble with fresh vegetables and condiments'],
|
| 481 |
'prep_time': '20 minutes',
|
| 482 |
+
'servings': '4',
|
| 483 |
+
'protein_content': 'Very High (25g per serving)'
|
| 484 |
},
|
| 485 |
'pizza': {
|
| 486 |
+
'ingredients': ['whole wheat pizza dough', 'tomato sauce (low sodium)', '1.5 cups mozzarella cheese', 'vegetables of choice', 'olive oil', 'Italian herbs', 'fresh basil'],
|
| 487 |
+
'instructions': ['Preheat oven to 450°F (230°C)', 'Roll out dough on floured surface', 'Spread sauce evenly, leaving border for crust', 'Add cheese and favorite toppings', 'Bake 12-15 minutes until golden and bubbly'],
|
| 488 |
'prep_time': '25 minutes',
|
| 489 |
+
'servings': '4',
|
| 490 |
+
'protein_content': 'Moderate (15g per serving)'
|
| 491 |
}
|
| 492 |
}
|
| 493 |
|
| 494 |
# Get template or create generic one
|
| 495 |
food_key = food_display.lower().replace(' ', '_')
|
| 496 |
template = recipe_templates.get(food_key, {
|
| 497 |
+
'ingredients': [f'Main protein source for {food_display}', 'Fresh vegetables', 'Healthy seasonings', 'Quality cooking oil', 'Herbs and spices'],
|
| 498 |
+
'instructions': ['Prepare all ingredients fresh', 'Use healthy cooking methods (grill, bake, steam)', 'Season with herbs instead of excess salt', 'Cook until properly done', 'Serve with colorful vegetables'],
|
| 499 |
'prep_time': '20-30 minutes',
|
| 500 |
+
'servings': '2-4',
|
| 501 |
+
'protein_content': 'Varies by ingredients'
|
| 502 |
})
|
| 503 |
|
| 504 |
# Apply dietary modifications
|
| 505 |
modifications = []
|
| 506 |
if 'vegan' in dietary_restrictions.lower():
|
| 507 |
+
modifications.append("🌱 Replace animal proteins with plant-based alternatives (tofu, tempeh, legumes)")
|
| 508 |
+
if 'vegetarian' in dietary_restrictions.lower():
|
| 509 |
+
modifications.append("🥬 Use vegetarian protein sources (eggs, dairy, plant proteins)")
|
| 510 |
if 'keto' in dietary_restrictions.lower():
|
| 511 |
+
modifications.append("🥑 Keep carbs under 5g per serving, increase healthy fats")
|
| 512 |
+
if 'high-protein' in dietary_restrictions.lower():
|
| 513 |
+
modifications.append("💪 Add extra protein sources to reach 25g+ per serving")
|
| 514 |
if 'gluten' in allergen_restrictions.lower():
|
| 515 |
+
modifications.append("⚠️ Use certified gluten-free alternatives for all grains")
|
| 516 |
+
if 'dairy' in allergen_restrictions.lower():
|
| 517 |
+
modifications.append("🥛 Replace dairy with plant-based alternatives (almond, oat milk)")
|
| 518 |
if trans_fat_note:
|
| 519 |
+
modifications.append("💚 Use only healthy oils: olive, avocado, or coconut oil")
|
| 520 |
|
| 521 |
+
recipe = f"""**🍽️ Professional {food_display} Recipe**
|
| 522 |
|
| 523 |
**Ingredients:**
|
| 524 |
{chr(10).join(f'• {ingredient}' for ingredient in template['ingredients'])}
|
|
|
|
| 526 |
**Instructions:**
|
| 527 |
{chr(10).join(f'{i+1}. {instruction}' for i, instruction in enumerate(template['instructions']))}
|
| 528 |
|
| 529 |
+
**Nutritional Highlights:**
|
| 530 |
+
• **Protein Content:** {template['protein_content']}
|
| 531 |
+
• **Prep Time:** {template['prep_time']}
|
| 532 |
+
• **Servings:** {template['servings']}
|
|
|
|
| 533 |
|
| 534 |
+
**Chef's Professional Tips:**
|
| 535 |
+
• Use the freshest ingredients available for optimal flavor
|
| 536 |
+
• Control portion sizes for balanced nutrition
|
| 537 |
+
• Include a variety of colorful vegetables for micronutrients
|
| 538 |
+
• Season gradually and taste as you go
|
| 539 |
+
• Let proteins rest before serving for better texture"""
|
| 540 |
|
| 541 |
if modifications:
|
| 542 |
recipe += f"\n\n**🎯 Your Personalized Modifications:**\n{chr(10).join(f'• {mod}' for mod in modifications)}"
|
| 543 |
|
| 544 |
+
recipe += "\n\n*💡 Recipe crafted using professional culinary database - completely free and personalized!*"
|
| 545 |
+
|
| 546 |
+
return recipe
|
| 547 |
+
|
| 548 |
+
def generate_multiple_recipes(food_name):
|
| 549 |
+
"""Generate 3 different recipe variations with food images"""
|
| 550 |
+
food_display = food_name.replace('_', ' ').title()
|
| 551 |
+
|
| 552 |
+
# Initialize recipe count in session state
|
| 553 |
+
if 'recipe_count' not in st.session_state:
|
| 554 |
+
st.session_state.recipe_count = 0
|
| 555 |
+
if 'generated_recipes' not in st.session_state:
|
| 556 |
+
st.session_state.generated_recipes = []
|
| 557 |
+
|
| 558 |
+
# Food images URLs - FIXED with correct food-specific images
|
| 559 |
+
food_images = {
|
| 560 |
+
'grilled_salmon': [
|
| 561 |
+
'https://images.unsplash.com/photo-1467003909585-2f8a72700288?w=300&h=200&fit=crop&auto=format',
|
| 562 |
+
'https://images.unsplash.com/photo-1519708227418-c8fd9a32b7a2?w=300&h=200&fit=crop&auto=format',
|
| 563 |
+
'https://images.unsplash.com/photo-1485704686097-ed47f7263ca4?w=300&h=200&fit=crop&auto=format',
|
| 564 |
+
'https://images.unsplash.com/photo-1544943910-4c1dc44aab44?w=300&h=200&fit=crop&auto=format',
|
| 565 |
+
'https://images.unsplash.com/photo-1559847844-d721426d6edc?w=300&h=200&fit=crop&auto=format',
|
| 566 |
+
'https://images.unsplash.com/photo-1553979459-d2229ba7433a?w=300&h=200&fit=crop&auto=format',
|
| 567 |
+
'https://images.unsplash.com/photo-1478145046317-39f10e56b5e9?w=300&h=200&fit=crop&auto=format',
|
| 568 |
+
'https://images.unsplash.com/photo-1535140728325-781d5ecd3e95?w=300&h=200&fit=crop&auto=format',
|
| 569 |
+
'https://images.unsplash.com/photo-1546833999-b9f581a1996d?w=300&h=200&fit=crop&auto=format'
|
| 570 |
+
],
|
| 571 |
+
'steak': [
|
| 572 |
+
'https://images.unsplash.com/photo-1546833999-b9f581a1996d?w=300&h=200&fit=crop&auto=format&q=80',
|
| 573 |
+
'https://images.unsplash.com/photo-1558030006-450675393462?w=300&h=200&fit=crop&auto=format&q=80',
|
| 574 |
+
'https://images.unsplash.com/photo-1544025162-d76694265947?w=300&h=200&fit=crop&auto=format&q=80',
|
| 575 |
+
'https://images.unsplash.com/photo-1504973960431-1c467e159aa4?w=300&h=200&fit=crop&auto=format&q=80',
|
| 576 |
+
'https://images.unsplash.com/photo-1615937691194-97ddb5266e4a?w=300&h=200&fit=crop&auto=format&q=80',
|
| 577 |
+
'https://images.unsplash.com/photo-1603360946369-dc9bb6258143?w=300&h=200&fit=crop&auto=format&q=80',
|
| 578 |
+
'https://images.unsplash.com/photo-1529692236671-f1f6cf9683ba?w=300&h=200&fit=crop&auto=format&q=80',
|
| 579 |
+
'https://images.unsplash.com/photo-1551782450-17144efb9c50?w=300&h=200&fit=crop&auto=format&q=80',
|
| 580 |
+
'https://images.unsplash.com/photo-1572802419224-296b0aeee0d9?w=300&h=200&fit=crop&auto=format&q=80'
|
| 581 |
+
],
|
| 582 |
+
'pizza': [
|
| 583 |
+
'https://images.unsplash.com/photo-1513104890138-7c749659a591?w=300&h=200&fit=crop&auto=format',
|
| 584 |
+
'https://images.unsplash.com/photo-1565299624946-b28f40a0ca4b?w=300&h=200&fit=crop&auto=format',
|
| 585 |
+
'https://images.unsplash.com/photo-1571407970349-bc81e7e96d47?w=300&h=200&fit=crop&auto=format',
|
| 586 |
+
'https://images.unsplash.com/photo-1574071318508-1cdbab80d002?w=300&h=200&fit=crop&auto=format',
|
| 587 |
+
'https://images.unsplash.com/photo-1506354666786-959d6d497f1a?w=300&h=200&fit=crop&auto=format',
|
| 588 |
+
'https://images.unsplash.com/photo-1593560708920-61dd98c46a4e?w=300&h=200&fit=crop&auto=format',
|
| 589 |
+
'https://images.unsplash.com/photo-1628840042765-356cda07504e?w=300&h=200&fit=crop&auto=format',
|
| 590 |
+
'https://images.unsplash.com/photo-1585238342024-78d387f4a707?w=300&h=200&fit=crop&auto=format',
|
| 591 |
+
'https://images.unsplash.com/photo-1571407970349-bc81e7e96d47?w=300&h=200&fit=crop&auto=format'
|
| 592 |
+
],
|
| 593 |
+
'chicken_curry': [
|
| 594 |
+
'https://images.unsplash.com/photo-1565557623262-b51c2513a641?w=300&h=200&fit=crop&auto=format',
|
| 595 |
+
'https://images.unsplash.com/photo-1574484284002-952d92456975?w=300&h=200&fit=crop&auto=format',
|
| 596 |
+
'https://images.unsplash.com/photo-1588166524941-3bf61a9c41db?w=300&h=200&fit=crop&auto=format',
|
| 597 |
+
'https://images.unsplash.com/photo-1631452180539-96aca7d48617?w=300&h=200&fit=crop&auto=format',
|
| 598 |
+
'https://images.unsplash.com/photo-1565299585323-38174c5f1b8b?w=300&h=200&fit=crop&auto=format',
|
| 599 |
+
'https://images.unsplash.com/photo-1603894584373-5ac82b2ae398?w=300&h=200&fit=crop&auto=format',
|
| 600 |
+
'https://images.unsplash.com/photo-1627662168223-7df99068099a?w=300&h=200&fit=crop&auto=format',
|
| 601 |
+
'https://images.unsplash.com/photo-1574484284002-952d92456975?w=300&h=200&fit=crop&auto=format',
|
| 602 |
+
'https://images.unsplash.com/photo-1588166524941-3bf61a9c41db?w=300&h=200&fit=crop&auto=format'
|
| 603 |
+
],
|
| 604 |
+
'hamburger': [
|
| 605 |
+
'https://images.unsplash.com/photo-1568901346375-23c9450c58cd?w=300&h=200&fit=crop&auto=format',
|
| 606 |
+
'https://images.unsplash.com/photo-1550547660-d9450f859349?w=300&h=200&fit=crop&auto=format',
|
| 607 |
+
'https://images.unsplash.com/photo-1571091718767-18b5b1457add?w=300&h=200&fit=crop&auto=format',
|
| 608 |
+
'https://images.unsplash.com/photo-1553979459-d2229ba7433a?w=300&h=200&fit=crop&auto=format',
|
| 609 |
+
'https://images.unsplash.com/photo-1572802419224-296b0aeee0d9?w=300&h=200&fit=crop&auto=format',
|
| 610 |
+
'https://images.unsplash.com/photo-1594212699903-ec8a3eca50f5?w=300&h=200&fit=crop&auto=format',
|
| 611 |
+
'https://images.unsplash.com/photo-1586816001966-79b736744398?w=300&h=200&fit=crop&auto=format',
|
| 612 |
+
'https://images.unsplash.com/photo-1551782450-17144efb9c50?w=300&h=200&fit=crop&auto=format',
|
| 613 |
+
'https://images.unsplash.com/photo-1565299624946-b28f40a0ca4b?w=300&h=200&fit=crop&auto=format'
|
| 614 |
+
],
|
| 615 |
+
'default': [
|
| 616 |
+
'https://images.unsplash.com/photo-1546793665-c74683f339c1?w=300&h=200&fit=crop&auto=format',
|
| 617 |
+
'https://images.unsplash.com/photo-1565299624946-b28f40a0ca4b?w=300&h=200&fit=crop&auto=format',
|
| 618 |
+
'https://images.unsplash.com/photo-1504674900247-0877df9cc836?w=300&h=200&fit=crop&auto=format',
|
| 619 |
+
'https://images.unsplash.com/photo-1555939594-58d7cb561ad1?w=300&h=200&fit=crop&auto=format',
|
| 620 |
+
'https://images.unsplash.com/photo-1567620905732-2d1ec7ab7445?w=300&h=200&fit=crop&auto=format',
|
| 621 |
+
'https://images.unsplash.com/photo-1482049016688-2d3e1b311543?w=300&h=200&fit=crop&auto=format',
|
| 622 |
+
'https://images.unsplash.com/photo-1540189549336-e6e99c3679fe?w=300&h=200&fit=crop&auto=format',
|
| 623 |
+
'https://images.unsplash.com/photo-1559847844-d721426d6edc?w=300&h=200&fit=crop&auto=format',
|
| 624 |
+
'https://images.unsplash.com/photo-1565299624946-b28f40a0ca4b?w=300&h=200&fit=crop&auto=format'
|
| 625 |
+
]
|
| 626 |
+
}
|
| 627 |
+
|
| 628 |
+
# Get images for this food or use default
|
| 629 |
+
images = food_images.get(food_name, food_images['default'])
|
| 630 |
+
|
| 631 |
+
# Generate recipe styles based on current count
|
| 632 |
+
recipe_styles = ['healthy', 'quick', 'gourmet', 'traditional', 'fusion', 'low-carb', 'protein-rich', 'vegetarian', 'spicy']
|
| 633 |
+
|
| 634 |
+
# Generate 3 different recipe styles
|
| 635 |
+
recipes = []
|
| 636 |
+
start_idx = st.session_state.recipe_count
|
| 637 |
+
|
| 638 |
+
for i in range(3):
|
| 639 |
+
style_idx = (start_idx + i) % len(recipe_styles)
|
| 640 |
+
style = recipe_styles[style_idx]
|
| 641 |
+
image_idx = (start_idx + i) % len(images)
|
| 642 |
+
|
| 643 |
+
recipes.append({
|
| 644 |
+
'title': f'{style.title()} {food_display}',
|
| 645 |
+
'image_url': images[image_idx],
|
| 646 |
+
'recipe': generate_recipe_variation(food_display, style)
|
| 647 |
+
})
|
| 648 |
+
|
| 649 |
+
# Update recipe count
|
| 650 |
+
st.session_state.recipe_count += 3
|
| 651 |
+
|
| 652 |
+
return recipes
|
| 653 |
+
|
| 654 |
+
def generate_recipe_variation(food_display, style):
|
| 655 |
+
"""Generate a recipe variation based on style"""
|
| 656 |
|
| 657 |
+
dietary_restrictions = ""
|
| 658 |
+
if st.session_state.dietary_preferences:
|
| 659 |
+
dietary_restrictions = f"Make it {', '.join(st.session_state.dietary_preferences).lower()}. "
|
| 660 |
+
|
| 661 |
+
allergen_restrictions = ""
|
| 662 |
+
if st.session_state.user_allergens:
|
| 663 |
+
allergen_restrictions = f"Avoid using {', '.join(st.session_state.user_allergens).lower()}. "
|
| 664 |
+
|
| 665 |
+
trans_fat_note = ""
|
| 666 |
+
if st.session_state.avoid_trans_fat:
|
| 667 |
+
trans_fat_note = "Use only healthy oils and avoid trans fats. "
|
| 668 |
+
|
| 669 |
+
# Expanded style-specific modifications
|
| 670 |
+
style_modifications = {
|
| 671 |
+
'healthy': {
|
| 672 |
+
'prefix': '🥗 **HEALTHY VERSION**',
|
| 673 |
+
'cooking_methods': ['grilled', 'baked', 'steamed', 'air-fried'],
|
| 674 |
+
'ingredients_focus': 'organic and fresh ingredients',
|
| 675 |
+
'special_notes': 'Low sodium, high fiber, nutrient-dense preparation',
|
| 676 |
+
'prep_time': 25,
|
| 677 |
+
'difficulty': 2
|
| 678 |
+
},
|
| 679 |
+
'quick': {
|
| 680 |
+
'prefix': '⚡ **QUICK & EASY VERSION**',
|
| 681 |
+
'cooking_methods': ['pan-seared', 'stir-fried', 'microwaved', 'one-pot'],
|
| 682 |
+
'ingredients_focus': 'simple and accessible ingredients',
|
| 683 |
+
'special_notes': 'Ready in 15-20 minutes, minimal prep time',
|
| 684 |
+
'prep_time': 15,
|
| 685 |
+
'difficulty': 1
|
| 686 |
+
},
|
| 687 |
+
'gourmet': {
|
| 688 |
+
'prefix': '🌟 **GOURMET VERSION**',
|
| 689 |
+
'cooking_methods': ['sous-vide', 'braised', 'roasted', 'flambéed'],
|
| 690 |
+
'ingredients_focus': 'premium and artisanal ingredients',
|
| 691 |
+
'special_notes': 'Restaurant-quality presentation and techniques',
|
| 692 |
+
'prep_time': 45,
|
| 693 |
+
'difficulty': 4
|
| 694 |
+
},
|
| 695 |
+
'traditional': {
|
| 696 |
+
'prefix': '🏛️ **TRADITIONAL VERSION**',
|
| 697 |
+
'cooking_methods': ['slow-cooked', 'braised', 'roasted', 'simmered'],
|
| 698 |
+
'ingredients_focus': 'classic and time-tested ingredients',
|
| 699 |
+
'special_notes': 'Authentic preparation methods passed down through generations',
|
| 700 |
+
'prep_time': 60,
|
| 701 |
+
'difficulty': 3
|
| 702 |
+
},
|
| 703 |
+
'fusion': {
|
| 704 |
+
'prefix': '🌍 **FUSION VERSION**',
|
| 705 |
+
'cooking_methods': ['stir-fried', 'grilled', 'pan-seared', 'wok-tossed'],
|
| 706 |
+
'ingredients_focus': 'international spices and modern techniques',
|
| 707 |
+
'special_notes': 'Creative blend of culinary traditions and flavors',
|
| 708 |
+
'prep_time': 30,
|
| 709 |
+
'difficulty': 3
|
| 710 |
+
},
|
| 711 |
+
'low-carb': {
|
| 712 |
+
'prefix': '🥑 **LOW-CARB VERSION**',
|
| 713 |
+
'cooking_methods': ['grilled', 'sautéed', 'roasted', 'steamed'],
|
| 714 |
+
'ingredients_focus': 'high-protein, low-carbohydrate ingredients',
|
| 715 |
+
'special_notes': 'Under 10g carbs per serving, keto-friendly',
|
| 716 |
+
'prep_time': 20,
|
| 717 |
+
'difficulty': 2
|
| 718 |
+
},
|
| 719 |
+
'protein-rich': {
|
| 720 |
+
'prefix': '💪 **HIGH-PROTEIN VERSION**',
|
| 721 |
+
'cooking_methods': ['grilled', 'baked', 'pan-seared', 'broiled'],
|
| 722 |
+
'ingredients_focus': 'lean proteins and muscle-building nutrients',
|
| 723 |
+
'special_notes': '25g+ protein per serving, perfect for fitness goals',
|
| 724 |
+
'prep_time': 25,
|
| 725 |
+
'difficulty': 2
|
| 726 |
+
},
|
| 727 |
+
'vegetarian': {
|
| 728 |
+
'prefix': '🌱 **VEGETARIAN VERSION**',
|
| 729 |
+
'cooking_methods': ['roasted', 'sautéed', 'grilled', 'steamed'],
|
| 730 |
+
'ingredients_focus': 'plant-based proteins and fresh vegetables',
|
| 731 |
+
'special_notes': 'No meat, rich in plant proteins and nutrients',
|
| 732 |
+
'prep_time': 30,
|
| 733 |
+
'difficulty': 2
|
| 734 |
+
},
|
| 735 |
+
'spicy': {
|
| 736 |
+
'prefix': '🌶️ **SPICY VERSION**',
|
| 737 |
+
'cooking_methods': ['stir-fried', 'grilled', 'blackened', 'seared'],
|
| 738 |
+
'ingredients_focus': 'hot peppers, spices, and bold flavors',
|
| 739 |
+
'special_notes': 'Heat level: Medium to Hot, full of flavor',
|
| 740 |
+
'prep_time': 20,
|
| 741 |
+
'difficulty': 2
|
| 742 |
+
}
|
| 743 |
+
}
|
| 744 |
+
|
| 745 |
+
style_info = style_modifications.get(style, style_modifications['healthy'])
|
| 746 |
+
|
| 747 |
+
recipe = f"""{style_info['prefix']}
|
| 748 |
+
|
| 749 |
+
**Cooking Method:** {', '.join(style_info['cooking_methods']).title()}
|
| 750 |
+
**Focus:** {style_info['ingredients_focus'].title()}
|
| 751 |
+
|
| 752 |
+
**Key Features:**
|
| 753 |
+
• {style_info['special_notes']}
|
| 754 |
+
• {dietary_restrictions.strip() if dietary_restrictions else 'Customizable to your dietary needs'}
|
| 755 |
+
• {allergen_restrictions.strip() if allergen_restrictions else 'Allergen-conscious preparation'}
|
| 756 |
+
• {trans_fat_note.strip() if trans_fat_note else 'Heart-healthy cooking methods'}
|
| 757 |
+
|
| 758 |
+
**Professional Tips:**
|
| 759 |
+
• Use high-quality, fresh ingredients for best results
|
| 760 |
+
• Season gradually and taste as you cook
|
| 761 |
+
• Control portion sizes for balanced nutrition
|
| 762 |
+
• Pair with colorful vegetables for complete nutrition
|
| 763 |
+
|
| 764 |
+
**Prep Time:** {style_info['prep_time']} minutes
|
| 765 |
+
**Difficulty:** {style_info['difficulty']}/5 stars"""
|
| 766 |
+
|
| 767 |
return recipe
|
| 768 |
|
| 769 |
# Main App
|
|
|
|
| 772 |
st.markdown("""
|
| 773 |
<div class="main-header">
|
| 774 |
<h1>🍽️ EatSmart Pro</h1>
|
| 775 |
+
<p>🌟 Your AI-Powered Nutrition Assistant 🌟</p>
|
| 776 |
</div>
|
| 777 |
""", unsafe_allow_html=True)
|
| 778 |
|
|
|
|
| 781 |
health_data, allergen_data = load_data()
|
| 782 |
|
| 783 |
if not model:
|
| 784 |
+
st.error("❌ Failed to load AI model. Please check the setup.")
|
| 785 |
return
|
| 786 |
|
| 787 |
+
# Model status with professional styling
|
| 788 |
+
if model_info["name"] == "ConvNeXt Large":
|
| 789 |
+
st.success(f"🎯 **HIGH ACCURACY MODEL ACTIVE** | {model_info['name']} ({model_info['params']} parameters, {model_info['accuracy']} accuracy)")
|
| 790 |
+
else:
|
| 791 |
+
st.warning(f"⚠️ Using {model_info['name']} model ({model_info['params']} parameters, {model_info['accuracy']} accuracy)")
|
| 792 |
|
| 793 |
+
# Professional Sidebar - Enhanced from chat history
|
| 794 |
with st.sidebar:
|
| 795 |
+
st.markdown("""
|
| 796 |
+
<div style="text-align: center; padding: 15px; background: linear-gradient(135deg, #28a745 0%, #007bff 100%); border-radius: 10px; margin-bottom: 20px;">
|
| 797 |
+
<h3 style="color: white; margin: 0;">⚙️ Your Preferences</h3>
|
| 798 |
+
<p style="color: #f8f9fa; margin: 5px 0; font-size: 0.9em;">Personalize your analysis</p>
|
| 799 |
+
</div>
|
| 800 |
+
""", unsafe_allow_html=True)
|
| 801 |
|
| 802 |
+
# Allergen preferences with enhanced UI from history
|
| 803 |
+
st.markdown("### 🚨 Allergen Alerts")
|
| 804 |
+
st.markdown("*Select allergens you want to be warned about:*")
|
|
|
|
|
|
|
|
|
|
| 805 |
|
| 806 |
+
allergen_options = ["Gluten", "Dairy", "Egg", "Fish", "Shellfish", "Nuts", "Peanuts", "Soy", "Sesame", "Wheat"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 807 |
|
| 808 |
+
# Enhanced allergen selection from chat history
|
| 809 |
+
for allergen in allergen_options:
|
| 810 |
+
if st.checkbox(f"🔴 {allergen}", key=f"allergen_{allergen}",
|
| 811 |
+
value=allergen in st.session_state.user_allergens):
|
| 812 |
+
if allergen not in st.session_state.user_allergens:
|
| 813 |
+
st.session_state.user_allergens.append(allergen)
|
| 814 |
+
else:
|
| 815 |
+
if allergen in st.session_state.user_allergens:
|
| 816 |
+
st.session_state.user_allergens.remove(allergen)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 817 |
|
| 818 |
# Trans fat settings
|
| 819 |
st.markdown("### 🧪 Trans Fat Settings")
|
| 820 |
st.session_state.avoid_trans_fat = st.checkbox(
|
| 821 |
+
"⚠️ Alert me about trans fats",
|
| 822 |
value=st.session_state.avoid_trans_fat,
|
| 823 |
+
help="Get warnings about foods that may contain trans fats"
|
| 824 |
)
|
| 825 |
|
| 826 |
+
# Enhanced dietary preferences from history
|
| 827 |
+
st.markdown("### 🌱 Dietary Preferences")
|
| 828 |
+
dietary_options = ["Vegetarian", "Vegan", "Keto", "Low-Carb", "High-Protein", "Mediterranean", "Paleo", "Gluten-Free", "Dairy-Free", "Low-Sodium"]
|
| 829 |
+
|
| 830 |
+
for diet in dietary_options:
|
| 831 |
+
if st.checkbox(f"🥬 {diet}", key=f"diet_{diet}",
|
| 832 |
+
value=diet in st.session_state.dietary_preferences):
|
| 833 |
+
if diet not in st.session_state.dietary_preferences:
|
| 834 |
+
st.session_state.dietary_preferences.append(diet)
|
| 835 |
+
else:
|
| 836 |
+
if diet in st.session_state.dietary_preferences:
|
| 837 |
+
st.session_state.dietary_preferences.remove(diet)
|
| 838 |
+
|
| 839 |
+
# Active preferences summary from history
|
| 840 |
+
if st.session_state.user_allergens or st.session_state.dietary_preferences or st.session_state.avoid_trans_fat:
|
| 841 |
+
st.markdown("---")
|
| 842 |
+
st.markdown("### 📋 Active Preferences")
|
| 843 |
+
if st.session_state.user_allergens:
|
| 844 |
+
st.markdown(f"🚨 **Allergen Alerts:** {', '.join(st.session_state.user_allergens)}")
|
| 845 |
+
if st.session_state.dietary_preferences:
|
| 846 |
+
st.markdown(f"🌱 **Diet:** {', '.join(st.session_state.dietary_preferences)}")
|
| 847 |
+
if st.session_state.avoid_trans_fat:
|
| 848 |
+
st.markdown("🧪 **Trans Fat Alerts:** Enabled")
|
| 849 |
+
|
| 850 |
+
# Professional features list
|
| 851 |
st.markdown("---")
|
| 852 |
+
st.markdown("### 🚀 Professional Features")
|
| 853 |
st.markdown("""
|
| 854 |
✅ **High-Accuracy AI Recognition**
|
| 855 |
✅ **Comprehensive Nutrition Analysis**
|
| 856 |
✅ **Personalized Allergen Alerts**
|
| 857 |
+
✅ **Professional Recipe Generation**
|
| 858 |
✅ **Trans Fat Detection**
|
| 859 |
+
✅ **Protein Content Analysis**
|
| 860 |
✅ **Health Score Assessment**
|
| 861 |
""")
|
| 862 |
|
|
|
|
| 864 |
col1, col2 = st.columns([1, 1])
|
| 865 |
|
| 866 |
with col1:
|
| 867 |
+
# Professional upload section
|
| 868 |
st.markdown("""
|
| 869 |
+
<div class="upload-section">
|
| 870 |
+
<h3 style="margin: 0;">📸 Upload Food Image</h3>
|
| 871 |
+
<p style="margin: 5px 0; font-size: 0.9em;">Choose your preferred method below</p>
|
| 872 |
</div>
|
| 873 |
""", unsafe_allow_html=True)
|
| 874 |
|
| 875 |
+
# File upload - no radio buttons as requested
|
| 876 |
+
st.markdown("#### 📁 Browse Files")
|
| 877 |
+
uploaded_file = st.file_uploader(
|
| 878 |
+
"Choose a food image from your device...",
|
| 879 |
+
type=["jpg", "jpeg", "png"],
|
| 880 |
+
help="Upload a clear image of your food for best results"
|
| 881 |
)
|
| 882 |
+
if uploaded_file:
|
| 883 |
+
try:
|
| 884 |
+
uploaded_image = uploaded_file.getvalue()
|
| 885 |
+
st.session_state.image_buffer = uploaded_image
|
| 886 |
+
st.success("✅ Image uploaded successfully!")
|
| 887 |
+
except Exception as e:
|
| 888 |
+
st.error(f"❌ Error uploading image: {str(e)}")
|
| 889 |
+
st.info("Please try a different image format (JPG, JPEG, or PNG).")
|
| 890 |
|
| 891 |
+
# Camera functionality - restored from history
|
| 892 |
+
st.markdown("#### 📷 Take Photo")
|
| 893 |
+
st.markdown("""
|
| 894 |
+
<div class="camera-section">
|
| 895 |
+
<p style="margin: 5px 0;">Use your device camera to capture food</p>
|
| 896 |
+
</div>
|
| 897 |
+
""", unsafe_allow_html=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 898 |
|
| 899 |
+
camera_photo = st.camera_input("Take a picture of your food")
|
| 900 |
+
if camera_photo:
|
| 901 |
+
try:
|
| 902 |
+
uploaded_image = camera_photo.getvalue()
|
| 903 |
+
st.session_state.image_buffer = uploaded_image
|
| 904 |
+
st.success("✅ Photo captured successfully!")
|
| 905 |
+
except Exception as e:
|
| 906 |
+
st.error(f"❌ Error capturing photo: {str(e)}")
|
| 907 |
+
st.info("Please try taking the photo again.")
|
|
|
|
| 908 |
|
| 909 |
# Display uploaded image and auto-analyze
|
| 910 |
if st.session_state.image_buffer:
|
|
|
|
| 927 |
st.session_state.prediction_result = predictions
|
| 928 |
st.session_state.last_image_buffer = st.session_state.image_buffer
|
| 929 |
st.session_state.last_analyzed_buffer = st.session_state.image_buffer
|
| 930 |
+
st.success("✅ Analysis complete! Ready for checking")
|
| 931 |
except Exception as e:
|
| 932 |
st.error(f"❌ Error analyzing image: {str(e)}")
|
| 933 |
st.info("Please try uploading a different image or refresh the page.")
|
|
|
|
| 937 |
st.info("The uploaded file may be corrupted. Please try uploading again.")
|
| 938 |
|
| 939 |
with col2:
|
| 940 |
+
# Professional analysis section
|
| 941 |
st.markdown("""
|
| 942 |
+
<div class="analysis-section">
|
| 943 |
+
<h3 style="margin: 0;">🔬 Smart Analysis & Insights</h3>
|
| 944 |
+
<p style="margin: 5px 0; font-size: 0.9em;">AI-powered nutrition analysis</p>
|
| 945 |
</div>
|
| 946 |
""", unsafe_allow_html=True)
|
| 947 |
|
| 948 |
# Show instruction if no image uploaded
|
| 949 |
if not st.session_state.image_buffer:
|
| 950 |
+
st.info("👆 Upload an image or take a photo above to start the analysis!")
|
| 951 |
|
| 952 |
# Display results
|
| 953 |
if st.session_state.prediction_result and st.session_state.last_image_buffer == st.session_state.image_buffer:
|
| 954 |
predictions = st.session_state.prediction_result
|
| 955 |
top_prediction = predictions[0]
|
| 956 |
|
| 957 |
+
# Main prediction with professional styling
|
| 958 |
st.markdown(f"""
|
| 959 |
<div class="info-card">
|
| 960 |
<h3>🎯 Food Identified</h3>
|
|
|
|
| 969 |
for pred in predictions[1:]:
|
| 970 |
st.write(f"• {pred['display_name']} ({pred['confidence']:.1f}%)")
|
| 971 |
|
| 972 |
+
# Professional tabs for detailed analysis
|
| 973 |
+
tab1, tab2, tab3 = st.tabs(["🏥 Health Analysis", "⚠️ Allergen Alerts", "👨🍳 Professional Recipes"])
|
| 974 |
|
| 975 |
with tab1:
|
| 976 |
+
# Enhanced health information
|
| 977 |
food_name = top_prediction['class']
|
| 978 |
nutrition = get_nutrition_info(food_name, health_data)
|
| 979 |
health_score = get_health_score(food_name, health_data)
|
| 980 |
|
| 981 |
+
# Professional health score
|
| 982 |
score_class = f"health-score-{health_score.lower()}"
|
| 983 |
st.markdown(f"""
|
| 984 |
<div class="{score_class}">
|
|
|
|
| 986 |
</div>
|
| 987 |
""", unsafe_allow_html=True)
|
| 988 |
|
| 989 |
+
# Professional nutrition metrics
|
| 990 |
col1, col2, col3, col4 = st.columns(4)
|
| 991 |
|
| 992 |
with col1:
|
|
|
|
| 1027 |
for benefit in benefits:
|
| 1028 |
st.markdown(f"• {benefit}")
|
| 1029 |
|
| 1030 |
+
# Enhanced ingredients with protein focus
|
| 1031 |
st.markdown("### 🥘 Common Ingredients")
|
| 1032 |
ingredients = get_common_ingredients(food_name)
|
| 1033 |
for ingredient in ingredients:
|
| 1034 |
st.markdown(f"• {ingredient}")
|
| 1035 |
|
| 1036 |
with tab2:
|
| 1037 |
+
# Enhanced allergen information from history
|
| 1038 |
allergen_info = get_allergen_info(food_name, allergen_data)
|
| 1039 |
|
| 1040 |
+
# User-specific allergen alerts with enhanced styling
|
| 1041 |
user_allergens_lower = [allergen.lower() for allergen in st.session_state.user_allergens]
|
| 1042 |
common_allergens_lower = [allergen.lower() for allergen in allergen_info.get('common_allergens', [])]
|
| 1043 |
may_contain_lower = [allergen.lower() for allergen in allergen_info.get('may_contain', [])]
|
|
|
|
| 1052 |
elif user_allergen in may_contain_lower:
|
| 1053 |
may_contain_matches.append(user_allergen.title())
|
| 1054 |
|
| 1055 |
+
# Enhanced alert display with animation
|
| 1056 |
if allergen_matches:
|
| 1057 |
st.markdown(f"""
|
| 1058 |
<div class="allergen-alert">
|
| 1059 |
+
<h3>🚨 CRITICAL ALLERGEN ALERT</h3>
|
| 1060 |
<p>This food contains: <strong>{', '.join(allergen_matches)}</strong></p>
|
| 1061 |
<p>You have marked these as allergens to avoid!</p>
|
| 1062 |
</div>
|
|
|
|
| 1065 |
if may_contain_matches:
|
| 1066 |
st.markdown(f"""
|
| 1067 |
<div class="allergen-info">
|
| 1068 |
+
<h3>⚠️ May Contain Alert</h3>
|
| 1069 |
<p>This food may contain: <strong>{', '.join(may_contain_matches)}</strong></p>
|
| 1070 |
<p>Please check ingredients carefully.</p>
|
| 1071 |
</div>
|
|
|
|
| 1088 |
for allergen in allergen_info['may_contain']:
|
| 1089 |
st.markdown(f"• {allergen}")
|
| 1090 |
|
| 1091 |
+
# Enhanced trans fat warning
|
| 1092 |
if st.session_state.avoid_trans_fat:
|
| 1093 |
+
trans_fat_foods = ['french_fries', 'donuts', 'fried_calamari', 'onion_rings', 'churros', 'chicken_wings', 'fish_and_chips']
|
| 1094 |
if food_name in trans_fat_foods:
|
| 1095 |
st.markdown("""
|
| 1096 |
<div class="allergen-alert">
|
| 1097 |
+
<h3>🚨 TRANS FAT WARNING</h3>
|
| 1098 |
+
<p><strong>This food may contain trans fats from frying oils!</strong></p>
|
| 1099 |
+
<p>⚠️ Trans fats can increase bad cholesterol and heart disease risk</p>
|
| 1100 |
+
<p>💡 <strong>Healthier alternatives:</strong> Baked, grilled, or air-fried versions</p>
|
| 1101 |
</div>
|
| 1102 |
""", unsafe_allow_html=True)
|
| 1103 |
+
|
| 1104 |
+
# Add specific recommendations
|
| 1105 |
+
st.markdown("### 💚 Healthier Preparation Tips")
|
| 1106 |
+
st.markdown("• **Bake instead of fry** - Use oven at 425°F")
|
| 1107 |
+
st.markdown("• **Air fry** - Reduces oil by 80%")
|
| 1108 |
+
st.markdown("• **Use healthy oils** - Olive oil, avocado oil")
|
| 1109 |
+
st.markdown("• **Grill or steam** - No added fats needed")
|
| 1110 |
+
else:
|
| 1111 |
+
st.success("✅ This food is typically low in trans fats!")
|
| 1112 |
+
else:
|
| 1113 |
+
st.info("💡 Enable trans fat alerts in preferences to get warnings about potentially harmful trans fats.")
|
| 1114 |
|
| 1115 |
with tab3:
|
| 1116 |
+
# Professional recipe generation with multiple recipes and limits
|
| 1117 |
+
st.markdown("### 👨🍳 Professional Recipes")
|
| 1118 |
+
|
| 1119 |
+
# Initialize recipe tracking for this food item
|
| 1120 |
+
if 'current_food_recipes' not in st.session_state:
|
| 1121 |
+
st.session_state.current_food_recipes = {}
|
| 1122 |
+
if 'last_food_analyzed' not in st.session_state:
|
| 1123 |
+
st.session_state.last_food_analyzed = None
|
| 1124 |
+
|
| 1125 |
+
# Reset recipe count if analyzing a different food
|
| 1126 |
+
if st.session_state.last_food_analyzed != food_name:
|
| 1127 |
+
st.session_state.recipe_count = 0
|
| 1128 |
+
st.session_state.current_food_recipes = {}
|
| 1129 |
+
st.session_state.last_food_analyzed = food_name
|
| 1130 |
+
|
| 1131 |
+
# Show current recipe count
|
| 1132 |
+
current_count = st.session_state.get('recipe_count', 0)
|
| 1133 |
+
remaining = 9 - current_count
|
| 1134 |
|
| 1135 |
+
if current_count > 0:
|
| 1136 |
+
st.info(f"📊 **Recipes Generated:** {current_count}/9 | **Remaining:** {remaining}")
|
| 1137 |
+
|
| 1138 |
+
# Button logic based on recipe count
|
| 1139 |
+
if current_count < 9:
|
| 1140 |
+
button_text = f"🍳 Generate {min(3, remaining)} More Professional Recipes"
|
| 1141 |
+
if current_count == 0:
|
| 1142 |
+
button_text = "🍳 Generate 3 Professional Recipes"
|
| 1143 |
+
|
| 1144 |
+
if st.button(button_text, type="primary", key=f"recipe_btn_{current_count}"):
|
| 1145 |
+
with st.spinner("👨🍳 Professional chef is creating your personalized recipes..."):
|
| 1146 |
+
# Generate 3 different recipe variations
|
| 1147 |
+
recipes = generate_multiple_recipes(food_name)
|
| 1148 |
+
|
| 1149 |
+
# Store recipes in session state
|
| 1150 |
+
if food_name not in st.session_state.current_food_recipes:
|
| 1151 |
+
st.session_state.current_food_recipes[food_name] = []
|
| 1152 |
+
st.session_state.current_food_recipes[food_name].extend(recipes)
|
| 1153 |
+
|
| 1154 |
+
st.success(f"✅ {len(recipes)} new recipes generated!")
|
| 1155 |
+
st.rerun()
|
| 1156 |
+
else:
|
| 1157 |
+
st.warning("🔒 **Recipe Limit Reached!** You've generated 9 recipes for this food item.")
|
| 1158 |
+
st.info("💡 **Tip:** Analyze a different food to get more recipe variations!")
|
| 1159 |
+
|
| 1160 |
+
# Display all generated recipes for this food
|
| 1161 |
+
if food_name in st.session_state.current_food_recipes:
|
| 1162 |
+
all_recipes = st.session_state.current_food_recipes[food_name]
|
| 1163 |
+
|
| 1164 |
+
for i, recipe_data in enumerate(all_recipes, 1):
|
| 1165 |
+
st.markdown(f"#### Recipe {i}: {recipe_data['title']}")
|
| 1166 |
|
| 1167 |
+
# Display food image and recipe
|
| 1168 |
+
col_img, col_recipe = st.columns([1, 2])
|
| 1169 |
+
|
| 1170 |
+
with col_img:
|
| 1171 |
+
# Always use styled placeholder - more reliable and professional
|
| 1172 |
+
# Get recipe style for color
|
| 1173 |
+
style_colors = {
|
| 1174 |
+
'healthy': '#28a745',
|
| 1175 |
+
'quick': '#fd7e14',
|
| 1176 |
+
'gourmet': '#6f42c1',
|
| 1177 |
+
'traditional': '#dc3545',
|
| 1178 |
+
'fusion': '#e83e8c',
|
| 1179 |
+
'low-carb': '#20c997',
|
| 1180 |
+
'protein-rich': '#007bff',
|
| 1181 |
+
'vegetarian': '#28a745',
|
| 1182 |
+
'spicy': '#dc3545'
|
| 1183 |
+
}
|
| 1184 |
+
|
| 1185 |
+
# Get food-specific emoji
|
| 1186 |
+
food_emojis = {
|
| 1187 |
+
'caesar_salad': '🥗',
|
| 1188 |
+
'grilled_salmon': '🐟',
|
| 1189 |
+
'steak': '🥩',
|
| 1190 |
+
'pizza': '🍕',
|
| 1191 |
+
'hamburger': '🍔',
|
| 1192 |
+
'chicken_curry': '🍛',
|
| 1193 |
+
'sushi': '🍣',
|
| 1194 |
+
'tacos': '🌮',
|
| 1195 |
+
'ice_cream': '🍦',
|
| 1196 |
+
'chocolate_cake': '🍰'
|
| 1197 |
+
}
|
| 1198 |
+
|
| 1199 |
+
recipe_style = recipe_data['title'].split()[0].lower()
|
| 1200 |
+
color = style_colors.get(recipe_style, '#17a2b8')
|
| 1201 |
+
emoji = food_emojis.get(food_name, '🍽️')
|
| 1202 |
+
|
| 1203 |
+
st.markdown(f"""
|
| 1204 |
+
<div style="width: 200px; height: 133px; background: linear-gradient(135deg, {color}, #17a2b8);
|
| 1205 |
+
border-radius: 15px; display: flex; align-items: center; justify-content: center;
|
| 1206 |
+
color: white; font-weight: bold; text-align: center; padding: 15px; box-sizing: border-box;
|
| 1207 |
+
box-shadow: 0 4px 15px rgba(0,0,0,0.2); margin-bottom: 10px;">
|
| 1208 |
+
<div>
|
| 1209 |
+
<div style="font-size: 3em; margin-bottom: 8px; filter: drop-shadow(2px 2px 4px rgba(0,0,0,0.3));">{emoji}</div>
|
| 1210 |
+
<div style="font-size: 0.85em; line-height: 1.2; text-shadow: 1px 1px 2px rgba(0,0,0,0.5);">{recipe_data['title']}</div>
|
| 1211 |
+
</div>
|
| 1212 |
+
</div>
|
| 1213 |
+
""", unsafe_allow_html=True)
|
| 1214 |
+
|
| 1215 |
+
with col_recipe:
|
| 1216 |
+
st.markdown(f"""
|
| 1217 |
+
<div class="recipe-section">
|
| 1218 |
+
{recipe_data['recipe']}
|
| 1219 |
+
</div>
|
| 1220 |
+
""", unsafe_allow_html=True)
|
| 1221 |
+
|
| 1222 |
+
st.markdown("---")
|
| 1223 |
+
|
| 1224 |
+
# Reset button for new food analysis
|
| 1225 |
+
if current_count > 0:
|
| 1226 |
+
if st.button("🔄 Clear All Recipes (Start Fresh)", type="secondary"):
|
| 1227 |
+
st.session_state.recipe_count = 0
|
| 1228 |
+
st.session_state.current_food_recipes = {}
|
| 1229 |
+
st.success("✅ All recipes cleared! You can now generate new ones.")
|
| 1230 |
+
st.rerun()
|
| 1231 |
|
| 1232 |
if __name__ == "__main__":
|
| 1233 |
main()
|
health_info.json
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"grilled_salmon": {
|
| 3 |
+
"calories": "231",
|
| 4 |
+
"protein": "25.4g",
|
| 5 |
+
"carbs": "0g",
|
| 6 |
+
"fat": "13.4g",
|
| 7 |
+
"fiber": "0g",
|
| 8 |
+
"health_score": "Excellent",
|
| 9 |
+
"benefits": [
|
| 10 |
+
"Rich in high-quality protein for muscle building",
|
| 11 |
+
"Excellent source of omega-3 fatty acids for heart health",
|
| 12 |
+
"Contains vitamin D for bone health",
|
| 13 |
+
"High in B vitamins for energy metabolism",
|
| 14 |
+
"Anti-inflammatory properties"
|
| 15 |
+
]
|
| 16 |
+
},
|
| 17 |
+
"pizza": {
|
| 18 |
+
"calories": "285",
|
| 19 |
+
"protein": "12.2g",
|
| 20 |
+
"carbs": "35.6g",
|
| 21 |
+
"fat": "10.4g",
|
| 22 |
+
"fiber": "2.5g",
|
| 23 |
+
"health_score": "Good",
|
| 24 |
+
"benefits": [
|
| 25 |
+
"Provides carbohydrates for energy",
|
| 26 |
+
"Contains protein from cheese",
|
| 27 |
+
"Tomato sauce provides lycopene antioxidants",
|
| 28 |
+
"Can be made healthier with vegetable toppings"
|
| 29 |
+
]
|
| 30 |
+
},
|
| 31 |
+
"chicken_curry": {
|
| 32 |
+
"calories": "217",
|
| 33 |
+
"protein": "19.8g",
|
| 34 |
+
"carbs": "9.2g",
|
| 35 |
+
"fat": "11.5g",
|
| 36 |
+
"fiber": "2.1g",
|
| 37 |
+
"health_score": "Good",
|
| 38 |
+
"benefits": [
|
| 39 |
+
"High-quality lean protein source",
|
| 40 |
+
"Spices provide anti-inflammatory compounds",
|
| 41 |
+
"Contains turmeric with curcumin benefits",
|
| 42 |
+
"Good source of essential amino acids"
|
| 43 |
+
]
|
| 44 |
+
},
|
| 45 |
+
"caesar_salad": {
|
| 46 |
+
"calories": "158",
|
| 47 |
+
"protein": "7.3g",
|
| 48 |
+
"carbs": "8.9g",
|
| 49 |
+
"fat": "11.2g",
|
| 50 |
+
"fiber": "3.2g",
|
| 51 |
+
"health_score": "Good",
|
| 52 |
+
"benefits": [
|
| 53 |
+
"Rich in vitamins A and K from romaine lettuce",
|
| 54 |
+
"Contains calcium from parmesan cheese",
|
| 55 |
+
"Good source of folate",
|
| 56 |
+
"Provides dietary fiber for digestion"
|
| 57 |
+
]
|
| 58 |
+
},
|
| 59 |
+
"hamburger": {
|
| 60 |
+
"calories": "540",
|
| 61 |
+
"protein": "25.8g",
|
| 62 |
+
"carbs": "40.3g",
|
| 63 |
+
"fat": "31.0g",
|
| 64 |
+
"fiber": "2.2g",
|
| 65 |
+
"health_score": "Poor",
|
| 66 |
+
"benefits": [
|
| 67 |
+
"High protein content for muscle maintenance",
|
| 68 |
+
"Provides iron from beef",
|
| 69 |
+
"Contains B vitamins",
|
| 70 |
+
"Can be made healthier with lean meat and whole grain bun"
|
| 71 |
+
]
|
| 72 |
+
},
|
| 73 |
+
"sushi": {
|
| 74 |
+
"calories": "200",
|
| 75 |
+
"protein": "8.8g",
|
| 76 |
+
"carbs": "43.8g",
|
| 77 |
+
"fat": "0.6g",
|
| 78 |
+
"fiber": "0.3g",
|
| 79 |
+
"health_score": "Good",
|
| 80 |
+
"benefits": [
|
| 81 |
+
"Low in calories and fat",
|
| 82 |
+
"High-quality protein from fish",
|
| 83 |
+
"Contains omega-3 fatty acids",
|
| 84 |
+
"Seaweed provides iodine and minerals"
|
| 85 |
+
]
|
| 86 |
+
},
|
| 87 |
+
"chocolate_cake": {
|
| 88 |
+
"calories": "352",
|
| 89 |
+
"protein": "5.0g",
|
| 90 |
+
"carbs": "50.7g",
|
| 91 |
+
"fat": "16.4g",
|
| 92 |
+
"fiber": "2.0g",
|
| 93 |
+
"health_score": "Poor",
|
| 94 |
+
"benefits": [
|
| 95 |
+
"Provides quick energy from carbohydrates",
|
| 96 |
+
"Dark chocolate contains antioxidants",
|
| 97 |
+
"Can boost mood temporarily",
|
| 98 |
+
"Best enjoyed in moderation"
|
| 99 |
+
]
|
| 100 |
+
},
|
| 101 |
+
"french_fries": {
|
| 102 |
+
"calories": "365",
|
| 103 |
+
"protein": "4.0g",
|
| 104 |
+
"carbs": "63.2g",
|
| 105 |
+
"fat": "17.0g",
|
| 106 |
+
"fiber": "5.7g",
|
| 107 |
+
"health_score": "Poor",
|
| 108 |
+
"benefits": [
|
| 109 |
+
"Provides potassium from potatoes",
|
| 110 |
+
"Contains some vitamin C",
|
| 111 |
+
"Source of carbohydrates for energy",
|
| 112 |
+
"Better when baked instead of fried"
|
| 113 |
+
]
|
| 114 |
+
},
|
| 115 |
+
"ice_cream": {
|
| 116 |
+
"calories": "207",
|
| 117 |
+
"protein": "3.5g",
|
| 118 |
+
"carbs": "23.6g",
|
| 119 |
+
"fat": "11.0g",
|
| 120 |
+
"fiber": "0.7g",
|
| 121 |
+
"health_score": "Poor",
|
| 122 |
+
"benefits": [
|
| 123 |
+
"Contains calcium from dairy",
|
| 124 |
+
"Provides some protein",
|
| 125 |
+
"Can be part of balanced diet in moderation",
|
| 126 |
+
"Frozen yogurt alternatives are healthier"
|
| 127 |
+
]
|
| 128 |
+
},
|
| 129 |
+
"tacos": {
|
| 130 |
+
"calories": "226",
|
| 131 |
+
"protein": "9.4g",
|
| 132 |
+
"carbs": "18.0g",
|
| 133 |
+
"fat": "13.8g",
|
| 134 |
+
"fiber": "3.2g",
|
| 135 |
+
"health_score": "Good",
|
| 136 |
+
"benefits": [
|
| 137 |
+
"Balanced macronutrients",
|
| 138 |
+
"Contains protein from meat or beans",
|
| 139 |
+
"Vegetables provide vitamins and minerals",
|
| 140 |
+
"Can be made very healthy with fresh ingredients"
|
| 141 |
+
]
|
| 142 |
+
},
|
| 143 |
+
"steak": {
|
| 144 |
+
"calories": "271",
|
| 145 |
+
"protein": "26.1g",
|
| 146 |
+
"carbs": "0g",
|
| 147 |
+
"fat": "17.4g",
|
| 148 |
+
"fiber": "0g",
|
| 149 |
+
"health_score": "Good",
|
| 150 |
+
"benefits": [
|
| 151 |
+
"Excellent source of high-quality protein",
|
| 152 |
+
"Rich in iron and zinc",
|
| 153 |
+
"Contains vitamin B12",
|
| 154 |
+
"Provides essential amino acids"
|
| 155 |
+
]
|
| 156 |
+
}
|
| 157 |
+
}
|