Skip to content

Metrics

Registry

AVAILABLE_METRICS maps each metric key (as used by ecgen-eval eval -m KEY) to its class.

from ecgen_eval.metrics import AVAILABLE_METRICS
metric = AVAILABLE_METRICS["mmd"]()

Base classes

MetricResult dataclass

MetricResult(metric_name: str, score: float, higher_is_better: bool = False, per_lead_scores: Dict[str, float] = dict(), extra: Dict[str, Any] = dict(), description: str = '', reference: str = '')

Standardised result returned by every metric.

Attributes:

Name Type Description
metric_name str

Short identifier (e.g. "mmd", "dtw").

score float

Primary scalar score. Convention: lower = more similar to real data, unless higher_is_better is True.

higher_is_better bool

If True, a higher score means better quality.

per_lead_scores dict

Optional per-lead breakdown {lead_name: score}.

extra dict

Any additional data the metric wants to expose (e.g. p-values, per-sample distances) for rich report rendering.

description str

One-line human-readable description of what was measured.

reference str

Citation for the metric.

BaseMetric

Bases: ABC

Abstract base class all metrics must inherit from.

Subclasses implement :meth:compute which receives two arrays: - real : np.ndarray of shape (N_r, L, T) - synth : np.ndarray of shape (N_s, L, T)

and returns a :class:MetricResult.

compute abstractmethod

compute(real: ndarray, synth: ndarray, lead_names: Optional[List[str]] = None) -> MetricResult

Compute the metric.

Parameters:

Name Type Description Default
real np.ndarray shape (N_r, L, T)
required
synth np.ndarray shape (N_s, L, T)
required
lead_names list of str
None

Returns:

Type Description
MetricResult
Source code in ecgen_eval/metrics/base.py
@abstractmethod
def compute(
    self,
    real: np.ndarray,
    synth: np.ndarray,
    lead_names: Optional[List[str]] = None,
) -> MetricResult:
    """
    Compute the metric.

    Parameters
    ----------
    real : np.ndarray  shape (N_r, L, T)
    synth : np.ndarray shape (N_s, L, T)
    lead_names : list of str, optional

    Returns
    -------
    MetricResult
    """
    ...

Fréchet utilities

Shared Fréchet Distance math used by FDMetric and FEDMetric.

FD = ‖μ_X − μ_Y‖² + Tr(Σ_X + Σ_Y − 2·(Σ_X Σ_Y)^½)

frechet_distance

frechet_distance(X: ndarray, Y: ndarray, eps: float = 1e-06) -> float

Compute the Fréchet Distance between two sets of feature vectors.

Parameters:

Name Type Description Default
X (ndarray, shape(N, D))

Feature matrix for the first distribution (e.g. real).

required
Y (ndarray, shape(M, D))

Feature matrix for the second distribution (e.g. synthetic).

required
eps float

Small value added to the covariance diagonal for numerical stability.

1e-06

Returns:

Type Description
float

Fréchet Distance (lower is better; 0 means identical distributions).

Source code in ecgen_eval/metrics/_frechet_utils.py
def frechet_distance(X: np.ndarray, Y: np.ndarray, eps: float = 1e-6) -> float:
    """
    Compute the Fréchet Distance between two sets of feature vectors.

    Parameters
    ----------
    X : np.ndarray, shape (N, D)
        Feature matrix for the first distribution (e.g. real).
    Y : np.ndarray, shape (M, D)
        Feature matrix for the second distribution (e.g. synthetic).
    eps : float
        Small value added to the covariance diagonal for numerical stability.

    Returns
    -------
    float
        Fréchet Distance (lower is better; 0 means identical distributions).
    """
    mu1, sigma1 = X.mean(axis=0), np.cov(X, rowvar=False)
    mu2, sigma2 = Y.mean(axis=0), np.cov(Y, rowvar=False)

    # Ensure 2-D even when D == 1
    if sigma1.ndim == 0:
        sigma1 = sigma1.reshape(1, 1)
    if sigma2.ndim == 0:
        sigma2 = sigma2.reshape(1, 1)

    n_feat = mu1.shape[0]
    sigma1 = sigma1 + eps * np.eye(n_feat)
    sigma2 = sigma2 + eps * np.eye(n_feat)

    diff = mu1 - mu2
    mean_sq = float(np.dot(diff, diff))

    sqrt_sigma = _matrix_sqrt(sigma1 @ sigma2)
    trace_term = float(np.trace(sigma1 + sigma2 - 2.0 * sqrt_sigma))
    return mean_sq + trace_term

