Skip to main content

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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

ndx-ecg-0.1.0.tar.gz (45.2 kB view details)

Uploaded Source

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

Hashes for ndx-ecg-0.1.0.tar.gz
Algorithm Hash digest
SHA256 8a327b993725aaf20363c15033714fe7a05c287b36e0c6e0738afe44f1758a67
MD5 7a4b962517986d66782dcb9543af3828
BLAKE2b-256 80b76961d18b52323dde23c2f3d465d42512a0632c308d719322dc3444916769

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page