doozer21 commited on
Commit
e3dcdb3
·
1 Parent(s): cff7bdf

feature: access file system on mobile

Browse files
Files changed (1) hide show
  1. app.py +190 -228
app.py CHANGED
@@ -1,19 +1,15 @@
1
- # app.py - FoodVision Streamlit Web Application
2
  # ============================================================
3
  #
4
- # FEATURES:
5
- # ---------
6
- # ✅ Upload image (JPEG, PNG, WebP)
7
- # ✅ Take photo with camera (mobile/desktop)
8
- # ✅ Real-time prediction with confidence scores
9
- # ✅ Top-3 predictions display
10
- # ✅ Beautiful UI with image preview
11
- # ✅ Works on mobile & desktop
12
- #
13
- # DEPLOYMENT:
14
- # -----------
15
- # Local: streamlit run app.py
16
- # Cloud: Deploy to Streamlit Cloud, Hugging Face Spaces, or Railway
17
  #
18
  # ============================================================
19
 
@@ -23,7 +19,6 @@ import torch.nn.functional as F
23
  from torchvision import transforms
24
  from PIL import Image
25
  import timm
26
- import numpy as np
27
  from pathlib import Path
28
 
29
  # ============================================================
@@ -31,75 +26,82 @@ from pathlib import Path
31
  # ============================================================
32
 
33
  st.set_page_config(
34
- page_title="FoodVision AI - Food Classifier",
35
  page_icon="🍕",
36
  layout="centered",
37
- initial_sidebar_state="expanded"
38
  )
39
 
40
  # ============================================================
41
- # CUSTOM CSS FOR BEAUTIFUL UI
42
  # ============================================================
43
 
44
  st.markdown("""
45
  <style>
46
- .main-header {
 
 
 
 
 
 
 
47
  text-align: center;
48
  color: #FF6B6B;
49
- font-size: 3rem;
50
- font-weight: bold;
51
  margin-bottom: 0.5rem;
52
  }
53
- .sub-header {
54
- text-align: center;
55
- color: #4ECDC4;
56
- font-size: 1.2rem;
57
- margin-bottom: 2rem;
58
- }
59
- .prediction-box {
60
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
61
- padding: 2rem;
62
- border-radius: 15px;
63
  color: white;
64
  text-align: center;
65
  margin: 1rem 0;
66
  }
67
- .confidence-bar {
68
- background-color: #f0f0f0;
69
- border-radius: 10px;
70
- height: 30px;
 
 
 
 
 
 
 
 
 
 
 
 
 
71
  margin: 0.5rem 0;
72
  overflow: hidden;
 
73
  }
74
- .confidence-fill {
 
75
  height: 100%;
76
  background: linear-gradient(90deg, #4CAF50, #8BC34A);
77
  display: flex;
78
  align-items: center;
79
  justify-content: center;
80
  color: white;
81
- font-weight: bold;
82
- transition: width 0.5s ease;
83
- }
84
- .stButton>button {
85
- width: 100%;
86
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
87
- color: white;
88
- font-size: 1.1rem;
89
- padding: 0.75rem;
90
- border-radius: 10px;
91
- border: none;
92
- font-weight: bold;
93
  }
94
- .stButton>button:hover {
95
- background: linear-gradient(135deg, #764ba2 0%, #667eea 100%);
96
- box-shadow: 0 4px 15px rgba(0,0,0,0.2);
 
97
  }
98
  </style>
99
  """, unsafe_allow_html=True)
100
 
101
  # ============================================================
102
- # FOOD CLASSES (101 categories)
103
  # ============================================================
104
 
