RaghavenderReddy commited on
Commit
8fc1fd9
·
verified ·
1 Parent(s): 2de8e74

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +689 -552
app.py CHANGED
@@ -2,13 +2,21 @@ import streamlit as st
2
  import json
3
  import os
4
  import uuid
5
- import base64
6
  from datetime import datetime
7
  from PIL import Image
8
- import io
 
9
  import streamlit.components.v1 as components
10
  from translations import get_translation, SUPPORTED_LANGUAGES
11
- from utils import load_users, save_users, load_labels, save_labels, validate_image, get_categories
 
 
 
 
 
 
 
 
12
 
13
  # Initialize session state
14
  if 'user' not in st.session_state:
@@ -18,13 +26,15 @@ if 'language' not in st.session_state:
18
  if 'location_fetched' not in st.session_state:
19
  st.session_state.location_fetched = False
20
  if 'user_location' not in st.session_state:
21
- st.session_state.user_location = {"lat": None, "lon": None}
 
 
22
 
23
- # Ensure data directory exists
24
- if not os.path.exists('data'):
25
- os.makedirs('data')
26
 
27
- # Ensure data files exist
28
  if not os.path.exists('data/users.json'):
29
  with open('data/users.json', 'w') as f:
30
  json.dump({}, f)
@@ -33,636 +43,763 @@ if not os.path.exists('data/labels.json'):
33
  with open('data/labels.json', 'w') as f:
34
  json.dump({}, f)
35
 
36
- def get_user_location():
37
- """Get and store user's location in session state"""
38
- # Session state is already initialized at the top of the file
39
- if not st.session_state.location_fetched:
40
- # Create a placeholder for location status
41
- location_placeholder = st.empty()
42
-
43
- # JavaScript component to get location
44
- location_data = components.html(
45
- """
46
- <script>
47
- function getLocation() {
48
- document.getElementById("status").innerHTML = "🔄 Requesting location permission...";
 
 
 
 
49
 
50
- if (navigator.geolocation) {
51
- navigator.geolocation.getCurrentPosition(
52
- function(position) {
53
- const lat = position.coords.latitude;
54
- const lon = position.coords.longitude;
55
- document.getElementById("status").innerHTML = "📍 Location captured: " + lat.toFixed(6) + ", " + lon.toFixed(6);
56
- // Send data to Streamlit
57
- window.parent.postMessage({
58
- type: 'streamlit:setComponentValue',
59
- value: {lat: lat, lon: lon, success: true}
60
- }, '*');
61
- },
62
- function(error) {
63
- let errorMsg = "📍 Location unavailable";
64
- switch(error.code) {
65
- case error.PERMISSION_DENIED:
66
- errorMsg = "📍 Location access denied by user";
67
- break;
68
- case error.POSITION_UNAVAILABLE:
69
- errorMsg = "📍 Location information unavailable";
70
- break;
71
- case error.TIMEOUT:
72
- errorMsg = "📍 Location request timed out";
73
- break;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
  }
75
- document.getElementById("status").innerHTML = errorMsg;
76
- window.parent.postMessage({
77
- type: 'streamlit:setComponentValue',
78
- value: {lat: null, lon: null, success: false}
79
- }, '*');
80
- },
81
- {enableHighAccuracy: true, timeout: 10000, maximumAge: 300000}
82
- );
83
- } else {
84
- document.getElementById("status").innerHTML = "📍 Location not supported by this browser";
 
 
 
 
 
 
85
  window.parent.postMessage({
86
  type: 'streamlit:setComponentValue',
87
  value: {lat: null, lon: null, success: false}
88
  }, '*');
89
  }
90
- }
91
-
92
- // Run immediately
93
- getLocation();
94
- </script>
95
- <div id="status" style="padding: 12px; background: linear-gradient(45deg, #74b9ff, #0984e3); color: white; border-radius: 10px; text-align: center; font-weight: 500; box-shadow: 0 4px 15px rgba(116, 185, 255, 0.3);">
96
- 🔄 Getting your location...
97
- </div>
98
- """,
99
- height=70
100
- )
 
 
 
 
101
 
102
- # Process location data
103
  if location_data and isinstance(location_data, dict):
104
- if location_data.get('success') == True:
105
  st.session_state.user_location = {
106
  "lat": float(location_data['lat']),
107
- "lon": float(location_data['lon'])
 
 
 
 
 
108
  }
109
  st.session_state.location_fetched = True
110
- location_placeholder.success(f"✅ Location captured: {st.session_state.user_location['lat']:.6f}, {st.session_state.user_location['lon']:.6f}")
111
  elif location_data.get('success') == False:
112
  st.session_state.location_fetched = True
113
- location_placeholder.warning("⚠️ Location not available - uploads will continue without location data")
114
 
115
- # Return the current location status
116
- return st.session_state.user_location if st.session_state.location_fetched else None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
117
 
118
- def authenticate_user(username, password):
119
- """Authenticate user with username and password"""
120
- users = load_users()
121
- if username in users and users[username]['password'] == password:
122
- return users[username]
123
- return None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
124
 
125
- def register_user(username, password, full_name, email, phone, age, language_preference):
126
- """Register a new user"""
127
- users = load_users()
128
- if username in users:
129
- return False
130
-
131
- user_data = {
132
- 'password': password,
133
- 'full_name': full_name,
134
- 'email': email,
135
- 'phone': phone,
136
- 'age': age,
137
- 'language_preference': language_preference,
138
- 'registered_at': datetime.now().isoformat()
139
- }
140
 
141
- users[username] = user_data
142
- save_users(users)
143
- return True
144
-
145
- def login_logout_section():
146
- """Handle login and logout functionality"""
147
- if st.session_state.user:
148
- # User is logged in - show user details and logout
149
- with st.sidebar:
150
- # Display user profile with clean formatting
151
- user = st.session_state.user
152
-
153
- st.markdown("### 👤 Profile")
154
-
155
- # Create a clean profile display
156
- profile_info = f"""
157
- **Name:** {user.get('full_name', 'N/A')}
158
- **Username:** {st.session_state.user['username']}
159
- **Email:** {user.get('email', 'N/A')}
160
- **Phone:** {user.get('phone', 'N/A')}
161
- **Age:** {user.get('age', 'N/A')}
162
- **Language:** {SUPPORTED_LANGUAGES.get(user.get('language_preference', 'en'), 'English')}
163
- """
164
 
165
- st.markdown(profile_info)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
166
 
167
- # Language selector
168
- st.markdown("### 🌐 Language")
169
- language_options = list(SUPPORTED_LANGUAGES.keys())
170
- language_labels = [f"{SUPPORTED_LANGUAGES[lang]} ({lang})" for lang in language_options]
171
 
172
- current_index = language_options.index(st.session_state.language) if st.session_state.language in language_options else 0
173
 
174
- selected_language = st.selectbox(
175
- "Select Language",
176
- options=language_options,
177
- format_func=lambda x: f"{SUPPORTED_LANGUAGES[x]} ({x})",
178
- index=current_index,
179
- key="language_selector"
180
- )
181
 
182
- if selected_language != st.session_state.language:
183
- st.session_state.language = selected_language
184
- st.rerun()
 
 
 
 
 
 
 
 
185
 
186
- # Logout button
187
- if st.button("🚪 " + get_translation("logout", st.session_state.language), use_container_width=True):
188
- st.session_state.user = None
189
- st.session_state.location_fetched = False
190
- st.session_state.user_location = {"lat": None, "lon": None}
191
- st.rerun()
192
-
193
- # Get user location automatically after login
194
- get_user_location()
195
-
196
- else:
197
- # User is not logged in - show login/register forms
198
- st.header(get_translation("welcome", st.session_state.language))
199
-
200
- tab1, tab2 = st.tabs([get_translation("login", st.session_state.language), get_translation("register", st.session_state.language)])
201
 
