"""Hugging Face Space for street-scene detection and segmentation.""" from __future__ import annotations import os import time import uuid from functools import lru_cache from pathlib import Path import gradio as gr import numpy as np import torch from PIL import Image from transformers import AutoImageProcessor, SegformerForSemanticSegmentation try: import spaces except ImportError: # `spaces` is injected by the ZeroGPU runtime. Keep local/CPU execution valid. class _SpacesFallback: @staticmethod def GPU(*_args, **_kwargs): def decorator(function): return function return decorator spaces = _SpacesFallback() from segmentation_utils import ( build_class_table, render_segmentation, resize_for_output, write_class_csv, ) from detection_utils import ( build_detection_summary, build_detection_table, build_street_indicators, render_detection, write_detection_csv, ) SEGMENTATION_MODEL_ID = "nvidia/segformer-b0-finetuned-cityscapes-1024-1024" DETECTION_MODEL_ID = "yolo26s.pt" OUTPUT_ROOT = Path("/tmp/street-scene-vision") SAMPLE_ROOT = ( "https://raw.githubusercontent.com/" "LabMingzeChen/HNIVision/main/space/examples" ) SAMPLE_IMAGES = [f"{SAMPLE_ROOT}/ubc-campus-main-mall.jpeg"] @lru_cache(maxsize=1) def load_model(): """Download once per container, then reuse the processor and model.""" torch.set_num_threads(max(1, min(4, os.cpu_count() or 1))) device = torch.device("cuda" if torch.cuda.is_available() else "cpu") processor = AutoImageProcessor.from_pretrained(SEGMENTATION_MODEL_ID) model = ( SegformerForSemanticSegmentation.from_pretrained(SEGMENTATION_MODEL_ID) .to(device) .eval() ) id2label = {int(key): value for key, value in model.config.id2label.items()} return processor, model, id2label, device @lru_cache(maxsize=1) def load_detector(): """Download YOLO26-s once per container and reuse it.""" from ultralytics import YOLO return YOLO(DETECTION_MODEL_ID) def _format_detection_indicators( detections: list[dict[str, object]], ) -> str: indicators = build_street_indicators(detections) average_confidence = ( sum(float(item["confidence"]) for item in detections) / len(detections) if detections else 0.0 ) return f""" ### Detection-based street indicators | Indicator | Visible count | |---|---:| | People | {indicators['people']} | | Active-mobility objects (`person` + `bicycle`) | {indicators['active_mobility']} | | Motor vehicles | {indicators['motor_vehicles']} | | All transport objects | {indicators['all_transport']} | | All detected objects | {len(detections)} | Average detection confidence: **{average_confidence:.2f}** > Counts describe visible COCO detections in this image. They are not traffic-flow, > occupancy, accessibility, or safety measurements. """ @spaces.GPU(duration=90) def detect_street_objects( image: Image.Image | None, confidence_threshold: float, ): """Run YOLO object detection and return visual and tabular outputs.""" if image is None: raise gr.Error("Please upload a street-scene image first.") started_at = time.perf_counter() prepared_image = resize_for_output(image) device = "cuda" if torch.cuda.is_available() else "cpu" try: detector = load_detector() predictions = detector.predict( source=np.asarray(prepared_image), conf=float(confidence_threshold), imgsz=1024, device=device, max_det=100, verbose=False, ) prediction = predictions[0] names = prediction.names detections: list[dict[str, object]] = [] if prediction.boxes is not None: coordinates = prediction.boxes.xyxy.detach().cpu().tolist() confidences = prediction.boxes.conf.detach().cpu().tolist() class_ids = prediction.boxes.cls.detach().cpu().tolist() for coordinates_row, confidence, class_id_value in zip( coordinates, confidences, class_ids, ): class_id = int(class_id_value) detections.append( { "class_id": class_id, "class_name": str(names[class_id]), "confidence": float(confidence), "x1": float(coordinates_row[0]), "y1": float(coordinates_row[1]), "x2": float(coordinates_row[2]), "y2": float(coordinates_row[3]), } ) except Exception as exc: raise gr.Error( f"Object detection failed: {type(exc).__name__}: {exc}" ) from exc overlay = render_detection(prepared_image, detections) summary_rows = build_detection_summary(detections) detection_rows = build_detection_table(detections) output_dir = OUTPUT_ROOT / uuid.uuid4().hex output_dir.mkdir(parents=True, exist_ok=True) overlay_path = output_dir / "street_object_detection_overlay.png" csv_path = output_dir / "street_object_detections.csv" overlay.save(overlay_path) write_detection_csv(csv_path, detection_rows) elapsed = time.perf_counter() - started_at visible_classes = len(summary_rows) status = ( f"Done · {prepared_image.width}×{prepared_image.height} · " f"{len(detections)} objects · {visible_classes} COCO classes · " f"{elapsed:.1f}s · device={device}" ) return ( overlay, summary_rows, detection_rows, [str(overlay_path), str(csv_path)], _format_detection_indicators(detections), status, ) @spaces.GPU(duration=90) def segment_street_scene( image: Image.Image | None, opacity: float, min_share_percent: float, ): """Run semantic segmentation and return visual, tabular, and raw outputs.""" if image is None: raise gr.Error("Please upload a street-scene image first.") started_at = time.perf_counter() prepared_image = resize_for_output(image) try: processor, model, id2label, device = load_model() inputs = processor(images=prepared_image, return_tensors="pt") inputs = {name: tensor.to(device) for name, tensor in inputs.items()} with torch.inference_mode(): outputs = model(**inputs) target_size = (prepared_image.height, prepared_image.width) class_map_tensor = processor.post_process_semantic_segmentation( outputs, target_sizes=[target_size], )[0] class_map = class_map_tensor.cpu().numpy().astype(np.uint8) except Exception as exc: raise gr.Error( f"Segmentation failed: {type(exc).__name__}: {exc}" ) from exc overlay, color_mask = render_segmentation( prepared_image, class_map, id2label, float(opacity), ) rows = build_class_table(class_map, id2label, float(min_share_percent)) output_dir = OUTPUT_ROOT / uuid.uuid4().hex output_dir.mkdir(parents=True, exist_ok=True) overlay_path = output_dir / "street_segmentation_overlay.png" mask_path = output_dir / "street_segmentation_color_mask.png" class_ids_path = output_dir / "street_segmentation_class_ids.png" csv_path = output_dir / "street_segmentation_classes.csv" overlay.save(overlay_path) color_mask.save(mask_path) Image.fromarray(class_map).save(class_ids_path) write_class_csv(csv_path, rows) elapsed = time.perf_counter() - started_at visible_classes = len(np.unique(class_map)) status = ( f"Done · {prepared_image.width}×{prepared_image.height} · " f"{visible_classes} street-scene classes · {elapsed:.1f}s · " f"device={device.type}" ) return ( overlay, color_mask, rows, [str(overlay_path), str(mask_path), str(class_ids_path), str(csv_path)], status, ) CSS = """ .gradio-container {max-width: 1260px !important;} .hero {text-align: center; margin: 0 auto 1rem;} .hero h1 {font-size: 2.1rem; margin-bottom: .3rem;} .muted {color: #64748b;} .project-links {display: flex; justify-content: center; gap: .55rem; flex-wrap: wrap; margin-top: .75rem;} .project-link { display: inline-block; padding: .42rem .78rem; border: 1px solid #d7deea; border-radius: 999px; color: inherit !important; text-decoration: none !important; background: white; font-size: .92rem; font-weight: 600; } .project-link:hover {border-color: #6366f1; box-shadow: 0 2px 8px rgba(99, 102, 241, .12);} .guide-grid {display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: .8rem; margin: .8rem 0;} .guide-card {border: 1px solid #e2e8f0; border-radius: 12px; padding: .85rem 1rem; background: rgba(255,255,255,.55);} .guide-card h3 {margin: 0 0 .35rem; font-size: 1rem;} .guide-card p {margin: 0; color: #475569; font-size: .93rem; line-height: 1.45;} @media (max-width: 760px) {.guide-grid {grid-template-columns: 1fr;}} """ with gr.Blocks(title="Street Scene Vision Toolkit", theme=gr.themes.Soft(), css=CSS) as demo: gr.Markdown( """
Map every pixel with semantic segmentation, then detect individual objects with bounding boxes.
YOLO26-s · COCO 80 objects · SegFormer-B0 · 19 Cityscapes classes · no API key required
Upload, paste, use a webcam, or select the UBC campus example.
Start with SegFormer semantic segmentation, then optionally run YOLO object detection on the same image.
Compare boxes, overlays, masks, counts, pixel shares, coordinates, and reusable CSV outputs.