diff --git a/docs/release.md b/docs/release.md index 78e8901c..780a8bfb 100644 --- a/docs/release.md +++ b/docs/release.md @@ -14,6 +14,12 @@ ## Unreleased +### Bug fixes + +* Preserve multidimensional array shapes in `FixedScaleOffset` and preserve logical + coordinates when copying equally shaped arrays across different memory orders. + By {user}`shixi-li `, {issue}`852` + ### Maintenance * **Migrate build system from setuptools/setup.py to meson-python.** This replaces the diff --git a/src/numcodecs/compat.py b/src/numcodecs/compat.py index d1844e10..dd3dbe93 100644 --- a/src/numcodecs/compat.py +++ b/src/numcodecs/compat.py @@ -185,13 +185,18 @@ def ndarray_copy(src, dst) -> NDArrayLike: src = ensure_ndarray_like(src) dst = ensure_ndarray_like(dst) - # flatten source array - src = src.reshape(-1, order="A") - # ensure same data type if dst.dtype != object: src = src.view(dst.dtype) + # preserve logical coordinates when equally shaped arrays use different memory orders + if src.shape == dst.shape: + np.copyto(dst, src) + return dst + + # flatten source array + src = src.reshape(-1, order="A") + # reshape source to match destination if src.shape != dst.shape: if dst.flags.f_contiguous: diff --git a/src/numcodecs/fixedscaleoffset.py b/src/numcodecs/fixedscaleoffset.py index b1db5205..9b4b9cc1 100644 --- a/src/numcodecs/fixedscaleoffset.py +++ b/src/numcodecs/fixedscaleoffset.py @@ -84,8 +84,9 @@ def encode(self, buf): # normalise input arr = ensure_ndarray(buf).view(self.dtype) - # flatten to simplify implementation - arr = arr.reshape(-1, order='A') + # preserve the historical one-element shape for scalar inputs + if arr.ndim == 0: + arr = arr.reshape(-1) # compute scale offset enc = (arr - self.offset) * self.scale @@ -100,8 +101,9 @@ def decode(self, buf, out=None): # interpret buffer as numpy array enc = ensure_ndarray(buf).view(self.astype) - # flatten to simplify implementation - enc = enc.reshape(-1, order='A') + # preserve the historical one-element shape for scalar inputs + if enc.ndim == 0: + enc = enc.reshape(-1) # decode scale offset dec = (enc / self.scale) + self.offset diff --git a/tests/test_compat.py b/tests/test_compat.py index 0d5f2d74..ec81b8a0 100644 --- a/tests/test_compat.py +++ b/tests/test_compat.py @@ -4,7 +4,7 @@ import numpy as np import pytest -from numcodecs.compat import ensure_bytes, ensure_contiguous_ndarray, ensure_text +from numcodecs.compat import ensure_bytes, ensure_contiguous_ndarray, ensure_text, ndarray_copy def test_ensure_text(): @@ -109,3 +109,20 @@ def test_ensure_contiguous_ndarray_max_buffer_size(): for buf in buffers: with pytest.raises(ValueError): ensure_contiguous_ndarray(buf, max_buffer_size=max_buffer_size) + + +@pytest.mark.parametrize(("source_order", "destination_order"), [("C", "F"), ("F", "C")]) +@pytest.mark.parametrize("destination_type", ["ndarray", "memoryview"]) +def test_ndarray_copy_same_shape_preserves_logical_coordinates( + source_order, destination_order, destination_type +): + shape = (2, 3, 4) + source = np.arange(np.prod(shape), dtype="