Spaces:
Sleeping
Sleeping
| import streamlit as st | |
| from PIL import Image | |
| import numpy as np | |
| import tensorflow as tf | |
| from tensorflow.keras.models import load_model | |
| import io | |
| import random | |
| import time | |
| from huggingface_hub import hf_hub_download | |
| # Load model from Hugging Face Model Hub | |
| model_path = hf_hub_download(repo_id="koulsahil/LandCoverClassification_EuroSat", filename="eurosat_rgb_model.h5") | |
| model = tf.keras.models.load_model(model_path) | |
| # Define the class labels (replace with your EuroSAT classes) | |
| class_labels = [ | |
| "AnnualCrop", "Forest", "HerbaceousVegetation", "Highway", "Industrial", | |
| "Pasture", "PermanentCrop", "Residential", "River", "SeaLake" | |
| ] | |
| # Function to preprocess the image for the model | |
| def preprocess_image(image): | |
| # Convert image to RGB if it has an alpha channel (4 channels) | |
| if image.mode == 'RGBA': | |
| image = image.convert('RGB') | |
| image = image.resize((64, 64)) # Resize to match EuroSAT input size | |
| image = np.array(image) / 255.0 # Normalize pixel values | |
| image = np.expand_dims(image, axis=0) # Add batch dimension | |
| return image | |
| # Function to make predictions | |
| def predict(image): | |
| processed_image = preprocess_image(image) | |
| predictions = model.predict(processed_image) | |
| predicted_class = class_labels[np.argmax(predictions)] | |
| confidence = np.max(predictions) | |
| return predicted_class, confidence | |
| def create_map_animation(): | |
| st.markdown(""" | |
| <div class="map-container"> | |
| <div class="sun"></div> | |
| <div class="cloud cloud1"></div> | |
| <div class="cloud cloud2"></div> | |
| <div class="moving-car car1"></div> | |
| <div class="moving-car car2"></div> | |
| </div> | |
| <style> | |
| .map-container { | |
| position: relative; | |
| height: 400px; | |
| width: 100%; | |
| background: #87CEEB; | |
| border-radius: 10px; | |
| overflow: hidden; | |
| margin: 20px 0; | |
| border: 2px solid #4682B4; | |
| } | |
| /* Sun */ | |
| .sun { | |
| position: absolute; | |
| top: 20px; | |
| right: 30px; | |
| width: 50px; | |
| height: 50px; | |
| background: #FFD700; | |
| border-radius: 50%; | |
| animation: sunGlow 2s ease-in-out infinite; | |
| } | |
| .sun::after { | |
| content: ""; | |
| position: absolute; | |
| width: 100%; | |
| height: 100%; | |
| border: 4px solid #FFD700; | |
| border-radius: 50%; | |
| animation: sunPulse 2s ease-out infinite; | |
| } | |
| /* Clouds */ | |
| .cloud { | |
| position: absolute; | |
| background: white; | |
| border-radius: 20px; | |
| animation: cloudFloat 20s linear infinite; | |
| } | |
| .cloud1 { | |
| top: 80px; | |
| left: -100px; | |
| width: 80px; | |
| height: 30px; | |
| animation-delay: 2s; | |
| } | |
| .cloud1::before { | |
| content: ""; | |
| position: absolute; | |
| top: -15px; | |
| left: 10px; | |
| width: 40px; | |
| height: 40px; | |
| background: white; | |
| border-radius: 50%; | |
| } | |
| .cloud2 { | |
| top: 120px; | |
| left: -100px; | |
| width: 100px; | |
| height: 35px; | |
| animation-delay: 5s; | |
| } | |
| .cloud2::before { | |
| content: ""; | |
| position: absolute; | |
| top: -15px; | |
| left: 15px; | |
| width: 50px; | |
| height: 50px; | |
| background: white; | |
| border-radius: 50%; | |
| } | |
| /* Cars */ | |
| .moving-car { | |
| position: absolute; | |
| bottom: 30px; | |
| width: 40px; | |
| height: 20px; | |
| border-radius: 5px; | |
| animation: drive 8s linear infinite; | |
| } | |
| .car1 { | |
| left: -50px; | |
| background: #FF4757; | |
| animation-delay: 1s; | |
| } | |
| .car2 { | |
| left: -50px; | |
| background: #2ED573; | |
| animation-delay: 3s; | |
| } | |
| .moving-car::after, .moving-car::before { | |
| content: ""; | |
| position: absolute; | |
| width: 10px; | |
| height: 10px; | |
| background: #333; | |
| border-radius: 50%; | |
| bottom: -5px; | |
| } | |
| .moving-car::after { left: 5px; } | |
| .moving-car::before { right: 5px; } | |
| /* Animations */ | |
| @keyframes sunGlow { | |
| 0%, 100% { box-shadow: 0 0 20px #FFD700; } | |
| 50% { box-shadow: 0 0 40px #FFD700; } | |
| } | |
| @keyframes sunPulse { | |
| 0% { transform: scale(1); opacity: 0.5; } | |
| 100% { transform: scale(1.5); opacity: 0; } | |
| } | |
| @keyframes cloudFloat { | |
| 0% { transform: translateX(0); } | |
| 100% { transform: translateX(1200px); } | |
| } | |
| @keyframes drive { | |
| 0% { transform: translateX(0); } | |
| 100% { transform: translateX(1200px); } | |
| } | |
| </style> | |
| """, unsafe_allow_html=True) | |
| # Set the page title and favicon (emoji as the icon) | |
| st.set_page_config( | |
| page_title="Land Cover Classification", # Title of the app | |
| page_icon="🌍", # Use the world emoji as the icon | |
| ) | |
| # Custom CSS for styling | |
| st.markdown(""" | |
| <style> | |
| .stButton button { | |
| background-color: #4CAF50; | |
| color: white; | |
| border-radius: 5px; | |
| padding: 10px 20px; | |
| font-size: 16px; | |
| } | |
| .stButton button:hover { | |
| background-color: #45a049; | |
| } | |
| .stProgress > div > div > div { | |
| background-color: #4CAF50; | |
| } | |
| .stMarkdown h1 { | |
| color: #4CAF50; | |
| } | |
| .stMarkdown h2 { | |
| color: #2E86C1; | |
| } | |
| .upload-section { | |
| height: 150px; /* Increase the height of the upload section */ | |
| } | |
| .thumbnail { | |
| cursor: pointer; | |
| border: 2px solid transparent; | |
| transition: border 0.3s ease; | |
| margin: 5px; | |
| } | |
| .thumbnail:hover { | |
| border: 2px solid #4CAF50; | |
| } | |
| </style> | |
| """, unsafe_allow_html=True) | |
| # Sidebar for project information | |
| import streamlit as st | |
| with st.sidebar: | |
| st.title("About the Project 🌍") | |
| st.write(""" | |
| Hi, I’m **Sahil!** I built this web app to classify satellite images using Neural Networks, | |
| This app leverages a **custom VGG16 deep learning model** trained on the **EuroSAT dataset** to classify land cover types in satellite images. | |
| The EuroSAT dataset is a collection of **27,000 labeled satellite images** covering **10 distinct land cover classes**, such as forests, crops, industrial areas, and water bodies. | |
| ### How to Use: | |
| 1. **Select an Image**: | |
| - Drag and drop one of the **sample image thumbnails** to the upload section to quickly test the model. | |
| - Or use the dropdown menu to choose from a list of sample images, or click the select random button to choose a random image from the list. | |
| - Alternatively, **upload your own satellite image** by dragging and dropping it into the upload section. | |
| 2. **Make a Prediction**: | |
| - Once an image is selected or uploaded, the model will automatically analyze it and display the **predicted land cover type** along with a **confidence score**. | |
| 3. **Interpret the Results**: | |
| - The app will show the **top predicted class** and its confidence level. | |
| - You can also view the **top 3 predictions** to understand the model's certainty across multiple classes. | |
| ### GitHub Repository: | |
| Explore the code, dataset, and model training process on GitHub: | |
| [GitHub Repository](https://koulmesahil.github.io/) | [LinkedIn](https://www.linkedin.com/in/sahilkoul123/) | |
| """) | |
| # Main layout | |
| st.title("Land Cover Classification from Satellite Images ") | |
| create_map_animation() | |
| st.write("🖼️📸 Drag and drop one of the thumbnails below, select a random image from the dropdown, or upload your own image to classify its land cover type.🌍") | |
| # Define sample images | |
| sample_images = { | |
| "Highway": "sample_images/Highway_1004.jpg", | |
| "Annual Crop": "sample_images/AnnualCrop_102.jpg", | |
| "Forest": "sample_images/Forest_1019.jpg", | |
| "Herbaceous Vegetation": "sample_images/HerbaceousVegetation_1024.jpg", | |
| "Industrial": "sample_images/Industrial_1015.jpg", | |
| "Pasture": "sample_images/Pasture_1023.jpg", | |
| "Sea Lake": "sample_images/SeaLake_1017.jpg", | |
| "River": "sample_images/River_1014.jpg", | |
| "Permenant Crop": "sample_images/PermanentCrop_1004.jpg", | |
| "Residential": "sample_images/Residential_1019.jpg", | |
| # Add more sample images here | |
| } | |
| # Thumbnail Section | |
| st.write("### Sample Image Thumbnails") | |
| cols = st.columns(len(sample_images)) # Create columns for thumbnails | |
| for idx, (label, image_path) in enumerate(sample_images.items()): | |
| with cols[idx]: | |
| # Display the thumbnail | |
| image = Image.open(image_path) | |
| image.thumbnail((100, 100)) # Resize the image to a smaller thumbnail | |
| st.image(image, use_container_width=True, caption=None) | |
| # Dropdown menu for sample images | |
| #st.write("### Select an Image from Dropdown") | |
| selected_image_label = st.selectbox("Choose a category to view a random image:", ["Pick a category"] + list(sample_images.keys())) | |
| if selected_image_label != "Pick a category": | |
| image_path = sample_images[selected_image_label] | |
| with open(image_path, "rb") as file: | |
| uploaded_file = file.read() | |
| st.session_state.uploaded_file = uploaded_file | |
| st.session_state.selected_image_label = selected_image_label | |
| # Add a "Select Random" button | |
| if st.button("Random Selection"): | |
| random_label, random_image_path = random.choice(list(sample_images.items())) | |
| with open(random_image_path, "rb") as file: | |
| uploaded_file = file.read() | |
| st.session_state.uploaded_file = uploaded_file | |
| st.session_state.selected_image_label = random_label | |
| st.success(f"Randomly selected image: **{random_label}**") | |
| # JavaScript & CSS to dynamically change the border color | |
| st.markdown( | |
| """ | |
| <style> | |
| /* Base styling for the upload box */ | |
| div.stFileUploader { | |
| height: 250px !important; | |
| width: 100% !important; | |
| border: 3px dashed grey !important; | |
| padding: 40px !important; | |
| border-radius: 10px; | |
| transition: all 0.3s ease-in-out; | |
| } | |
| /* Upload text styling */ | |
| div.stFileUploader > label { | |
| font-size: 20px !important; | |
| font-weight: bold !important; | |
| color: grey !important; | |
| text-align: center !important; | |
| } | |
| /* JavaScript to change colors dynamically */ | |
| <script> | |
| function updateUploaderColor() { | |
| var uploader = document.querySelector("div.stFileUploader"); | |
| if (uploader && uploader.querySelector("input").files.length > 0) { | |
| uploader.style.borderColor = "#4CAF50"; | |
| uploader.style.color = "#4CAF50"; | |
| } else { | |
| uploader.style.borderColor = "grey"; | |
| uploader.style.color = "grey"; | |
| } | |
| } | |
| document.addEventListener("DOMContentLoaded", function() { | |
| var fileInput = document.querySelector("div.stFileUploader input"); | |
| if (fileInput) { | |
| fileInput.addEventListener("change", updateUploaderColor); | |
| } | |
| }); | |
| </script> | |
| </style> | |
| """, | |
| unsafe_allow_html=True, | |
| ) | |
| st.write("### Upload Your Image") | |
| uploaded_file = st.file_uploader( | |
| "Drag and drop an image here", | |
| type=["jpg", "jpeg", "png"], | |
| key="uploader", | |
| accept_multiple_files=False, | |
| help="Upload an image to classify its land cover type." | |
| ) | |
| if uploaded_file: | |
| st.markdown( | |
| """ | |
| <style> | |
| div.stFileUploader { | |
| border-color: #4CAF50 !important; | |
| color: #4CAF50 !important; | |
| } | |
| </style> | |
| """, | |
| unsafe_allow_html=True, | |
| ) | |
| # Display the uploaded/selected image and make predictions | |
| if uploaded_file is not None: | |
| try: | |
| file_type = uploaded_file.type | |
| if file_type not in ["image/jpeg", "image/png"]: | |
| st.error("Unsupported file type. Please upload a JPG or PNG image.") | |
| else: | |
| image = Image.open(uploaded_file) | |
| st.session_state.uploaded_file = uploaded_file.getvalue() | |
| st.session_state.selected_image_label = "Uploaded Image" | |
| except Exception as e: | |
| st.error(f"Error loading image: {e}") | |
| # Display the uploaded/selected image and make predictions | |
| if "uploaded_file" in st.session_state: | |
| image = Image.open(io.BytesIO(st.session_state.uploaded_file)) | |
| # Display the image with a smaller size | |
| st.image(image, caption=f"Selected Image: {st.session_state.selected_image_label}", width=300) | |
| # Show balloons effect after the prediction is done | |
| with st.spinner("Analyzing the image..."): | |
| time.sleep(1) # Simulate processing delay | |
| predicted_class, confidence = predict(image) | |
| st.balloons() # Display balloons when prediction is complete | |
| # Display the prediction results in a more prominent section | |
| st.markdown("## Prediction Results") | |
| st.success(f"**Predicted Class:** {predicted_class}") | |
| st.info(f"**Confidence:** {confidence * 100:.2f}%") | |
| # Visualize confidence as a progress bar with a label | |
| st.markdown("**Confidence Level:**") | |
| st.progress(float(confidence)) | |
| # Show top 3 predictions | |
| st.markdown("### Top 3 Predictions") | |
| processed_image = preprocess_image(image) | |
| predictions = model.predict(processed_image) | |
| top_indices = np.argsort(predictions[0])[-3:][::-1] # Get top 3 predictions | |
| for i in top_indices: | |
| st.write(f"- **{class_labels[i]}**: {predictions[0][i] * 100:.2f}%") | |
| # Footer | |
| st.markdown("---") | |
| st.markdown(""" | |
| [GitHub Repository](https://koulmesahil.github.io/) | [LinkedIn](https://www.linkedin.com/in/sahilkoul123/) | |
| """) |