예제 #1
0
 def test_to_rr(self):
     ppg = import_ppg().ppg.to_numpy()
     signal, peaks = oxi_peaks(ppg)
     rr = to_rr(peaks)
     assert rr.mean() == 874.2068965517242
     rr = to_rr(np.where(peaks)[0])
     assert rr.mean() == 874.2068965517242
예제 #2
0
def plot_oximeter(x, sfreq=75, ax=None):
    """Plot PPG signal.

    Parameters
    ----------
    x : 1d array-like or `systole.recording.Oximeter`
        The ppg signal, or the Oximeter instance used to record the signal.
    sfreq : int
        Signal sampling frequency. Default is 75 Hz.
    ax : `Matplotlib.Axes` or None
        Where to draw the plot. Default is *None* (create a new figure).

    Returns
    -------
    ax : `Matplotlib.Axes`
        The figure.
    """
    if isinstance(x, (list, np.ndarray)):
        times = np.arange(0, len(x) / sfreq, 1 / sfreq)
        recording = np.asarray(x)
        signal, peaks = oxi_peaks(x, new_sfreq=sfreq)
        threshold = None
        label = 'Offline estimation'
    else:
        times = np.asarray(x.times)
        recording = np.asarray(x.recording)
        peaks = np.asarray(x.peaks)
        threshold = np.asarray(x.threshold)
        label = 'Online estimation'

    if ax is None:
        fig, ax = plt.subplots(figsize=(13, 5))
    ax.set_title('Oximeter recording', fontweight='bold')

    if threshold is not None:
        ax.plot(times,
                threshold,
                linestyle='--',
                color='gray',
                label='Threshold')
        ax.fill_between(x=times,
                        y1=threshold,
                        y2=recording.min(),
                        alpha=0.2,
                        color='gray')
    ax.plot(times, recording, label='Recording', color='#4c72b0')
    ax.fill_between(x=times, y1=recording, y2=recording.min(), color='w')
    ax.plot(times[np.where(peaks)[0]],
            recording[np.where(peaks)[0]],
            'o',
            color='#c44e52',
            label=label)
    ax.set_ylabel('PPG level')
    ax.set_xlabel('Time (s)')
    ax.legend()

    return ax
예제 #3
0
 def test_norm_triggers(self):
     ppg = import_ppg('1')[0, :]  # Import PPG recording
     signal, peaks = oxi_peaks(ppg)
     peaks[np.where(peaks)[0] + 1] = 1
     peaks[np.where(peaks)[0] + 2] = 1
     y = norm_triggers(peaks)
     assert sum(y) == 378
     peaks = -peaks
     y = norm_triggers(peaks, threshold=-1, direction='lower')
     assert sum(y) == 378
예제 #4
0
 def test_to_angle(self):
     """Test to_angles function"""
     rr = import_rr().rr.values
     # Create event vector
     events = rr + np.random.normal(500, 100, len(rr))
     ang = to_angles(list(np.cumsum(rr)), list(np.cumsum(events)))
     assert ~np.any(np.asarray(ang) < 0)
     assert ~np.any(np.asarray(ang) > np.pi * 2)
     ppg = import_ppg('1')[0, :]  # Import PPG recording
     signal, peaks = oxi_peaks(ppg)
     ang = to_angles(peaks, peaks)
예제 #5
0
 def test_heart_rate(self):
     """Test heart_rate function"""
     ppg = import_ppg('1')[0, :]  # Import PPG recording
     signal, peaks = oxi_peaks(ppg)
     heartrate, time = heart_rate(peaks)
     assert len(heartrate) == len(time)
     heartrate, time = heart_rate(list(peaks))
     assert len(heartrate) == len(time)
     heartrate, time = heart_rate(peaks,
                                  unit='bpm',
                                  kind='cubic',
                                  sfreq=500)
     assert len(heartrate) == len(time)
