opencodecs 0.3.0__tar.gz → 0.3.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {opencodecs-0.3.0 → opencodecs-0.3.1}/CHANGES.rst +42 -0
- {opencodecs-0.3.0/src/opencodecs.egg-info → opencodecs-0.3.1}/PKG-INFO +1 -1
- {opencodecs-0.3.0 → opencodecs-0.3.1}/pyproject.toml +1 -1
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/__init__.py +1 -1
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_czi_reader.py +124 -23
- {opencodecs-0.3.0 → opencodecs-0.3.1/src/opencodecs.egg-info}/PKG-INFO +1 -1
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs.egg-info/SOURCES.txt +2 -0
- opencodecs-0.3.1/tests/test_czi_mixed_subblocks.py +82 -0
- opencodecs-0.3.1/tests/test_czi_read_dispatch.py +199 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/3rdparty/VENDOR.toml +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/3rdparty/bcdec/LICENSE +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/3rdparty/bcdec/README.md +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/3rdparty/bcdec/bcdec.h +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/3rdparty/bitshuffle/LICENSE +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/3rdparty/bitshuffle/README.rst +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/3rdparty/bitshuffle/bitshuffle.c +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/3rdparty/bitshuffle/bitshuffle.h +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/3rdparty/bitshuffle/bitshuffle_core.c +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/3rdparty/bitshuffle/bitshuffle_core.h +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/3rdparty/bitshuffle/bitshuffle_internals.h +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/3rdparty/bitshuffle/iochain.c +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/3rdparty/bitshuffle/iochain.h +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/3rdparty/cfitsio/License.txt +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/3rdparty/cfitsio/fits_hcompress.c +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/3rdparty/cfitsio/fits_hdecompress.c +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/3rdparty/cfitsio/fits_hdecompress_api.h +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/3rdparty/cfitsio/fitsio2.h +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/3rdparty/cfitsio/pliocomp.c +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/3rdparty/cfitsio/ricecomp.c +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/3rdparty/cfitsio/ricecomp.h +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/3rdparty/libspng/LICENSE +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/3rdparty/libspng/spng.c +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/3rdparty/libspng/spng.h +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/3rdparty/oc_eer/LICENSE +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/3rdparty/oc_eer/oc_eer.c +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/3rdparty/oc_eer/oc_eer.h +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/3rdparty/oc_giflzw/LICENSE +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/3rdparty/oc_giflzw/oc_giflzw.c +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/3rdparty/oc_giflzw/oc_giflzw.h +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/3rdparty/oc_tifflzw/LICENSE +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/3rdparty/oc_tifflzw/oc_tifflzw.c +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/3rdparty/oc_tifflzw/oc_tifflzw.h +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/3rdparty/qoi/LICENSE +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/3rdparty/qoi/qoi.h +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/3rdparty/rgbe/rgbe.c +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/3rdparty/rgbe/rgbe.h +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/3rdparty/rgbe/rgbe.txt +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/3rdparty/vendor_hashes.json +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/LICENSE +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/MANIFEST.in +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/README.md +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/THIRD-PARTY.md +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/bench/build_libjxl.sh +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/patches/libjxl/README.md +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/setup.cfg +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/setup.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_aec_codec.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_avif_codec.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_b2nd_codec.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_bcn_codec.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_bitshuffle_codec.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_blosc2_codec.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_bmp_codec.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_brotli_codec.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_brunsli_codec.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_byteshuffle_codec.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_bz2_codec.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_cms_codec.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_czi_codec.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_czi_writer.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_deflate_codec.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_dicom.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_dicom_codec.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_dicom_pyramid.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_dicomrle_codec.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_dicomweb.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_dm.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_dm_codec.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_eer_reader.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_emd.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_emd_codec.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_ets.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_fits.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_fits_codec.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_fits_compressed.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_gif_codec.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_gzip_codec.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_h5_common.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_hdf5_codec.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_hdf5_http.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_heif_codec.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_htj2k_codec.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_htj2k_pyramid.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_imaris.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_jpeg2k_codec.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_jpeg2k_pyramid.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_jpeg_codec.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_jpeg_pyramid.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_jpegls_codec.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_jxl_codec.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_lerc_codec.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_lif_codec.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_lif_native.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_lz4_codec.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_lzma_codec.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_mozjpeg_codec.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_mozjpeg_pyramid.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_mrc.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_mrc_codec.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_mrc_writer.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_n5.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_nd2_codec.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_nd2_native.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_ndtiff.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_ndtiff_pyramid.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_ndtiff_writer.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_nifti.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_nifti_codec.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_nifti_writer.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_none_codec.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_nrrd.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_nrrd_codec.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_numpy_codec.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_numpy_reader.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_oib_codec.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_oib_native.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_oir_codec.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_ole2.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_ome_xml.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_omezarr.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_omezarr_writer.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_packints_codec.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_pcodec_codec.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_png_codec.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_predictor_codec.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_pyramid_build.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_qoi_codec.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_quantize_codec.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_rcomp_codec.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_rgbe.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_rgbe_codec.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_scaled_pyramid.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_snappy_codec.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_sperr_codec.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_sz3_codec.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_tiff_codec.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_tiff_http.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_tiff_pyramid.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_tiff_writer.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_ultrahdr_codec.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_vsi_codec.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_vsi_pyramid.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_webp_codec.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_zarr_codecs.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_zfp_codec.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_zstd_codec.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/__init__.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/_aec.pyx +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/_avif.pyx +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/_b2nd.pyx +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/_bcdec.pyx +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/_bitshuffle.pyx +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/_blosc2.pyx +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/_bmp.pyx +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/_brotli.pyx +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/_brunsli.pyx +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/_bytetools.pyx +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/_charls.pyx +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/_deflate.pyx +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/_eer.pyx +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/_gif.pyx +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/_hcomp.pyx +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/_heif.pyx +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/_isal.pyx +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/_jpeg.pyx +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/_jpeg2k.pyx +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/_jpegxr.pyx +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/_jxl.pyx +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/_lerc.pyx +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/_lz4.pyx +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/_mozjpeg.pyx +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/_ndtiff.pyx +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/_openjph.pyx +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/_pcodec.pyx +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/_plio.pyx +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/_png.pyx +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/_qoi.pyx +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/_rcomp.pyx +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/_registry.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/_rgbe.pyx +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/_snappy.pyx +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/_sperr.pyx +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/_sz3.pyx +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/_tiff.pyx +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/_uhdr.pyx +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/_webp.pyx +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/_zfp.pyx +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/_zstd.pyx +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/avif.pxd +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/b2nd.pxd +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/b2nd_helpers.c +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/b2nd_helpers.h +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/bcdec.pxd +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/bitshuffle.pxd +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/blosc2.pxd +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/brotli.pxd +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/brunsli.pxd +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/byteplanes.h +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/charls.pxd +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/eer.pxd +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/giflib.pxd +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/heif.pxd +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/heif_shim.h +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/isal_shim.c +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/jpegxr_shim.c +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/jpegxr_shim.h +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/lerc.pxd +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/libaec.pxd +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/libjxl.pxd +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/libuhdr.pxd +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/lz4.pxd +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/mozjpeg.pxd +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/openjpeg.pxd +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/openjph.pxd +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/openjph_shim.cpp +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/openjph_shim.h +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/pcodec.pxd +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/qoi.pxd +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/rgbe.pxd +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/snappy.pxd +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/sperr.pxd +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/spng.pxd +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/sz3c.pxd +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/turbojpeg.pxd +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/webp.pxd +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/webp_shim.c +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/webp_shim.h +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/zfp.pxd +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/zlib_h.pxd +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/zstd.pxd +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/core/__init__.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/core/_io_helpers.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/core/_optional_backend.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/core/_range_index.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/core/_sidecar_header.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/core/_write_helpers.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/core/buffers.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/core/checksums.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/core/codec.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/core/color.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/core/errors.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/core/io.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/core/native_source.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/core/parallel.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/core/pipeline.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/core/pyramid.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/core/rows.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/core/scratch.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/core/segment_compression.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/core/streaming.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/core/tile_cache.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/core/verification.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/jxl.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/parallel.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/tiff_reader.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/tifffile_patch.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/uhdr.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/zarr.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs.egg-info/dependency_links.txt +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs.egg-info/requires.txt +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs.egg-info/top_level.txt +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_auto_pyramid.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_avif_sequence.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_b2nd_file_reader.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_bcdec.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_bcn_bands.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_blosc2_partial.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_blosc2_threads.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_bounded_pipeline.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_brunsli.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_capability_manifest.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_charls.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_chunked_means_cheap.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_codec_registry.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_container_pipeline.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_container_streaming.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_core_codec_api.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_corpus.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_corpus_codec_decode.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_corpus_manifest.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_corpus_native_fixtures.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_corpus_other.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_corpus_png.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_corpus_tiff.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_corpus_vendors.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_coverage_final.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_coverage_finishing.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_coverage_last.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_coverage_more.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_coverage_push.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_czi_bounded_iteration.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_czi_direct_decode.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_czi_fixture.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_czi_parallel_write.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_czi_pyramid.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_czi_read_out.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_czi_region_index.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_czi_region_windows.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_czi_segment_parts.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_czi_slide_geometry.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_czi_verification.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_czi_write_pipeline.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_czi_writer.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_czi_writer_shuffle.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_data_source.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_decode_releases_gil.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_deflate.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_delta_parity.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_dicom.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_dicom_encodings.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_dicom_parallel.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_dicom_slide_selection.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_dicom_wsi_pyramid.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_dicomweb.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_direct_filter_outputs.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_dm.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_dm_offsets.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_dm_synthetic.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_edge_cases.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_eer.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_eer_parallel.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_eer_worker_budget.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_emd.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_encode_comprehensive.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_error_handling.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_filter_scratch.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_fits.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_fits_parallel.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_gif.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_gzip_presized.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_hdf5_http.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_hdf5_local.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_hdf5_parallel.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_hdf5_remote.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_heif_avif_features.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_heif_multi_image.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_htj2k_conformance.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_http_byte_savings.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_http_prefetch.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_http_unified.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_imaris.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_incremental_byte_codecs.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_io_chunked.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_isal.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_jpeg2k_region.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_jpegxr.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_jxl.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_lif_indexing.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_lif_native.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_live_archives.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_mozjpeg.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_mrc.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_mrc_mrcz_http.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_mrc_nifti_writers.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_n5.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_native_output_sinks.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_native_parity.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_native_source_callbacks.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_nd2_native.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_ndtiff.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_nifti.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_nrrd.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_nrrd_iteration.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_nrrd_slices.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_offset_readers.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_oib_indexing.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_oib_native.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_ome_xml.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_omezarr.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_omezarr_sharded_range.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_open_pyramid.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_openjph.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_optional_backend.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_owned_color_context.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_owned_image_decoders.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_parallel_jxl.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_phase4_filters.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_phase5_icc.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_phase6_codecs.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_phase7_cms.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_pipeline_budget.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_pipeline_catalog.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_pipeline_executor.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_png_row_sessions.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_public_output_buffers.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_pyramid_batched_regions.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_range_pipeline_completion.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_rcomp_truncated_input.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_reader_contract.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_reader_source_kinds.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_reader_streaming.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_reference_parity.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_rgbe.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_scaled_pyramid.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_segment_encoder_binding.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_shared_io_regressions.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_sidecar_header.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_snappy.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_source_pool_lifetime.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_streaming_means_streaming.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_tier1_scientific.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_tiff_codec_encode.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_tiff_http.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_tiff_native.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_tiff_parallel_decode.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_tiff_pipeline.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_tiff_pyramid.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_tiff_read_path.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_tiff_reader.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_tiff_spool.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_tiff_writer.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_tiff_writer_float_predictor.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_tiff_writer_verification.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_tifffile_patch.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_tile_cache.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_uhdr.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_unified_api.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_url_sources.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_vendor_provenance.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_verified_encoding.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_version_consistency.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_vsi_oir.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_vsi_pyramid.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_webp_animation.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_writer_contract.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_writer_pipeline_completion.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_writer_verification.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_zarr_adapters.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_zarr_region_fast_path.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_zfp_blocks.py +0 -0
- {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_zstd_owned_output.py +0 -0
|
@@ -10,6 +10,48 @@ Versions follow the same ``YYYY.M.D`` cadence as upstream when we
|
|
|
10
10
|
publish; the entries below cluster work by date rather than by
|
|
11
11
|
release because most of it has shipped continuously to ``main``.
|
|
12
12
|
|
|
13
|
+
0.3.1 (2026-09-28)
|
|
14
|
+
------------------
|
|
15
|
+
|
|
16
|
+
A CZI read scheduling fix, and a clear refusal for the files that cannot
|
|
17
|
+
stack. Figures compare against 0.3.0 on the same machines.
|
|
18
|
+
|
|
19
|
+
**CZI whole-stack reads hand out work by bytes**
|
|
20
|
+
|
|
21
|
+
``read`` derived its batch size from the worker count, giving each worker
|
|
22
|
+
``ceil(n/workers)`` consecutive sub-blocks. That makes the read wait on
|
|
23
|
+
that many sub-blocks even when most workers have already finished: nine
|
|
24
|
+
8 MiB sub-blocks over eight workers is two rounds, and measured 15.1 ms
|
|
25
|
+
against 7.9 ms for nine one-sub-block tasks. Tasks are now sized by
|
|
26
|
+
output bytes, about 8 MiB each, so a large sub-block gets a task to
|
|
27
|
+
itself and small ones ride together. That keeps the reason batching
|
|
28
|
+
existed, which is that a 0.7 MiB tile cannot pay for its own future,
|
|
29
|
+
without paying the rounding. ``_READ_MAX_WORKERS`` goes 8 to 32: the old
|
|
30
|
+
value was measured against the dispatch above, where extra workers could
|
|
31
|
+
not help. With byte-sized tasks, 20 to 32 measured fastest on both a
|
|
32
|
+
20-core arm64 Mac and a 128-core x86-64 Linux host, and past 32 the Linux
|
|
33
|
+
host gives it back to memory-bandwidth contention. A semaphore keeps the
|
|
34
|
+
resolved worker count a real bound when the byte sizing yields more tasks
|
|
35
|
+
than that.
|
|
36
|
+
|
|
37
|
+
Eight files of 2000 x 2000 uint16 ZSTDHDR sub-blocks, read back to back:
|
|
38
|
+
22.1 to about 15 ms per file on macOS, which moves a whole-stack read
|
|
39
|
+
from a little behind aicspylibczi to about 1.3x ahead of it.
|
|
40
|
+
|
|
41
|
+
**A mixed sub-block CZI says so, instead of failing inside the codec**
|
|
42
|
+
|
|
43
|
+
``read`` stacks every sub-block into one array, and sized that array from
|
|
44
|
+
the first sub-block, which is wrong for any file whose sub-blocks differ:
|
|
45
|
+
a pyramidal slide scan interleaves down-scaled sub-blocks with
|
|
46
|
+
full-resolution ones, and a mosaic can clip its edge tiles. The decode
|
|
47
|
+
destination check then failed with an element count the caller never
|
|
48
|
+
chose, naming neither the file's shape mix nor a way forward. It now
|
|
49
|
+
refuses up front, listing the shapes it cannot stack and the APIs that do
|
|
50
|
+
work (``CziPyramidReader.read_region``, ``entries_at_level``,
|
|
51
|
+
``read_tile`` and ``iter_tiles``), and the new ``CziReader.is_uniform``
|
|
52
|
+
lets a caller branch before asking. Decoding was never at fault: JPEG XR
|
|
53
|
+
sub-blocks are pixel-exact against aicspylibczi.
|
|
54
|
+
|
|
13
55
|
0.3.0 (2026-09-27)
|
|
14
56
|
------------------
|
|
15
57
|
|
|
@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
|
|
|
7
7
|
|
|
8
8
|
[project]
|
|
9
9
|
name = "opencodecs"
|
|
10
|
-
version = "0.3.
|
|
10
|
+
version = "0.3.1"
|
|
11
11
|
description = "Streaming, network-aware image codecs for scientific imaging (prototype: JPEG XL)"
|
|
12
12
|
readme = "README.md"
|
|
13
13
|
requires-python = ">=3.10"
|
|
@@ -60,12 +60,46 @@ _DEFAULT_POOL_SIZE = max(2 * (os.cpu_count() or 4), 8)
|
|
|
60
60
|
_POOL: ThreadPoolExecutor | None = None
|
|
61
61
|
|
|
62
62
|
#: Most concurrent workers a whole-stack read() will use when the caller
|
|
63
|
-
#: gives no count
|
|
64
|
-
#:
|
|
65
|
-
#:
|
|
66
|
-
#:
|
|
67
|
-
#:
|
|
68
|
-
|
|
63
|
+
#: gives no count, on top of the per-machine cap of one worker per core.
|
|
64
|
+
#: An earlier sweep set this to 8, but it was measured against a dispatch
|
|
65
|
+
#: that derived the batch size from the worker count, where extra workers
|
|
66
|
+
#: could not help (see the task sizing in ``read``). With tasks sized by
|
|
67
|
+
#: bytes, a fresh output each read, and 8 MB sub-blocks, 20-32 measured
|
|
68
|
+
#: fastest on both a 20-core arm64 Mac and a 128-core x86-64 Linux host,
|
|
69
|
+
#: and past 32 the Linux host gives it back to memory-bandwidth contention.
|
|
70
|
+
_READ_MAX_WORKERS = 32
|
|
71
|
+
|
|
72
|
+
#: How much decoded output one scheduled task should carry. A sub-block at
|
|
73
|
+
#: or above this size gets a task to itself; smaller ones ride together up
|
|
74
|
+
#: to roughly this much. Below it the pool's own scheduling and the
|
|
75
|
+
#: first-touch faults on a fresh output cost more than the extra
|
|
76
|
+
#: concurrency buys, and above it the read waits on the tail of a long
|
|
77
|
+
#: task. 8 MB was the best common value across 0.7 MB and 8 MB sub-blocks
|
|
78
|
+
#: on both machines.
|
|
79
|
+
_READ_TASK_BYTES = 8 << 20
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _read_tasks(n_entries: int, tile_bytes: int,
|
|
83
|
+
budget: int | None = None) -> list[range]:
|
|
84
|
+
"""Partition sub-block indices into tasks of roughly ``budget`` bytes.
|
|
85
|
+
|
|
86
|
+
A sub-block at or above the budget gets a task to itself; smaller ones
|
|
87
|
+
ride together up to it. Kept out of ``read`` so the partition can be
|
|
88
|
+
asserted on its own: it is the part of this with an off-by-one to get
|
|
89
|
+
wrong, and comparing decoded pixels cannot see such a bug, because
|
|
90
|
+
every partition of the same sub-blocks decodes to the same array.
|
|
91
|
+
|
|
92
|
+
``budget`` defaults to ``_READ_TASK_BYTES`` read at call time, not as a
|
|
93
|
+
default argument value: a default would bind the number at import and
|
|
94
|
+
leave the module constant looking tunable while changing nothing.
|
|
95
|
+
"""
|
|
96
|
+
if n_entries <= 0: # pragma: no cover - empty CZI defense
|
|
97
|
+
return []
|
|
98
|
+
if budget is None:
|
|
99
|
+
budget = _READ_TASK_BYTES
|
|
100
|
+
per_task = max(1, budget // tile_bytes) if tile_bytes > 0 else n_entries
|
|
101
|
+
return [range(i, min(i + per_task, n_entries))
|
|
102
|
+
for i in range(0, n_entries, per_task)]
|
|
69
103
|
|
|
70
104
|
|
|
71
105
|
def _get_pool() -> ThreadPoolExecutor:
|
|
@@ -512,11 +546,16 @@ class CziReader(Reader):
|
|
|
512
546
|
# Lazy caches for metadata accessors.
|
|
513
547
|
self._metadata_bytes_cache: bytes | None = None
|
|
514
548
|
self._metadata_xml_cache: str | None = None
|
|
549
|
+
self._uniform_cache: bool | None = None
|
|
515
550
|
|
|
516
551
|
self._parse_header()
|
|
517
552
|
|
|
518
553
|
# Reader-ABC contract: populate shape/dtype/n_frames eagerly so
|
|
519
|
-
# callers can inspect a file without decoding it.
|
|
554
|
+
# callers can inspect a file without decoding it. This describes the
|
|
555
|
+
# stack ``read`` returns, which only exists when every sub-block
|
|
556
|
+
# stores the same number of pixels - see ``is_uniform``. A pyramidal
|
|
557
|
+
# or otherwise mixed CZI has no such stack, and ``read`` says so
|
|
558
|
+
# rather than sizing one from the first sub-block.
|
|
520
559
|
if self.entries:
|
|
521
560
|
first = self.entries[0]
|
|
522
561
|
self.dtype = first.dtype
|
|
@@ -1279,6 +1318,8 @@ class CziReader(Reader):
|
|
|
1279
1318
|
"""
|
|
1280
1319
|
if not self.entries: # pragma: no cover - empty CZI defense
|
|
1281
1320
|
return np.empty((0,))
|
|
1321
|
+
if not self.is_uniform:
|
|
1322
|
+
raise self._nonuniform_error("read()")
|
|
1282
1323
|
|
|
1283
1324
|
first = self.entries[0]
|
|
1284
1325
|
tile_shape = first.stored_shape
|
|
@@ -1338,34 +1379,50 @@ class CziReader(Reader):
|
|
|
1338
1379
|
# call: creating one costs 1-2 ms on macOS and several on a
|
|
1339
1380
|
# 128-core Linux host, which is more than the read itself.
|
|
1340
1381
|
#
|
|
1341
|
-
#
|
|
1342
|
-
#
|
|
1343
|
-
#
|
|
1344
|
-
#
|
|
1345
|
-
#
|
|
1346
|
-
# measured
|
|
1382
|
+
# Work is handed out by BYTES, not by worker count. Deriving
|
|
1383
|
+
# the batch size from the worker count -- ceil(n/workers)
|
|
1384
|
+
# sub-blocks each -- makes the read wait on that many
|
|
1385
|
+
# sub-blocks even when most workers have already finished:
|
|
1386
|
+
# nine 8 MB sub-blocks over eight workers is two rounds, and
|
|
1387
|
+
# measured 15.1 ms against 7.9 ms for nine one-sub-block
|
|
1388
|
+
# tasks on the same machine. Sizing a task by output bytes
|
|
1389
|
+
# gives a big sub-block its own task and groups small ones,
|
|
1390
|
+
# which keeps the original reason for batching (a 0.7 MB tile
|
|
1391
|
+
# cannot pay for its own future) without paying the rounding.
|
|
1347
1392
|
from .core.parallel import resolve_workers
|
|
1348
1393
|
|
|
1349
1394
|
n = len(self.entries)
|
|
1395
|
+
tasks = _read_tasks(n, out.nbytes // n)
|
|
1350
1396
|
if n_workers is not None:
|
|
1351
|
-
workers = max(1, min(int(n_workers),
|
|
1397
|
+
workers = max(1, min(int(n_workers), len(tasks)))
|
|
1352
1398
|
else:
|
|
1353
1399
|
workers = resolve_workers(
|
|
1354
|
-
None,
|
|
1400
|
+
None, len(tasks), output_bytes=out.nbytes,
|
|
1355
1401
|
has_decode_work=any(e.compression != 0 for e in self.entries),
|
|
1356
1402
|
max_workers=_READ_MAX_WORKERS)
|
|
1357
|
-
|
|
1358
|
-
|
|
1403
|
+
|
|
1404
|
+
def _run_task(batch):
|
|
1405
|
+
for i in batch:
|
|
1359
1406
|
_worker(i)
|
|
1407
|
+
|
|
1408
|
+
if workers <= 1 or len(tasks) == 1:
|
|
1409
|
+
for batch in tasks:
|
|
1410
|
+
_run_task(batch)
|
|
1411
|
+
elif workers >= len(tasks):
|
|
1412
|
+
list(_get_pool().map(_run_task, tasks))
|
|
1360
1413
|
else:
|
|
1361
|
-
|
|
1362
|
-
|
|
1414
|
+
# More tasks than the resolved budget: the pool is shared
|
|
1415
|
+
# and larger than that budget, so map alone would run
|
|
1416
|
+
# every task at once. A semaphore keeps the budget a
|
|
1417
|
+
# budget while still letting a worker pick up the next
|
|
1418
|
+
# task the moment it finishes one.
|
|
1419
|
+
gate = threading.Semaphore(workers)
|
|
1363
1420
|
|
|
1364
|
-
def
|
|
1365
|
-
|
|
1366
|
-
|
|
1421
|
+
def _gated(batch):
|
|
1422
|
+
with gate:
|
|
1423
|
+
_run_task(batch)
|
|
1367
1424
|
|
|
1368
|
-
list(_get_pool().map(
|
|
1425
|
+
list(_get_pool().map(_gated, tasks))
|
|
1369
1426
|
|
|
1370
1427
|
if as_rgb:
|
|
1371
1428
|
out = _bgr_to_rgb(out, first.pixel_type)
|
|
@@ -1398,6 +1455,50 @@ class CziReader(Reader):
|
|
|
1398
1455
|
f"{full} or {squeezed}")
|
|
1399
1456
|
return out
|
|
1400
1457
|
|
|
1458
|
+
# ----- Sub-block uniformity -----
|
|
1459
|
+
|
|
1460
|
+
@property
|
|
1461
|
+
def is_uniform(self) -> bool:
|
|
1462
|
+
"""True when every sub-block decodes to the same pixel count and type.
|
|
1463
|
+
|
|
1464
|
+
This is exactly the condition under which the sub-blocks stack into
|
|
1465
|
+
one rectangular array, so it is what ``read``, ``__getitem__`` with a
|
|
1466
|
+
slice and ``self.shape`` depend on. Pyramidal CZIs interleave
|
|
1467
|
+
down-scaled sub-blocks with the full-resolution ones and are not
|
|
1468
|
+
uniform; neither is a mosaic whose edge tiles are clipped.
|
|
1469
|
+
|
|
1470
|
+
Compares pixel counts rather than shapes because CZI sizes a decode
|
|
1471
|
+
destination by element count, so sub-blocks differing only in
|
|
1472
|
+
singleton axes stack fine.
|
|
1473
|
+
"""
|
|
1474
|
+
if self._uniform_cache is None:
|
|
1475
|
+
self._uniform_cache = len({
|
|
1476
|
+
(math.prod(e.stored_shape), e.pixel_type)
|
|
1477
|
+
for e in self.entries}) <= 1
|
|
1478
|
+
return self._uniform_cache
|
|
1479
|
+
|
|
1480
|
+
def _nonuniform_error(self, call: str) -> "CziError":
|
|
1481
|
+
"""The error for asking a mixed-sub-block CZI for a single stack.
|
|
1482
|
+
|
|
1483
|
+
Sizing the stack from the first sub-block and letting the decode
|
|
1484
|
+
destination check fail deep in the codec reports an element count the
|
|
1485
|
+
caller never chose, which says nothing about the file or the way out.
|
|
1486
|
+
"""
|
|
1487
|
+
shapes = sorted({tuple(s for s in e.stored_shape if s != 1)
|
|
1488
|
+
for e in self.entries})
|
|
1489
|
+
preview = ", ".join(str(sh) for sh in shapes[:4])
|
|
1490
|
+
if len(shapes) > 4:
|
|
1491
|
+
preview += ", ..."
|
|
1492
|
+
return CziError(
|
|
1493
|
+
f"{call} stacks sub-blocks into one array, but this CZI stores "
|
|
1494
|
+
f"{len(shapes)} different sub-block shapes ({preview}), so no "
|
|
1495
|
+
f"such array exists"
|
|
1496
|
+
+ (" - the file is pyramidal" if self.is_pyramidal else "")
|
|
1497
|
+
+ ". Use CziPyramidReader(reader).read_region(level, y=..., x=...)"
|
|
1498
|
+
" for one resolution level, entries_at_level(n) to select a "
|
|
1499
|
+
"level's sub-blocks, or read_tile(i) / iter_tiles() for "
|
|
1500
|
+
"tile-at-a-time access.")
|
|
1501
|
+
|
|
1401
1502
|
# ----- Pyramid support -----
|
|
1402
1503
|
|
|
1403
1504
|
@property
|
|
@@ -297,8 +297,10 @@ tests/test_coverage_push.py
|
|
|
297
297
|
tests/test_czi_bounded_iteration.py
|
|
298
298
|
tests/test_czi_direct_decode.py
|
|
299
299
|
tests/test_czi_fixture.py
|
|
300
|
+
tests/test_czi_mixed_subblocks.py
|
|
300
301
|
tests/test_czi_parallel_write.py
|
|
301
302
|
tests/test_czi_pyramid.py
|
|
303
|
+
tests/test_czi_read_dispatch.py
|
|
302
304
|
tests/test_czi_read_out.py
|
|
303
305
|
tests/test_czi_region_index.py
|
|
304
306
|
tests/test_czi_region_windows.py
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""A CZI whose sub-blocks are not all the same size, and how ``read`` says so.
|
|
2
|
+
|
|
3
|
+
``read`` stacks every sub-block into one rectangular array, which only exists
|
|
4
|
+
when they all hold the same number of pixels. A pyramid CZI interleaves
|
|
5
|
+
down-scaled sub-blocks with the full-resolution ones, and a mosaic can clip
|
|
6
|
+
its edge tiles, so both break that assumption. Sizing the stack from the
|
|
7
|
+
first sub-block and letting the decode destination check fail reported an
|
|
8
|
+
element count the caller never chose -- on a real AxioScan slide,
|
|
9
|
+
``destination holds 3080192 elements, expected 1048576`` -- which named
|
|
10
|
+
neither the file's shape mix nor the API that does work. These pin the
|
|
11
|
+
up-front refusal and the fact that a uniform file is untouched by it.
|
|
12
|
+
|
|
13
|
+
The dispatch that read uses to fill that stack is covered separately, in
|
|
14
|
+
test_czi_read_dispatch.py.
|
|
15
|
+
"""
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import pathlib
|
|
19
|
+
import sys
|
|
20
|
+
|
|
21
|
+
import numpy as np
|
|
22
|
+
import pytest
|
|
23
|
+
|
|
24
|
+
sys.path.insert(0, str(pathlib.Path(__file__).parent))
|
|
25
|
+
|
|
26
|
+
from _czi_fixture import mosaic_czi_bytes, pyramid_czi_bytes # noqa: E402
|
|
27
|
+
|
|
28
|
+
from opencodecs._czi_reader import CziError, CziReader # noqa: E402
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _tile(h, w, seed):
|
|
32
|
+
return np.random.RandomState(seed).randint(0, 4096, (h, w)).astype(np.uint16)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test_mixed_tile_sizes_are_not_uniform():
|
|
36
|
+
data = mosaic_czi_bytes([(_tile(64, 64, 1), (0, 0)),
|
|
37
|
+
(_tile(64, 48, 2), (0, 64))])
|
|
38
|
+
with CziReader(buffer=data) as r:
|
|
39
|
+
assert r.is_uniform is False
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_uniform_tiles_are_uniform_and_read():
|
|
43
|
+
data = mosaic_czi_bytes([(_tile(64, 64, 1), (0, 0)),
|
|
44
|
+
(_tile(64, 64, 2), (0, 64))])
|
|
45
|
+
with CziReader(buffer=data) as r:
|
|
46
|
+
assert r.is_uniform is True
|
|
47
|
+
assert r.read().shape == (2, 64, 64)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def test_read_refuses_a_mixed_file_and_names_the_way_out():
|
|
51
|
+
data = mosaic_czi_bytes([(_tile(64, 64, 1), (0, 0)),
|
|
52
|
+
(_tile(64, 48, 2), (0, 64))])
|
|
53
|
+
with CziReader(buffer=data) as r:
|
|
54
|
+
with pytest.raises(CziError) as exc:
|
|
55
|
+
r.read()
|
|
56
|
+
message = str(exc.value)
|
|
57
|
+
# The shapes it could not stack, and every API that does work.
|
|
58
|
+
assert "(64, 64)" in message and "(64, 48)" in message
|
|
59
|
+
assert "read_region" in message
|
|
60
|
+
assert "entries_at_level" in message
|
|
61
|
+
assert "read_tile" in message
|
|
62
|
+
# Not the element-count error from deep inside the codec.
|
|
63
|
+
assert "elements" not in message
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def test_read_refuses_a_pyramid_and_says_it_is_pyramidal():
|
|
67
|
+
base = _tile(64, 64, 3)
|
|
68
|
+
data = pyramid_czi_bytes([base, base[::2, ::2], base[::4, ::4]])
|
|
69
|
+
with CziReader(buffer=data) as r:
|
|
70
|
+
assert r.is_pyramidal is True
|
|
71
|
+
assert r.is_uniform is False
|
|
72
|
+
with pytest.raises(CziError, match="pyramidal"):
|
|
73
|
+
r.read()
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def test_mixed_file_still_reads_one_tile_at_a_time():
|
|
77
|
+
"""The refusal is about the stack, not about the sub-blocks."""
|
|
78
|
+
tiles = [_tile(64, 64, 4), _tile(64, 48, 5)]
|
|
79
|
+
data = mosaic_czi_bytes([(tiles[0], (0, 0)), (tiles[1], (0, 64))])
|
|
80
|
+
with CziReader(buffer=data) as r:
|
|
81
|
+
for i, expected in enumerate(tiles):
|
|
82
|
+
assert np.array_equal(r.read_tile(i), expected)
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
"""How ``CziReader.read`` hands sub-blocks to threads.
|
|
2
|
+
|
|
3
|
+
``read`` sizes each scheduled task by output bytes rather than by worker
|
|
4
|
+
count, because deriving the batch size from the worker count made the read
|
|
5
|
+
wait on ``ceil(n/workers)`` sub-blocks even when most workers had already
|
|
6
|
+
finished. Two properties of that need holding down, and neither is visible
|
|
7
|
+
in the decoded pixels:
|
|
8
|
+
|
|
9
|
+
**The partition.** Every partition of the same sub-blocks decodes to the
|
|
10
|
+
same array, so an output comparison passes whatever the task boundaries
|
|
11
|
+
are. That is not hypothetical: the first version of these tests only
|
|
12
|
+
compared arrays, and every case in it happened to fall into the serial
|
|
13
|
+
branch, so the parallel dispatch it was named after went untested.
|
|
14
|
+
``_read_tasks`` is asserted directly instead.
|
|
15
|
+
|
|
16
|
+
**The worker bound.** When the byte sizing yields more tasks than the
|
|
17
|
+
resolved worker count, a shared pool bigger than that count would run all
|
|
18
|
+
of them at once. Too many threads first-touching one fresh output measured
|
|
19
|
+
slower, which is the whole reason there is a bound. An unbounded pool still
|
|
20
|
+
returns correct pixels, so these watch peak concurrency.
|
|
21
|
+
"""
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import pathlib
|
|
25
|
+
import sys
|
|
26
|
+
import threading
|
|
27
|
+
import time
|
|
28
|
+
|
|
29
|
+
import numpy as np
|
|
30
|
+
import pytest
|
|
31
|
+
|
|
32
|
+
sys.path.insert(0, str(pathlib.Path(__file__).parent))
|
|
33
|
+
|
|
34
|
+
from _czi_fixture import mosaic_czi_bytes # noqa: E402
|
|
35
|
+
|
|
36
|
+
from opencodecs import _czi_reader as cz # noqa: E402
|
|
37
|
+
from opencodecs._czi_reader import CziReader, _read_tasks # noqa: E402
|
|
38
|
+
|
|
39
|
+
MIB = 1 << 20
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _tiles(n, size, seed=0):
|
|
43
|
+
rs = np.random.RandomState(seed)
|
|
44
|
+
return [rs.randint(0, 4096, (size, size)).astype(np.uint16) for _ in range(n)]
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _mosaic(tiles):
|
|
48
|
+
return mosaic_czi_bytes(
|
|
49
|
+
[(t, (0, i * t.shape[1])) for i, t in enumerate(tiles)],
|
|
50
|
+
compression=6, hilo=True)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
# ---------------------------------------------------------------- partition
|
|
54
|
+
|
|
55
|
+
@pytest.mark.parametrize("n,tile_bytes,budget,expected", [
|
|
56
|
+
# A sub-block at or over the budget gets a task to itself.
|
|
57
|
+
(4, 8 * MIB, 8 * MIB, [range(0, 1), range(1, 2), range(2, 3), range(3, 4)]),
|
|
58
|
+
(2, 32 * MIB, 8 * MIB, [range(0, 1), range(1, 2)]),
|
|
59
|
+
# Smaller ones ride together, and the last task is short.
|
|
60
|
+
(10, 2 * MIB, 8 * MIB, [range(0, 4), range(4, 8), range(8, 10)]),
|
|
61
|
+
(63, 1 * MIB, 8 * MIB, [range(0, 8), range(8, 16), range(16, 24),
|
|
62
|
+
range(24, 32), range(32, 40), range(40, 48),
|
|
63
|
+
range(48, 56), range(56, 63)]),
|
|
64
|
+
# Everything fits in one task: the caller takes the serial path.
|
|
65
|
+
(5, 1024, 8 * MIB, [range(0, 5)]),
|
|
66
|
+
(1, 8 * MIB, 8 * MIB, [range(0, 1)]),
|
|
67
|
+
# A zero-byte tile must not divide by zero.
|
|
68
|
+
(3, 0, 8 * MIB, [range(0, 3)]),
|
|
69
|
+
])
|
|
70
|
+
def test_read_tasks_partition(n, tile_bytes, budget, expected):
|
|
71
|
+
assert _read_tasks(n, tile_bytes, budget) == expected
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@pytest.mark.parametrize("n,tile_bytes", [(1, 512), (7, 3 * MIB), (64, 9 * MIB)])
|
|
75
|
+
def test_read_tasks_covers_every_index_exactly_once(n, tile_bytes):
|
|
76
|
+
seen = [i for task in _read_tasks(n, tile_bytes) for i in task]
|
|
77
|
+
assert seen == list(range(n))
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
# ------------------------------------------------------------- worker bound
|
|
81
|
+
|
|
82
|
+
class _ConcurrencyWatch:
|
|
83
|
+
"""Record peak overlap of the decode calls a read makes."""
|
|
84
|
+
|
|
85
|
+
def __init__(self, hold=0.05):
|
|
86
|
+
self.hold = hold
|
|
87
|
+
self.peak = 0
|
|
88
|
+
self.calls = 0
|
|
89
|
+
self._live = 0
|
|
90
|
+
self._lock = threading.Lock()
|
|
91
|
+
|
|
92
|
+
def wrap(self, real):
|
|
93
|
+
def decode(*args, **kwargs):
|
|
94
|
+
with self._lock:
|
|
95
|
+
self._live += 1
|
|
96
|
+
self.calls += 1
|
|
97
|
+
self.peak = max(self.peak, self._live)
|
|
98
|
+
try:
|
|
99
|
+
# Hold the slot open so genuinely concurrent workers overlap;
|
|
100
|
+
# without it fast tiles can finish before the next one starts
|
|
101
|
+
# and a parallel run would look serial.
|
|
102
|
+
time.sleep(self.hold)
|
|
103
|
+
return real(*args, **kwargs)
|
|
104
|
+
finally:
|
|
105
|
+
with self._lock:
|
|
106
|
+
self._live -= 1
|
|
107
|
+
return decode
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
@pytest.fixture
|
|
111
|
+
def small_budget(monkeypatch):
|
|
112
|
+
"""One task per sub-block for cheap test-sized tiles."""
|
|
113
|
+
monkeypatch.setattr(cz, "_READ_TASK_BYTES", 1024)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _watched_read(monkeypatch, data, **kw):
|
|
117
|
+
watch = _ConcurrencyWatch()
|
|
118
|
+
monkeypatch.setattr(CziReader, "_decode_one",
|
|
119
|
+
watch.wrap(CziReader._decode_one), raising=True)
|
|
120
|
+
with CziReader(buffer=data) as r:
|
|
121
|
+
n = len(r.entries)
|
|
122
|
+
tile_bytes = r.entries[0].dtype.itemsize * int(
|
|
123
|
+
np.prod(r.entries[0].stored_shape))
|
|
124
|
+
tasks = _read_tasks(n, tile_bytes, cz._READ_TASK_BYTES)
|
|
125
|
+
out = r.read(**kw)
|
|
126
|
+
return out, watch, len(tasks), n
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def test_read_runs_tasks_concurrently(monkeypatch, small_budget):
|
|
130
|
+
"""One task per sub-block, enough workers for all of them: they overlap.
|
|
131
|
+
|
|
132
|
+
The worker count is explicit because the automatic one is also bounded by
|
|
133
|
+
output bytes, and a test-sized read is far under that floor (see
|
|
134
|
+
``test_a_tiny_read_stays_serial``).
|
|
135
|
+
"""
|
|
136
|
+
tiles = _tiles(8, 32, seed=1)
|
|
137
|
+
out, watch, n_tasks, n = _watched_read(
|
|
138
|
+
monkeypatch, _mosaic(tiles), n_workers=8)
|
|
139
|
+
assert n_tasks == n == 8, "budget should give one task per sub-block"
|
|
140
|
+
assert watch.calls == 8
|
|
141
|
+
assert watch.peak > 1, "tasks ran one after another; dispatch is not parallel"
|
|
142
|
+
assert np.array_equal(out, np.stack(tiles, axis=0))
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def test_a_tiny_read_stays_serial(monkeypatch, small_budget):
|
|
146
|
+
"""Byte-sized tasks do not override the "too little to thread" floor.
|
|
147
|
+
|
|
148
|
+
``resolve_workers`` caps the count by output bytes as well, so a read
|
|
149
|
+
this small resolves to one worker however many tasks the byte sizing
|
|
150
|
+
produced. Pinned because it surprised me while writing these: the
|
|
151
|
+
partition is per-sub-block here, and the read is still serial.
|
|
152
|
+
"""
|
|
153
|
+
tiles = _tiles(8, 32, seed=5)
|
|
154
|
+
out, watch, n_tasks, _ = _watched_read(monkeypatch, _mosaic(tiles))
|
|
155
|
+
assert n_tasks == 8
|
|
156
|
+
assert watch.peak == 1
|
|
157
|
+
assert np.array_equal(out, np.stack(tiles, axis=0))
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def test_worker_count_bounds_concurrency(monkeypatch, small_budget):
|
|
161
|
+
"""More tasks than workers must not all run at once.
|
|
162
|
+
|
|
163
|
+
This is the semaphore's job. The shared pool is far larger than the
|
|
164
|
+
budget, so without it every task starts immediately and the decoded
|
|
165
|
+
pixels are still perfectly correct.
|
|
166
|
+
"""
|
|
167
|
+
tiles = _tiles(8, 32, seed=2)
|
|
168
|
+
out, watch, n_tasks, _ = _watched_read(
|
|
169
|
+
monkeypatch, _mosaic(tiles), n_workers=2)
|
|
170
|
+
assert n_tasks == 8, "need more tasks than workers to exercise the bound"
|
|
171
|
+
assert watch.calls == 8
|
|
172
|
+
assert watch.peak > 1, "the gated branch did not run anything in parallel"
|
|
173
|
+
assert watch.peak <= 2, f"worker budget of 2 was exceeded (peak {watch.peak})"
|
|
174
|
+
assert np.array_equal(out, np.stack(tiles, axis=0))
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def test_serial_read_never_overlaps(monkeypatch, small_budget):
|
|
178
|
+
tiles = _tiles(4, 32, seed=3)
|
|
179
|
+
out, watch, _, _ = _watched_read(monkeypatch, _mosaic(tiles), n_workers=1)
|
|
180
|
+
assert watch.peak == 1
|
|
181
|
+
assert np.array_equal(out, np.stack(tiles, axis=0))
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
# ---------------------------------------------------------------- integrity
|
|
185
|
+
|
|
186
|
+
@pytest.mark.parametrize("n,size", [(8, 32), (3, 64), (17, 16)])
|
|
187
|
+
def test_parallel_read_matches_serial(monkeypatch, n, size):
|
|
188
|
+
"""Same pixels whatever the partition, across every dispatch branch."""
|
|
189
|
+
tiles = _tiles(n, size, seed=4)
|
|
190
|
+
data = _mosaic(tiles)
|
|
191
|
+
expected = np.stack(tiles, axis=0)
|
|
192
|
+
with CziReader(buffer=data) as r:
|
|
193
|
+
assert np.array_equal(r.read(n_workers=1), expected)
|
|
194
|
+
for budget in (1024, 4096, 1 << 30): # per-tile, grouped, single task
|
|
195
|
+
monkeypatch.setattr(cz, "_READ_TASK_BYTES", budget)
|
|
196
|
+
with CziReader(buffer=data) as r:
|
|
197
|
+
assert np.array_equal(r.read(), expected), f"budget={budget}"
|
|
198
|
+
# An explicit count is a budget, not an instruction to spend it.
|
|
199
|
+
assert np.array_equal(r.read(n_workers=64), expected)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|