105
  FOOD_CLASSES = [
@@ -126,65 +128,67 @@ FOOD_CLASSES = [
126
  ]
127
 
128
  # ============================================================
129
- # MODEL LOADING (WITH CACHING)
130
  # ============================================================
131
 
132
  @st.cache_resource
133
- def load_model(model_path):
134
  """
135
- Loads the trained model with caching.
136
-
137
- Args:
138
- model_path: Path to .pth checkpoint file
139
-
140
- Returns:
141
- Loaded PyTorch model in eval mode
142
  """
143
  try:
144
- # Detect device
145
  device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
146
 
147
- # Load checkpoint
148
- checkpoint = torch.load(model_path, map_location=device)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
149
 
150
- # Get model config from checkpoint
151
  model_config = checkpoint.get('model_config', {
152
  'model_id': 'convnextv2_base.fcmae_ft_in22k_in1k_384'
153
  })
154
 
155
- # Create model architecture
156
  model = timm.create_model(
157
  model_config['model_id'],
158
  pretrained=False,
159
  num_classes=101
160
  )
161
 
162
- # Load trained weights
163
  model.load_state_dict(checkpoint['model_state_dict'])
164
  model.to(device)
165
  model.eval()
166
 
167
- return model, device, checkpoint.get('best_val_acc', 0)
 
 
168
 
169
  except Exception as e:
170
  st.error(f"❌ Error loading model: {str(e)}")
171
- st.info("💡 Make sure 'model1_best.pth' is in the same directory as app.py")
172
- return None, None, 0
173
 
174
  # ============================================================
175
  # IMAGE PREPROCESSING
176
  # ============================================================
177
 
178
  def preprocess_image(image):
179
- """
180
- Preprocesses image for model input.
181
-
182
- Args:
183
- image: PIL Image
184
-
185
- Returns:
186
- Preprocessed tensor ready for model
187
- """
188
  transform = transforms.Compose([
189
  transforms.Resize(256),
190
  transforms.CenterCrop(224),
@@ -195,56 +199,29 @@ def preprocess_image(image):
195
  )
196
  ])
197
 
198
- # Convert to RGB (handle PNG with alpha, grayscale, etc.)
199
  image = image.convert('RGB')
200
-
201
- # Apply transforms
202
- img_tensor = transform(image).unsqueeze(0) # Add batch dimension
203
-
204
- return img_tensor
205
 
206
  # ============================================================
207
- # PREDICTION FUNCTION
208
  # ============================================================
209
 
210
  def predict(model, image_tensor, device, top_k=3):
211
- """
212
- Makes prediction on preprocessed image.
213
-
214
- Args:
215
- model: Trained PyTorch model
216
- image_tensor: Preprocessed image tensor
217
- device: torch device
218
- top_k: Number of top predictions to return
219
-
220
- Returns:
221
- List of (class_name, confidence) tuples
222
- """
223
  with torch.no_grad():
224
- # Move to device
225
  image_tensor = image_tensor.to(device)
226
-
227
- # Forward pass
228
  outputs = model(image_tensor)
229
-
230
- # Get probabilities
231
  probabilities = F.softmax(outputs, dim=1)
232
 
233
- # Get top-k predictions
234
  top_probs, top_indices = torch.topk(probabilities, top_k)
235
-
236
- # Convert to Python lists
237
  top_probs = top_probs.cpu().numpy()[0]
238
  top_indices = top_indices.cpu().numpy()[0]
239
 
240
- # Create results
241
  results = []
242
  for prob, idx in zip(top_probs, top_indices):
243
- class_name = FOOD_CLASSES[idx]
244
- # Format class name (replace underscores, title case)
245
- formatted_name = class_name.replace('_', ' ').title()
246
  confidence = float(prob) * 100
247
- results.append((formatted_name, confidence))
248
 
249
  return results
250
 
@@ -254,159 +231,144 @@ def predict(model, image_tensor, device, top_k=3):
254
 
255
  def main():
256
  # Header
257
- st.markdown('<h1 class="main-header">🍕 FoodVision AI</h1>', unsafe_allow_html=True)
258
- st.markdown('<p class="sub-header">Identify 101 food dishes with AI-powered computer vision</p>', unsafe_allow_html=True)
259
-
260
- # Sidebar
261
- with st.sidebar:
262
- st.header("📊 Model Information")
263
- st.write("**Architecture:** ConvNeXt V2 Base")
264
- st.write("**Training:** Food-101 Dataset")
265
- st.write("**Classes:** 101 food categories")
266
-
267
- st.markdown("---")
268
-
269
- st.header("🎯 How to Use")
270
- st.write("1. Upload a food image or take a photo")
271
- st.write("2. Wait for AI analysis")
272
- st.write("3. View top-3 predictions")
273
-
274
- st.markdown("---")
275
-
276
- st.header("🔗 Resources")
277
- st.markdown("[Food-101 Dataset](https://data.vision.ee.ethz.ch/cvl/datasets_extra/food-101/)")
278
- st.markdown("[ConvNeXt V2 Paper](https://arxiv.org/abs/2301.00808)")
279
-
280
- # Load model
281
- model_path = "model1_best.pth"
282
-
283
- if not Path(model_path).exists():
284
- st.error(f"❌ Model file '{model_path}' not found!")
285
- st.info("💡 Please place your trained model file in the same directory as app.py")
286
- st.stop()
287
 
 
288
  with st.spinner("🔄 Loading AI model..."):
