import gradio as gr import requests import base64 from PIL import Image from io import BytesIO import tempfile import os API_BASE = "http://134.199.195.79:8001" def b64_to_pil(b64_str): return Image.open(BytesIO(base64.b64decode(b64_str))) def analyze_single(image): if image is None: return None, "⚠ Upload a satellite tile first." buf = BytesIO() image.save(buf, format="PNG") buf.seek(0) try: r = requests.post( f"{API_BASE}/analyze", files={"file": ("tile.png", buf, "image/png")}, timeout=300, ) r.raise_for_status() data = r.json() annotated = b64_to_pil(data["annotated_image"]) built = data.get("built_fraction") built_str = f"{built:.1%}" if built else "—" boxes = data.get("boxes_found", 0) header = f"**Built area:** {built_str} | **Urban clusters detected:** {boxes}\n\n---\n\n" return annotated, header + data["analysis"] except Exception as e: return None, f"❌ Error: {e}" def analyze_corridor(images): if not images: return [], "⚠ Upload at least 2 tiles.", None files = [] for i, img in enumerate(images): buf = BytesIO() if isinstance(img, tuple): img = img[0] if isinstance(img, str): img = Image.open(img) img.save(buf, format="PNG") files.append(("files", (f"tile_{i+1}.png", buf.getvalue(), "image/png"))) try: r = requests.post( f"{API_BASE}/corridor-report", files=files, timeout=900, ) r.raise_for_status() data = r.json() annotated_pils = [b64_to_pil(b) for b in data["annotated_images"]] md = f"## 🛰 Corridor Summary\n\n{data['corridor_summary']}\n\n---\n\n" for t in data["tiles"]: frac = t["built_fraction"] frac_str = f"{frac:.1%}" if frac else "—" md += f"### Tile {t['tile']} · Built: {frac_str}\n{t['analysis']}\n\n" gallery_output = [(img, f"Tile {i+1}") for i, img in enumerate(annotated_pils)] return gallery_output, md, data.get("pdf_path") except Exception as e: return [], f"❌ Error: {e}", None def download_pdf(pdf_path): if not pdf_path: return None try: r = requests.get( f"{API_BASE}/download-report", params={"path": pdf_path}, timeout=60, ) r.raise_for_status() tmp = tempfile.NamedTemporaryFile(suffix=".pdf", delete=False) tmp.write(r.content) tmp.flush() return tmp.name except Exception as e: return None CSS = """ @import url('https://fonts.googleapis.com/css2?family=Syne:wght@400;700;800&family=JetBrains+Mono:wght@400;500&display=swap'); :root { --bg: #060a06; --surface: #0d150d; --border: #1a2e1a; --accent: #00e676; --accent2: #69ff47; --text: #d4ecd4; --muted: #5a7a5a; --danger: #ff5252; } body, .gradio-container { background: var(--bg) !important; font-family: 'Syne', sans-serif !important; color: var(--text) !important; } .app-header { border-bottom: 1px solid var(--border); padding: 2rem 0 1.5rem; margin-bottom: 1.5rem; } .app-title { font-size: 2.2rem; font-weight: 800; color: var(--accent); letter-spacing: -0.03em; line-height: 1; margin: 0; } .app-sub { font-family: 'JetBrains Mono', monospace; font-size: 0.72rem; color: var(--muted); margin-top: 0.4rem; letter-spacing: 0.08em; } .tab-nav { background: var(--surface) !important; border: 1px solid var(--border) !important; border-radius: 6px !important; gap: 2px !important; padding: 3px !important; } .tab-nav button { font-family: 'JetBrains Mono', monospace !important; font-size: 0.78rem !important; color: var(--muted) !important; background: transparent !important; border-radius: 4px !important; padding: 6px 18px !important; letter-spacing: 0.05em; transition: all 0.15s; } .tab-nav button.selected { background: var(--accent) !important; color: #000 !important; font-weight: 700 !important; } button.primary { background: var(--accent) !important; color: #000 !important; font-family: 'JetBrains Mono', monospace !important; font-weight: 700 !important; font-size: 0.82rem !important; letter-spacing: 0.06em !important; border: none !important; border-radius: 4px !important; padding: 10px 24px !important; transition: opacity 0.15s !important; } button.primary:hover { opacity: 0.85 !important; } button.secondary { background: transparent !important; color: var(--accent) !important; font-family: 'JetBrains Mono', monospace !important; font-size: 0.78rem !important; border: 1px solid var(--accent) !important; border-radius: 4px !important; padding: 8px 20px !important; transition: all 0.15s !important; } button.secondary:hover { background: var(--accent) !important; color: #000 !important; } .panel { background: var(--surface) !important; border: 1px solid var(--border) !important; border-radius: 8px !important; } label span { font-family: 'JetBrains Mono', monospace !important; font-size: 0.72rem !important; color: var(--muted) !important; letter-spacing: 0.08em !important; text-transform: uppercase !important; } textarea, .prose { font-family: 'JetBrains Mono', monospace !important; font-size: 0.82rem !important; background: var(--bg) !important; color: var(--text) !important; border: 1px solid var(--border) !important; border-radius: 4px !important; } .footer-strip { font-family: 'JetBrains Mono', monospace; font-size: 0.68rem; color: var(--muted); border-top: 1px solid var(--border); padding-top: 1rem; margin-top: 2rem; letter-spacing: 0.04em; } """ with gr.Blocks(title="Urban Expansion Detector") as demo: pdf_path_state = gr.State(None) gr.HTML("""
🛰 URBAN EXPANSION DETECTOR
QWEN2.5-VL 72B · LORA FINE-TUNE · AMD MI300X · 8,000 SENTINEL-2 TILES
Upload 2–6 tiles along a corridor (e.g. Delhi–Meerut RRTS). Each tile is analyzed independently, then a corridor-level summary is generated.
""") corridor_in = gr.Gallery( label="INPUT — CORRIDOR TILES", type="pil", columns=3, height=260, elem_classes="panel", ) corridor_btn = gr.Button("ANALYZE CORRIDOR →", variant="primary") corridor_out_gallery = gr.Gallery( label="OUTPUT — ANNOTATED TILES", columns=3, height=300, elem_classes="panel", ) corridor_out_text = gr.Markdown( label="CORRIDOR SUMMARY", elem_classes="panel", ) with gr.Row(): pdf_btn = gr.Button("📄 EXPORT PDF REPORT", variant="secondary") pdf_out = gr.File(label="DOWNLOAD", elem_classes="panel") corridor_btn.click( analyze_corridor, inputs=[corridor_in], outputs=[corridor_out_gallery, corridor_out_text, pdf_path_state], ) pdf_btn.click( download_pdf, inputs=[pdf_path_state], outputs=[pdf_out], ) gr.HTML(""" """) demo.launch(css=CSS)