예제 #6
0
 def test_norm_triggers(self):
     ppg = import_ppg('1')[0, :]  # Import PPG recording
     signal, peaks = oxi_peaks(ppg)
     peaks[np.where(peaks)[0] + 1] = 1
     peaks[np.where(peaks)[0] + 2] = 1
     peaks[-1:] = 1
     y = norm_triggers(peaks)
     assert sum(y) == 379
     peaks = -peaks.astype(int)
     y = norm_triggers(peaks, threshold=-1, direction='lower')
     assert sum(y) == 379
     with pytest.raises(ValueError):
         norm_triggers(None)
     with pytest.raises(ValueError):
         norm_triggers(peaks, direction='invalid')
예제 #7
0
 def test_heart_rate(self):
     """Test heart_rate function"""
     ppg = import_ppg().ppg.to_numpy()  # Import PPG recording
     signal, peaks = oxi_peaks(ppg)
     heartrate, time = heart_rate(peaks)
     assert len(heartrate) == len(time)
     heartrate, time = heart_rate(list(peaks))
     assert len(heartrate) == len(time)
     heartrate, time = heart_rate(peaks,
                                  unit='bpm',
                                  kind='cubic',
                                  sfreq=500)
     assert len(heartrate) == len(time)
     with pytest.raises(ValueError):
         heartrate, time = heart_rate([1, 2, 3])
예제 #8
0
    def find_peaks(self, **kwargs):
        """Find peaks in recorded signal.

        Returns
        -------
        Oximeter instance. The peaks occurences are stored in the `peaks`
        attribute.

        Other Parameters
        ----------------
        **kwargs : `~systole.detection.oxi_peaks` properties.
        """
        # Peak detection
        resampled_signal, peaks = oxi_peaks(self.recording,
                                            new_sfreq=75,
                                            **kwargs)

        # R-R intervals (in miliseconds)
        self.rr = (np.diff(np.where(peaks)[0]) / self.sfreq) * 1000

        # Beats per minutes
        self.bpm = 60000 / self.rr

        return self
예제 #9
0
 def test_oxi_peaks(self):
     """Test oxi_peaks function"""
     ppg = import_ppg('1')[0, :]  # Import PPG recording
     signal, peaks = oxi_peaks(ppg)
     assert len(signal) == len(peaks)
     assert np.all(np.unique(peaks) == [0, 1])
예제 #10
0
def plot_raw(signal, sfreq=75):
    """Interactive visualization of PPG signal and beats detection.

    Parameters
    ----------
    signal : `pd.DataFrame` instance or 1d array-like
        Dataframe of signal recording in the long format. Should contain at
        least the two following columns: ['time', 'signal']. If an array is
        provided, will automatically create the DataFrame using th array as
        signal and *sfreq* as sampling frequency.
    sfreq : int
        Signal sampling frequency. Default is 75 Hz.
    """
    if isinstance(signal, pd.DataFrame):
        # Find peaks - Remove learning phase
        signal, peaks = oxi_peaks(signal.signal, noise_removal=False)
    else:
        signal, peaks = oxi_peaks(signal, noise_removal=False)
    time = np.arange(0, len(signal)) / 1000

    # Extract heart rate
    hr, time = heart_rate(peaks, sfreq=1000, unit='rr', kind='previous')

    #############
    # Upper panel
    #############

    # Signal
    ppg_trace = go.Scattergl(x=time,
                             y=signal,
                             mode='lines',
                             name='PPG',
                             hoverinfo='skip',
                             showlegend=False,
                             line=dict(width=1, color='#c44e52'))
    # Peaks
    peaks_trace = go.Scattergl(x=time[peaks],
                               y=signal[peaks],
                               mode='markers',
                               name='Peaks',
                               hoverinfo='y',
                               showlegend=False,
                               marker=dict(size=8,
                                           color='white',
                                           line=dict(width=2,
                                                     color='DarkSlateGrey')))

    #############
    # Lower panel
    #############

    # Instantaneous Heart Rate - Lines
    rr_trace = go.Scattergl(x=time,
                            y=hr,
                            mode='lines',
                            name='R-R intervals',
                            hoverinfo='skip',
                            showlegend=False,
                            line=dict(width=1, color='#4c72b0'))

    # Instantaneous Heart Rate - Peaks
    rr_peaks = go.Scattergl(x=time[peaks],
                            y=hr[peaks],
                            mode='markers',
                            name='R-R intervals',
                            showlegend=False,
                            marker=dict(size=6,
                                        color='white',
                                        line=dict(width=2,
                                                  color='DarkSlateGrey')))

    raw = make_subplots(rows=2,
                        cols=1,
                        shared_xaxes=True,
                        vertical_spacing=.05,
                        row_titles=['Recording', 'Heart rate'])

    raw.update_layout(plot_bgcolor="white",
                      paper_bgcolor="white",
                      margin=dict(l=5, r=5, b=5, t=5),
                      autosize=True)

    raw.add_trace(ppg_trace, 1, 1)
    raw.add_trace(peaks_trace, 1, 1)
    raw.add_trace(rr_trace, 2, 1)
    raw.add_trace(rr_peaks, 2, 1)

    return raw
