import streamlit as st import json import os import uuid from datetime import datetime from PIL import Image import base64 from io import BytesIO import streamlit.components.v1 as components from translations import get_translation, SUPPORTED_LANGUAGES from utils import load_users, save_users, load_labels, save_labels, validate_image, get_categories, calculate_statistics # Page configuration st.set_page_config( page_title="LabelIt! 🇮🇳", page_icon="🏷️", layout="wide", initial_sidebar_state="expanded" ) # Initialize session state if 'user' not in st.session_state: st.session_state.user = None if 'language' not in st.session_state: st.session_state.language = 'en' if 'location_fetched' not in st.session_state: st.session_state.location_fetched = False if 'user_location' not in st.session_state: st.session_state.user_location = {"lat": None, "lon": None, "accuracy": None, "method": None, "timestamp": None} if 'manual_location_mode' not in st.session_state: st.session_state.manual_location_mode = False # Check for existing data structure (for Hugging Face Spaces compatibility) DATA_DIR = 'data' IMAGES_DIR = os.path.join(DATA_DIR, 'images') USERS_FILE = os.path.join(DATA_DIR, 'users.json') LABELS_FILE = os.path.join(DATA_DIR, 'labels.json') # Only create directories if they don't exist and we have permissions if not os.path.exists(DATA_DIR): try: os.makedirs(DATA_DIR, exist_ok=True) except (OSError, PermissionError): st.error("Data directory not found. Please ensure 'data' folder exists in the workspace.") if not os.path.exists(IMAGES_DIR): try: os.makedirs(IMAGES_DIR, exist_ok=True) except (OSError, PermissionError): st.error("Images directory not found. Please ensure 'data/images' folder exists in the workspace.") # Initialize data files only if they don't exist if os.path.exists(DATA_DIR) and not os.path.exists(USERS_FILE): try: with open(USERS_FILE, 'w') as f: json.dump({}, f) except (OSError, PermissionError): pass if os.path.exists(DATA_DIR) and not os.path.exists(LABELS_FILE): try: with open(LABELS_FILE, 'w') as f: json.dump({}, f) except (OSError, PermissionError): pass def get_enhanced_location(): """Enhanced location capture with multiple fallback methods and accuracy indicators""" if not st.session_state.location_fetched and not st.session_state.manual_location_mode: st.markdown("### 📍 Location Capture for Image Upload") # Show importance message st.info("📍 **Location Required**: Each image upload needs GPS coordinates for better dataset quality and regional insights.") col1, col2 = st.columns([2, 1]) with col1: # Enhanced JavaScript location component with multiple fallback methods location_data = components.html( """
🔄 Initializing advanced location services...
""", height=140 ) with col2: if st.button("📝 Enter Manually", help="Enter coordinates manually if automatic detection fails"): st.session_state.manual_location_mode = True st.rerun() # Process the location data if location_data and isinstance(location_data, dict): if location_data.get('success') and location_data.get('lat') and location_data.get('lon'): st.session_state.user_location = { "lat": float(location_data['lat']), "lon": float(location_data['lon']), "accuracy": location_data.get('accuracy'), "method": location_data.get('method', 'Unknown'), "timestamp": location_data.get('timestamp'), "city": location_data.get('city'), "country": location_data.get('country') } st.session_state.location_fetched = True st.rerun() elif location_data.get('success') == False: st.session_state.location_fetched = True elif st.session_state.manual_location_mode: st.markdown("### 📝 Manual Location Entry") col1, col2, col3 = st.columns([1, 1, 1]) with col1: manual_lat = st.number_input( "Latitude", value=0.0, format="%.6f", help="Enter latitude (-90 to 90)" ) with col2: manual_lon = st.number_input( "Longitude", value=0.0, format="%.6f", help="Enter longitude (-180 to 180)" ) with col3: st.write("") # Spacing if st.button("✅ Use These Coordinates"): if -90 <= manual_lat <= 90 and -180 <= manual_lon <= 180: st.session_state.user_location = { "lat": manual_lat, "lon": manual_lon, "accuracy": None, "method": "Manual", "timestamp": datetime.now().isoformat() } st.session_state.location_fetched = True st.session_state.manual_location_mode = False st.success("✅ Manual coordinates saved!") st.rerun() else: st.error("❌ Invalid coordinates. Latitude: -90 to 90, Longitude: -180 to 180") if st.button("🔙 Back to Auto-Detection"): st.session_state.manual_location_mode = False st.rerun() return st.session_state.user_location def display_analytics_sidebar(): """Display real-time analytics in the sidebar""" with st.sidebar: st.markdown("## 📊 Live Analytics") # Calculate statistics stats = calculate_statistics() # Display key metrics with enhanced styling st.markdown(f"""