289
- model, device, accuracy = load_model(model_path)
290
 
291
  if model is None:
292
  st.stop()
293
 
294
- st.success(f"✅ Model loaded! Accuracy: {accuracy:.2f}%")
295
-
296
- # Update sidebar with actual accuracy
297
- with st.sidebar:
298
  st.write(f"**Accuracy:** {accuracy:.2f}%")
 
 
299
 
300
- # Main content area
301
  st.markdown("---")
302
 
303
- # Two columns for input methods
304
- col1, col2 = st.columns(2)
305
 
306
- with col1:
307
- st.subheader("📁 Upload Image")
308
- uploaded_file = st.file_uploader(
309
- "Choose a food image",
310
- type=['jpg', 'jpeg', 'png', 'webp'],
311
- help="Supported formats: JPG, JPEG, PNG, WebP"
312
- )
313
 
314
- with col2:
315
- st.subheader("📸 Take Photo")
316
- camera_photo = st.camera_input(
317
- "Take a picture",
318
- help="Works on mobile and desktop with camera"
319
- )
320
 
321
- # Process image (either uploaded or from camera)
322
  image_source = None
 
323
 
324
- if uploaded_file is not None:
325
- image_source = uploaded_file
326
- source_type = "uploaded"
327
- elif camera_photo is not None:
328
  image_source = camera_photo
329
- source_type = "camera"
 
 
 
330
 
331
- # If we have an image, process it
332
  if image_source is not None:
333
  try:
334
  # Load image
335
  image = Image.open(image_source)
336
 
337
- # Display image
 
 
 
 
 
 
 
338
  st.markdown("---")
339
- st.subheader("📷 Your Image")
340
- st.image(image, use_container_width=True, caption=f"Image from {source_type}")
341
 
342
- # Predict button
343
- if st.button("🔮 Analyze Food", use_container_width=True):
344
- with st.spinner("🧠 AI is analyzing your food..."):
345
- # Preprocess
346
- img_tensor = preprocess_image(image)
347
-
348
- # Predict
349
- predictions = predict(model, img_tensor, device, top_k=3)
350
-
351
- # Display results
352
- st.markdown("---")
353
- st.subheader("🎯 Prediction Results")
354
-
355
- # Top prediction (large display)
356
- top_food, top_conf = predictions[0]
357
 
 
358
  st.markdown(f"""
359
- <div class="prediction-box">
360
- <h2>🏆 {top_food}</h2>
361
- <h3>{top_conf:.1f}% Confidence</h3>
 
362
  </div>
363
  """, unsafe_allow_html=True)
364
-
365
- # Top-3 predictions with bars
366
- st.markdown("### 📊 Top 3 Predictions")
367
-
368
- for i, (food, conf) in enumerate(predictions, 1):
369
- # Emoji for rank
370
- emoji = "🥇" if i == 1 else "🥈" if i == 2 else "🥉"
371
-
372
- # Display with confidence bar
373
- st.write(f"**{emoji} {food}**")
374
- st.markdown(f"""
375
- <div class="confidence-bar">
376
- <div class="confidence-fill" style="width: {conf}%">
377
- {conf:.1f}%
378
- </div>
379
- </div>
380
- """, unsafe_allow_html=True)
381
-
382
- # Additional info
383
- st.info(f"💡 **Tip:** The model is {top_conf:.1f}% confident this is {top_food.lower()}!")
384
-
385
- # Fun facts (optional)
386
- if top_conf > 90:
387
- st.success("🎉 Very high confidence! The model is very sure about this prediction.")
388
- elif top_conf > 70:
389
- st.success("👍 Good confidence! This looks like a solid prediction.")
390
- else:
391
- st.warning("🤔 Moderate confidence. The food might be ambiguous or partially visible.")
392
 
393
  except Exception as e:
394
- st.error(f"❌ Error processing image: {str(e)}")
395
- st.info("💡 Try a different image or check if the file is corrupted")
396
 
397
  else:
398
- # No image yet - show example
399
- st.info("👆 Upload an image or take a photo to get started!")
 
 
 
 
 
 
 
 
 
400
 
401
- st.markdown("---")
402
- st.subheader("💡 Tips for Best Results")
403
- st.write("• Use clear, well-lit photos")
404
- st.write("• Make sure the food is the main subject")
405
- st.write("• Avoid heavily filtered or edited images")
406
- st.write("• Try different angles if confidence is low")
 
 
 
 
 
 
 
 
 
 
 
 
 
407
 
408
  # ============================================================
409
- # RUN APP
410
  # ============================================================
411
 
412
  if __name__ == "__main__":
 
1
+ # app.py - FoodVision Streamlit Web Application (Mobile-Optimized)
2
  # ============================================================
3
  #
4
+ # IMPROVEMENTS:
5
+ # -------------
6
+ # ✅ Mobile-friendly single-column layout
7
+ # ✅ Auto-prediction on image upload (no button needed)
8
+ # ✅ Simplified, responsive CSS
9
+ # ✅ Better error handling
10
+ # ✅ Loads model from Hugging Face Hub OR local file
11
+ # ✅ Optimized for slow connections
12
+ # ✅ Touch-friendly interface
 
 
 
 
13
  #
14
  # ============================================================
15
 
 
19
  from torchvision import transforms
20
  from PIL import Image
21
  import timm
 
22
  from pathlib import Path
23
 
24
  # ============================================================
 
26
  # ============================================================
27
 
28
  st.set_page_config(
29
+ page_title="🍕 FoodVision AI",
30
  page_icon="🍕",
31
  layout="centered",
32
+ initial_sidebar_state="collapsed" # Better for mobile
33
  )
34
 
35
  # ============================================================
36
+ # MINIMAL CSS (Mobile-First)
37
  # ============================================================
38
 
39
  st.markdown("""
40
  <style>
41
+ /* Remove extra padding on mobile */
42
+ .block-container {
43
+ padding-top: 2rem;
44
+ padding-bottom: 2rem;
45
+ }
46
+
47
+ /* Cleaner header */
48
+ h1 {
49
  text-align: center;
50
  color: #FF6B6B;
 
 
51
  margin-bottom: 0.5rem;
52
  }
53
+
54
+ /* Result cards */
55
+ .prediction-card {
 
 
 
 
56
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
57
+ padding: 1.5rem;
58
+ border-radius: 12px;
59
  color: white;
60
  text-align: center;
61
  margin: 1rem 0;
62
  }
63
+
64
+ .prediction-card h2 {
65
+ margin: 0;
66
+ font-size: 1.8rem;
67
+ }
68
+
69
+ .prediction-card h3 {
70
+ margin: 0.5rem 0 0 0;
71
+ font-size: 1.2rem;
72
+ opacity: 0.9;
73
+ }
74
+
75
+ /* Confidence bars */
76
+ .conf-bar {
77
+ background: #f0f0f0;
78
+ border-radius: 8px;
79
+ height: 36px;
80
  margin: 0.5rem 0;
81
  overflow: hidden;
82
+ position: relative;
83
  }
84
+
85
+ .conf-fill {
86
  height: 100%;
87
  background: linear-gradient(90deg, #4CAF50, #8BC34A);
88
  display: flex;
89
  align-items: center;
90
  justify-content: center;
91
  color: white;
92
+ font-weight: 600;
93
+ font-size: 0.95rem;
 
 
 
 
 
 
 
 
 
 
94
  }
95
+
96
+ /* Info boxes */
97
+ .stAlert {
98
+ margin-top: 1rem;
99
  }
100
  </style>
101
  """, unsafe_allow_html=True)
102
 
103
  # ============================================================
104
+ # FOOD CLASSES
105
  # ============================================================
106
 
107
  FOOD_CLASSES = [
 
128
  ]
129
 
130
  # ============================================================
131
+ # MODEL LOADING
132
  # ============================================================
133
 
134
  @st.cache_resource
135
+ def load_model():
136
  """
137
+ Loads model from local file or Hugging Face Hub.
138
+ Cached for performance across sessions.
 
 
 
 
 
139
  """
140
  try:
 
