import streamlit as st import torch import numpy as np from PIL import Image import matplotlib.pyplot as plt import matplotlib.patches as mpatches from matplotlib.colors import ListedColormap import io import os import sys sys.path.append(os.path.dirname(__file__)) from utils.inference import ( load_model, run_inference, get_damage_stats, prediction_to_colored_image, export_geojson, LABELS, COLORS ) from utils.climate import ( get_climate_data, parse_climate_data, calculate_anomaly, plot_climate_trends, get_climate_summary ) # ── Page config ────────────────────────────────────────────────────────────── st.set_page_config( page_title = "GeoAI Disaster Mapper", page_icon = "🛰️", layout = "wide", initial_sidebar_state = "expanded" ) # ── Custom CSS ──────────────────────────────────────────────────────────────── st.markdown(""" """, unsafe_allow_html=True) # ── Header ──────────────────────────────────────────────────────────────────── st.markdown("""

🛰️ GeoAI DisasterMapper

Automated Building Damage Detection & Climate Vulnerability Analysis

""", unsafe_allow_html=True) # ── Sidebar ─────────────────────────────────────────────────────────────────── with st.sidebar: st.markdown('

⚙️ Configuration

', unsafe_allow_html=True) # Model path model_path = st.text_input( "Model Path", value="model/best_model_resnet50.pth", help="Path to trained U-Net model" ) st.markdown("---") st.markdown('

📍 Location

', unsafe_allow_html=True) # Disaster presets disaster_presets = { "Custom" : (0.0, 0.0, None), "Morocco Earthquake 2023" : (31.07, -8.41, "2023-09-08"), "Turkey Earthquake 2023" : (37.17, 37.03, "2023-02-06"), "Nepal Earthquake 2015" : (27.73, 85.33, "2015-04-25"), "Palu Tsunami 2018" : (-0.90, 119.88, "2018-09-28"), "Hurricane Michael 2018" : (30.19, -85.68, "2018-10-10"), } selected_preset = st.selectbox("Disaster Preset", list(disaster_presets.keys())) preset_lat, preset_lon, preset_date = disaster_presets[selected_preset] col1, col2 = st.columns(2) with col1: lat = st.number_input("Latitude", value=preset_lat, format="%.4f") with col2: lon = st.number_input("Longitude", value=preset_lon, format="%.4f") disaster_date = st.text_input( "Disaster Date (YYYY-MM-DD)", value=preset_date if preset_date else "" ) st.markdown("---") st.markdown('

🌡️ Climate Settings

', unsafe_allow_html=True) start_year = st.slider("Climate Data Start Year", 2000, 2020, 2010) end_year = st.slider("Climate Data End Year", 2015, 2024, 2023) st.markdown("---") st.markdown("""
About
U-Net ResNet50 trained on xBD dataset
Macro F1: 0.76 | 5 damage classes
Climate data: NASA POWER API

