opencodecs 0.6.0__tar.gz → 0.7.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.
Files changed (475) hide show
  1. {opencodecs-0.6.0 → opencodecs-0.7.1}/CHANGES.rst +121 -0
  2. {opencodecs-0.6.0/src/opencodecs.egg-info → opencodecs-0.7.1}/PKG-INFO +13 -1
  3. {opencodecs-0.6.0 → opencodecs-0.7.1}/pyproject.toml +16 -2
  4. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/__init__.py +1 -1
  5. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_czi_reader.py +4 -0
  6. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_czi_writer.py +195 -14
  7. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_jxl_codec.py +17 -1
  8. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_omezarr.py +4 -1
  9. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_omezarr_writer.py +19 -7
  10. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/core/segment_compression.py +6 -0
  11. {opencodecs-0.6.0 → opencodecs-0.7.1/src/opencodecs.egg-info}/PKG-INFO +13 -1
  12. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs.egg-info/SOURCES.txt +2 -0
  13. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs.egg-info/requires.txt +12 -0
  14. opencodecs-0.7.1/tests/test_czi_foreign_readers.py +163 -0
  15. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_czi_pread_indices.py +3 -1
  16. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_czi_read_dispatch.py +7 -1
  17. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_czi_recompress.py +211 -1
  18. opencodecs-0.7.1/tests/test_foreign_readers.py +653 -0
  19. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_segment_encoder_binding.py +3 -1
  20. {opencodecs-0.6.0 → opencodecs-0.7.1}/3rdparty/VENDOR.toml +0 -0
  21. {opencodecs-0.6.0 → opencodecs-0.7.1}/3rdparty/bcdec/LICENSE +0 -0
  22. {opencodecs-0.6.0 → opencodecs-0.7.1}/3rdparty/bcdec/README.md +0 -0
  23. {opencodecs-0.6.0 → opencodecs-0.7.1}/3rdparty/bcdec/bcdec.h +0 -0
  24. {opencodecs-0.6.0 → opencodecs-0.7.1}/3rdparty/bitshuffle/LICENSE +0 -0
  25. {opencodecs-0.6.0 → opencodecs-0.7.1}/3rdparty/bitshuffle/README.rst +0 -0
  26. {opencodecs-0.6.0 → opencodecs-0.7.1}/3rdparty/bitshuffle/bitshuffle.c +0 -0
  27. {opencodecs-0.6.0 → opencodecs-0.7.1}/3rdparty/bitshuffle/bitshuffle.h +0 -0
  28. {opencodecs-0.6.0 → opencodecs-0.7.1}/3rdparty/bitshuffle/bitshuffle_core.c +0 -0
  29. {opencodecs-0.6.0 → opencodecs-0.7.1}/3rdparty/bitshuffle/bitshuffle_core.h +0 -0
  30. {opencodecs-0.6.0 → opencodecs-0.7.1}/3rdparty/bitshuffle/bitshuffle_internals.h +0 -0
  31. {opencodecs-0.6.0 → opencodecs-0.7.1}/3rdparty/bitshuffle/iochain.c +0 -0
  32. {opencodecs-0.6.0 → opencodecs-0.7.1}/3rdparty/bitshuffle/iochain.h +0 -0
  33. {opencodecs-0.6.0 → opencodecs-0.7.1}/3rdparty/cfitsio/License.txt +0 -0
  34. {opencodecs-0.6.0 → opencodecs-0.7.1}/3rdparty/cfitsio/fits_hcompress.c +0 -0
  35. {opencodecs-0.6.0 → opencodecs-0.7.1}/3rdparty/cfitsio/fits_hdecompress.c +0 -0
  36. {opencodecs-0.6.0 → opencodecs-0.7.1}/3rdparty/cfitsio/fits_hdecompress_api.h +0 -0
  37. {opencodecs-0.6.0 → opencodecs-0.7.1}/3rdparty/cfitsio/fitsio2.h +0 -0
  38. {opencodecs-0.6.0 → opencodecs-0.7.1}/3rdparty/cfitsio/pliocomp.c +0 -0
  39. {opencodecs-0.6.0 → opencodecs-0.7.1}/3rdparty/cfitsio/ricecomp.c +0 -0
  40. {opencodecs-0.6.0 → opencodecs-0.7.1}/3rdparty/cfitsio/ricecomp.h +0 -0
  41. {opencodecs-0.6.0 → opencodecs-0.7.1}/3rdparty/libspng/LICENSE +0 -0
  42. {opencodecs-0.6.0 → opencodecs-0.7.1}/3rdparty/libspng/spng.c +0 -0
  43. {opencodecs-0.6.0 → opencodecs-0.7.1}/3rdparty/libspng/spng.h +0 -0
  44. {opencodecs-0.6.0 → opencodecs-0.7.1}/3rdparty/oc_eer/LICENSE +0 -0
  45. {opencodecs-0.6.0 → opencodecs-0.7.1}/3rdparty/oc_eer/oc_eer.c +0 -0
  46. {opencodecs-0.6.0 → opencodecs-0.7.1}/3rdparty/oc_eer/oc_eer.h +0 -0
  47. {opencodecs-0.6.0 → opencodecs-0.7.1}/3rdparty/oc_giflzw/LICENSE +0 -0
  48. {opencodecs-0.6.0 → opencodecs-0.7.1}/3rdparty/oc_giflzw/oc_giflzw.c +0 -0
  49. {opencodecs-0.6.0 → opencodecs-0.7.1}/3rdparty/oc_giflzw/oc_giflzw.h +0 -0
  50. {opencodecs-0.6.0 → opencodecs-0.7.1}/3rdparty/oc_tifflzw/LICENSE +0 -0
  51. {opencodecs-0.6.0 → opencodecs-0.7.1}/3rdparty/oc_tifflzw/oc_tifflzw.c +0 -0
  52. {opencodecs-0.6.0 → opencodecs-0.7.1}/3rdparty/oc_tifflzw/oc_tifflzw.h +0 -0
  53. {opencodecs-0.6.0 → opencodecs-0.7.1}/3rdparty/qoi/LICENSE +0 -0
  54. {opencodecs-0.6.0 → opencodecs-0.7.1}/3rdparty/qoi/qoi.h +0 -0
  55. {opencodecs-0.6.0 → opencodecs-0.7.1}/3rdparty/rgbe/rgbe.c +0 -0
  56. {opencodecs-0.6.0 → opencodecs-0.7.1}/3rdparty/rgbe/rgbe.h +0 -0
  57. {opencodecs-0.6.0 → opencodecs-0.7.1}/3rdparty/rgbe/rgbe.txt +0 -0
  58. {opencodecs-0.6.0 → opencodecs-0.7.1}/3rdparty/vendor_hashes.json +0 -0
  59. {opencodecs-0.6.0 → opencodecs-0.7.1}/LICENSE +0 -0
  60. {opencodecs-0.6.0 → opencodecs-0.7.1}/MANIFEST.in +0 -0
  61. {opencodecs-0.6.0 → opencodecs-0.7.1}/README.md +0 -0
  62. {opencodecs-0.6.0 → opencodecs-0.7.1}/THIRD-PARTY.md +0 -0
  63. {opencodecs-0.6.0 → opencodecs-0.7.1}/bench/build_libjxl.sh +0 -0
  64. {opencodecs-0.6.0 → opencodecs-0.7.1}/patches/libjxl/README.md +0 -0
  65. {opencodecs-0.6.0 → opencodecs-0.7.1}/setup.cfg +0 -0
  66. {opencodecs-0.6.0 → opencodecs-0.7.1}/setup.py +0 -0
  67. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_aec_codec.py +0 -0
  68. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_avif_codec.py +0 -0
  69. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_avif_heif_params.py +0 -0
  70. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_b2nd_codec.py +0 -0
  71. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_bcn_codec.py +0 -0
  72. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_bitshuffle_codec.py +0 -0
  73. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_blosc2_codec.py +0 -0
  74. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_bmp_codec.py +0 -0
  75. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_brotli_codec.py +0 -0
  76. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_brunsli_codec.py +0 -0
  77. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_byteshuffle_codec.py +0 -0
  78. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_bz2_codec.py +0 -0
  79. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_cms_codec.py +0 -0
  80. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_czi_codec.py +0 -0
  81. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_deflate_codec.py +0 -0
  82. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_dicom.py +0 -0
  83. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_dicom_codec.py +0 -0
  84. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_dicom_pyramid.py +0 -0
  85. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_dicomrle_codec.py +0 -0
  86. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_dicomweb.py +0 -0
  87. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_dm.py +0 -0
  88. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_dm_codec.py +0 -0
  89. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_eer_reader.py +0 -0
  90. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_emd.py +0 -0
  91. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_emd_codec.py +0 -0
  92. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_ets.py +0 -0
  93. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_filter_args.py +0 -0
  94. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_fits.py +0 -0
  95. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_fits_codec.py +0 -0
  96. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_fits_compressed.py +0 -0
  97. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_gif_codec.py +0 -0
  98. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_gzip_codec.py +0 -0
  99. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_h5_common.py +0 -0
  100. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_hdf5_codec.py +0 -0
  101. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_hdf5_http.py +0 -0
  102. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_heif_codec.py +0 -0
  103. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_htj2k_codec.py +0 -0
  104. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_htj2k_pyramid.py +0 -0
  105. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_imaris.py +0 -0
  106. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_jpeg2k_codec.py +0 -0
  107. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_jpeg2k_pyramid.py +0 -0
  108. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_jpeg_codec.py +0 -0
  109. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_jpeg_pyramid.py +0 -0
  110. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_jpegls_codec.py +0 -0
  111. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_lerc_codec.py +0 -0
  112. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_lif_codec.py +0 -0
  113. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_lif_native.py +0 -0
  114. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_lz4_codec.py +0 -0
  115. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_lzma_codec.py +0 -0
  116. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_mozjpeg_codec.py +0 -0
  117. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_mozjpeg_pyramid.py +0 -0
  118. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_mrc.py +0 -0
  119. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_mrc_codec.py +0 -0
  120. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_mrc_writer.py +0 -0
  121. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_n5.py +0 -0
  122. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_nd2_codec.py +0 -0
  123. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_nd2_native.py +0 -0
  124. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_ndtiff.py +0 -0
  125. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_ndtiff_pyramid.py +0 -0
  126. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_ndtiff_writer.py +0 -0
  127. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_nifti.py +0 -0
  128. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_nifti_codec.py +0 -0
  129. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_nifti_writer.py +0 -0
  130. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_none_codec.py +0 -0
  131. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_nrrd.py +0 -0
  132. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_nrrd_codec.py +0 -0
  133. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_numpy_codec.py +0 -0
  134. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_numpy_reader.py +0 -0
  135. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_oib_codec.py +0 -0
  136. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_oib_native.py +0 -0
  137. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_oir_codec.py +0 -0
  138. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_ole2.py +0 -0
  139. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_ome_xml.py +0 -0
  140. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_packints_codec.py +0 -0
  141. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_pcodec_codec.py +0 -0
  142. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_png_codec.py +0 -0
  143. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_predictor_codec.py +0 -0
  144. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_pyramid_build.py +0 -0
  145. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_qoi_codec.py +0 -0
  146. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_quantize_codec.py +0 -0
  147. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_rcomp_codec.py +0 -0
  148. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_rgbe.py +0 -0
  149. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_rgbe_codec.py +0 -0
  150. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_scaled_pyramid.py +0 -0
  151. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_snappy_codec.py +0 -0
  152. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_sperr_codec.py +0 -0
  153. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_sz3_codec.py +0 -0
  154. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_tiff_codec.py +0 -0
  155. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_tiff_http.py +0 -0
  156. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_tiff_pyramid.py +0 -0
  157. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_tiff_writer.py +0 -0
  158. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_ultrahdr_codec.py +0 -0
  159. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_vsi_codec.py +0 -0
  160. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_vsi_pyramid.py +0 -0
  161. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_webp_codec.py +0 -0
  162. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_zarr_codecs.py +0 -0
  163. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_zfp_codec.py +0 -0
  164. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/_zstd_codec.py +0 -0
  165. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/__init__.py +0 -0
  166. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/_aec.pyx +0 -0
  167. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/_avif.pyx +0 -0
  168. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/_b2nd.pyx +0 -0
  169. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/_bcdec.pyx +0 -0
  170. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/_bitshuffle.pyx +0 -0
  171. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/_blosc2.pyx +0 -0
  172. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/_bmp.pyx +0 -0
  173. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/_brotli.pyx +0 -0
  174. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/_brunsli.pyx +0 -0
  175. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/_bytetools.pyx +0 -0
  176. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/_charls.pyx +0 -0
  177. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/_deflate.pyx +0 -0
  178. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/_eer.pyx +0 -0
  179. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/_gif.pyx +0 -0
  180. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/_hcomp.pyx +0 -0
  181. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/_heif.pyx +0 -0
  182. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/_isal.pyx +0 -0
  183. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/_jpeg.pyx +0 -0
  184. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/_jpeg2k.pyx +0 -0
  185. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/_jpeg_common.py +0 -0
  186. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/_jpegxr.pyx +0 -0
  187. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/_jxl.pyx +0 -0
  188. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/_lerc.pyx +0 -0
  189. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/_lz4.pyx +0 -0
  190. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/_mozjpeg.pyx +0 -0
  191. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/_ndtiff.pyx +0 -0
  192. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/_openjph.pyx +0 -0
  193. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/_pcodec.pyx +0 -0
  194. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/_plio.pyx +0 -0
  195. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/_png.pyx +0 -0
  196. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/_qoi.pyx +0 -0
  197. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/_rcomp.pyx +0 -0
  198. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/_registry.py +0 -0
  199. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/_rgbe.pyx +0 -0
  200. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/_snappy.pyx +0 -0
  201. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/_sperr.pyx +0 -0
  202. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/_sz3.pyx +0 -0
  203. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/_tiff.pyx +0 -0
  204. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/_uhdr.pyx +0 -0
  205. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/_webp.pyx +0 -0
  206. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/_zfp.pyx +0 -0
  207. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/_zstd.pyx +0 -0
  208. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/avif.pxd +0 -0
  209. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/b2nd.pxd +0 -0
  210. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/b2nd_helpers.c +0 -0
  211. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/b2nd_helpers.h +0 -0
  212. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/bcdec.pxd +0 -0
  213. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/bitshuffle.pxd +0 -0
  214. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/blosc2.pxd +0 -0
  215. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/brotli.pxd +0 -0
  216. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/brunsli.pxd +0 -0
  217. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/byteplanes.h +0 -0
  218. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/charls.pxd +0 -0
  219. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/eer.pxd +0 -0
  220. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/giflib.pxd +0 -0
  221. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/heif.pxd +0 -0
  222. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/heif_shim.h +0 -0
  223. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/isal_shim.c +0 -0
  224. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/jpegxr_shim.c +0 -0
  225. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/jpegxr_shim.h +0 -0
  226. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/lerc.pxd +0 -0
  227. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/libaec.pxd +0 -0
  228. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/libjxl.pxd +0 -0
  229. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/libuhdr.pxd +0 -0
  230. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/lz4.pxd +0 -0
  231. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/mozjpeg.pxd +0 -0
  232. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/oc_decoder_vtable.h +0 -0
  233. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/openjpeg.pxd +0 -0
  234. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/openjph.pxd +0 -0
  235. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/openjph_shim.cpp +0 -0
  236. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/openjph_shim.h +0 -0
  237. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/pcodec.pxd +0 -0
  238. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/qoi.pxd +0 -0
  239. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/rgbe.pxd +0 -0
  240. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/snappy.pxd +0 -0
  241. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/sperr.pxd +0 -0
  242. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/spng.pxd +0 -0
  243. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/sz3c.pxd +0 -0
  244. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/turbojpeg.pxd +0 -0
  245. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/webp.pxd +0 -0
  246. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/webp_shim.c +0 -0
  247. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/webp_shim.h +0 -0
  248. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/zfp.pxd +0 -0
  249. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/zlib_h.pxd +0 -0
  250. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/codecs/zstd.pxd +0 -0
  251. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/core/__init__.py +0 -0
  252. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/core/_io_helpers.py +0 -0
  253. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/core/_optional_backend.py +0 -0
  254. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/core/_range_index.py +0 -0
  255. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/core/_sidecar_header.py +0 -0
  256. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/core/_write_helpers.py +0 -0
  257. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/core/buffers.py +0 -0
  258. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/core/checksums.py +0 -0
  259. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/core/codec.py +0 -0
  260. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/core/color.py +0 -0
  261. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/core/errors.py +0 -0
  262. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/core/io.py +0 -0
  263. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/core/native_source.py +0 -0
  264. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/core/parallel.py +0 -0
  265. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/core/pipeline.py +0 -0
  266. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/core/pyramid.py +0 -0
  267. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/core/rows.py +0 -0
  268. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/core/scratch.py +0 -0
  269. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/core/streaming.py +0 -0
  270. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/core/tile_cache.py +0 -0
  271. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/core/verification.py +0 -0
  272. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/jxl.py +0 -0
  273. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/parallel.py +0 -0
  274. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/tiff_reader.py +0 -0
  275. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/uhdr.py +0 -0
  276. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs/zarr.py +0 -0
  277. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs.egg-info/dependency_links.txt +0 -0
  278. {opencodecs-0.6.0 → opencodecs-0.7.1}/src/opencodecs.egg-info/top_level.txt +0 -0
  279. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_auto_pyramid.py +0 -0
  280. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_avif_heif_params.py +0 -0
  281. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_avif_sequence.py +0 -0
  282. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_b2nd_file_reader.py +0 -0
  283. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_bcdec.py +0 -0
  284. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_bcn_bands.py +0 -0
  285. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_bcn_rounding.py +0 -0
  286. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_bitshuffle_format.py +0 -0
  287. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_blosc2_partial.py +0 -0
  288. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_blosc2_threads.py +0 -0
  289. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_bmp_headers.py +0 -0
  290. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_bounded_pipeline.py +0 -0
  291. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_brunsli.py +0 -0
  292. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_byteplanes.py +0 -0
  293. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_bytestream_standards.py +0 -0
  294. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_capability_manifest.py +0 -0
  295. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_charls.py +0 -0
  296. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_chunked_means_cheap.py +0 -0
  297. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_codec_registry.py +0 -0
  298. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_container_pipeline.py +0 -0
  299. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_container_streaming.py +0 -0
  300. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_core_codec_api.py +0 -0
  301. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_corpus.py +0 -0
  302. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_corpus_codec_decode.py +0 -0
  303. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_corpus_manifest.py +0 -0
  304. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_corpus_native_fixtures.py +0 -0
  305. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_corpus_other.py +0 -0
  306. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_corpus_png.py +0 -0
  307. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_corpus_tiff.py +0 -0
  308. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_corpus_vendors.py +0 -0
  309. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_coverage_final.py +0 -0
  310. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_coverage_finishing.py +0 -0
  311. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_coverage_last.py +0 -0
  312. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_coverage_more.py +0 -0
  313. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_coverage_push.py +0 -0
  314. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_czi_bounded_iteration.py +0 -0
  315. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_czi_direct_decode.py +0 -0
  316. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_czi_fixture.py +0 -0
  317. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_czi_mixed_subblocks.py +0 -0
  318. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_czi_parallel_write.py +0 -0
  319. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_czi_pyramid.py +0 -0
  320. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_czi_read_out.py +0 -0
  321. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_czi_region_index.py +0 -0
  322. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_czi_region_windows.py +0 -0
  323. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_czi_segment_parts.py +0 -0
  324. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_czi_slide_geometry.py +0 -0
  325. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_czi_verification.py +0 -0
  326. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_czi_write_pipeline.py +0 -0
  327. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_czi_writer.py +0 -0
  328. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_czi_writer_shuffle.py +0 -0
  329. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_data_source.py +0 -0
  330. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_decode_releases_gil.py +0 -0
  331. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_deflate.py +0 -0
  332. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_delta_parity.py +0 -0
  333. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_dicom.py +0 -0
  334. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_dicom_encodings.py +0 -0
  335. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_dicom_parallel.py +0 -0
  336. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_dicom_slide_selection.py +0 -0
  337. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_dicom_wsi_pyramid.py +0 -0
  338. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_dicomweb.py +0 -0
  339. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_direct_filter_outputs.py +0 -0
  340. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_dm.py +0 -0
  341. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_dm_offsets.py +0 -0
  342. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_dm_synthetic.py +0 -0
  343. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_edge_cases.py +0 -0
  344. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_eer.py +0 -0
  345. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_eer_parallel.py +0 -0
  346. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_eer_worker_budget.py +0 -0
  347. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_emd.py +0 -0
  348. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_encode_comprehensive.py +0 -0
  349. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_error_handling.py +0 -0
  350. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_fd_writes.py +0 -0
  351. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_filter_compat.py +0 -0
  352. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_filter_scratch.py +0 -0
  353. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_fits.py +0 -0
  354. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_fits_parallel.py +0 -0
  355. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_gif.py +0 -0
  356. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_gif_compositing.py +0 -0
  357. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_gzip_presized.py +0 -0
  358. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_hdf5_http.py +0 -0
  359. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_hdf5_local.py +0 -0
  360. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_hdf5_parallel.py +0 -0
  361. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_hdf5_remote.py +0 -0
  362. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_heif_avif_features.py +0 -0
  363. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_heif_multi_image.py +0 -0
  364. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_htj2k_compat.py +0 -0
  365. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_htj2k_conformance.py +0 -0
  366. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_http_byte_savings.py +0 -0
  367. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_http_prefetch.py +0 -0
  368. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_http_unified.py +0 -0
  369. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_imaris.py +0 -0
  370. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_incremental_byte_codecs.py +0 -0
  371. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_io_chunked.py +0 -0
  372. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_isal.py +0 -0
  373. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_jpeg.py +0 -0
  374. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_jpeg2k_compat.py +0 -0
  375. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_jpeg2k_region.py +0 -0
  376. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_jpegxr.py +0 -0
  377. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_jxl.py +0 -0
  378. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_lerc.py +0 -0
  379. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_lif_indexing.py +0 -0
  380. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_lif_native.py +0 -0
  381. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_live_archives.py +0 -0
  382. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_mozjpeg.py +0 -0
  383. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_mrc.py +0 -0
  384. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_mrc_mrcz_http.py +0 -0
  385. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_mrc_nifti_writers.py +0 -0
  386. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_n5.py +0 -0
  387. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_native_output_sinks.py +0 -0
  388. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_native_parity.py +0 -0
  389. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_native_source_callbacks.py +0 -0
  390. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_nd2_native.py +0 -0
  391. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_ndtiff.py +0 -0
  392. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_nifti.py +0 -0
  393. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_nrrd.py +0 -0
  394. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_nrrd_iteration.py +0 -0
  395. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_nrrd_slices.py +0 -0
  396. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_numpy_codec_format.py +0 -0
  397. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_offset_readers.py +0 -0
  398. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_oib_indexing.py +0 -0
  399. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_oib_native.py +0 -0
  400. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_ome_xml.py +0 -0
  401. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_omezarr.py +0 -0
  402. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_omezarr_sharded_range.py +0 -0
  403. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_open_pyramid.py +0 -0
  404. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_openjph.py +0 -0
  405. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_optional_backend.py +0 -0
  406. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_owned_color_context.py +0 -0
  407. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_owned_image_decoders.py +0 -0
  408. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_parallel_jxl.py +0 -0
  409. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_phase4_filters.py +0 -0
  410. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_phase5_icc.py +0 -0
  411. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_phase6_codecs.py +0 -0
  412. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_phase7_cms.py +0 -0
  413. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_pipeline_budget.py +0 -0
  414. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_pipeline_catalog.py +0 -0
  415. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_pipeline_executor.py +0 -0
  416. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_platform_branches.py +0 -0
  417. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_png_row_sessions.py +0 -0
  418. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_png_standard.py +0 -0
  419. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_public_output_buffers.py +0 -0
  420. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_pyramid_batched_regions.py +0 -0
  421. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_qoi_standard.py +0 -0
  422. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_range_pipeline_completion.py +0 -0
  423. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_rcomp_truncated_input.py +0 -0
  424. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_read_file_into.py +0 -0
  425. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_reader_contract.py +0 -0
  426. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_reader_source_kinds.py +0 -0
  427. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_reader_streaming.py +0 -0
  428. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_reference_parity.py +0 -0
  429. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_rgbe.py +0 -0
  430. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_rgbe_toplevel.py +0 -0
  431. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_scaled_pyramid.py +0 -0
  432. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_shared_io_regressions.py +0 -0
  433. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_sidecar_header.py +0 -0
  434. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_snappy.py +0 -0
  435. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_source_pool_lifetime.py +0 -0
  436. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_streaming_means_streaming.py +0 -0
  437. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_threading_policy.py +0 -0
  438. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_tier1_scientific.py +0 -0
  439. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_tiff_codec_encode.py +0 -0
  440. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_tiff_fused_decode.py +0 -0
  441. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_tiff_http.py +0 -0
  442. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_tiff_native.py +0 -0
  443. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_tiff_parallel_decode.py +0 -0
  444. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_tiff_pipeline.py +0 -0
  445. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_tiff_predictor2_interop.py +0 -0
  446. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_tiff_predictor_loops.py +0 -0
  447. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_tiff_pyramid.py +0 -0
  448. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_tiff_read_path.py +0 -0
  449. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_tiff_reader.py +0 -0
  450. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_tiff_spool.py +0 -0
  451. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_tiff_stack_read.py +0 -0
  452. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_tiff_workers.py +0 -0
  453. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_tiff_writer.py +0 -0
  454. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_tiff_writer_float_predictor.py +0 -0
  455. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_tiff_writer_verification.py +0 -0
  456. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_tile_cache.py +0 -0
  457. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_uhdr.py +0 -0
  458. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_unified_api.py +0 -0
  459. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_url_sources.py +0 -0
  460. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_vendor_provenance.py +0 -0
  461. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_verified_encoding.py +0 -0
  462. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_version_consistency.py +0 -0
  463. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_vsi_oir.py +0 -0
  464. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_vsi_pyramid.py +0 -0
  465. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_webp_animation.py +0 -0
  466. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_webp_decode_options.py +0 -0
  467. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_webp_encode_standard.py +0 -0
  468. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_wrapper_streams.py +0 -0
  469. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_writer_contract.py +0 -0
  470. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_writer_pipeline_completion.py +0 -0
  471. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_writer_verification.py +0 -0
  472. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_zarr_adapters.py +0 -0
  473. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_zarr_region_fast_path.py +0 -0
  474. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_zfp_blocks.py +0 -0
  475. {opencodecs-0.6.0 → opencodecs-0.7.1}/tests/test_zstd_owned_output.py +0 -0
