This extension is developed to extend NWB data standards to incorporate ECG recordings.
Project description
ndx-ecg Extension for NWB
This extension is developed to extend NWB data standards to incorporate ECG recordings. CardiacSeries
, the main neurodata-type in this extension, in fact extends the base type of NWB TimeSeries and can be stored into three specific data interfaces of ECG
, HeartRate
and AuxiliaryAnalysis
. Also, the ECGRecordingGroup
is another neurodata-type in this module which extends LabMetaData
which itself extends the NWBContainer and stores descriptive meta-data recording channels information along with the electrodes implementation (ECGChannels
and ECGElectrodes
respectively as extensions of DynamicTable) and a link to another extended neurodata-type -ECGRecDevice
- which extends the type Device.
Installation
Simply clone the repo and navigate to the root directory, then:
pip install .
Test
A roundTrip test is runnable through pytest
from the root. The test script can be found here:
\src\pynwb\tests
An example use-case
The following is an example use case of ndx-ecg
with explanatory comments. First, we build up an nwbfile
and define an endpoint recording device:
from datetime import datetime
from uuid import uuid4
import numpy as np
from dateutil.tz import tzlocal
from pynwb import NWBHDF5IO, NWBFile
from hdmf.common import DynamicTable
from ndx_ecg import (
CardiacSeries,
ECG,
HeartRate,
AuxiliaryAnalysis,
ECGRecordingGroup,
ECGRecDevice,
ECGElectrodes,
ECGChannels
)
nwbfile = NWBFile(
session_description='ECG test-rec session',
identifier=str(uuid4()),
session_start_time=datetime.now(tzlocal()),
experimenter='experimenter',
lab='DCL',
institution='UKW',
experiment_description='',
session_id='',
)
# define an endpoint main recording device
main_device = nwbfile.create_device(
name='endpoint_recording_device',
description='description_of_the_ERD', # ERD: Endpoint recording device
manufacturer='manufacturer_of_the_ERD'
)
Then, we define instances of ECGElectrodes
and ECGChannels
, to represent the meta-data on the recording electrodes and also the recording channels:
'''
creating an ECG electrodes table
as a DynamicTable
'''
ecg_electrodes_table = ECGElectrodes(
description='descriptive meta-data on ECG recording electrodes'
)
# add electrodes
ecg_electrodes_table.add_row(
electrode_name='el_0',
electrode_location='right upper-chest',
electrode_info='descriptive info on el_0'
)
ecg_electrodes_table.add_row(
electrode_name='el_1',
electrode_location='left lower-chest',
electrode_info='descriptive info on el_1'
)
ecg_electrodes_table.add_row(
electrode_name='reference',
electrode_location='top of the head',
electrode_info='descriptive info on reference'
)
# adding the object of DynamicTable
nwbfile.add_acquisition(ecg_electrodes_table) # storage point for DT
'''
creating an ECG recording channels table
as a DynamicTable
'''
ecg_channels_table = ECGChannels(
description='descriptive meta-data on ECG recording channels'
)
# add channels
ecg_channels_table.add_row(
channel_name='ch_0',
channel_type='single',
involved_electrodes='el_0',
channel_info='channel info on ch_0'
)
ecg_channels_table.add_row(
channel_name='ch_1',
channel_type='differential',
involved_electrodes='el_0 and el_1',
channel_info='channel info on ch_1'
)
# adding the object of DynamicTable
nwbfile.add_acquisition(ecg_channels_table) # storage point for DT
Now, we can define an instance of ECGRecDevice
:
# define an ECGRecDevice-type device for ecg recording
ecg_device = ECGRecDevice(
name='recording_device',
description='description_of_the_ECGRD',
manufacturer='manufacturer_of_the_ECGRD',
filtering='notch-60Hz-analog',
gain='100',
offset='0',
synchronization='taken care of via ...',
endpoint_recording_device=main_device
)
# adding the object of ECGRecDevice
nwbfile.add_device(ecg_device)
And also an instance of ECGChannelsGroup
:
ecg_recording_group = ECGRecordingGroup(
name='recording_group',
group_description='a group to store electrodes and channels table, and linking to ECGRecDevice.',
electrodes=ecg_electrodes_table,
channels=ecg_channels_table,
recording_device=ecg_device
)
# adding the object of ECGChannelsGroup
nwbfile.add_lab_meta_data(ecg_recording_group) # storage point for custom LMD
#
Now, we have all the required standard arguments to genearate instances of CardiacSeries
and stroing them in our three different NWBDataInterfaces:
# storing the ECG data
dum_data_ecg = np.random.randn(20, 2)
dum_time_ecg = np.linspace(0, 10, len(dum_data_ecg))
ecg_cardiac_series = CardiacSeries(
name='ecg_raw_CS',
data=dum_data_ecg,
timestamps=dum_time_ecg,
unit='mV',
recording_group=ecg_recording_group
)
ecg_raw = ECG(
cardiac_series=[ecg_cardiac_series],
processing_description='raw acquisition'
)
Here, we built an instance of our CradiacSeries
to store a dummy raw ECG acquisition into a specified ECG
interface, and we store it as an acquisition into the nwbfile
:
# adding the raw acquisition of ECG to the nwb_file inside an 'ECG' container
nwbfile.add_acquisition(ecg_raw)
In the following, we have taken the similar approach but this time storing dummy data as processed data, into specific interfaces of HeartRate
and AuxiliaryAnalysis
, then storing it into a -to be defined- ecg_module
:
# storing the HeartRate data
dum_data_hr = np.random.randn(10, 2)
dum_time_hr = np.linspace(0, 10, len(dum_data_hr))
hr_cardiac_series = CardiacSeries(
name='heart_rate_CS',
data=dum_data_hr,
timestamps=dum_time_hr,
unit='bpm',
recording_group=ecg_recording_group
)
# defining an ecg_module to store the processed cardiac data and analysis
ecg_module = nwbfile.create_processing_module(
name='cardio_module',
description='a module to store processed cardiac data'
)
hr = HeartRate(
cardiac_series=[hr_cardiac_series],
processing_description='processed heartRate of the animal'
)
# adding the heart rate data to the nwb_file inside an 'HeartRate' container
ecg_module.add(hr)
# storing the Auxiliary data
# An example could be the concept of ceiling that is being used in the literature published by DCL@UKW
dum_data_ceil = np.random.randn(10, 2)
dum_time_ceil = np.linspace(0, 10, len(dum_data_ceil))
ceil_cardiac_series = CardiacSeries(
name='heart_rate_ceil_CS',
data=dum_data_ceil,
timestamps=dum_time_ceil,
unit='bpm',
recording_group=ecg_recording_group
)
ceil = AuxiliaryAnalysis(
cardiac_series=[ceil_cardiac_series],
processing_description='processed auxiliary analysis'
)
# adding the 'ceiling' auxiliary analysis to the nwb_file inside an 'AuxiliaryAnalysis' container
ecg_module.add(ceil)
# storing the processed heart rate: as an NWBDataInterface with the new assigned name instead of default
# An example could be the concept of HR2ceiling that is being used in the literature published by DCL@UKW
dum_data_hr2ceil = np.random.randn(10, 2)
dum_time_hr2ceil = np.linspace(0, 10, len(dum_data_hr2ceil))
hr2ceil_cardiac_series = CardiacSeries(
name='heart_rate_to_ceil_CS',
data=dum_data_hr2ceil,
timestamps=dum_time_hr2ceil,
unit='bpm',
recording_group=ecg_recording_group
)
hr2ceil = HeartRate(
name='HR2Ceil',
cardiac_series=[hr2ceil_cardiac_series],
processing_description='processed heartRate to ceiling'
)
# adding the 'HR2ceiling' processed HR to the nwb_file inside an 'HeartRate' container
ecg_module.add(hr2ceil)
Now, the nwbfile
is ready to be written on the disk and read back.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
File details
Details for the file ndx-ecg-0.1.0.tar.gz
.
File metadata
- Download URL: ndx-ecg-0.1.0.tar.gz
- Upload date:
- Size: 45.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/4.0.2 CPython/3.12.0
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | 8a327b993725aaf20363c15033714fe7a05c287b36e0c6e0738afe44f1758a67 |
|
MD5 | 7a4b962517986d66782dcb9543af3828 |
|
BLAKE2b-256 | 80b76961d18b52323dde23c2f3d465d42512a0632c308d719322dc3444916769 |