Project
GeoAI + Climate Change Research
2015 Gorkha Earthquake, Nepal
""", unsafe_allow_html=True) # ── Main content ────────────────────────────────────────────────────────────── tab1, tab2, tab3 = st.tabs(["🛰️ Damage Detection", "🌡️ Climate Analysis", "📊 Model Info"]) # ── Tab 1: Damage Detection ─────────────────────────────────────────────────── with tab1: st.markdown('

Upload Satellite Images

', unsafe_allow_html=True) col1, col2 = st.columns(2) with col1: st.markdown("**Before Disaster**") pre_file = st.file_uploader( "Upload pre-disaster image", type=['png', 'jpg', 'jpeg', 'tif', 'tiff'], key="pre" ) if pre_file: pre_image = Image.open(pre_file) st.image(pre_image, caption="Before Disaster", use_column_width=True) with col2: st.markdown("**After Disaster**") post_file = st.file_uploader( "Upload post-disaster image", type=['png', 'jpg', 'jpeg', 'tif', 'tiff'], key="post" ) if post_file: post_image = Image.open(post_file) st.image(post_image, caption="After Disaster", use_column_width=True) st.markdown("---") if pre_file and post_file: if st.button("🔍 RUN DAMAGE DETECTION"): with st.spinner("Loading model and running inference..."): try: # Load model if not os.path.exists(model_path): st.error(f"Model not found at: {model_path}") st.stop() device = 'cuda' if torch.cuda.is_available() else 'cpu' model = load_model(model_path, device) # Run inference pre_image = Image.open(pre_file) post_image = Image.open(post_file) pred = run_inference(model, pre_image, post_image, device) # Get stats stats = get_damage_stats(pred) st.success("Inference complete ✓") # ── Damage metrics ──────────────────────────────────── st.markdown('

Damage Statistics

', unsafe_allow_html=True) metric_cols = st.columns(5) damage_colors_hex = { 'background' : '#64748b', 'no-damage' : '#2ecc71', 'minor' : '#f1c40f', 'major' : '#e67e22', 'destroyed' : '#e74c3c' } for i, label in enumerate(LABELS): with metric_cols[i]: pct = stats.get(label, {}).get('percent', 0) st.markdown(f"""
{pct:.1f}%
{label}
""", unsafe_allow_html=True) # ── Damage map visualization ────────────────────────── st.markdown('

Damage Map

', unsafe_allow_html=True) colored_pred = prediction_to_colored_image(pred) fig, axes = plt.subplots(1, 3, figsize=(18, 6)) fig.patch.set_facecolor('#0a0e1a') for ax in axes: ax.set_facecolor('#0a0e1a') ax.axis('off') axes[0].imshow(np.array(pre_image.convert('RGB'))) axes[0].set_title('Before Disaster', color='white', fontsize=12, pad=10) axes[1].imshow(np.array(post_image.convert('RGB'))) axes[1].set_title('After Disaster', color='white', fontsize=12, pad=10) axes[2].imshow(np.array(post_image.convert('RGB'))) axes[2].imshow(colored_pred, alpha=0.6) axes[2].set_title('Damage Prediction Overlay', color='white', fontsize=12, pad=10) # Legend patches = [ mpatches.Patch(color=COLORS[i], label=LABELS[i]) for i in range(len(LABELS)) ] fig.legend( handles=patches, loc='lower center', ncol=5, facecolor='#1a2035', labelcolor='white', fontsize=10, bbox_to_anchor=(0.5, -0.05) ) plt.tight_layout() buf = io.BytesIO() plt.savefig(buf, format='png', dpi=150, bbox_inches='tight', facecolor='#0a0e1a') buf.seek(0) plt.close() st.image(buf, use_column_width=True) # ── GeoJSON export ──────────────────────────────────── st.markdown('

Export Results

', unsafe_allow_html=True) geojson_path = export_geojson(pred) if geojson_path: with open(geojson_path, 'r') as f: geojson_data = f.read() st.download_button( label = "⬇️ Download GeoJSON", data = geojson_data, file_name = "damage_map.geojson", mime = "application/json" ) st.markdown("""
📌 Open the GeoJSON file in QGIS or ArcGIS to view damage polygons with exact coordinates on a map.
""", unsafe_allow_html=True) else: st.info("No significant damage detected in uploaded images.") except Exception as e: st.error(f"Error during inference: {str(e)}") else: st.markdown("""
👆 Upload both before and after disaster satellite images to run damage detection. Use the xBD test images or any high-resolution satellite image pair.
""", unsafe_allow_html=True) # ── Tab 2: Climate Analysis ─────────────────────────────────────────────────── with tab2: st.markdown('

Climate Vulnerability Analysis

', unsafe_allow_html=True) st.markdown("""
Fetches temperature, rainfall and humidity data from NASA POWER API for the selected disaster location. Analyzes climate trends to assess long-term vulnerability.
""", unsafe_allow_html=True) if lat == 0.0 and lon == 0.0: st.warning("Please set latitude and longitude in the sidebar first.") else: st.markdown(f"**Location:** {lat}°N, {lon}°E | **Period:** {start_year}–{end_year}") if st.button("🌡️ FETCH CLIMATE DATA"): with st.spinner("Fetching data from NASA POWER API..."): try: raw_data = get_climate_data(lat, lon, start_year, end_year) if raw_data is None: st.error("Failed to fetch climate data. Check your internet connection.") st.stop() df = parse_climate_data(raw_data) df = calculate_anomaly(df) summary = get_climate_summary( df, disaster_date if disaster_date else None ) # ── Climate metrics ─────────────────────────────────── st.markdown('

Climate Summary

', unsafe_allow_html=True) mcols = st.columns(4) with mcols[0]: st.metric( "Avg Temperature", f"{summary['avg_temp']}°C" ) with mcols[1]: st.metric( "Max Temperature", f"{summary['max_temp']}°C" ) with mcols[2]: st.metric( "Avg Rainfall", f"{summary['avg_rainfall']} mm/day" ) with mcols[3]: warming = summary.get('warming_rate', 0) or 0 st.metric( "Warming Rate", f"{warming:+.3f}°C/year", delta=f"{'↑ Warming' if warming > 0 else '↓ Cooling'}" ) # Pre vs post disaster if disaster_date and 'temp_change' in summary: st.markdown('

Pre vs Post Disaster Climate

', unsafe_allow_html=True) pcols = st.columns(3) with pcols[0]: st.metric( "Pre-disaster Avg Temp", f"{summary['pre_disaster_avg_temp']}°C" ) with pcols[1]: st.metric( "Post-disaster Avg Temp", f"{summary['post_disaster_avg_temp']}°C" ) with pcols[2]: change = summary['temp_change'] st.metric( "Temperature Change", f"{change:+.2f}°C", delta=f"{'Warmer' if change > 0 else 'Cooler'} after disaster" ) # ── Climate charts ──────────────────────────────────── st.markdown('

Climate Trends

', unsafe_allow_html=True) location_name = selected_preset if selected_preset != "Custom" \ else f"{lat}°N, {lon}°E" chart_buf = plot_climate_trends( df, location_name, disaster_date if disaster_date else None ) st.image(chart_buf, use_column_width=True) # Download CSV csv = df.to_csv(index=False) st.download_button( label = "⬇️ Download Climate Data (CSV)", data = csv, file_name = "climate_data.csv", mime = "text/csv" ) # Vulnerability assessment st.markdown('

Vulnerability Assessment

', unsafe_allow_html=True) warming_rate = summary.get('warming_rate') or 0 if warming_rate > 0.02: risk_level = "🔴 HIGH" risk_color = "#e74c3c" risk_msg = f"Region is warming at {warming_rate:.3f}°C/year — significantly above global average." elif warming_rate > 0.01: risk_level = "🟠 MEDIUM" risk_color = "#e67e22" risk_msg = f"Region shows moderate warming trend of {warming_rate:.3f}°C/year." else: risk_level = "🟡 LOW-MEDIUM" risk_color = "#f1c40f" risk_msg = f"Region shows relatively stable temperature trend." st.markdown(f"""
Climate Risk: {risk_level}

