import streamlit as st
import os
import cv2
import yaml
import numpy as np
import torch
from PIL import Image
from src.pipeline import EcoPulsePipeline
from src.cnn_model import load_model
from src.transforms import EUROSAT_TRANSFORM
from src.visualization import apply_grad_cam, create_greenery_overlay
# --- Configuration & Caching ---
st.set_page_config(page_title="EcoPulse Dashboard", page_icon="🌿", layout="wide")
@st.cache_resource(show_spinner="Loading deep learning models (this may take a moment)...")
def load_pipeline(version=1):
"""Load the full pipeline once and cache it in GPU memory."""
# Ensure config exists
config_path = "config/config.yaml"
return EcoPulsePipeline(config_path)
@st.cache_resource(show_spinner="Loading Grad-CAM resources...")
def load_grad_cam_resources():
"""Load the CNN specifically for Grad-CAM."""
config_path = "config/config.yaml"
with open(config_path, "r") as f:
config = yaml.safe_load(f)
device = "cuda" if torch.cuda.is_available() else "cpu"
model = load_model(
weights_path=os.path.join(config['paths']['output_models'], 'resnet50_eurosat.pth'),
num_classes=config['model']['num_classes'],
device=device
)
model.eval()
transform = EUROSAT_TRANSFORM
return model, transform, config['classes'], config['greenery_classes']
# --- Main App ---
def main():
st.title("EcoPulse Satellite Analysis")
# --- Sidebar Controls ---
with st.sidebar:
st.markdown("### System Controls")
st.markdown("---")
st.markdown("**Hardware Status**")
if torch.cuda.is_available():
gpu_name = torch.cuda.get_device_name(0)
vram_used = torch.cuda.memory_allocated(0) / (1024**3)
vram_total = torch.cuda.get_device_properties(0).total_memory / (1024**3)
st.markdown(f"`GPU:` {gpu_name}")
st.progress(vram_used / vram_total, text=f"VRAM Allocation: {vram_used:.1f}GB / {vram_total:.1f}GB")
else:
st.warning("Running on CPU (No CUDA detected)")
st.markdown("\n**Active Pipeline**\n`SAM (ViT-B) + ResNet-50`")
st.markdown("
", unsafe_allow_html=True)
st.markdown("**Maintenance**")
if st.button("Clear Model Cache", use_container_width=True):
st.cache_resource.clear()
st.toast("Cache Cleared. Models will reload on next execution.")
if st.button("Terminate Session", help="Stop the Streamlit process securely", type="primary", use_container_width=True):
st.warning("Terminating server process...")
st.stop()
st.markdown("---")
st.caption("EcoPulse v1.0.0 | Environmental Auditing Platform")
st.markdown("""
Welcome to the **EcoPulse Dashboard**. Use the tabs below to either analyze a single image or compare two different regions.
""")
# Initialize models (cached — runs once, then reuses)
with st.spinner("Loading deep learning models (this may take a moment)..."):
pipeline = load_pipeline(version=1)
cnn_model, transform, all_classes, greenery_classes = load_grad_cam_resources()
tab1, tab2 = st.tabs(["Single Image Analysis", "Region Comparison"])
with tab1:
st.header("Single Image Analysis")
uploaded_file = st.file_uploader("Upload a Satellite Image (.jpg, .png)", type=["jpg", "png", "jpeg"], key="single")
if uploaded_file is not None:
# Save temp file
temp_dir = "data/temp"
os.makedirs(temp_dir, exist_ok=True)
temp_path = os.path.join(temp_dir, uploaded_file.name)
with open(temp_path, "wb") as f:
f.write(uploaded_file.getbuffer())
st.info("Image uploaded successfully. Running EcoPulse pipeline...")
# Process Image
with st.spinner("Segmenting and Classifying..."):
image_np, results = pipeline.process_image(temp_path)
# --- Display Metrics ---
st.header("Analysis Results")
green_pct = results['greenery_percentage']
total_px = results['total_pixels']
green_px = results['green_pixels']
col1, col2, col3 = st.columns(3)
col1.metric("Greenery Coverage", f"{green_pct:.1f}%", delta=None)
col2.metric("Green Pixels", f"{green_px:,}")
col3.metric("Total Pixels", f"{total_px:,}")
# --- Visualizations ---
st.subheader("Visual Overlays")
# Build composite greenery overlay
composite, green_masks = create_greenery_overlay(image_np, results['mask_classifications'])
v_col1, v_col2 = st.columns(2)
with v_col1:
st.image(image_np, caption="Original Satellite Image", width='stretch')
with v_col2:
st.image(composite, caption="Greenery Segmentation Overlay", width='stretch')
# --- Grad-CAM Interpretability ---
st.divider()
st.header("Model Interpretability (Grad-CAM)")
st.markdown("Select a detected greenery region below to see exactly which features the CNN focused on to make its classification.")
if len(green_masks) > 0:
# Sort masks by size (pixel count) descending
green_masks = sorted(green_masks, key=lambda x: x['pixels'], reverse=True)
# Create dropdown options
options = {f"Region {i+1} (Class: {m['class']}, Size: {m['pixels']:,} px)": m for i, m in enumerate(green_masks)}
selected_option = st.selectbox("Select a Greenery Region to Analyze:", list(options.keys()))
selected_mask_data = options[selected_option]
# Generate Grad-CAM for the selected mask
bbox = selected_mask_data['bbox'] # [x, y, w, h]
x, y, w_box, h_box = [int(v) for v in bbox]
h_img, w_img = image_np.shape[:2]
# Clamp bounding box coordinates to image boundaries
x = max(0, min(x, w_img - 1))
y = max(0, min(y, h_img - 1))
w_box = min(w_box, w_img - x)
h_box = min(h_box, h_img - y)
if w_box > 0 and h_box > 0:
crop = image_np[y:y+h_box, x:x+w_box]
crop_pil = Image.fromarray(crop)
input_tensor = transform(crop_pil).unsqueeze(0)
with st.spinner("Generating Grad-CAM Heatmap..."):
heatmap, pred_idx = apply_grad_cam(cnn_model, input_tensor, target_class=None)
# Create overlay
heatmap_resized = cv2.resize(heatmap, (crop.shape[1], crop.shape[0]))
heatmap_colored = cv2.applyColorMap(np.uint8(255 * heatmap_resized), cv2.COLORMAP_JET)
heatmap_colored = cv2.cvtColor(heatmap_colored, cv2.COLOR_BGR2RGB)
alpha = 0.5
gradcam_overlay = np.uint8(crop * (1 - alpha) + heatmap_colored * alpha)
g_col1, g_col2 = st.columns(2)
with g_col1:
st.image(crop, caption=f"Cropped Region (Original)", width='stretch')
with g_col2:
st.image(gradcam_overlay, caption=f"Grad-CAM Heatmap (Class: {all_classes[pred_idx]})", width='stretch')
else:
st.warning("Selected region is too small to analyze.")
else:
st.info("No greenery regions detected in this image.")
with tab2:
st.header("Region Comparison")
st.markdown("Upload two satellite images to compare their greenery coverage side-by-side.")
c_col1, c_col2 = st.columns(2)
with c_col1:
file_a = st.file_uploader("Upload Area A", type=["jpg", "png", "jpeg"], key="area_a")
with c_col2:
file_b = st.file_uploader("Upload Area B", type=["jpg", "png", "jpeg"], key="area_b")
if file_a and file_b:
if st.button("Run Comparison Analysis"):
# Save temp files
temp_dir = "data/temp"
os.makedirs(temp_dir, exist_ok=True)
path_a = os.path.join(temp_dir, "compare_a_" + file_a.name)
path_b = os.path.join(temp_dir, "compare_b_" + file_b.name)
with open(path_a, "wb") as f: f.write(file_a.getbuffer())
with open(path_b, "wb") as f: f.write(file_b.getbuffer())
with st.spinner("Analyzing both regions (this may take a minute)..."):
img_a_np, results_a = pipeline.process_image(path_a)
img_b_np, results_b = pipeline.process_image(path_b)
pct_a = results_a['greenery_percentage']
pct_b = results_b['greenery_percentage']
st.divider()
st.subheader("Comparison Result")
diff = pct_a - pct_b
if abs(diff) < 1:
st.success("Both regions have nearly identical greenery coverage.")
else:
winner = "Area A" if diff > 0 else "Area B"
st.info(f"**{winner}** is more vegetated by **{abs(diff):.1f}%**.")
st.markdown("### Detailed Metrics")
res_col1, res_col2 = st.columns(2)
with res_col1:
st.markdown("**Area A**")
st.metric("Greenery Coverage", f"{pct_a:.1f}%")
st.caption(f"Green Pixels: {results_a['green_pixels']:,} / Total: {results_a['total_pixels']:,}")
with res_col2:
st.markdown("**Area B**")
st.metric("Greenery Coverage", f"{pct_b:.1f}%")
st.caption(f"Green Pixels: {results_b['green_pixels']:,} / Total: {results_b['total_pixels']:,}")
# Visual comparison
st.markdown("### Visual Side-by-Side Analysis")
# Generate overlays
comp_a, _ = create_greenery_overlay(img_a_np, results_a['mask_classifications'])
comp_b, _ = create_greenery_overlay(img_b_np, results_b['mask_classifications'])
v_res_col1, v_res_col2 = st.columns(2)
v_res_col1.image(comp_a, caption=f"Area A Overlay ({pct_a:.1f}% Green)", width='stretch')
v_res_col2.image(comp_b, caption=f"Area B Overlay ({pct_b:.1f}% Green)", width='stretch')
if __name__ == "__main__":
main()