Spaces:
Sleeping
Sleeping
Commit Β·
b501a90
1
Parent(s): 9a9e768
Video Uploading Default
Browse files
app.py
CHANGED
|
@@ -128,6 +128,30 @@ CONFIG = {
|
|
| 128 |
"DEVICE" : "cuda:0" if torch.cuda.is_available() else "cpu",
|
| 129 |
}
|
| 130 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 131 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 132 |
# ITU LOGO β encode to base64 for embedding in HTML
|
| 133 |
# Place ITULog.png in the same directory as this script.
|
|
@@ -748,15 +772,15 @@ def _load_yolo():
|
|
| 748 |
f"YOLOv8 checkpoint not found:\n{ckpt}\n"
|
| 749 |
f"Check HF_TOKEN secret and repo MuhammadAdil63/angio-ai-checkpoints"
|
| 750 |
)
|
| 751 |
-
print(f"[
|
| 752 |
try:
|
| 753 |
import torch.serialization
|
| 754 |
from ultralytics.nn.tasks import SegmentationModel
|
| 755 |
torch.serialization.add_safe_globals([SegmentationModel])
|
| 756 |
except Exception as _e:
|
| 757 |
-
print(f"[
|
| 758 |
_yolo_model = YOLO(ckpt)
|
| 759 |
-
print(f"[
|
| 760 |
|
| 761 |
|
| 762 |
def run_yolo_seg(frame_rgb: np.ndarray, seg_conf: float) -> np.ndarray:
|
|
@@ -793,7 +817,7 @@ def run_yolo_seg(frame_rgb: np.ndarray, seg_conf: float) -> np.ndarray:
|
|
| 793 |
verbose = False,
|
| 794 |
)
|
| 795 |
except Exception as e:
|
| 796 |
-
print(f"[
|
| 797 |
return np.zeros((512, 512), dtype=np.uint8)
|
| 798 |
|
| 799 |
result = results[0]
|
|
@@ -801,7 +825,7 @@ def run_yolo_seg(frame_rgb: np.ndarray, seg_conf: float) -> np.ndarray:
|
|
| 801 |
seg_map = np.zeros((h, w), dtype=np.uint8)
|
| 802 |
|
| 803 |
if result.masks is None or result.boxes is None:
|
| 804 |
-
print("[
|
| 805 |
return seg_map
|
| 806 |
|
| 807 |
masks = result.masks.data.cpu().numpy() # (N, Hm, Wm) float32
|
|
@@ -823,9 +847,9 @@ def run_yolo_seg(frame_rgb: np.ndarray, seg_conf: float) -> np.ndarray:
|
|
| 823 |
n_written += int(binary.sum())
|
| 824 |
|
| 825 |
detected_cls = [_YOLO_CLASS_NAMES[min(int(c) + 1, 25)] for c in cls_ids]
|
| 826 |
-
print(f"[
|
| 827 |
f"vessel px: {n_written} ({100 * n_written / (h * w):.1f}%)")
|
| 828 |
-
print(f"[
|
| 829 |
return seg_map
|
| 830 |
|
| 831 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
@@ -1510,8 +1534,10 @@ with gr.Blocks(css=CSS, title="Angio AI") as demo:
|
|
| 1510 |
video_input = gr.Video(
|
| 1511 |
label="Upload XCA video (mp4 / avi / dicom-wrapped)",
|
| 1512 |
elem_id="upload-zone",
|
| 1513 |
-
height=
|
| 1514 |
)
|
|
|
|
|
|
|
| 1515 |
gr.HTML('</div>')
|
| 1516 |
|
| 1517 |
gr.HTML('<div class="card"><div class="card-title">Model controls</div>')
|
|
@@ -1540,7 +1566,7 @@ with gr.Blocks(css=CSS, title="Angio AI") as demo:
|
|
| 1540 |
<ol style="font-size:13px;color:#1a2533;line-height:1.9;padding-left:16px">
|
| 1541 |
<li>Best frame extracted from video</li>
|
| 1542 |
<li>Stenosis detection β Mask2Former</li>
|
| 1543 |
-
<li>Coronary segmentation β
|
| 1544 |
<li>Binary vessel mask β ResUNet</li>
|
| 1545 |
<li>FFR estimation β QFR v4</li>
|
| 1546 |
<li>SYNTAX score computation</li>
|
|
@@ -1592,8 +1618,8 @@ with gr.Blocks(css=CSS, title="Angio AI") as demo:
|
|
| 1592 |
|
| 1593 |
with gr.Row():
|
| 1594 |
with gr.Column():
|
| 1595 |
-
gr.HTML('<div class="card-title" style="margin-bottom:6px">Coronary segmentation (
|
| 1596 |
-
seg_out = gr.Image(label="26-class overlay", height=300)
|
| 1597 |
with gr.Column():
|
| 1598 |
gr.HTML('<div class="card-title" style="margin-bottom:6px">FFR analysis</div>')
|
| 1599 |
ffr_out = gr.Image(label="FFR pipeline output", height=300)
|
|
@@ -1616,6 +1642,11 @@ with gr.Blocks(css=CSS, title="Angio AI") as demo:
|
|
| 1616 |
def run_and_switch(video, sc, sg, px):
|
| 1617 |
return analyse(video, sc, sg, px)
|
| 1618 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1619 |
btn_analyse.click(
|
| 1620 |
fn=run_and_switch,
|
| 1621 |
inputs=[video_input, sten_conf, seg_conf, px_per_mm],
|
|
|
|
| 128 |
"DEVICE" : "cuda:0" if torch.cuda.is_available() else "cpu",
|
| 129 |
}
|
| 130 |
|
| 131 |
+
# ββ Demo videos (hosted in HF Hub model repo alongside checkpoints) ββββββββββ
|
| 132 |
+
# Upload your two XCA demo videos to MuhammadAdil63/angio-ai-checkpoints as:
|
| 133 |
+
# demo_video_1.mp4
|
| 134 |
+
# demo_video_2.mp4
|
| 135 |
+
# The buttons below download and load them automatically.
|
| 136 |
+
DEMO_VIDEO_1_NAME = "demo_video_1.mp4"
|
| 137 |
+
DEMO_VIDEO_2_NAME = "demo_video_2.mp4"
|
| 138 |
+
|
| 139 |
+
def _get_demo_video(filename: str) -> str:
|
| 140 |
+
"""Download demo video from HF Hub and return local path."""
|
| 141 |
+
try:
|
| 142 |
+
from huggingface_hub import hf_hub_download
|
| 143 |
+
path = hf_hub_download(
|
| 144 |
+
repo_id = "MuhammadAdil63/angio-ai-checkpoints",
|
| 145 |
+
filename = filename,
|
| 146 |
+
repo_type= "model",
|
| 147 |
+
token = os.environ.get("HF_TOKEN"),
|
| 148 |
+
)
|
| 149 |
+
return path
|
| 150 |
+
except Exception as e:
|
| 151 |
+
print(f"[DEMO] Could not load {filename}: {e}")
|
| 152 |
+
return None
|
| 153 |
+
|
| 154 |
+
|
| 155 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 156 |
# ITU LOGO β encode to base64 for embedding in HTML
|
| 157 |
# Place ITULog.png in the same directory as this script.
|
|
|
|
| 772 |
f"YOLOv8 checkpoint not found:\n{ckpt}\n"
|
| 773 |
f"Check HF_TOKEN secret and repo MuhammadAdil63/angio-ai-checkpoints"
|
| 774 |
)
|
| 775 |
+
print(f"[SEG] Loading checkpoint: {ckpt}")
|
| 776 |
try:
|
| 777 |
import torch.serialization
|
| 778 |
from ultralytics.nn.tasks import SegmentationModel
|
| 779 |
torch.serialization.add_safe_globals([SegmentationModel])
|
| 780 |
except Exception as _e:
|
| 781 |
+
print(f"[SEG] safe_globals warning: {_e}")
|
| 782 |
_yolo_model = YOLO(ckpt)
|
| 783 |
+
print(f"[SEG] Model loaded OK")
|
| 784 |
|
| 785 |
|
| 786 |
def run_yolo_seg(frame_rgb: np.ndarray, seg_conf: float) -> np.ndarray:
|
|
|
|
| 817 |
verbose = False,
|
| 818 |
)
|
| 819 |
except Exception as e:
|
| 820 |
+
print(f"[SEG] predict() failed: {e}")
|
| 821 |
return np.zeros((512, 512), dtype=np.uint8)
|
| 822 |
|
| 823 |
result = results[0]
|
|
|
|
| 825 |
seg_map = np.zeros((h, w), dtype=np.uint8)
|
| 826 |
|
| 827 |
if result.masks is None or result.boxes is None:
|
| 828 |
+
print("[SEG] No masks returned -- check conf threshold or checkpoint.")
|
| 829 |
return seg_map
|
| 830 |
|
| 831 |
masks = result.masks.data.cpu().numpy() # (N, Hm, Wm) float32
|
|
|
|
| 847 |
n_written += int(binary.sum())
|
| 848 |
|
| 849 |
detected_cls = [_YOLO_CLASS_NAMES[min(int(c) + 1, 25)] for c in cls_ids]
|
| 850 |
+
print(f"[SEG] Detections: {len(cls_ids)} | classes: {detected_cls} | "
|
| 851 |
f"vessel px: {n_written} ({100 * n_written / (h * w):.1f}%)")
|
| 852 |
+
print(f"[SEG] seg_map unique labels: {np.unique(seg_map)}")
|
| 853 |
return seg_map
|
| 854 |
|
| 855 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
|
|
| 1534 |
video_input = gr.Video(
|
| 1535 |
label="Upload XCA video (mp4 / avi / dicom-wrapped)",
|
| 1536 |
elem_id="upload-zone",
|
| 1537 |
+
height=220,
|
| 1538 |
)
|
| 1539 |
+
btn_demo1 = gr.Button("βΆ XCA Video Run", variant="secondary", size="sm")
|
| 1540 |
+
gr.HTML('<div style="font-size:11px;color:#7a9ab6;margin:4px 0 8px 2px">Click to load the demo XCA video, or upload your own above.</div>')
|
| 1541 |
gr.HTML('</div>')
|
| 1542 |
|
| 1543 |
gr.HTML('<div class="card"><div class="card-title">Model controls</div>')
|
|
|
|
| 1566 |
<ol style="font-size:13px;color:#1a2533;line-height:1.9;padding-left:16px">
|
| 1567 |
<li>Best frame extracted from video</li>
|
| 1568 |
<li>Stenosis detection β Mask2Former</li>
|
| 1569 |
+
<li>Coronary segmentation β 26-class anatomy labelling</li>
|
| 1570 |
<li>Binary vessel mask β ResUNet</li>
|
| 1571 |
<li>FFR estimation β QFR v4</li>
|
| 1572 |
<li>SYNTAX score computation</li>
|
|
|
|
| 1618 |
|
| 1619 |
with gr.Row():
|
| 1620 |
with gr.Column():
|
| 1621 |
+
gr.HTML('<div class="card-title" style="margin-bottom:6px">Coronary segmentation (26-class)</div>')
|
| 1622 |
+
seg_out = gr.Image(label="26-class coronary overlay", height=300)
|
| 1623 |
with gr.Column():
|
| 1624 |
gr.HTML('<div class="card-title" style="margin-bottom:6px">FFR analysis</div>')
|
| 1625 |
ffr_out = gr.Image(label="FFR pipeline output", height=300)
|
|
|
|
| 1642 |
def run_and_switch(video, sc, sg, px):
|
| 1643 |
return analyse(video, sc, sg, px)
|
| 1644 |
|
| 1645 |
+
def load_demo1():
|
| 1646 |
+
return _get_demo_video(DEMO_VIDEO_1_NAME)
|
| 1647 |
+
|
| 1648 |
+
btn_demo1.click(fn=load_demo1, inputs=[], outputs=[video_input])
|
| 1649 |
+
|
| 1650 |
btn_analyse.click(
|
| 1651 |
fn=run_and_switch,
|
| 1652 |
inputs=[video_input, sten_conf, seg_conf, px_per_mm],
|