doozer21 commited on
Commit
cbdf1e6
Β·
verified Β·
1 Parent(s): e3dcdb3

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +133 -71
app.py CHANGED
@@ -4,7 +4,8 @@
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
@@ -20,6 +21,7 @@ from torchvision import transforms
20
  from PIL import Image
21
  import timm
22
  from pathlib import Path
 
23
 
24
  # ============================================================
25
  # PAGE CONFIGURATION
@@ -29,9 +31,20 @@ 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
  # ============================================================
@@ -93,8 +106,13 @@ st.markdown("""
93
  font-size: 0.95rem;
94
  }
95
 
96
- /* Info boxes */
97
- .stAlert {
 
 
 
 
 
98
  margin-top: 1rem;
99
  }
100
  </style>
@@ -127,6 +145,14 @@ FOOD_CLASSES = [
127
  "sushi", "tacos", "takoyaki", "tiramisu", "tuna_tartare", "waffles"
128
  ]
129
 
 
 
 
 
 
 
 
 
130
  # ============================================================
131
  # MODEL LOADING
132
  # ============================================================
@@ -257,106 +283,142 @@ def main():
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("---")
 
4
  # IMPROVEMENTS:
5
  # -------------
6
  # βœ… Mobile-friendly single-column layout
7
+ # βœ… Fixed mobile upload issues with session state
8
+ # βœ… Persistent predictions across reruns
9
  # βœ… Simplified, responsive CSS
10
  # βœ… Better error handling
11
  # βœ… Loads model from Hugging Face Hub OR local file
 
21
  from PIL import Image
22
  import timm
23
  from pathlib import Path
24
+ import hashlib
25
 
26
  # ============================================================
27
  # PAGE CONFIGURATION
 
31
  page_title="πŸ• FoodVision AI",
32
  page_icon="πŸ•",
33
  layout="centered",
34
+ initial_sidebar_state="collapsed"
35
  )
36
 
37
+ # ============================================================
38
+ # SESSION STATE INITIALIZATION
39
+ # ============================================================
40
+
41
+ if 'predictions' not in st.session_state:
42
+ st.session_state.predictions = None
43
+ if 'processed_image' not in st.session_state:
44
+ st.session_state.processed_image = None
45
+ if 'last_image_hash' not in st.session_state:
46
+ st.session_state.last_image_hash = None
47
+
48
  # ============================================================
49
  # MINIMAL CSS (Mobile-First)
50
  # ============================================================
 
106
  font-size: 0.95rem;
107
  }
108
 
109
+ /* Make file uploader more visible */
110
+ .stFileUploader {
111
+ margin-bottom: 1rem;
112
+ }
113
+
114
+ /* Make camera input more visible */
115
+ .stCameraInput {
116
  margin-top: 1rem;
117
  }
118
  </style>
 
145
  "sushi", "tacos", "takoyaki", "tiramisu", "tuna_tartare", "waffles"
146
  ]
147
 
148
+ # ============================================================
149
+ # HELPER FUNCTIONS
150
+ # ============================================================
151
+
152
+ def get_image_hash(image_bytes):
153
+ """Create a hash of image bytes to detect if it's a new image."""
154
+ return hashlib.md5(image_bytes).hexdigest()
155
+
156
  # ============================================================
157
  # MODEL LOADING
158
  # ============================================================
 
283
  uploaded_file = st.file_uploader(
284
  "Choose a food image",
285
  type=['jpg', 'jpeg', 'png', 'webp'],
286
+ key="file_uploader"
287
  )
288
 
289
  # Camera input (below uploader)
290
  st.markdown("**Or use your camera:**")
291
  camera_photo = st.camera_input(
292
  "Take a picture",
293
+ key="camera_input"
294
  )
295
 
296
  # Determine which image to use
297
  image_source = None
298
  source_name = ""
299
+ image_bytes = None
300
 
301
  if camera_photo is not None:
302
  image_source = camera_photo
303
  source_name = "camera"
304
+ image_bytes = camera_photo.getvalue()
305
  elif uploaded_file is not None:
306
  image_source = uploaded_file
307
  source_name = "upload"
308
+ image_bytes = uploaded_file.getvalue()
309
 
310
+ # Process image if we have one
311
+ if image_source is not None and image_bytes is not None:
312
  try:
313
+ # Check if this is a new image
314
+ current_hash = get_image_hash(image_bytes)
 
 
 
 
 
 
 
 
315
 
316
+ # Only process if it's a new image
317
+ if current_hash != st.session_state.last_image_hash:
318
+ # Load image
319
+ image = Image.open(image_source)
320
+
321
+ # Store image in session state
322
+ st.session_state.processed_image = image
323
+ st.session_state.last_image_hash = current_hash
324
+
325
+ # Show loading indicator
326
+ with st.spinner("🧠 Analyzing your food..."):
327
+ # Preprocess and predict
328
+ img_tensor = preprocess_image(image)
329
+ predictions = predict(model, img_tensor, device, top_k=3)
330
+
331
+ # Store predictions in session state
332
+ st.session_state.predictions = predictions
333
 
334
+ # Display results (from session state)
335
+ if st.session_state.processed_image is not None:
336
+ # Show image preview
337
+ st.image(
338
+ st.session_state.processed_image,
339
+ caption=f"Image from {source_name}",
340
+ use_column_width=True
341
+ )
342
 
343
+ if st.session_state.predictions is not None:
344
+ st.markdown("---")
345
+
346
+ # Display top prediction prominently
347
+ top_food, top_conf = st.session_state.predictions[0]
348
 
 
349
  st.markdown(f"""
350
+ <div class="prediction-card">
351
+ <h2>πŸ† {top_food}</h2>
352
+ <h3>{top_conf:.1f}% Confidence</h3>
 
353
  </div>
354
  """, unsafe_allow_html=True)
355
+
356
+ # Show all top-3 predictions
357
+ st.markdown("### πŸ“Š Top 3 Predictions")
358
+
359
+ for i, (food, conf) in enumerate(st.session_state.predictions, 1):
360
+ emoji = "πŸ₯‡" if i == 1 else "πŸ₯ˆ" if i == 2 else "πŸ₯‰"
361
+
362
+ st.markdown(f"**{emoji} {food}**")
363
+ st.markdown(f"""
364
+ <div class="conf-bar">
365
+ <div class="conf-fill" style="width: {conf}%">
366
+ {conf:.1f}%
367
+ </div>
368
+ </div>
369
+ """, unsafe_allow_html=True)
370
+
371
+ # Feedback based on confidence
372
+ st.markdown("---")
373
+ if top_conf > 90:
374
+ st.success("πŸŽ‰ **Very confident!** The model is very sure about this prediction.")
375
+ elif top_conf > 70:
376
+ st.success("πŸ‘ **Good confidence!** This looks like a solid prediction.")
377
+ elif top_conf > 50:
378
+ st.warning("πŸ€” **Moderate confidence.** The food might be ambiguous or partially visible.")
379
+ else:
380
+ st.warning("πŸ˜• **Low confidence.** Try a clearer photo with better lighting.")
381
+
382
+ # Add a clear button to reset
383
+ if st.button("πŸ”„ Analyze Another Image", use_container_width=True):
384
+ st.session_state.predictions = None
385
+ st.session_state.processed_image = None
386
+ st.session_state.last_image_hash = None
387
+ st.rerun()
388
 
389
  except Exception as e:
390
  st.error(f"❌ Error: {str(e)}")
391
  st.info("Try a different image or check if the file is corrupted")
392
+
393
+ # Reset state on error
394
+ st.session_state.predictions = None
395
+ st.session_state.processed_image = None
396
+ st.session_state.last_image_hash = None
397
 
398
  else:
399
+ # Instructions (only show if no predictions)
400
+ if st.session_state.predictions is None:
401
+ st.info("πŸ‘† Upload a food image or take a photo to get started!")
402
+
403
+ with st.expander("πŸ’‘ Tips for Best Results"):
404
+ st.markdown("""
405
+ - Use clear, well-lit photos
406
+ - Make sure food is the main subject
407
+ - Avoid heavily filtered images
408
+ - Try different angles if confidence is low
409
+ - Works best with common dishes
410
+ """)
411
+
412
+ with st.expander("🍽️ What can it recognize?"):
413
+ st.markdown("""
414
+ The model can identify **101 popular dishes** including:
415
+ - πŸ• Pizza, Pasta, Burgers
416
+ - 🍣 Sushi, Ramen, Pad Thai
417
+ - πŸ₯— Salads, Sandwiches
418
+ - 🍰 Desserts (cakes, ice cream, etc.)
419
+ - 🍳 Breakfast foods
420
+ - And many more!
421
+ """)
422
 
423
  # Footer
424
  st.markdown("---")