Skip to content

Config

ecgbench.config — the typed representation of a dataset's YAML file. Every other module takes a DatasetConfig, never a raw dict.

config

Dataset configuration system.

Every dataset is fully described by a YAML config file. This module provides the typed DatasetConfig dataclass and a loader that parses YAML into it.

CreatorInfo dataclass

CreatorInfo(type: str, name: str, url: str | None = None)

Dataset creator or contributing organisation.

StratificationConfig dataclass

StratificationConfig(method: str, mapping_source: str | None = None, superclass_column: str | None = None)

How to derive stratification labels for splitting.

ValidationConfig dataclass

ValidationConfig(expected_leads: int, expected_samples: dict[int, int], checks: list[str], amplitude_range_mv: tuple[float, float] = (-10.0, 10.0))

Quality validation settings for a dataset.

PredefinedSplitConfig dataclass

PredefinedSplitConfig(column: str, fold_mapping: dict[str, list[int]])

Describes a dataset's built-in fold assignments.

LabelFieldConfig dataclass

LabelFieldConfig(type: str = 'string', description: str = '', unit: str | None = None, vocabulary: list[str] | None = None, nullable: bool = True, example: str | None = None)

Type and meaning of one declarative label column (labels.fields.<name>).

The YAML counterpart of ecgbench.labels._fields.Field for datasets whose labels are a plain column select: type is a Frictionless Table Schema type (string, integer, number, boolean, array[string] …), vocabulary the closed set of values as strings, nullable whether a record may lack a value.

LabelConfig dataclass

LabelConfig(available: bool = True, source_csv: str | None = None, separator: str = ',', join_column: str | None = None, columns: list[str] | None = None, fields: dict[str, LabelFieldConfig] | None = None, unavailable_reason: str = '')

Where a dataset's per-record labels and metadata come from.

