Exemple #1
0
def correct_peaks(peaks, extra_correction=True, missed_correction=True,
                  short_correction=True, long_correction=True,
                  ectopic_correction=True):
    """Correct long, short, extra, missed and ectopic beats in peaks vector.

    Parameters
    ----------
    peaks : 1d array-like
        Boolean vector of peaks.

    Returns
    -------
    correction : dictionnary
        The corrected RR time series and the number of artefacts corrected:

        * clean_peaks: 1d array-like
            The corrected boolean time-serie.
        * ectopic: int
            The number of ectopic beats corrected.
        * short: int
            The number of short beats corrected.
        * long: int
            The number of long beats corrcted.
        * extra: int
            The number of extra beats corrected.
        * missed: int
            The number of missed beats corrected.
    """
    if isinstance(peaks, list):
        peaks = np.asarray(peaks, dtype=bool)

    clean_peaks = peaks.copy()
    nEctopic, nShort, nLong, nExtra, nMissed = 0, 0, 0, 0, 0

    artefacts = rr_artefacts(np.diff(np.where(clean_peaks)[0]))

    # Correct missed beats
    if missed_correction:
        if np.any(artefacts['missed']):
            for this_id in np.where(artefacts['missed'])[0]:
                this_id += nMissed
                clean_peaks = correct_missed_peaks(clean_peaks, this_id)
                nMissed += 1
        artefacts = rr_artefacts(np.diff(np.where(clean_peaks)[0]))

    # Correct extra beats
    if extra_correction:
        if np.any(artefacts['extra']):
            for this_id in np.where(artefacts['extra'])[0]:
                this_id -= nExtra
                clean_peaks = correct_extra_peaks(clean_peaks, this_id)
                nExtra += 1
        artefacts = rr_artefacts(np.diff(np.where(clean_peaks)[0]))

    return {'clean_peaks': clean_peaks, 'ectopic': nEctopic, 'short': nShort,
            'long': nLong, 'extra': nExtra, 'missed': nMissed}
Exemple #2
0
plot1 = plot_timedomain(rr)
plotly.io.show(plot1)

#%%
# Frequency domain
# ----------------
plot2 = plot_frequency(rr)
plotly.io.show(plot2)

#%%
# Nonlinear domain
# ----------------
plot3 = plot_nonlinear(rr)
plotly.io.show(plot3)

#%%
# Artefact detection
# ------------------

artefacts = rr_artefacts(rr)

#%%
# Subspaces visualization
# -----------------------
# You can visualize the two main subspaces and spot outliers. The left pamel
# plot subspaces that are more sensitive to ectopic beats detection. The right
# panel plot subspaces that will be more sensitive to long or short beats,
# comprizing the extra and missed beats.

plot_subspaces(rr)
Exemple #3
0
 def test_rr_artefacts(self):
     rr = simulate_rr()  # Import PPG recording
     artefacts = rr_artefacts(rr)
     artefacts = rr_artefacts(list(rr))
     assert all(350 == x
                for x in [len(artefacts[k]) for k in artefacts.keys()])
