Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -1,14 +1,17 @@
|
|
1 |
import streamlit as st
|
2 |
import json
|
3 |
import os
|
|
|
|
|
|
|
|
|
|
|
|
|
4 |
import boto3
|
5 |
from PIL import Image
|
6 |
import firebase_admin
|
7 |
from firebase_admin import credentials, auth
|
8 |
import pandas as pd
|
9 |
-
import uuid
|
10 |
-
from datetime import datetime
|
11 |
-
from io import BytesIO
|
12 |
import streamlit_tags as st_tags
|
13 |
from dotenv import load_dotenv
|
14 |
|
@@ -164,13 +167,17 @@ def save_metadata(user_id, s3_path, food_name, portion_size, portion_unit, cooki
|
|
164 |
image_id = str(uuid.uuid4())
|
165 |
timestamp = datetime.now().isoformat()
|
166 |
|
|
|
|
|
|
|
|
|
167 |
# Create item for DynamoDB
|
168 |
item = {
|
169 |
'image_id': image_id,
|
170 |
'user_id': user_id,
|
171 |
'upload_timestamp': timestamp,
|
172 |
'food_name': food_name,
|
173 |
-
'portion_size': portion_size,
|
174 |
'portion_unit': portion_unit,
|
175 |
'cooking_method': cooking_method,
|
176 |
'ingredients': ingredients,
|
@@ -207,6 +214,10 @@ if "tokens" not in st.session_state:
|
|
207 |
if "uploads_count" not in st.session_state:
|
208 |
st.session_state["uploads_count"] = 0
|
209 |
|
|
|
|
|
|
|
|
|
210 |
# Streamlit Layout - Authentication Section
|
211 |
st.sidebar.title("π User Authentication")
|
212 |
auth_option = st.sidebar.radio("Select an option", ["Login", "Sign Up", "Logout"])
|
@@ -252,22 +263,31 @@ if "user_id" not in st.session_state and not st.session_state.get("demo_mode", F
|
|
252 |
# Add links to guidelines and terms
|
253 |
st.markdown("### π While You're Here")
|
254 |
st.markdown("Take a moment to read our guidelines and token system:")
|
255 |
-
|
256 |
-
|
257 |
-
|
|
|
258 |
with open("PARTICIPATION_GUIDELINES.md", "r") as f:
|
259 |
guidelines = f.read()
|
260 |
-
st.markdown(guidelines)
|
261 |
-
|
262 |
-
|
|
|
|
|
|
|
263 |
with open("TOKEN_REWARDS.md", "r") as f:
|
264 |
rewards = f.read()
|
265 |
-
st.markdown(rewards)
|
266 |
-
|
267 |
-
|
|
|
|
|
|
|
268 |
with open("TERMS_OF_SERVICE.md", "r") as f:
|
269 |
terms = f.read()
|
270 |
-
st.markdown(terms)
|
|
|
|
|
271 |
|
272 |
st.stop()
|
273 |
|
@@ -296,7 +316,7 @@ if uploaded_file:
|
|
296 |
if "original_image" in st.session_state:
|
297 |
original_img = st.session_state["original_image"]
|
298 |
|
299 |
-
# Process the image - resize and compress
|
300 |
processed_img = resize_image(original_img, max_size=512, quality=85)
|
301 |
st.session_state["processed_image"] = processed_img
|
302 |
|
@@ -305,21 +325,36 @@ if "original_image" in st.session_state:
|
|
305 |
processed_size = get_image_size_kb(processed_img)
|
306 |
size_reduction = ((original_size - processed_size) / original_size) * 100 if original_size > 0 else 0
|
307 |
|
308 |
-
# Display images side by side
|
309 |
col1, col2 = st.columns(2)
|
310 |
with col1:
|
311 |
st.subheader("π· Original Image")
|
|
|
312 |
st.image(original_img, caption=f"Original ({original_img.width}x{original_img.height} px, {original_size:.1f} KB)", use_container_width=True)
|
|
|
313 |
with col2:
|
314 |
st.subheader("πΌοΈ Processed Image")
|
|
|
315 |
st.image(processed_img, caption=f"Processed ({processed_img.width}x{processed_img.height} px, {processed_size:.1f} KB)", use_container_width=True)
|
|
|
316 |
|
317 |
# Show size reduction
|
318 |
if size_reduction > 5: # Only show if there's a meaningful reduction
|
319 |
st.success(f"β
Image size reduced by {size_reduction:.1f}% for faster uploads and processing")
|
320 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
321 |
# Food metadata form
|
322 |
-
st.subheader("π² Food Details")
|
323 |
|
324 |
food_name = st.selectbox("Food Name", options=[""] + FOOD_SUGGESTIONS, index=0)
|
325 |
if food_name == "":
|
@@ -327,7 +362,7 @@ if "original_image" in st.session_state:
|
|
327 |
|
328 |
col1, col2 = st.columns(2)
|
329 |
with col1:
|
330 |
-
portion_size = st.number_input("Portion Size", min_value=0.1, step=0.1)
|
331 |
with col2:
|
332 |
portion_unit = st.selectbox("Unit", options=UNIT_OPTIONS)
|
333 |
|
@@ -341,50 +376,87 @@ if "original_image" in st.session_state:
|
|
341 |
maxtags=5
|
342 |
)
|
343 |
|
344 |
-
#
|
345 |
-
|
346 |
-
|
347 |
-
|
348 |
-
|
349 |
-
|
350 |
-
|
351 |
-
|
352 |
-
|
353 |
-
|
354 |
-
|
355 |
-
|
356 |
-
|
357 |
-
|
358 |
-
|
359 |
-
|
360 |
-
|
361 |
-
|
362 |
-
|
363 |
-
|
364 |
-
|
365 |
-
|
366 |
-
|
367 |
-
|
368 |
-
float(portion_size),
|
369 |
-
portion_unit,
|
370 |
-
cooking_method,
|
371 |
-
ingredients,
|
372 |
-
tokens_awarded
|
373 |
-
)
|
374 |
-
|
375 |
-
if success:
|
376 |
-
st.session_state["tokens"] += tokens_awarded
|
377 |
-
st.session_state["uploads_count"] += 1
|
378 |
-
st.success(f"β
Food image uploaded successfully! You earned {tokens_awarded} tokens.")
|
379 |
-
|
380 |
-
# Clear the form and image for a new submission
|
381 |
-
st.session_state.pop("original_image", None)
|
382 |
-
st.session_state.pop("processed_image", None)
|
383 |
-
st.experimental_rerun()
|
384 |
-
else:
|
385 |
-
st.error("Failed to save metadata. Please try again.")
|
386 |
else:
|
387 |
-
st.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
388 |
|
389 |
# Display earned tokens
|
390 |
st.sidebar.markdown("---")
|
@@ -408,4 +480,4 @@ if st.sidebar.button("Token Rewards System"):
|
|
408 |
if st.sidebar.button("Terms of Service"):
|
409 |
with open("TERMS_OF_SERVICE.md", "r") as f:
|
410 |
terms = f.read()
|
411 |
-
st.sidebar.markdown(terms)
|
|
|
1 |
import streamlit as st
|
2 |
import json
|
3 |
import os
|
4 |
+
import uuid
|
5 |
+
from datetime import datetime
|
6 |
+
from io import BytesIO
|
7 |
+
from decimal import Decimal # Add this import for DynamoDB float handling
|
8 |
+
|
9 |
+
# Third-party library imports
|
10 |
import boto3
|
11 |
from PIL import Image
|
12 |
import firebase_admin
|
13 |
from firebase_admin import credentials, auth
|
14 |
import pandas as pd
|
|
|
|
|
|
|
15 |
import streamlit_tags as st_tags
|
16 |
from dotenv import load_dotenv
|
17 |
|
|
|
167 |
image_id = str(uuid.uuid4())
|
168 |
timestamp = datetime.now().isoformat()
|
169 |
|
170 |
+
# Ensure portion_size is a Decimal (DynamoDB doesn't support float)
|
171 |
+
if not isinstance(portion_size, Decimal):
|
172 |
+
portion_size = Decimal(str(portion_size))
|
173 |
+
|
174 |
# Create item for DynamoDB
|
175 |
item = {
|
176 |
'image_id': image_id,
|
177 |
'user_id': user_id,
|
178 |
'upload_timestamp': timestamp,
|
179 |
'food_name': food_name,
|
180 |
+
'portion_size': portion_size, # Decimal type
|
181 |
'portion_unit': portion_unit,
|
182 |
'cooking_method': cooking_method,
|
183 |
'ingredients': ingredients,
|
|
|
214 |
if "uploads_count" not in st.session_state:
|
215 |
st.session_state["uploads_count"] = 0
|
216 |
|
217 |
+
# Initialize food items list for storing multiple annotations
|
218 |
+
if "food_items" not in st.session_state:
|
219 |
+
st.session_state["food_items"] = []
|
220 |
+
|
221 |
# Streamlit Layout - Authentication Section
|
222 |
st.sidebar.title("π User Authentication")
|
223 |
auth_option = st.sidebar.radio("Select an option", ["Login", "Sign Up", "Logout"])
|
|
|
263 |
# Add links to guidelines and terms
|
264 |
st.markdown("### π While You're Here")
|
265 |
st.markdown("Take a moment to read our guidelines and token system:")
|
266 |
+
|
267 |
+
# Use expanders instead of columns for better document display
|
268 |
+
with st.expander("π Participation Guidelines"):
|
269 |
+
try:
|
270 |
with open("PARTICIPATION_GUIDELINES.md", "r") as f:
|
271 |
guidelines = f.read()
|
272 |
+
st.markdown(guidelines, unsafe_allow_html=True)
|
273 |
+
except Exception as e:
|
274 |
+
st.error(f"Could not load guidelines: {e}")
|
275 |
+
|
276 |
+
with st.expander("πͺ Token Rewards System"):
|
277 |
+
try:
|
278 |
with open("TOKEN_REWARDS.md", "r") as f:
|
279 |
rewards = f.read()
|
280 |
+
st.markdown(rewards, unsafe_allow_html=True)
|
281 |
+
except Exception as e:
|
282 |
+
st.error(f"Could not load rewards information: {e}")
|
283 |
+
|
284 |
+
with st.expander("π Terms of Service"):
|
285 |
+
try:
|
286 |
with open("TERMS_OF_SERVICE.md", "r") as f:
|
287 |
terms = f.read()
|
288 |
+
st.markdown(terms, unsafe_allow_html=True)
|
289 |
+
except Exception as e:
|
290 |
+
st.error(f"Could not load terms: {e}")
|
291 |
|
292 |
st.stop()
|
293 |
|
|
|
316 |
if "original_image" in st.session_state:
|
317 |
original_img = st.session_state["original_image"]
|
318 |
|
319 |
+
# Process the image - resize and compress with more visible difference
|
320 |
processed_img = resize_image(original_img, max_size=512, quality=85)
|
321 |
st.session_state["processed_image"] = processed_img
|
322 |
|
|
|
325 |
processed_size = get_image_size_kb(processed_img)
|
326 |
size_reduction = ((original_size - processed_size) / original_size) * 100 if original_size > 0 else 0
|
327 |
|
328 |
+
# Display images side by side with border to highlight differences
|
329 |
col1, col2 = st.columns(2)
|
330 |
with col1:
|
331 |
st.subheader("π· Original Image")
|
332 |
+
st.markdown(f"<div style='border:2px solid red;padding:5px;'>", unsafe_allow_html=True)
|
333 |
st.image(original_img, caption=f"Original ({original_img.width}x{original_img.height} px, {original_size:.1f} KB)", use_container_width=True)
|
334 |
+
st.markdown("</div>", unsafe_allow_html=True)
|
335 |
with col2:
|
336 |
st.subheader("πΌοΈ Processed Image")
|
337 |
+
st.markdown(f"<div style='border:2px solid green;padding:5px;'>", unsafe_allow_html=True)
|
338 |
st.image(processed_img, caption=f"Processed ({processed_img.width}x{processed_img.height} px, {processed_size:.1f} KB)", use_container_width=True)
|
339 |
+
st.markdown("</div>", unsafe_allow_html=True)
|
340 |
|
341 |
# Show size reduction
|
342 |
if size_reduction > 5: # Only show if there's a meaningful reduction
|
343 |
st.success(f"β
Image size reduced by {size_reduction:.1f}% for faster uploads and processing")
|
344 |
|
345 |
+
# Display existing food annotations if any
|
346 |
+
if st.session_state["food_items"]:
|
347 |
+
st.subheader("π Added Food Items")
|
348 |
+
for i, item in enumerate(st.session_state["food_items"]):
|
349 |
+
with st.expander(f"π½οΈ {item['food_name']} ({item['portion_size']} {item['portion_unit']})"):
|
350 |
+
st.write(f"**Cooking Method:** {item['cooking_method']}")
|
351 |
+
st.write(f"**Ingredients:** {', '.join(item['ingredients'])}")
|
352 |
+
if st.button(f"Remove Item #{i+1}", key=f"remove_{i}"):
|
353 |
+
st.session_state["food_items"].pop(i)
|
354 |
+
st.experimental_rerun()
|
355 |
+
|
356 |
# Food metadata form
|
357 |
+
st.subheader("π² Add Food Details")
|
358 |
|
359 |
food_name = st.selectbox("Food Name", options=[""] + FOOD_SUGGESTIONS, index=0)
|
360 |
if food_name == "":
|
|
|
362 |
|
363 |
col1, col2 = st.columns(2)
|
364 |
with col1:
|
365 |
+
portion_size = st.number_input("Portion Size", min_value=0.1, step=0.1, format="%.2f")
|
366 |
with col2:
|
367 |
portion_unit = st.selectbox("Unit", options=UNIT_OPTIONS)
|
368 |
|
|
|
376 |
maxtags=5
|
377 |
)
|
378 |
|
379 |
+
# Add this food to the list
|
380 |
+
col1, col2 = st.columns([1, 1])
|
381 |
+
|
382 |
+
with col1:
|
383 |
+
if st.button("β Add This Food Item"):
|
384 |
+
if food_name and portion_size and portion_unit and cooking_method:
|
385 |
+
# Add the food item to the session state
|
386 |
+
st.session_state["food_items"].append({
|
387 |
+
"food_name": food_name,
|
388 |
+
"portion_size": portion_size,
|
389 |
+
"portion_unit": portion_unit,
|
390 |
+
"cooking_method": cooking_method,
|
391 |
+
"ingredients": ingredients
|
392 |
+
})
|
393 |
+
st.success(f"β
Added {food_name} to your submission")
|
394 |
+
st.experimental_rerun()
|
395 |
+
else:
|
396 |
+
st.error("β Please fill in all required fields")
|
397 |
+
|
398 |
+
# Submit all foods button
|
399 |
+
with col2:
|
400 |
+
if st.button("π€ Submit All Food Items"):
|
401 |
+
if not st.session_state["food_items"]:
|
402 |
+
st.error("β Please add at least one food item before submitting")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
403 |
else:
|
404 |
+
with st.spinner("Processing your submission..."):
|
405 |
+
all_saved = True
|
406 |
+
total_tokens = 0
|
407 |
+
|
408 |
+
# Determine image quality (simplified version)
|
409 |
+
image_quality = "high" if original_img.width >= 1000 and original_img.height >= 1000 else "standard"
|
410 |
+
|
411 |
+
# Upload image to S3 once
|
412 |
+
s3_path = upload_to_s3(processed_img, st.session_state["user_id"])
|
413 |
+
|
414 |
+
if s3_path:
|
415 |
+
# Save each food item with the same image
|
416 |
+
for food_item in st.session_state["food_items"]:
|
417 |
+
# Check if metadata is complete
|
418 |
+
has_metadata = True # Already validated
|
419 |
+
|
420 |
+
# Check if the food is in a unique category (simplified)
|
421 |
+
is_unique_category = food_item["food_name"] not in ["Pizza", "Burger", "Pasta", "Salad"]
|
422 |
+
|
423 |
+
# Calculate tokens for this item
|
424 |
+
tokens_awarded = calculate_tokens(image_quality, has_metadata, is_unique_category)
|
425 |
+
total_tokens += tokens_awarded
|
426 |
+
|
427 |
+
# Convert float to Decimal for DynamoDB
|
428 |
+
portion_size_decimal = Decimal(str(food_item["portion_size"]))
|
429 |
+
|
430 |
+
# Save metadata to DynamoDB
|
431 |
+
success = save_metadata(
|
432 |
+
st.session_state["user_id"],
|
433 |
+
s3_path,
|
434 |
+
food_item["food_name"],
|
435 |
+
portion_size_decimal, # Use Decimal type
|
436 |
+
food_item["portion_unit"],
|
437 |
+
food_item["cooking_method"],
|
438 |
+
food_item["ingredients"],
|
439 |
+
tokens_awarded
|
440 |
+
)
|
441 |
+
|
442 |
+
if not success:
|
443 |
+
all_saved = False
|
444 |
+
break
|
445 |
+
|
446 |
+
if all_saved:
|
447 |
+
st.session_state["tokens"] += total_tokens
|
448 |
+
st.session_state["uploads_count"] += 1
|
449 |
+
st.success(f"β
All food items uploaded successfully! You earned {total_tokens} tokens.")
|
450 |
+
|
451 |
+
# Clear the form and image for a new submission
|
452 |
+
st.session_state.pop("original_image", None)
|
453 |
+
st.session_state.pop("processed_image", None)
|
454 |
+
st.session_state["food_items"] = []
|
455 |
+
st.experimental_rerun()
|
456 |
+
else:
|
457 |
+
st.error("Failed to save some items. Please try again.")
|
458 |
+
else:
|
459 |
+
st.error("Failed to upload image. Please try again.")
|
460 |
|
461 |
# Display earned tokens
|
462 |
st.sidebar.markdown("---")
|
|
|
480 |
if st.sidebar.button("Terms of Service"):
|
481 |
with open("TERMS_OF_SERVICE.md", "r") as f:
|
482 |
terms = f.read()
|
483 |
+
st.sidebar.markdown(terms)
|