202
- with tab1:
203
- # Login form
204
- with st.form("login_form"):
205
- username = st.text_input(get_translation("username", st.session_state.language))
206
- password = st.text_input(get_translation("password", st.session_state.language), type="password")
207
- login_button = st.form_submit_button(get_translation("login", st.session_state.language))
208
-
209
- if login_button:
210
- if username and password:
211
- user = authenticate_user(username, password)
212
- if user:
213
- user['username'] = username
214
- st.session_state.user = user
215
- # Set language preference
216
- st.session_state.language = user.get('language_preference', 'en')
217
- st.success(get_translation("login_success", st.session_state.language))
218
- st.rerun()
219
- else:
220
- st.error(get_translation("invalid_credentials", st.session_state.language))
221
- else:
222
- st.error(get_translation("please_fill_all_fields", st.session_state.language))
223
 
224
- with tab2:
225
- # Registration form
226
- with st.form("register_form"):
227
- new_username = st.text_input(get_translation("username", st.session_state.language), key="reg_username")
228
- new_password = st.text_input(get_translation("password", st.session_state.language), type="password", key="reg_password")
229
- full_name = st.text_input(get_translation("full_name", st.session_state.language))
230
- email = st.text_input(get_translation("email", st.session_state.language))
231
- phone = st.text_input(get_translation("phone", st.session_state.language))
232
- age = st.number_input(get_translation("age", st.session_state.language), min_value=1, max_value=120, value=25)
233
-
234
- # Language preference
235
- language_options = list(SUPPORTED_LANGUAGES.keys())
236
- language_pref = st.selectbox(
237
- get_translation("preferred_language", st.session_state.language),
238
- options=language_options,
239
- format_func=lambda x: f"{SUPPORTED_LANGUAGES[x]} ({x})",
240
- index=language_options.index('en')
241
- )
242
-
243
- register_button = st.form_submit_button(get_translation("register", st.session_state.language))
244
-
245
- if register_button:
246
- if new_username and new_password and full_name and email:
247
- if register_user(new_username, new_password, full_name, email, phone, age, language_pref):
248
- st.success(get_translation("registration_success", st.session_state.language))
249
- else:
250
- st.error(get_translation("username_exists", st.session_state.language))
251
- else:
252
- st.error(get_translation("please_fill_required_fields", st.session_state.language))
253
 
254
- def upload_image_section():
255
- """Image upload section"""
256
- st.header(get_translation("upload_image", st.session_state.language))
257
 
258
  # Initialize upload counter for form reset
259
  if 'upload_counter' not in st.session_state:
260
  st.session_state.upload_counter = 0
261
 
262
- # Show current location status with coordinates
263
- if st.session_state.get('user_location', {}).get('lat'):
264
- lat, lon = st.session_state.user_location['lat'], st.session_state.user_location['lon']
265
- st.markdown(f"""
266
- <div style="background: linear-gradient(45deg, #00b894, #00cec9); color: white; padding: 12px; border-radius: 10px; margin: 10px 0; text-align: center; font-weight: 500; box-shadow: 0 4px 15px rgba(0, 184, 148, 0.3);">
267
- 📍 Location ready: {lat:.6f}, {lon:.6f}
268
- </div>
269
- """, unsafe_allow_html=True)
270
- else:
 
 
 
 
 
271
  st.markdown(f"""
272
- <div style="background: linear-gradient(45deg, #fd79a8, #e84393); color: white; padding: 12px; border-radius: 10px; margin: 10px 0; text-align: center; font-weight: 500; box-shadow: 0 4px 15px rgba(253, 121, 168, 0.3);">
273
- 📍 Location not available - uploads will continue without location data
 
274
  </div>
275
  """, unsafe_allow_html=True)
 
 
 
 
 
276
 
277
- with st.form(f"upload_form_{st.session_state.upload_counter}"):
 
278
  uploaded_file = st.file_uploader(
279
- get_translation("choose_image", st.session_state.language),
280
- type=['png', 'jpg', 'jpeg', 'gif']
281
- )
282
-
283
- title = st.text_input(get_translation("image_title", st.session_state.language))
284
- description = st.text_area(get_translation("description", st.session_state.language))
285
-
286
- # Category selection
287
- categories = get_categories()
288
- category = st.selectbox(
289
- get_translation("category", st.session_state.language),
290
- options=categories,
291
- format_func=lambda x: get_translation(x.lower(), st.session_state.language)
292
  )
293
 
294
- # Labels section
295
- st.subheader(get_translation("labels", st.session_state.language))
296
 
297
- # Language selection for label
298
- label_language = st.selectbox(
299
- get_translation("label_language", st.session_state.language),
300
- options=list(SUPPORTED_LANGUAGES.keys()),
301
- format_func=lambda x: f"{SUPPORTED_LANGUAGES[x]} ({x})",
302
- index=list(SUPPORTED_LANGUAGES.keys()).index(st.session_state.language)
303
- )
304
-
305
- label_text = st.text_input(get_translation("label_text", st.session_state.language))
 
 
306
 
307
- submit_button = st.form_submit_button(get_translation("upload_image", st.session_state.language))
308
 