Exemple #4
0
def plot_subspaces(x, c1=.17, c2=.13, xlim=10, ylim=5, ax=None):
    """Plot hrv subspace as described by Lipponen & Tarvainen (2019).

    Parameters
    ----------
    x : 1d array-like
        Array of RR intervals or subspace1. If subspace1 is provided, subspace2
        and 3 must also be provided.
    c1 : float
        Fixed variable controling the slope of the threshold lines. Default is
        0.13.
    c2 : float
        Fixed variable controling the intersect of the threshold lines. Default
        is 0.17.
    xlim : int
        Absolute range of the x axis. Default is 10.
    ylim : int
        Absolute range of the y axis. Default is 5.
    ax : `Matplotlib.Axes` or None
        Where to draw the plot. Default is *None* (create a new figure).

    Returns
    -------
    ax : `Matplotlib.Axes`
        The figure.

    References
    ----------
    [1] Lipponen, J. A., & Tarvainen, M. P. (2019). A robust algorithm for
        heart rate variability time series artefact correction using novel beat
        classification. Journal of Medical Engineering & Technology, 43(3),
        173–181. https://doi.org/10.1080/03091902.2019.1640306
    """
    if not isinstance(x, (np.ndarray, np.generic)):
        x = np.asarray(x)
    artefacts = rr_artefacts(x)

    # Rescale to show outlier in scatterplot
    if xlim is not None:
        artefacts['subspace1'][artefacts['subspace1'] < -xlim] = -xlim
        artefacts['subspace1'][artefacts['subspace1'] > xlim] = xlim
    if ylim is not None:
        artefacts['subspace2'][artefacts['subspace2'] < -ylim] = -ylim
        artefacts['subspace2'][artefacts['subspace2'] > ylim] = ylim

        artefacts['subspace3'][artefacts['subspace3'] < -ylim*2] = -ylim*2
        artefacts['subspace3'][artefacts['subspace3'] > ylim*2] = ylim*2

    # Filter for normal beats
    normalBeats = ((~artefacts['ectopic']) & (~artefacts['short']) &
                   (~artefacts['long']) & (~artefacts['missed']) &
                   (~artefacts['extra']))

    #############
    # First panel
    #############

    if ax is None:
        fig, ax = plt.subplots(1, 2, figsize=(10, 5))

    # Plot normal beats
    ax[0].scatter(artefacts['subspace1'][normalBeats],
                  artefacts['subspace2'][normalBeats],
                  color='gray', edgecolors='k', s=15,
                  alpha=0.2, zorder=10, label='Normal')

    # Plot outliers
    ax[0].scatter(artefacts['subspace1'][artefacts['ectopic']],
                  artefacts['subspace2'][artefacts['ectopic']],
                  color='r', edgecolors='k', zorder=10, label='Ectopic')
    ax[0].scatter(artefacts['subspace1'][artefacts['short']],
                  artefacts['subspace2'][artefacts['short']],
                  color='b', edgecolors='k', zorder=10,
                  marker='s', label='Short')
    ax[0].scatter(artefacts['subspace1'][artefacts['long']],
                  artefacts['subspace2'][artefacts['long']],
                  color='g', edgecolors='k', zorder=10,
                  marker='s', label='Long')
    ax[0].scatter(artefacts['subspace1'][artefacts['missed']],
                  artefacts['subspace2'][artefacts['missed']],
                  color='g', edgecolors='k', zorder=10, label='Missed')
    ax[0].scatter(artefacts['subspace1'][artefacts['extra']],
                  artefacts['subspace2'][artefacts['extra']],
                  color='b', edgecolors='k', zorder=10, label='Extra')
    # Upper area
    def f1(x): return -c1*x + c2
    ax[0].plot([-1, -10], [f1(-1), f1(-10)], 'k', linewidth=1, linestyle='--')
    ax[0].plot([-1, -1], [f1(-1), 10], 'k', linewidth=1, linestyle='--')
    x = [-10, -10, -1, -1]
    y = [f1(-10), 10, 10, f1(-1)]
    ax[0].fill(x, y, color='gray', alpha=0.3)

    # Lower area
    def f2(x): return -c1*x - c2
    ax[0].plot([1, 10], [f2(1), f2(10)], 'k', linewidth=1, linestyle='--')
    ax[0].plot([1, 1], [f2(1), -10], 'k', linewidth=1, linestyle='--')
    x = [1, 1, 10, 10]
    y = [f2(1), -10, -10, f2(10)]
    ax[0].fill(x, y, color='gray', alpha=0.3)

    ax[0].set_xlabel('Subspace $S_{11}$')
    ax[0].set_ylabel('Subspace $S_{12}$')
    ax[0].set_ylim(-ylim, ylim)
    ax[0].set_xlim(-xlim, xlim)
    ax[0].set_title('Subspace 1 \n (ectopic beats detection)')
    ax[0].legend()

    ##############
    # Second panel
    ##############

    # Plot normal beats
    ax[1].scatter(artefacts['subspace1'][normalBeats],
                  artefacts['subspace3'][normalBeats],
                  color='gray', edgecolors='k', alpha=0.2,
                  zorder=10, s=15, label='Normal')

    # Plot outliers
    ax[1].scatter(artefacts['subspace1'][artefacts['ectopic']],
                  artefacts['subspace3'][artefacts['ectopic']],
                  color='r', edgecolors='k', zorder=10, label='Ectopic')
    ax[1].scatter(artefacts['subspace1'][artefacts['short']],
                  artefacts['subspace3'][artefacts['short']],
                  color='b', edgecolors='k', zorder=10,
                  marker='s', label='Short')
    ax[1].scatter(artefacts['subspace1'][artefacts['long']],
                  artefacts['subspace3'][artefacts['long']],
                  color='g', edgecolors='k', zorder=10,
                  marker='s', label='Long')
    ax[1].scatter(artefacts['subspace1'][artefacts['missed']],
                  artefacts['subspace3'][artefacts['missed']],
                  color='g', edgecolors='k', zorder=10, label='Missed')
    ax[1].scatter(artefacts['subspace1'][artefacts['extra']],
                  artefacts['subspace3'][artefacts['extra']],
                  color='b', edgecolors='k', zorder=10, label='Extra')
    # Upper area
    ax[1].plot([-1, -10], [1, 1], 'k', linewidth=1, linestyle='--')
    ax[1].plot([-1, -1], [1, 10], 'k', linewidth=1, linestyle='--')
    x = [-10, -10, -1, -1]
    y = [1, 10, 10, 1]
    ax[1].fill(x, y, color='gray', alpha=0.3)

    # Lower area
    ax[1].plot([1, 10], [-1, -1], 'k', linewidth=1, linestyle='--')
    ax[1].plot([1, 1], [-1, -10], 'k', linewidth=1, linestyle='--')
    x = [1, 1, 10, 10]
    y = [-1, -10, -10, -1]
    ax[1].fill(x, y, color='gray', alpha=0.3)

    ax[1].set_xlabel('Subspace $S_{21}$')
    ax[1].set_ylabel('Subspace $S_{22}$')
    ax[1].set_ylim(-ylim*2, ylim*2)
    ax[1].set_xlim(-xlim, xlim)
    ax[1].set_title('Subspace 2 \n (long and short beats detection)')
    ax[1].legend()

    plt.tight_layout()

    return ax
