""" Gradio frontend — Flood Detection + ZoeDepth depth estimation. Improved UI: dark theme, styled cards, progress steps, metric badges. """ import os os.environ["TF_ENABLE_ONEDNN_OPTS"] = "0" os.environ["TF_CPP_MIN_LOG_LEVEL"] = "2" os.environ["TF_XLA_FLAGS"] = "--tf_xla_auto_jit=0" import gradio as gr import spaces import numpy as np from PIL import Image import sys import asyncio # Suppress harmless asyncio garbage collection bug in Python 3.13 if hasattr(asyncio, "base_events") and hasattr(asyncio.base_events, "BaseEventLoop"): _original_del = asyncio.base_events.BaseEventLoop.__del__ def _safe_del(self): try: _original_del(self) except Exception: pass asyncio.base_events.BaseEventLoop.__del__ = _safe_del import base64 from gradio.themes.utils.colors import Color sys.path.insert(0, os.path.dirname(__file__)) from app.model_utils import run_pipeline def get_base64_image(path): try: with open(path, "rb") as img_file: return base64.b64encode(img_file.read()).decode('utf-8') except Exception: return "" logo_base64 = get_base64_image(os.path.join(os.path.dirname(__file__), "clipart1553592.png")) logo_src = f"data:image/png;base64,{logo_base64}" # ── Risk config ──────────────────────────────────────────────────────────────── RISK_CFG = { "Low": {"colour": "#27ae60", "bg": "#eafaf1", "bar": 20}, "Moderate": {"colour": "#f39c12", "bg": "#fef9e7", "bar": 50}, "High": {"colour": "#e67e22", "bg": "#fdf2e9", "bar": 75}, "Critical": {"colour": "#e74c3c", "bg": "#fdedec", "bar": 100}, } CSS = """ @import url('https://fonts.googleapis.com/css2?family=Geist:wght@400;500;600;700;800&display=swap'); :root, .dark { --primary-50: #f0f7fb !important; --primary-100: #e1f0f7 !important; --primary-200: #c4e1ef !important; --primary-300: #a6d2e8 !important; --primary-400: #89c3e0 !important; --primary-500: #43a0d6 !important; --primary-600: #3680ab !important; --primary-700: #286080 !important; --primary-800: #1b4056 !important; --primary-900: #0d202b !important; --primary-950: #071015 !important; } /* ── light theme overrides (warm off-white) ── */ .light { --block-background-fill: #f7f3eb !important; --background-fill-primary: #f7f3eb !important; --background-fill-secondary: #eee9dd !important; --border-color-primary: #dfd8c9 !important; --body-text-color: #1f2937 !important; --body-text-color-subdued: #4b5563 !important; --block-label-text-color: #1f2937 !important; --block-info-text-color: #4b5563 !important; } .light .upload-container span, .light .upload-container p, .light .upload-container div { color: #4b5563 !important; } /* ── page ── */ body, .gradio-container { font-family: 'Geist', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif !important; } .light body, .light .gradio-container { background-color: #f7f3eb !important; } /* ── theme toggle switch ── */ .theme-switch { position: absolute; top: 32px; right: 36px; display: inline-block; width: 44px; height: 24px; z-index: 10; } .theme-switch input { opacity: 0; width: 0; height: 0; } .theme-slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #cbd5e1; transition: .4s; border-radius: 24px; } .theme-slider:before { position: absolute; content: ""; height: 18px; width: 18px; left: 3px; bottom: 3px; background-color: #f1f5f9; transition: .4s; border-radius: 50%; box-shadow: 0 1px 3px rgba(0,0,0,0.2); } input:checked + .theme-slider { background-color: #3ba3e8; } input:checked + .theme-slider:before { transform: translateX(20px); background-color: #f8fafc; } #header-band { background: var(--block-background-fill); border-radius: 12px; position: relative; padding: 32px 36px 24px; margin-bottom: 24px; box-shadow: var(--block-shadow); border: 1px solid var(--border-color-primary); } #header-band h1 { color: var(--body-text-color); font-size: 2.2em; margin: 0 0 8px; font-weight: 800; letter-spacing: -0.03em; } #header-band p { color: var(--body-text-color-subdued); margin: 0; font-size: 1.05em; font-weight: 400; } /* ── metric pill row ── */ .metric-pill { display: inline-block; background: var(--background-fill-secondary); border: 1px solid var(--border-color-primary); border-radius: 8px; padding: 6px 16px; margin: 6px 6px 6px 0; font-size: 0.9em; color: var(--body-text-color-subdued); font-weight: 500; } .metric-pill strong { color: var(--body-text-color); font-weight: 700; } /* ── upload + button panel ── */ #left-panel { background: var(--block-background-fill); border-radius: 12px; padding: 20px; border: 1px solid var(--border-color-primary); box-shadow: var(--block-shadow); } /* ── analyse button ── */ #analyse-btn { background: var(--primary-500) !important; color: white !important; border: none !important; border-radius: 8px !important; font-size: 1.1em !important; font-weight: 600 !important; padding: 14px !important; margin-top: 12px !important; box-shadow: 0 4px 14px rgba(0,0,0,0.1) !important; transition: all .2s ease !important; } #analyse-btn:hover { transform: translateY(-2px) !important; box-shadow: 0 8px 25px rgba(0,0,0,0.15) !important; background: var(--primary-600) !important; } /* ── section labels ── */ .section-label { font-size: 0.85em; font-weight: 700; letter-spacing: .1em; text-transform: uppercase; color: var(--body-text-color-subdued); margin: 24px 0 10px; } /* ── image cards ── */ .image-card { background: var(--block-background-fill) !important; border-radius: 12px !important; border: 1px solid var(--border-color-primary) !important; overflow: hidden; box-shadow: var(--block-shadow) !important; } /* ── risk card ── */ #risk-card { border-radius: 12px; overflow: hidden; background: var(--block-background-fill); box-shadow: var(--block-shadow); border: 1px solid var(--border-color-primary); } /* ── how-it-works table ── */ .how-table { width:100%; border-collapse:collapse; font-size:0.95em; } .how-table th { background: var(--background-fill-secondary); color: var(--body-text-color); padding:14px 16px; text-align:left; border-bottom: 2px solid var(--border-color-primary); font-weight: 600; } .how-table td { padding:14px 16px; border-bottom:1px solid var(--border-color-primary); color: var(--body-text-color); } .how-table tr:last-child td { border-bottom: none; } """ # ── helpers ─────────────────────────────────────────────────────────────────── def _score_bar(score: float, colour: str) -> str: """Animated CSS progress bar.""" return f"""
""" def _stat_row(label, value, colour) -> str: return f"""{err_msg}Attention UNet segmentation · Grad-CAM explainability · ZoeDepth metric depth · AI-powered risk scoring
| Step | Component | What it does |
|---|---|---|
| 1 | Preprocessing | Resize to 512×512, convert to RGB, normalise to [0,1] |
| 2 | Attention UNet | Predicts binary flood mask: attention gates suppress irrelevant features |
| 3 | Grad-CAM | Gradient-weighted class activation map at conv2d_130: TURBO colormap with contour |
| 4 | ZoeDepth (Intel/zoedepth-nyu) | Monocular metric depth estimation: outputs depth in metres per pixel |
| 5 | Risk Engine | Combines flood coverage % + avg depth → Low / Moderate / High / Critical |