@@ -10,6 +10,127 @@ 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.7.1 (unreleased)
14
+ ------------------
15
+
16
+ Every encoder is now tested against a decoder that is not ours.
17
+ ``tests/test_foreign_readers.py`` encodes with each of the 46 codecs that can
18
+ encode and decodes with the format's own library where it has a Python
19
+ binding (zstandard, lz4, brotli, python-blosc2, cramjam, lerc, zfpy, pcodec,
20
+ qoi, pillow-heif, the standard library), imagecodecs, and format readers
21
+ (tifffile, mrcfile, nibabel, pydicom, numcodecs, numpy); it also opens the
22
+ files every writer produces (TIFF with each compression, pyramids, NDTiff,
23
+ OME-Zarr, MRC, NIfTI, RGBE, JPEG XL animation, CZI pyramids). A codec that
24
+ gains an encoder without a case fails the suite. CI installs these readers.
25
+ It found:
26
+
27
+ - Fix: the JPEG XL encoder refused ``level``, so ``TiffWriter`` and the NDTiff
28
+ writer raised for JPEG XL with ``compression_level``, which their
29
+ documentation promised to pass through. ``level`` now means what it means
30
+ in ``imagecodecs.jpegxl_encode``: 100 or below is lossy at that quality,
31
+ above 100 lossless. Passing both ``level`` and ``quality`` raises.
32
+ - Fix: OME-Zarr stores written with ``compressor="blosc2"`` opened only in
33
+ opencodecs. No zarr specification defines Blosc2; the one numcodecs plugin
34
+ for it, ocf-blosc2 (Open Climate Fix), registers the id ``blosc2`` with
35
+ the parameters ``cname`` and ``clevel``, where the writer recorded
36
+ ``level``, which that plugin refuses. v2 stores now record ``cname`` and
37
+ ``clevel``, and v3 stores name the codec ``numcodecs.blosc2``, as zarr
38
+ names a numcodecs codec. With ocf-blosc2 installed, zarr opens both and
39
+ reads the pixels exactly. The chunks themselves are unchanged, and stores
40
+ written the old way still read here.
41
+ - Fix: on Windows, which has no ``os.preadv``, the CZI reader maps the file,
42
+ and ``close()`` left the closed mapping in place: the next call raised the
43
+ mapping's ``ValueError: mmap closed or invalid`` instead of the reader's
44
+ own closed-reader ``CziError``. Two tests encoded the positional-read path
45
+ as universal (the payload index is kept on every path; a stored stack is
46
+ one copy out of the mapping on one worker there) and now assert each
47
+ path's own behavior.
48
+
49
+ - Fix: a CZI this library wrote with default metadata could not be opened by
50
+ libCZI, so not by czicompress and not by ZEN. ``CziWriter``'s default
51
+ metadata was a bare ``<Metadata/>``, and libCZI requires the document to be
52
+ rooted at ``ImageDocument``; it refuses anything else outright with
53
+ ``Root-node "ImageDocument" not found``. Every round-trip test in this
54
+ suite passed because every one of them read the file back with our own
55
+ reader, which accepts it. Metadata lacking the root is now wrapped in one,
56
+ so a caller writing a new file cannot produce an unopenable one.
57
+ ``czi_recompress`` passes ``metadata_as_is=True``: it promises the source's
58
+ metadata is carried across unchanged, and rewriting it there would break
59
+ that promise, while silently "fixing" a source libCZI could not open either
60
+ is a surprise rather than a service. Its output is then exactly as openable
61
+ as its input.
62
+ - ``tests/test_czi_foreign_readers.py`` never uses opencodecs to read. It
63
+ writes with opencodecs and opens with pylibCZIrw (libCZI itself, so what
64
+ ZEN and czicompress see), czifile (an independent implementation) and
65
+ aicspylibczi, skipping any reader that is absent rather than passing
66
+ silently. It covers all three compressions, a foreign decoder reading
67
+ pixels back exactly, both metadata regressions, a recompressed real file,
68
+ and 481 JPEG XR sub-blocks carried across. Its header records what to
69
+ assume when one fails: the writer is wrong, not the reader, because all
70
+ three open ZEN files and we are the new participant in a format we did not
71
+ define.
72
+ - ``tests/_czi_fixture.py`` takes the same default. That serializer is an
73
+ independent reimplementation of the layout, and
74
+ ``test_file_bytes_match_the_fixture_serializer`` compares the writer's bytes
75
+ against it, so the two have to agree on the default; it is written as a
76
+ literal on both sides rather than imported, because importing would make
77
+ them agree by construction and hollow the test out. Fixture files are also
78
+ the sources for the ``czi_recompress`` tests, so that corpus now produces
79
+ files libCZI accepts.
80
+ - Documented why the ZSTDHDR zstd level is 3 and not a parameter. Measured end
81
+ to end on a 503 MB uncompressed CZI, against ZEISS czicompress at 0.87 s and
82
+ 319.9 MB on the same file: level 3 gives 0.24 s and 318.3 MB, level 6 gives
83
+ 0.92 s and 315.3 MB, level 9 gives 1.38 s and 314.4 MB. The encode cost
84
+ transfers almost 1:1 into wall clock because this path is encode-bound
85
+ rather than I/O-bound, so 32 workers hide none of it, and level 6 is already
86
+ slower than the tool it replaces. About 1% of size for 4 to 6x the time is
87
+ not a trade worth a default. The hi-lo byte-plane shuffle is where the gain
88
+ actually is, and it is already on: on real microscopy pixels it takes the
89
+ ratio from 0.537 to 0.485 while being faster than raw zstd, 923 against
90
+ 199 MB/s.
91
+
92
+ 0.7.0 (2026-10-03)
93
+ ------------------
94
+
95
+ ``czi_recompress`` takes ``recompress_when``, a predicate over the reader's
96
+ ``CziSubBlockEntry``. Sub-blocks it accepts are decoded and re-encoded as
97
+ before; sub-blocks it rejects are carried across BYTE FOR BYTE, keeping their
98
+ original compression. The default stays ``None``, which re-encodes everything,
99
+ so existing callers are unaffected. The returned summary gains
100
+ ``recompressed`` and ``carried`` counts.
101
+
102
+ This exists because a sub-block's compression is not a detail to be
103
+ normalized away. Re-encoding one that is already compressed is wasted work,
104
+ and re-encoding a LOSSY one is destructive: the pixels are decoded from a
105
+ lossy encoding and written back losslessly, so the file grows and the
106
+ artifacts become permanent. Measured on a 505 MB Axioscan slide scan of 481
107
+ JPEG XR sub-blocks:
108
+
109
+ ==================================== ======== ========== ============
110
+ strategy time output vs source
111
+ ==================================== ======== ========== ============
112
+ carry JPEG XR across (predicate) 0.36 s 489 MB 0.97x
113
+ re-encode everything (no predicate) 12.6 s 1309 MB 2.59x
114
+ ==================================== ======== ========== ============
115
+
116
+ So the predicate is not a speed knob. Without it that file more than doubles
117
+ in size, takes 33x longer, and loses the option of ever recovering the
118
+ original encoding. This is what ZEISS's ``czicompress`` expresses with
119
+ ``--strategy``, and the three strategies map directly::
120
+
121
+ uncompressed lambda e: e.compression == 0
122
+ uncompressed+zstd lambda e: e.compression in (0, 5, 6)
123
+ all None
124
+
125
+ Supporting this needed a writer that can emit a sub-block it cannot itself
126
+ produce. ``_build_subblock`` gained ``encoded=``, which writes the payload
127
+ exactly as given and records the compression code as-is, with no encode step
128
+ and no look at the pixels. That is the only path by which a JPEG XR sub-block
129
+ can reach a file this writer wrote, and it is why carrying one across cannot
130
+ alter it. Verification is refused on that path: nothing was encoded, so there
131
+ is nothing to check. ``verify=True`` therefore applies only to the sub-blocks
132
+ that were re-encoded.
133
+
13
134
  0.6.0 (2026-10-03)