141
  device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
142
 
143
+ # Try loading from local file first (for HF Spaces)
144
+ local_path = Path("model1_best.pth")
145
+
146
+ if local_path.exists():
147
+ checkpoint = torch.load(local_path, map_location=device)
148
+ else:
149
+ # Fallback: try to download from HF Hub
150
+ try:
151
+ from huggingface_hub import hf_hub_download
152
+ model_path = hf_hub_download(
153
+ repo_id="doozer21/FoodVision",
154
+ filename="model1_best.pth"
155
+ )
156
+ checkpoint = torch.load(model_path, map_location=device)
157
+ except Exception as e:
158
+ st.error("❌ Could not load model from local file or Hugging Face Hub")
159
+ st.info("Make sure model1_best.pth is in your Space's repository")
160
+ return None, None, None
161
 
162
+ # Get config
163
  model_config = checkpoint.get('model_config', {
164
  'model_id': 'convnextv2_base.fcmae_ft_in22k_in1k_384'
165
  })
166
 
167
+ # Create and load model
168
  model = timm.create_model(
169
  model_config['model_id'],
170
  pretrained=False,
171
  num_classes=101
172
  )
173
 
 
174
  model.load_state_dict(checkpoint['model_state_dict'])
175
  model.to(device)
176
  model.eval()
177
 
178
+ accuracy = checkpoint.get('best_val_acc', 0)
179
+
180
+ return model, device, accuracy
181
 
182
  except Exception as e:
183
  st.error(f"❌ Error loading model: {str(e)}")
184
+ return None, None, None
 
185
 
186
  # ============================================================
187
  # IMAGE PREPROCESSING
188
  # ============================================================
189
 
190
  def preprocess_image(image):
191
+ """Preprocess image for model input."""
 
 
 
 
 
 
 
 
192
  transform = transforms.Compose([
193
  transforms.Resize(256),
194
  transforms.CenterCrop(224),
 
199
  )
200
  ])
201
 
 
202
  image = image.convert('RGB')
203
+ return transform(image).unsqueeze(0)
 
 
 
 
204
 
205
  # ============================================================
206
+ # PREDICTION
207
  # ============================================================
208
 
209
  def predict(model, image_tensor, device, top_k=3):
210
+ """Make prediction on image."""
 
 
 
 
 
 
 
 
 
 
 
211
  with torch.no_grad():
 
212
  image_tensor = image_tensor.to(device)
 
 
213
  outputs = model(image_tensor)
 
 
214
  probabilities = F.softmax(outputs, dim=1)
215
 
 
216
  top_probs, top_indices = torch.topk(probabilities, top_k)
 
 
217
  top_probs = top_probs.cpu().numpy()[0]
218
  top_indices = top_indices.cpu().numpy()[0]
219
 
 
220
  results = []
221
  for prob, idx in zip(top_probs, top_indices):
222
+ class_name = FOOD_CLASSES[idx].replace('_', ' ').title()
 
 
223
  confidence = float(prob) * 100
224
+ results.append((class_name, confidence))
225
 
226
  return results
227
 
 
231
 
232
  def main():
233
  # Header
234
+ st.title("🍕 FoodVision AI")
235
+ st.markdown("**Identify 101 food dishes instantly**")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
236
 
237
+ # Load model with status
238
  with st.spinner("🔄 Loading AI model..."):
239
+ model, device, accuracy = load_model()
240
 
241
  if model is None:
242
  st.stop()
243
 
244
+ # Show model info in expander (cleaner for mobile)
245
+ with st.expander("ℹ️ Model Info"):
246
+ st.write(f"**Architecture:** ConvNeXt V2 Base")
 
247
  st.write(f"**Accuracy:** {accuracy:.2f}%")
248
+ st.write(f"**Device:** {'GPU' if device.type == 'cuda' else 'CPU'}")
249
+ st.write(f"**Classes:** 101 food categories")
250
 
 
251
  st.markdown("---")
252
 
253
+ # Single-column layout (mobile-friendly)
254
+ st.subheader("📸 Upload or Take a Photo")
255
 
256
+ # File uploader
257
+ uploaded_file = st.file_uploader(
258
+ "Choose a food image",
259
+ type=['jpg', 'jpeg', 'png', 'webp'],
260
+ label_visibility="collapsed"
261
+ )
 