_matrix_sqrt

_matrix_sqrt(M: ndarray) -> ndarray

Symmetric matrix square root via eigendecomposition.

Source code in ecgen_eval/metrics/_frechet_utils.py
def _matrix_sqrt(M: np.ndarray) -> np.ndarray:
    """Symmetric matrix square root via eigendecomposition."""
    M = (M + M.T) / 2.0
    eigenvalues, eigenvectors = np.linalg.eigh(M)
    eigenvalues = np.maximum(eigenvalues, 0.0)
    return eigenvectors @ np.diag(np.sqrt(eigenvalues)) @ eigenvectors.T

Metric classes

MMDMetric

MMDMetric(kernel_bandwidths: Optional[List[float]] = None, n_subsample: int = 1000)

Bases: BaseMetric

MMD² computed per lead on a handcrafted statistical feature vector, then averaged across leads to yield a single scalar score.

Parameters:

Name Type Description Default
kernel_bandwidths list of float

Multiplicative factors applied to the median-heuristic bandwidth. Default: [0.1, 1.0, 10.0] (multi-scale RBF).

None
n_subsample int

Maximum number of samples drawn from each dataset per lead before computing the kernel matrix (for speed). Default: 1000.

1000
Source code in ecgen_eval/metrics/mmd.py
def __init__(
    self,
    kernel_bandwidths: Optional[List[float]] = None,
    n_subsample: int = 1000,
):
    self.kernel_bandwidths = kernel_bandwidths or [0.1, 1.0, 10.0]
    self.n_subsample = n_subsample

compute

compute(real: ndarray, synth: ndarray, lead_names: Optional[List[str]] = None) -> MetricResult

Parameters:

Name Type Description Default
real ndarray
required
synth (N_s, L, T)
required
Source code in ecgen_eval/metrics/mmd.py
def compute(
    self,
    real: np.ndarray,
    synth: np.ndarray,
    lead_names: Optional[List[str]] = None,
) -> MetricResult:
    """
    Parameters
    ----------
    real  : (N_r, L, T)
    synth : (N_s, L, T)
    """
    self._check_inputs(real, synth)
    n_leads = real.shape[1]
    if lead_names is None:
        lead_names = [f"L{i+1}" for i in range(n_leads)]

    per_lead: dict = {}
    for li, lname in enumerate(lead_names):
        feats_r = self._extract_features(real[:, li, :])   # (N_r, F)
        feats_s = self._extract_features(synth[:, li, :])  # (N_s, F)

        # Subsample for speed
        feats_r = self._subsample(feats_r)
        feats_s = self._subsample(feats_s)

        mmd2 = self._mmd_squared(feats_r, feats_s)
        per_lead[lname] = float(np.maximum(mmd2, 0.0))  # clip small negatives

    overall = float(np.mean(list(per_lead.values())))

    return MetricResult(
        metric_name=self.name,
        score=overall,
        higher_is_better=self.higher_is_better,
        per_lead_scores=per_lead,
        description=self.description,
        reference=self.reference,
        extra={"per_lead_mmd2": per_lead},
    )

FDMetric

FDMetric(n_subsample: int = 2000, eps: float = 1e-06)

Bases: BaseMetric

Fréchet Distance between Gaussian fits to ECG feature vectors per lead.

Parameters:

Name Type Description Default
n_subsample int

Maximum samples per set. Default: 2000.

2000
eps float

Small regularisation term added to covariance diagonal for stability.

1e-06
Source code in ecgen_eval/metrics/fd.py
def __init__(self, n_subsample: int = 2000, eps: float = 1e-6):
    self.n_subsample = n_subsample
    self.eps = eps

FEDMetric

FEDMetric(backbone: str = 'ecgfounder', device: str = 'cpu', batch_size: int = 32, n_subsample: Optional[int] = None, eps: float = 1e-06)

Bases: BaseMetric

Fréchet ECG Distance using deep foundation-model embeddings.

Parameters:

Name Type Description Default
backbone str

Which foundation model to use as the feature extractor. One of "ecgfounder" (default), "hubert_ecg", "ecgfm".

'ecgfounder'
device str

PyTorch device string, e.g. "cpu" or "cuda".

'cpu'
batch_size int

Number of recordings per encoder forward pass.

32
n_subsample int or None

If set, randomly subsample at most this many recordings from each set before computing embeddings (useful for large datasets). Default: None (use all recordings).

None
eps float

Regularisation term added to covariance diagonal for numerical stability.

1e-06
Notes

Model weights are downloaded from HuggingFace on the first call to compute(). Set the HF_HOME environment variable to control the cache directory.

Source code in ecgen_eval/metrics/fed.py
def __init__(
    self,
    backbone: str = "ecgfounder",
    device: str = "cpu",
    batch_size: int = 32,
    n_subsample: Optional[int] = None,
    eps: float = 1e-6,
):
    self.backbone = backbone
    self.device = device
    self.batch_size = batch_size
    self.n_subsample = n_subsample
    self.eps = eps
    self._encoder = None  # loaded lazily on first compute()

compute

compute(real: ndarray, synth: ndarray, lead_names: Optional[List[str]] = None) -> MetricResult

Compute the Fréchet ECG Distance between real and synth.

Parameters:

Name Type Description Default
real (ndarray, shape(N, L, T))
required
synth (ndarray, shape(M, L, T))
required
lead_names list[str] or None

Ignored for FED (embeddings are global, not per-lead).

None

Returns:

Type Description
MetricResult

score is the scalar FED value. per_lead_scores is empty because FED operates on whole-recording embeddings.

Source code in ecgen_eval/metrics/fed.py
def compute(
    self,
    real: np.ndarray,
    synth: np.ndarray,
    lead_names: Optional[List[str]] = None,
) -> MetricResult:
    """
    Compute the Fréchet ECG Distance between *real* and *synth*.

    Parameters
    ----------
    real : np.ndarray, shape (N, L, T)
    synth : np.ndarray, shape (M, L, T)
    lead_names : list[str] or None
        Ignored for FED (embeddings are global, not per-lead).

    Returns
    -------
    MetricResult
        ``score`` is the scalar FED value.  ``per_lead_scores`` is empty
        because FED operates on whole-recording embeddings.
    """
    self._check_inputs(real, synth)

    # Lazy-load encoder (downloads weights on first call)
    if self._encoder is None:
        from ecgen_eval.metrics.encoders import get_encoder
        self._encoder = get_encoder(
            self.backbone, device=self.device, batch_size=self.batch_size
        )

    # Optional subsampling
    rng = np.random.default_rng(42)
    r = self._maybe_subsample(real, rng)
    s = self._maybe_subsample(synth, rng)

    # Extract embeddings: (N, D) and (M, D)
    emb_real = self._encoder.encode(r)
    emb_synth = self._encoder.encode(s)

    score = frechet_distance(emb_real, emb_synth, eps=self.eps)

    return MetricResult(
        metric_name=self.name,
        score=float(score),
        higher_is_better=self.higher_is_better,
        per_lead_scores={},
        description=self.description,
        reference=self.reference,
        extra={"backbone": self.backbone},
    )

SWDMetric