14
135
  ------------------
15
136
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: opencodecs
3
- Version: 0.6.0
3
+ Version: 0.7.1
4
4
  Summary: Streaming, network-aware image codecs for scientific imaging (prototype: JPEG XL)
5
5
  Author: Kevin Cutler
6
6
  License-Expression: BSD-3-Clause
@@ -39,6 +39,18 @@ Requires-Dist: nd2; extra == "reference"
39
39
  Requires-Dist: readlif; extra == "reference"
40
40
  Requires-Dist: oiffile; extra == "reference"
41
41
  Requires-Dist: blosc2; extra == "reference"
42
+ Requires-Dist: zstandard; extra == "reference"
43
+ Requires-Dist: lz4; extra == "reference"
44
+ Requires-Dist: brotli; extra == "reference"
45
+ Requires-Dist: cramjam; extra == "reference"
46
+ Requires-Dist: lerc; extra == "reference"
47
+ Requires-Dist: zfpy; extra == "reference"
48
+ Requires-Dist: pcodec; extra == "reference"
49
+ Requires-Dist: ocf-blosc2; extra == "reference"
50
+ Requires-Dist: qoi; extra == "reference"
51
+ Requires-Dist: pillow-heif; extra == "reference"
52
+ Requires-Dist: pylibCZIrw; extra == "reference"
53
+ Requires-Dist: aicspylibczi; extra == "reference"
42
54
  Provides-Extra: test
