Adding a Metric¶
New metrics plug into the CLI, the report and the radar chart automatically once they are registered.
1. Implement BaseMetric¶
Create ecgen_eval/metrics/my_metric.py:
"""
My Metric for ECG evaluation.
Reference
---------
Author, A. (2025). Title. *Venue*. https://doi.org/...
"""
from __future__ import annotations
from typing import List, Optional
import numpy as np
from ecgen_eval.metrics.base import BaseMetric, MetricResult
_DESCRIPTION = "One-line description shown in reports and list-metrics."
_REFERENCE = "Author, A. (2025). Title. Venue. https://doi.org/..."
class MyMetric(BaseMetric):
name = "my_metric"
description = _DESCRIPTION
reference = _REFERENCE
higher_is_better = False
def __init__(self, n_subsample: int = 1000, seed: int = 42):
self.n_subsample = n_subsample
self.seed = seed
def compute(
self,
real: np.ndarray,
synth: np.ndarray,
lead_names: Optional[List[str]] = None,
) -> MetricResult:
self._check_inputs(real, synth) # validates (N, L, T) and lead count
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):
r = real[:, li, :]
s = synth[:, li, :]
per_lead[lname] = float(abs(r.std() - s.std()))
return MetricResult(
metric_name=self.name,
score=float(np.mean(list(per_lead.values()))),
higher_is_better=self.higher_is_better,
per_lead_scores=per_lead,
description=self.description,
reference=self.reference,
extra={},
)
Guidelines:
- Keep the constructor callable without arguments — the CLI and
list-metricsdocls(). - Use
n_subsampleand a fixedseedfor anything random or quadratic inN. - Put optional dependencies inside functions with an
ImportErrorfallback. - Put anything a plot might need (distributions, curves, sub-scores) in
extra.
2. Register it¶
In ecgen_eval/metrics/__init__.py:
from ecgen_eval.metrics.my_metric import MyMetric
AVAILABLE_METRICS = {
...
"my_metric": MyMetric,
}
__all__ = [..., "MyMetric"]
The key becomes a valid value for ecgen-eval eval -m my_metric.
3. Test it¶
Add tests in tests/test_metrics.py using the real_ecg / synth_ecg fixtures from
tests/conftest.py:
def test_my_metric(real_ecg, synth_ecg):
from ecgen_eval.metrics import MyMetric
result = MyMetric().compute(real_ecg, synth_ecg)
assert np.isfinite(result.score)
assert len(result.per_lead_scores) == real_ecg.shape[1]
def test_my_metric_identical_is_best(real_ecg):
from ecgen_eval.metrics import MyMetric
assert MyMetric().compute(real_ecg, real_ecg).score == pytest.approx(0.0, abs=1e-6)
4. Document it¶
- Add a row to the table in
docs/user-guide/metrics.mdand a section indocs/user-guide/metric-reference.md. - Add the citation to
docs/references.mdand the README. - The API reference picks up the class from its docstring automatically once it is listed in
docs/api/metrics.md.