Package: ndx-ecg
ndx-ecg Extension for NWBThis 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 ofECG,HeartRateandAuxiliaryAnalysis. Also, theECGRecordingGroupis another neurodata-type in this module which extendsLabMetaDatawhich itself extends the NWBContainer and stores descriptive meta-data recording channels information along with the electrodes implementation (ECGChannelsandECGElectrodesrespectively as extensions of DynamicTable) and a link to another extended neurodata-type -ECGRecDevice- which extends the type Device.InstallationSimply clone the repo and navigate to the root directory, then:pip install .TestA roundTrip test is runnable throughpytestfrom the root. The test script can be found here:\src\pynwb\testsAn example use-caseThe following is an example use case ofndx-ecgwith explanatory comments. First, we build up annwbfileand define an endpoint recording device:fromdatetimeimportdatetimefromuuidimportuuid4importnumpyasnpfromdateutil.tzimporttzlocalfrompynwbimportNWBHDF5IO,NWBFilefromhdmf.commonimportDynamicTablefromndx_ecgimport(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 devicemain_device=nwbfile.create_device(name='endpoint_recording_device',description='description_of_the_ERD',# ERD: Endpoint recording devicemanufacturer='manufacturer_of_the_ERD')Then, we define instances ofECGElectrodesandECGChannels, to represent the meta-data on the recording electrodes and also the recording channels:'''creating an ECG electrodes tableas a DynamicTable'''ecg_electrodes_table=ECGElectrodes(description='descriptive meta-data on ECG recording electrodes')# add electrodesecg_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 DynamicTablenwbfile.add_acquisition(ecg_electrodes_table)# storage point for DT'''creating an ECG recording channels tableas a DynamicTable'''ecg_channels_table=ECGChannels(description='descriptive meta-data on ECG recording channels')# add channelsecg_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 DynamicTablenwbfile.add_acquisition(ecg_channels_table)# storage point for DTNow, we can define an instance ofECGRecDevice:# define an ECGRecDevice-type device for ecg recordingecg_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 ECGRecDevicenwbfile.add_device(ecg_device)And also an instance ofECGChannelsGroup: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 ECGChannelsGroupnwbfile.add_lab_meta_data(ecg_recording_group)# storage point for custom LMD#Now, we have all the required standard arguments to genearate instances ofCardiacSeriesand stroing them in our three different NWBDataInterfaces:# storing the ECG datadum_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 ourCradiacSeriesto store a dummy raw ECG acquisition into a specifiedECGinterface, and we store it as an acquisition into thenwbfile:# adding the raw acquisition of ECG to the nwb_file inside an 'ECG' containernwbfile.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 ofHeartRateandAuxiliaryAnalysis, then storing it into a -to be defined-ecg_module:# storing the HeartRate datadum_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 analysisecg_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' containerecg_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@UKWdum_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' containerecg_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@UKWdum_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' containerecg_module.add(hr2ceil)Now, thenwbfileis ready to be written on the disk and read back.