ndx-ecg


Namendx-ecg JSON
Version 0.1.0 PyPI version JSON
download
home_page
SummaryThis extension is developed to extend NWB data standards to incorporate ECG recordings.
upload_time2023-11-13 15:27:48
maintainer
docs_urlNone
authorHamidreza Alimohammadi ([AT]DefenseCircuitsLab)
requires_python
licenseBSD
keywords neurodatawithoutborders nwb nwb-extension ndx-extension ndx-ecg
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # 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.

<div align="center">
<img src="https://github.com/Defense-Circuits-Lab/ndx_ecg/assets/63550467/4efaddd3-ced3-47d3-9b53-c82647e8c450" width="1000">
</div>

## 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:
```python
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:
```python
'''
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```:
```python
# 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```:
```python
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:
```python
# 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`:
```python
# 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`:
```python
# 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. 


            

Raw data

            {
    "_id": null,
    "home_page": "",
    "name": "ndx-ecg",
    "maintainer": "",
    "docs_url": null,
    "requires_python": "",
    "maintainer_email": "",
    "keywords": "NeurodataWithoutBorders,NWB,nwb-extension,ndx-extension,ndx-ecg",
    "author": "Hamidreza Alimohammadi ([AT]DefenseCircuitsLab)",
    "author_email": "alimohammadi.hamidreza@gmail.com",
    "download_url": "https://files.pythonhosted.org/packages/80/b7/6961d18b52323dde23c2f3d465d42512a0632c308d719322dc3444916769/ndx-ecg-0.1.0.tar.gz",
    "platform": null,
    "description": "# ndx-ecg Extension for NWB\r\n\r\nThis 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.\r\n\r\n<div align=\"center\">\r\n<img src=\"https://github.com/Defense-Circuits-Lab/ndx_ecg/assets/63550467/4efaddd3-ced3-47d3-9b53-c82647e8c450\" width=\"1000\">\r\n</div>\r\n\r\n## Installation\r\nSimply clone the repo and navigate to the root directory, then:\r\n```\r\npip install .\r\n```\r\n## Test\r\nA roundTrip test is runnable through ```pytest``` from the root. The test script can be found here:\r\n```\r\n\\src\\pynwb\\tests\r\n```\r\n## An example use-case\r\nThe following is an example use case of ```ndx-ecg``` with explanatory comments. First, we build up an ```nwbfile``` and define an endpoint recording device:\r\n```python\r\nfrom datetime import datetime\r\nfrom uuid import uuid4\r\nimport numpy as np\r\nfrom dateutil.tz import tzlocal\r\nfrom pynwb import NWBHDF5IO, NWBFile\r\nfrom hdmf.common import DynamicTable\r\n\r\nfrom ndx_ecg import (\r\n    CardiacSeries,\r\n    ECG,\r\n    HeartRate,\r\n    AuxiliaryAnalysis,\r\n    ECGRecordingGroup,\r\n    ECGRecDevice,\r\n    ECGElectrodes,\r\n    ECGChannels\r\n)\r\n\r\nnwbfile = NWBFile(\r\n    session_description='ECG test-rec session',\r\n    identifier=str(uuid4()),\r\n    session_start_time=datetime.now(tzlocal()),\r\n    experimenter='experimenter',\r\n    lab='DCL',\r\n    institution='UKW',\r\n    experiment_description='',\r\n    session_id='',\r\n)\r\n# define an endpoint main recording device\r\nmain_device = nwbfile.create_device(\r\n    name='endpoint_recording_device',\r\n    description='description_of_the_ERD',  # ERD: Endpoint recording device\r\n    manufacturer='manufacturer_of_the_ERD'\r\n)\r\n```\r\nThen, we define instances of `ECGElectrodes` and `ECGChannels`, to represent the meta-data on the recording electrodes and also the recording channels:\r\n```python\r\n'''\r\ncreating an ECG electrodes table\r\nas a DynamicTable\r\n'''\r\necg_electrodes_table = ECGElectrodes(\r\n    description='descriptive meta-data on ECG recording electrodes'\r\n)\r\n\r\n# add electrodes\r\necg_electrodes_table.add_row(\r\n    electrode_name='el_0',\r\n    electrode_location='right upper-chest',\r\n    electrode_info='descriptive info on el_0'\r\n)\r\necg_electrodes_table.add_row(\r\n    electrode_name='el_1',\r\n    electrode_location='left lower-chest',\r\n    electrode_info='descriptive info on el_1'\r\n)\r\necg_electrodes_table.add_row(\r\n    electrode_name='reference',\r\n    electrode_location='top of the head',\r\n    electrode_info='descriptive info on reference'\r\n)\r\n# adding the object of DynamicTable\r\nnwbfile.add_acquisition(ecg_electrodes_table)  # storage point for DT\r\n\r\n'''\r\ncreating an ECG recording channels table\r\nas a DynamicTable\r\n'''\r\necg_channels_table = ECGChannels(\r\n    description='descriptive meta-data on ECG recording channels'\r\n)\r\n\r\n# add channels\r\necg_channels_table.add_row(\r\n    channel_name='ch_0',\r\n    channel_type='single',\r\n    involved_electrodes='el_0',\r\n    channel_info='channel info on ch_0'\r\n)\r\necg_channels_table.add_row(\r\n    channel_name='ch_1',\r\n    channel_type='differential',\r\n    involved_electrodes='el_0 and el_1',\r\n    channel_info='channel info on ch_1'\r\n)\r\n# adding the object of DynamicTable\r\nnwbfile.add_acquisition(ecg_channels_table)  # storage point for DT\r\n```\r\nNow, we can define an instance of ```ECGRecDevice```:\r\n```python\r\n# define an ECGRecDevice-type device for ecg recording\r\necg_device = ECGRecDevice(\r\n    name='recording_device',\r\n    description='description_of_the_ECGRD',\r\n    manufacturer='manufacturer_of_the_ECGRD',\r\n    filtering='notch-60Hz-analog',\r\n    gain='100',\r\n    offset='0',\r\n    synchronization='taken care of via ...',\r\n    endpoint_recording_device=main_device\r\n)\r\n# adding the object of ECGRecDevice\r\nnwbfile.add_device(ecg_device)\r\n```\r\nAnd also an instance of ```ECGChannelsGroup```:\r\n```python\r\necg_recording_group = ECGRecordingGroup(\r\n    name='recording_group',\r\n    group_description='a group to store electrodes and channels table, and linking to ECGRecDevice.',\r\n    electrodes=ecg_electrodes_table,\r\n    channels=ecg_channels_table,\r\n    recording_device=ecg_device\r\n)\r\n# adding the object of ECGChannelsGroup\r\nnwbfile.add_lab_meta_data(ecg_recording_group)  # storage point for custom LMD\r\n#\r\n```\r\nNow, we have all the required standard arguments to genearate instances of `CardiacSeries` and stroing them in our three different NWBDataInterfaces:\r\n```python\r\n# storing the ECG data\r\ndum_data_ecg = np.random.randn(20, 2)\r\ndum_time_ecg = np.linspace(0, 10, len(dum_data_ecg))\r\necg_cardiac_series = CardiacSeries(\r\n    name='ecg_raw_CS',\r\n    data=dum_data_ecg,\r\n    timestamps=dum_time_ecg,\r\n    unit='mV',\r\n    recording_group=ecg_recording_group\r\n)\r\n\r\necg_raw = ECG(\r\n    cardiac_series=[ecg_cardiac_series],\r\n    processing_description='raw acquisition'\r\n)\r\n```\r\nHere, 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`:\r\n```python\r\n# adding the raw acquisition of ECG to the nwb_file inside an 'ECG' container\r\nnwbfile.add_acquisition(ecg_raw)\r\n```\r\nIn 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`:\r\n```python\r\n# storing the HeartRate data\r\ndum_data_hr = np.random.randn(10, 2)\r\ndum_time_hr = np.linspace(0, 10, len(dum_data_hr))\r\nhr_cardiac_series = CardiacSeries(\r\n    name='heart_rate_CS',\r\n    data=dum_data_hr,\r\n    timestamps=dum_time_hr,\r\n    unit='bpm',\r\n    recording_group=ecg_recording_group\r\n)\r\n\r\n# defining an ecg_module to store the processed cardiac data and analysis\r\necg_module = nwbfile.create_processing_module(\r\n    name='cardio_module',\r\n    description='a module to store processed cardiac data'\r\n)\r\n\r\nhr = HeartRate(\r\n    cardiac_series=[hr_cardiac_series],\r\n    processing_description='processed heartRate of the animal'\r\n)\r\n# adding the heart rate data to the nwb_file inside an 'HeartRate' container\r\necg_module.add(hr)\r\n\r\n# storing the Auxiliary data\r\n# An example could be the concept of ceiling that is being used in the literature published by DCL@UKW\r\ndum_data_ceil = np.random.randn(10, 2)\r\ndum_time_ceil = np.linspace(0, 10, len(dum_data_ceil))\r\nceil_cardiac_series = CardiacSeries(\r\n    name='heart_rate_ceil_CS',\r\n    data=dum_data_ceil,\r\n    timestamps=dum_time_ceil,\r\n    unit='bpm',\r\n    recording_group=ecg_recording_group\r\n)\r\n\r\nceil = AuxiliaryAnalysis(\r\n    cardiac_series=[ceil_cardiac_series],\r\n    processing_description='processed auxiliary analysis'\r\n)\r\n# adding the 'ceiling' auxiliary analysis to the nwb_file inside an 'AuxiliaryAnalysis' container\r\necg_module.add(ceil)\r\n\r\n# storing the processed heart rate: as an NWBDataInterface with the new assigned name instead of default\r\n# An example could be the concept of HR2ceiling that is being used in the literature published by DCL@UKW\r\ndum_data_hr2ceil = np.random.randn(10, 2)\r\ndum_time_hr2ceil = np.linspace(0, 10, len(dum_data_hr2ceil))\r\nhr2ceil_cardiac_series = CardiacSeries(\r\n    name='heart_rate_to_ceil_CS',\r\n    data=dum_data_hr2ceil,\r\n    timestamps=dum_time_hr2ceil,\r\n    unit='bpm',\r\n    recording_group=ecg_recording_group\r\n)\r\n\r\nhr2ceil = HeartRate(\r\n    name='HR2Ceil',\r\n    cardiac_series=[hr2ceil_cardiac_series],\r\n    processing_description='processed heartRate to ceiling'\r\n)\r\n# adding the 'HR2ceiling' processed HR to the nwb_file inside an 'HeartRate' container\r\necg_module.add(hr2ceil)\r\n\r\n```\r\nNow, the `nwbfile` is ready to be written on the disk and read back. \r\n\r\n",
    "bugtrack_url": null,
    "license": "BSD",
    "summary": "This extension is developed to extend NWB data standards to incorporate ECG recordings.",
    "version": "0.1.0",
    "project_urls": null,
    "split_keywords": [
        "neurodatawithoutborders",
        "nwb",
        "nwb-extension",
        "ndx-extension",
        "ndx-ecg"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "80b76961d18b52323dde23c2f3d465d42512a0632c308d719322dc3444916769",
                "md5": "7a4b962517986d66782dcb9543af3828",
                "sha256": "8a327b993725aaf20363c15033714fe7a05c287b36e0c6e0738afe44f1758a67"
            },
            "downloads": -1,
            "filename": "ndx-ecg-0.1.0.tar.gz",
            "has_sig": false,
            "md5_digest": "7a4b962517986d66782dcb9543af3828",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": null,
            "size": 45238,
            "upload_time": "2023-11-13T15:27:48",
            "upload_time_iso_8601": "2023-11-13T15:27:48.748342Z",
            "url": "https://files.pythonhosted.org/packages/80/b7/6961d18b52323dde23c2f3d465d42512a0632c308d719322dc3444916769/ndx-ecg-0.1.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2023-11-13 15:27:48",
    "github": false,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "lcname": "ndx-ecg"
}
        
Elapsed time: 0.13952s