Source code for biopsykit.signals.ecg.event_extraction._q_peak_forounzafar2018

import numpy as np
import pandas as pd
from tpcp import Parameter

from biopsykit.signals._base_extraction import HANDLE_MISSING_EVENTS, CanHandleMissingEventsMixin
from biopsykit.signals.ecg.event_extraction._base_ecg_extraction import BaseEcgExtractionWithHeartbeats
from biopsykit.utils.array_handling import sanitize_input_series

__all__ = ["QPeakExtractionForouzanfar2018"]

from biopsykit.utils.dtypes import (
    EcgRawDataFrame,
    HeartbeatSegmentationDataFrame,
    is_ecg_raw_dataframe,
    is_heartbeat_segmentation_dataframe,
    is_q_peak_dataframe,
)


[docs]class QPeakExtractionForouzanfar2018(BaseEcgExtractionWithHeartbeats, CanHandleMissingEventsMixin): r"""Q-peak extraction algorithm by Forouzanfar et al. (2018). This algorithm detects the Q-peak of an ECG signal based on the last sample before the R-peak that is below a certain threshold (:math:`-1.2 \cdot R_peak/f_s`), where R_peak is the amplitude of the R-peak and f_s is the sampling frequency of the ECG signal. In this implementation, a *scaling_factor* is used instead of *f_s* since the sampling rates of the datasets can differ from the original publication (2000 Hz). Thus, the scaling_factor is set to the sampling rate of the original publication (2000 Hz) by default. For more information on the algorithm, see [For18]_. References ---------- .. [For18] Forouzanfar, M., Baker, F. C., De Zambotti, M., McCall, C., Giovangrandi, L., & Kovacs, G. T. A. (2018). Toward a better noninvasive assessment of preejection period: A novel automatic algorithm for B-point detection and correction on thoracic impedance cardiogram. Psychophysiology, 55(8), e13072. https://doi.org/10.1111/psyp.13072 """ scaling_factor: Parameter[float] def __init__(self, scaling_factor: float = 2000, handle_missing_events: HANDLE_MISSING_EVENTS = "warn"): """Initialize new ``QPeakExtractionForouzanfar2018`` algorithm instance. Parameters ---------- scaling_factor : float, optional Scaling factor for the threshold used to detect the Q-peak. Default: 2000 handle_missing_events : one of {"warn", "raise", "ignore"}, optional How to handle missing data in the input dataframes. Default: "warn" """ super().__init__(handle_missing_events=handle_missing_events) self.scaling_factor = scaling_factor # @make_action_safe
[docs] def extract( self, *, ecg: EcgRawDataFrame, heartbeats: HeartbeatSegmentationDataFrame, sampling_rate_hz: float, # noqa: ARG002 ): """Extract Q-peaks from given ECG signal. The results are saved in the ``points_`` attribute of the super class. Parameters ---------- ecg: :class:`~pandas.DataFrame` ECG signal heartbeats: :class:`~pandas.DataFrame` DataFrame containing one row per segmented heartbeat, each row contains start, end, and R-peak location (in samples from beginning of signal) of that heartbeat, index functions as id of heartbeat sampling_rate_hz: float Sampling rate of ECG signal in hz Returns ------- self Raises ------ :exc:`~biopsykit.utils.exceptions.EventExtractionError` If the event extraction fails and ``handle_missing`` is set to "raise" """ self._check_valid_missing_handling() is_ecg_raw_dataframe(ecg) is_heartbeat_segmentation_dataframe(heartbeats) ecg = sanitize_input_series(ecg, name="ecg") ecg = ecg.squeeze() # result df q_peaks = pd.DataFrame(index=heartbeats.index, columns=["q_peak_sample", "nan_reason"]) # search Q-peak for each heartbeat of the given signal for idx, data in heartbeats.iterrows(): heartbeat_start = data["start_sample"] r_peak_sample = data["r_peak_sample"] # set an individual threshold for detecting the Q-peaks based on the R-peak threshold = (-1.2 * ecg.iloc[r_peak_sample]) / self.scaling_factor # search for the Q-peak as the last sample before the R-peak that is below the threshold ecg_before_r_peak = ecg.iloc[heartbeat_start:r_peak_sample].reset_index(drop=True) ecg_below = np.where(ecg_before_r_peak < threshold)[0] if len(ecg_below) == 0: q_peaks.loc[idx, "q_peak_sample"] = np.nan q_peaks.loc[idx, "nan_reason"] = "no_value_below_threshold" continue q_peak_sample = heartbeat_start + ecg_below[-1] q_peaks.loc[idx, "q_peak_sample"] = q_peak_sample q_peaks = q_peaks.astype({"q_peak_sample": "Int64", "nan_reason": "object"}) is_q_peak_dataframe(q_peaks) self.points_ = q_peaks return self