Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changes/4351.bugfix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Missing Zarr format 3 imagecodecs now name `imagecodecs-zarr` as a known provider, alongside `virtual-tiff` where both packages register the codec.
107 changes: 88 additions & 19 deletions src/zarr/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,31 +53,100 @@
#
# The two Zarr formats resolve codecs through different registries, so they get different
# tables: a name can mean one thing as a Zarr format 3 codec name and another as a Zarr
# format 2 codec id. `imagecodecs_*` is exactly that -- `virtual-tiff` declares 15 of those
# names under `zarr.codecs`, while `imagecodecs-numcodecs` declares all 81 under
# `numcodecs.codecs`, so the format 2 side can use a prefix and the format 3 side cannot.
# format 2 codec id. For example, `imagecodecs_*` names are registered by
# `imagecodecs-zarr` and `virtual-tiff` under `zarr.codecs`, and by
# `imagecodecs-numcodecs` under `numcodecs.codecs`.

# Zarr format 3 codec names (entry point group "zarr.codecs").
_CODEC_PACKAGES: dict[str, tuple[str, ...]] = {
"gribberish": ("gribberish",),
# `virtual-tiff` declares these 15 `imagecodecs_*` names, out of the 81 that exist as
# numcodecs ids. They are listed exactly rather than by prefix so that the other 66 get no
# hint instead of a hint pointing at a package that does not provide them.
"imagecodecs_deflate": ("virtual-tiff",),
"imagecodecs_delta": ("virtual-tiff",),
"imagecodecs_floatpred": ("virtual-tiff",),
# Verified against imagecodecs-zarr 2026.8.16's published zarr.codecs entry points:
# https://pypi.org/project/imagecodecs-zarr/2026.8.16/
# virtual-tiff also provides 13 of these names, plus jpeg8 and jetraw.
# Keep exact names: neither package provides every possible imagecodecs_* name.
"imagecodecs_aec": ("imagecodecs-zarr",),
"imagecodecs_apng": ("imagecodecs-zarr",),
"imagecodecs_avif": ("imagecodecs-zarr",),
"imagecodecs_b2nd": ("imagecodecs-zarr",),
"imagecodecs_bfloat16": ("imagecodecs-zarr",),
"imagecodecs_bitorder": ("imagecodecs-zarr",),
"imagecodecs_bitshuffle": ("imagecodecs-zarr",),
"imagecodecs_blosc": ("imagecodecs-zarr",),
"imagecodecs_blosc2": ("imagecodecs-zarr",),
"imagecodecs_bmp": ("imagecodecs-zarr",),
"imagecodecs_brotli": ("imagecodecs-zarr",),
"imagecodecs_byteshuffle": ("imagecodecs-zarr",),
"imagecodecs_bz2": ("imagecodecs-zarr",),
"imagecodecs_ccittfax3": ("imagecodecs-zarr",),
"imagecodecs_ccittfax4": ("imagecodecs-zarr",),
"imagecodecs_ccittrle": ("imagecodecs-zarr",),
"imagecodecs_checksum": ("imagecodecs-zarr",),
"imagecodecs_chunked": ("imagecodecs-zarr",),
"imagecodecs_cms": ("imagecodecs-zarr",),
"imagecodecs_dds": ("imagecodecs-zarr",),
"imagecodecs_deflate": ("imagecodecs-zarr", "virtual-tiff"),
"imagecodecs_delta": ("imagecodecs-zarr", "virtual-tiff"),
"imagecodecs_dicomrle": ("imagecodecs-zarr",),
"imagecodecs_eer": ("imagecodecs-zarr",),
"imagecodecs_exr": ("imagecodecs-zarr",),
"imagecodecs_float24": ("imagecodecs-zarr",),
"imagecodecs_floatpred": ("imagecodecs-zarr", "virtual-tiff"),
"imagecodecs_gif": ("imagecodecs-zarr",),
"imagecodecs_hcomp": ("imagecodecs-zarr",),
"imagecodecs_heif": ("imagecodecs-zarr",),
"imagecodecs_htj2k": ("imagecodecs-zarr",),
"imagecodecs_isal": ("imagecodecs-zarr",),
"imagecodecs_jetraw": ("virtual-tiff",),
"imagecodecs_jpeg": ("virtual-tiff",),
"imagecodecs_jpeg2k": ("virtual-tiff",),
"imagecodecs_jpeg": ("imagecodecs-zarr", "virtual-tiff"),
"imagecodecs_jpeg2k": ("imagecodecs-zarr", "virtual-tiff"),
"imagecodecs_jpeg8": ("virtual-tiff",),
"imagecodecs_jpegxl": ("virtual-tiff",),
"imagecodecs_jpegxr": ("virtual-tiff",),
"imagecodecs_lerc": ("virtual-tiff",),
"imagecodecs_lzw": ("virtual-tiff",),
"imagecodecs_packbits": ("virtual-tiff",),
"imagecodecs_png": ("virtual-tiff",),
"imagecodecs_webp": ("virtual-tiff",),
"imagecodecs_zstd": ("virtual-tiff",),
"imagecodecs_jpegls": ("imagecodecs-zarr",),
"imagecodecs_jpegxl": ("imagecodecs-zarr", "virtual-tiff"),
"imagecodecs_jpegxr": ("imagecodecs-zarr", "virtual-tiff"),
"imagecodecs_jpegxs": ("imagecodecs-zarr",),
"imagecodecs_lerc": ("imagecodecs-zarr", "virtual-tiff"),
"imagecodecs_ljpeg": ("imagecodecs-zarr",),
"imagecodecs_lz4": ("imagecodecs-zarr",),
"imagecodecs_lz4f": ("imagecodecs-zarr",),
"imagecodecs_lz4h5": ("imagecodecs-zarr",),
"imagecodecs_lzf": ("imagecodecs-zarr",),
"imagecodecs_lzfse": ("imagecodecs-zarr",),
"imagecodecs_lzham": ("imagecodecs-zarr",),
"imagecodecs_lzma": ("imagecodecs-zarr",),
"imagecodecs_lzo": ("imagecodecs-zarr",),
"imagecodecs_lzw": ("imagecodecs-zarr", "virtual-tiff"),
"imagecodecs_meshopt": ("imagecodecs-zarr",),
"imagecodecs_openzl": ("imagecodecs-zarr",),
"imagecodecs_packbits": ("imagecodecs-zarr", "virtual-tiff"),
"imagecodecs_packints": ("imagecodecs-zarr",),
"imagecodecs_pcodec": ("imagecodecs-zarr",),
"imagecodecs_pcx": ("imagecodecs-zarr",),
"imagecodecs_pglz": ("imagecodecs-zarr",),
"imagecodecs_pixarlog": ("imagecodecs-zarr",),
"imagecodecs_plio": ("imagecodecs-zarr",),
"imagecodecs_png": ("imagecodecs-zarr", "virtual-tiff"),
"imagecodecs_qoi": ("imagecodecs-zarr",),
"imagecodecs_quantize": ("imagecodecs-zarr",),
"imagecodecs_rcomp": ("imagecodecs-zarr",),
"imagecodecs_rgbe": ("imagecodecs-zarr",),
"imagecodecs_snappy": ("imagecodecs-zarr",),
"imagecodecs_sperr": ("imagecodecs-zarr",),
"imagecodecs_spng": ("imagecodecs-zarr",),
"imagecodecs_sz3": ("imagecodecs-zarr",),
"imagecodecs_szip": ("imagecodecs-zarr",),
"imagecodecs_tga": ("imagecodecs-zarr",),
"imagecodecs_tiff": ("imagecodecs-zarr",),
"imagecodecs_ultrahdr": ("imagecodecs-zarr",),
"imagecodecs_wavpack": ("imagecodecs-zarr",),
"imagecodecs_webp": ("imagecodecs-zarr", "virtual-tiff"),
"imagecodecs_wic": ("imagecodecs-zarr",),
"imagecodecs_xor": ("imagecodecs-zarr",),
"imagecodecs_zfp": ("imagecodecs-zarr",),
"imagecodecs_zlib": ("imagecodecs-zarr",),
"imagecodecs_zlibng": ("imagecodecs-zarr",),
"imagecodecs_zopfli": ("imagecodecs-zarr",),
"imagecodecs_zstd": ("imagecodecs-zarr", "virtual-tiff"),
"imagecodecs_zstd1": ("imagecodecs-zarr",),
"n5_default": ("zarr-n5",),
}

