Skip to content
3 changes: 3 additions & 0 deletions changes/4238.bugfix.md
Original file line number Diff line number Diff line change
@@ -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.
4 changes: 3 additions & 1 deletion src/zarr/codecs/cast_value.py
Original file line number Diff line number Diff line change
Expand Up @@ -316,8 +316,10 @@ def _do_cast(
to_src = int if np.issubdtype(src_dtype, np.integer) else float
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()}
# NOTE: the use of np.ascontiguousarray below makes a copy when
# the input is not contiguous.
return cast_array_rs( # type: ignore[no-any-return]
Comment thread
d-v-b marked this conversation as resolved.
arr,
np.ascontiguousarray(arr),
target_dtype=target_dtype,
rounding_mode=self.rounding,
out_of_range_mode=self.out_of_range,
Expand Down
45 changes: 45 additions & 0 deletions tests/test_codecs/test_cast_value.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we parametrize this test over the shape of the array? Specifically, we need to test 0-dimensional arrays, because I think the current fix will not work for 0-dim arrays, and we need to do np.asarray(order="C") instead of np.ascontiguousarray.


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)
Loading