tdurzynski commited on
Commit
5f31935
Β·
verified Β·
1 Parent(s): b5d02dd

Update app.py

Browse files

This is 1st version from Cursor

Files changed (1) hide show
  1. app.py +324 -49
app.py CHANGED
@@ -6,6 +6,14 @@ from PIL import Image
6
  import firebase_admin
7
  from firebase_admin import credentials, auth
8
  import pandas as pd
 
 
 
 
 
 
 
 
9
 
10
  # Load AWS credentials using correct HF Secrets
11
  AWS_ACCESS_KEY = os.getenv("AWS_ACCESS_KEY")
@@ -19,23 +27,38 @@ FIREBASE_CONFIG = json.loads(os.getenv("FIREBASE_CONFIG", "{}"))
19
 
20
  # Initialize Firebase Admin SDK (Prevent multiple initialization)
21
  if not firebase_admin._apps:
22
- cred = credentials.Certificate(FIREBASE_CONFIG)
23
- firebase_admin.initialize_app(cred)
 
 
 
 
 
 
 
24
 
25
  # Initialize AWS Services (S3 & DynamoDB)
26
- s3 = boto3.client(
27
- "s3",
28
- aws_access_key_id=AWS_ACCESS_KEY,
29
- aws_secret_access_key=AWS_SECRET_KEY,
30
- )
31
-
32
- dynamodb = boto3.resource(
33
- "dynamodb",
34
- region_name=AWS_REGION,
35
- aws_access_key_id=AWS_ACCESS_KEY,
36
- aws_secret_access_key=AWS_SECRET_KEY,
37
- )
38
- metadata_table = dynamodb.Table(DYNAMODB_TABLE)
 
 
 
 
 
 
 
 
39
 
40
  # Food Intellisense List
