Skip to content

Catalogue

ecgbench.catalogue — the 64 surveyed datasets, read from the Markdown front matter in docs/_datasets/ (shipped in the wheel as ecgbench/_datasets/). Pure Python, no heavy dependencies, cached with functools.cache.

Remember that a catalogue entry is a description: it does not imply a config exists, and status: is not a reliable signal of that either.

catalogue

Catalogue of publicly available ECG datasets.

Provides functions to list, search, and filter the curated collection of ECG datasets bundled with ECGBench. Source of truth: one Markdown file per dataset at docs/_datasets/<slug>.md (front matter holds the row fields).

No heavy dependencies — always importable.

RelatedLink(slug: str, relation: str, shares_records: bool | None = None, note: str = '', verified: bool = False, derived: bool = False)

A link from one catalogue dataset to another.

Relationships are declared once, in the front matter of either endpoint; _load derives the reverse edge and marks it derived=True. Both directions are therefore always consistent — the point of not writing them twice by hand.

shares_records is the field that matters for leakage: True means the two datasets contain the same recordings, so training on one and evaluating on the other contaminates the test set. verified says whether that overlap was checked against the actual data files, as opposed to taken from documentation.

CatalogueEntry dataclass

CatalogueEntry(slug: str, name: str, category: str, status: str, url: str, url_label: str | None, format: str, patients: str, records: str, access: str, license: str | None, origin_institution: str, origin_country: str | None, leads: int | str | None, paper_title: str | None, paper_doi: str | None, order: int = 0, search_keywords: str = '', related: tuple[RelatedLink, ...] = (), config_slug: str | None = None, raw: dict = dict())

A single dataset in the ECGBench catalogue.

Fields mirror the YAML front matter in docs/_datasets/<slug>.md.

slug and config_slug live in different namespaces: the former is the dashed Markdown filename (ptb-xl), the latter the underscored YAML config name (ptbxl). Nothing maps one to the other mechanically, so the config slug is declared in the front matter and None means the dataset has no config — a derived layer, a not-yet-implemented source, or a withdrawn one.

list_datasets

list_datasets() -> list[CatalogueEntry]

Return all datasets in the catalogue.

Note

ecgbench.metadata.list_all() returns the same datasets merged with their configs (DatasetMeta); this catalogue-only view is kept for compatibility and will be deprecated once the metadata layer is complete.

Source code in ecgbench/catalogue.py
def list_datasets() -> list[CatalogueEntry]:
    """Return all datasets in the catalogue.

    Note:
        ``ecgbench.metadata.list_all()`` returns the same datasets merged with
        their configs (``DatasetMeta``); this catalogue-only view is kept for
        compatibility and will be deprecated once the metadata layer is complete.
    """
    return list(_load())

get_dataset

get_dataset(key: str) -> CatalogueEntry | None

Look up a single dataset by slug or by exact name (case-insensitive).

Parameters:

Name Type Description Default
key str

Slug (e.g. ptb-xl) or display name.

required

Returns:

Type Description
CatalogueEntry | None

CatalogueEntry if found, None otherwise.

Note

ecgbench.metadata.get() also accepts the config slug and raises with close matches instead of returning None; this catalogue-only lookup will be deprecated once the metadata layer is complete.

Source code in ecgbench/catalogue.py
def get_dataset(key: str) -> CatalogueEntry | None:
    """Look up a single dataset by slug or by exact name (case-insensitive).

    Args:
        key: Slug (e.g. ``ptb-xl``) or display name.

    Returns:
        CatalogueEntry if found, None otherwise.

    Note:
        ``ecgbench.metadata.get()`` also accepts the config slug and raises with
        close matches instead of returning ``None``; this catalogue-only lookup
        will be deprecated once the metadata layer is complete.
    """
    key_lower = key.lower()
    for entry in _load():
        if entry.slug.lower() == key_lower or entry.name.lower() == key_lower:
            return entry
    return None

search

search(query: str | None = None, category: str | None = None, access: str | None = None, status: str | None = None) -> list[CatalogueEntry]

Search and filter datasets.

All filters are AND-combined. Each is case-insensitive substring match.

Note

ecgbench.metadata.search() searches a superset of these fields (page prose and config text included) and adds structured filters such as leads= and signal_format=; this function will be deprecated once the metadata layer is complete.

Parameters:

Name Type Description Default
query str | None

Free-text search across name, origin, format, paper, and keywords.

None
category str | None

Filter by category slug (e.g. 12-lead-physionet).

None
access str | None

Filter by access type (open | credentialed | restricted).

None
status str | None

Filter by status key (not_started, implementing, completed, needs_review, unavailable). unavailable means the source has withdrawn the data, not that work is outstanding.

