Spaces:
Running on Zero
Running on Zero
| import io | |
| import spaces | |
| import numpy as np | |
| import pandas as pd | |
| import matplotlib | |
| matplotlib.use("Agg") | |
| import matplotlib.pyplot as plt | |
| from PIL import Image | |
| import torch | |
| import gradio as gr | |
| from huggingface_hub import hf_hub_download | |
| from river_model import load_trained_model, build_processor, WATER_CLASS_ID | |
| # Configuration | |
| REPO_ID = "beaunix/river-segmentation" | |
| CKPT_FILENAME = "best_model.pt" | |
| # Cyberpunk HUD palette - navy + turquoise edition | |
| BG = "#000000" | |
| PANEL = "#050912" | |
| NAVY = "#0A1C40" | |
| TURQUOISE = "#00E5D4" | |
| LIGHTBLUE = "#5FD8FF" | |
| SILVER = "#C0C0C0" | |
| GRID = "#10182a" | |
| WATER_COLOR_RGB = (95, 216, 255) # lightblue, for the mask overlay | |
| BG_COLOR_RGB = (0, 0, 0) # black fallback for all other classes | |
| # Model loading (CPU, once at startup) | |
| print("[INIT] Loading River-Segmentation model...") | |
| _ckpt_path = hf_hub_download(repo_id=REPO_ID, filename=CKPT_FILENAME) | |
| MODEL = load_trained_model(_ckpt_path) | |
| PROCESSOR = build_processor() | |
| print("[INIT] Model ready (CPU).") | |
| # Inference (single GPU allocation) | |
| def run_inference_gpu(image: Image.Image): | |
| device = "cuda" if torch.cuda.is_available() else "cpu" | |
| MODEL.to(device) | |
| MODEL.eval() | |
| orig_w, orig_h = image.size | |
| inputs = PROCESSOR(images=image, return_tensors="pt") | |
| pixel_values = inputs["pixel_values"].to(device) | |
| with torch.no_grad(): | |
| if device == "cuda": | |
| with torch.autocast(device_type="cuda", dtype=torch.float16): | |
| outputs = MODEL(pixel_values=pixel_values) | |
| else: | |
| outputs = MODEL(pixel_values=pixel_values) | |
| logits = outputs.logits | |
| logits_up = torch.nn.functional.interpolate( | |
| logits, size=(orig_h, orig_w), mode="bilinear", align_corners=False, | |
| ) | |
| pred_mask = logits_up.argmax(dim=1).squeeze(0).cpu().numpy().astype(np.uint8) | |
| MODEL.to("cpu") | |
| return pred_mask | |
| # Post-processing: collapse to binary water mask | |
| def binary_water_mask(pred_mask: np.ndarray) -> np.ndarray: | |
| return (pred_mask == WATER_CLASS_ID).astype(np.uint8) | |
| def mask_to_rgb(binary_mask: np.ndarray) -> np.ndarray: | |
| h, w = binary_mask.shape | |
| rgb = np.zeros((h, w, 3), dtype=np.uint8) | |
| rgb[binary_mask == 1] = WATER_COLOR_RGB | |
| rgb[binary_mask == 0] = BG_COLOR_RGB | |
| return rgb | |
| def build_overlay(orig_image: Image.Image, rgb_mask: np.ndarray) -> np.ndarray: | |
| orig_np = np.array(orig_image.convert("RGB")) | |
| overlay = (orig_np * 0.55 + rgb_mask * 0.45).astype(np.uint8) | |
| return overlay | |
| def build_hud_figure(orig_image, rgb_mask, overlay, water_pct): | |
| plt.rcParams.update({ | |
| "font.family": "monospace", | |
| "text.color": SILVER, | |
| "axes.edgecolor": TURQUOISE, | |
| }) | |
| fig, axes = plt.subplots(1, 3, figsize=(15, 5.2)) | |
| fig.patch.set_facecolor(BG) | |
| for ax in axes: | |
| ax.set_facecolor(PANEL) | |
| ax.axis("off") | |
| for s in ax.spines.values(): | |
| s.set_color(TURQUOISE) | |
| axes[0].imshow(np.array(orig_image.convert("RGB"))) | |
| axes[0].set_title("ORIGINAL", color=LIGHTBLUE, fontsize=11) | |
| axes[1].imshow(rgb_mask) | |
| axes[1].set_title("WATER MASK (binary)", color=LIGHTBLUE, fontsize=11) | |
| axes[2].imshow(overlay) | |
| axes[2].set_title("OVERLAY", color=LIGHTBLUE, fontsize=11) | |
| fig.suptitle( | |
| f"AEGIS-RIVER-SEGMENTATION // WATER BODY DETECTION " | |
| f"water_ratio={water_pct:.2f}%", | |
| color=TURQUOISE, fontsize=13, y=1.02 | |
| ) | |
| fig.tight_layout() | |
| buf = io.BytesIO() | |
| fig.savefig(buf, format="png", dpi=140, bbox_inches="tight", facecolor=BG) | |
| plt.close(fig) | |
| buf.seek(0) | |
| return Image.open(buf) | |
| # Main handler | |
| def analyze(image: Image.Image): | |
| empty_df = pd.DataFrame() | |
| if image is None: | |
| return "Please upload an image.", None, empty_df | |
| pred_mask = run_inference_gpu(image) | |
| water_mask = binary_water_mask(pred_mask) | |
| rgb_mask = mask_to_rgb(water_mask) | |
| overlay = build_overlay(image, rgb_mask) | |
| total_px = water_mask.size | |
| water_px = int(water_mask.sum()) | |
| water_pct = round((water_px / total_px) * 100, 2) | |
| hud_img = build_hud_figure(image, rgb_mask, overlay, water_pct) | |
| status = f"Analysis complete // Water coverage: {water_pct:.2f}%" | |
| summary_df = pd.DataFrame({ | |
| "Metric": ["Image size", "Water pixels", "Total pixels", "Water ratio (%)"], | |
| "Value": [ | |
| f"{image.size[0]} x {image.size[1]}", | |
| f"{water_px:,}", f"{total_px:,}", f"{water_pct:.2f}", | |
| ], | |
| }) | |
| return status, hud_img, summary_df | |
| # Gradio UI - Navy + Turquoise Cyberpunk HUD | |
| CSS = """ | |
| .gradio-container { background: #000000 !important; } | |
| h1, h2, h3, p, span, label { color: #00E5D4 !important; font-family: monospace !important; } | |
| .block, .form { border: 1px solid #0A1C40 !important; border-radius: 6px !important; | |
| background: #050912 !important; } | |
| .gr-button { border: 1px solid #00E5D4 !important; color: #00E5D4 !important; | |
| background: #050912 !important; font-family: monospace !important; } | |
| """ | |
| with gr.Blocks(css=CSS, title="Aegis-River-Segmentation") as demo: | |
| gr.Markdown("# AEGIS-RIVER-SEGMENTATION // WATER BODY MASK") | |
| gr.Markdown( | |
| "SegFormer-B2 semantic segmentation, collapsed to a binary water mask. " | |
| "Upload a river or waterway image (drag and drop) to extract the " | |
| "water region and its coverage ratio." | |
| ) | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| image_in = gr.Image(label="Input image", type="pil") | |
| run_btn = gr.Button("RUN ANALYSIS", variant="primary") | |
| status = gr.Markdown() | |
| hud_out = gr.Image(label="Water segmentation HUD", type="pil") | |
| summary_out = gr.Dataframe(label="Summary metrics", interactive=False) | |
| run_btn.click( | |
| fn=analyze, | |
| inputs=[image_in], | |
| outputs=[status, hud_out, summary_out], | |
| ) | |
| if __name__ == "__main__": | |
| demo.queue().launch(show_api=False, ssr_mode=False) | |