From c5097e2eba942a07f8d29fa2f962cfe1995b0b45 Mon Sep 17 00:00:00 2001 From: Dante Rigo Date: Fri, 28 Aug 2026 10:53:48 -0700 Subject: [PATCH] Fix ZarrAvgMerger under zarr 3.3: map chunks=True to auto-chunking zarr 3.3 removed boolean chunk arguments, so ZarrAvgMerger's documented default chunks=True raised ValueError on every construction. Map True to None (zarr 3.3's spelling of auto-chunking) when zarr >= 3.3.0; the substitution is version-gated because on zarr 3.0 None means a single whole-shape chunk, not auto-chunking. chunks=False and explicit shapes still work on every version and pass through unchanged. zarr 3.3 also requires the bytes codec to specify endianness for multi-byte dtypes, so the test fixtures' {"name": "bytes", "configuration": {}} configs now set endian little; this form is accepted from zarr 3.0 on. Adds a regression test asserting the default produces auto-chunking rather than one whole-shape chunk. Verified with the full test_zarr_avg_merger suite on zarr 3.0.0, 3.2.0 and 3.3.0. The zarr 2 paths are untouched: the mapping is gated on the zarr version, and the fixture change only affects v3-selected configs. Signed-off-by: Dante Rigo --- monai/inferers/merger.py | 8 ++++++- tests/inferers/test_zarr_avg_merger.py | 30 +++++++++++++++++++++----- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/monai/inferers/merger.py b/monai/inferers/merger.py index 3d07925e457..e55b18402a1 100644 --- a/monai/inferers/merger.py +++ b/monai/inferers/merger.py @@ -307,7 +307,13 @@ def __init__( self.value_store = zarr.storage.TempStore() if value_store is None else value_store # type: ignore self.count_store = zarr.storage.TempStore() if count_store is None else count_store # type: ignore - self.chunks = chunks + # zarr 3.3 rejects boolean chunk arguments; `None` there means auto-chunking, + # which is what `chunks=True` meant before. On zarr < 3.3 `None` instead means + # a single whole-shape chunk, so the substitution must not be applied there. + if chunks is True and version_geq(get_package_version("zarr"), "3.3.0"): + self.chunks: Sequence[int] | bool | None = None + else: + self.chunks = chunks # Initialize codecs/compressor attributes with proper types self.codecs: list | None = None diff --git a/tests/inferers/test_zarr_avg_merger.py b/tests/inferers/test_zarr_avg_merger.py index b4e26d6bebd..e8aff312836 100644 --- a/tests/inferers/test_zarr_avg_merger.py +++ b/tests/inferers/test_zarr_avg_merger.py @@ -195,11 +195,20 @@ ] # Define zarr v3 codec configurations with proper bytes codec -ZARR_V3_LZ4_CODECS = [{"name": "bytes", "configuration": {}}, {"name": "blosc", "configuration": {"cname": "lz4"}}] +ZARR_V3_LZ4_CODECS = [ + {"name": "bytes", "configuration": {"endian": "little"}}, + {"name": "blosc", "configuration": {"cname": "lz4"}}, +] -ZARR_V3_PICKLE_CODECS = [{"name": "bytes", "configuration": {}}, {"name": "blosc", "configuration": {"cname": "zstd"}}] +ZARR_V3_PICKLE_CODECS = [ + {"name": "bytes", "configuration": {"endian": "little"}}, + {"name": "blosc", "configuration": {"cname": "zstd"}}, +] -ZARR_V3_LZMA_CODECS = [{"name": "bytes", "configuration": {}}, {"name": "blosc", "configuration": {"cname": "zlib"}}] +ZARR_V3_LZMA_CODECS = [ + {"name": "bytes", "configuration": {"endian": "little"}}, + {"name": "blosc", "configuration": {"cname": "zlib"}}, +] # test for LZ4 compressor (zarr v2) or codecs (zarr v3) TEST_CASE_13_COMPRESSOR_LZ4 = [ @@ -289,7 +298,10 @@ TEST_CASE_19_VALUE_CODECS = [ dict( merged_shape=TENSOR_4x4.shape, - value_codecs=[{"name": "bytes", "configuration": {}}, {"name": "blosc", "configuration": {"cname": "zstd"}}], + value_codecs=[ + {"name": "bytes", "configuration": {"endian": "little"}}, + {"name": "blosc", "configuration": {"cname": "zstd"}}, + ], ), [ (TENSOR_4x4[..., :2, :2], (0, 0)), @@ -304,7 +316,10 @@ TEST_CASE_20_COUNT_CODECS = [ dict( merged_shape=TENSOR_4x4.shape, - count_codecs=[{"name": "bytes", "configuration": {}}, {"name": "blosc", "configuration": {"cname": "zlib"}}], + count_codecs=[ + {"name": "bytes", "configuration": {"endian": "little"}}, + {"name": "blosc", "configuration": {"cname": "zlib"}}, + ], ), [ (TENSOR_4x4[..., :2, :2], (0, 0)), @@ -449,6 +464,11 @@ def test_zarr_avg_merge_none_merged_shape_error(self): with self.assertRaises(ValueError): ZarrAvgMerger(merged_shape=None, store=self.merged_name) + def test_zarr_avg_merger_default_chunks_auto(self): + """chunks=True must mean auto-chunking, not one whole-shape chunk (zarr 3.3 removed boolean chunks).""" + merger = ZarrAvgMerger(merged_shape=(4096, 4096), store=self.merged_name) + self.assertNotEqual(merger.output.chunks, (4096, 4096)) + if __name__ == "__main__": unittest.main()