None
Source code in ecgbench/catalogue.py
def search(
    query: str | None = None,
    category: str | None = None,
    access: str | None = None,
    status: str | None = None,
) -> list[CatalogueEntry]:
    """Search and filter datasets.

    All filters are AND-combined. Each is case-insensitive substring match.

    Note:
        ``ecgbench.metadata.search()`` searches a superset of these fields (page
        prose and config text included) and adds structured filters such as
        ``leads=`` and ``signal_format=``; this function will be deprecated once
        the metadata layer is complete.

    Args:
        query: Free-text search across name, origin, format, paper, and keywords.
        category: Filter by category slug (e.g. ``12-lead-physionet``).
        access: Filter by access type (``open`` | ``credentialed`` | ``restricted``).
        status: Filter by status key (``not_started``, ``implementing``,
            ``completed``, ``needs_review``, ``unavailable``). ``unavailable``
            means the source has withdrawn the data, not that work is outstanding.
    """
    results: list[CatalogueEntry] = list(_load())

    if category is not None:
        c = category.lower()
        results = [r for r in results if c in r.category.lower()]

    if access is not None:
        a = access.lower()
        results = [r for r in results if a in r.access.lower()]

    if status is not None:
        s = status.lower()
        results = [r for r in results if s == r.status.lower()]

    if query is not None:
        q = query.lower()
        results = [
            r
            for r in results
            if q in r.name.lower()
            or q in (r.origin_institution or "").lower()
            or q in (r.origin_country or "").lower()
            or q in r.format.lower()
            or q in (r.paper_title or "").lower()
            or q in (r.search_keywords or "").lower()
        ]

    return results

categories

categories() -> list[str]

Return the unique category slugs in catalogue order.

Source code in ecgbench/catalogue.py
def categories() -> list[str]:
    """Return the unique category slugs in catalogue order."""
    seen: set[str] = set()
    result: list[str] = []
    for entry in _load():
        if entry.category and entry.category not in seen:
            seen.add(entry.category)
            result.append(entry.category)
    return result

get_download_url

get_download_url(key: str) -> str | None

Look up the URL for a dataset by slug or name.

Source code in ecgbench/catalogue.py
def get_download_url(key: str) -> str | None:
    """Look up the URL for a dataset by slug or name."""
    entry = get_dataset(key)
    return entry.url if entry else None

get_config

get_config(dataset_name: str)

Load the YAML config that implements a catalogue dataset.

The catalogue slug and the config slug are unrelated strings (ptb-xl vs ptbxl, mit-bih-arrhythmia-database vs mitdb), so the mapping is declared: each front matter carries config_slug, exposed as CatalogueEntry.config_slug. dataset_name may be a catalogue slug, a display name, or a config slug.

A catalogue entry without config_slug falls back to the historical fuzzy match — lowercase, hyphens/underscores/spaces removed — against the available config slugs, and logs a warning naming the file to fix. That fallback resolved only 5 of 51 configs, which is why the field exists.

Returns DatasetConfig if found, else None.

Source code in ecgbench/catalogue.py
def get_config(dataset_name: str):
    """Load the YAML config that implements a catalogue dataset.

    The catalogue slug and the config slug are unrelated strings (``ptb-xl`` vs
    ``ptbxl``, ``mit-bih-arrhythmia-database`` vs ``mitdb``), so the mapping is
    declared: each front matter carries ``config_slug``, exposed as
    ``CatalogueEntry.config_slug``. ``dataset_name`` may be a catalogue slug, a
    display name, or a config slug.

    A catalogue entry without ``config_slug`` falls back to the historical
    fuzzy match — lowercase, hyphens/underscores/spaces removed — against the
    available config slugs, and logs a warning naming the file to fix. That
    fallback resolved only 5 of 51 configs, which is why the field exists.

    Returns ``DatasetConfig`` if found, else ``None``.
    """
    from ecgbench.config import list_available_configs, load_config

    available = list_available_configs()

    entry = get_dataset(dataset_name)
    if entry is None:
        entry = next((e for e in _load() if e.config_slug == dataset_name), None)

    if entry is not None and entry.config_slug is not None:
        if entry.config_slug not in available:
            raise ValueError(
                f"{entry.slug}: front matter declares config_slug={entry.config_slug!r}, "
                f"but no such config exists in ecgbench/data/configs/"
            )
        return load_config(entry.config_slug)

    def _normalise(s: str) -> str:
        return s.lower().replace("-", "").replace(" ", "").replace("_", "")

    targets = {_normalise(dataset_name)}
    if entry is not None:
        logger.warning(
            "docs/_datasets/%s.md has no config_slug; falling back to fuzzy matching "
            "against config filenames. Declare config_slug in its front matter.",
            entry.slug,
        )
        targets.add(_normalise(entry.slug))
        targets.add(_normalise(entry.name))

    for slug in available:
        if _normalise(slug) in targets:
            return load_config(slug)
    return None

to_dataframe

to_dataframe()

Return the catalogue as a pandas DataFrame.

Source code in ecgbench/catalogue.py
def to_dataframe():
    """Return the catalogue as a pandas DataFrame."""
    try:
        import pandas as pd
    except ImportError as err:
        raise ImportError(
            "pandas is required for to_dataframe(). "
            "Install it with: pip install ecgbench[all]"
        ) from err

    rows = [
        {
            "slug": e.slug,
            "config_slug": e.config_slug,
            "name": e.name,
            "category": e.category,
            "status": e.status,
            "url": e.url,
            "format": e.format,
            "patients": e.patients,
            "records": e.records,
            "access": e.access,
            "license": e.license,
            "origin_institution": e.origin_institution,
            "origin_country": e.origin_country,
            "leads": e.leads,
            "paper_title": e.paper_title,
            "paper_doi": e.paper_doi,
        }
        for e in _load()
    ]
    return pd.DataFrame(rows)