diff --git a/changes/4238.bugfix.md b/changes/4238.bugfix.md new file mode 100644 index 0000000000..6c7b4260d6 --- /dev/null +++ b/changes/4238.bugfix.md @@ -0,0 +1,3 @@ +The codec *cast_value* expects a contiguous numpy array to work properly. +It used to break when place before or after a *transpose* codec, which produces non-contiguous arrays. +This fix resolves the issue. diff --git a/src/zarr/codecs/cast_value.py b/src/zarr/codecs/cast_value.py index eb8a4de248..4faec27fbc 100644 --- a/src/zarr/codecs/cast_value.py +++ b/src/zarr/codecs/cast_value.py @@ -317,7 +317,7 @@ def _do_cast( to_tgt = int if np.issubdtype(target_dtype, np.integer) else float scalar_map_entries = {to_src(k): to_tgt(v) for k, v in scalar_map.items()} return cast_array_rs( # type: ignore[no-any-return] - arr, + np.ascontiguousarray(arr), target_dtype=target_dtype, rounding_mode=self.rounding, out_of_range_mode=self.out_of_range, diff --git a/tests/test_codecs/test_cast_value.py b/tests/test_codecs/test_cast_value.py index c43edb76e8..58a8d6733f 100644 --- a/tests/test_codecs/test_cast_value.py +++ b/tests/test_codecs/test_cast_value.py @@ -4,10 +4,13 @@ import numpy as np import pytest +from numpy.testing import assert_array_equal import zarr from tests.conftest import Expect, ExpectFail +from zarr.codecs import BytesCodec, TransposeCodec from zarr.codecs.cast_value import CastValue +from zarr.storage import MemoryStore try: import cast_value_rs # noqa: F401 @@ -477,3 +480,45 @@ def test_parse_scalar_map(case: Expect[Any, Any]) -> None: from zarr.codecs.cast_value import parse_scalar_map assert parse_scalar_map(case.input) == case.output + + +@requires_cast_value_rs +def test_enforce_contiguous_arrays() -> None: + """ + Transpose codec produces non-contiguous arrays. + Ensure cast_value makes them contiguous before processing. + """ + data = np.arange(20, dtype=np.float32).reshape(5, 2, 2) + + def make_array(filters: list[Any]) -> Any: + return zarr.create_array( + store=MemoryStore(), + shape=data.shape, + dtype=data.dtype, + chunks=data.shape, + filters=filters, + serializer=BytesCodec(endian="little"), + compressors=None, + zarr_format=3, + ) + + # Cast before transpose + array = make_array( + [ + CastValue(data_type="uint16"), + TransposeCodec(order=(1, 2, 0)), + ] + ) + array[:] = data + assert_array_equal(array[:], data) + + # Cast after transpose + array = make_array( + [ + TransposeCodec(order=(1, 2, 0)), + CastValue(data_type="uint16"), + ] + ) + + array[:] = data + assert_array_equal(array[:], data)