SWDMetric(n_projections: int = 200, n_subsample: int = 1000, seed: int = 42)

Bases: BaseMetric

SWD on a 7-dimensional statistical feature space, averaged per lead.

Parameters:

Name Type Description Default
n_projections int

Number of random 1-D projections. Default: 200.

200
n_subsample int

Max samples per lead. Default: 1000.

1000
seed int

Random seed. Default: 42.

42
Source code in ecgen_eval/metrics/swd.py
def __init__(
    self,
    n_projections: int = 200,
    n_subsample: int = 1000,
    seed: int = 42,
):
    self.n_projections = n_projections
    self.n_subsample = n_subsample
    self.seed = seed

PRDistMetric

PRDistMetric(k: int = 3, n_subsample: int = 1000, seed: int = 42)

Bases: BaseMetric

Precision/Recall for distributions, per lead.

Parameters:

Name Type Description Default
k int

k for k-NN manifold estimation. Default: 3.

3
n_subsample int

Max samples per lead. Default: 1000.

1000
seed int

Random seed. Default: 42.

42
Source code in ecgen_eval/metrics/pr_dist.py
def __init__(self, k: int = 3, n_subsample: int = 1000, seed: int = 42):
    self.k = k
    self.n_subsample = n_subsample
    self.seed = seed

DTWMetric

DTWMetric(n_pairs: int = 200, downsample: int = 5, window: float = 0.1, seed: int = 42)

Bases: BaseMetric

Mean pairwise DTW distance between random (real, synthetic) pairs per lead.

Parameters:

Name Type Description Default
n_pairs int

Number of (real, synthetic) pairs to sample. Default: 200.

200
downsample int

Take every downsample-th sample before computing DTW (speed trade-off). Default: 5 (500 Hz → 100 Hz effective, 5000 pts → 1000 pts).

5
window float

Sakoe-Chiba band as a fraction of signal length (0–1). Default: 0.1.

0.1
seed int

Random seed for reproducibility.

42
Source code in ecgen_eval/metrics/dtw.py
def __init__(
    self,
    n_pairs: int = 200,
    downsample: int = 5,
    window: float = 0.1,
    seed: int = 42,
):
    self.n_pairs = n_pairs
    self.downsample = downsample
    self.window = window
    self.seed = seed

compute

compute(real: ndarray, synth: ndarray, lead_names: Optional[List[str]] = None) -> MetricResult

Parameters:

Name Type Description Default
real ndarray
required
synth (N_s, L, T)
required
Source code in ecgen_eval/metrics/dtw.py
def compute(
    self,
    real: np.ndarray,
    synth: np.ndarray,
    lead_names: Optional[List[str]] = None,
) -> MetricResult:
    """
    Parameters
    ----------
    real  : (N_r, L, T)
    synth : (N_s, L, T)
    """
    self._check_inputs(real, synth)
    n_leads = real.shape[1]
    if lead_names is None:
        lead_names = [f"L{i+1}" for i in range(n_leads)]

    rng = np.random.default_rng(self.seed)
    r_idx = rng.integers(0, real.shape[0], size=self.n_pairs)
    s_idx = rng.integers(0, synth.shape[0], size=self.n_pairs)

    dtw_fn = self._get_dtw_fn()

    per_lead: dict = {}
    per_lead_all_dists: dict = {}

    for li, lname in enumerate(lead_names):
        dists = []
        for ri, si in zip(r_idx, s_idx):
            r_sig = real[ri, li, :: self.downsample].astype(np.float64)
            s_sig = synth[si, li, :: self.downsample].astype(np.float64)
            d = dtw_fn(r_sig, s_sig)
            dists.append(d)
        per_lead[lname] = float(np.mean(dists))
        per_lead_all_dists[lname] = [float(x) for x in dists]

    overall = float(np.mean(list(per_lead.values())))

    return MetricResult(
        metric_name=self.name,
        score=overall,
        higher_is_better=self.higher_is_better,
        per_lead_scores=per_lead,
        description=self.description,
        reference=self.reference,
        extra={
            "per_lead_dtw": per_lead,
            "per_lead_all_distances": per_lead_all_dists,
            "n_pairs": self.n_pairs,
            "downsample_factor": self.downsample,
            "window_fraction": self.window,
        },
    )