Expand Down
46 changes: 35 additions & 11 deletions tests/test_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def unregistered_v2_codec(monkeypatch: pytest.MonkeyPatch) -> str:
[
("n5_default", ("zarr-n5",)),
("gribberish", ("gribberish",)),
("imagecodecs_jpeg2k", ("virtual-tiff",)),
("imagecodecs_jpeg2k", ("imagecodecs-zarr", "virtual-tiff")),
("omfiles.pfor", ("omfiles",)),
("any-numcodecs.array-array", ("zarr-any-numcodecs",)),
("totally-made-up", ()),
Expand Down Expand Up @@ -81,7 +81,10 @@ def test_packages_for_numcodec_v2(name: str, expected: tuple[str, ...]) -> None:

def test_packages_for_codec_is_format_specific() -> None:
"""The same name can mean different packages in each format's registry."""
assert _packages_for_codec("imagecodecs_jpeg2k", zarr_format=3) == ("virtual-tiff",)
assert _packages_for_codec("imagecodecs_jpeg2k", zarr_format=3) == (
"imagecodecs-zarr",
"virtual-tiff",
)
assert _packages_for_codec("imagecodecs_jpeg2k", zarr_format=2) == ("imagecodecs-numcodecs",)
# `crc32c` is a codec zarr implements in format 3, so only format 2 gets a hint for it.
assert _packages_for_codec("crc32c", zarr_format=3) == ()
Expand Down Expand Up @@ -270,15 +273,36 @@ def test_get_numcodec_non_mapping_input_still_raises_value_error(data: object) -
get_numcodec(data) # type: ignore[arg-type]


def test_imagecodecs_prefix_does_not_over_match_in_zarr_format_3() -> None:
"""virtual-tiff provides 15 of the 81 `imagecodecs_*` names; the rest must get no hint.

Recommending virtual-tiff for a name it does not provide is worse than saying nothing.
"""
assert _packages_for_codec("imagecodecs_jpeg2k", zarr_format=3) == ("virtual-tiff",)
for name in ("imagecodecs_jpegls", "imagecodecs_avif", "imagecodecs_blosc"):
assert _packages_for_codec(name, zarr_format=3) == ()
assert _packages_for_codec(name, zarr_format=2) == ("imagecodecs-numcodecs",)
@pytest.mark.parametrize(
("name", "expected"),
[
("imagecodecs_jpeg2k", ("imagecodecs-zarr", "virtual-tiff")),
("imagecodecs_jpeg8", ("virtual-tiff",)),
("imagecodecs_jetraw", ("virtual-tiff",)),
("imagecodecs_jpegls", ("imagecodecs-zarr",)),
("imagecodecs_avif", ("imagecodecs-zarr",)),
("imagecodecs_blosc", ("imagecodecs-zarr",)),
("imagecodecs_lzma", ("imagecodecs-zarr",)),
("imagecodecs_wavpack", ("imagecodecs-zarr",)),
("imagecodecs_zstd1", ("imagecodecs-zarr",)),
("imagecodecs_not_a_codec", ()),
],
)
def test_missing_imagecodec_reports_packages(
name: str, expected: tuple[str, ...], monkeypatch: pytest.MonkeyPatch
) -> None:
"""Missing imagecodecs name their providers without guessing from the prefix."""
monkeypatch.setitem(zarr.registry._codec_registries, name, zarr.registry.Registry())
message = (
f"An implementation for codec {name!r} is not available. Register one explicitly "
f"using the codec registry (see {_ZARR_CODEC_DOCS_URL}), or install a Python package "
"that registers a codec implementation with zarr."
)
if expected:
message += f" Known packages supporting this codec: {', '.join(expected)}."
with pytest.raises(UnknownCodecError) as excinfo:
get_codec_class(name)
assert str(excinfo.value) == message


def test_resolve_codec_reports_missing_codec() -> None:
Expand Down
Loading