41
  FOOD_SUGGESTIONS = [
@@ -50,6 +73,140 @@ FOOD_SUGGESTIONS = [
50
  # Unit options for food weight/volume
51
  UNIT_OPTIONS = ["grams", "ounces", "teaspoons", "tablespoons", "cups", "slices", "pieces"]
52
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
  # Streamlit Layout - Authentication Section
54
  st.sidebar.title("πŸ”‘ User Authentication")
55
  auth_option = st.sidebar.radio("Select an option", ["Login", "Sign Up", "Logout"])
@@ -59,8 +216,11 @@ if auth_option == "Sign Up":
59
  password = st.sidebar.text_input("Password", type="password")
60
  if st.sidebar.button("Sign Up"):
61
  try:
62
- user = auth.create_user(email=email, password=password)
63
- st.sidebar.success("βœ… User created successfully! Please log in.")
 
 
 
64
  except Exception as e:
65
  st.sidebar.error(f"Error: {e}")
66
 
@@ -69,10 +229,15 @@ if auth_option == "Login":
69
  password = st.sidebar.text_input("Password", type="password")
70
  if st.sidebar.button("Login"):
71
  try:
72
- user = auth.get_user_by_email(email)
73
- st.session_state["user_id"] = user.uid
74
- st.session_state["tokens"] = 0 # Initialize token count
75
- st.sidebar.success("βœ… Logged in successfully!")
 
 
 
 
 
76
  except Exception as e:
77
  st.sidebar.error(f"Login failed: {e}")
78
 
@@ -81,56 +246,166 @@ if auth_option == "Logout" and "user_id" in st.session_state:
81
  st.sidebar.success("βœ… Logged out successfully!")
82
 
83
  # Ensure user is logged in before uploading
84
- if "user_id" not in st.session_state:
85
  st.warning("⚠️ Please log in to upload images.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
86
  st.stop()
87
 
88
- # Streamlit Layout - Three Panel Design
89
  st.title("🍽️ Food Image Review & Annotation")
90
- col1, col2 = st.columns([1, 1])
91
 
92
  # Compliance & Disclaimer Section
93
- st.markdown("### **Terms & Conditions**")
94
- st.write(
95
- "By uploading an image, you agree to transfer full copyright to the research team for AI training purposes."
96
- " You are responsible for ensuring you own the image and it does not violate any copyright laws."
97
- " We do not guarantee when tokens will be redeemable. Keep track of your user ID.")
98
- terms_accepted = st.checkbox("I agree to the terms and conditions", key="terms_accepted")
99
- if not terms_accepted:
100
- st.warning("⚠️ You must agree to the terms before proceeding.")
101
- st.stop()
 
102
 
103
  # Upload Image
104
  uploaded_file = st.file_uploader("Upload an image of your food", type=["jpg", "png", "jpeg"])
105
  if uploaded_file:
106
  original_img = Image.open(uploaded_file)
107
  st.session_state["original_image"] = original_img
108
- st.session_state["tokens"] += 1 # Earn 1 token for upload
109
 
110
  # If an image has been uploaded, process and display it
111
  if "original_image" in st.session_state:
112
  original_img = st.session_state["original_image"]
113
 
114
- def resize_image(image, max_size=512):
115
- aspect_ratio = image.width / image.height
116
- if image.width > image.height:
117
- new_width = max_size
118
- new_height = int(max_size / aspect_ratio)
119
- else:
120
- new_height = max_size
121
- new_width = int(max_size * aspect_ratio)
122
- return image.resize((new_width, new_height))
123
-
124
- processed_img = resize_image(original_img)
125
 
 
126
  col1, col2 = st.columns(2)
127
  with col1:
128
  st.subheader("πŸ“· Original Image")
129
- st.image(original_img, caption=f"Original ({original_img.width}x{original_img.height} pixels)", use_container_width=True)
130
  with col2:
131
  st.subheader("πŸ–ΌοΈ Processed Image")
132
- st.image(processed_img, caption=f"Processed ({processed_img.width}x{processed_img.height} pixels)", use_container_width=True)
133
- st.session_state["processed_image"] = processed_img
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
134
 
135
  # Display earned tokens
136
- st.success(f"πŸŽ‰ Tokens Earned: {st.session_state['tokens']}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
15
+ # Load environment variables from .env file if it exists
16
+ load_dotenv()
17
 
18
  # Load AWS credentials using correct HF Secrets
19
  AWS_ACCESS_KEY = os.getenv("AWS_ACCESS_KEY")
 
27
 
28
  # Initialize Firebase Admin SDK (Prevent multiple initialization)
29
  if not firebase_admin._apps:
30
+ try:
31
+ cred = credentials.Certificate(FIREBASE_CONFIG)
32
+ firebase_admin.initialize_app(cred)
33
+ except Exception as e:
34
+ st.error(f"Firebase initialization error: {e}")
35
+ if st.button("Continue in Demo Mode"):
36
+ st.session_state["demo_mode"] = True
37
+ else:
38
+ st.stop()
39
 
40
  # Initialize AWS Services (S3 & DynamoDB)
41
+ try:
42
+ s3 = boto3.client(
43
+ "s3",
44
+ aws_access_key_id=AWS_ACCESS_KEY,
45
+ aws_secret_access_key=AWS_SECRET_KEY,
46
+ region_name=AWS_REGION
47
+ )
48
+
49
+ dynamodb = boto3.resource(
50
+ "dynamodb",
51
+ region_name=AWS_REGION,
52
+ aws_access_key_id=AWS_ACCESS_KEY,
53
+ aws_secret_access_key=AWS_SECRET_KEY,
54
+ )
55
+ metadata_table = dynamodb.Table(DYNAMODB_TABLE)
56
+ except Exception as e:
57
+ st.error(f"AWS initialization error: {e}")
58
+ if st.button("Continue in Demo Mode"):
59
+ st.session_state["demo_mode"] = True
60
+ else:
61
+ st.stop()
62
 
63
  # Food Intellisense List
64
  FOOD_SUGGESTIONS = [
 
73
  # Unit options for food weight/volume
74
  UNIT_OPTIONS = ["grams", "ounces", "teaspoons", "tablespoons", "cups", "slices", "pieces"]
75
 
76
+ # Cooking methods
77
+ COOKING_METHODS = [
78
+ "Baked", "Boiled", "Broiled", "Fried", "Grilled", "Microwaved",
79
+ "Pan-seared", "Poached", "Raw", "Roasted", "SautΓ©ed", "Steamed",
80
+ "Stewed", "Stir-fried", "Takeout/Restaurant", "Unknown"
81
+ ]
82
+
83
+ # Helper functions
84
+ def resize_image(image, max_size=512, quality=85):
85
+ """
86
+ Resize image while preserving aspect ratio and reducing file size
87
+
88
+ Args:
89
+ image: PIL Image object
90
+ max_size: Maximum dimension (width or height)
91
+ quality: JPEG quality (0-100)
92
+
93
+ Returns:
94
+ Resized PIL Image
95
+ """
96
+ # Calculate new dimensions
97
+ width, height = image.width, image.height
98
+
99
+ # Only resize if the image is larger than max_size
100
+ if width > max_size or height > max_size:
101
+ if width > height:
102
+ new_width = max_size
103
+ new_height = int(height * (max_size / width))
104
+ else:
105
+ new_height = max_size
106
+ new_width = int(width * (max_size / height))
107
+
108
+ # Resize the image
109
+ resized_img = image.resize((new_width, new_height), Image.LANCZOS)
110
+ else:
111
+ # If image is already smaller than max_size, don't resize
112
+ return image
113
+
114
+ # Convert to RGB if image has alpha channel (for JPEG conversion)
115
+ if resized_img.mode == 'RGBA':
116
+ resized_img = resized_img.convert('RGB')
117
+
118
+ # Compress the image
119
+ buffer = BytesIO()
120
+ resized_img.save(buffer, format="JPEG", quality=quality, optimize=True)
121
+ buffer.seek(0)
122
+
123
+ # Return the compressed image
124
+ return Image.open(buffer)
125
+
126
+ def get_image_size_kb(image):
127
+ """Get image file size in KB"""
128
+ buffer = BytesIO()
129
+ image.save(buffer, format="JPEG")
130
+ size_bytes = buffer.tell()
131
+ return size_bytes / 1024 # Convert to KB
132
+
133
+ def upload_to_s3(image, user_id):
134
+ """Upload image to S3 bucket and return the S3 path"""
135
+ if st.session_state.get("demo_mode", False):
136
+ return f"demo/{user_id}/demo_image.jpg"
137
+
138
+ try:
139
+ # Generate a unique ID for the image
140
+ image_id = str(uuid.uuid4())
141
+ timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
142
+ s3_path = f"{user_id}/{timestamp}_{image_id}.jpg"
143
+
144
+ # Convert PIL image to bytes
145
+ buffer = BytesIO()
146
+ image.save(buffer, format="JPEG", quality=85, optimize=True)
147
+ buffer.seek(0)
148
+
149
+ # Upload to S3
150
+ s3.upload_fileobj(buffer, S3_BUCKET_NAME, s3_path)
151
+ return s3_path
152
+ except Exception as e:
153
+ st.error(f"Failed to upload image: {e}")
154
+ return None
155
+
156
+ def save_metadata(user_id, s3_path, food_name, portion_size, portion_unit, cooking_method, ingredients, tokens_awarded):
157
+ """Save metadata to DynamoDB"""
158
+ if st.session_state.get("demo_mode", False):
159
+ st.success("Demo mode: Metadata would be saved to DynamoDB")
160
+ return True
161
+
162
+ try:
163
+ # Generate a unique ID for the database entry
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,
177
+ 's3_path': s3_path,
178
+ 'tokens_awarded': tokens_awarded
179
+ }
180
+
181
+ # Save to DynamoDB
182
+ metadata_table.put_item(Item=item)
183
+ return True
184
+ except Exception as e:
185
+ st.error(f"Failed to save metadata: {e}")
186
+ return False
187
+
188
+ def calculate_tokens(image_quality, has_metadata, is_unique_category):
189
+ """Calculate tokens based on various factors"""
190
+ tokens = 1 # Base token for upload
191
+
192
+ if image_quality == "high":
193
+ tokens += 1
194
+
195
+ if has_metadata:
196
+ tokens += 1
197
+
198
+ if is_unique_category:
199
+ tokens += 1
200
+
201
+ return tokens
202
+
203
+ # Initialize session state for first-time users
204
+ if "tokens" not in st.session_state:
205
+ st.session_state["tokens"] = 0
206
+
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"])
 
216
  password = st.sidebar.text_input("Password", type="password")
217
  if st.sidebar.button("Sign Up"):
218
  try:
219
+ if st.session_state.get("demo_mode", False):
220
+ st.sidebar.success("βœ… Demo mode: User created successfully! Please log in.")
221
+ else:
222
+ user = auth.create_user(email=email, password=password)
223
+ st.sidebar.success("βœ… User created successfully! Please log in.")
224
  except Exception as e:
225
  st.sidebar.error(f"Error: {e}")
226
 
 
229
  password = st.sidebar.text_input("Password", type="password")
230
  if st.sidebar.button("Login"):
231
  try:
232
+ if st.session_state.get("demo_mode", False):
233
+ st.session_state["user_id"] = "demo_user_123"
234
+ st.session_state["tokens"] = 0 # Initialize token count
235
+ st.sidebar.success("βœ… Demo mode: Logged in successfully!")
236
+ else:
237
+ user = auth.get_user_by_email(email)
238
+ st.session_state["user_id"] = user.uid
239
+ st.session_state["tokens"] = 0 # Initialize token count
240
+ st.sidebar.success("βœ… Logged in successfully!")
241
  except Exception as e:
242
  st.sidebar.error(f"Login failed: {e}")
243
 
 
246
  st.sidebar.success("βœ… Logged out successfully!")
247
 
248
  # Ensure user is logged in before uploading
249
+ if "user_id" not in st.session_state and not st.session_state.get("demo_mode", False):
250
  st.warning("⚠️ Please log in to upload images.")
251
+
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
+ col1, col2, col3 = st.columns(3)
256
+ with col1:
257
+ if st.button("Participation Guidelines"):
258
+ with open("PARTICIPATION_GUIDELINES.md", "r") as f:
259
+ guidelines = f.read()
260
+ st.markdown(guidelines)
261
+ with col2:
262
+ if st.button("Token Rewards"):
263
+ with open("TOKEN_REWARDS.md", "r") as f:
264
+ rewards = f.read()
265
+ st.markdown(rewards)
266
+ with col3:
267
+ if st.button("Terms of Service"):
268
+ with open("TERMS_OF_SERVICE.md", "r") as f:
269
+ terms = f.read()
270
+ st.markdown(terms)
271
+
272
  st.stop()
273
 
274
+ # Streamlit Layout - Main App
275
  st.title("🍽️ Food Image Review & Annotation")
 
276
 
277
  # Compliance & Disclaimer Section
278
+ with st.expander("πŸ“œ Terms & Conditions", expanded=False):
279
+ st.markdown("### **Terms & Conditions**")
280
+ st.write(
281
+ "By uploading an image, you agree to transfer full copyright to the research team for AI training purposes."
282
+ " You are responsible for ensuring you own the image and it does not violate any copyright laws."
283
+ " We do not guarantee when tokens will be redeemable. Keep track of your user ID.")
284
+ terms_accepted = st.checkbox("I agree to the terms and conditions", key="terms_accepted")
285
+ if not terms_accepted:
286
+ st.warning("⚠️ You must agree to the terms before proceeding.")
287
+ st.stop()
288
 
289
  # Upload Image
290
  uploaded_file = st.file_uploader("Upload an image of your food", type=["jpg", "png", "jpeg"])
291
  if uploaded_file:
292
  original_img = Image.open(uploaded_file)
293
  st.session_state["original_image"] = original_img
 
294
 
295
  # If an image has been uploaded, process and display it
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
+
303
+ # Calculate file sizes
304
+ original_size = get_image_size_kb(original_img)
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 == "":
326
+ food_name = st.text_input("Or enter a custom food name")
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
+
334
+ cooking_method = st.selectbox("Cooking Method", options=[""] + COOKING_METHODS)
335
+
336
+ ingredients = st_tags.st_tags(
337
+ label="Main Ingredients (Add up to 5)",
338
+ text="Press enter to add",
339
+ value=[],
340
+ suggestions=["Salt", "Pepper", "Olive Oil", "Butter", "Garlic", "Onion", "Tomato"],
341
+ maxtags=5
342
+ )
343
+
344
+ # Submit button
345
+ if st.button("πŸ“€ Submit Food Image"):
346
+ with st.spinner("Processing your submission..."):
347
+ # Determine image quality (simplified version)
348
+ image_quality = "high" if original_img.width >= 1000 and original_img.height >= 1000 else "standard"
349
+
350
+ # Check if metadata is complete
351
+ has_metadata = bool(food_name and portion_size and portion_unit and cooking_method)
352
+
353
+ # Check if the food is in a unique category (simplified)
354
+ is_unique_category = food_name not in ["Pizza", "Burger", "Pasta", "Salad"]
355
+
356
+ # Calculate tokens
357
+ tokens_awarded = calculate_tokens(image_quality, has_metadata, is_unique_category)
358
+
359
+ # Upload image to S3
360
+ s3_path = upload_to_s3(processed_img, st.session_state["user_id"])
361
+
362
+ if s3_path:
363
+ # Save metadata to DynamoDB
364
+ success = save_metadata(
365
+ st.session_state["user_id"],
366
+ s3_path,
367
+ food_name,
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.error("Failed to upload image. Please try again.")
388
 
389
  # Display earned tokens
390
+ st.sidebar.markdown("---")
391
+ st.sidebar.subheader("πŸ† Your Statistics")
392
+ st.sidebar.info(f"πŸͺ™ Total Tokens: {st.session_state['tokens']}")
393
+ st.sidebar.info(f"πŸ“Έ Total Uploads: {st.session_state.get('uploads_count', 0)}")
394
+
395
+ # Help and Documentation Links
396
+ st.sidebar.markdown("---")
397
+ st.sidebar.subheader("πŸ“š Resources")
398
+ if st.sidebar.button("Participation Guidelines"):
399
+ with open("PARTICIPATION_GUIDELINES.md", "r") as f:
400
+ guidelines = f.read()
401
+ st.sidebar.markdown(guidelines)
402
+
403
+ if st.sidebar.button("Token Rewards System"):
404
+ with open("TOKEN_REWARDS.md", "r") as f:
405
+ rewards = f.read()
406
+ st.sidebar.markdown(rewards)
407
+
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)