PRDMetric

PRDMetric(n_subsample: int = 500, seed: int = 42)

Bases: BaseMetric

PRD computed per lead between each real recording and its nearest synthetic neighbour in the flattened feature space, averaged over real samples.

Parameters:

Name Type Description Default
n_subsample int

Cap on number of real/synthetic samples used (for speed). Default: 500.

500
seed int

Random seed when subsampling.

42
Source code in ecgen_eval/metrics/prd.py
def __init__(self, n_subsample: int = 500, seed: int = 42):
    self.n_subsample = n_subsample
    self.seed = seed

compute

compute(real: ndarray, synth: ndarray, lead_names: Optional[List[str]] = None) -> MetricResult

Parameters:

Name Type Description Default
real ndarray
required
synth (N_s, L, T)
required
Source code in ecgen_eval/metrics/prd.py
def compute(
    self,
    real: np.ndarray,
    synth: np.ndarray,
    lead_names: Optional[List[str]] = None,
) -> MetricResult:
    """
    Parameters
    ----------
    real  : (N_r, L, T)
    synth : (N_s, L, T)
    """
    self._check_inputs(real, synth)
    n_leads = real.shape[1]
    if lead_names is None:
        lead_names = [f"L{i+1}" for i in range(n_leads)]

    rng = np.random.default_rng(self.seed)
    r = real if real.shape[0] <= self.n_subsample else real[
        rng.choice(real.shape[0], self.n_subsample, replace=False)]
    s = synth if synth.shape[0] <= self.n_subsample else synth[
        rng.choice(synth.shape[0], self.n_subsample, replace=False)]

    # For each real signal find nearest synthetic via flattened Euclidean distance
    # Compute per-lead PRD for those pairs
    r_flat = r.reshape(r.shape[0], -1).astype(np.float64)  # (Nr, L*T)
    s_flat = s.reshape(s.shape[0], -1).astype(np.float64)  # (Ns, L*T)

    # argmin_s ‖r − s‖² = argmin_s (‖s‖² − 2·r·s); ‖r‖² is constant per row.
    # Uses an (Nr, Ns) matrix instead of an (Nr, Ns, L*T) difference tensor.
    s_sq = np.einsum("ij,ij->i", s_flat, s_flat)        # (Ns,)
    sq_dist = s_sq[None, :] - 2.0 * (r_flat @ s_flat.T)  # (Nr, Ns), up to a row constant
    nn_idx = np.argmin(sq_dist, axis=1)                  # (Nr,)

    nn_synth = s[nn_idx]  # (Nr, L, T)

    per_lead: dict = {}
    for li, lname in enumerate(lead_names):
        r_lead = r[:, li, :]          # (Nr, T)
        s_lead = nn_synth[:, li, :]   # (Nr, T)
        num = np.sqrt(np.sum((r_lead - s_lead) ** 2, axis=-1))
        den = np.sqrt(np.sum(r_lead ** 2, axis=-1)) + 1e-9
        prd_per_sample = 100.0 * num / den
        per_lead[lname] = float(np.mean(prd_per_sample))

    overall = float(np.mean(list(per_lead.values())))

    return MetricResult(
        metric_name=self.name,
        score=overall,
        higher_is_better=self.higher_is_better,
        per_lead_scores=per_lead,
        description=self.description,
        reference=self.reference,
    )

MAEMetric

MAEMetric(n_subsample: int = 500, seed: int = 42)

Bases: BaseMetric

Nearest-neighbour MAE and RMSE per lead.

Parameters:

Name Type Description Default
n_subsample int