43
55
  Requires-Dist: pytest>=8; extra == "test"
44
56
  Requires-Dist: pytest-cov; extra == "test"
@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
7
7
 
8
8
  [project]
9
9
  name = "opencodecs"
10
- version = "0.6.0"
10
+ version = "0.7.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"
@@ -54,7 +54,21 @@ reference = [
54
54
  "nd2", # ND2
55
55
  "readlif", # LIF
56
56
  "oiffile", # OIB
57
- "blosc2", # MRCZ
57
+ "blosc2", # MRCZ, and Blosc2 / B2ND streams
58
+ # The libraries' own bindings, which test_foreign_readers.py decodes
59
+ # our streams with alongside imagecodecs.
60
+ "zstandard", # zstd
61
+ "lz4", # LZ4 frame
62
+ "brotli", # Brotli
63
+ "cramjam", # Snappy raw and framed
64
+ "lerc", # LERC
65
+ "zfpy", # ZFP
66
+ "pcodec", # pcodec
67
+ "ocf-blosc2", # Blosc2 in zarr (no zarr spec defines it)
68
+ "qoi", # QOI
69
+ "pillow-heif", # HEIF, libheif's decoder
70
+ "pylibCZIrw", # CZI through libCZI
71
+ "aicspylibczi", # CZI, the Allen Institute's libCZI binding
58
72
  ]
