Skip to content

Commit 07ad050

Browse files
author
Nicolas Legrand
committed
- Docstrings, README and examples
1 parent 8444785 commit 07ad050

File tree

10 files changed

+284
-138
lines changed

10 files changed

+284
-138
lines changed

README.rst

Lines changed: 13 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -43,92 +43,25 @@ The following packages are required to use Systole:
4343
* Pandas (>=0.24)
4444
* Matplotlib (>=3.0.2)
4545
* Seaborn (>=0.9.0)
46+
* py-ecg-detectors (>=1.0.2)
4647

47-
Recording
48-
=========
49-
50-
Systole natively supports the recording of PPG signals through the `Nonin 3012LP Xpod USB pulse oximeter <https://www.nonin.com/products/xpod/>`_ together with the `Nonin 8000SM 'soft-clip' fingertip sensors <https://www.nonin.com/products/8000s/>`_.
51-
It can easily interface with `PsychoPy <https://www.psychopy.org/>`_ to record PPG signal during psychological experiments, and to synchronize stimulus deliver to e.g., systole or diastole.
52-
53-
For example, you can record and plot data in less than 6 lines of code:
54-
55-
.. code-block:: python
56-
57-
import serial
58-
from systole.recording import Oximeter
59-
ser = serial.Serial('COM4') # Add your USB port here
60-
61-
# Open serial port, initialize and plot recording for Oximeter
62-
oxi = Oximeter(serial=ser).setup().read(duration=10)
63-
64-
65-
Interfacing with PsychoPy
66-
-------------------------
67-
68-
The ``Oximeter`` class can be used together with a stimulus presentation software to record cardiac activity during psychological experiments.
69-
70-
* The ``read()`` method
71-
72-
will record for a predefined amount of time (specified by the ``duration`` parameter, in seconds). This 'serial mode' is the easiest and most robust method, but it does not allow the execution of other instructions in the meantime.
73-
74-
.. code-block:: python
75-
76-
# Code 1 {}
77-
oximeter.read(duration=10)
78-
# Code 2 {}
79-
80-
* The ``readInWaiting()`` method
48+
Interactive plotting functions and reports generation will also require the following packages to be installed:
8149

82-
will only read the bytes temporally stored in the USB buffer. For the Nonin device, this represents up to 10 seconds of recording (this procedure should be executed at least one time every 10 seconds for a continuous recording). When inserted into a while loop, it can record PPG signal in parallel with other commands.
50+
* plotly (>=4.8.0)
51+
* plotly_express (>=0.4.1)
8352

84-
.. code-block:: python
85-
86-
import time
87-
tstart = time.time()
88-
while time.time() - tstart < 10:
89-
oximeter.readInWaiting()
90-
# Insert code here {...}
91-
92-
Online detection
93-
----------------
94-
95-
Online heart beat detection, for cardiac-stimulus synchrony:
96-
97-
.. code-block:: python
98-
99-
import serial
100-
import time
101-
from systole.recording import Oximeter
102-
103-
# Open serial port
104-
ser = serial.Serial('COM4') # Change this value according to your setup
105-
106-
# Create an Oxymeter instance and initialize recording
107-
oxi = Oximeter(serial=ser, sfreq=75, add_channels=4).setup()
53+
For an overview of all the recording functionalities, you can refer to the following tutorials:
10854

109-
# Online peak detection for 10 seconds
110-
tstart = time.time()
111-
while time.time() - tstart < 10:
112-
while oxi.serial.inWaiting() >= 5:
113-
paquet = list(oxi.serial.read(5))
114-
oxi.add_paquet(paquet[2]) # Add new data point
115-
if oxi.peaks[-1] == 1:
116-
print('Heartbeat detected')
55+
* Recording
56+
* Artefacts detection and artefacts correction
57+
* Heart rate variability
11758

