diff --git a/CHANGELOG.md b/CHANGELOG.md index 625a7e786..36bd0a35d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,11 +3,12 @@ ## PyNWB 2.7.0 (Upcoming) ### 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 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) - - Support `stimulus_template` as optional predefined column in `IntracellularStimuliTable`. @stephprince [#1815](https://github.com/NeurodataWithoutBorders/pynwb/pull/1815) - - Support `NWBDataInterface` and `DynamicTable` in `NWBFile.stimulus`. @rly [#1842](https://github.com/NeurodataWithoutBorders/pynwb/pull/1842) +- 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) +- Support `stimulus_template` as optional predefined column in `IntracellularStimuliTable`. @stephprince [#1815](https://github.com/NeurodataWithoutBorders/pynwb/pull/1815) +- Support `NWBDataInterface` and `DynamicTable` in `NWBFile.stimulus`. @rly [#1842](https://github.com/NeurodataWithoutBorders/pynwb/pull/1842) - Added support for python 3.12 and upgraded dependency versions. This also includes infrastructure updates for developers. @mavaylon1 [#1853](https://github.com/NeurodataWithoutBorders/pynwb/pull/1853) - Added `grid_spacing`, `grid_spacing_unit`, `origin_coords`, `origin_coords_unit` to `ImagingPlane` fields. @h-mayorquin [#1892](https://github.com/NeurodataWithoutBorders/pynwb/pull/1892) - Added `mock_Units` for generating Units tables. @h-mayorquin [#1875](https://github.com/NeurodataWithoutBorders/pynwb/pull/1875) and [#1883](https://github.com/NeurodataWithoutBorders/pynwb/pull/1883) diff --git a/docs/gallery/domain/plot_behavior.py b/docs/gallery/domain/plot_behavior.py index 8f341bea1..35fbae81f 100644 --- a/docs/gallery/domain/plot_behavior.py +++ b/docs/gallery/domain/plot_behavior.py @@ -105,6 +105,9 @@ # # 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 +# 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 @@ -112,6 +115,7 @@ name="SpatialSeries", description="Position (x, y) in an open field.", data=position_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 b9388e8df..1b3078535 100644 --- a/src/pynwb/behavior.py +++ b/src/pynwb/behavior.py @@ -23,9 +23,11 @@ class SpatialSeries(TimeSeries): __nwbfields__ = ('reference_frame',) @docval(*get_docval(TimeSeries.__init__, 'name'), # required - {'name': 'data', 'type': ('array_data', 'data', TimeSeries), 'shape': ((None, ), (None, None)), # 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, + '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'}, {'name': 'unit', 'type': str, 'doc': 'The base unit of measurement (should be SI unit)', @@ -36,7 +38,7 @@ def __init__(self, **kwargs): """ Create a SpatialSeries TimeSeries dataset """ - name, data, reference_frame, unit = popargs('name', 'data', 'reference_frame', 'unit', kwargs) + name, data, bounds, reference_frame, unit = popargs('name', 'data', 'bounds', 'reference_frame', 'unit', kwargs) super().__init__(name, data, unit, **kwargs) # NWB 2.5 restricts length of second dimension to be <= 3 @@ -47,6 +49,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.reference_frame = reference_frame @staticmethod diff --git a/tests/integration/hdf5/test_behavior.py b/tests/integration/hdf5/test_behavior.py new file mode 100644 index 000000000..d4b230bcc --- /dev/null +++ b/tests/integration/hdf5/test_behavior.py @@ -0,0 +1,17 @@ +import numpy as np + +from pynwb.behavior import SpatialSeries +from pynwb.testing import AcquisitionH5IOMixin, TestCase + + +class TestSpatialSeriesIO(AcquisitionH5IOMixin, TestCase): + + def setUpContainer(self): + """ Return the test TimeSeries to read/write """ + return SpatialSeries( + name='test_sS', + data=np.ones((3, 2)), + 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 0b7173da0..6bcf1a9eb 100644 --- a/tests/unit/test_behavior.py +++ b/tests/unit/test_behavior.py @@ -12,11 +12,13 @@ def test_init(self): sS = SpatialSeries( name='test_sS', data=np.ones((3, 2)), + 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.reference_frame, 'reference_frame') def test_set_unit(self):