Skip to content

baselineremove

biosigpy.ecg.baselineremove.BaselineRemoveResult

Bases: NamedTuple

Named, unpackable result of :func:baselineremove.

Source code in src/biosigpy/ecg/baselineremove.py
22
23
24
25
26
class BaselineRemoveResult(NamedTuple):
    """Named, unpackable result of :func:`baselineremove`."""

    ecg_detrended: np.ndarray
    baseline: np.ndarray

biosigpy.ecg.baselineremove.BaselineRemoveWarning

Bases: UserWarning

Structured Biosiglib diagnostic emitted by :func:baselineremove.

Source code in src/biosigpy/ecg/baselineremove.py
29
30
31
32
33
34
35
36
37
38
class BaselineRemoveWarning(UserWarning):
    """Structured Biosiglib diagnostic emitted by :func:`baselineremove`."""

    def __init__(self) -> None:
        self.warning_id = "no_valid_fiducial_positions"
        self.affected_ids = ("fiducial_positions",)
        super().__init__(
            "no_valid_fiducial_positions; "
            "affected_ids: fiducial_positions"
        )

biosigpy.ecg.baselineremove.baselineremove

baselineremove(ecg: ArrayLike, fiducial_positions: ArrayLike, offset: int, window_size: int = 5) -> BaselineRemoveResult

Remove an ECG baseline estimated at supplied fiducial positions.

Parameters:

Name Type Description Default
ecg array_like

Non-empty one-dimensional finite real ECG signal.

required
fiducial_positions array_like

Non-empty positive finite positions on the canonical one-based sample grid. Fractional, unordered, and repeated positions are accepted.

required
offset int

Nonnegative sample offset subtracted before position rounding.

required
window_size int

Positive nominal averaging-window size. Even values produce a symmetric effective span of window_size + 1 samples away from signal boundaries.

5

Returns:

Type Description
BaselineRemoveResult

Named result containing the detrended ECG and estimated baseline as one-dimensional arrays aligned with ecg.

Warns:

Type Description
BaselineRemoveWarning

If no adjusted fiducial position lies inside the signal. The original ECG and an all-zero baseline are returned.

Raises:

Type Description
TypeError

If a vector is non-numeric or complex, or a scalar is not an integer.

ValueError

If a vector is empty, has an invalid shape or nonfinite values, a scalar is outside its allowed range, or exactly one valid fiducial position remains.

Notes

Positions are rounded half away from zero after subtracting offset. Two valid positions define a line, three define a quadratic, and four or more define a cubic not-a-knot spline. The end pieces extrapolate over ECG samples outside the first and last valid positions.

Examples:

>>> result = baselineremove([2, 4, 8, 16, 32], [1, 5], 0, 2)
>>> result.baseline
array([ 3.  ,  8.25, 13.5 , 18.75, 24.  ])
Source code in src/biosigpy/ecg/baselineremove.py
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
def baselineremove(
    ecg: ArrayLike,
    fiducial_positions: ArrayLike,
    offset: int,
    window_size: int = 5,
) -> BaselineRemoveResult:
    """Remove an ECG baseline estimated at supplied fiducial positions.

    Parameters
    ----------
    ecg : array_like
        Non-empty one-dimensional finite real ECG signal.
    fiducial_positions : array_like
        Non-empty positive finite positions on the canonical one-based sample
        grid. Fractional, unordered, and repeated positions are accepted.
    offset : int
        Nonnegative sample offset subtracted before position rounding.
    window_size : int, default=5
        Positive nominal averaging-window size. Even values produce a
        symmetric effective span of ``window_size + 1`` samples away from
        signal boundaries.

    Returns
    -------
    BaselineRemoveResult
        Named result containing the detrended ECG and estimated baseline as
        one-dimensional arrays aligned with ``ecg``.

    Warns
    -----
    BaselineRemoveWarning
        If no adjusted fiducial position lies inside the signal. The original
        ECG and an all-zero baseline are returned.

    Raises
    ------
    TypeError
        If a vector is non-numeric or complex, or a scalar is not an integer.
    ValueError
        If a vector is empty, has an invalid shape or nonfinite values, a
        scalar is outside its allowed range, or exactly one valid fiducial
        position remains.

    Notes
    -----
    Positions are rounded half away from zero after subtracting ``offset``.
    Two valid positions define a line, three define a quadratic, and four or
    more define a cubic not-a-knot spline. The end pieces extrapolate over ECG
    samples outside the first and last valid positions.

    Examples
    --------
    >>> result = baselineremove([2, 4, 8, 16, 32], [1, 5], 0, 2)
    >>> result.baseline
    array([ 3.  ,  8.25, 13.5 , 18.75, 24.  ])
    """

    signal = as_real_vector(ecg, name="ecg")
    if signal.size == 0:
        raise ValueError("ecg must not be empty")
    if not np.all(np.isfinite(signal)):
        raise ValueError("ecg must contain only finite values")

    positions = as_real_vector(
        fiducial_positions, name="fiducial_positions"
    )
    if positions.size == 0:
        raise ValueError("fiducial_positions must not be empty")
    if not np.all(np.isfinite(positions)):
        raise ValueError(
            "fiducial_positions must contain only finite values"
        )
    if np.any(positions <= 0.0):
        raise ValueError("fiducial_positions must be positive")

    offset_value = as_integer_scalar(offset, name="offset")
    if offset_value < 0:
        raise ValueError("offset must be nonnegative")
    window_value = as_integer_scalar(window_size, name="window_size")
    if window_value <= 0:
        raise ValueError("window_size must be positive")

    adjusted_positions = _round_half_away_from_zero(
        positions - offset_value
    )
    valid_positions = np.unique(
        adjusted_positions[
            (adjusted_positions >= 1) &
            (adjusted_positions <= signal.size)
        ]
    ).astype(np.intp)

    if valid_positions.size == 0:
        warnings.warn(BaselineRemoveWarning(), stacklevel=2)
        return BaselineRemoveResult(signal.copy(), np.zeros_like(signal))
    if valid_positions.size == 1:
        raise ValueError("at least two valid fiducial positions are required")

    radius = window_value // 2
    fiducial_levels = np.asarray(
        [
            np.mean(
                signal[
                    max(0, int(position) - 1 - radius):
                    min(signal.size, int(position) + radius)
                ]
            )
            for position in valid_positions
        ],
        dtype=np.float64,
    )
    spline = CubicSpline(
        valid_positions.astype(np.float64),
        fiducial_levels,
        bc_type="not-a-knot",
        extrapolate=True,
    )
    sample_grid = np.arange(1, signal.size + 1, dtype=np.float64)
    baseline = np.asarray(spline(sample_grid), dtype=np.float64)
    return BaselineRemoveResult(signal - baseline, baseline)

View executable example