diff --git a/CHANGELOG.md b/CHANGELOG.md index 51a4cc38a..52f69b02b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ - Fixed `mock_DeviceModel` defaulting `manufacturer` to `None`. The mock now defaults it to `"manufacturer"`. @HugoFara [#2232](https://github.com/NeurodataWithoutBorders/pynwb/pull/2232) - Fixed reading a file whose dates carry a sub-minute UTC offset (e.g. `1900-10-01T00:00:00-05:50:36`). @h-mayorquin [#2230](https://github.com/NeurodataWithoutBorders/pynwb/pull/2230) - Fixed wide pandas DataFrames in the tutorials spilling out of the content column and into the right margin. @bendichter [#2236](https://github.com/NeurodataWithoutBorders/pynwb/pull/2236) +- Fixed `set_data_io` being silently ignored on `NWBData` subclasses (`GrayscaleImage`, `RGBImage`, `RGBAImage`, `ExternalImage`, `ImageReferences`, and `ScratchData`), so requested chunking and compression were dropped without warning and the datasets were written uncompressed. @h-mayorquin [#2233](https://github.com/NeurodataWithoutBorders/pynwb/pull/2233) ## PyNWB 4.1.0 (July 23, 2026) diff --git a/src/pynwb/core.py b/src/pynwb/core.py index 085495316..04de129f5 100644 --- a/src/pynwb/core.py +++ b/src/pynwb/core.py @@ -1,7 +1,5 @@ from warnings import warn -import numpy as np - from hdmf import Container, Data from hdmf.container import AbstractContainer, MultiContainerInterface as hdmf_MultiContainerInterface, Table from hdmf.common import DynamicTable, DynamicTableRegion # noqa: F401 @@ -102,50 +100,6 @@ class NWBData(NWBMixin, Data): allow_positional=AllowPositional.WARNING,) def __init__(self, **kwargs): super().__init__(**kwargs) - self.__data = kwargs['data'] - - @property - def data(self): - """The data managed by this object""" - return self.__data - - def __len__(self): - """Size of the data. Same as len(self.data)""" - return len(self.__data) - - def __getitem__(self, args): - if isinstance(self.data, (tuple, list)) and isinstance(args, (tuple, list)): - return [self.data[i] for i in args] - return self.data[args] - - def append(self, arg): - """ - Append a single element to the data - - Note: The arg to append should be 1 dimension less than the data. - For example, if the data is a 2D array, arg should be a 1D array. - Appending to scalar data is not supported. To append multiple - elements, use extend. - """ - if isinstance(self.data, list): - self.data.append(arg) - elif isinstance(self.data, np.ndarray): - self.__data = np.concatenate((self.__data, [arg])) - else: - msg = "NWBData cannot append to object of type '%s'" % type(self.__data) - raise ValueError(msg) - - def extend(self, arg): - """ - Extend the data with multiple elements. - """ - if isinstance(self.data, list): - self.data.extend(arg) - elif isinstance(self.data, np.ndarray): - self.__data = np.concatenate((self.__data, arg)) - else: - msg = "NWBData cannot extend object of type '%s'" % type(self.__data) - raise ValueError(msg) @register_class('ScratchData', CORE_NAMESPACE) diff --git a/tests/unit/test_core.py b/tests/unit/test_core.py index eefe28c84..fd4be2cb5 100644 --- a/tests/unit/test_core.py +++ b/tests/unit/test_core.py @@ -2,10 +2,11 @@ from dateutil.tz import tzlocal import numpy as np +from hdmf.backends.hdf5 import H5DataIO from hdmf.utils import docval from pynwb import NWBFile, TimeSeries, available_namespaces -from pynwb.core import NWBContainer, NWBData +from pynwb.core import NWBContainer, NWBData, ScratchData from pynwb.testing import TestCase @@ -99,7 +100,24 @@ def test_extend_scalar(self): obj = NWBData(name="obj1", data=1) with self.assertRaises(ValueError): obj.extend(2) - + + def test_set_data_io_visible_through_data(self): + """set_data_io is inherited from Data, so its wrapping must be visible on NWBData.data. + + NWBData used to declare its own data storage, which shadowed the parent's, so the DataIO + was applied to the parent attribute and then never read back. See #2233. + """ + obj = MyNWBData("obj1", data=np.array([1, 2, 3])) + obj.set_data_io(H5DataIO, dict(compression="gzip")) + self.assertIsInstance(obj.data, H5DataIO) + self.assertEqual(obj.data.io_settings["compression"], "gzip") + + def test_set_data_io_visible_through_data_on_scratch_data(self): + """The shadow was on NWBData, so every subclass was affected, not only the image types.""" + obj = ScratchData(name="obj1", data=np.array([1, 2, 3]), description="test") + obj.set_data_io(H5DataIO, dict(compression="gzip")) + self.assertIsInstance(obj.data, H5DataIO) + def test_slicing_list_with_list(self): obj = MyNWBData("obj1", data=[[1, 2, 3], [4, 5, 6]]) self.assertEqual(obj[[1,]], [[4, 5, 6]])