"""
ECG Annotation Application
===========================
A lightweight Gradio tool that lets a clinician review ECG rhythm segments
(one 10-second Lead II strip at a time) and correct mislabeled rhythm
annotations (VT / SVT / Others), with comments, autosave, and resume support.
Run:
python ecg_annotation_app.py --dataset-path /path/to/test_ds
See README.md for full instructions.
"""
from __future__ import annotations
import argparse
import json
import os
from datetime import datetime, timezone
from pathlib import Path
from typing import Any
import gradio as gr
import gdown
import numpy as np
import pandas as pd
import plotly.graph_objects as go
from datasets import load_from_disk
# --------------------------------------------------------------------------
# Constants
# --------------------------------------------------------------------------
# Default source of the dataset: a Google Drive folder shared as
# "Anyone with the link" -> "Viewer". Overridable via --gdrive-url or env var.
DEFAULT_GDRIVE_URL = (
"https://drive.google.com/drive/u/1/folders/1CZK9OQzsIM0cBwWeZ9RtIdG7LmebpBr9"
)
# Rhythm columns present in the source dataset (used to compute ground truth).
GROUND_TRUTH_COLUMNS = ["VT", "SVT", "AFIB", "AFLT"]
# Labels the clinician can assign. Order here defines the "1 / 2 / 3" shortcuts.
DOCTOR_LABELS = ["VT", "SVT", "Others"]
CSV_COLUMNS = [
"sample_index",
"dataset",
"record",
"ground_truth",
"doctor_VT",
"doctor_SVT",
"doctor_Others",
"comments",
"reviewed",
"annotated_at",
]
# Injected once into
. Handles keyboard shortcuts without interfering
# with typing in the comments textbox.
KEYBOARD_JS = """
"""
CUSTOM_CSS = """
.gradio-container { max-width: 1500px !important; width: 97% !important; margin: auto !important;
padding-top: 6px !important; }
#rhythm-checkboxes label { font-size: 1.05em; }
#meta-line { font-size: 1.05em; padding: 8px 12px; border-radius: 8px;
background: rgba(128,128,128,0.08); margin-bottom: 4px; }
.gradio-container .form { gap: 6px !important; }
footer { display: none !important; }
"""
# --------------------------------------------------------------------------
# Dataset acquisition (download from Google Drive, then cache locally)
# --------------------------------------------------------------------------
# Files that mark the root of an HF `save_to_disk()` dataset directory.
_DATASET_MARKER_FILES = {"dataset_info.json", "dataset_dict.json", "state.json"}
def _is_dataset_root(path: Path) -> bool:
return any((path / marker).exists() for marker in _DATASET_MARKER_FILES)
def _find_dataset_root(path: Path) -> Path:
"""
Google Drive folder downloads sometimes wrap the dataset in an extra
directory level (e.g. downloading folder "test_ds" produces
`