Exemple #5
0
# * The category in which the artefact belongs will have an influence on the
# correction procedure (see Artefact correction).

#%%
# Simulate RR time series
# -----------------------
# This function will simulate RR time series containing ectopic, extra, missed,
# long and short artefacts.

rr = simulate_rr()

#%%
# Artefact detection
# ------------------

outliers = rr_artefacts(rr)

#%%
# Subspaces visualization
# -----------------------
# You can visualize the two main subspaces and spot outliers. The left pamel
# plot subspaces that are more sensitive to ectopic beats detection. The right
# panel plot subspaces that will be more sensitive to long or short beats,
# comprizing the extra and missed beats.

plot_subspaces(rr)

#%%
# References
# ----------
# .. [#] Lipponen, J. A., & Tarvainen, M. P. (2019). A robust algorithm for
Exemple #6
0
def correct_rr(rr, extra_correction=True, missed_correction=True,
               short_correction=True, long_correction=True,
               ectopic_correction=True):
    """Correct long and short beats using interpolation.

    Parameters
    ----------
    rr : 1d array-like
        RR intervals (ms).
    correct_extra : boolean
      If True, correct extra beats in the RR time series.
    correct_missed : boolean
      If True, correct missed beats in the RR time series.
    correct_short : boolean
      If True, correct short beats in the RR time series.
    correct_long : boolean
      If True, correct long beats in the RR time series.
    correct_ectopic : boolean
      If True, correct ectopic beats in the RR time series.

    Returns
    -------
    correction : dictionnary
        The corrected RR time series and the number of artefacts corrected:

        * clean_rr: 1d array-like
            The corrected RR time-serie.
        * ectopic: int
            The number of ectopic beats corrected.
        * short: int
            The number of short beats corrected.
        * long: int
            The number of long beats corrcted.
        * extra: int
            The number of extra beats corrected.
        * missed: int
            The number of missed beats corrected.
    """
    if isinstance(rr, list):
        rr = np.asarray(rr)

    clean_rr = rr.copy()
    nEctopic, nShort, nLong, nExtra, nMissed = 0, 0, 0, 0, 0

    artefacts = rr_artefacts(clean_rr)

    # Correct missed beats
    if missed_correction:
        if np.any(artefacts['missed']):
            for this_id in np.where(artefacts['missed'])[0]:
                this_id += nMissed
                clean_rr = correct_missed(clean_rr, this_id)
                nMissed += 1
        artefacts = rr_artefacts(clean_rr)

    # Correct extra beats
    if extra_correction:
        if np.any(artefacts['extra']):
            for this_id in np.where(artefacts['extra'])[0]:
                this_id -= nExtra
                clean_rr = correct_missed(clean_rr, this_id)
                nExtra += 1
        artefacts = rr_artefacts(clean_rr)

    # Correct ectopic beats
    if ectopic_correction:
        if np.any(artefacts['ectopic']):
            # Also correct the beat before
            for i in np.where(artefacts['ectopic'])[0]:
                if (i > 0) & (i < len(artefacts['ectopic'])):
                    artefacts['ectopic'][i-1] = True
            this_id = np.where(artefacts['ectopic'])[0]
            clean_rr = interpolate_bads(clean_rr, [this_id])
            nEctopic = np.sum(artefacts['ectopic'])

    # Correct short beats
    if short_correction:
        if np.any(artefacts['short']):
            this_id = np.where(artefacts['short'])[0]
            clean_rr = interpolate_bads(clean_rr, this_id)
            nShort = len(this_id)

    # Correct long beats
    if long_correction:
        if np.any(artefacts['long']):
            this_id = np.where(artefacts['long'])[0]
            clean_rr = interpolate_bads(clean_rr, this_id)
            nLong = len(this_id)

    return {'clean_rr': clean_rr, 'ectopic': nEctopic, 'short': nShort,
            'long': nLong, 'extra': nExtra, 'missed': nMissed}
 def test_rr_artefacts(self):
     rr = simulate_rr()  # Simulate RR time series
     artefacts = rr_artefacts(rr)
     artefacts = rr_artefacts(list(rr))
     assert all(350 == x
                for x in [len(artefacts[k]) for k in artefacts.keys()])