From f0b3f08126681964eb16a94a88995ea55509fdea Mon Sep 17 00:00:00 2001 From: rly Date: Tue, 23 Jul 2024 14:45:55 -0700 Subject: [PATCH 01/14] Deprecate ImageMaskSeries --- docs/gallery/general/plot_file.py | 1 - src/pynwb/image.py | 12 ++++++++---- src/pynwb/nwb-schema | 2 +- tests/unit/test_image.py | 10 ++-------- 4 files changed, 11 insertions(+), 14 deletions(-) diff --git a/docs/gallery/general/plot_file.py b/docs/gallery/general/plot_file.py index b410d4b69..830cdc733 100644 --- a/docs/gallery/general/plot_file.py +++ b/docs/gallery/general/plot_file.py @@ -55,7 +55,6 @@ * **Optical physiology and imaging:** :py:class:`~pynwb.image.ImageSeries` is the base type for image recordings and is further refined by the - :py:class:`~pynwb.image.ImageMaskSeries`, :py:class:`~pynwb.image.OpticalSeries`, :py:class:`~pynwb.ophys.OnePhotonSeries`, and :py:class:`~pynwb.ophys.TwoPhotonSeries` types. diff --git a/src/pynwb/image.py b/src/pynwb/image.py index 518ec8a8c..0d7c75cd0 100644 --- a/src/pynwb/image.py +++ b/src/pynwb/image.py @@ -280,7 +280,7 @@ def __init__(self, **kwargs): @register_class('ImageMaskSeries', CORE_NAMESPACE) class ImageMaskSeries(ImageSeries): ''' - An alpha mask that is applied to a presented visual stimulus. The data[] array contains an array + DEPRECATED. An alpha mask that is applied to a presented visual stimulus. The data[] array contains an array of mask values that are applied to the displayed image. Mask values are stored as RGBA. Mask can vary with time. The timestamps array indicates the starting time of a mask, and that mask pattern continues until it's explicitly changed. @@ -299,9 +299,13 @@ class ImageMaskSeries(ImageSeries): 'The device used to capture the masked ImageSeries data should be stored in the ImageSeries.'), 'default': None},) def __init__(self, **kwargs): - masked_imageseries = popargs('masked_imageseries', kwargs) - super().__init__(**kwargs) - self.masked_imageseries = masked_imageseries + raise ValueError( + "This neurodata type is deprecated. If you are interested in using it, " + "please create an issue on https://github.com/NeurodataWithoutBorders/nwb-schema/issues." + ) + # masked_imageseries = popargs('masked_imageseries', kwargs) + # super().__init__(**kwargs) + # self.masked_imageseries = masked_imageseries @register_class('OpticalSeries', CORE_NAMESPACE) diff --git a/src/pynwb/nwb-schema b/src/pynwb/nwb-schema index d65d42257..cc832667b 160000 --- a/src/pynwb/nwb-schema +++ b/src/pynwb/nwb-schema @@ -1 +1 @@ -Subproject commit d65d42257003543c569ea7ac0cd6d7aee01c88d6 +Subproject commit cc832667be9d5016c6d2d0c9f701d1b1694c0c7f diff --git a/tests/unit/test_image.py b/tests/unit/test_image.py index c279101f5..92637d82e 100644 --- a/tests/unit/test_image.py +++ b/tests/unit/test_image.py @@ -375,16 +375,10 @@ def test_init(self): external_file=['external_file'], starting_frame=[0], format='external', timestamps=[1., .2]) - ims = ImageMaskSeries(name='test_ims', unit='unit', + with self.assertRaises(ValueError): + ImageMaskSeries(name='test_ims', unit='unit', masked_imageseries=iS, external_file=['external_file'], starting_frame=[0], format='external', timestamps=[1., 2.]) - self.assertEqual(ims.name, 'test_ims') - self.assertEqual(ims.unit, 'unit') - self.assertIs(ims.masked_imageseries, iS) - self.assertEqual(ims.external_file, ['external_file']) - self.assertEqual(ims.starting_frame, [0]) - self.assertEqual(ims.format, 'external') - class OpticalSeriesConstructor(TestCase): From 48e1466ba2ffad7b108064f7de3e6debb6a2b21a Mon Sep 17 00:00:00 2001 From: Ryan Ly Date: Thu, 19 Sep 2024 22:13:21 -0700 Subject: [PATCH 02/14] Update image.py --- src/pynwb/image.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/pynwb/image.py b/src/pynwb/image.py index 0d7c75cd0..c775297d7 100644 --- a/src/pynwb/image.py +++ b/src/pynwb/image.py @@ -280,7 +280,8 @@ def __init__(self, **kwargs): @register_class('ImageMaskSeries', CORE_NAMESPACE) class ImageMaskSeries(ImageSeries): ''' - DEPRECATED. An alpha mask that is applied to a presented visual stimulus. The data[] array contains an array + DEPRECATED as of NWB 2.8.0 and PyNWB 3.0.0. + An alpha mask that is applied to a presented visual stimulus. The data[] array contains an array of mask values that are applied to the displayed image. Mask values are stored as RGBA. Mask can vary with time. The timestamps array indicates the starting time of a mask, and that mask pattern continues until it's explicitly changed. @@ -299,13 +300,14 @@ class ImageMaskSeries(ImageSeries): 'The device used to capture the masked ImageSeries data should be stored in the ImageSeries.'), 'default': None},) def __init__(self, **kwargs): - raise ValueError( - "This neurodata type is deprecated. If you are interested in using it, " - "please create an issue on https://github.com/NeurodataWithoutBorders/nwb-schema/issues." - ) - # masked_imageseries = popargs('masked_imageseries', kwargs) - # super().__init__(**kwargs) - # self.masked_imageseries = masked_imageseries + if not self._in_construct_mode: + raise ValueError( + "The ImageMaskSeries neurodata type is deprecated. If you are interested in using it, " + "please create an issue on https://github.com/NeurodataWithoutBorders/nwb-schema/issues." + ) + masked_imageseries = popargs('masked_imageseries', kwargs) + super().__init__(**kwargs) + self.masked_imageseries = masked_imageseries @register_class('OpticalSeries', CORE_NAMESPACE) From 7b73185129831770a8c54ed59f88536004f3cfcd Mon Sep 17 00:00:00 2001 From: Ryan Ly Date: Tue, 1 Oct 2024 12:23:57 -0700 Subject: [PATCH 03/14] Add "bounds" to SpatialSeries fields (#1907) --- CHANGELOG.md | 8 +++++++- docs/gallery/domain/plot_behavior.py | 4 ++-- src/pynwb/behavior.py | 10 ++++++---- src/pynwb/nwb-schema | 2 +- tests/integration/hdf5/test_behavior.py | 2 +- tests/unit/test_behavior.py | 4 ++-- 6 files changed, 19 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e5909f577..b12defa2c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # PyNWB Changelog +## PyNWB 3.0.0 (Upcoming) + +### Enhancements and minor changes +- Added support for NWB schema 2.8.0: + - Fixed support for `data__bounds` field to `SpatialSeries` to set optional boundary range (min, max) for each dimension of data. Removed `SpatialSeries.bounds` field that was not functional. @rly [#1907](https://github.com/NeurodataWithoutBorders/pynwb/pull/1907) + ## PyNWB 2.8.3 (Upcoming) ### Performance @@ -43,7 +49,7 @@ ## PyNWB 2.7.0 (May 2, 2024) ### Enhancements and minor changes -- Added `bounds` field to `SpatialSeries` to set optional boundary range (min, max) for each dimension of data. @mavaylon1 [#1869](https://github.com/NeurodataWithoutBorders/pynwb/pull/1869/files) +- Added `bounds` field to `SpatialSeries` to set optional boundary range (min, max) for each dimension of data. @mavaylon1 [#1869](https://github.com/NeurodataWithoutBorders/pynwb/pull/1869) - Added support for NWB schema 2.7.0. See [2.7.0 release notes](https://nwb-schema.readthedocs.io/en/latest/format_release_notes.html) for details - Deprecated `ImagingRetinotopy` neurodata type. @rly [#1813](https://github.com/NeurodataWithoutBorders/pynwb/pull/1813) - Modified `OptogeneticSeries` to allow 2D data, primarily in extensions of `OptogeneticSeries`. @rly [#1812](https://github.com/NeurodataWithoutBorders/pynwb/pull/1812) diff --git a/docs/gallery/domain/plot_behavior.py b/docs/gallery/domain/plot_behavior.py index 35fbae81f..8d5b38d2a 100644 --- a/docs/gallery/domain/plot_behavior.py +++ b/docs/gallery/domain/plot_behavior.py @@ -105,7 +105,7 @@ # # For position data ``reference_frame`` indicates the zero-position, e.g. # the 0,0 point might be the bottom-left corner of an enclosure, as viewed from the tracking camera. -# In :py:class:`~pynwb.behavior.SpatialSeries`, the ``bounds`` field allows the user to set +# In :py:class:`~pynwb.behavior.SpatialSeries`, the ``data__bounds`` field allows the user to set # the boundary range, i.e., (min, max), for each dimension of ``data``. The units are the same as in ``data``. # This field does not enforce a boundary on the dataset itself. @@ -115,7 +115,7 @@ name="SpatialSeries", description="Position (x, y) in an open field.", data=position_data, - bounds=[(0,50), (0,50)], + data__bounds=[(0,50), (0,50)], timestamps=timestamps, reference_frame="(0,0) is bottom left corner", ) diff --git a/src/pynwb/behavior.py b/src/pynwb/behavior.py index 1b3078535..432a86891 100644 --- a/src/pynwb/behavior.py +++ b/src/pynwb/behavior.py @@ -20,13 +20,13 @@ class SpatialSeries(TimeSeries): tracking camera. The unit of data will indicate how to interpret SpatialSeries values. """ - __nwbfields__ = ('reference_frame',) + __nwbfields__ = ('data__bounds', 'reference_frame',) @docval(*get_docval(TimeSeries.__init__, 'name'), # required {'name': 'data', 'type': ('array_data', 'data', TimeSeries), 'shape': ((None, ), (None, None)), # required 'doc': ('The data values. Can be 1D or 2D. The first dimension must be time. If 2D, there can be 1, 2, ' 'or 3 columns, which represent x, y, and z.')}, - {'name': 'bounds', 'type': list, 'shape': ((1, 2), (2, 2), (3, 2)), 'default': None, + {'name': 'data__bounds', 'type': ('data', 'array_data'), 'shape': ((1, 2), (2, 2), (3, 2)), 'default': None, 'doc': 'The boundary range (min, max) for each dimension of data.'}, {'name': 'reference_frame', 'type': str, # required 'doc': 'description defining what the zero-position is'}, @@ -38,7 +38,9 @@ def __init__(self, **kwargs): """ Create a SpatialSeries TimeSeries dataset """ - name, data, bounds, reference_frame, unit = popargs('name', 'data', 'bounds', 'reference_frame', 'unit', kwargs) + name, data, data__bounds, reference_frame, unit = popargs( + 'name', 'data', 'data__bounds', 'reference_frame', 'unit', kwargs + ) super().__init__(name, data, unit, **kwargs) # NWB 2.5 restricts length of second dimension to be <= 3 @@ -49,7 +51,7 @@ def __init__(self, **kwargs): "The second dimension should have length <= 3 to represent at most x, y, z." % (name, str(data_shape))) - self.bounds = bounds + self.data__bounds = data__bounds self.reference_frame = reference_frame @staticmethod diff --git a/src/pynwb/nwb-schema b/src/pynwb/nwb-schema index d65d42257..942672b61 160000 --- a/src/pynwb/nwb-schema +++ b/src/pynwb/nwb-schema @@ -1 +1 @@ -Subproject commit d65d42257003543c569ea7ac0cd6d7aee01c88d6 +Subproject commit 942672b613204d885d1c901f7f5fe996b9216925 diff --git a/tests/integration/hdf5/test_behavior.py b/tests/integration/hdf5/test_behavior.py index d4b230bcc..1525c43c9 100644 --- a/tests/integration/hdf5/test_behavior.py +++ b/tests/integration/hdf5/test_behavior.py @@ -11,7 +11,7 @@ def setUpContainer(self): return SpatialSeries( name='test_sS', data=np.ones((3, 2)), - bounds=[(-1,1),(-1,1),(-1,1)], + data__bounds=[(-1,1),(-1,1),(-1,1)], reference_frame='reference_frame', timestamps=[1., 2., 3.] ) diff --git a/tests/unit/test_behavior.py b/tests/unit/test_behavior.py index 6bcf1a9eb..4d30ca57d 100644 --- a/tests/unit/test_behavior.py +++ b/tests/unit/test_behavior.py @@ -12,13 +12,13 @@ def test_init(self): sS = SpatialSeries( name='test_sS', data=np.ones((3, 2)), - bounds=[(-1,1),(-1,1),(-1,1)], + data__bounds=[(-1,1),(-1,1),(-1,1)], reference_frame='reference_frame', timestamps=[1., 2., 3.] ) self.assertEqual(sS.name, 'test_sS') self.assertEqual(sS.unit, 'meters') - self.assertEqual(sS.bounds, [(-1,1),(-1,1),(-1,1)]) + self.assertEqual(sS.data__bounds, [(-1,1),(-1,1),(-1,1)]) self.assertEqual(sS.reference_frame, 'reference_frame') def test_set_unit(self): From ab9dd9c0d77e248ae27350a82b8ed577e3f9452b Mon Sep 17 00:00:00 2001 From: rly Date: Mon, 18 Nov 2024 15:37:36 -0800 Subject: [PATCH 04/14] Update schema to latest --- src/pynwb/nwb-schema | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pynwb/nwb-schema b/src/pynwb/nwb-schema index 942672b61..473fcc41e 160000 --- a/src/pynwb/nwb-schema +++ b/src/pynwb/nwb-schema @@ -1 +1 @@ -Subproject commit 942672b613204d885d1c901f7f5fe996b9216925 +Subproject commit 473fcc41e871288767cfb37d83315cca7469b9d1 From f05027ec8b67f0574df4418e17cb2d77e099229a Mon Sep 17 00:00:00 2001 From: Ryan Ly Date: Mon, 18 Nov 2024 15:57:20 -0800 Subject: [PATCH 05/14] Temporarily revert fix for SpatialSeries.bounds (#1996) --- CHANGELOG.md | 2 +- docs/gallery/domain/plot_behavior.py | 4 ---- src/pynwb/behavior.py | 9 +++------ tests/integration/hdf5/test_behavior.py | 1 - tests/unit/test_behavior.py | 3 --- 5 files changed, 4 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dfd57f722..2910a19b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ ### Enhancements and minor changes - Added support for NWB schema 2.8.0: - - Fixed support for `data__bounds` field to `SpatialSeries` to set optional boundary range (min, max) for each dimension of data. Removed `SpatialSeries.bounds` field that was not functional. @rly [#1907](https://github.com/NeurodataWithoutBorders/pynwb/pull/1907) + - Removed `SpatialSeries.bounds` field that was not functional. This will be fixed in a future release. @rly [#1907](https://github.com/NeurodataWithoutBorders/pynwb/pull/1907), ## PyNWB 2.8.3 (Upcoming) diff --git a/docs/gallery/domain/plot_behavior.py b/docs/gallery/domain/plot_behavior.py index 8d5b38d2a..8f341bea1 100644 --- a/docs/gallery/domain/plot_behavior.py +++ b/docs/gallery/domain/plot_behavior.py @@ -105,9 +105,6 @@ # # For position data ``reference_frame`` indicates the zero-position, e.g. # the 0,0 point might be the bottom-left corner of an enclosure, as viewed from the tracking camera. -# In :py:class:`~pynwb.behavior.SpatialSeries`, the ``data__bounds`` field allows the user to set -# the boundary range, i.e., (min, max), for each dimension of ``data``. The units are the same as in ``data``. -# This field does not enforce a boundary on the dataset itself. timestamps = np.linspace(0, 50) / 200 @@ -115,7 +112,6 @@ name="SpatialSeries", description="Position (x, y) in an open field.", data=position_data, - data__bounds=[(0,50), (0,50)], timestamps=timestamps, reference_frame="(0,0) is bottom left corner", ) diff --git a/src/pynwb/behavior.py b/src/pynwb/behavior.py index 7a647bc53..d4d43d515 100644 --- a/src/pynwb/behavior.py +++ b/src/pynwb/behavior.py @@ -20,14 +20,12 @@ class SpatialSeries(TimeSeries): tracking camera. The unit of data will indicate how to interpret SpatialSeries values. """ - __nwbfields__ = ('data__bounds', 'reference_frame',) + __nwbfields__ = ('reference_frame',) @docval(*get_docval(TimeSeries.__init__, 'name'), # required {'name': 'data', 'type': ('array_data', 'data', TimeSeries), 'shape': ((None, ), (None, None)), # required 'doc': ('The data values. Can be 1D or 2D. The first dimension must be time. If 2D, there can be 1, 2, ' 'or 3 columns, which represent x, y, and z.')}, - {'name': 'data__bounds', 'type': ('data', 'array_data'), 'shape': ((1, 2), (2, 2), (3, 2)), 'default': None, - 'doc': 'The boundary range (min, max) for each dimension of data.'}, {'name': 'reference_frame', 'type': str, 'doc': 'description defining what the zero-position is', 'default': None}, {'name': 'unit', 'type': str, 'doc': 'The base unit of measurement (should be SI unit)', @@ -38,8 +36,8 @@ def __init__(self, **kwargs): """ Create a SpatialSeries TimeSeries dataset """ - name, data, data__bounds, reference_frame, unit = popargs( - 'name', 'data', 'data__bounds', 'reference_frame', 'unit', kwargs + name, data, reference_frame, unit = popargs( + 'name', 'data', 'reference_frame', 'unit', kwargs ) super().__init__(name, data, unit, **kwargs) @@ -51,7 +49,6 @@ def __init__(self, **kwargs): "The second dimension should have length <= 3 to represent at most x, y, z." % (name, str(data_shape))) - self.data__bounds = data__bounds self.reference_frame = reference_frame @staticmethod diff --git a/tests/integration/hdf5/test_behavior.py b/tests/integration/hdf5/test_behavior.py index 60c6c4324..39770876b 100644 --- a/tests/integration/hdf5/test_behavior.py +++ b/tests/integration/hdf5/test_behavior.py @@ -11,7 +11,6 @@ def setUpContainer(self): return SpatialSeries( name='test_sS', data=np.ones((3, 2)), - data__bounds=[(-1,1),(-1,1),(-1,1)], reference_frame='reference_frame', timestamps=[1., 2., 3.] ) diff --git a/tests/unit/test_behavior.py b/tests/unit/test_behavior.py index 8d5e2d4e5..37a471688 100644 --- a/tests/unit/test_behavior.py +++ b/tests/unit/test_behavior.py @@ -12,13 +12,11 @@ def test_init(self): sS = SpatialSeries( name='test_sS', data=np.ones((3, 2)), - data__bounds=[(-1,1),(-1,1),(-1,1)], reference_frame='reference_frame', timestamps=[1., 2., 3.] ) self.assertEqual(sS.name, 'test_sS') self.assertEqual(sS.unit, 'meters') - self.assertEqual(sS.data__bounds, [(-1,1),(-1,1),(-1,1)]) self.assertEqual(sS.reference_frame, 'reference_frame') def test_init_minimum(self): @@ -27,7 +25,6 @@ def test_init_minimum(self): data=np.ones((3, 2)), timestamps=[1., 2., 3.] ) - assert sS.bounds is None assert sS.reference_frame is None def test_set_unit(self): From 1b4601419244c1912f4cfefb765763d2db3f3050 Mon Sep 17 00:00:00 2001 From: Steph Prince <40640337+stephprince@users.noreply.github.com> Date: Mon, 18 Nov 2024 16:08:01 -0800 Subject: [PATCH 06/14] Add dataset to store list of software that produced a file (#1924) Co-authored-by: Ryan Ly --- CHANGELOG.md | 3 ++- src/pynwb/file.py | 4 ++++ src/pynwb/io/file.py | 1 + tests/integration/hdf5/test_nwbfile.py | 28 ++++++++++++++++++++++++++ tests/unit/test_file.py | 2 ++ 5 files changed, 37 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2910a19b1..02eea11d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,8 @@ ### Enhancements and minor changes - Added support for NWB schema 2.8.0: - - Removed `SpatialSeries.bounds` field that was not functional. This will be fixed in a future release. @rly [#1907](https://github.com/NeurodataWithoutBorders/pynwb/pull/1907), + - Removed `SpatialSeries.bounds` field that was not functional. This will be fixed in a future release. @rly [#1907](https://github.com/NeurodataWithoutBorders/pynwb/pull/1907), [#1996](https://github.com/NeurodataWithoutBorders/pynwb/pull/1996) + - Added support for `NWBFile.was_generated_by` field. @stephprince [#1924](https://github.com/NeurodataWithoutBorders/pynwb/pull/1924) ## PyNWB 2.8.3 (Upcoming) diff --git a/src/pynwb/file.py b/src/pynwb/file.py index 7621df2ac..a447c126d 100644 --- a/src/pynwb/file.py +++ b/src/pynwb/file.py @@ -261,6 +261,7 @@ class NWBFile(MultiContainerInterface, HERDManager): 'slices', 'source_script', 'source_script_file_name', + 'was_generated_by', 'data_collection', 'surgery', 'virus', @@ -339,6 +340,8 @@ class NWBFile(MultiContainerInterface, HERDManager): 'doc': 'Script file used to create this NWB file.', 'default': None}, {'name': 'source_script_file_name', 'type': str, 'doc': 'Name of the source_script file', 'default': None}, + {'name': 'was_generated_by', 'type': 'array_data', + 'doc': 'List of software package names and versions used to generate this NWB File.', 'default': None}, {'name': 'data_collection', 'type': str, 'doc': 'Notes about data collection and analysis.', 'default': None}, {'name': 'surgery', 'type': str, @@ -448,6 +451,7 @@ def __init__(self, **kwargs): 'slices', 'source_script', 'source_script_file_name', + 'was_generated_by', 'surgery', 'virus', 'stimulus_notes', diff --git a/src/pynwb/io/file.py b/src/pynwb/io/file.py index 90e8f36b7..53d257a05 100644 --- a/src/pynwb/io/file.py +++ b/src/pynwb/io/file.py @@ -97,6 +97,7 @@ def __init__(self, spec): 'session_id', 'slices', 'source_script', + 'was_generated_by', 'stimulus', 'surgery', 'virus'] diff --git a/tests/integration/hdf5/test_nwbfile.py b/tests/integration/hdf5/test_nwbfile.py index e164ec649..24a08eb1b 100644 --- a/tests/integration/hdf5/test_nwbfile.py +++ b/tests/integration/hdf5/test_nwbfile.py @@ -42,6 +42,7 @@ def setUp(self): session_id='007', slices='noslices', source_script='nosources', + was_generated_by=[('nosoftware', '0.0.0')], surgery='nosurgery', virus='novirus', source_script_file_name='nofilename') @@ -128,6 +129,7 @@ def build_nwbfile(self): virus='a virus', source_script='noscript', source_script_file_name='nofilename', + was_generated_by=[('nosoftware', '0.0.0')], stimulus_notes='test stimulus notes', data_collection='test data collection notes', keywords=('these', 'are', 'keywords')) @@ -176,6 +178,32 @@ def build_nwbfile(self): self.nwbfile.experimenter = ('experimenter1', 'experimenter2') +class TestWasGeneratedByConstructorRoundtrip(TestNWBFileIO): + """ Test that a list of software packages / versions in a constructor is written to and read from file """ + + def build_nwbfile(self): + description = 'test nwbfile was_generated_by' + identifier = 'TEST_was_generated_by' + self.nwbfile = NWBFile(session_description=description, + identifier=identifier, + session_start_time=self.start_time, + was_generated_by=[('software1', '0.1.0'), + ('software2', '0.2.0'), + ('software3', '0.3.0')],) + +class TestWasGeneratedBySetterRoundtrip(TestNWBFileIO): + """ Test that a single tuple of software versions packages in a setter is written to and read from file """ + + def build_nwbfile(self): + description = 'test nwbfile was_generated_by' + identifier = 'TEST_was_generated_by' + self.nwbfile = NWBFile(session_description=description, + identifier=identifier, + session_start_time=self.start_time) + self.nwbfile.was_generated_by = [('software1', '0.1.0'), + ('software2', '0.2.0'), + ('software3', '0.3.0')] + class TestPublicationsConstructorRoundtrip(TestNWBFileIO): """ Test that a list of multiple publications in a constructor is written to and read from file """ diff --git a/tests/unit/test_file.py b/tests/unit/test_file.py index 8cb3415d9..23f993b02 100644 --- a/tests/unit/test_file.py +++ b/tests/unit/test_file.py @@ -42,6 +42,7 @@ def setUp(self): virus='a virus', source_script='noscript', source_script_file_name='nofilename', + was_generated_by=[('nosoftware', '0.0.0')], stimulus_notes='test stimulus notes', data_collection='test data collection notes', keywords=('these', 'are', 'keywords')) @@ -61,6 +62,7 @@ def test_constructor(self): self.assertEqual(self.nwbfile.related_publications, ('my pubs',)) self.assertEqual(self.nwbfile.source_script, 'noscript') self.assertEqual(self.nwbfile.source_script_file_name, 'nofilename') + self.assertEqual(self.nwbfile.was_generated_by, [('nosoftware', '0.0.0')]) self.assertEqual(self.nwbfile.keywords, ('these', 'are', 'keywords')) self.assertEqual(self.nwbfile.timestamps_reference_time, self.ref_time) From 52a7e6df922ea4e0a93a195805cc7025b0e3d6b5 Mon Sep 17 00:00:00 2001 From: Ryan Ly Date: Mon, 18 Nov 2024 17:29:21 -0800 Subject: [PATCH 07/14] Add `model_number`, `model_name`, and `serial_number` to Device (#1997) --- CHANGELOG.md | 1 + docs/gallery/domain/ecephys.py | 12 ++++++-- docs/gallery/domain/ophys.py | 9 ++++-- src/pynwb/device.py | 43 ++++++++++++++++++++------- tests/integration/hdf5/test_device.py | 11 +++++-- tests/unit/test_device.py | 14 +++++++-- 6 files changed, 69 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 02eea11d0..153600238 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Added support for NWB schema 2.8.0: - Removed `SpatialSeries.bounds` field that was not functional. This will be fixed in a future release. @rly [#1907](https://github.com/NeurodataWithoutBorders/pynwb/pull/1907), [#1996](https://github.com/NeurodataWithoutBorders/pynwb/pull/1996) - Added support for `NWBFile.was_generated_by` field. @stephprince [#1924](https://github.com/NeurodataWithoutBorders/pynwb/pull/1924) + - Added support for `model_number`, `model_name`, and `serial_number` fields to `Device`. @stephprince [#1997](https://github.com/NeurodataWithoutBorders/pynwb/pull/1997) ## PyNWB 2.8.3 (Upcoming) diff --git a/docs/gallery/domain/ecephys.py b/docs/gallery/domain/ecephys.py index 48a979272..30f05a031 100644 --- a/docs/gallery/domain/ecephys.py +++ b/docs/gallery/domain/ecephys.py @@ -77,10 +77,16 @@ # # The electrodes table references a required :py:class:`~pynwb.ecephys.ElectrodeGroup`, which is used to represent a # group of electrodes. Before creating an :py:class:`~pynwb.ecephys.ElectrodeGroup`, you must define a -# :py:class:`~pynwb.device.Device` object using the method :py:meth:`.NWBFile.create_device`. - +# :py:class:`~pynwb.device.Device` object using the method :py:meth:`.NWBFile.create_device`. The fields +# ``description``, ``manufacturer``, ``model_number``, ``model_name``, and ``serial_number`` are optional, but +# recommended. device = nwbfile.create_device( - name="array", description="the best array", manufacturer="Probe Company 9000" + name="array", + description="A 12-channel array with 4 shanks and 3 channels per shank", + manufacturer="Array Technologies", + model_number="PRB_1_4_0480_123", + model_name="Neurovoxels 0.99", + serial_number="1234567890", ) ####################### diff --git a/docs/gallery/domain/ophys.py b/docs/gallery/domain/ophys.py index f8f6da98a..3f42e6c14 100644 --- a/docs/gallery/domain/ophys.py +++ b/docs/gallery/domain/ophys.py @@ -86,12 +86,17 @@ # :align: center # # Create a :py:class:`~pynwb.device.Device` named ``"Microscope"`` in the :py:class:`~pynwb.file.NWBFile` object. Then -# create an :py:class:`~pynwb.ophys.OpticalChannel` named ``"OpticalChannel"``. +# create an :py:class:`~pynwb.ophys.OpticalChannel` named ``"OpticalChannel"``. The fields +# ``description``, ``manufacturer``, ``model_number``, ``model_name``, and ``serial_number`` are optional, but +# recommended. device = nwbfile.create_device( name="Microscope", description="My two-photon microscope", - manufacturer="The best microscope manufacturer", + manufacturer="Loki Labs", + model_number="ABC-123", + model_name="Loki 1.0", + serial_number="1234567890", ) optical_channel = OpticalChannel( name="OpticalChannel", diff --git a/src/pynwb/device.py b/src/pynwb/device.py index 6fcd610c8..f842776ae 100644 --- a/src/pynwb/device.py +++ b/src/pynwb/device.py @@ -10,18 +10,41 @@ class Device(NWBContainer): Metadata about a data acquisition device, e.g., recording system, electrode, microscope. """ - __nwbfields__ = ('name', - 'description', - 'manufacturer') + __nwbfields__ = ( + 'name', + 'description', + 'manufacturer', + 'model_number', + 'model_name', + 'serial_number', + ) - @docval({'name': 'name', 'type': str, 'doc': 'the name of this device'}, - {'name': 'description', 'type': str, - 'doc': 'Description of the device (e.g., model, firmware version, processing software version, etc.)', - 'default': None}, - {'name': 'manufacturer', 'type': str, 'doc': 'the name of the manufacturer of this device', - 'default': None}) + @docval( + {'name': 'name', 'type': str, 'doc': 'the name of this device'}, + {'name': 'description', 'type': str, + 'doc': ("Description of the device as free-form text. If there is any software/firmware associated " + "with the device, the names and versions of those can be added to `NWBFile.was_generated_by`."), + 'default': None}, + {'name': 'manufacturer', 'type': str, + 'doc': ("The name of the manufacturer of the device, e.g., Imec, Plexon, Thorlabs."), + 'default': None}, + {'name': 'model_number', 'type': str, + 'doc': ('The model number (or part/product number) of the device, e.g., PRB_1_4_0480_1, ' + 'PLX-VP-32-15SE(75)-(260-80)(460-10)-300-(1)CON/32m-V, BERGAMO.'), + 'default': None}, + {'name': 'model_name', 'type': str, + 'doc': ('The model name of the device, e.g., Neuropixels 1.0, V-Probe, Bergamo III.'), + 'default': None}, + {'name': 'serial_number', 'type': str, + 'doc': 'The serial number of the device.', + 'default': None}, + ) def __init__(self, **kwargs): - description, manufacturer = popargs('description', 'manufacturer', kwargs) + description, manufacturer, model_number, model_name, serial_number = popargs( + 'description', 'manufacturer', 'model_number', 'model_name', 'serial_number', kwargs) super().__init__(**kwargs) self.description = description self.manufacturer = manufacturer + self.model_number = model_number + self.model_name = model_name + self.serial_number = serial_number diff --git a/tests/integration/hdf5/test_device.py b/tests/integration/hdf5/test_device.py index d59af8f8b..820edbd53 100644 --- a/tests/integration/hdf5/test_device.py +++ b/tests/integration/hdf5/test_device.py @@ -6,9 +6,14 @@ class TestDeviceIO(NWBH5IOMixin, TestCase): def setUpContainer(self): """ Return the test Device to read/write """ - return Device(name='device_name', - description='description', - manufacturer='manufacturer') + return Device( + name='device_name', + description='description', + manufacturer='manufacturer', + model_number='model_number', + model_name='model_name', + serial_number='serial_number', + ) def addContainer(self, nwbfile): """ Add the test Device to the given NWBFile """ diff --git a/tests/unit/test_device.py b/tests/unit/test_device.py index 6e11346dd..305577b53 100644 --- a/tests/unit/test_device.py +++ b/tests/unit/test_device.py @@ -5,10 +5,18 @@ class TestDevice(TestCase): def test_init(self): - device = Device(name='device_name', - description='description', - manufacturer='manufacturer') + device = Device( + name='device_name', + description='description', + manufacturer='manufacturer', + model_number='model_number', + model_name='model_name', + serial_number='serial_number', + ) self.assertEqual(device.name, 'device_name') self.assertEqual(device.description, 'description') self.assertEqual(device.manufacturer, 'manufacturer') + self.assertEqual(device.model_number, 'model_number') + self.assertEqual(device.model_name, 'model_name') + self.assertEqual(device.serial_number, 'serial_number') From f89ce9924fb89da900db5893bcd715f0e99ae9f5 Mon Sep 17 00:00:00 2001 From: rly Date: Tue, 19 Nov 2024 09:11:29 -0800 Subject: [PATCH 08/14] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e4473c1e8..64f2d44cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - Removed `SpatialSeries.bounds` field that was not functional. This will be fixed in a future release. @rly [#1907](https://github.com/NeurodataWithoutBorders/pynwb/pull/1907), [#1996](https://github.com/NeurodataWithoutBorders/pynwb/pull/1996) - Added support for `NWBFile.was_generated_by` field. @stephprince [#1924](https://github.com/NeurodataWithoutBorders/pynwb/pull/1924) - Added support for `model_number`, `model_name`, and `serial_number` fields to `Device`. @stephprince [#1997](https://github.com/NeurodataWithoutBorders/pynwb/pull/1997) + - Deprecated `EventWaveform` neurodata type. @rly [#1940](https://github.com/NeurodataWithoutBorders/pynwb/pull/1940) ## PyNWB 2.8.3 (Upcoming) From 2c44de4ac889963aeaee995a36631579137437a3 Mon Sep 17 00:00:00 2001 From: Ryan Ly Date: Tue, 19 Nov 2024 09:50:39 -0800 Subject: [PATCH 09/14] Deprecate EventWaveform class (#1940) --- docs/gallery/domain/ecephys.py | 2 +- docs/gallery/general/plot_file.py | 4 +--- src/pynwb/ecephys.py | 11 +++++++++-- tests/integration/hdf5/test_ecephys.py | 11 ++++------- tests/unit/test_ecephys.py | 22 ++-------------------- 5 files changed, 17 insertions(+), 33 deletions(-) diff --git a/docs/gallery/domain/ecephys.py b/docs/gallery/domain/ecephys.py index 30f05a031..3e49857a9 100644 --- a/docs/gallery/domain/ecephys.py +++ b/docs/gallery/domain/ecephys.py @@ -317,7 +317,7 @@ # :py:class:`~pynwb.ecephys.ElectricalSeries` objects as :ref:`acquisition ` data, and use # the :py:class:`~pynwb.ecephys.EventDetection` class for identifying the spike events in your raw traces. # If you do not want to store the raw voltage traces and only the waveform 'snippets' surrounding spike events, -# you should use :py:class:`~pynwb.ecephys.SpikeEventSeries` objects. +# you should store the snippets with :py:class:`~pynwb.ecephys.SpikeEventSeries` objects. # # The results of spike sorting (or clustering) should be stored in the top-level :py:class:`~pynwb.misc.Units` table. # The :py:class:`~pynwb.misc.Units` table can contain simply the spike times of sorted units, or you can also include diff --git a/docs/gallery/general/plot_file.py b/docs/gallery/general/plot_file.py index d80db10ac..659defa38 100644 --- a/docs/gallery/general/plot_file.py +++ b/docs/gallery/general/plot_file.py @@ -92,7 +92,6 @@ :py:class:`~pynwb.behavior.EyeTracking`. * **Extracellular electrophysiology:** :py:class:`~pynwb.ecephys.EventDetection`, - :py:class:`~pynwb.ecephys.EventWaveform`, :py:class:`~pynwb.ecephys.FeatureExtraction`, :py:class:`~pynwb.ecephys.FilteredEphys`, :py:class:`~pynwb.ecephys.LFP`. @@ -593,8 +592,7 @@ #################### # .. [#] Some data interface objects have a default name. This default name is the type of the data interface. For -# example, the default name for :py:class:`~pynwb.ophys.ImageSegmentation` is "ImageSegmentation" and the default -# name for :py:class:`~pynwb.ecephys.EventWaveform` is "EventWaveform". +# example, the default name for :py:class:`~pynwb.ophys.ImageSegmentation` is "ImageSegmentation". # # .. [#] HDF5 is the primary backend supported by NWB. # diff --git a/src/pynwb/ecephys.py b/src/pynwb/ecephys.py index e6dadbe97..07d584a4f 100644 --- a/src/pynwb/ecephys.py +++ b/src/pynwb/ecephys.py @@ -120,8 +120,7 @@ class SpikeEventSeries(ElectricalSeries): """ Stores "snapshots" of spike events (i.e., threshold crossings) in data. This may also be raw data, as reported by ephys hardware. If so, the TimeSeries::description field should describing how - events were detected. All SpikeEventSeries should reside in a module (under EventWaveform - interface) even if the spikes were reported and stored by hardware. All events span the same + events were detected. All events span the same recording channels and store snapshots of equal duration. TimeSeries::data array structure: [num events] [num channels] [num samples] (or [num events] [num samples] for single electrode). @@ -181,6 +180,7 @@ def __init__(self, **kwargs): @register_class('EventWaveform', CORE_NAMESPACE) class EventWaveform(MultiContainerInterface): """ + DEPRECATED as of NWB 2.8.0 and PyNWB 3.0.0. Spike data for spike events detected in raw data stored in this NWBFile, or events detect at acquisition """ @@ -193,6 +193,13 @@ class EventWaveform(MultiContainerInterface): 'create': 'create_spike_event_series' } + def __init__(self, **kwargs): + if not self._in_construct_mode: + raise ValueError( + "The EventWaveform neurodata type is deprecated. If you are interested in using it, " + "please create an issue on https://github.com/NeurodataWithoutBorders/nwb-schema/issues." + ) + @register_class('Clustering', CORE_NAMESPACE) class Clustering(NWBDataInterface): diff --git a/tests/integration/hdf5/test_ecephys.py b/tests/integration/hdf5/test_ecephys.py index c44725277..754c2a8f4 100644 --- a/tests/integration/hdf5/test_ecephys.py +++ b/tests/integration/hdf5/test_ecephys.py @@ -9,7 +9,6 @@ Clustering, ClusterWaveforms, SpikeEventSeries, - EventWaveform, EventDetection, FeatureExtraction, ) @@ -185,7 +184,7 @@ def roundtripExportContainer(self, cache_spec=False): return super().roundtripExportContainer(cache_spec) -class EventWaveformConstructor(NWBH5IOFlexMixin, TestCase): +class SpikeEventSeriesConstructor(NWBH5IOFlexMixin, TestCase): def getContainerType(self): return "SpikeEventSeries" @@ -202,18 +201,16 @@ def addContainer(self): description='the first and third electrodes', table=table) ses = SpikeEventSeries( - name='test_sES', + name='SpikeEventSeries', data=((1, 1), (2, 2), (3, 3)), timestamps=[0., 1., 2.], electrodes=region ) - ew = EventWaveform() - self.nwbfile.add_acquisition(ew) - ew.add_spike_event_series(ses) + self.nwbfile.add_acquisition(ses) def getContainer(self, nwbfile: NWBFile): - return nwbfile.acquisition['EventWaveform'] + return nwbfile.acquisition['SpikeEventSeries'] class ClusterWaveformsConstructor(AcquisitionH5IOMixin, TestCase): diff --git a/tests/unit/test_ecephys.py b/tests/unit/test_ecephys.py index 1415c3d30..0400916d3 100644 --- a/tests/unit/test_ecephys.py +++ b/tests/unit/test_ecephys.py @@ -267,27 +267,9 @@ def test_init(self): class EventWaveformConstructor(TestCase): - def _create_table_and_region(self): - table = make_electrode_table() - region = DynamicTableRegion( - name='electrodes', - data=[0, 2], - description='the first and third electrodes', - table=table - ) - return table, region - def test_init(self): - table, region = self._create_table_and_region() - sES = SpikeEventSeries('test_sES', list(range(10)), list(range(10)), region) - - pm = ProcessingModule(name='test_module', description='a test module') - ew = EventWaveform() - pm.add(table) - pm.add(ew) - ew.add_spike_event_series(sES) - self.assertEqual(ew.spike_event_series['test_sES'], sES) - self.assertEqual(ew['test_sES'], ew.spike_event_series['test_sES']) + with self.assertRaises(ValueError): + EventWaveform() class ClusteringConstructor(TestCase): From 6aa9376f2242326085080f39d74a9d1ec0d096c6 Mon Sep 17 00:00:00 2001 From: rly Date: Tue, 19 Nov 2024 10:03:03 -0800 Subject: [PATCH 10/14] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 64f2d44cf..45e2fd394 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - Added support for `NWBFile.was_generated_by` field. @stephprince [#1924](https://github.com/NeurodataWithoutBorders/pynwb/pull/1924) - Added support for `model_number`, `model_name`, and `serial_number` fields to `Device`. @stephprince [#1997](https://github.com/NeurodataWithoutBorders/pynwb/pull/1997) - Deprecated `EventWaveform` neurodata type. @rly [#1940](https://github.com/NeurodataWithoutBorders/pynwb/pull/1940) + - Deprecated `ImageMaskSeries` neurodata type. @rly [#1941](https://github.com/NeurodataWithoutBorders/pynwb/pull/1941) ## PyNWB 2.8.3 (Upcoming) From 69bc360b537a169a5a0347243668235d5074aea8 Mon Sep 17 00:00:00 2001 From: Ryan Ly Date: Tue, 19 Nov 2024 11:17:38 -0800 Subject: [PATCH 11/14] Deprecate ImageMaskSeries (#1941) --- docs/gallery/general/plot_file.py | 1 - src/pynwb/image.py | 6 ++++++ tests/unit/test_image.py | 10 ++-------- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/docs/gallery/general/plot_file.py b/docs/gallery/general/plot_file.py index 659defa38..2691b2705 100644 --- a/docs/gallery/general/plot_file.py +++ b/docs/gallery/general/plot_file.py @@ -55,7 +55,6 @@ * **Optical physiology and imaging:** :py:class:`~pynwb.image.ImageSeries` is the base type for image recordings and is further refined by the - :py:class:`~pynwb.image.ImageMaskSeries`, :py:class:`~pynwb.image.OpticalSeries`, :py:class:`~pynwb.ophys.OnePhotonSeries`, and :py:class:`~pynwb.ophys.TwoPhotonSeries` types. diff --git a/src/pynwb/image.py b/src/pynwb/image.py index 518ec8a8c..c775297d7 100644 --- a/src/pynwb/image.py +++ b/src/pynwb/image.py @@ -280,6 +280,7 @@ def __init__(self, **kwargs): @register_class('ImageMaskSeries', CORE_NAMESPACE) class ImageMaskSeries(ImageSeries): ''' + DEPRECATED as of NWB 2.8.0 and PyNWB 3.0.0. An alpha mask that is applied to a presented visual stimulus. The data[] array contains an array of mask values that are applied to the displayed image. Mask values are stored as RGBA. Mask can vary with time. The timestamps array indicates the starting time of a mask, and that mask @@ -299,6 +300,11 @@ class ImageMaskSeries(ImageSeries): 'The device used to capture the masked ImageSeries data should be stored in the ImageSeries.'), 'default': None},) def __init__(self, **kwargs): + if not self._in_construct_mode: + raise ValueError( + "The ImageMaskSeries neurodata type is deprecated. If you are interested in using it, " + "please create an issue on https://github.com/NeurodataWithoutBorders/nwb-schema/issues." + ) masked_imageseries = popargs('masked_imageseries', kwargs) super().__init__(**kwargs) self.masked_imageseries = masked_imageseries diff --git a/tests/unit/test_image.py b/tests/unit/test_image.py index c279101f5..92637d82e 100644 --- a/tests/unit/test_image.py +++ b/tests/unit/test_image.py @@ -375,16 +375,10 @@ def test_init(self): external_file=['external_file'], starting_frame=[0], format='external', timestamps=[1., .2]) - ims = ImageMaskSeries(name='test_ims', unit='unit', + with self.assertRaises(ValueError): + ImageMaskSeries(name='test_ims', unit='unit', masked_imageseries=iS, external_file=['external_file'], starting_frame=[0], format='external', timestamps=[1., 2.]) - self.assertEqual(ims.name, 'test_ims') - self.assertEqual(ims.unit, 'unit') - self.assertIs(ims.masked_imageseries, iS) - self.assertEqual(ims.external_file, ['external_file']) - self.assertEqual(ims.starting_frame, [0]) - self.assertEqual(ims.format, 'external') - class OpticalSeriesConstructor(TestCase): From 9f6182eb5504dc92faa1072ea802f35295085f64 Mon Sep 17 00:00:00 2001 From: Ryan Ly Date: Tue, 19 Nov 2024 16:55:53 -0800 Subject: [PATCH 12/14] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 45e2fd394..409e470c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ## PyNWB 3.0.0 (Upcoming) ### Enhancements and minor changes -- Added support for NWB schema 2.8.0: +- Added support for NWB schema 2.8.0. @rly [#2001](https://github.com/NeurodataWithoutBorders/pynwb/pull/2001), - Removed `SpatialSeries.bounds` field that was not functional. This will be fixed in a future release. @rly [#1907](https://github.com/NeurodataWithoutBorders/pynwb/pull/1907), [#1996](https://github.com/NeurodataWithoutBorders/pynwb/pull/1996) - Added support for `NWBFile.was_generated_by` field. @stephprince [#1924](https://github.com/NeurodataWithoutBorders/pynwb/pull/1924) - Added support for `model_number`, `model_name`, and `serial_number` fields to `Device`. @stephprince [#1997](https://github.com/NeurodataWithoutBorders/pynwb/pull/1997) From 9fe6239736b2907d848da7838c4b53f00e142895 Mon Sep 17 00:00:00 2001 From: Ryan Ly Date: Tue, 19 Nov 2024 16:56:04 -0800 Subject: [PATCH 13/14] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 409e470c1..b6cc2d7ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ## PyNWB 3.0.0 (Upcoming) ### Enhancements and minor changes -- Added support for NWB schema 2.8.0. @rly [#2001](https://github.com/NeurodataWithoutBorders/pynwb/pull/2001), +- Added support for NWB schema 2.8.0. @rly [#2001](https://github.com/NeurodataWithoutBorders/pynwb/pull/2001) - Removed `SpatialSeries.bounds` field that was not functional. This will be fixed in a future release. @rly [#1907](https://github.com/NeurodataWithoutBorders/pynwb/pull/1907), [#1996](https://github.com/NeurodataWithoutBorders/pynwb/pull/1996) - Added support for `NWBFile.was_generated_by` field. @stephprince [#1924](https://github.com/NeurodataWithoutBorders/pynwb/pull/1924) - Added support for `model_number`, `model_name`, and `serial_number` fields to `Device`. @stephprince [#1997](https://github.com/NeurodataWithoutBorders/pynwb/pull/1997) From 8834110cb1b464d79c3848b871e0558ab9fd0dd7 Mon Sep 17 00:00:00 2001 From: rly Date: Sun, 24 Nov 2024 23:15:37 -0800 Subject: [PATCH 14/14] Update schema to 2.8.0 tag --- src/pynwb/nwb-schema | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pynwb/nwb-schema b/src/pynwb/nwb-schema index 473fcc41e..54f4980e3 160000 --- a/src/pynwb/nwb-schema +++ b/src/pynwb/nwb-schema @@ -1 +1 @@ -Subproject commit 473fcc41e871288767cfb37d83315cca7469b9d1 +Subproject commit 54f4980e3be54f2c05582371d25b76074e160d40