Max samples per lead for speed. Default: 500.

500
seed int

Random seed. Default: 42.

42
Source code in ecgen_eval/metrics/mae.py
def __init__(self, n_subsample: int = 500, seed: int = 42):
    self.n_subsample = n_subsample
    self.seed = seed

PSNRMetric

PSNRMetric(n_subsample: int = 500, seed: int = 42)

Bases: BaseMetric

Nearest-neighbour PSNR averaged per lead and across leads.

Parameters:

Name Type Description Default
n_subsample int

Maximum samples drawn from each dataset per lead. Default: 500.

500
seed int

Random seed for subsampling. Default: 42.

42
Source code in ecgen_eval/metrics/psnr.py
def __init__(self, n_subsample: int = 500, seed: int = 42):
    self.n_subsample = n_subsample
    self.seed = seed

SSIMMetric

SSIMMetric(k1: float = 0.01, k2: float = 0.03, n_subsample: int = 500, seed: int = 42)

Bases: BaseMetric

Global 1-D SSIM averaged per lead.

Parameters:

Name Type Description Default
k1 float

Stability constants. Default: 0.01, 0.03 (standard values).

0.01
k2 float

Stability constants. Default: 0.01, 0.03 (standard values).

0.01
n_subsample int

Max samples per lead for speed. Default: 500.

500
seed int

Random seed. Default: 42.

42
Source code in ecgen_eval/metrics/ssim.py
def __init__(
    self,
    k1: float = 0.01,
    k2: float = 0.03,
    n_subsample: int = 500,
    seed: int = 42,
):
    self.k1 = k1
    self.k2 = k2
    self.n_subsample = n_subsample
    self.seed = seed

SQIMetric

SQIMetric(n_subsample: int = 1000, seed: int = 42)

Bases: BaseMetric

Template-correlation SQI per lead.

Parameters:

Name Type Description Default
n_subsample int

Max samples per lead. Default: 1000.

1000
seed int

Random seed. Default: 42.

42
Source code in ecgen_eval/metrics/sqi.py
def __init__(self, n_subsample: int = 1000, seed: int = 42):
    self.n_subsample = n_subsample
    self.seed = seed

PSDMetric

PSDMetric(fs: float = 500.0, nperseg: Optional[int] = None, n_subsample: int = 1000)

Bases: BaseMetric

Jensen-Shannon divergence between mean normalised PSDs per lead.

Parameters:

Name Type Description Default
fs float

Sampling frequency in Hz. Default: 500.

500.0
nperseg int

Welch segment length. Default: min(256, T).

None
n_subsample int

Cap on samples used per set. Default: 1000.

1000
Source code in ecgen_eval/metrics/psd.py
def __init__(
    self,
    fs: float = 500.0,
    nperseg: Optional[int] = None,
    n_subsample: int = 1000,
):
    self.fs = fs
    self.nperseg = nperseg
    self.n_subsample = n_subsample

compute

compute(real: ndarray, synth: ndarray, lead_names: Optional[List[str]] = None) -> MetricResult

Parameters:

Name Type Description Default
real ndarray
required
synth (N_s, L, T)
required
Source code in ecgen_eval/metrics/psd.py
def compute(
    self,
    real: np.ndarray,
    synth: np.ndarray,
    lead_names: Optional[List[str]] = None,
) -> MetricResult:
    """
    Parameters
    ----------
    real  : (N_r, L, T)
    synth : (N_s, L, T)
    """
    self._check_inputs(real, synth)
    n_leads = real.shape[1]
    if lead_names is None:
        lead_names = [f"L{i+1}" for i in range(n_leads)]

    rng = np.random.default_rng(42)
    r = real if real.shape[0] <= self.n_subsample else real[
        rng.choice(real.shape[0], self.n_subsample, replace=False)]
    s = synth if synth.shape[0] <= self.n_subsample else synth[
        rng.choice(synth.shape[0], self.n_subsample, replace=False)]

    psd_fn = self._make_psd_fn(real.shape[-1])

    per_lead: dict = {}
    per_lead_psd_real: dict = {}
    per_lead_psd_synth: dict = {}

    for li, lname in enumerate(lead_names):
        psd_r = np.stack([psd_fn(r[i, li, :]) for i in range(r.shape[0])], axis=0)
        psd_s = np.stack([psd_fn(s[i, li, :]) for i in range(s.shape[0])], axis=0)

        mean_r = np.mean(psd_r, axis=0)
        mean_s = np.mean(psd_s, axis=0)

        jsd = self._js_divergence(mean_r, mean_s)
        per_lead[lname] = float(jsd)
        per_lead_psd_real[lname] = mean_r.tolist()
        per_lead_psd_synth[lname] = mean_s.tolist()

    overall = float(np.mean(list(per_lead.values())))

    return MetricResult(
        metric_name=self.name,
        score=overall,
        higher_is_better=self.higher_is_better,
        per_lead_scores=per_lead,
        description=self.description,
        reference=self.reference,
        extra={
            "per_lead_psd_real": per_lead_psd_real,
            "per_lead_psd_synth": per_lead_psd_synth,
        },
    )

SpectralEntropyMetric

SpectralEntropyMetric(fs: float = 500.0, nperseg: Optional[int] = None, n_subsample: int = 1000, seed: int = 42)

Bases: BaseMetric

Spectral entropy distribution comparison per lead.

Parameters:

Name Type Description Default
fs float

Sampling frequency in Hz. Default: 500.

500.0
nperseg int or None

Welch segment length. None → min(256, T). Default: None.

None
n_subsample int

Max samples per lead. Default: 1000.

1000
seed int

Random seed. Default: 42.

42
Source code in ecgen_eval/metrics/spectral_entropy.py
def __init__(
    self,
    fs: float = 500.0,
    nperseg: Optional[int] = None,
    n_subsample: int = 1000,
    seed: int = 42,
):
    self.fs = fs
    self.nperseg = nperseg
    self.n_subsample = n_subsample
    self.seed = seed

HRVMetric

HRVMetric(fs: float = 500.0, n_subsample: int = 1000, seed: int = 42)

Bases: BaseMetric

HRV statistics comparison per lead.

Parameters:

Name Type Description Default
fs float

Sampling frequency in Hz. Default: 500.

500.0
n_subsample int

Max samples per lead. Default: 1000.

1000
seed int

Random seed. Default: 42.

42
Source code in ecgen_eval/metrics/hrv.py
def __init__(self, fs: float = 500.0, n_subsample: int = 1000, seed: int = 42):
    self.fs = fs
    self.n_subsample = n_subsample
    self.seed = seed

HeartRateMetric

HeartRateMetric(fs: float = 500.0, n_subsample: int = 1000, seed: int = 42)

Bases: BaseMetric

Heart-rate distribution comparison per lead.

Parameters:

Name Type Description Default
fs float

Sampling frequency in Hz. Default: 500.

500.0
n_subsample int

Max samples per lead. Default: 1000.

1000
seed int

Random seed. Default: 42.

42
Source code in ecgen_eval/metrics/heart_rate.py
def __init__(self, fs: float = 500.0, n_subsample: int = 1000, seed: int = 42):
    self.fs = fs
    self.n_subsample = n_subsample
    self.seed = seed

NNDistMetric

NNDistMetric(n_subsample: int = 1000, seed: int = 42)

Bases: BaseMetric

NND and Authenticity computed per lead on a 7-feature statistical space.

Parameters:

Name Type Description Default
n_subsample int

Max samples per lead. Default: 1000.

1000
seed int

Random seed. Default: 42.

42
Source code in ecgen_eval/metrics/nn_dist.py
def __init__(self, n_subsample: int = 1000, seed: int = 42):
    self.n_subsample = n_subsample
    self.seed = seed