{risk_msg}

Areas damaged by disasters become increasingly vulnerable to climate-induced secondary hazards such as landslides, floods, and extreme heat events.
""", unsafe_allow_html=True) except Exception as e: st.error(f"Error fetching climate data: {str(e)}") # ── Tab 3: Model Info ───────────────────────────────────────────────────────── with tab3: st.markdown('

Model Architecture

', unsafe_allow_html=True) col1, col2 = st.columns(2) with col1: st.markdown("""
Architecture
U-Net with ResNet50 encoder
Pretrained on ImageNet
6 input channels (3 pre + 3 post)
5 output classes

Training
Dataset: xBD (xView2 Challenge)
Optimizer: AdamW (lr=0.0005)
Loss: BCE + Dice combined
Epochs: 30 with early stopping
Augmentation: flip + rotation

Performance
Overall Accuracy: 93%
Macro F1 Score: 0.76
Destroyed F1: 0.71
""", unsafe_allow_html=True) with col2: st.markdown("""
Damage Classes

🟢 No Damage — intact structures
🟡 Minor Damage — superficial damage
🟠 Major Damage — structural damage
🔴 Destroyed — complete collapse

Training Data
19 disaster events worldwide
Guatemala volcano eruption
Hurricane Michael, Harvey, Florence
Midwest flooding, Santa Rosa wildfire
Palu tsunami, Mexico earthquake

Climate Data
Source: NASA POWER API
Parameters: Temperature, Rainfall, Humidity
Resolution: Monthly averages
Coverage: Global, 2000–present
""", unsafe_allow_html=True) st.markdown('

Training Curves

', unsafe_allow_html=True) curve_path = "outputs/training_curves.png" cm_path = "outputs/confusion_matrix.png" if os.path.exists(curve_path): st.image(curve_path, caption="Training Loss & Accuracy Curves", use_column_width=True) else: st.info("Place training_curves.png in outputs/ folder to display here.") if os.path.exists(cm_path): st.image(cm_path, caption="Confusion Matrix", use_column_width=True) else: st.info("Place confusion_matrix.png in outputs/ folder to display here.") st.markdown('

Project Pipeline

', unsafe_allow_html=True) st.markdown(""" ``` xBD Dataset (USA/Guatemala disasters) ↓ Preprocessing → 256x256 chips with augmentation ↓ U-Net ResNet50 Training (Google Colab T4 GPU) ↓ Inference on unseen test disasters ↓ Apply to target disaster location ↓ GeoJSON damage map export ↓ NASA POWER climate vulnerability overlay ``` """) # ── Footer ──────────────────────────────────────────────────────────────────── st.markdown("---") st.markdown("""
GeoAI-DisasterMapper | U-Net ResNet50 | xBD Dataset | NASA POWER API
Built for Graduate Research Assistant Application — GeoAI & Climate Change
""", unsafe_allow_html=True)