RaghavenderReddy commited on
Commit
68143df
ยท
verified ยท
1 Parent(s): b22bc60

Upload 2 files

Browse files
Files changed (2) hide show
  1. README.md +79 -20
  2. app.py +668 -0
README.md CHANGED
@@ -1,20 +1,79 @@
1
- ---
2
- title: LABEL IT
3
- emoji: ๐Ÿš€
4
- colorFrom: red
5
- colorTo: red
6
- sdk: docker
7
- app_port: 8501
8
- tags:
9
- - streamlit
10
- pinned: false
11
- short_description: Streamlit template space
12
- license: mit
13
- ---
14
-
15
- # Welcome to Streamlit!
16
-
17
- Edit `/src/streamlit_app.py` to customize this app to your heart's desire. :heart:
18
-
19
- If you have any questions, checkout our [documentation](https://docs.streamlit.io) and [community
20
- forums](https://discuss.streamlit.io).
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # LabelIt! - Multilingual Image Labeling Platform
2
+
3
+ A collaborative web application for multilingual image labeling built with Streamlit. Users can upload images and add labels in multiple languages, creating a rich multilingual dataset.
4
+
5
+ ## Features
6
+
7
+ - **Multilingual Support**: English, Hindi (เคนเคฟเคจเฅเคฆเฅ€), and Telugu (เฐคเฑ†เฐฒเฑเฐ—เฑ)
8
+ - **User Authentication**: Registration and login system with detailed profiles
9
+ - **Image Upload**: Support for PNG, JPG, JPEG, GIF formats (max 10MB)
10
+ - **Automatic Location Capture**: GPS coordinates captured on login
11
+ - **Category System**: Organize images by Animals, Food, Objects, Nature, People, Transportation
12
+ - **Collaborative Labeling**: Users can add alternative labels in different languages
13
+ - **Instagram-style Feed**: Browse and label existing images
14
+ - **Responsive Design**: Mobile-friendly interface
15
+
16
+ ## Installation
17
+
18
+ 1. Clone or download this repository
19
+ 2. Install Python 3.8 or higher
20
+ 3. Install dependencies:
21
+ ```bash
22
+ pip install streamlit pillow
23
+ ```
24
+
25
+ ## Running the Application
26
+
27
+ 1. Navigate to the project directory
28
+ 2. Run the Streamlit app:
29
+ ```bash
30
+ streamlit run app.py --server.port 5000
31
+ ```
32
+ 3. Open your browser and go to `http://localhost:5000`
33
+
34
+ ## Usage
35
+
36
+ 1. **Register**: Create an account with your details and language preference
37
+ 2. **Login**: Access the application with your credentials
38
+ 3. **Location**: Allow location access for automatic GPS coordinate capture
39
+ 4. **Upload Images**: Add images with titles, descriptions, categories, and initial labels
40
+ 5. **Browse Feed**: View uploaded images and add alternative labels in different languages
41
+ 6. **Collaborate**: Contribute translations and additional labels to existing images
42
+
43
+ ## Data Storage
44
+
45
+ - User data stored in `data/users.json`
46
+ - Image metadata and labels in `data/labels.json`
47
+ - Uploaded images saved in `data/images/` directory
48
+
49
+ ## File Structure
50
+
51
+ ```
52
+ label-it/
53
+ โ”œโ”€โ”€ app.py # Main Streamlit application
54
+ โ”œโ”€โ”€ translations.py # Multilingual translation system
55
+ โ”œโ”€โ”€ utils.py # Utility functions
56
+ โ”œโ”€โ”€ pyproject.toml # Project dependencies
57
+ โ”œโ”€โ”€ README.md # This file
58
+ โ”œโ”€โ”€ .streamlit/
59
+ โ”‚ โ””โ”€โ”€ config.toml # Streamlit configuration
60
+ โ””โ”€โ”€ data/ # Data storage directory
61
+ โ”œโ”€โ”€ users.json # User accounts
62
+ โ”œโ”€โ”€ labels.json # Image metadata and labels
63
+ โ””โ”€โ”€ images/ # Uploaded image files
64
+ ```
65
+
66
+ ## Contributing
67
+
68
+ 1. Fork the repository
69
+ 2. Create a feature branch
70
+ 3. Make your changes
71
+ 4. Submit a pull request
72
+
73
+ ## License
74
+
75
+ MIT License - feel free to use and modify for your projects.
76
+
77
+ ## Support
78
+
79
+ For issues or questions, please create an issue in the repository or contact the development team.
app.py ADDED
@@ -0,0 +1,668 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 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:
15
+ st.session_state.user = None
16
+ if 'language' not in st.session_state:
17
+ st.session_state.language = 'en'
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)
31
+
32
+ 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()