👥 {stats['total_users']}

Total Contributors

""", unsafe_allow_html=True) st.markdown(f"""

🖼️ {stats['total_images']}

Total Images

""", unsafe_allow_html=True) st.markdown(f"""

🏷️ {stats['total_labels']}

Total Labels

""", unsafe_allow_html=True) st.markdown(f"""

🌍 {stats['languages_used']}

Languages Used

""", unsafe_allow_html=True) # Location statistics st.markdown(f"""

🌍 {stats['images_with_location']}

Images with GPS Data

""", unsafe_allow_html=True) # GPS accuracy breakdown if stats['gps_accuracy_breakdown']: st.markdown("### 🎯 GPS Accuracy Levels") for accuracy_level, count in sorted(stats['gps_accuracy_breakdown'].items(), key=lambda x: x[1], reverse=True): percentage = (count / stats['images_with_location'] * 100) if stats['images_with_location'] > 0 else 0 accuracy_emoji = {"High": "🟢", "Medium": "🟠", "Low": "🔴", "Unknown": "⚪"}.get(accuracy_level, "⚪") st.markdown(f"{accuracy_emoji} **{accuracy_level}**: {count} ({percentage:.1f}%)") # Location methods breakdown if stats['location_methods']: st.markdown("### 📍 Location Capture Methods") for method, count in sorted(stats['location_methods'].items(), key=lambda x: x[1], reverse=True): percentage = (count / stats['images_with_location'] * 100) if stats['images_with_location'] > 0 else 0 method_emoji = {"GPS": "🛰️", "IP": "🌐", "Manual": "📝"}.get(method, "❓") st.markdown(f"{method_emoji} **{method}**: {count} ({percentage:.1f}%)") # Language usage breakdown if stats['language_breakdown']: st.markdown("### 🗣️ Language Usage") for lang_code, count in sorted(stats['language_breakdown'].items(), key=lambda x: x[1], reverse=True): lang_name = SUPPORTED_LANGUAGES.get(lang_code, lang_code) percentage = (count / stats['total_labels'] * 100) if stats['total_labels'] > 0 else 0 st.markdown(f"**{lang_name}**: {count} labels ({percentage:.1f}%)") # Category breakdown if stats['category_breakdown']: st.markdown("### 📂 Category Breakdown") for category, count in sorted(stats['category_breakdown'].items(), key=lambda x: x[1], reverse=True): percentage = (count / stats['total_images'] * 100) if stats['total_images'] > 0 else 0 st.markdown(f"**{get_translation(st.session_state.language, category.lower())}**: {count} ({percentage:.1f}%)") # Country/Region statistics if stats['country_breakdown']: st.markdown("### 🌏 Top Countries") for country, count in sorted(stats['country_breakdown'].items(), key=lambda x: x[1], reverse=True)[:5]: percentage = (count / stats['images_with_location'] * 100) if stats['images_with_location'] > 0 else 0 st.markdown(f"🏴 **{country}**: {count} ({percentage:.1f}%)") def register_user(): """Enhanced user registration form""" st.subheader(get_translation(st.session_state.language, "register")) with st.form("register_form"): col1, col2 = st.columns(2) with col1: username = st.text_input(get_translation(st.session_state.language, "username")) password = st.text_input(get_translation(st.session_state.language, "password"), type="password") confirm_password = st.text_input(get_translation(st.session_state.language, "confirm_password"), type="password") with col2: full_name = st.text_input(get_translation(st.session_state.language, "full_name")) email = st.text_input(get_translation(st.session_state.language, "email")) phone = st.text_input(get_translation(st.session_state.language, "phone")) age = st.number_input(get_translation(st.session_state.language, "age"), min_value=13, max_value=120, value=25) preferred_language = st.selectbox( get_translation(st.session_state.language, "preferred_language"), options=list(SUPPORTED_LANGUAGES.keys()), format_func=lambda x: SUPPORTED_LANGUAGES[x] ) submitted = st.form_submit_button(get_translation(st.session_state.language, "register")) if submitted: if not username or not password or not full_name or not email: st.error(get_translation(st.session_state.language, "fill_all_fields")) return if password != confirm_password: st.error(get_translation(st.session_state.language, "passwords_dont_match")) return users = load_users() if username in users: st.error(get_translation(st.session_state.language, "username_exists")) return users[username] = { 'password': password, 'preferred_language': preferred_language, 'created_at': datetime.now().isoformat(), 'user_details': { 'full_name': full_name, 'email': email, 'phone': phone, 'age': age } } save_users(users) st.success(get_translation(st.session_state.language, "registration_successful")) st.rerun() def login_user(): """User login form""" st.subheader(get_translation(st.session_state.language, "login")) with st.form("login_form"): username = st.text_input(get_translation(st.session_state.language, "username")) password = st.text_input(get_translation(st.session_state.language, "password"), type="password") submitted = st.form_submit_button(get_translation(st.session_state.language, "login")) if submitted: if not username or not password: st.error(get_translation(st.session_state.language, "fill_all_fields")) return users = load_users() if username not in users: st.error(get_translation(st.session_state.language, "invalid_credentials")) return if users[username]['password'] != password: st.error(get_translation(st.session_state.language, "invalid_credentials")) return st.session_state.user = username st.session_state.language = users[username]['preferred_language'] # Reset location fetch status for new login st.session_state.location_fetched = False st.session_state.manual_location_mode = False st.success(get_translation(st.session_state.language, "login_successful")) st.rerun() def upload_image(): """Enhanced image upload form with location display""" st.subheader(f"📤 {get_translation(st.session_state.language, 'upload_image')}") # Initialize upload counter for form reset if 'upload_counter' not in st.session_state: st.session_state.upload_counter = 0 # Show enhanced location status location = get_enhanced_location() if location and location.get('lat'): method_icon = "🛰️" if location.get('method') == "GPS" else "🌐" if location.get('method') == "IP" else "📝" accuracy_text = "" if location.get('accuracy'): if location['accuracy'] <= 10: accuracy_text = f" (±{location['accuracy']:.0f}m - High Accuracy)" elif location['accuracy'] <= 50: accuracy_text = f" (±{location['accuracy']:.0f}m - Medium Accuracy)" else: accuracy_text = f" (±{location['accuracy']:.0f}m - Low Accuracy)" st.markdown(f"""
{method_icon} Location Ready: {location['lat']:.6f}, {location['lon']:.6f}{accuracy_text}
Method: {location.get('method', 'Unknown')} | Captured: {datetime.fromisoformat(location.get('timestamp', datetime.now().isoformat())).strftime('%H:%M:%S') if location.get('timestamp') else 'Unknown'}
""", unsafe_allow_html=True) if st.button("🔄 Refresh Location"): st.session_state.location_fetched = False st.session_state.manual_location_mode = False st.rerun() # Use unique form key to ensure proper reset with st.form(f"upload_form_{st.session_state.upload_counter}", clear_on_submit=True): uploaded_file = st.file_uploader( get_translation(st.session_state.language, "choose_image"), type=['png', 'jpg', 'jpeg', 'gif'], help=get_translation(st.session_state.language, "image_help") ) col1, col2 = st.columns(2) with col1: title = st.text_input(get_translation(st.session_state.language, "image_title")) description = st.text_area(get_translation(st.session_state.language, "image_description")) with col2: label = st.text_input(get_translation(st.session_state.language, "native_language_label")) category = st.selectbox( get_translation(st.session_state.language, "category"), options=get_categories(), format_func=lambda x: get_translation(st.session_state.language, x.lower()) ) submitted = st.form_submit_button(get_translation(st.session_state.language, "upload")) if submitted: if not uploaded_file or not title or not description or not label: st.error(get_translation(st.session_state.language, "fill_all_fields")) return # Validate image validation_result = validate_image(uploaded_file) if not validation_result['valid']: st.error(get_translation(st.session_state.language, validation_result['error'])) return # Check if location is available (but allow upload without it) current_location = st.session_state.get('user_location', {}) location_status = "No location data" if current_location and current_location.get('lat'): location_status = f"📍 {current_location.get('method', 'Unknown')} location captured" else: st.warning("⚠️ **Location not captured** - Image will be uploaded without GPS coordinates. For better dataset quality, try refreshing location.") # Generate unique filename file_extension = uploaded_file.name.split('.')[-1] unique_filename = f"{uuid.uuid4()}.{file_extension}" # Save image to existing images directory image_path = os.path.join(IMAGES_DIR, unique_filename) if os.path.exists(IMAGES_DIR): try: image = Image.open(uploaded_file) image.save(image_path) except (OSError, PermissionError): st.error("Unable to save image. Please check if 'data/images' folder exists and has write permissions.") return else: st.error("Images directory not found. Please ensure 'data/images' folder exists in the workspace.") return # Prepare comprehensive location metadata location_metadata = None if current_location and current_location.get('lat'): location_metadata = { 'latitude': current_location['lat'], 'longitude': current_location['lon'], 'accuracy': current_location.get('accuracy'), 'altitude': current_location.get('altitude'), 'heading': current_location.get('heading'), 'speed': current_location.get('speed'), 'method': current_location.get('method', 'Unknown'), 'timestamp': current_location.get('timestamp', datetime.now().isoformat()), 'city': current_location.get('city'), 'country': current_location.get('country'), 'region': current_location.get('region'), 'timezone': current_location.get('timezone'), 'isp': current_location.get('isp') } # Save comprehensive label data labels = load_labels() entry_id = str(uuid.uuid4()) labels[entry_id] = { 'image_path': image_path, 'filename': unique_filename, 'title': title, 'description': description, 'category': category, 'uploaded_by': st.session_state.user, 'uploaded_at': datetime.now().isoformat(), 'location': location_metadata, # Comprehensive location data 'location_status': location_status, 'labels': [ { 'text': label, 'language': st.session_state.language, 'added_by': st.session_state.user, 'added_at': datetime.now().isoformat() } ] } # Save labels and verify the save operation try: save_labels(labels) st.success("Data saved successfully!") except Exception as e: st.error(f"Error saving data: {str(e)}") return # Show success message with location info if location_metadata: success_msg = f"✅ {get_translation(st.session_state.language, 'upload_successful')} with {location_metadata['method']} coordinates!" else: success_msg = f"✅ {get_translation(st.session_state.language, 'upload_successful')} (without location data)" st.success(success_msg) # Debug: Show what was saved st.info(f"Saved entry ID: {entry_id}") st.info(f"Image path: {image_path}") st.info(f"Data directory exists: {os.path.exists(DATA_DIR)}") st.info(f"Labels file exists: {os.path.exists(LABELS_FILE)}") # Initialize counter if it doesn't exist if 'upload_counter' not in st.session_state: st.session_state.upload_counter = 0 st.session_state.upload_counter += 1 # Reset location for next upload to get fresh coordinates st.session_state.location_fetched = False st.session_state.manual_location_mode = False st.rerun() def display_image_feed(): """Enhanced image feed with improved styling and functionality""" st.subheader(f"📱 {get_translation(st.session_state.language, 'image_feed')}") # Category filter categories = ['All'] + get_categories() selected_category = st.selectbox( get_translation(st.session_state.language, "filter_by_category"), options=categories, format_func=lambda x: get_translation(st.session_state.language, x.lower()) if x != 'All' else get_translation(st.session_state.language, 'all') ) labels = load_labels() if not labels: st.info(get_translation(st.session_state.language, "no_images_yet")) return # Filter by category filtered_labels = labels if selected_category != 'All': filtered_labels = {k: v for k, v in labels.items() if v.get('category') == selected_category} # Display images in a grid for entry_id, entry_data in sorted(filtered_labels.items(), key=lambda x: x[1]['uploaded_at'], reverse=True): with st.container(): col1, col2 = st.columns([1, 2]) with col1: # Display image image_path = entry_data['image_path'] if os.path.exists(image_path): image = Image.open(image_path) st.image(image, width=300, caption=entry_data['title']) else: st.error(get_translation(st.session_state.language, "image_not_found")) with col2: # Display metadata st.markdown(f"### {entry_data['title']}") st.markdown(f"**{get_translation(st.session_state.language, 'image_description')}:** {entry_data['description']}") st.markdown(f"**{get_translation(st.session_state.language, 'category')}:** {get_translation(st.session_state.language, entry_data['category'].lower())}") st.markdown(f"**📤 Uploaded by:** {entry_data['uploaded_by']}") st.markdown(f"**📅 Date:** {datetime.fromisoformat(entry_data['uploaded_at']).strftime('%Y-%m-%d %H:%M')}") # Enhanced location display - handle both old and new location formats location = entry_data.get('location') if location and (location.get('latitude') or location.get('lat')): # Handle both old format (lat/lon) and new format (latitude/longitude) lat = location.get('latitude') or location.get('lat', 0) lon = location.get('longitude') or location.get('lon', 0) method_icon = "🛰️" if location.get('method') == "GPS" else "🌐" if location.get('method') == "IP" else "📝" accuracy_info = "" if location.get('accuracy'): accuracy_info = f" (±{location['accuracy']:.0f}m accuracy)" location_text = f"{method_icon} {lat:.6f}, {lon:.6f}{accuracy_info}" if location.get('city') and location.get('country'): location_text += f" - {location['city']}, {location['country']}" st.markdown(f"**📍 Location:** {location_text}") # Display labels st.markdown(f"**{get_translation(st.session_state.language, 'labels')}:**") for label_data in entry_data['labels']: lang_name = SUPPORTED_LANGUAGES.get(label_data['language'], label_data['language']) st.markdown(f"- **{label_data['text']}** ({lang_name}) - by {label_data['added_by']}") # Add new label form with st.expander(get_translation(st.session_state.language, "add_label")): with st.form(f"label_form_{entry_id}"): new_label = st.text_input(get_translation(st.session_state.language, "new_label")) label_language = st.selectbox( get_translation(st.session_state.language, "label_language"), options=list(SUPPORTED_LANGUAGES.keys()), format_func=lambda x: SUPPORTED_LANGUAGES[x] ) if st.form_submit_button(get_translation(st.session_state.language, "add_label")): if not new_label: st.error(get_translation(st.session_state.language, "enter_label")) else: # Check if label already exists existing_labels = [l['text'].lower() for l in entry_data['labels']] if new_label.lower() in existing_labels: st.error(get_translation(st.session_state.language, "label_exists")) else: # Add new label labels[entry_id]['labels'].append({ 'text': new_label, 'language': label_language, 'added_by': st.session_state.user, 'added_at': datetime.now().isoformat() }) save_labels(labels) st.success(get_translation(st.session_state.language, "label_added")) st.rerun() st.markdown("---") def main(): """Main application function""" # Header with language selector col1, col2, col3 = st.columns([2, 1, 1]) with col1: st.title("🏷️ LabelIt! 🇮🇳") st.markdown(get_translation(st.session_state.language, "app_subtitle")) with col2: # Language selector selected_language = st.selectbox( "🌍 Language", options=list(SUPPORTED_LANGUAGES.keys()), format_func=lambda x: SUPPORTED_LANGUAGES[x], index=list(SUPPORTED_LANGUAGES.keys()).index(st.session_state.language) ) if selected_language != st.session_state.language: st.session_state.language = selected_language st.rerun() with col3: if st.session_state.user: if st.button(get_translation(st.session_state.language, "logout")): st.session_state.user = None st.session_state.location_fetched = False st.session_state.manual_location_mode = False st.rerun() # Display analytics sidebar display_analytics_sidebar() # Main content if not st.session_state.user: # Authentication tabs tab1, tab2 = st.tabs([ get_translation(st.session_state.language, "login"), get_translation(st.session_state.language, "register") ]) with tab1: login_user() with tab2: register_user() else: # User is logged in - show user details in sidebar with st.sidebar: st.markdown("---") st.markdown(f"### 👤 {get_translation(st.session_state.language, 'welcome')}, {st.session_state.user}!") users = load_users() user_details = users[st.session_state.user]['user_details'] st.markdown(f"**{get_translation(st.session_state.language, 'full_name')}:** {user_details['full_name']}") st.markdown(f"**{get_translation(st.session_state.language, 'email')}:** {user_details['email']}") st.markdown(f"**{get_translation(st.session_state.language, 'phone')}:** {user_details['phone']}") st.markdown(f"**{get_translation(st.session_state.language, 'age')}:** {user_details['age']}") # Main application tabs tab1, tab2 = st.tabs([ get_translation(st.session_state.language, "upload_image"), get_translation(st.session_state.language, "feed") ]) with tab1: upload_image() with tab2: display_image_feed() if __name__ == "__main__": main()