{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "\n# Recording PPG signal\n\nThe py:class:`systole.recording.Oximeter` class can be used to read incoming PPG signal \nfrom [Nonin 3012LP Xpod USB pulse oximeter](https://www.nonin.com/products/xpod/) together with the [Nonin 8000SM 'soft-clip' \nfingertip sensors](https://www.nonin.com/products/8000s/). This function can easily \nbe integrated with other stimulus presentation software like [PsychoPy](https://www.psychopy.org/) to record cardiac activity during psychological \nexperiments, or to synchronize stimulus delivery around cardiac phases (e.g. systole or\ndiastole).\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# Author: Nicolas Legrand \n# Licence: Apache-2.0" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Recording PPG singal\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "from systole.recording import Oximeter\nfrom systole import serialSim" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Recording and plotting your first PPG time-series only require a few lines of code:\nFirst, open a serial port. Note that here we are using Systole's PPG simulation \nfunction so the example can run without any device plugged on the computer for the\ndemonstration. If you want to connect to an actual Nonin pulse oximeter, you simply\nhave to provide the port reference it is plugged in (see commented lines below). \n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "ser = serialSim() # Simulate a device\n\n# Use a USB device\n#import serial\n#ser = serial.Serial(\"COM4\") # Use this line for USB recording" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Once the reference of the port created, you can create a recording instance, \ninitialize it and read some incoming signal in just one line of code. \n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "oxi = Oximeter(serial=ser).setup().read(duration=10)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "can be directly plotted using built-in functions.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "oxi.plot_raw(show_heart_rate=True, figsize=(13, 8))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Interfacing with PsychoPy\nOne nice feature provided by Systole is that it can run the recording of PPG signal\ntogether with other Python scripts like Psychopy, which can be used to build \npsychological experiments. There are two ways for interfacing with other scripts, it\ncan be done either in a serial or in a (pseudo-) parallel way.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# - The ``read()`` method will record for a predefined amount of time\n# (specified by the ``duration`` parameter, in seconds). This 'serial mode'\n# is the easiest and most robust method, but it does not allow the execution\n# of other instructions in the meantime.\n\n# Code 1 {}\noxi.read(duration=10)\n# Code 2 {}\n\n# - The ``readInWaiting()`` method will only read the bytes temporally stored\n# in the USB buffer. For the Nonin device, this represents up to 10 seconds of\n# recording (this procedure should be executed at least one time every 10\n# seconds for a continuous recording). When inserted into a while loop, it can\n# record PPG signal almost in parallel with other commands.\n\nimport time\ntstart = time.time()\nwhile time.time() - tstart < 10:\n oxi.readInWaiting()\n # Insert code here {...}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Online detection\nThe recording instance is also detecting heartbeats automatically in the background, \nand this information can be accessed in real-time to deliver stimuli time-locked to \nspecific cardiac phases. Note that the delay between the actual heartbeat and the \nexecution of computer code (here the `print` command in the example below) can be \nimportant. Also, it is important to note that the systolic peak detected in the PPG\nsignal is delayed relative to the R peaks observed in the ECG signal.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# Create an Oxymeter instance and initialize recording\noxi = Oximeter(serial=ser, sfreq=75, add_channels=4).setup()\n\n# Online peak detection - run for 10 seconds\ntstart = time.time()\nwhile time.time() - tstart < 10:\n while oxi.serial.inWaiting() >= 5:\n paquet = list(oxi.serial.read(5))\n oxi.add_paquet(paquet[2]) # Add new data point\n if oxi.peaks[-1] == 1:\n print(\"Heartbeat detected\")" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.16" } }, "nbformat": 4, "nbformat_minor": 0 }