MohitML10's picture
Update app.py
5a5e8d2 verified
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("""
<div class="app-header">
<p class="app-title">🛰 URBAN EXPANSION DETECTOR</p>
<p class="app-sub">
QWEN2.5-VL 72B · LORA FINE-TUNE · AMD MI300X · 8,000 SENTINEL-2 TILES
</p>
</div>
""")
with gr.Tabs():
with gr.Tab("SINGLE TILE"):
with gr.Row(equal_height=True):
with gr.Column(scale=1, elem_classes="panel"):
single_in = gr.Image(
label="INPUT — SATELLITE TILE",
type="pil",
height=340,
)
single_btn = gr.Button("ANALYZE →", variant="primary")
with gr.Column(scale=1, elem_classes="panel"):
single_out_img = gr.Image(
label="OUTPUT — ANNOTATED",
height=340,
interactive=False,
)
single_out_text = gr.Textbox(
label="MODEL ANALYSIS",
lines=7,
elem_classes="panel",
)
single_btn.click(
analyze_single,
inputs=[single_in],
outputs=[single_out_img, single_out_text],
)
with gr.Tab("CORRIDOR ANALYSIS"):
gr.HTML("""
<p style="font-family:'JetBrains Mono',monospace;font-size:0.78rem;
color:#5a7a5a;margin-bottom:1rem;">
Upload 2–6 tiles along a corridor (e.g. Delhi–Meerut RRTS).
Each tile is analyzed independently, then a corridor-level summary is generated.
</p>
""")
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("""
<div class="footer-strip">
MODEL · MohitML10/urban-expansion-detector-72b-v3 &nbsp;·&nbsp;
HARDWARE · AMD MI300X 192GB HBM3 &nbsp;·&nbsp;
DATA · 8,000 SENTINEL-2 TILES (437 INDIA) &nbsp;·&nbsp;
USE CASE · URBAN PLANNING · RRTS FEASIBILITY
</div>
""")
demo.launch(css=CSS)