118-
Peaks detection
119-
===============
120-
121-
Heartbeats can be detected in the PPG signal either online or offline.
122-
123-
Methods from clipping correction and peak detection algorithm is adapted from [#]_.
124-
125-
.. code-block:: python
126-
127-
# Plot data
128-
oxi.plot_oximeter()
59+
Recording
60+
=========
12961

130-
.. figure:: https://github.com/embodied-computation-group/systole/raw/master/Images/recording.png
131-
:align: center
62+
Systole natively supports recording of physiological signals from the following setups:
63+
* `Nonin 3012LP Xpod USB pulse oximeter <https://www.nonin.com/products/xpod/>`_ together with the `Nonin 8000SM 'soft-clip' fingertip sensors <https://www.nonin.com/products/8000s/>`_ (USB).
64+
* Remote Data Access (RDA) via BrainVision Recorder together with Brain product ExG amplifier `<https://www.brainproducts.com/>`_ (Ethernet).
13265

13366
Artefact correction
13467
===================

examples/Tutorial_HRV.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
"""
2+
Recording
3+
=========
4+
5+
6+
"""
7+
8+
# Author: Nicolas Legrand <[email protected]>
9+
# Licence: GPL v3
10+
11+
# It can easily interface with `PsychoPy <https://www.psychopy.org/>`_ to
12+
# record PPG signal during psychological experiments, and to synchronize
13+
# stimulus deliver to e.g., systole or diastole.
14+
15+
# For example, you can record and plot data in less than 6 lines of code:
16+
17+
18+
#%%
19+
# Event related cardiac deceleration
20+
# ----------------------------------
21+
import serial
22+
from systole.recording import Oximeter
23+
ser = serial.Serial('COM4') # Add your USB port here
24+
25+
# Open serial port, initialize and plot recording for Oximeter
26+
oxi = Oximeter(serial=ser).setup().read(duration=10)
27+
28+
29+
Interfacing with PsychoPy
30+
-------------------------
31+
32+
The ``Oximeter`` class can be used together with a stimulus presentation software to record cardiac activity during psychological experiments.
33+
34+
* The ``read()`` method
35+
36+
will record for a predefined amount of time (specified by the ``duration`` parameter, in seconds). This 'serial mode' is the easiest and most robust method, but it does not allow the execution of other instructions in the meantime.
37+
38+
.. code-block:: python
39+
40+
# Code 1 {}
41+
oximeter.read(duration=10)
42+
# Code 2 {}
43+
44+
* The ``readInWaiting()`` method
45+
46+
will only read the bytes temporally stored in the USB buffer. For the Nonin device, this represents up to 10 seconds of recording (this procedure should be executed at least one time every 10 seconds for a continuous recording). When inserted into a while loop, it can record PPG signal in parallel with other commands.
47+
48+
.. code-block:: python
49+
50+
import time
51+
tstart = time.time()
52+
while time.time() - tstart < 10:
53+
oximeter.readInWaiting()
54+
# Insert code here {...}

examples/Tutorial_recording.py

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
"""
2+
Recording PPG signal
3+
====================
4+
"""
5+
6+
# Author: Nicolas Legrand <[email protected]>
7+
# Licence: GPL v3
8+
9+
# The py:class:systole.recording.Oximeter class can be used to read incoming
10+
# PPG signal from `Nonin 3012LP Xpod USB pulse oximeter
11+
# <https://www.nonin.com/products/xpod/>`_ together with the `Nonin 8000SM
12+
# 'soft-clip' fingertip sensors <https://www.nonin.com/products/8000s/>`_.
13+
# This function can easily be integrated with other stimulus presentation
14+
# software lie `PsychoPy <https://www.psychopy.org/>`_ to record cardiac
15+
# activity during psychological experiments, or to synchronize stimulus
16+
# delivery with cardiac phases (e.g. systole or diastole).
17+
18+
19+
#%%
20+
# Reading
21+
# -------
22+
# Recording and plotting your first time-series will only require 5 lines
23+
# of code:
24+
25+
import serial
26+
from systole.recording import Oximeter
27+
ser = serial.Serial('COM4') # Add your USB port here
28+
29+
# Open serial port, initialize and plot recording for Oximeter
30+
oxi = Oximeter(serial=ser).setup().read(duration=10)
31+
32+
# The signal can be directly plotted using built-in functions.
33+
oxi.plot_oximeter()
34+
35+
##############################################################################
36+
# .. figure:: https://github.com/embodied-computation-group/systole/raw/master/Images/recording.png
37+
# :align: center
38+
##############################################################################
39+
40+
#%%
41+
# Interfacing with PsychoPy
42+
# -------------------------
43+
44+
# * The ``read()`` method will record for a predefined amount of time
45+
# (specified by the ``duration`` parameter, in seconds). This 'serial mode'
46+
# is the easiest and most robust method, but it does not allow the execution
47+
# of other instructions in the meantime.
48+
49+
# Code 1 {}
50+
oximeter.read(duration=10)
51+
# Code 2 {}
52+
53+
# * The ``readInWaiting()`` method will only read the bytes temporally stored
54+
# in the USB buffer. For the Nonin device, this represents up to 10 seconds of
55+
# recording (this procedure should be executed at least one time every 10
56+
# seconds for a continuous recording). When inserted into a while loop, it can
57+
# record PPG signal in parallel with other commands.
58+
59+
import time
60+
tstart = time.time()
61+
while time.time() - tstart < 10:
62+
oximeter.readInWaiting()
63+
# Insert code here {...}
64+
65+
#%%
66+
# Online detection
67+
# ----------------
68+
# Online heart beat detection, for cardiac-stimulus synchrony
69+
70+
import serial
71+
import time
72+
from systole.recording import Oximeter
73+
74+
# Open serial port
75+
ser = serial.Serial('COM4') # Change this value according to your setup
76+
77+
# Create an Oxymeter instance and initialize recording
78+
oxi = Oximeter(serial=ser, sfreq=75, add_channels=4).setup()
79+
80+
# Online peak detection for 10 seconds
81+
tstart = time.time()
82+
while time.time() - tstart < 10:
83+
while oxi.serial.inWaiting() >= 5:
84+
paquet = list(oxi.serial.read(5))
85+
oxi.add_paquet(paquet[2]) # Add new data point
86+
if oxi.peaks[-1] == 1:
87+
print('Heartbeat detected')

examples/plot_ECGProcessing.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
# ---------------
2828
# The peaks detection algorithms are imported from the py-ecg-detectors module:
2929
# https://github.com/berndporr/py-ecg-detectors
30-
signal, peaks = ecg_peaks(signal_df.ecg, method='hamilton', sfreq=2000,
30+
signal, peaks = ecg_peaks(signal_df.ecg, method='hamilton', sfreq=1000,
3131
find_local=True)
3232

3333
#%%
@@ -50,10 +50,12 @@
5050
neutral[
5151
np.round(np.where(signal_df.stim.to_numpy() == 3)[0]).astype(int)] = 1
5252

53+
#%%
5354
# Event related plot
55+
# ------------------
5456
sns.set_context('talk')
5557
fig, ax = plt.subplots(figsize=(8, 5))
56-
for cond, col, data in zip(
58+
for cond, data, col in zip(
5759
['Neutral', 'Disgust'], [neutral, disgust],
5860
[sns.xkcd_rgb["denim blue"], sns.xkcd_rgb["pale red"]]):
5961

@@ -72,6 +74,7 @@
7274
ax.set_ylabel('Heart Rate (BPM)')
7375
ax.set_title('Instantaneous heart rate after neutral and disgusting images')
7476
sns.despine()
77+
plt.tight_layout()
7578

7679

7780
#%%

source/api.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ Detection
1717
:toctree: generated/
1818

1919
oxi_peaks
20+
ecg_peaks
2021
rr_artefacts
2122
interpolate_clipping
2223

@@ -72,6 +73,7 @@ Recording
7273
:toctree: generated/
7374

7475
recording.Oximeter
76+
recording.BrainVisionExG
7577

7678
Utils
7779
-----
@@ -86,3 +88,4 @@ Utils
8688
heart_rate
8789
to_angles
8890
to_epochs
91+
to_rr

systole/detection.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,17 +49,16 @@ def oxi_peaks(x, sfreq=75, win=1, new_sfreq=1000, clipping=True,
4949
>>> df = import_ppg() # Import PPG recording
5050
>>> signal, peaks = oxi_peaks(df.ppg.to_numpy())
5151
>>> print(f'{sum(peaks)} peaks detected.')
52+
378 peaks detected.
5253
5354
References
5455
----------
55-
Some of the processing steps were adapted from the HeartPy toolbox:
56-
https://python-heart-rate-analysis-toolkit.readthedocs.io/en/latest/index.html
57-
5856
.. [1] van Gent, P., Farah, H., van Nes, N. and van Arem, B., 2019.
5957
Analysing Noisy Driver Physiology Real-Time Using Off-the-Shelf Sensors:
6058
Heart Rate Analysis Software from the Taking the Fast Lane Project. Journal
6159
of Open Research Software, 7(1), p.32. DOI: http://doi.org/10.5334/jors.241
6260
"""
61+
6362
if isinstance(x, list):
6463
x = np.asarray(x)
6564

@@ -151,12 +150,14 @@ def ecg_peaks(x, sfreq=1000, new_sfreq=1000, method='pan-tompkins',
151150
>>> signal, peaks = ecg_peaks(signal_df.ecg.to_numpy(), method='hamilton',
152151
>>> sfreq=2000, find_local=True)
153152
>>> print(f'{sum(peaks)} peaks detected.')
153+
24 peaks detected.
154154
155155
References
156156
----------
157157
.. [#] Howell, L., Porr, B. Popular ECG R peak detectors written in
158158
python. DOI: 10.5281/zenodo.3353396
159159
"""
160+
160161
if isinstance(x, list):
161162
x = np.asarray(x)
162163

@@ -256,6 +257,8 @@ def rr_artefacts(rr, c1=0.13, c2=0.17, alpha=5.2):
256257
>>> rr = simulate_rr() # Simulate RR time series
257258
>>> artefacts = rr_artefacts(rr)
258259
>>> print(artefacts.keys())
260+
dict_keys(['subspace1', 'subspace2', 'subspace3', 'mRR', 'ectopic', 'long',
261+
'short', 'missed', 'extra', 'threshold1', 'threshold2'])
259262
260263
References
261264
----------
@@ -388,14 +391,14 @@ def interpolate_clipping(signal, threshold=255):
388391
Examples
389392
--------
390393
.. plot::
394+
391395
>>> import matplotlib.pyplot as plt
392396
>>> from systole import import_ppg
393397
>>> from systole.detection import interpolate_clipping
394398
>>> df = import_ppg()
395-
>>> clean_signal = interpolate_clipping(df.ppg.to_numpy(),
396-
>>> threshold=255)
399+
>>> clean_signal = interpolate_clipping(df.ppg.to_numpy())
397400
>>> plt.plot(df.time, clean_signal, color='#F15854')
398-
>>> plt.plot(df.time, ppg, color='#5DA5DA')
401+
>>> plt.plot(df.time, df.ppg, color='#5DA5DA')
399402
>>> plt.axhline(y=255, linestyle='--', color='k')
400403
>>> plt.xlabel('Time (s)')
401404
>>> plt.ylabel('PPG level (a.u)')

0 commit comments

Comments
 (0)