59
73
  test = [
60
74
  "pytest>=8",
@@ -349,4 +349,4 @@ __all__ = [
349
349
  "czi_recompress", "subblock_dims",
350
350
  ]
351
351
 
352
- __version__ = "0.6.0"
352
+ __version__ = "0.7.1"
@@ -813,6 +813,10 @@ class CziReader(Reader):
813
813
  finally:
814
814
  os.close(self._fd)
815
815
  self._owns_fd = False
816
+ # As on the positional-read path: a closed reader has no
817
+ # buffer, so the next call raises the reader's own "closed"
818
+ # error, not the mapping's "mmap closed or invalid".
819
+ self._mmap = None
816
820
 
817
821
  @classmethod
818
822
  def from_http(
@@ -70,10 +70,67 @@ _CMP_NAME_TO_CODE = {
70
70
  }
71
71
 
72
72
 
73
+ #: libCZI refuses metadata that is not rooted at ``ImageDocument``, with
74
+ #: ``Root-node "ImageDocument" not found``. That means czicompress and ZEN
75
+ #: cannot open the file at all -- not a degraded read, no read. The writer's
76
+ #: old default was a bare ``<Metadata/>``, which opencodecs' own reader
77
+ #: accepts, so every round-trip test passed while the files were unreadable
78
+ #: by every other CZI tool. Found 2026-10-04 when czicompress rejected a file
79
+ #: this writer had just produced.
80
+ #:
81
+ #: So metadata lacking the root is WRAPPED in one, and a caller writing a NEW
82
+ #: file cannot produce an unopenable one by accident or on purpose.
83
+ #:
84
+ #: ``metadata_as_is=True`` opts out, and exactly one caller needs it:
85
+ #: ``czi_recompress``, which promises the source's metadata is carried across
86
+ #: UNCHANGED. Rewriting it there would break that promise, and silently
87
+ #: "fixing" a source that libCZI could not open either is a surprise, not a
88
+ #: service -- the output is then exactly as openable as the input, which is
89
+ #: what faithful means.
90
+ _DEFAULT_METADATA = b"<ImageDocument><Metadata/></ImageDocument>"
91
+
92
+
93
+ def _ensure_image_document(xml: bytes) -> bytes:
94
+ """Return ``xml`` rooted at ImageDocument, wrapping it if it is not."""
95
+ head = xml.lstrip()
96
+ decl = b""
97
+ if head.startswith(b"<?xml"):
98
+ end = head.find(b"?>")
99
+ if end != -1:
100
+ decl, head = head[:end + 2], head[end + 2:].lstrip()
101
+ if head.startswith(b"<ImageDocument>") or head.startswith(b"<ImageDocument "):
102
+ return xml
103
+ return decl + b"<ImageDocument>" + head + b"</ImageDocument>"
104
+
105
+
73
106
  class CziWriterError(RuntimeError):
74
107
  """Raised on writer state-machine violations."""
75
108
 
76
109
 
110
+ class _CarriedSubBlock:
111
+ """A sub-block copied across unchanged, not re-encoded.
112
+
113
+ Carries the encoded payload and the compression code it was written
114
+ with, plus a zero-filled array of the right shape and dtype so the
115
+ existing header path can read shape/dtype without the pixels ever being
116
+ decoded. ``np.empty`` would do, but zeros make an accidental use of
117
+ these pixels obvious rather than random.
118
+ """
119
+
120
+ __slots__ = ("payload", "compression", "dims", "pyramid_type",
121
+ "shape_array", "logical_shape", "pixel_type")
122
+
123
+ def __init__(self, payload, *, compression, dims, pyramid_type,
124
+ shape_array, logical_shape, pixel_type):
125
+ self.payload = payload
126
+ self.compression = compression
127
+ self.pixel_type = pixel_type
128
+ self.dims = dims
129
+ self.pyramid_type = pyramid_type
130
+ self.shape_array = shape_array
131
+ self.logical_shape = logical_shape
132
+
133
+
77
134
  class CziCancelled(CziWriterError):
78
135
  """Raised when a caller's ``should_continue`` stops a rewrite.
79
136
 
@@ -239,6 +296,27 @@ def _zstdhdr_encode(pixel_bytes, itemsize: int, hilo: bool, *, scratch=None):
239
296
  before this returns, and verification later reads the encoded payload
240
297
  and the caller's own pixel snapshot rather than this buffer, so the
241
298
  next frame may reuse it.
299
+
300
+ WHY LEVEL 3, AND WHY IT IS NOT A PARAMETER
301
+ Measured end to end through this pipeline on a 503 MB uncompressed CZI
302
+ (2026-10-04), against ZEISS czicompress at 0.87 s / 319.9 MB on the same
303
+ file:
304
+
305
+ level 3 0.24 s 318.3 MB beats czicompress on size AND speed
306
+ level 6 0.92 s 315.3 MB 0.93% smaller, 3.91x slower
307
+ level 9 1.38 s 314.4 MB 1.23% smaller, 5.83x slower
308
+
309
+ The encode cost transfers almost 1:1 into wall clock, because this path is
310
+ encode-bound rather than I/O-bound: there is no I/O to hide it behind, even
311
+ with 32 workers. So a higher level buys about 1% of size for 4-6x the time,
312
+ and level 6 is already slower than the tool we replaced. Level 3 is the
313
+ only setting that wins on both axes.
314
+
315
+ The hi-lo shuffle, by contrast, is free and large: on real microscopy
316
+ pixels it took the ratio from 0.537 to 0.485 (9.7% smaller) while being
317
+ FASTER than raw zstd, 923 MB/s against 199 MB/s, because shuffled byte
318
+ planes are far more compressible. That is where the gain is, and it is
319
+ already on by default.
242
320
  """
243
321
  from .codecs._zstd import encode as zstd_encode
244
322
  if hilo and itemsize > 1:
@@ -339,6 +417,7 @@ def _build_subblock(
339
417
  dims: tuple | None = None,
340
418
  verification_input: list | None = None,
341
419
  scratch=None,
420
+ encoded: object | None = None,
342
421
  ) -> tuple[_SubBlockSegment, dict]:
343
422
  """Build one ZISRAWSUBBLOCK segment as parts.
344
423
 
@@ -351,7 +430,21 @@ def _build_subblock(
351
430
  check runs after this returns while the producer may already be
352
431
  refilling ``array``. Without it nothing here outlives the call, so the
353
432
  array's own buffer feeds the compressor or becomes the raw payload.
433
+
434
+ With ``encoded`` the payload is written exactly as given and
435
+ ``compression_code`` is recorded as-is, with no encode step and no look
436
+ at the pixels. That is how a sub-block is carried across unchanged, and
437
+ it is the only path that can emit a compression this writer cannot
438
+ produce (JPEG XR, for instance). ``array`` is then used only for its
439
+ shape and dtype, so a zero-filled array of the right shape is enough.
440
+ Verification is not available on this path: there is nothing to check,
441
+ because nothing was encoded.
354
442
  """
443
+ if encoded is not None and verification_input is not None:
444
+ raise CziWriterError(
445
+ "czi: verification_input cannot be combined with encoded; a "
446
+ "carried-across sub-block is not re-encoded, so there is "
447
+ "nothing to verify")
355
448
  h, w = array.shape[:2]
356
449
  logical_h, logical_w = logical_shape
357
450
  start_y, start_x = location
@@ -390,7 +483,10 @@ def _build_subblock(
390
483
  pixels = memoryview(contiguous).cast("B")
391
484
  keep = contiguous
392
485
  itemsize = array.dtype.itemsize
393
- if compression_code == 0:
486
+ if encoded is not None:
487
+ data = encoded
488
+ keep = encoded
489
+ elif compression_code == 0:
394
490
  data = pixels
395
491
  elif compression_code == 5:
396
492
  from .codecs._zstd import encode as zstd_encode
@@ -477,15 +573,22 @@ class _CziStreamWriter(Writer):
477
573
  """Shared sub-block sink with optional bounded pixel verification."""
478
574
 
479
575
  def __init__(self, path: str | Path, *, compression: str = "none",
480
- hilo: bool = True, metadata_xml: bytes | str = b"<Metadata/>",
481
- verify: bool = False, background_encode: bool = False):
576
+ hilo: bool = True, metadata_xml: bytes | str | None = None,
577
+ verify: bool = False, background_encode: bool = False,
578
+ metadata_as_is: bool = False):
482
579
  if compression not in _CMP_NAME_TO_CODE:
483
580
  raise CziWriterError(f"unknown compression {compression!r}")
484
581
  self._path = Path(path)
485
582
  self._cmp_code = _CMP_NAME_TO_CODE[compression]
486
583
  self._hilo = bool(hilo)
487
- xml = metadata_xml.encode("utf-8") if isinstance(metadata_xml, str) else metadata_xml
488
- self._metadata_segment = _build_metadata_segment(xml)
584
+ if metadata_xml is None:
585
+ xml = _DEFAULT_METADATA
586
+ elif isinstance(metadata_xml, str):
587
+ xml = metadata_xml.encode("utf-8")
588
+ else:
589
+ xml = metadata_xml
590
+ self._metadata_segment = _build_metadata_segment(
591
+ xml if metadata_as_is else _ensure_image_document(xml))
489
592
  self._header_size = (32 + 88 + 31) // 32 * 32
490
593
  self._position = self._header_size + len(self._metadata_segment)
491
594
  self._entries: list[dict] = []
@@ -687,6 +790,17 @@ class _CziStreamWriter(Writer):
687
790
  workers = resolve_workers(None, 1 << 30, output_bytes=None, max_workers=8)
688
791
  workers = max(1, int(workers))
689
792
 
793
+ def item_bytes(item):
794
+ """Cost of one snapshotted item, for batching and reservation.
795
+
796
+ A carried sub-block has no array: its cost is the encoded payload
797
+ it already owns. Both batching sites go through here so they
798
+ cannot disagree about what an item weighs.
799
+ """
800
+ if isinstance(item, _CarriedSubBlock):
801
+ return len(item.payload)
802
+ return item[0].nbytes
803
+
690
804
  def snapshot(frame):
691
805
  """Own the frame's pixels before the producer can touch them.
692
806
 
@@ -695,6 +809,10 @@ class _CziStreamWriter(Writer):
695
809
  it is a down-scaled level. Those forms are what make a faithful
696
810
  re-encode possible on this path as well as on ``write_frame``.
697
811
  """
812
+ if isinstance(frame, _CarriedSubBlock):
813
+ # Nothing to snapshot: the payload is already bytes we own,
814
+ # and the pixels are never looked at.
815
+ return frame
698
816
  dims = override_pyramid = None
699
817
  if isinstance(frame, tuple):
700
818
  if len(frame) == 2:
@@ -728,7 +846,7 @@ class _CziStreamWriter(Writer):
728
846
  batch, batch_bytes = [], 0
729
847
  for frame in frames:
730
848
  item = snapshot(frame)
731
- n = item[0].nbytes
849
+ n = item_bytes(item)
732
850
  if batch and batch_bytes + n > batch_limit:
733
851
  yield batch
734
852
  batch, batch_bytes = [], 0
@@ -738,7 +856,7 @@ class _CziStreamWriter(Writer):
738
856
  yield batch
739
857
 
740
858
  def size(batch):
741
- n = sum(item[0].nbytes for item in batch)
859
+ n = sum(item_bytes(item) for item in batch)
742
860
  # Snapshot + encoded output (bounded by the input for these
743
861
  # codecs) + decoded verification result and comparison scratch.
744
862
  return 2 * n + (verification_reservation(n) if verify else 0)
@@ -746,7 +864,19 @@ class _CziStreamWriter(Writer):
746
864
  def encode(batch):
747
865
  out = []
748
866
  scratch = _worker_scratch()
749
- for pixels, logical_shape, pyramid_type, dims in batch:
867
+ for item in batch:
868
+ if isinstance(item, _CarriedSubBlock):
869
+ segment, entry = _build_subblock(
870
+ item.shape_array,
871
+ pixel_type=item.pixel_type,
872
+ compression_code=item.compression, hilo=self._hilo,
873
+ file_position=0, logical_shape=item.logical_shape,
874
+ pyramid_type=item.pyramid_type, dims=item.dims,
875
+ verification_input=None, scratch=None,
876
+ encoded=item.payload)
877
+ out.append((segment, entry))
878
+ continue
879
+ pixels, logical_shape, pyramid_type, dims = item
750
880
  captured = [] if verify else None
751
881
  segment, entry = _build_subblock(
752
882
  pixels, pixel_type=_DTYPE_TO_PIXELTYPE[pixels.dtype],
@@ -992,7 +1122,8 @@ __all__ = ["CziWriter", "CziPyramidWriter", "CziWriterError"]
992
1122
  def czi_recompress(src, dst, *, compression: str = "zstdhdr", hilo: bool = True,
993
1123
  workers=None, verify: bool = False,
994
1124
  background_encode: bool = False,
995
- should_continue=None) -> dict:
1125
+ should_continue=None,
1126
+ recompress_when=None) -> dict:
996
1127
  """Rewrite a CZI with different compression, keeping the container.
997
1128
 
998
1129
  Every sub-block keeps its dimension coordinates, its scene and mosaic
@@ -1016,8 +1147,30 @@ def czi_recompress(src, dst, *, compression: str = "zstdhdr", hilo: bool = True,
1016
1147
  know which. Encodes already dispatched still finish, so cancellation
1017
1148
  takes effect within roughly ``workers`` sub-blocks rather than instantly.
1018
1149
 
1019
- Returns a summary dict: sub-block count, input and output sizes, and the
1020
- compression that was applied.
1150
+ ``recompress_when`` is an optional predicate over the reader's
1151
+ ``CziSubBlockEntry``. Sub-blocks it accepts are decoded and re-encoded;
1152
+ sub-blocks it rejects are carried across BYTE FOR BYTE, keeping their
1153
+ original compression. Default ``None`` re-encodes everything, which is
1154
+ what earlier versions did.
1155
+
1156
+ This is what makes the selective strategies of ZEISS's ``czicompress``
1157
+ expressible, and the reason they exist: re-encoding an already-compressed
1158
+ sub-block is at best wasted work, and for a lossy one it is destructive.
1159
+ A JPEG XR sub-block decoded and re-encoded as lossless zstdhdr grows,
1160
+ and bakes the lossy artifacts in permanently. Carrying it across cannot
1161
+ do that.
1162
+
1163
+ uncompressed only lambda e: e.compression == 0
1164
+ uncompressed+zstd lambda e: e.compression in (0, 5, 6)
1165
+ everything None
1166
+
1167
+ ``verify=True`` applies only to the sub-blocks that were re-encoded; a
1168
+ carried-across payload is unchanged by definition, so there is nothing
1169
+ to check.
1170
+
1171
+ Returns a summary dict: sub-block count, input and output sizes, the
1172
+ compression that was applied, and how many sub-blocks were re-encoded
1173
+ versus carried across.
1021
1174
  """
1022
1175
  import os
1023
1176
 
@@ -1027,7 +1180,9 @@ def czi_recompress(src, dst, *, compression: str = "zstdhdr", hilo: bool = True,
1027
1180
  n = len(reader.entries)
1028
1181
  if not n:
1029
1182
  raise CziWriterError(f"czi: {src} has no sub-blocks to recompress")
1030
- metadata = reader.metadata_bytes or b"<Metadata/>"
1183
+ metadata = reader.metadata_bytes or _DEFAULT_METADATA
1184
+
1185
+ counts = {"recompressed": 0, "carried": 0}
1031
1186
 
1032
1187
  def frames():
1033
1188
  # One sub-block at a time: a slide scan does not fit in memory,
@@ -1038,12 +1193,36 @@ def czi_recompress(src, dst, *, compression: str = "zstdhdr", hilo: bool = True,
1038
1193
  f"czi: recompress of {src} cancelled after "
1039
1194
  f"{i} of {n} sub-blocks")
1040
1195
  entry = reader.entries[i]
1041
- yield reader[i], subblock_dims(entry), entry.pyramid_type
1196
+ if recompress_when is None or recompress_when(entry):
1197
+ counts["recompressed"] += 1
1198
+ yield reader[i], subblock_dims(entry), entry.pyramid_type
1199
+ else:
1200
+ # Carried across: the encoded bytes are copied straight
1201
+ # from the source and the original compression is kept,
1202
+ # so the pixels are never decoded and cannot change.
1203
+ counts["carried"] += 1
1204
+ d = subblock_dims(entry)
1205
+ axes = {name.rstrip(b"\x00"): (size, stored)
1206
+ for name, _start, size, _co, stored in d}
1207
+ log_h, st_h = axes[b"Y"]
1208
+ log_w, st_w = axes[b"X"]
1209
+ with reader._payload(entry) as view:
1210
+ # Copy: the reader hands back a buffer it reuses, so
1211
+ # a borrowed view would be overwritten by the next
1212
+ # sub-block before the writer emits this one.
1213
+ payload = bytes(view)
1214
+ yield _CarriedSubBlock(
1215
+ payload, compression=entry.compression,
1216
+ dims=d, pyramid_type=entry.pyramid_type,
1217
+ shape_array=np.zeros((st_h, st_w), entry.dtype),
1218
+ logical_shape=(log_h, log_w),
1219
+ pixel_type=entry.pixel_type)
1042
1220
 
1043
1221
  try:
1044
1222
  with CziWriter(str(dst), compression=compression, hilo=hilo,
1045
1223
  metadata_xml=metadata, verify=verify,
1046
- background_encode=background_encode) as writer:
1224
+ background_encode=background_encode,
1225
+ metadata_as_is=True) as writer:
1047
1226
  writer.write_many(frames(), workers=workers, copy_frames=False)
1048
1227
  except BaseException:
1049
1228
  # An interrupted rewrite must not leave a file behind. A CZI
@@ -1061,4 +1240,6 @@ def czi_recompress(src, dst, *, compression: str = "zstdhdr", hilo: bool = True,
1061
1240
  "src_bytes": os.path.getsize(src),
1062
1241
  "dst_bytes": os.path.getsize(dst),
1063
1242
  "compression": compression,
1243
+ "recompressed": counts["recompressed"],
1244
+ "carried": counts["carried"],
1064
1245
  }
@@ -139,6 +139,22 @@ class JpegXLReader(Reader):
139
139
  self._inner.close()
140
140
 
141
141
 
142
+ def jxl_level_as_quality(opts: dict) -> dict:
143
+ """imagecodecs.jpegxl_encode's ``level`` is a quality: 100 or below is
144
+ lossy at that quality, above 100 lossless. The encoder's ``quality``
145
+ has exactly that meaning, so ``level`` is passed as ``quality``.
146
+ Writers that take one compression level for every codec (TIFF,
147
+ NDTiff) reach JPEG XL through this too."""
148
+ if opts.get("level") is None:
149
+ opts.pop("level", None)
150
+ return opts
151
+ if opts.get("quality") is not None:
152
+ raise TypeError("jxl: pass level or quality, not both")
153
+ opts = dict(opts)
154
+ opts["quality"] = opts.pop("level")
155
+ return opts
156
+
157
+
142
158
  class JpegXLCodec(Codec):
143
159
  """Native JPEG XL codec (libjxl 0.11).
144
160
 
@@ -176,7 +192,7 @@ class JpegXLCodec(Codec):
176
192
  return _jxl_check_signature(head)
177
193
 
178
194
  def encode(self, arr: np.ndarray, *, dest=None, **opts) -> bytes | None:
179
- return _jxl_encode(arr, dest=dest, **opts)
195
+ return _jxl_encode(arr, dest=dest, **jxl_level_as_quality(opts))
180
196
 
181
197
  def decode(self, src: Any, **opts) -> np.ndarray:
182
198
  """Decode a JXL codestream.
@@ -496,7 +496,10 @@ def _run_decoder(name: str, cfg: dict, data: bytes) -> bytes:
496
496
  _DECODER_CACHE["blosc2"] = fn
497
497
  if fn:
498
498
  return fn(data)
499
- if name == "blosc2":
499
+ if name in ("blosc2", "numcodecs.blosc2"):
500
+ # "numcodecs.blosc2" is ocf-blosc2's v3 name, which our writer
501
+ # uses; plain "blosc2" is the v2 id, and what v3 stores this
502
+ # package wrote before 0.7.0 carry.
500
503
  fn = _DECODER_CACHE.get("blosc2")
501
504
  if not fn:
502
505
  from .codecs._blosc2 import decode as _b_decode
@@ -53,7 +53,10 @@ _CODEC_NAME_MAP = {
53
53
  "none": (None, None),
54
54
  "raw": (None, None),
55
55
  "zstd": ("zstd", "zstd"),
56
- "blosc2": ("blosc2", "blosc2"), # blosc2 wire format (v2 + v3)
56
+ # No zarr specification defines Blosc2. These are the names ocf-blosc2,
57
+ # the one numcodecs plugin for it, registers: "blosc2" for v2, and
58
+ # "numcodecs.blosc2" for v3, which is how zarr names a numcodecs codec.
59
+ "blosc2": ("blosc2", "numcodecs.blosc2"),
57
60
  "gzip": ("gzip", "gzip"),
58
61
  "blosc": ("blosc", None), # v2-only (no v3 spec)
59
62
  }
@@ -68,6 +71,16 @@ class OmeZarrWriterError(RuntimeError):
68
71
  # ---------------------------------------------------------------------------
69
72
 
70
73
 
74
+ def _blosc2_config(level: int | None, v3: bool = False) -> dict:
75
+ """The parameters ocf-blosc2's Blosc2(cname, clevel) takes, so zarr with
76
+ that plugin installed opens the store. cname names the inner codec;
77
+ _encode_chunk compresses with zstd, blosc2's default, and ocf-blosc2
78
+ reads "zstd" as that. (Before 0.7.0 the writer recorded "level", which
79
+ no reader but opencodecs accepted; those stores still read here.)"""
80
+ config = {"cname": "zstd", "clevel": level if level is not None else 5}
81
+ return config if v3 else {"id": "blosc2", **config}
82
+
83
+
71
84
  def _encode_chunk(raw: bytes, codec: str, level: int | None) -> bytes:
72
85
  """Compress one chunk's bytes."""
73
86
  if codec in (None, "none", "raw"):
@@ -309,8 +322,7 @@ def _write_v2(
309
322
  metadata["compressor"] = {"id": "zstd",
310
323
  "level": level if level is not None else 3}
311
324
  elif compressor == "blosc2":
312
- metadata["compressor"] = {"id": "blosc2",
313
- "level": level if level is not None else 5}
325
+ metadata["compressor"] = _blosc2_config(level)
314
326
  elif compressor == "gzip":
315
327
  metadata["compressor"] = {"id": "gzip",
316
328
  "level": level if level is not None else 6}
@@ -375,8 +387,8 @@ def _write_v3(
375
387
  })
376
388
  elif compressor == "blosc2":
377
389
  codecs.append({
378
- "name": "blosc2",
379
- "configuration": {"clevel": level if level is not None else 5},
390
+ "name": "numcodecs.blosc2",
391
+ "configuration": _blosc2_config(level, v3=True),
380
392
  })
381
393
  elif compressor == "gzip":
382
394
  codecs.append({
@@ -460,8 +472,8 @@ def _v3_codecs_for_shard_inner(
460
472
  })
461
473
  elif compressor == "blosc2":
462
474
  codecs.append({
463
- "name": "blosc2",
464
- "configuration": {"clevel": level if level is not None else 5},
475
+ "name": "numcodecs.blosc2",
476
+ "configuration": _blosc2_config(level, v3=True),
465
477
  })
466
478
  elif compressor == "gzip":
467
479
  codecs.append({
@@ -254,6 +254,9 @@ def encode_segment(data, codec: str | int, *, level: int | None = None,
254
254
  kw["level"] = level
255
255
  if code == JPEG2000:
256
256
  kw.setdefault("planar", False)
257
+ if code == JXL:
258
+ from .._jxl_codec import jxl_level_as_quality
259
+ kw = jxl_level_as_quality(kw)
257
260
  if code in (ZSTD, JPEG2000, JXL):
258
261
  from .pipeline import native_workers, in_worker
259
262
  # Zstandard counts background workers: zero means inline serial.
@@ -292,6 +295,9 @@ def bind_segment_encoder(codec: str | int, *, level=None, owned_output=False,
292
295
  options.setdefault("level", level)
293
296
  if code == JPEG2000:
294
297
  options.setdefault("planar", False)
298
+ if code == JXL:
299
+ from .._jxl_codec import jxl_level_as_quality
300
+ options = jxl_level_as_quality(options)
295
301
  eager = partial(fn, **options)
296
302
  if code not in (ZSTD, JPEG2000, JXL):
297
303
  return eager