309
- if submit_button:
310
- if uploaded_file and title and description and label_text:
311
- # Validate image
312
- validation_result = validate_image(uploaded_file)
313
- if not validation_result['valid']:
314
- st.error(validation_result['error'])
315
- return
316
-
317
- # Process and save image
318
- try:
319
- # Create images directory if it doesn't exist
320
- images_dir = 'data/images'
321
- if not os.path.exists(images_dir):
322
- os.makedirs(images_dir)
323
-
324
- # Generate unique filename
325
- file_extension = uploaded_file.name.split('.')[-1]
326
- unique_filename = f"{uuid.uuid4()}.{file_extension}"
327
- file_path = os.path.join(images_dir, unique_filename)
328
-
329
- # Save image file
330
- with open(file_path, 'wb') as f:
331
- f.write(uploaded_file.read())
332
-
333
- # Convert image to base64 for display
334
- uploaded_file.seek(0) # Reset file pointer
335
- image = Image.open(uploaded_file)
336
- buffered = io.BytesIO()
337
- image.save(buffered, format="PNG")
338
- img_base64 = base64.b64encode(buffered.getvalue()).decode()
339
-
340
- # Prepare image data
341
- image_data = {
342
- 'id': str(uuid.uuid4()),
343
- 'title': title,
344
- 'description': description,
345
- 'category': category,
346
- 'filename': unique_filename,
347
- 'file_path': file_path,
348
- 'image_base64': img_base64,
349
- 'uploader': st.session_state.user['username'],
350
- 'upload_time': datetime.now().isoformat(),
351
- 'labels': [
352
- {
353
- 'text': label_text,
354
- 'language': label_language,
355
- 'contributor': st.session_state.user['username'],
356
- 'timestamp': datetime.now().isoformat()
357
- }
358
- ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
359
  }
360
-
361
- # Add location if available
362
- if st.session_state.get('user_location', {}).get('lat'):
363
- image_data['location'] = st.session_state.user_location.copy()
364
-
365
- # Save to labels.json
366
- labels_data = load_labels()
367
- labels_data[image_data['id']] = image_data
368
- save_labels(labels_data)
369
-
370
- st.success(get_translation("image_uploaded", st.session_state.language))
371
-
372
- # Reset form by incrementing counter
373
- st.session_state.upload_counter += 1
374
- st.rerun()
375
-
376
- except Exception as e:
377
- st.error(f"Error uploading image: {str(e)}")
378
  else:
379
- st.error(get_translation("please_fill_all_fields", st.session_state.language))
 
 
 
 
 
 
 
 
380
 
381
- def view_images_section():
382
- """View and label images section"""
383
- st.header(get_translation("image_feed", st.session_state.language))
384
 
385
  # Category filter
386
  categories = ['All'] + get_categories()
387
- category_filter = st.selectbox(
388
- get_translation("filter_by_category", st.session_state.language),
389
  options=categories,
390
- format_func=lambda x: get_translation(x.lower(), st.session_state.language) if x != 'All' else get_translation("all", st.session_state.language)
391
  )
392
 
393
- # Load and display images
394
- labels_data = load_labels()
395
 
396
- if not labels_data:
397
- st.info(get_translation("no_images", st.session_state.language))
398
  return
399
 
400
  # Filter by category
401
- filtered_data = labels_data
402
- if category_filter != 'All':
403
- filtered_data = {k: v for k, v in labels_data.items() if v.get('category') == category_filter}
404
-
405
- if not filtered_data:
406
- st.info(get_translation("no_images_in_category", st.session_state.language))
407
- return
408
 
409
- # Custom CSS for better image display
410
- st.markdown("""
411
- <style>
412
- .image-container {
413
- border: 2px solid #e0e0e0;
414
- border-radius: 10px;
415
- padding: 15px;
416
- margin: 10px 0;
417
- background: white;
418
- box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
419
- }
420
- .image-title {
421
- font-size: 1.2em;
422
- font-weight: bold;
423
- color: #2c3e50;
424
- margin-bottom: 8px;
425
- }
426
- .image-description {
427
- color: #555;
428
- margin-bottom: 10px;
429
- }
430
- .labels-section {
431
- background: #f8f9fa;
432
- padding: 10px;
433
- border-radius: 8px;
434
- margin: 10px 0;
435
- }
436
- .label-item {
437
- background: linear-gradient(45deg, #74b9ff, #0984e3);
438
- color: white;
439
- padding: 5px 10px;
440
- border-radius: 15px;
441
- margin: 2px;
442
- display: inline-block;
443
- font-size: 0.9em;
444
- }
445
- .location-badge {
446
- background: linear-gradient(45deg, #00b894, #00cec9);
447
- color: white;
448
- padding: 5px 10px;
449
- border-radius: 15px;
450
- margin: 5px 0;
451
- display: inline-block;
452
- font-size: 0.85em;
453
- font-weight: 500;
454
- }
455
- </style>
456
- """, unsafe_allow_html=True)
457
-
458
- # Display images
459
- for image_id, data in filtered_data.items():
460
  with st.container():
461
- st.markdown('<div class="image-container">', unsafe_allow_html=True)
462
-
463
  col1, col2 = st.columns([1, 2])
464
 
465
  with col1:
466
  # Display image
467
- try:
468
- if data.get('image_base64'):
469
- st.image(f"data:image/png;base64,{data['image_base64']}", width=250)
470
- else:
471
- st.error("Image not available")
472
- except Exception as e:
473
- st.error(f"Error displaying image: {str(e)}")
474
 
475
  with col2:
476
- # Image details
477
- st.markdown(f'<div class="image-title">{data.get("title", "Untitled")}</div>', unsafe_allow_html=True)
478
- st.markdown(f'<div class="image-description">{data.get("description", "No description")}</div>', unsafe_allow_html=True)
479
-
480
- # Category
481
- if data.get('category'):
482
- category_translation = get_translation(data['category'].lower(), st.session_state.language)
483
- st.write(f"🏷️ **{get_translation('category', st.session_state.language)}:** {category_translation}")
484
-
485
- # Upload info
486
- if data.get('uploader'):
487
- st.write(f"👤 {data['uploader']}")
488
 
489
- if data.get('upload_time'):
490
- try:
491
- upload_time = datetime.fromisoformat(data['upload_time']).strftime("%Y-%m-%d %H:%M")
492
- except:
493
- upload_time = data['upload_time']
494
- st.write(f"📅 {upload_time}")
495
-
496
- # Location information with enhanced display
497
- if data.get('location') and data['location'].get('lat') and data['location'].get('lon'):
498
- lat, lon = data['location']['lat'], data['location']['lon']
499
- st.markdown(f"""
500
- <div class="location-badge">
501
- 📍 Coordinates: {lat:.6f}, {lon:.6f}
502
- </div>
503
- """, unsafe_allow_html=True)
504
- else:
505
- st.markdown(f"""
506
- <div class="location-badge" style="background: linear-gradient(45deg, #fd79a8, #e84393);">
507
- 📍 Location not available
508
- </div>
509
- """, unsafe_allow_html=True)
510
-
511
- # Labels section
512
- st.markdown('<div class="labels-section">', unsafe_allow_html=True)
513
- st.write(f"**{get_translation('labels', st.session_state.language)}:**")
514
-
515
- if data.get('labels'):
516
- labels_html = ""
517
- for label in data['labels']:
518
- lang_name = SUPPORTED_LANGUAGES.get(label.get('language', 'en'), 'English')
519
- labels_html += f'<span class="label-item">{label.get("text", "")} ({lang_name})</span> '
520
- st.markdown(labels_html, unsafe_allow_html=True)
521
- else:
522
- st.write(get_translation("no_labels", st.session_state.language))
523
 
524
- st.markdown('</div>', unsafe_allow_html=True)
 
 
 
 
525
 
526
  # Add new label form
527
- with st.expander(get_translation("add_label", st.session_state.language)):
528
- with st.form(f"label_form_{image_id}"):
529
- new_label_language = st.selectbox(
530
- get_translation("label_language", st.session_state.language),
 
531
  options=list(SUPPORTED_LANGUAGES.keys()),
532
- format_func=lambda x: f"{SUPPORTED_LANGUAGES[x]} ({x})",
533
- index=list(SUPPORTED_LANGUAGES.keys()).index(st.session_state.language),
534
- key=f"label_lang_{image_id}"
535
- )
536
-
537
- new_label_text = st.text_input(
538
- get_translation("label_text", st.session_state.language),
539
- key=f"label_text_{image_id}"
540
  )
541
 
542
- add_label_button = st.form_submit_button(get_translation("add_label", st.session_state.language))
543
-
544
- if add_label_button and new_label_text:
545
- # Add new label
546
- new_label = {
547
- 'text': new_label_text,
548
- 'language': new_label_language,
549
- 'contributor': st.session_state.user['username'],
550
- 'timestamp': datetime.now().isoformat()
551
- }
552
-
553
- # Update labels data
554
- labels_data = load_labels()
555
- if 'labels' not in labels_data[image_id]:
556
- labels_data[image_id]['labels'] = []
557
- labels_data[image_id]['labels'].append(new_label)
558
- save_labels(labels_data)
559
-
560
- st.success(get_translation("label_added", st.session_state.language))
561
- st.rerun()
562
 
563
- st.markdown('</div>', unsafe_allow_html=True)
564
  st.markdown("---")
565
 
566
  def main():
567
  """Main application function"""
568
- # Page configuration
569
- st.set_page_config(
570
- page_title="LabelIt! - Multilingual Image Labeling",
571
- page_icon="🏷️",
572
- layout="wide"
573
- )
574
 
575
- # Apply light theme styling
576
- st.markdown("""
577
- <style>
578
- .main {
579
- background-color: #ffffff;
580
- color: #000000;
581
- }
582
- .stApp {
583
- background-color: #ffffff;
584
- }
585
- /* Ensure all text is visible */
586
- .stMarkdown, .stText, p, span, div {
587
- color: #000000 !important;
588
- }
589
- /* Make form labels visible */
590
- .stSelectbox label, .stTextInput label, .stTextArea label, .stFileUploader label {
591
- color: #000000 !important;
592
- font-weight: 500 !important;
593
- }
594
- /* Sidebar styling */
595
- .css-1d391kg {
596
- background-color: #f8f9fa;
597
- }
598
- /* Button styling */
599
- .stButton > button {
600
- background-color: #007bff;
601
- color: white;
602
- border: none;
603
- border-radius: 5px;
604
- padding: 0.5rem 1rem;
605
- }
606
- .stButton > button:hover {
607
- background-color: #0056b3;
608
- }
609
- /* Tab styling */
610
- .stTabs [data-baseweb="tab-list"] {
611
- gap: 8px;
612
- }
613
- .stTabs [data-baseweb="tab"] {
614
- background-color: #e9ecef;
615
- color: #000000;
616
- border-radius: 5px;
617
- padding: 0.5rem 1rem;
618
- }
619
- .stTabs [aria-selected="true"] {
620
- background-color: #007bff;
621
- color: white;
622
- }
623
- /* Form styling */
624
- .stForm {
625
- background-color: #f8f9fa;
626
- padding: 1rem;
627
- border-radius: 10px;
628
- border: 1px solid #dee2e6;
629
- }
630
- /* Success/Error message styling */
631
- .stSuccess {
632
- background-color: #d4edda;
633
- color: #155724;
634
- border: 1px solid #c3e6cb;
635
- }
636
- .stError {
637
- background-color: #f8d7da;
638
- color: #721c24;
639
- border: 1px solid #f5c6cb;
640
- }
641
- </style>
642
- """, unsafe_allow_html=True)
643
 
644
- # Main title
645
- st.title("🏷️ LabelIt! - " + get_translation("multilingual_image_labeling", st.session_state.language))
 
646
 
647
- # Login/logout section
648
- login_logout_section()
 
 
 
 
 
 
 
 
 
 
649
 
650
- # Main application content (only show if user is logged in)
651
- if st.session_state.user:
652
- # Navigation tabs
 
 
 
 
 
 
 
 
 
 
 
653
  tab1, tab2 = st.tabs([
654
- get_translation("upload_image", st.session_state.language),
655
- get_translation("view_images", st.session_state.language)
656
  ])
657
 
658
  with tab1:
659
- upload_image_section()
660
 
661
  with tab2:
662
- view_images_section()
 
663
  else:
664
- # Show welcome message for non-logged in users
665
- st.info(get_translation("please_login", st.session_state.language))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
666
 
667
  if __name__ == "__main__":
668
- main()
 
2
  import json
3
  import os
4
  import uuid
 
5
  from datetime import datetime
6
  from PIL import Image
7
+ import base64
8
+ from io import BytesIO
9
  import streamlit.components.v1 as components
10
  from translations import get_translation, SUPPORTED_LANGUAGES
11
+ from utils import load_users, save_users, load_labels, save_labels, validate_image, get_categories, calculate_statistics
12
+
13
+ # Page configuration
14
+ st.set_page_config(
15
+ page_title="LabelIt! 🇮🇳",
16
+ page_icon="🏷️",
17
+ layout="wide",
18
+ initial_sidebar_state="expanded"
19
+ )
20
 
21
  # Initialize session state
22
  if 'user' not in st.session_state:
 
26
  if 'location_fetched' not in st.session_state:
27
  st.session_state.location_fetched = False
28
  if 'user_location' not in st.session_state:
29
+ st.session_state.user_location = {"lat": None, "lon": None, "accuracy": None, "method": None, "timestamp": None}
30
+ if 'manual_location_mode' not in st.session_state:
31
+ st.session_state.manual_location_mode = False
32
 
33
+ # Create data directories if they don't exist
34
+ os.makedirs('data', exist_ok=True)
35
+ os.makedirs('data/images', exist_ok=True)
36
 
37
+ # Initialize data files
38
  if not os.path.exists('data/users.json'):
39
  with open('data/users.json', 'w') as f:
40
  json.dump({}, f)
 
43
  with open('data/labels.json', 'w') as f:
44
  json.dump({}, f)
45
 
46
+ def get_enhanced_location():
47
+ """Enhanced location capture with multiple fallback methods and accuracy indicators"""
48
+ if not st.session_state.location_fetched and not st.session_state.manual_location_mode:
49
+ st.markdown("### 📍 Location Capture for Image Upload")
50
+
51
+ # Show importance message
52
+ st.info("📍 **Location Required**: Each image upload needs GPS coordinates for better dataset quality and regional insights.")
53
+
54
+ col1, col2 = st.columns([2, 1])
55
+
56
+ with col1:
57
+ # Enhanced JavaScript location component with multiple fallback methods
58
+ location_data = components.html(
59
+ """
60
+ <script>
61
+ let locationAttempts = 0;
62
+ const maxAttempts = 3;
63
 
64
+ function tryGetLocation() {
65
+ locationAttempts++;
66
+ document.getElementById("status").innerHTML = `🔄 Attempting high-accuracy GPS location (${locationAttempts}/${maxAttempts})...`;
67
+
68
+ if (navigator.geolocation) {
69
+ const options = {
70
+ enableHighAccuracy: true,
71
+ timeout: 20000,
72
+ maximumAge: 30000
73
+ };
74
+
75
+ navigator.geolocation.getCurrentPosition(
76
+ function(position) {
77
+ const lat = position.coords.latitude;
78
+ const lon = position.coords.longitude;
79
+ const accuracy = position.coords.accuracy;
80
+ const altitude = position.coords.altitude;
81
+ const heading = position.coords.heading;
82
+ const speed = position.coords.speed;
83
+ const timestamp = new Date().toISOString();
84
+
85
+ let accuracyLevel = "Low";
86
+ let accuracyColor = "#e74c3c";
87
+ let accuracyEmoji = "🟡";
88
+ if (accuracy <= 10) {
89
+ accuracyLevel = "High";
90
+ accuracyColor = "#27ae60";
91
+ accuracyEmoji = "🟢";
92
+ } else if (accuracy <= 50) {
93
+ accuracyLevel = "Medium";
94
+ accuracyColor = "#f39c12";
95
+ accuracyEmoji = "🟠";
96
+ } else {
97
+ accuracyEmoji = "🔴";
98
+ }
99
+
100
+ document.getElementById("status").innerHTML = `
101
+ <div style="background: linear-gradient(45deg, #27ae60, #2ecc71); color: white; padding: 15px; border-radius: 10px; margin: 10px 0; box-shadow: 0 4px 15px rgba(39, 174, 96, 0.3);">
102
+ <div style="font-weight: bold; margin-bottom: 8px;">✅ GPS Location Captured Successfully!</div>
103
+ <div style="font-size: 0.95em; margin-bottom: 5px;">📍 <strong>${lat.toFixed(6)}, ${lon.toFixed(6)}</strong></div>
104
+ <div style="font-size: 0.85em; margin-bottom: 5px;">
105
+ ${accuracyEmoji} Accuracy: <span style="color: ${accuracyColor}; font-weight: bold;">${accuracyLevel}</span> (±${accuracy.toFixed(0)}m)
106
+ </div>
107
+ <div style="font-size: 0.8em; opacity: 0.9;">⏰ ${new Date(timestamp).toLocaleString()}</div>
108
+ ${altitude ? `<div style="font-size: 0.8em; opacity: 0.9;">⛰️ Altitude: ${altitude.toFixed(0)}m</div>` : ''}
109
+ </div>
110
+ `;
111
+
112
+ window.parent.postMessage({
113
+ type: 'streamlit:setComponentValue',
114
+ value: {
115
+ lat: lat,
116
+ lon: lon,
117
+ accuracy: accuracy,
118
+ altitude: altitude,
119
+ heading: heading,
120
+ speed: speed,
121
+ method: "GPS",
122
+ timestamp: timestamp,
123
+ success: true
124
+ }
125
+ }, '*');
126
+ },
127
+ function(error) {
128
+ let errorMsg = "📍 GPS location unavailable";
129
+ let errorDetail = "";
130
+ switch(error.code) {
131
+ case error.PERMISSION_DENIED:
132
+ errorMsg = "🚫 Location access denied";
133
+ errorDetail = "Please allow location access in your browser settings";
134
+ break;
135
+ case error.POSITION_UNAVAILABLE:
136
+ errorMsg = "📍 GPS position unavailable";
137
+ errorDetail = "Your device's GPS might be disabled";
138
+ break;
139
+ case error.TIMEOUT:
140
+ errorMsg = "⏱️ GPS request timed out";
141
+ errorDetail = "GPS is taking too long to respond";
142
+ break;
143
+ }
144
+
145
+ if (locationAttempts < maxAttempts) {
146
+ document.getElementById("status").innerHTML = `
147
+ <div style="background: linear-gradient(45deg, #f39c12, #e67e22); color: white; padding: 15px; border-radius: 10px; margin: 10px 0;">
148
+ <div style="font-weight: bold; margin-bottom: 5px;">${errorMsg}</div>
149
+ <div style="font-size: 0.9em; margin-bottom: 5px;">${errorDetail}</div>
150
+ <div style="font-size: 0.8em;">🔄 Retrying in 2 seconds... (${locationAttempts}/${maxAttempts})</div>
151
+ </div>
152
+ `;
153
+ setTimeout(tryGetLocation, 2000);
154
+ } else {
155
+ // Try IP-based location as fallback
156
+ tryIPLocation();
157
+ }
158
+ },
159
+ options
160
+ );
161
+ } else {
162
+ tryIPLocation();
163
+ }
164
+ }
165
+
166
+ function tryIPLocation() {
167
+ document.getElementById("status").innerHTML = `
168
+ <div style="background: linear-gradient(45deg, #3498db, #2980b9); color: white; padding: 15px; border-radius: 10px; margin: 10px 0;">
169
+ <div style="font-weight: bold;">🌐 Trying IP-based location...</div>
170
+ <div style="font-size: 0.9em; opacity: 0.9;">Falling back to approximate location</div>
171
+ </div>
172
+ `;
173
+
174
+ // Multiple IP geolocation services for reliability
175
+ const services = [
176
+ 'https://ipapi.co/json/',
177
+ 'https://api.ipify.org?format=json',
178
+ 'https://httpbin.org/ip'
179
+ ];
180
+
181
+ fetch('https://ipapi.co/json/')
182
+ .then(response => response.json())
183
+ .then(data => {
184
+ if (data.latitude && data.longitude && !data.error) {
185
+ document.getElementById("status").innerHTML = `
186
+ <div style="background: linear-gradient(45deg, #3498db, #2980b9); color: white; padding: 15px; border-radius: 10px; margin: 10px 0; box-shadow: 0 4px 15px rgba(52, 152, 219, 0.3);">
187
+ <div style="font-weight: bold; margin-bottom: 8px;">🌐 IP-based Location Found</div>
188
+ <div style="font-size: 0.95em; margin-bottom: 5px;">📍 <strong>${data.latitude.toFixed(6)}, ${data.longitude.toFixed(6)}</strong></div>
189
+ <div style="font-size: 0.85em; margin-bottom: 5px;">
190
+ 🟠 Accuracy: <span style="color: #f39c12; font-weight: bold;">Approximate</span> (City-level)
191
+ </div>
192
+ <div style="font-size: 0.8em; opacity: 0.9;">🏙️ ${data.city || 'Unknown'}, ${data.country_name || data.country || 'Unknown'}</div>
193
+ <div style="font-size: 0.8em; opacity: 0.9;">🌐 ISP: ${data.org || 'Unknown'}</div>
194
+ </div>
195
+ `;
196
+
197
+ window.parent.postMessage({
198
+ type: 'streamlit:setComponentValue',
199
+ value: {
200
+ lat: data.latitude,
201
+ lon: data.longitude,
202
+ accuracy: 10000,
203
+ method: "IP",
204
+ timestamp: new Date().toISOString(),
205
+ city: data.city,
206
+ country: data.country_name || data.country,
207
+ region: data.region,
208
+ timezone: data.timezone,
209
+ isp: data.org,
210
+ success: true
211
+ }
212
+ }, '*');
213
+ } else {
214
+ showLocationUnavailable();
215
  }
216
+ })
217
+ .catch(() => {
218
+ showLocationUnavailable();
219
+ });
220
+ }
221
+
222
+ function showLocationUnavailable() {
223
+ document.getElementById("status").innerHTML = `
224
+ <div style="background: linear-gradient(45deg, #e74c3c, #c0392b); color: white; padding: 15px; border-radius: 10px; margin: 10px 0; box-shadow: 0 4px 15px rgba(231, 76, 60, 0.3);">
225
+ <div style="font-weight: bold; margin-bottom: 8px;">⚠️ Location Not Available</div>
226
+ <div style="font-size: 0.9em; margin-bottom: 5px;">Unable to detect your location automatically</div>
227
+ <div style="font-size: 0.85em; opacity: 0.9;">• You can continue uploading without location data</div>
228
+ <div style="font-size: 0.85em; opacity: 0.9;">• Or enter coordinates manually using the button</div>
229
+ </div>
230
+ `;
231
+
232
  window.parent.postMessage({
233
  type: 'streamlit:setComponentValue',
234
  value: {lat: null, lon: null, success: false}
235
  }, '*');
236
  }
237
+
238
+ // Start location capture immediately
239
+ tryGetLocation();
240
+ </script>
241
+ <div id="status" style="padding: 15px; background: linear-gradient(45deg, #74b9ff, #0984e3); color: white; border-radius: 10px; text-align: center; font-weight: 500; box-shadow: 0 4px 15px rgba(116, 185, 255, 0.3);">
242
+ 🔄 Initializing advanced location services...
243
+ </div>
244
+ """,
245
+ height=140
246
+ )
247
+
248
+ with col2:
249
+ if st.button("📝 Enter Manually", help="Enter coordinates manually if automatic detection fails"):
250
+ st.session_state.manual_location_mode = True
251
+ st.rerun()
252
 
253
+ # Process the location data
254
  if location_data and isinstance(location_data, dict):
255
+ if location_data.get('success') and location_data.get('lat') and location_data.get('lon'):
256
  st.session_state.user_location = {
257
  "lat": float(location_data['lat']),
258
+ "lon": float(location_data['lon']),
259
+ "accuracy": location_data.get('accuracy'),
260
+ "method": location_data.get('method', 'Unknown'),
261
+ "timestamp": location_data.get('timestamp'),
262
+ "city": location_data.get('city'),
263
+ "country": location_data.get('country')
264
  }
265
  st.session_state.location_fetched = True
266
+ st.rerun()
267
  elif location_data.get('success') == False:
268
  st.session_state.location_fetched = True
 
269
 
270
+ elif st.session_state.manual_location_mode:
271
+ st.markdown("### 📝 Manual Location Entry")
272
+
273
+ col1, col2, col3 = st.columns([1, 1, 1])
274
+
275
+ with col1:
276
+ manual_lat = st.number_input(
277
+ "Latitude",
278
+ value=0.0,
279
+ format="%.6f",
280
+ help="Enter latitude (-90 to 90)"
281
+ )
282
+
283
+ with col2:
284
+ manual_lon = st.number_input(
285
+ "Longitude",
286
+ value=0.0,
287
+ format="%.6f",
288
+ help="Enter longitude (-180 to 180)"
289
+ )
290
+
291
+ with col3:
292
+ st.write("") # Spacing
293
+ if st.button("✅ Use These Coordinates"):
294
+ if -90 <= manual_lat <= 90 and -180 <= manual_lon <= 180:
295
+ st.session_state.user_location = {
296
+ "lat": manual_lat,
297
+ "lon": manual_lon,
298
+ "accuracy": None,
299
+ "method": "Manual",
300
+ "timestamp": datetime.now().isoformat()
301
+ }
302
+ st.session_state.location_fetched = True
303
+ st.session_state.manual_location_mode = False
304
+ st.success("✅ Manual coordinates saved!")
305
+ st.rerun()
306
+ else:
307
+ st.error("❌ Invalid coordinates. Latitude: -90 to 90, Longitude: -180 to 180")
308
+
309
+ if st.button("🔙 Back to Auto-Detection"):
310
+ st.session_state.manual_location_mode = False
311
+ st.rerun()
312
+
313
+ return st.session_state.user_location
314
 
315
+ def display_analytics_sidebar():
316
+ """Display real-time analytics in the sidebar"""
317
+ with st.sidebar:
318
+ st.markdown("## 📊 Live Analytics")
319
+
320
+ # Calculate statistics
321
+ stats = calculate_statistics()
322
+
323
+ # Display key metrics with enhanced styling
324
+ st.markdown(f"""
325
+ <div style="background: linear-gradient(45deg, #667eea, #764ba2); color: white; padding: 15px; border-radius: 10px; margin: 10px 0; text-align: center;">
326
+ <h3 style="margin: 0; font-size: 2em;">👥 {stats['total_users']}</h3>
327
+ <p style="margin: 5px 0 0 0;">Total Contributors</p>
328
+ </div>
329
+ """, unsafe_allow_html=True)
330
+
331
+ st.markdown(f"""
332
+ <div style="background: linear-gradient(45deg, #f093fb, #f5576c); color: white; padding: 15px; border-radius: 10px; margin: 10px 0; text-align: center;">
333
+ <h3 style="margin: 0; font-size: 2em;">🖼️ {stats['total_images']}</h3>
334
+ <p style="margin: 5px 0 0 0;">Total Images</p>
335
+ </div>
336
+ """, unsafe_allow_html=True)
337
+
338
+ st.markdown(f"""
339
+ <div style="background: linear-gradient(45deg, #4facfe, #00f2fe); color: white; padding: 15px; border-radius: 10px; margin: 10px 0; text-align: center;">
340
+ <h3 style="margin: 0; font-size: 2em;">🏷️ {stats['total_labels']}</h3>
341
+ <p style="margin: 5px 0 0 0;">Total Labels</p>
342
+ </div>
343
+ """, unsafe_allow_html=True)
344
+
345
+ st.markdown(f"""
346
+ <div style="background: linear-gradient(45deg, #43e97b, #38f9d7); color: white; padding: 15px; border-radius: 10px; margin: 10px 0; text-align: center;">
347
+ <h3 style="margin: 0; font-size: 2em;">🌍 {stats['languages_used']}</h3>
348
+ <p style="margin: 5px 0 0 0;">Languages Used</p>
349
+ </div>
350
+ """, unsafe_allow_html=True)
351
+
352
+ # Location statistics
353
+ st.markdown(f"""
354
+ <div style="background: linear-gradient(45deg, #fd79a8, #e84393); color: white; padding: 15px; border-radius: 10px; margin: 10px 0; text-align: center;">
355
+ <h3 style="margin: 0; font-size: 2em;">🌍 {stats['images_with_location']}</h3>
356
+ <p style="margin: 5px 0 0 0;">Images with GPS Data</p>
357
+ </div>
358
+ """, unsafe_allow_html=True)
359
+
360
+ # GPS accuracy breakdown
361
+ if stats['gps_accuracy_breakdown']:
362
+ st.markdown("### 🎯 GPS Accuracy Levels")
363
+ for accuracy_level, count in sorted(stats['gps_accuracy_breakdown'].items(), key=lambda x: x[1], reverse=True):
364
+ percentage = (count / stats['images_with_location'] * 100) if stats['images_with_location'] > 0 else 0
365
+ accuracy_emoji = {"High": "🟢", "Medium": "🟠", "Low": "🔴", "Unknown": "⚪"}.get(accuracy_level, "⚪")
366
+ st.markdown(f"{accuracy_emoji} **{accuracy_level}**: {count} ({percentage:.1f}%)")
367
+
368
+ # Location methods breakdown
369
+ if stats['location_methods']:
370
+ st.markdown("### 📍 Location Capture Methods")
371
+ for method, count in sorted(stats['location_methods'].items(), key=lambda x: x[1], reverse=True):
372
+ percentage = (count / stats['images_with_location'] * 100) if stats['images_with_location'] > 0 else 0
373
+ method_emoji = {"GPS": "🛰️", "IP": "🌐", "Manual": "📝"}.get(method, "❓")
374
+ st.markdown(f"{method_emoji} **{method}**: {count} ({percentage:.1f}%)")
375
+
376
+ # Language usage breakdown
377
+ if stats['language_breakdown']:
378
+ st.markdown("### 🗣️ Language Usage")
379
+ for lang_code, count in sorted(stats['language_breakdown'].items(), key=lambda x: x[1], reverse=True):
380
+ lang_name = SUPPORTED_LANGUAGES.get(lang_code, lang_code)
381
+ percentage = (count / stats['total_labels'] * 100) if stats['total_labels'] > 0 else 0
382
+ st.markdown(f"**{lang_name}**: {count} labels ({percentage:.1f}%)")
383
+
384
+ # Category breakdown
385
+ if stats['category_breakdown']:
386
+ st.markdown("### 📂 Category Breakdown")
387
+ for category, count in sorted(stats['category_breakdown'].items(), key=lambda x: x[1], reverse=True):
388
+ percentage = (count / stats['total_images'] * 100) if stats['total_images'] > 0 else 0
389
+ st.markdown(f"**{get_translation(st.session_state.language, category.lower())}**: {count} ({percentage:.1f}%)")
390
+
391
+ # Country/Region statistics
392
+ if stats['country_breakdown']:
393
+ st.markdown("### 🌏 Top Countries")
394
+ for country, count in sorted(stats['country_breakdown'].items(), key=lambda x: x[1], reverse=True)[:5]:
395
+ percentage = (count / stats['images_with_location'] * 100) if stats['images_with_location'] > 0 else 0
396
+ st.markdown(f"🏴 **{country}**: {count} ({percentage:.1f}%)")
397
 
398
+ def register_user():
399
+ """Enhanced user registration form"""
400
+ st.subheader(get_translation(st.session_state.language, "register"))
 
 
 
 
 
 
 
 
 
 
 
 
401
 
402
+ with st.form("register_form"):
403
+ col1, col2 = st.columns(2)
404
+
405
+ with col1:
406
+ username = st.text_input(get_translation(st.session_state.language, "username"))
407
+ password = st.text_input(get_translation(st.session_state.language, "password"), type="password")
408
+ confirm_password = st.text_input(get_translation(st.session_state.language, "confirm_password"), type="password")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
409
 
410
+ with col2:
411
+ full_name = st.text_input(get_translation(st.session_state.language, "full_name"))
412
+ email = st.text_input(get_translation(st.session_state.language, "email"))
413
+ phone = st.text_input(get_translation(st.session_state.language, "phone"))
414
+ age = st.number_input(get_translation(st.session_state.language, "age"), min_value=13, max_value=120, value=25)
415
+
416
+ preferred_language = st.selectbox(
417
+ get_translation(st.session_state.language, "preferred_language"),
418
+ options=list(SUPPORTED_LANGUAGES.keys()),
419
+ format_func=lambda x: SUPPORTED_LANGUAGES[x]
420
+ )
421
+
422
+ submitted = st.form_submit_button(get_translation(st.session_state.language, "register"))
423
+
424
+ if submitted:
425
+ if not username or not password or not full_name or not email:
426
+ st.error(get_translation(st.session_state.language, "fill_all_fields"))
427
+ return
428
 
429
+ if password != confirm_password:
430
+ st.error(get_translation(st.session_state.language, "passwords_dont_match"))
431
+ return
 
432
 
433
+ users = load_users()
434
 
435
+ if username in users:
436
+ st.error(get_translation(st.session_state.language, "username_exists"))
437
+ return
 
 
 
 
438
 
439
+ users[username] = {
440
+ 'password': password,
441
+ 'preferred_language': preferred_language,
442
+ 'created_at': datetime.now().isoformat(),
443
+ 'user_details': {
444
+ 'full_name': full_name,
445
+ 'email': email,
446
+ 'phone': phone,
447
+ 'age': age
448
+ }
449
+ }
450
 
451
+ save_users(users)
452
+ st.success(get_translation(st.session_state.language, "registration_successful"))
453
+ st.rerun()
454
+
455
+ def login_user():
456
+ """User login form"""
457
+ st.subheader(get_translation(st.session_state.language, "login"))
458
+
459
+ with st.form("login_form"):
460
+ username = st.text_input(get_translation(st.session_state.language, "username"))
461
+ password = st.text_input(get_translation(st.session_state.language, "password"), type="password")
 
 
 
 
462
 
463
+ submitted = st.form_submit_button(get_translation(st.session_state.language, "login"))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
464
 
465
+ if submitted:
466
+ if not username or not password:
467
+ st.error(get_translation(st.session_state.language, "fill_all_fields"))
468
+ return
469
+
470
+ users = load_users()
471
+
472
+ if username not in users:
473
+ st.error(get_translation(st.session_state.language, "invalid_credentials"))
474
+ return
475
+
476
+ if users[username]['password'] != password:
477
+ st.error(get_translation(st.session_state.language, "invalid_credentials"))
478
+ return
479
+
480
+ st.session_state.user = username
481
+ st.session_state.language = users[username]['preferred_language']
482
+ # Reset location fetch status for new login
483
+ st.session_state.location_fetched = False
484
+ st.session_state.manual_location_mode = False
485
+ st.success(get_translation(st.session_state.language, "login_successful"))
486
+ st.rerun()
 
 
 
 
 
 
 
487
 
488
+ def upload_image():
489
+ """Enhanced image upload form with location display"""
490
+ st.subheader(f"📤 {get_translation(st.session_state.language, 'upload_image')}")
491
 
492
  # Initialize upload counter for form reset
493
  if 'upload_counter' not in st.session_state:
494
  st.session_state.upload_counter = 0
495
 
496
+ # Show enhanced location status
497
+ location = get_enhanced_location()
498
+
499
+ if location and location.get('lat'):
500
+ method_icon = "🛰️" if location.get('method') == "GPS" else "🌐" if location.get('method') == "IP" else "📝"
501
+ accuracy_text = ""
502
+ if location.get('accuracy'):
503
+ if location['accuracy'] <= 10:
504
+ accuracy_text = f" (±{location['accuracy']:.0f}m - High Accuracy)"
505
+ elif location['accuracy'] <= 50:
506
+ accuracy_text = f" (±{location['accuracy']:.0f}m - Medium Accuracy)"
507
+ else:
508
+ accuracy_text = f" (±{location['accuracy']:.0f}m - Low Accuracy)"
509
+
510
  st.markdown(f"""
511
+ <div style="background: linear-gradient(45deg, #00b894, #00cec9); color: white; padding: 15px; border-radius: 10px; margin: 15px 0; text-align: center; font-weight: 500; box-shadow: 0 4px 15px rgba(0, 184, 148, 0.3);">
512
+ {method_icon} Location Ready: {location['lat']:.6f}, {location['lon']:.6f}{accuracy_text}
513
+ <br><small>Method: {location.get('method', 'Unknown')} | Captured: {datetime.fromisoformat(location.get('timestamp', datetime.now().isoformat())).strftime('%H:%M:%S') if location.get('timestamp') else 'Unknown'}</small>
514
  </div>
515
  """, unsafe_allow_html=True)
516
+
517
+ if st.button("🔄 Refresh Location"):
518
+ st.session_state.location_fetched = False
519
+ st.session_state.manual_location_mode = False
520
+ st.rerun()
521
 
522
+ # Use unique form key to ensure proper reset
523
+ with st.form(f"upload_form_{st.session_state.upload_counter}", clear_on_submit=True):
524
  uploaded_file = st.file_uploader(
525
+ get_translation(st.session_state.language, "choose_image"),
526
+ type=['png', 'jpg', 'jpeg', 'gif'],
527
+ help=get_translation(st.session_state.language, "image_help")
 
 
 
 
 
 
 
 
 
 
528
  )
529
 
530
+ col1, col2 = st.columns(2)
 
531
 
532
+ with col1:
533
+ title = st.text_input(get_translation(st.session_state.language, "image_title"))
534
+ description = st.text_area(get_translation(st.session_state.language, "image_description"))
535
+
536
+ with col2:
537
+ label = st.text_input(get_translation(st.session_state.language, "native_language_label"))
538
+ category = st.selectbox(
539
+ get_translation(st.session_state.language, "category"),
540
+ options=get_categories(),
541
+ format_func=lambda x: get_translation(st.session_state.language, x.lower())
542
+ )
543
 
544
+ submitted = st.form_submit_button(get_translation(st.session_state.language, "upload"))
545
 
546
+ if submitted:
547
+ if not uploaded_file or not title or not description or not label:
548
+ st.error(get_translation(st.session_state.language, "fill_all_fields"))
549
+ return
550
+
551
+ # Validate image
552
+ validation_result = validate_image(uploaded_file)
553
+ if not validation_result['valid']:
554
+ st.error(get_translation(st.session_state.language, validation_result['error']))
555
+ return
556
+
557
+ # Check if location is available (but allow upload without it)
558
+ current_location = st.session_state.get('user_location', {})
559
+ location_status = "No location data"
560
+ if current_location and current_location.get('lat'):
561
+ location_status = f"📍 {current_location.get('method', 'Unknown')} location captured"
562
+ else:
563
+ st.warning("⚠️ **Location not captured** - Image will be uploaded without GPS coordinates. For better dataset quality, try refreshing location.")
564
+
565
+ # Generate unique filename
566
+ file_extension = uploaded_file.name.split('.')[-1]
567
+ unique_filename = f"{uuid.uuid4()}.{file_extension}"
568
+
569
+ # Save image
570
+ image_path = os.path.join('data/images', unique_filename)
571
+ image = Image.open(uploaded_file)
572
+ image.save(image_path)
573
+
574
+ # Prepare comprehensive location metadata
575
+ location_metadata = None
576
+ if current_location and current_location.get('lat'):
577
+ location_metadata = {
578
+ 'latitude': current_location['lat'],
579
+ 'longitude': current_location['lon'],
580
+ 'accuracy': current_location.get('accuracy'),
581
+ 'altitude': current_location.get('altitude'),
582
+ 'heading': current_location.get('heading'),
583
+ 'speed': current_location.get('speed'),
584
+ 'method': current_location.get('method', 'Unknown'),
585
+ 'timestamp': current_location.get('timestamp', datetime.now().isoformat()),
586
+ 'city': current_location.get('city'),
587
+ 'country': current_location.get('country'),
588
+ 'region': current_location.get('region'),
589
+ 'timezone': current_location.get('timezone'),
590
+ 'isp': current_location.get('isp')
591
+ }
592
+
593
+ # Save comprehensive label data
594
+ labels = load_labels()
595
+ entry_id = str(uuid.uuid4())
596
+
597
+ labels[entry_id] = {
598
+ 'image_path': image_path,
599
+ 'filename': unique_filename,
600
+ 'title': title,
601
+ 'description': description,
602
+ 'category': category,
603
+ 'uploaded_by': st.session_state.user,
604
+ 'uploaded_at': datetime.now().isoformat(),
605
+ 'location': location_metadata, # Comprehensive location data
606
+ 'location_status': location_status,
607
+ 'labels': [
608
+ {
609
+ 'text': label,
610
+ 'language': st.session_state.language,
611
+ 'added_by': st.session_state.user,
612
+ 'added_at': datetime.now().isoformat()
613
  }
614
+ ]
615
+ }
616
+
617
+ save_labels(labels)
618
+
619
+ # Show success message with location info
620
+ if location_metadata:
621
+ success_msg = f"✅ {get_translation(st.session_state.language, 'upload_successful')} with {location_metadata['method']} coordinates!"
 
 
 
 
 
 
 
 
 
 
622
  else:
623
+ success_msg = f"✅ {get_translation(st.session_state.language, 'upload_successful')} (without location data)"
624
+
625
+ st.success(success_msg)
626
+ st.session_state.upload_counter += 1
627
+
628
+ # Reset location for next upload to get fresh coordinates
629
+ st.session_state.location_fetched = False
630
+ st.session_state.manual_location_mode = False
631
+ st.rerun()
632
 
633
+ def display_image_feed():
634
+ """Enhanced image feed with improved styling and functionality"""
635
+ st.subheader(f"📱 {get_translation(st.session_state.language, 'image_feed')}")
636
 
637
  # Category filter
638
  categories = ['All'] + get_categories()
639
+ selected_category = st.selectbox(
640
+ get_translation(st.session_state.language, "filter_by_category"),
641
  options=categories,
642
+ format_func=lambda x: get_translation(st.session_state.language, x.lower()) if x != 'All' else get_translation(st.session_state.language, 'all')
643
  )
644
 
645
+ labels = load_labels()
 
646
 
647
+ if not labels:
648
+ st.info(get_translation(st.session_state.language, "no_images_yet"))
649
  return
650
 
651
  # Filter by category
652
+ filtered_labels = labels
653
+ if selected_category != 'All':
654
+ filtered_labels = {k: v for k, v in labels.items() if v.get('category') == selected_category}
 
 
 
 
655
 
656
+ # Display images in a grid
657
+ for entry_id, entry_data in sorted(filtered_labels.items(), key=lambda x: x[1]['uploaded_at'], reverse=True):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
658
  with st.container():
 
 
659
  col1, col2 = st.columns([1, 2])
660
 
661
  with col1:
662
  # Display image
663
+ image_path = entry_data['image_path']
664
+ if os.path.exists(image_path):
665
+ image = Image.open(image_path)
666
+ st.image(image, width=300, caption=entry_data['title'])
667
+ else:
668
+ st.error(get_translation(st.session_state.language, "image_not_found"))
 
669
 
670
  with col2:
671
+ # Display metadata
672
+ st.markdown(f"### {entry_data['title']}")
673
+ st.markdown(f"**{get_translation(st.session_state.language, 'image_description')}:** {entry_data['description']}")
674
+ st.markdown(f"**{get_translation(st.session_state.language, 'category')}:** {get_translation(st.session_state.language, entry_data['category'].lower())}")
675
+ st.markdown(f"**📤 Uploaded by:** {entry_data['uploaded_by']}")
676
+ st.markdown(f"**📅 Date:** {datetime.fromisoformat(entry_data['uploaded_at']).strftime('%Y-%m-%d %H:%M')}")
 
 
 
 
 
 
677
 
678
+ # Enhanced location display
679
+ if entry_data.get('location') and entry_data['location'].get('lat'):
680
+ loc = entry_data['location']
681
+ method_icon = "🛰️" if loc.get('method') == "GPS" else "🌐" if loc.get('method') == "IP" else "📝"
682
+ accuracy_info = ""
683
+ if loc.get('accuracy'):
684
+ accuracy_info = f" (±{loc['accuracy']:.0f}m accuracy)"
685
+
686
+ location_text = f"{method_icon} {loc['lat']:.6f}, {loc['lon']:.6f}{accuracy_info}"
687
+ if loc.get('city') and loc.get('country'):
688
+ location_text += f" - {loc['city']}, {loc['country']}"
689
+
690
+ st.markdown(f"**📍 Location:** {location_text}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
691
 
692
+ # Display labels
693
+ st.markdown(f"**{get_translation(st.session_state.language, 'labels')}:**")
694
+ for label_data in entry_data['labels']:
695
+ lang_name = SUPPORTED_LANGUAGES.get(label_data['language'], label_data['language'])
696
+ st.markdown(f"- **{label_data['text']}** ({lang_name}) - by {label_data['added_by']}")
697
 
698
  # Add new label form
699
+ with st.expander(get_translation(st.session_state.language, "add_label")):
700
+ with st.form(f"label_form_{entry_id}"):
701
+ new_label = st.text_input(get_translation(st.session_state.language, "new_label"))
702
+ label_language = st.selectbox(
703
+ get_translation(st.session_state.language, "label_language"),
704
  options=list(SUPPORTED_LANGUAGES.keys()),
705
+ format_func=lambda x: SUPPORTED_LANGUAGES[x]
 
 
 
 
 
 
 
706
  )
707
 
708
+ if st.form_submit_button(get_translation(st.session_state.language, "add_label")):
709
+ if not new_label:
710
+ st.error(get_translation(st.session_state.language, "enter_label"))
711
+ else:
712
+ # Check if label already exists
713
+ existing_labels = [l['text'].lower() for l in entry_data['labels']]
714
+ if new_label.lower() in existing_labels:
715
+ st.error(get_translation(st.session_state.language, "label_exists"))
716
+ else:
717
+ # Add new label
718
+ labels[entry_id]['labels'].append({
719
+ 'text': new_label,
720
+ 'language': label_language,
721
+ 'added_by': st.session_state.user,
722
+ 'added_at': datetime.now().isoformat()
723
+ })
724
+ save_labels(labels)
725
+ st.success(get_translation(st.session_state.language, "label_added"))
726
+ st.rerun()
 
727
 
 
728
  st.markdown("---")
729
 
730
  def main():
731
  """Main application function"""
 
 
 
 
 
 
732
 
733
+ # Header with language selector
734
+ col1, col2, col3 = st.columns([2, 1, 1])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
735
 
736
+ with col1:
737
+ st.title("🏷️ LabelIt! 🇮🇳")
738
+ st.markdown(get_translation(st.session_state.language, "app_subtitle"))
739
 
740
+ with col2:
741
+ # Language selector
742
+ selected_language = st.selectbox(
743
+ "🌍 Language",
744
+ options=list(SUPPORTED_LANGUAGES.keys()),
745
+ format_func=lambda x: SUPPORTED_LANGUAGES[x],
746
+ index=list(SUPPORTED_LANGUAGES.keys()).index(st.session_state.language)
747
+ )
748
+
749
+ if selected_language != st.session_state.language:
750
+ st.session_state.language = selected_language
751
+ st.rerun()
752
 
753
+ with col3:
754
+ if st.session_state.user:
755
+ if st.button(get_translation(st.session_state.language, "logout")):
756
+ st.session_state.user = None
757
+ st.session_state.location_fetched = False
758
+ st.session_state.manual_location_mode = False
759
+ st.rerun()
760
+
761
+ # Display analytics sidebar
762
+ display_analytics_sidebar()
763
+
764
+ # Main content
765
+ if not st.session_state.user:
766
+ # Authentication tabs
767
  tab1, tab2 = st.tabs([
768
+ get_translation(st.session_state.language, "login"),
769
+ get_translation(st.session_state.language, "register")
770
  ])
771
 
772
  with tab1:
773
+ login_user()
774
 
775
  with tab2:
776
+ register_user()
777
+
778
  else:
779
+ # User is logged in - show user details in sidebar
780
+ with st.sidebar:
781
+ st.markdown("---")
782
+ st.markdown(f"### 👤 {get_translation(st.session_state.language, 'welcome')}, {st.session_state.user}!")
783
+
784
+ users = load_users()
785
+ user_details = users[st.session_state.user]['user_details']
786
+
787
+ st.markdown(f"**{get_translation(st.session_state.language, 'full_name')}:** {user_details['full_name']}")
788
+ st.markdown(f"**{get_translation(st.session_state.language, 'email')}:** {user_details['email']}")
789
+ st.markdown(f"**{get_translation(st.session_state.language, 'phone')}:** {user_details['phone']}")
790
+ st.markdown(f"**{get_translation(st.session_state.language, 'age')}:** {user_details['age']}")
791
+
792
+ # Main application tabs
793
+ tab1, tab2 = st.tabs([
794
+ get_translation(st.session_state.language, "upload_image"),
795
+ get_translation(st.session_state.language, "feed")
796
+ ])
797
+
798
+ with tab1:
799
+ upload_image()
800
+
801
+ with tab2:
802
+ display_image_feed()
803
 
804
  if __name__ == "__main__":
805
+ main()