예제 #11
0
def plot_raw(signal, sfreq=75, type='ppg'):
    """Interactive visualization of PPG signal and beats detection.

    Parameters
    ----------
    signal : :py:class:`pandas.DataFrame` or 1d array-like
        Dataframe of signal recording in the long format. Should contain at
        least one ``'time'`` and one signal colum (can be ``'ppg'`` or
        ``'ecg'``). If an array is provided, will automatically create the
        DataFrame using the array as signal and ``sfreq`` as sampling
        frequency.
    sfreq : int
        Signal sampling frequency. Default is 75 Hz.
    type : str
        The recording modality. Can be ``'ppg'`` (pulse oximeter) or ``'ecg'``
        (electrocardiography).
    """
    import plotly.graph_objs as go
    from plotly.subplots import make_subplots

    if isinstance(signal, pd.DataFrame):
        # Find peaks - Remove learning phase
        if type == 'ppg':
            signal, peaks = oxi_peaks(signal.ppg, noise_removal=False)
        elif type == 'ecg':
            signal, peaks = ecg_peaks(signal.ecg,
                                      method='hamilton',
                                      find_local=True)
    else:
        if type == 'ppg':
            signal, peaks = oxi_peaks(signal, noise_removal=False, sfreq=sfreq)
        elif type == 'ecg':
            signal, peaks = ecg_peaks(signal,
                                      method='hamilton',
                                      sfreq=sfreq,
                                      find_local=True)
    time = np.arange(0, len(signal)) / 1000

    # Extract heart rate
    hr, time = heart_rate(peaks, sfreq=1000, unit='rr', kind='linear')

    #############
    # Upper panel
    #############

    # Signal
    ppg_trace = go.Scattergl(x=time,
                             y=signal,
                             mode='lines',
                             name='PPG',
                             hoverinfo='skip',
                             showlegend=False,
                             line=dict(width=1, color='#c44e52'))
    # Peaks
    peaks_trace = go.Scattergl(x=time[peaks],
                               y=signal[peaks],
                               mode='markers',
                               name='Peaks',
                               hoverinfo='y',
                               showlegend=False,
                               marker=dict(size=8,
                                           color='white',
                                           line=dict(width=2,
                                                     color='DarkSlateGrey')))

    #############
    # Lower panel
    #############

    # Instantaneous Heart Rate - Lines
    rr_trace = go.Scattergl(x=time,
                            y=hr,
                            mode='lines',
                            name='R-R intervals',
                            hoverinfo='skip',
                            showlegend=False,
                            line=dict(width=1, color='#4c72b0'))

    # Instantaneous Heart Rate - Peaks
    rr_peaks = go.Scattergl(x=time[peaks],
                            y=hr[peaks],
                            mode='markers',
                            name='R-R intervals',
                            showlegend=False,
                            marker=dict(size=6,
                                        color='white',
                                        line=dict(width=2,
                                                  color='DarkSlateGrey')))

    raw = make_subplots(rows=2,
                        cols=1,
                        shared_xaxes=True,
                        vertical_spacing=.05,
                        row_titles=['Recording', 'Heart rate'])

    raw.update_layout(plot_bgcolor="white",
                      paper_bgcolor="white",
                      margin=dict(l=5, r=5, b=5, t=5),
                      autosize=True,
                      xaxis_title="Time (s)")

    raw.add_trace(ppg_trace, 1, 1)
    raw.add_trace(peaks_trace, 1, 1)
    raw.add_trace(rr_trace, 2, 1)
    raw.add_trace(rr_peaks, 2, 1)

    return raw
