LABEL-IT / app.py
RaghavenderReddy's picture
Update app.py
ab482d3 verified
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(
"""
<script>
let locationAttempts = 0;
const maxAttempts = 3;
function tryGetLocation() {
locationAttempts++;
document.getElementById("status").innerHTML = `๐Ÿ”„ Attempting high-accuracy GPS location (${locationAttempts}/${maxAttempts})...`;
if (navigator.geolocation) {
const options = {
enableHighAccuracy: true,
timeout: 20000,
maximumAge: 30000
};
navigator.geolocation.getCurrentPosition(
function(position) {
const lat = position.coords.latitude;
const lon = position.coords.longitude;
const accuracy = position.coords.accuracy;
const altitude = position.coords.altitude;
const heading = position.coords.heading;
const speed = position.coords.speed;
const timestamp = new Date().toISOString();
let accuracyLevel = "Low";
let accuracyColor = "#e74c3c";
let accuracyEmoji = "๐ŸŸก";
if (accuracy <= 10) {
accuracyLevel = "High";
accuracyColor = "#27ae60";
accuracyEmoji = "๐ŸŸข";
} else if (accuracy <= 50) {
accuracyLevel = "Medium";
accuracyColor = "#f39c12";
accuracyEmoji = "๐ŸŸ ";
} else {
accuracyEmoji = "๐Ÿ”ด";
}
document.getElementById("status").innerHTML = `
<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);">
<div style="font-weight: bold; margin-bottom: 8px;">โœ… GPS Location Captured Successfully!</div>
<div style="font-size: 0.95em; margin-bottom: 5px;">๐Ÿ“ <strong>${lat.toFixed(6)}, ${lon.toFixed(6)}</strong></div>
<div style="font-size: 0.85em; margin-bottom: 5px;">
${accuracyEmoji} Accuracy: <span style="color: ${accuracyColor}; font-weight: bold;">${accuracyLevel}</span> (ยฑ${accuracy.toFixed(0)}m)
</div>
<div style="font-size: 0.8em; opacity: 0.9;">โฐ ${new Date(timestamp).toLocaleString()}</div>
${altitude ? `<div style="font-size: 0.8em; opacity: 0.9;">โ›ฐ๏ธ Altitude: ${altitude.toFixed(0)}m</div>` : ''}
</div>
`;
window.parent.postMessage({
type: 'streamlit:setComponentValue',
value: {
lat: lat,
lon: lon,
accuracy: accuracy,
altitude: altitude,
heading: heading,
speed: speed,
method: "GPS",
timestamp: timestamp,
success: true
}
}, '*');
},
function(error) {
let errorMsg = "๐Ÿ“ GPS location unavailable";
let errorDetail = "";
switch(error.code) {
case error.PERMISSION_DENIED:
errorMsg = "๐Ÿšซ Location access denied";
errorDetail = "Please allow location access in your browser settings";
break;
case error.POSITION_UNAVAILABLE:
errorMsg = "๐Ÿ“ GPS position unavailable";
errorDetail = "Your device's GPS might be disabled";
break;
case error.TIMEOUT:
errorMsg = "โฑ๏ธ GPS request timed out";
errorDetail = "GPS is taking too long to respond";
break;
}
if (locationAttempts < maxAttempts) {
document.getElementById("status").innerHTML = `
<div style="background: linear-gradient(45deg, #f39c12, #e67e22); color: white; padding: 15px; border-radius: 10px; margin: 10px 0;">
<div style="font-weight: bold; margin-bottom: 5px;">${errorMsg}</div>
<div style="font-size: 0.9em; margin-bottom: 5px;">${errorDetail}</div>
<div style="font-size: 0.8em;">๐Ÿ”„ Retrying in 2 seconds... (${locationAttempts}/${maxAttempts})</div>
</div>
`;
setTimeout(tryGetLocation, 2000);
} else {
// Try IP-based location as fallback
tryIPLocation();
}
},
options
);
} else {
tryIPLocation();
}
}
function tryIPLocation() {
document.getElementById("status").innerHTML = `
<div style="background: linear-gradient(45deg, #3498db, #2980b9); color: white; padding: 15px; border-radius: 10px; margin: 10px 0;">
<div style="font-weight: bold;">๐ŸŒ Trying IP-based location...</div>
<div style="font-size: 0.9em; opacity: 0.9;">Falling back to approximate location</div>
</div>
`;
// Multiple IP geolocation services for reliability
const services = [
'https://ipapi.co/json/',
'https://api.ipify.org?format=json',
'https://httpbin.org/ip'
];
fetch('https://ipapi.co/json/')
.then(response => response.json())
.then(data => {
if (data.latitude && data.longitude && !data.error) {
document.getElementById("status").innerHTML = `
<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);">
<div style="font-weight: bold; margin-bottom: 8px;">๐ŸŒ IP-based Location Found</div>
<div style="font-size: 0.95em; margin-bottom: 5px;">๐Ÿ“ <strong>${data.latitude.toFixed(6)}, ${data.longitude.toFixed(6)}</strong></div>
<div style="font-size: 0.85em; margin-bottom: 5px;">
๐ŸŸ  Accuracy: <span style="color: #f39c12; font-weight: bold;">Approximate</span> (City-level)
</div>
<div style="font-size: 0.8em; opacity: 0.9;">๐Ÿ™๏ธ ${data.city || 'Unknown'}, ${data.country_name || data.country || 'Unknown'}</div>
<div style="font-size: 0.8em; opacity: 0.9;">๐ŸŒ ISP: ${data.org || 'Unknown'}</div>
</div>
`;
window.parent.postMessage({
type: 'streamlit:setComponentValue',
value: {
lat: data.latitude,
lon: data.longitude,
accuracy: 10000,
method: "IP",
timestamp: new Date().toISOString(),
city: data.city,
country: data.country_name || data.country,
region: data.region,
timezone: data.timezone,
isp: data.org,
success: true
}
}, '*');
} else {
showLocationUnavailable();
}
})
.catch(() => {
showLocationUnavailable();
});
}
function showLocationUnavailable() {
document.getElementById("status").innerHTML = `
<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);">
<div style="font-weight: bold; margin-bottom: 8px;">โš ๏ธ Location Not Available</div>
<div style="font-size: 0.9em; margin-bottom: 5px;">Unable to detect your location automatically</div>
<div style="font-size: 0.85em; opacity: 0.9;">โ€ข You can continue uploading without location data</div>
<div style="font-size: 0.85em; opacity: 0.9;">โ€ข Or enter coordinates manually using the button</div>
</div>
`;
window.parent.postMessage({
type: 'streamlit:setComponentValue',
value: {lat: null, lon: null, success: false}
}, '*');
}
// Start location capture immediately
tryGetLocation();
</script>
<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);">
๐Ÿ”„ Initializing advanced location services...
</div>
""",
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"""
<div style="background: linear-gradient(45deg, #667eea, #764ba2); color: white; padding: 15px; border-radius: 10px; margin: 10px 0; text-align: center;">
<h3 style="margin: 0; font-size: 2em;">๐Ÿ‘ฅ {stats['total_users']}</h3>
<p style="margin: 5px 0 0 0;">Total Contributors</p>
</div>
""", unsafe_allow_html=True)
st.markdown(f"""
<div style="background: linear-gradient(45deg, #f093fb, #f5576c); color: white; padding: 15px; border-radius: 10px; margin: 10px 0; text-align: center;">
<h3 style="margin: 0; font-size: 2em;">๐Ÿ–ผ๏ธ {stats['total_images']}</h3>
<p style="margin: 5px 0 0 0;">Total Images</p>
</div>
""", unsafe_allow_html=True)
st.markdown(f"""
<div style="background: linear-gradient(45deg, #4facfe, #00f2fe); color: white; padding: 15px; border-radius: 10px; margin: 10px 0; text-align: center;">
<h3 style="margin: 0; font-size: 2em;">๐Ÿท๏ธ {stats['total_labels']}</h3>
<p style="margin: 5px 0 0 0;">Total Labels</p>
</div>
""", unsafe_allow_html=True)
st.markdown(f"""
<div style="background: linear-gradient(45deg, #43e97b, #38f9d7); color: white; padding: 15px; border-radius: 10px; margin: 10px 0; text-align: center;">
<h3 style="margin: 0; font-size: 2em;">๐ŸŒ {stats['languages_used']}</h3>
<p style="margin: 5px 0 0 0;">Languages Used</p>
</div>
""", unsafe_allow_html=True)
# Location statistics
st.markdown(f"""
<div style="background: linear-gradient(45deg, #fd79a8, #e84393); color: white; padding: 15px; border-radius: 10px; margin: 10px 0; text-align: center;">
<h3 style="margin: 0; font-size: 2em;">๐ŸŒ {stats['images_with_location']}</h3>
<p style="margin: 5px 0 0 0;">Images with GPS Data</p>
</div>
""", 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"""
<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);">
{method_icon} Location Ready: {location['lat']:.6f}, {location['lon']:.6f}{accuracy_text}
<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>
</div>
""", 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()