262
 
263
+ # Camera input (below uploader)
264
+ st.markdown("**Or use your camera:**")
265
+ camera_photo = st.camera_input(
266
+ "Take a picture",
267
+ label_visibility="collapsed"
268
+ )
269
 
270
+ # Determine which image to use
271
  image_source = None
272
+ source_name = ""
273
 
274
+ if camera_photo is not None:
 
 
 
275
  image_source = camera_photo
276
+ source_name = "camera"
277
+ elif uploaded_file is not None:
278
+ image_source = uploaded_file
279
+ source_name = "upload"
280
 
281
+ # Process image automatically (no button needed!)
282
  if image_source is not None:
283
  try:
284
  # Load image
285
  image = Image.open(image_source)
286
 
287
+ # Show image preview
288
+ st.image(image, caption=f"Image from {source_name}", use_column_width=True)
289
+
290
+ # Auto-predict with spinner
291
+ with st.spinner("🧠 Analyzing your food..."):
292
+ img_tensor = preprocess_image(image)
293
+ predictions = predict(model, img_tensor, device, top_k=3)
294
+
295
  st.markdown("---")
 
 
296
 
297
+ # Display top prediction prominently
298
+ top_food, top_conf = predictions[0]
299
+
300
+ st.markdown(f"""
301
+ <div class="prediction-card">
302
+ <h2>🏆 {top_food}</h2>
303
+ <h3>{top_conf:.1f}% Confidence</h3>
304
+ </div>
305
+ """, unsafe_allow_html=True)
306
+
307
+ # Show all top-3 predictions
308
+ st.markdown("### 📊 Top 3 Predictions")
309
+
310
+ for i, (food, conf) in enumerate(predictions, 1):
311
+ emoji = "🥇" if i == 1 else "🥈" if i == 2 else "🥉"
312
 
313
+ st.markdown(f"**{emoji} {food}**")
314
  st.markdown(f"""
315
+ <div class="conf-bar">
316
+ <div class="conf-fill" style="width: {conf}%">
317
+ {conf:.1f}%
318
+ </div>
319
  </div>
320
  """, unsafe_allow_html=True)
321
+
322
+ # Feedback based on confidence
323
+ st.markdown("---")
324
+ if top_conf > 90:
325
+ st.success("🎉 **Very confident!** The model is very sure about this prediction.")
326
+ elif top_conf > 70:
327
+ st.success("👍 **Good confidence!** This looks like a solid prediction.")
328
+ elif top_conf > 50:
329
+ st.warning("🤔 **Moderate confidence.** The food might be ambiguous or partially visible.")
330
+ else:
331
+ st.warning("😕 **Low confidence.** Try a clearer photo with better lighting.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
332
 
333
  except Exception as e:
334
+ st.error(f"❌ Error: {str(e)}")
335
+ st.info("Try a different image or check if the file is corrupted")
336
 
337
  else:
338
+ # Instructions
339
+ st.info("👆 Upload a food image or take a photo to get started!")
340
+
341
+ with st.expander("💡 Tips for Best Results"):
342
+ st.markdown("""
343
+ - Use clear, well-lit photos
344
+ - Make sure food is the main subject
345
+ - Avoid heavily filtered images
346
+ - Try different angles if confidence is low
347
+ - Works best with common dishes
348
+ """)
349
 
350
+ with st.expander("🍽️ What can it recognize?"):
351
+ st.markdown("""
352
+ The model can identify **101 popular dishes** including:
353
+ - 🍕 Pizza, Pasta, Burgers
354
+ - 🍣 Sushi, Ramen, Pad Thai
355
+ - 🥗 Salads, Sandwiches
356
+ - 🍰 Desserts (cakes, ice cream, etc.)
357
+ - 🍳 Breakfast foods
358
+ - And many more!
359
+ """)
360
+
361
+ # Footer
362
+ st.markdown("---")
363
+ st.markdown(
364
+ "<div style='text-align: center; color: #666; font-size: 0.9rem;'>"
365
+ "Built with Streamlit • ConvNeXt V2 • Food-101 Dataset"
366
+ "</div>",
367
+ unsafe_allow_html=True
368
+ )
369
 
370
  # ============================================================
371
+ # RUN
372
  # ============================================================
373
 
374
  if __name__ == "__main__":