Skip to content

Commit f5d7fb7

Browse files
authored
Merge pull request #71 from James-Mc1ntyre/master
Added recording class for Nonin 3231 device, to work with the cardiocieption HRD task
2 parents e515392 + 713ef26 commit f5d7fb7

File tree

1 file changed

+169
-0
lines changed

1 file changed

+169
-0
lines changed

systole/recording.py

+169
Original file line numberDiff line numberDiff line change
@@ -748,3 +748,172 @@ def read(self, duration):
748748
def close(self):
749749
"""Close TCPIP connections"""
750750
self.con.close()
751+
752+
753+
754+
755+
756+
class Nonin3231USB:
757+
"""Recording Nonin 3231 USB HR signal through USB connection
758+
759+
Parameters
760+
----------
761+
762+
serial : pySerial object
763+
The `serial` instance interfacing with the USB port.
764+
765+
766+
Examples
767+
--------
768+
First, you will need to define a :py:func:`serial` instance, indexing the
769+
USB port where the Nonin 3231 Pulse Oximeter is plugged.
770+
771+
>>> import serial
772+
>>> ser = serial.Serial('COM4')
773+
774+
This instance is then used to create an :py:func:`Nonin3231USB` instance
775+
that will be used for the recording from a Nonin 3231 USB device.
776+
777+
>>> from ecg.recording import Nonin3231USB
778+
>>> exg = Nonin3231USB(serial).read(30)
779+
780+
Use the :py:func:`read` method to record some signal and save it in the
781+
`exg` dictionary.
782+
783+
.. warning:: The signals received fom the host are appened to a list. This
784+
process can require more time at each iteration as the signal length
785+
increase in memory. You should alway make sure that this will not
786+
interfer with other task and regularly save intermediate recording to
787+
save resources.
788+
789+
Notes
790+
-----
791+
"""
792+
793+
def __init__(
794+
self,
795+
serial,
796+
add_channels: Optional[int] = 1,
797+
):
798+
self.reset(serial, add_channels)
799+
800+
def reset(
801+
self,
802+
serial,
803+
add_channels: Optional[int] = 1,
804+
):
805+
"""Initialize/restart the recording instance.
806+
807+
Parameters
808+
----------
809+
serial : pySerial object
810+
The `serial` instance interfacing with the USB port.
811+
812+
Returns
813+
-------
814+
Nonin3231USB instance.
815+
"""
816+
self.serial = serial
817+
818+
# Initialize recording with empty lists
819+
self.recording: List[float] = []
820+
self.bpm: List[float] = []
821+
self.SpO2: List[float] = []
822+
self.times: List[float] = []
823+
self.n_channels: Optional[int] = add_channels
824+
if add_channels is not None:
825+
self.channels: Optional[Dict[str, List]] = {}
826+
for i in range(add_channels):
827+
self.channels[f"Channel_{i}"] = []
828+
else:
829+
self.channels = None
830+
831+
# print('channels: '+str(self.channels))
832+
833+
return self
834+
835+
def setup(self):
836+
self.reset(
837+
serial=self.serial,
838+
)
839+
840+
return self
841+
842+
def read(self, duration: float):
843+
"""Read PPG signal for some amount of time.
844+
845+
Parameters
846+
----------
847+
duration : int or float
848+
Length of the desired recording time.
849+
"""
850+
# init start ime
851+
tstart = time.time()
852+
853+
# read for length of duration
854+
while time.time() - tstart < duration:
855+
# assert one full line of data is there
856+
if self.serial.inWaiting() >= 12:
857+
# Store line of data
858+
data = list(self.serial.readline())
859+
# assert full data line
860+
if len(data) < 12:
861+
continue
862+
# get bpm reading
863+
self.bpm.append(data[8])
864+
# give bpm to recording as well
865+
self.recording.append(data[8])
866+
# get SpO2 reading (we don't use it)
867+
self.SpO2.append(data[6])
868+
# get 'time' reading (seconds)
869+
self.times.append(data[5])
870+
# Add 0 to the additional channels
871+
if self.channels is not None:
872+
for ch in self.channels:
873+
self.channels[ch].append(0)
874+
875+
return self
876+
877+
878+
def readInWaiting(self, bool = False):
879+
"""Read in waiting Nonin data.
880+
881+
Parameters
882+
----------
883+
stop : bool
884+
Stop the recording when an error is detected. Default is *False*.
885+
"""
886+
887+
# nonin device reads out 12 bits per message
888+
while self.serial.inWaiting() >= 12:
889+
890+
# Store full message line
891+
data = list(self.serial.readline())
892+
# assert that there is a full line of data
893+
if len(data) < 12:
894+
continue
895+
# get bpm reading
896+
self.bpm.append(data[8])
897+
# give bpm to recording as well
898+
self.recording.append(data[8])
899+
# get SpO2 reading (we don't use it)
900+
self.SpO2.append(data[6])
901+
# get 'time' reading (seconds)
902+
self.times.append(data[5])
903+
# Add 0 to the additional channels for triggers
904+
if self.channels is not None:
905+
for ch in self.channels:
906+
self.channels[ch].append(0)
907+
908+
909+
910+
def save(self, fname: str):
911+
"""
912+
Not saving the data as of now, only results
913+
"""
914+
None
915+
916+
917+
def close(self):
918+
"""Close serial connections"""
919+
self.serial.close()

0 commit comments

Comments
 (0)