diff --git a/src/pynwb/data/nwb.file.yaml b/src/pynwb/data/nwb.file.yaml index bb20d8e10..9b9e79364 100644 --- a/src/pynwb/data/nwb.file.yaml +++ b/src/pynwb/data/nwb.file.yaml @@ -435,6 +435,10 @@ groups: name: trials doc: repeated experimental events that have a logical grouping quantity: '?' + - neurodata_type_inc: TimeIntervals + name: invalid_times + doc: time intervals that should be removed from analysis + quantity: '?' - neurodata_type_inc: TimeIntervals doc: an optional additional table for describing other experimental time intervals quantity: '*' diff --git a/src/pynwb/file.py b/src/pynwb/file.py index 335306857..88db564bb 100644 --- a/src/pynwb/file.py +++ b/src/pynwb/file.py @@ -168,6 +168,7 @@ class NWBFile(MultiContainerInterface): {'name': 'units', 'child': True, 'required_name': 'units'}, {'name': 'subject', 'child': True, 'required_name': 'subject'}, {'name': 'sweep_table', 'child': True, 'required_name': 'sweep_table'}, + {'name': 'invalid_times', 'child': True, 'required_name': 'invalid_times'}, 'epoch_tags',) @docval({'name': 'session_description', 'type': str, @@ -227,6 +228,8 @@ class NWBFile(MultiContainerInterface): 'doc': 'A sorted list of tags used across all epochs', 'default': set()}, {'name': 'trials', 'type': TimeIntervals, 'doc': 'A table containing trial data', 'default': None}, + {'name': 'invalid_times', 'type': TimeIntervals, + 'doc': 'A table containing times to be omitted from analysis', 'default': None}, {'name': 'time_intervals', 'type': (list, tuple), 'doc': 'any TimeIntervals tables storing time intervals', 'default': None}, {'name': 'units', 'type': DynamicTable, @@ -289,6 +292,9 @@ def __init__(self, **kwargs): trials = getargs('trials', kwargs) if trials is not None: self.trials = trials + invalid_times = getargs('invalid_times', kwargs) + if invalid_times is not None: + self.invalid_times = invalid_times units = getargs('units', kwargs) if units is not None: self.units = units @@ -507,6 +513,30 @@ def add_trial(self, **kwargs): self.__check_trials() call_docval_func(self.trials.add_interval, kwargs) + def __check_invalid_times(self): + if self.invalid_times is None: + self.invalid_times = TimeIntervals('invalid_times', 'time intervals to be removed from analysis') + + @docval(*get_docval(DynamicTable.add_column)) + def add_invalid_times_column(self, **kwargs): + """ + Add a column to the trial table. + See :py:meth:`~pynwb.core.DynamicTable.add_column` for more details + """ + self.__check_invalid_times() + call_docval_func(self.invalid_times.add_column, kwargs) + + def add_invalid_time_interval(self, **kwargs): + """ + Add a trial to the trial table. + See :py:meth:`~pynwb.core.DynamicTable.add_row` for more details. + + Required fields are *start_time*, *stop_time*, and any columns that have + been added (through calls to `add_invalid_times_columns`). + """ + self.__check_invalid_times() + call_docval_func(self.invalid_times.add_interval, kwargs) + @docval({'name': 'electrode_table', 'type': DynamicTable, 'doc': 'the ElectrodeTable for this file'}) def set_electrode_table(self, **kwargs): """ @@ -592,3 +622,7 @@ def ElectrodeTable(name='electrodes', def TrialTable(name='trials', description='metadata about experimental trials'): return _tablefunc(name, description, ['start', 'end']) + + +def InvalidTimesTable(name='invalid_times', description='time intervals to be removed from analysis'): + return _tablefunc(name, description, ['start_time', 'stop_time']) diff --git a/tests/unit/pynwb_tests/test_file.py b/tests/unit/pynwb_tests/test_file.py index 0947c1803..89b76cfc5 100644 --- a/tests/unit/pynwb_tests/test_file.py +++ b/tests/unit/pynwb_tests/test_file.py @@ -176,6 +176,23 @@ def test_add_trial(self): self.nwbfile.add_trial(start_time=50.0, stop_time=70.0) self.assertEqual(len(self.nwbfile.trials), 3) + def test_add_invalid_times_column(self): + self.nwbfile.add_invalid_times_column('comments', 'description of reason for omitting time') + self.assertEqual(self.nwbfile.invalid_times.colnames, ('start_time', 'stop_time', 'comments')) + + def test_add_invalid_time_interval(self): + + self.nwbfile.add_invalid_time_interval(start_time=0.0, stop_time=12.0) + self.assertEqual(len(self.nwbfile.invalid_times), 1) + self.nwbfile.add_invalid_time_interval(start_time=15.0, stop_time=16.0) + self.nwbfile.add_invalid_time_interval(start_time=17.0, stop_time=20.5) + self.assertEqual(len(self.nwbfile.invalid_times), 3) + + def test_add_invalid_time_w_ts(self): + ts = TimeSeries(name='name', data=[1.2], rate=1.0, unit='na') + self.nwbfile.add_invalid_time_interval(start_time=18.0, stop_time=20.6, + timeseries=ts, tags=('hi', 'there')) + def test_add_electrode(self): dev1 = self.nwbfile.create_device('dev1') # noqa: F405 group = self.nwbfile.create_electrode_group('tetrode1',