#!/usr/bin/env python3 from __future__ import annotations import os from functools import lru_cache from typing import Any import gradio as gr from huggingface_hub import hf_hub_download from ultralytics import YOLO TITLE = "Crack Detection Studio" DEFAULT_MODEL_REPO_ID = "Mezosky/cracker-yolo26l-baseline" DEFAULT_MODEL_FILENAME = "best.pt" DEFAULT_DEVICE = os.getenv("DEVICE", "cpu") DEFAULT_IMGSZ = int(os.getenv("IMGSZ", "640")) DEFAULT_CONF = float(os.getenv("CONF", "0.25")) DEFAULT_IOU = float(os.getenv("IOU", "0.45")) @lru_cache(maxsize=1) def get_model() -> tuple[YOLO, str]: model_repo_id = os.getenv("MODEL_REPO_ID", DEFAULT_MODEL_REPO_ID) model_filename = os.getenv("MODEL_FILENAME", DEFAULT_MODEL_FILENAME) model_path = hf_hub_download(repo_id=model_repo_id, filename=model_filename, repo_type="model") return YOLO(model_path), model_path def build_rows(result: Any) -> list[list[Any]]: rows: list[list[Any]] = [] boxes = getattr(result, "boxes", None) if boxes is None or len(boxes) == 0: return rows xyxy = boxes.xyxy.cpu().tolist() confs = boxes.conf.cpu().tolist() class_ids = boxes.cls.cpu().tolist() names = getattr(result, "names", {}) for idx, (box, conf, class_id) in enumerate(zip(xyxy, confs, class_ids), start=1): class_id_int = int(class_id) class_name = names.get(class_id_int, f"class_{class_id_int}") if isinstance(names, dict) else str(class_id_int) x1, y1, x2, y2 = [round(float(value), 1) for value in box] rows.append([idx, class_name, round(float(conf), 4), x1, y1, x2, y2]) return rows def infer(image, conf_threshold, iou_threshold, image_size): if image is None: return None, [], "Upload an image to start detection." model, _ = get_model() results = model.predict( source=image, conf=float(conf_threshold), iou=float(iou_threshold), imgsz=int(image_size), device=DEFAULT_DEVICE, verbose=False, ) result = results[0] rendered = result.plot() rendered_rgb = rendered[..., ::-1] rows = build_rows(result) if not rows: summary = "No crack detections found at this confidence threshold." else: avg_conf = sum(row[2] for row in rows) / len(rows) summary = f"Detected **{len(rows)} crack box(es)**. Mean confidence: **{avg_conf:.3f}**." return rendered_rgb, rows, summary def build_demo() -> gr.Blocks: css = """ .gradio-container { background: radial-gradient(1200px 600px at 5% 0%, #0f172a 0%, #111827 35%, #030712 100%); } .hero { background: linear-gradient(135deg, #0ea5e9 0%, #22d3ee 45%, #34d399 100%); border-radius: 18px; padding: 18px 22px; color: #00111a; box-shadow: 0 12px 30px rgba(34, 211, 238, 0.25); margin-bottom: 12px; } .glass { background: rgba(255, 255, 255, 0.06); border: 1px solid rgba(255, 255, 255, 0.15); border-radius: 14px; padding: 10px; } """ with gr.Blocks(title=TITLE, css=css) as demo: gr.HTML( """
Upload one image and get instant crack localization from YOLO26.