Features¶
Extractor¶
FeatureExtractor
¶
FeatureExtractor(lead_for_intervals: str = 'II', rpeak_method: Optional[str] = None, delineation_method: Optional[str] = None, features: Union[None, FeatureSpec, Iterable[FeatureSpec]] = None, backend: Union[str, DelineationBackend, None] = None, backend_kwargs: Optional[Dict[str, Any]] = None)
Extract clinically meaningful ECG features from an ECGDataset.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
lead_for_intervals
|
str
|
Lead whose R peaks define the beats (RR/HR/HRV). Defaults to "II"; if not present the first available lead is used. |
'II'
|
rpeak_method
|
str or None
|
|
None
|
delineation_method
|
str or None
|
|
None
|
features
|
None, str, FeatureGroup, callable or a list of them
|
What to compute: group names ( |
None
|
backend
|
str or DelineationBackend
|
Delineation library: |
None
|
backend_kwargs
|
dict
|
Extra constructor arguments for a backend given by name. |
None
|
Outputs
beat_df : pd.DataFrame One row per (sample_idx, beat_idx, lead). Always has r_sample and rr_ms; plus the per-beat columns (intervals) and per-lead columns (amplitudes) of the selected groups.
summary_df : pd.DataFrame
One row per recording: sample_idx plus the selected feature columns.
Source code in ecgen_eval/features/extractor.py
columns
property
¶
Summary columns this extractor produces (unknown for groups without columns).
extract
¶
extract(dataset: ECGDataset) -> Tuple[DataFrame, DataFrame]
Run the feature extraction pipeline.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dataset
|
ECGDataset — shape (N, L, T).
|
|
required |
Returns:
| Type | Description |
|---|---|
(beat_df, summary_df)
|
|
Source code in ecgen_eval/features/extractor.py
extract_features
¶
extract_features(dataset: ECGDataset, lead_for_intervals: str = 'II', features: Union[None, FeatureSpec, Iterable[FeatureSpec]] = None, backend: Union[str, DelineationBackend, None] = None, **kwargs) -> Tuple[DataFrame, DataFrame]
Convenience wrapper around :class:FeatureExtractor.
Extra keyword arguments (rpeak_method, delineation_method, backend_kwargs)
are passed to :class:FeatureExtractor.
Returns:
| Type | Description |
|---|---|
(beat_df, summary_df)
|
|
Source code in ecgen_eval/features/extractor.py
Feature groups¶
groups
¶
Feature groups: what is measured from a delineated recording.
A feature group computes a few related summary_df columns from a
:class:RecordingContext (the recording plus the R peaks and fiducials found by the
delineation backend). Groups can also add per-beat or per-lead columns to beat_df.
Built-in groups
============== ==============================================================================
rhythm rr_mean_ms, rr_std_ms, hr_bpm
intervals pr_interval_ms, qrs_duration_ms, qt_interval_ms, qtc_bazett_ms,
p_wave_duration_ms
hrv hrv_sdnn_ms, hrv_rmssd_ms, hrv_pnn50
amplitude st_deviation_mv, t_amplitude_mv
============== ==============================================================================
Add your own by subclassing :class:FeatureGroup, or decorate a function with
:func:feature_group::
@feature_group("qrs_energy", columns=["qrs_energy"])
def qrs_energy(ctx):
return {"qrs_energy": float(np.sum(ctx.primary_signal ** 2))}
RecordingContext
dataclass
¶
RecordingContext(recording: ndarray, lead_names: List[str], fs: float, primary_lead_idx: int, rpeaks: ndarray, waves: Optional[Dict[str, ndarray]], cache: dict = dict())
Everything a feature group may use for one recording.
FeatureGroup
¶
Base class for a group of related features.
Subclasses set name, columns and description and implement :meth:compute.
:meth:per_beat / :meth:per_lead optionally add columns to beat_df.
compute
¶
compute(ctx: RecordingContext) -> Dict[str, float]
per_beat
¶
per_beat(ctx: RecordingContext) -> Dict[str, ndarray]
per_lead
¶
per_lead(ctx: RecordingContext) -> Dict[str, ndarray]
FunctionFeatureGroup
¶
FunctionFeatureGroup(name: str, func: Callable[[RecordingContext], Dict[str, float]], columns: Optional[Sequence[str]] = None, description: str = '')
Bases: FeatureGroup
Wrap a plain function func(ctx) -> dict as a feature group.
columns may be omitted; individual columns of such a group can then not be selected
by name, but the group can.
Source code in ecgen_eval/features/groups.py
register_feature_group
¶
register_feature_group(group: FeatureGroup, overwrite: bool = False) -> FeatureGroup
Register a :class:FeatureGroup instance so it can be selected by name.
Source code in ecgen_eval/features/groups.py
feature_group
¶
feature_group(name: str, columns: Optional[Sequence[str]] = None, description: str = '', overwrite: bool = False)
Decorator registering func(ctx) -> dict as a feature group.
Source code in ecgen_eval/features/groups.py
available_features
¶
resolve_features
¶
resolve_features(features: Union[None, FeatureSpec, Iterable[FeatureSpec]]) -> List[Tuple[FeatureGroup, Optional[List[str]]]]
Turn a feature selection into the groups to run.
features may be None / "all" (every registered group), or any mix of group
names, column names, :class:FeatureGroup instances and func(ctx) -> dict callables.
Returns:
| Type | Description |
|---|---|
list of ``(group, columns)`` — ``columns`` lists the columns of that group to keep, or is
|
|
None to keep all of them.
|
|
Source code in ecgen_eval/features/groups.py
Backends¶
backends
¶
Delineation backends: the library that finds the beats and their fiducial points.
A backend turns one recording into R peaks and a dict of fiducial arrays aligned with them
(element i belongs to beat i, NaN when missing). Feature groups
(:mod:ecgen_eval.features.groups) only read these, so any backend works with any feature.
Fiducial keys understood by the built-in feature groups:
ECG_QRS_Onsets, ECG_QRS_Offsets, ECG_P_Onsets, ECG_P_Offsets,
ECG_T_Peaks, ECG_T_Offsets.
Built-in backends
hybrid(default): NeuroKit2 R peaks, multi-lead spatial-velocity QRS boundaries, NeuroKit2 prominence P/T on every lead with tangent-method T offset (validated against GE 12SL, see :mod:ecgen_eval.features.delineation).neurokit2: plain NeuroKit2ecg_peaks+ecg_delineate(dwt,cwt,peakorprominence) on the primary lead, or every lead combined with the median.scipy: no NeuroKit2. SciPy Pan-Tompkins R peaks + spatial-velocity QRS boundaries; P and T fiducials are absent, so PR, QT, QTc and P duration are NaN.
Add your own with :func:register_backend.
DelineationBackend
¶
Base class for delineation backends.
Subclasses set name (and optionally description / requires) and implement
:meth:delineate. Constructors must be callable with keyword arguments only.
delineate
¶
delineate(recording: ndarray, fs: float, primary_lead_idx: int) -> Tuple[ndarray, Optional[Dict[str, ndarray]]]
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
recording
|
(L, T) ECG.
|
|
required |
fs
|
float
|
|
required |
primary_lead_idx
|
int — lead whose R peaks define the beats.
|
|
required |
Returns:
| Type | Description |
|---|---|
(rpeaks, waves) — sorted R-peak sample indices, and fiducial arrays aligned with them
|
|
(``None`` when the recording could not be delineated).
|
|
Source code in ecgen_eval/features/backends.py
HybridBackend
¶
Bases: DelineationBackend
Default backend: NeuroKit2 R peaks, spatial-velocity QRS, NeuroKit2 P/T on every lead.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
rpeak_method
|
str
|
|
'neurokit'
|
method
|
str
|
|
'prominence'
|
pt_waves
|
bool
|
Delineate P and T waves (needs NeuroKit2). |
True
|
Source code in ecgen_eval/features/backends.py
ScipyBackend
¶
Bases: HybridBackend
SciPy-only backend: Pan-Tompkins R peaks and spatial-velocity QRS, no P/T waves.
Source code in ecgen_eval/features/backends.py
NeuroKitBackend
¶
Bases: DelineationBackend
Plain NeuroKit2 pipeline: nk.ecg_clean → nk.ecg_peaks → nk.ecg_delineate.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
method
|
str
|
|
'dwt'
|
rpeak_method
|
str
|
|
'neurokit'
|
leads
|
str
|
|
'primary'
|
Source code in ecgen_eval/features/backends.py
register_backend
¶
register_backend(cls: Type[DelineationBackend], name: Optional[str] = None, overwrite: bool = False) -> Type[DelineationBackend]
Register a :class:DelineationBackend subclass so it can be selected by name.
Usable as a decorator::
@register_backend
class MyBackend(DelineationBackend):
name = "mylib"
def delineate(self, recording, fs, primary_lead_idx): ...
Source code in ecgen_eval/features/backends.py
get_backend
¶
get_backend(backend: Union[str, DelineationBackend, None] = None, **kwargs) -> DelineationBackend
Return a backend instance from a registered name (kwargs go to its constructor).
Source code in ecgen_eval/features/backends.py
R-peak detection and delineation¶
qrs_detection
¶
R-peak detection for ECG signals.
Primary: neurokit2.ecg_clean() + neurokit2.ecg_peaks() Fallback: scipy-based implementation in ecgen_eval.features.utils
detect_rpeaks
¶
Detect R-peak sample indices in a single-lead ECG signal.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
signal
|
np.ndarray shape (T,)
|
Single-lead ECG time series. |
required |
fs
|
float
|
Sampling frequency in Hz. |
required |
method
|
str
|
Passed to neurokit2.ecg_peaks() (e.g. |
'neurokit'
|
Returns:
| Type | Description |
|---|---|
np.ndarray of int — R-peak sample indices, sorted ascending.
|
|
Source code in ecgen_eval/features/qrs_detection.py
delineate_ecg
¶
delineate_ecg(signal: ndarray, rpeaks: ndarray, fs: float, method: str = 'prominence') -> Optional[dict]
Delineate P, QRS and T boundaries of a single-lead ECG.
Thin wrapper around :func:ecgen_eval.features.delineation.delineate_recording; returns
fiducial arrays aligned with rpeaks, or None when there are fewer than 2 R peaks.
Source code in ecgen_eval/features/qrs_detection.py
Interval features¶
interval_features
¶
Interval feature extraction: RR, HR, PR, QRS, QT, QTc, P-wave duration.
Intervals are computed per beat from the aligned fiducial points of
:func:ecgen_eval.features.delineation.delineate_recording, checked against plausible
physiological ranges, and summarised with the median over beats:
- PR = QRS onset - P onset
- QRS = QRS offset - QRS onset
- QT = T offset - QRS onset; QTc (Bazett) = QT / sqrt(mean RR in s)
- P duration = P offset - P onset
QRS onset/offset need only NumPy/SciPy; the P and T points need NeuroKit2. Features whose points are missing are NaN (there are no fixed placeholder values).
extract_rr_hr
¶
RR intervals (ms) and heart rate (bpm) from R-peak positions.
Source code in ecgen_eval/features/interval_features.py
beat_intervals
¶
Per-beat intervals (ms, NaN where missing or implausible) from aligned fiducials.
Source code in ecgen_eval/features/interval_features.py
extract_interval_features
¶
extract_interval_features(signal: ndarray, fs: float, rpeaks: ndarray, waves: Optional[dict] = None) -> Dict[str, float]
Extract PR, QRS, QT, QTc, and P-wave duration.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
signal
|
(T,) or (L, T) ECG; only used to delineate when ``waves`` is None.
|
|
required |
fs
|
float
|
|
required |
rpeaks
|
(M,) R-peak sample indices.
|
|
required |
waves
|
dict or None
|
Fiducials aligned with |
None
|
Returns:
| Type | Description |
|---|---|
dict with keys: pr_interval_ms, qrs_duration_ms, qt_interval_ms,
|
qtc_bazett_ms, p_wave_duration_ms |
Source code in ecgen_eval/features/interval_features.py
Amplitude features¶
amplitude_features
¶
Amplitude feature extraction: ST deviation, T-wave amplitude.
Both are measured per beat against the beat's baseline, the median level of the PR segment just before the QRS onset ([QRS onset - 40 ms, QRS onset - 10 ms]):
- ST deviation = signal at the J point (QRS offset) + 80 ms - baseline.
- T amplitude = signed extreme of the T wave (the sample furthest from the baseline) between J + 80 ms and the T offset (or QRS onset + min(0.6 RR, 550 ms) when the T offset is unknown).
The recording value is the median over beats. Fiducials come from
:func:ecgen_eval.features.delineation.delineate_recording, so leads of a recording share the
same beat timing.
extract_amplitude_features
¶
extract_amplitude_features(signal: ndarray, fs: float, rpeaks: ndarray, waves: Optional[dict] = None) -> Dict[str, float]
Extract ST deviation and T-wave amplitude for one lead.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
signal
|
(T,) Single-lead ECG (mV).
|
|
required |
fs
|
float
|
|
required |
rpeaks
|
(M,) R-peak sample indices.
|
|
required |
waves
|
dict or None
|
Fiducials aligned with |
None
|
Returns:
| Type | Description |
|---|---|
dict with keys: st_deviation_mv, t_amplitude_mv
|
|
Source code in ecgen_eval/features/amplitude_features.py
HRV features¶
hrv_features
¶
HRV feature extraction: SDNN, RMSSD, pNN50.
Computed directly from RR interval series; does not require neurokit2. When neurokit2 is installed it is used to cross-check, but the same manual computation is the primary path to keep the dependency optional.
extract_hrv_features
¶
Compute time-domain HRV indices from R-peak positions.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
rpeaks
|
np.ndarray Shape (M,) — sorted R-peak sample indices.
|
|
required |
fs
|
float — sampling frequency in Hz.
|
|
required |
Returns:
| Type | Description |
|---|---|
dict with keys: hrv_sdnn_ms, hrv_rmssd_ms, hrv_pnn50
|
|
Source code in ecgen_eval/features/hrv_features.py
Utilities¶
utils
¶
Shared helpers for ECG feature extraction (filtering, peak refinement).
bandpass_filter
¶
4th-order Butterworth bandpass filter. Falls back to identity if scipy absent.
Source code in ecgen_eval/features/utils.py
find_r_peaks_scipy
¶
Pan-Tompkins-inspired R-peak detector using scipy.signal.find_peaks.
Steps: bandpass (5–15 Hz) → derivative → square → moving-window integrate → find_peaks with minimum RR distance of 300 ms → refine on filtered signal. Returns array of sample indices.