Exported fold CSVs are identification-only, so ground truth always lives in the source dataset. This block says which file holds it and how to join it back to the fold CSVs. Datasets needing a derivation beyond a column select (PTB-XL's SCP-to-superclass reduction, say) get a module in ecgbench/labels/ instead; the fields here still describe the source file so a missing-file error can name it.

CroissantConfig dataclass

CroissantConfig(keywords: list[str] = list(), rai_data_collection: str = '', rai_data_biases: str = '', rai_personal_sensitive_info: str = '')

Croissant (MLCommons) metadata fields.

DatasetConfig dataclass

DatasetConfig(name: str, slug: str, version: str, url: str, download_url: str | None = None, license: str = '', description: str = '', citation: str = '', doi: str = '', creators: list[CreatorInfo] = list(), signal_format: str = 'wfdb', signal_unit_scale: float = 1.0, signal_units: str = 'mV', leads: int = 12, lead_names: list[str] | None = None, alternate_lead_names: dict[int, list[str]] | None = None, record_lead_layouts: list[list[str]] | None = None, duration_seconds: float = 10.0, sampling_rates: list[int] = (lambda: [500])(), default_sampling_rate: int = 500, metadata_csv: str = '', metadata_csv_separator: str = ',', record_id_column: str = 'ecg_id', patient_id_column: str | None = None, signal_path_columns: dict[int, str] = dict(), zero_padded_identifiers: bool = False, label_column: str = '', label_format: str = 'single', stratification: StratificationConfig | None = None, has_predefined_splits: bool = False, predefined_splits: PredefinedSplitConfig | None = None, n_folds: int = 10, validation: ValidationConfig | None = None, labels: LabelConfig | None = None, publish_fold_csvs: bool = True, no_publish_reason: str = '', croissant: CroissantConfig = CroissantConfig())

Complete typed representation of a dataset YAML config.

identifier_dtypes

identifier_dtypes() -> dict[str, str]

Columns to read as strings, for pandas.read_csv(dtype=...).

Empty unless :attr:zero_padded_identifiers is set — see there for why this is opt-in rather than universal. Every read of a metadata or fold CSV goes through this, so the exported CSVs, the validation engine and ECGDataset cannot disagree about what a record is called. Unknown keys are ignored by pandas, so the result is safe to hand to any CSV whatever columns it actually has.

Source code in ecgbench/config.py
def identifier_dtypes(self) -> dict[str, str]:
    """Columns to read as strings, for ``pandas.read_csv(dtype=...)``.

    Empty unless :attr:`zero_padded_identifiers` is set — see there for why
    this is opt-in rather than universal. Every read of a metadata or fold CSV
    goes through this, so the exported CSVs, the validation engine and
    ``ECGDataset`` cannot disagree about what a record is called. Unknown keys
    are ignored by pandas, so the result is safe to hand to any CSV whatever
    columns it actually has.
    """
    if not self.zero_padded_identifiers:
        return {}
    columns = [self.record_id_column, self.patient_id_column]
    columns.extend(self.signal_path_columns.values())
    return {column: "str" for column in columns if column}

load_config

load_config(dataset_slug: str) -> DatasetConfig

Load and validate a dataset config from YAML.

Searches ecgbench/data/configs/{dataset_slug}.yaml. Parses YAML into DatasetConfig dataclass with full validation.

Raises:

Type Description
FileNotFoundError

if config YAML doesn't exist

ValueError

if required fields are missing or invalid

Source code in ecgbench/config.py
def load_config(dataset_slug: str) -> DatasetConfig:
    """Load and validate a dataset config from YAML.

    Searches ecgbench/data/configs/{dataset_slug}.yaml.
    Parses YAML into DatasetConfig dataclass with full validation.

    Raises:
        FileNotFoundError: if config YAML doesn't exist
        ValueError: if required fields are missing or invalid
    """
    config_path = _CONFIGS_DIR / f"{dataset_slug}.yaml"
    if not config_path.exists():
        available = list_available_configs()
        raise FileNotFoundError(
            f"Config not found: {config_path}. Available configs: {available}"
        )

    with open(config_path, encoding="utf-8") as f:
        raw = yaml.safe_load(f)

    if not raw or not isinstance(raw, dict):
        raise ValueError(f"Config file is empty or not a YAML mapping: {config_path}")

    # Validate required fields
    missing = [k for k in _REQUIRED_FIELDS if not raw.get(k)]
    if missing:
        raise ValueError(
            f"Config '{dataset_slug}' missing required fields: {missing}"
        )

    return DatasetConfig(
        name=raw["name"],
        slug=raw["slug"],
        version=raw["version"],
        url=raw["url"],
        download_url=raw.get("download_url"),
        license=raw.get("license", ""),
        description=raw.get("description", ""),
        citation=raw.get("citation", ""),
        doi=raw.get("doi", ""),
        creators=_parse_creators(raw.get("creators")),
        signal_format=raw.get("signal_format", "wfdb"),
        signal_unit_scale=float(raw.get("signal_unit_scale", 1.0)),
        signal_units=raw.get("signal_units", "mV"),
        leads=raw.get("leads", 12),
        lead_names=raw.get("lead_names"),
        # YAML mapping keys arrive as ints already, but a quoted "9:" would not,
        # and the lookup is by signal.shape[0].
        alternate_lead_names=(
            {int(k): list(v) for k, v in raw["alternate_lead_names"].items()}
            if raw.get("alternate_lead_names")
            else None
        ),
        record_lead_layouts=(
            [list(layout) for layout in raw["record_lead_layouts"]]
            if raw.get("record_lead_layouts")
            else None
        ),
        duration_seconds=raw.get("duration_seconds", 10.0),
        sampling_rates=raw.get("sampling_rates", [500]),
        default_sampling_rate=raw.get("default_sampling_rate", 500),
        metadata_csv=raw["metadata_csv"],
        metadata_csv_separator=raw.get("metadata_csv_separator", ","),
        record_id_column=raw["record_id_column"],
        patient_id_column=raw.get("patient_id_column"),
        signal_path_columns=_parse_signal_path_columns(raw.get("signal_path_columns")),
        zero_padded_identifiers=bool(raw.get("zero_padded_identifiers", False)),
        label_column=raw["label_column"],
        label_format=raw.get("label_format", "single"),
        stratification=_parse_stratification(raw.get("stratification")),
        has_predefined_splits=raw.get("has_predefined_splits", False),
        predefined_splits=_parse_predefined_splits(raw.get("predefined_splits")),
        n_folds=int(raw.get("n_folds", 10)),
        validation=_parse_validation(raw.get("validation")),
        labels=_parse_labels(raw.get("labels")),
        publish_fold_csvs=bool(raw.get("publish_fold_csvs", True)),
        no_publish_reason=raw.get("no_publish_reason", ""),
        croissant=_parse_croissant(raw.get("croissant")),
    )

list_available_configs

list_available_configs() -> list[str]

Return slugs of all available dataset configs.

Source code in ecgbench/config.py
def list_available_configs() -> list[str]:
    """Return slugs of all available dataset configs."""
    if not _CONFIGS_DIR.exists():
        return []
    return sorted(
        p.stem for p in _CONFIGS_DIR.glob("*.yaml")
        if not p.stem.startswith("_")
    )