# ── angio_ai_app.py ───────────────────────────────────────────────────────────────────────────── # Angio AI — Coronary Angiography Analysis System # Muhammad Adil · MS Data Science · ITU Lahore # Supervisor: Dr. Arif Mehmood # # ── DEPLOYMENT — HuggingFace Spaces ────────────────────────────────────────── # Checkpoints are loaded from HF Hub: MuhammadAdil63/angio-ai-checkpoints # Set HF_TOKEN as a Space Secret (Settings → Secrets) for private repo access. # Files expected in that repo: # stenosis/yolov8x_seg.pt, stenosis/yolov8l_sgd.pt, stenosis/yolov8l_adamw.pt (stenosis ensemble; # loaded from ./weights/stenosis first, HF Hub as fallback) # binary_best.pth (ResUNet binary vessel segmentation) # best.pt (YOLOv8m-seg 26-class ARCADE syntax) # # ── DEPENDENCIES ──────────────────────────────────────────────────────────────────────────── # pip install gradio torch torchvision opencv-python-headless # scikit-image scipy matplotlib transformers # ultralytics (for YOLOv8m-seg inference) # ───────────────────────────────────────────────────────────────────────────── # ── Force PyTorch weights_only=False globally (must be before any imports) ─── import os as _os _os.environ["TORCH_FORCE_WEIGHTS_ONLY_LOAD"] = "0" # ── Python 3.13 audioop shim (removed from stdlib, needed by pydub/gradio) ─── import sys, types if "audioop" not in sys.modules: sys.modules["audioop"] = types.ModuleType("audioop") if "pyaudioop" not in sys.modules: sys.modules["pyaudioop"] = types.ModuleType("pyaudioop") # ── Patch gradio_client _json_schema_to_python_type (APIInfoParseError fix) ── def _patch_gradio_client(): try: import gradio_client.utils as _gcu _orig = _gcu._json_schema_to_python_type def _safe(schema, defs=None): if not isinstance(schema, dict): return "Any" try: return _orig(schema, defs) except Exception: return "Any" _gcu._json_schema_to_python_type = _safe _orig_top = _gcu.json_schema_to_python_type def _safe_top(schema, defs=None): try: return _orig_top(schema, defs) except Exception: return "Any" _gcu.json_schema_to_python_type = _safe_top except Exception: pass _patch_gradio_client() # ── PyTorch 2.7 global weights_only patch ──────────────────────────────────── # YOLO and other libraries call torch.load internally without weights_only=False. # Monkey-patch torch.load to default to weights_only=False for all calls. import torch as _torch _orig_torch_load = _torch.load def _patched_torch_load(f, map_location=None, pickle_module=None, weights_only=False, mmap=None, **kwargs): return _orig_torch_load(f, map_location=map_location, pickle_module=pickle_module, weights_only=False, **kwargs) _torch.load = _patched_torch_load import os import sys import json import base64 import tempfile import warnings warnings.filterwarnings("ignore") import cv2 import numpy as np import matplotlib matplotlib.use("Agg") import matplotlib.pyplot as plt import matplotlib.patches as mpatches from pathlib import Path import gradio as gr import torch # ── Patch torch.load for PyTorch 2.7 (weights_only=True default breaks YOLO) ─ _orig_tload = torch.load def _safe_tload(f, map_location=None, pickle_module=None, weights_only=None, mmap=None, **kwargs): return _orig_tload(f, map_location=map_location, weights_only=False, **kwargs) torch.load = _safe_tload import huggingface_hub # noqa: F401 (ensure installed; used in _hf_ckpt) # ───────────────────────────────────────────────────────────────────────────── # CONFIG — edit these paths before running # ───────────────────────────────────────────────────────────────────────────── # ── Checkpoint download from HuggingFace Hub ───────────────────────────────── # Checkpoints live in: MuhammadAdil63/angio-ai-checkpoints (private repo) # huggingface_hub reads HF_TOKEN from environment (set as a Space secret). def _hf_ckpt(filename: str) -> str: """Download checkpoint from HF Hub on first call, return local cache path.""" from huggingface_hub import hf_hub_download return hf_hub_download( repo_id = "MuhammadAdil63/angio-ai-checkpoints", filename = filename, repo_type= "model", token = os.environ.get("HF_TOKEN"), # set in Space Secrets ) CONFIG = { # ── Checkpoint paths (resolved lazily via HF Hub) ───────────────────────── # These callables are invoked once inside each model's _load_*() function. "RESUNET_CKPT" : lambda: _hf_ckpt("binary_best.pth"), "YOLO_CKPT" : lambda: _hf_ckpt("best.pt"), # ── YOLO inference params ───────────────────────────────────────────────── "YOLO_CONF" : 0.25, "YOLO_IOU" : 0.70, "YOLO_IMGSZ" : 512, # ── FFR pipeline scale (ARCADE hardcoded) ──────────────────────────────── "PX_PER_MM" : 3.75, # ── Device — CPU on HF free tier ───────────────────────────────────────── "DEVICE" : "cuda:0" if torch.cuda.is_available() else "cpu", } # ── Demo videos (hosted in HF Hub model repo alongside checkpoints) ────────── # Upload your two XCA demo videos to MuhammadAdil63/angio-ai-checkpoints as: # demo_video_1.mp4 # demo_video_2.mp4 # The buttons below download and load them automatically. DEMO_VIDEO_1_NAME = "demo_video_1.mp4" DEMO_VIDEO_2_NAME = "demo_video_2.mp4" def _get_demo_video(filename: str) -> str: """Download demo video from HF Hub and return local path.""" try: from huggingface_hub import hf_hub_download path = hf_hub_download( repo_id = "MuhammadAdil63/angio-ai-checkpoints", filename = filename, repo_type= "model", token = os.environ.get("HF_TOKEN"), ) return path except Exception as e: print(f"[DEMO] Could not load {filename}: {e}") return None # ───────────────────────────────────────────────────────────────────────────── # ITU LOGO — encode to base64 for embedding in HTML # Place ITULog.png in the same directory as this script. # ───────────────────────────────────────────────────────────────────────────── def _encode_logo(path: str) -> str: try: with open(path, "rb") as f: return "data:image/png;base64," + base64.b64encode(f.read()).decode() except FileNotFoundError: return "" LOGO_ITU = _encode_logo(str(Path(__file__).parent / "ITULog.png")) # ───────────────────────────────────────────────────────────────────────────── # CSS THEME — white / bluish, minimalist, medical # ───────────────────────────────────────────────────────────────────────────── CSS = """ /* ── Base ── */ :root { --primary: #1a6fa8; --primary-lt: #e8f3fb; --primary-mid: #4a9fd4; --accent: #0d9e6e; /* Angio AI green */ --bg: #f7fafd; --surface: #ffffff; --border: #cce0f0; --text: #1a2533; --text-sec: #5a7a96; --danger: #d64040; --warn: #d98b00; --radius: 10px; --shadow: 0 2px 12px rgba(26,111,168,0.10); } body, .gradio-container { background: var(--bg) !important; font-family: 'Segoe UI', system-ui, sans-serif !important; color: var(--text) !important; } /* ── Header bar ── */ #header { background: var(--surface); border-bottom: 2px solid var(--border); padding: 14px 28px; display: flex; align-items: center; justify-content: space-between; box-shadow: var(--shadow); margin-bottom: 0 !important; } #logo-itu img { height: 48px; } #logo-angio { font-size: 26px; font-weight: 700; color: var(--accent); letter-spacing: 1px; } #logo-angio span { font-size: 13px; color: var(--text-sec); font-weight: 400; display: block; letter-spacing: 0; } /* ── Tab bar ── */ .tab-nav button { color: var(--text-sec) !important; border-bottom: 3px solid transparent !important; font-size: 15px !important; font-weight: 500 !important; padding: 10px 24px !important; background: transparent !important; } .tab-nav button.selected { color: var(--primary) !important; border-bottom-color: var(--primary) !important; } /* ── Cards ── */ .card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 22px; box-shadow: var(--shadow); margin-bottom: 16px; } .card-title { font-size: 14px; font-weight: 600; color: var(--primary); text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 12px; padding-bottom: 8px; border-bottom: 1px solid var(--primary-lt); } /* ── Upload zone ── */ #upload-zone { border: 2px dashed var(--primary-mid) !important; border-radius: var(--radius) !important; background: var(--primary-lt) !important; min-height: 200px !important; } /* ── Sliders ── */ input[type=range] { accent-color: var(--primary); } .gradio-slider label { color: var(--text-sec) !important; font-size: 13px !important; } /* ── Buttons ── */ #btn-analyse { background: var(--primary) !important; color: white !important; border: none !important; border-radius: var(--radius) !important; font-size: 15px !important; font-weight: 600 !important; padding: 12px 32px !important; cursor: pointer !important; width: 100% !important; transition: background 0.2s !important; } #btn-analyse:hover { background: #155d90 !important; } #btn-reset { background: transparent !important; color: var(--text-sec) !important; border: 1px solid var(--border) !important; border-radius: var(--radius) !important; font-size: 13px !important; padding: 8px 20px !important; cursor: pointer !important; width: 100% !important; } /* ── Metric badges ── */ .metric-row { display: flex; gap: 12px; flex-wrap: wrap; margin-bottom: 12px; } .metric-badge { flex: 1; min-width: 110px; background: var(--primary-lt); border: 1px solid var(--border); border-radius: 8px; padding: 10px 14px; text-align: center; } .metric-badge .val { font-size: 22px; font-weight: 700; color: var(--primary); } .metric-badge .val.ischemic { color: var(--danger); } .metric-badge .val.borderline { color: var(--warn); } .metric-badge .val.ok { color: var(--accent); } .metric-badge .lbl { font-size: 11px; color: var(--text-sec); text-transform: uppercase; letter-spacing: 0.4px; } /* ── Result images ── */ #result-images img { border-radius: var(--radius); border: 1px solid var(--border); } /* ── Status ── */ #status-box { padding: 10px 16px; border-radius: 8px; font-size: 13px; font-weight: 500; border: 1px solid var(--border); background: var(--primary-lt); color: var(--primary); } /* ── Footer ── */ #footer { text-align: center; font-size: 12px; color: var(--text-sec); padding: 20px; border-top: 1px solid var(--border); margin-top: 24px; } """ # ───────────────────────────────────────────────────────────────────────────── # HEADER HTML # ───────────────────────────────────────────────────────────────────────────── def _header_html() -> str: return """
Run analysis to see results.
| # | Segment | Confidence |
|---|
weights/stenosis/yolov8x_seg.pt · yolov8l_sgd.pt · yolov8l_adamw.ptMuhammadAdil63/angio-ai-checkpointsbinary_best.pthbest.pt