예제 #12
0
def report_oxi(signal, file_name='report.png', dpi=600):
    """Generate HRV report.

    Parameters
    ----------
    signal : 1d array-like
        PPG singal.
    file_name : str
        Output file name.
    dpi : int
        Image quality.
    """
    from systole import import_ppg
    signal = import_ppg('1')[0]
    plot_oximeter(signal)

    # Find peaks
    signal, peaks = oxi_peaks(signal)

    # Extract instantaneous heartrate
    sfreq = 1000
    noisy_hr, time = heart_rate(peaks, sfreq=sfreq, unit='bpm', kind='cubic')

    time = np.arange(0, len(signal) / sfreq, 1 / sfreq)

    fig = plt.figure(figsize=(8, 13))
    gs = fig.add_gridspec(4, 3)
    fig_ax1 = fig.add_subplot(gs[0, :])

    fig_ax1.plot(time, signal, linewidth=.2)
    fig_ax1.set_ylabel('PPG level')
    fig_ax1.set_xlim(0, time[-1])
    fig_ax1.set_title('Signal', fontweight='bold')

    fig_ax2 = fig.add_subplot(gs[1, :])
    fig_ax2.plot(time, noisy_hr, linewidth=.8, color='r')
    fig_ax2.set_ylabel('BPM')
    fig_ax2.set_xlabel('Time (s)')
    fig_ax2.set_xlim(0, time[-1])
    fig_ax2.set_title('RR time-course', fontweight='bold')

    # HRV
    rr = np.diff(np.where(peaks)[0])

    fig_ax3 = fig.add_subplot(gs[2, 0])
    fig_ax3.hist(rr, bins=30, alpha=.5)
    fig_ax3.hist(rr[(rr <= 400) | (rr >= 1500)], bins=30, color='r')
    fig_ax3.set_title('Distribution', fontweight='bold')
    fig_ax3.set_ylabel('Count')
    fig_ax3.set_xlabel('RR(s)')

    fig_ax4 = fig.add_subplot(gs[2, 1])
    plot_psd(rr, show=True, ax=fig_ax4)

    fig_ax5 = fig.add_subplot(gs[2, 2])
    fig_ax5.plot(rr[:-1], rr[1:], color='gray', markersize=1, alpha=0.8)
    fig_ax5.set_title('Pointcare Plot', fontweight='bold')
    fig_ax5.set_ylabel('$RR_{n+1}$')
    fig_ax5.set_xlabel('$RR_n$')
    plt.tight_layout(h_pad=0.05)
    if file_name is not None:
        plt.savefig(file_name, dpi=dpi)
    plt.close()
예제 #13
0
 def test_oxi_peaks(self):
     """Test oxi_peaks function"""
     df = import_ppg()  # Import PPG recording
     signal, peaks = oxi_peaks(df.ppg.to_numpy())
     assert len(signal) == len(peaks)
     assert np.all(np.unique(peaks) == [0, 1])