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.
Files changed (441) hide show
  1. {opencodecs-0.3.0 → opencodecs-0.3.1}/CHANGES.rst +42 -0
  2. {opencodecs-0.3.0/src/opencodecs.egg-info → opencodecs-0.3.1}/PKG-INFO +1 -1
  3. {opencodecs-0.3.0 → opencodecs-0.3.1}/pyproject.toml +1 -1
  4. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/__init__.py +1 -1
  5. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_czi_reader.py +124 -23
  6. {opencodecs-0.3.0 → opencodecs-0.3.1/src/opencodecs.egg-info}/PKG-INFO +1 -1
  7. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs.egg-info/SOURCES.txt +2 -0
  8. opencodecs-0.3.1/tests/test_czi_mixed_subblocks.py +82 -0
  9. opencodecs-0.3.1/tests/test_czi_read_dispatch.py +199 -0
  10. {opencodecs-0.3.0 → opencodecs-0.3.1}/3rdparty/VENDOR.toml +0 -0
  11. {opencodecs-0.3.0 → opencodecs-0.3.1}/3rdparty/bcdec/LICENSE +0 -0
  12. {opencodecs-0.3.0 → opencodecs-0.3.1}/3rdparty/bcdec/README.md +0 -0
  13. {opencodecs-0.3.0 → opencodecs-0.3.1}/3rdparty/bcdec/bcdec.h +0 -0
  14. {opencodecs-0.3.0 → opencodecs-0.3.1}/3rdparty/bitshuffle/LICENSE +0 -0
  15. {opencodecs-0.3.0 → opencodecs-0.3.1}/3rdparty/bitshuffle/README.rst +0 -0
  16. {opencodecs-0.3.0 → opencodecs-0.3.1}/3rdparty/bitshuffle/bitshuffle.c +0 -0
  17. {opencodecs-0.3.0 → opencodecs-0.3.1}/3rdparty/bitshuffle/bitshuffle.h +0 -0
  18. {opencodecs-0.3.0 → opencodecs-0.3.1}/3rdparty/bitshuffle/bitshuffle_core.c +0 -0
  19. {opencodecs-0.3.0 → opencodecs-0.3.1}/3rdparty/bitshuffle/bitshuffle_core.h +0 -0
  20. {opencodecs-0.3.0 → opencodecs-0.3.1}/3rdparty/bitshuffle/bitshuffle_internals.h +0 -0
  21. {opencodecs-0.3.0 → opencodecs-0.3.1}/3rdparty/bitshuffle/iochain.c +0 -0
  22. {opencodecs-0.3.0 → opencodecs-0.3.1}/3rdparty/bitshuffle/iochain.h +0 -0
  23. {opencodecs-0.3.0 → opencodecs-0.3.1}/3rdparty/cfitsio/License.txt +0 -0
  24. {opencodecs-0.3.0 → opencodecs-0.3.1}/3rdparty/cfitsio/fits_hcompress.c +0 -0
  25. {opencodecs-0.3.0 → opencodecs-0.3.1}/3rdparty/cfitsio/fits_hdecompress.c +0 -0
  26. {opencodecs-0.3.0 → opencodecs-0.3.1}/3rdparty/cfitsio/fits_hdecompress_api.h +0 -0
  27. {opencodecs-0.3.0 → opencodecs-0.3.1}/3rdparty/cfitsio/fitsio2.h +0 -0
  28. {opencodecs-0.3.0 → opencodecs-0.3.1}/3rdparty/cfitsio/pliocomp.c +0 -0
  29. {opencodecs-0.3.0 → opencodecs-0.3.1}/3rdparty/cfitsio/ricecomp.c +0 -0
  30. {opencodecs-0.3.0 → opencodecs-0.3.1}/3rdparty/cfitsio/ricecomp.h +0 -0
  31. {opencodecs-0.3.0 → opencodecs-0.3.1}/3rdparty/libspng/LICENSE +0 -0
  32. {opencodecs-0.3.0 → opencodecs-0.3.1}/3rdparty/libspng/spng.c +0 -0
  33. {opencodecs-0.3.0 → opencodecs-0.3.1}/3rdparty/libspng/spng.h +0 -0
  34. {opencodecs-0.3.0 → opencodecs-0.3.1}/3rdparty/oc_eer/LICENSE +0 -0
  35. {opencodecs-0.3.0 → opencodecs-0.3.1}/3rdparty/oc_eer/oc_eer.c +0 -0
  36. {opencodecs-0.3.0 → opencodecs-0.3.1}/3rdparty/oc_eer/oc_eer.h +0 -0
  37. {opencodecs-0.3.0 → opencodecs-0.3.1}/3rdparty/oc_giflzw/LICENSE +0 -0
  38. {opencodecs-0.3.0 → opencodecs-0.3.1}/3rdparty/oc_giflzw/oc_giflzw.c +0 -0
  39. {opencodecs-0.3.0 → opencodecs-0.3.1}/3rdparty/oc_giflzw/oc_giflzw.h +0 -0
  40. {opencodecs-0.3.0 → opencodecs-0.3.1}/3rdparty/oc_tifflzw/LICENSE +0 -0
  41. {opencodecs-0.3.0 → opencodecs-0.3.1}/3rdparty/oc_tifflzw/oc_tifflzw.c +0 -0
  42. {opencodecs-0.3.0 → opencodecs-0.3.1}/3rdparty/oc_tifflzw/oc_tifflzw.h +0 -0
  43. {opencodecs-0.3.0 → opencodecs-0.3.1}/3rdparty/qoi/LICENSE +0 -0
  44. {opencodecs-0.3.0 → opencodecs-0.3.1}/3rdparty/qoi/qoi.h +0 -0
  45. {opencodecs-0.3.0 → opencodecs-0.3.1}/3rdparty/rgbe/rgbe.c +0 -0
  46. {opencodecs-0.3.0 → opencodecs-0.3.1}/3rdparty/rgbe/rgbe.h +0 -0
  47. {opencodecs-0.3.0 → opencodecs-0.3.1}/3rdparty/rgbe/rgbe.txt +0 -0
  48. {opencodecs-0.3.0 → opencodecs-0.3.1}/3rdparty/vendor_hashes.json +0 -0
  49. {opencodecs-0.3.0 → opencodecs-0.3.1}/LICENSE +0 -0
  50. {opencodecs-0.3.0 → opencodecs-0.3.1}/MANIFEST.in +0 -0
  51. {opencodecs-0.3.0 → opencodecs-0.3.1}/README.md +0 -0
  52. {opencodecs-0.3.0 → opencodecs-0.3.1}/THIRD-PARTY.md +0 -0
  53. {opencodecs-0.3.0 → opencodecs-0.3.1}/bench/build_libjxl.sh +0 -0
  54. {opencodecs-0.3.0 → opencodecs-0.3.1}/patches/libjxl/README.md +0 -0
  55. {opencodecs-0.3.0 → opencodecs-0.3.1}/setup.cfg +0 -0
  56. {opencodecs-0.3.0 → opencodecs-0.3.1}/setup.py +0 -0
  57. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_aec_codec.py +0 -0
  58. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_avif_codec.py +0 -0
  59. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_b2nd_codec.py +0 -0
  60. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_bcn_codec.py +0 -0
  61. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_bitshuffle_codec.py +0 -0
  62. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_blosc2_codec.py +0 -0
  63. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_bmp_codec.py +0 -0
  64. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_brotli_codec.py +0 -0
  65. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_brunsli_codec.py +0 -0
  66. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_byteshuffle_codec.py +0 -0
  67. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_bz2_codec.py +0 -0
  68. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_cms_codec.py +0 -0
  69. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_czi_codec.py +0 -0
  70. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_czi_writer.py +0 -0
  71. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_deflate_codec.py +0 -0
  72. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_dicom.py +0 -0
  73. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_dicom_codec.py +0 -0
  74. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_dicom_pyramid.py +0 -0
  75. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_dicomrle_codec.py +0 -0
  76. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_dicomweb.py +0 -0
  77. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_dm.py +0 -0
  78. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_dm_codec.py +0 -0
  79. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_eer_reader.py +0 -0
  80. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_emd.py +0 -0
  81. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_emd_codec.py +0 -0
  82. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_ets.py +0 -0
  83. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_fits.py +0 -0
  84. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_fits_codec.py +0 -0
  85. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_fits_compressed.py +0 -0
  86. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_gif_codec.py +0 -0
  87. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_gzip_codec.py +0 -0
  88. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_h5_common.py +0 -0
  89. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_hdf5_codec.py +0 -0
  90. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_hdf5_http.py +0 -0
  91. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_heif_codec.py +0 -0
  92. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_htj2k_codec.py +0 -0
  93. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_htj2k_pyramid.py +0 -0
  94. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_imaris.py +0 -0
  95. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_jpeg2k_codec.py +0 -0
  96. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_jpeg2k_pyramid.py +0 -0
  97. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_jpeg_codec.py +0 -0
  98. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_jpeg_pyramid.py +0 -0
  99. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_jpegls_codec.py +0 -0
  100. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_jxl_codec.py +0 -0
  101. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_lerc_codec.py +0 -0
  102. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_lif_codec.py +0 -0
  103. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_lif_native.py +0 -0
  104. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_lz4_codec.py +0 -0
  105. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_lzma_codec.py +0 -0
  106. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_mozjpeg_codec.py +0 -0
  107. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_mozjpeg_pyramid.py +0 -0
  108. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_mrc.py +0 -0
  109. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_mrc_codec.py +0 -0
  110. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_mrc_writer.py +0 -0
  111. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_n5.py +0 -0
  112. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_nd2_codec.py +0 -0
  113. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_nd2_native.py +0 -0
  114. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_ndtiff.py +0 -0
  115. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_ndtiff_pyramid.py +0 -0
  116. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_ndtiff_writer.py +0 -0
  117. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_nifti.py +0 -0
  118. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_nifti_codec.py +0 -0
  119. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_nifti_writer.py +0 -0
  120. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_none_codec.py +0 -0
  121. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_nrrd.py +0 -0
  122. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_nrrd_codec.py +0 -0
  123. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_numpy_codec.py +0 -0
  124. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_numpy_reader.py +0 -0
  125. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_oib_codec.py +0 -0
  126. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_oib_native.py +0 -0
  127. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_oir_codec.py +0 -0
  128. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_ole2.py +0 -0
  129. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_ome_xml.py +0 -0
  130. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_omezarr.py +0 -0
  131. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_omezarr_writer.py +0 -0
  132. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_packints_codec.py +0 -0
  133. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_pcodec_codec.py +0 -0
  134. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_png_codec.py +0 -0
  135. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_predictor_codec.py +0 -0
  136. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_pyramid_build.py +0 -0
  137. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_qoi_codec.py +0 -0
  138. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_quantize_codec.py +0 -0
  139. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_rcomp_codec.py +0 -0
  140. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_rgbe.py +0 -0
  141. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_rgbe_codec.py +0 -0
  142. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_scaled_pyramid.py +0 -0
  143. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_snappy_codec.py +0 -0
  144. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_sperr_codec.py +0 -0
  145. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_sz3_codec.py +0 -0
  146. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_tiff_codec.py +0 -0
  147. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_tiff_http.py +0 -0
  148. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_tiff_pyramid.py +0 -0
  149. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_tiff_writer.py +0 -0
  150. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_ultrahdr_codec.py +0 -0
  151. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_vsi_codec.py +0 -0
  152. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_vsi_pyramid.py +0 -0
  153. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_webp_codec.py +0 -0
  154. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_zarr_codecs.py +0 -0
  155. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_zfp_codec.py +0 -0
  156. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/_zstd_codec.py +0 -0
  157. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/__init__.py +0 -0
  158. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/_aec.pyx +0 -0
  159. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/_avif.pyx +0 -0
  160. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/_b2nd.pyx +0 -0
  161. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/_bcdec.pyx +0 -0
  162. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/_bitshuffle.pyx +0 -0
  163. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/_blosc2.pyx +0 -0
  164. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/_bmp.pyx +0 -0
  165. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/_brotli.pyx +0 -0
  166. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/_brunsli.pyx +0 -0
  167. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/_bytetools.pyx +0 -0
  168. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/_charls.pyx +0 -0
  169. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/_deflate.pyx +0 -0
  170. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/_eer.pyx +0 -0
  171. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/_gif.pyx +0 -0
  172. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/_hcomp.pyx +0 -0
  173. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/_heif.pyx +0 -0
  174. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/_isal.pyx +0 -0
  175. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/_jpeg.pyx +0 -0
  176. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/_jpeg2k.pyx +0 -0
  177. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/_jpegxr.pyx +0 -0
  178. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/_jxl.pyx +0 -0
  179. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/_lerc.pyx +0 -0
  180. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/_lz4.pyx +0 -0
  181. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/_mozjpeg.pyx +0 -0
  182. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/_ndtiff.pyx +0 -0
  183. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/_openjph.pyx +0 -0
  184. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/_pcodec.pyx +0 -0
  185. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/_plio.pyx +0 -0
  186. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/_png.pyx +0 -0
  187. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/_qoi.pyx +0 -0
  188. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/_rcomp.pyx +0 -0
  189. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/_registry.py +0 -0
  190. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/_rgbe.pyx +0 -0
  191. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/_snappy.pyx +0 -0
  192. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/_sperr.pyx +0 -0
  193. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/_sz3.pyx +0 -0
  194. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/_tiff.pyx +0 -0
  195. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/_uhdr.pyx +0 -0
  196. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/_webp.pyx +0 -0
  197. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/_zfp.pyx +0 -0
  198. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/_zstd.pyx +0 -0
  199. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/avif.pxd +0 -0
  200. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/b2nd.pxd +0 -0
  201. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/b2nd_helpers.c +0 -0
  202. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/b2nd_helpers.h +0 -0
  203. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/bcdec.pxd +0 -0
  204. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/bitshuffle.pxd +0 -0
  205. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/blosc2.pxd +0 -0
  206. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/brotli.pxd +0 -0
  207. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/brunsli.pxd +0 -0
  208. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/byteplanes.h +0 -0
  209. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/charls.pxd +0 -0
  210. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/eer.pxd +0 -0
  211. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/giflib.pxd +0 -0
  212. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/heif.pxd +0 -0
  213. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/heif_shim.h +0 -0
  214. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/isal_shim.c +0 -0
  215. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/jpegxr_shim.c +0 -0
  216. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/jpegxr_shim.h +0 -0
  217. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/lerc.pxd +0 -0
  218. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/libaec.pxd +0 -0
  219. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/libjxl.pxd +0 -0
  220. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/libuhdr.pxd +0 -0
  221. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/lz4.pxd +0 -0
  222. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/mozjpeg.pxd +0 -0
  223. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/openjpeg.pxd +0 -0
  224. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/openjph.pxd +0 -0
  225. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/openjph_shim.cpp +0 -0
  226. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/openjph_shim.h +0 -0
  227. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/pcodec.pxd +0 -0
  228. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/qoi.pxd +0 -0
  229. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/rgbe.pxd +0 -0
  230. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/snappy.pxd +0 -0
  231. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/sperr.pxd +0 -0
  232. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/spng.pxd +0 -0
  233. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/sz3c.pxd +0 -0
  234. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/turbojpeg.pxd +0 -0
  235. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/webp.pxd +0 -0
  236. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/webp_shim.c +0 -0
  237. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/webp_shim.h +0 -0
  238. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/zfp.pxd +0 -0
  239. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/zlib_h.pxd +0 -0
  240. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/codecs/zstd.pxd +0 -0
  241. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/core/__init__.py +0 -0
  242. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/core/_io_helpers.py +0 -0
  243. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/core/_optional_backend.py +0 -0
  244. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/core/_range_index.py +0 -0
  245. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/core/_sidecar_header.py +0 -0
  246. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/core/_write_helpers.py +0 -0
  247. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/core/buffers.py +0 -0
  248. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/core/checksums.py +0 -0
  249. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/core/codec.py +0 -0
  250. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/core/color.py +0 -0
  251. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/core/errors.py +0 -0
  252. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/core/io.py +0 -0
  253. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/core/native_source.py +0 -0
  254. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/core/parallel.py +0 -0
  255. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/core/pipeline.py +0 -0
  256. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/core/pyramid.py +0 -0
  257. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/core/rows.py +0 -0
  258. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/core/scratch.py +0 -0
  259. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/core/segment_compression.py +0 -0
  260. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/core/streaming.py +0 -0
  261. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/core/tile_cache.py +0 -0
  262. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/core/verification.py +0 -0
  263. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/jxl.py +0 -0
  264. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/parallel.py +0 -0
  265. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/tiff_reader.py +0 -0
  266. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/tifffile_patch.py +0 -0
  267. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/uhdr.py +0 -0
  268. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs/zarr.py +0 -0
  269. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs.egg-info/dependency_links.txt +0 -0
  270. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs.egg-info/requires.txt +0 -0
  271. {opencodecs-0.3.0 → opencodecs-0.3.1}/src/opencodecs.egg-info/top_level.txt +0 -0
  272. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_auto_pyramid.py +0 -0
  273. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_avif_sequence.py +0 -0
  274. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_b2nd_file_reader.py +0 -0
  275. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_bcdec.py +0 -0
  276. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_bcn_bands.py +0 -0
  277. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_blosc2_partial.py +0 -0
  278. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_blosc2_threads.py +0 -0
  279. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_bounded_pipeline.py +0 -0
  280. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_brunsli.py +0 -0
  281. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_capability_manifest.py +0 -0
  282. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_charls.py +0 -0
  283. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_chunked_means_cheap.py +0 -0
  284. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_codec_registry.py +0 -0
  285. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_container_pipeline.py +0 -0
  286. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_container_streaming.py +0 -0
  287. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_core_codec_api.py +0 -0
  288. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_corpus.py +0 -0
  289. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_corpus_codec_decode.py +0 -0
  290. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_corpus_manifest.py +0 -0
  291. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_corpus_native_fixtures.py +0 -0
  292. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_corpus_other.py +0 -0
  293. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_corpus_png.py +0 -0
  294. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_corpus_tiff.py +0 -0
  295. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_corpus_vendors.py +0 -0
  296. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_coverage_final.py +0 -0
  297. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_coverage_finishing.py +0 -0
  298. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_coverage_last.py +0 -0
  299. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_coverage_more.py +0 -0
  300. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_coverage_push.py +0 -0
  301. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_czi_bounded_iteration.py +0 -0
  302. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_czi_direct_decode.py +0 -0
  303. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_czi_fixture.py +0 -0
  304. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_czi_parallel_write.py +0 -0
  305. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_czi_pyramid.py +0 -0
  306. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_czi_read_out.py +0 -0
  307. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_czi_region_index.py +0 -0
  308. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_czi_region_windows.py +0 -0
  309. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_czi_segment_parts.py +0 -0
  310. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_czi_slide_geometry.py +0 -0
  311. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_czi_verification.py +0 -0
  312. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_czi_write_pipeline.py +0 -0
  313. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_czi_writer.py +0 -0
  314. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_czi_writer_shuffle.py +0 -0
  315. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_data_source.py +0 -0
  316. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_decode_releases_gil.py +0 -0
  317. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_deflate.py +0 -0
  318. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_delta_parity.py +0 -0
  319. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_dicom.py +0 -0
  320. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_dicom_encodings.py +0 -0
  321. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_dicom_parallel.py +0 -0
  322. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_dicom_slide_selection.py +0 -0
  323. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_dicom_wsi_pyramid.py +0 -0
  324. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_dicomweb.py +0 -0
  325. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_direct_filter_outputs.py +0 -0
  326. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_dm.py +0 -0
  327. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_dm_offsets.py +0 -0
  328. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_dm_synthetic.py +0 -0
  329. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_edge_cases.py +0 -0
  330. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_eer.py +0 -0
  331. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_eer_parallel.py +0 -0
  332. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_eer_worker_budget.py +0 -0
  333. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_emd.py +0 -0
  334. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_encode_comprehensive.py +0 -0
  335. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_error_handling.py +0 -0
  336. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_filter_scratch.py +0 -0
  337. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_fits.py +0 -0
  338. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_fits_parallel.py +0 -0
  339. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_gif.py +0 -0
  340. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_gzip_presized.py +0 -0
  341. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_hdf5_http.py +0 -0
  342. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_hdf5_local.py +0 -0
  343. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_hdf5_parallel.py +0 -0
  344. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_hdf5_remote.py +0 -0
  345. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_heif_avif_features.py +0 -0
  346. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_heif_multi_image.py +0 -0
  347. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_htj2k_conformance.py +0 -0
  348. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_http_byte_savings.py +0 -0
  349. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_http_prefetch.py +0 -0
  350. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_http_unified.py +0 -0
  351. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_imaris.py +0 -0
  352. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_incremental_byte_codecs.py +0 -0
  353. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_io_chunked.py +0 -0
  354. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_isal.py +0 -0
  355. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_jpeg2k_region.py +0 -0
  356. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_jpegxr.py +0 -0
  357. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_jxl.py +0 -0
  358. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_lif_indexing.py +0 -0
  359. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_lif_native.py +0 -0
  360. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_live_archives.py +0 -0
  361. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_mozjpeg.py +0 -0
  362. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_mrc.py +0 -0
  363. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_mrc_mrcz_http.py +0 -0
  364. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_mrc_nifti_writers.py +0 -0
  365. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_n5.py +0 -0
  366. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_native_output_sinks.py +0 -0
  367. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_native_parity.py +0 -0
  368. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_native_source_callbacks.py +0 -0
  369. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_nd2_native.py +0 -0
  370. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_ndtiff.py +0 -0
  371. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_nifti.py +0 -0
  372. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_nrrd.py +0 -0
  373. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_nrrd_iteration.py +0 -0
  374. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_nrrd_slices.py +0 -0
  375. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_offset_readers.py +0 -0
  376. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_oib_indexing.py +0 -0
  377. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_oib_native.py +0 -0
  378. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_ome_xml.py +0 -0
  379. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_omezarr.py +0 -0
  380. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_omezarr_sharded_range.py +0 -0
  381. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_open_pyramid.py +0 -0
  382. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_openjph.py +0 -0
  383. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_optional_backend.py +0 -0
  384. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_owned_color_context.py +0 -0
  385. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_owned_image_decoders.py +0 -0
  386. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_parallel_jxl.py +0 -0
  387. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_phase4_filters.py +0 -0
  388. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_phase5_icc.py +0 -0
  389. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_phase6_codecs.py +0 -0
  390. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_phase7_cms.py +0 -0
  391. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_pipeline_budget.py +0 -0
  392. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_pipeline_catalog.py +0 -0
  393. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_pipeline_executor.py +0 -0
  394. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_png_row_sessions.py +0 -0
  395. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_public_output_buffers.py +0 -0
  396. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_pyramid_batched_regions.py +0 -0
  397. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_range_pipeline_completion.py +0 -0
  398. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_rcomp_truncated_input.py +0 -0
  399. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_reader_contract.py +0 -0
  400. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_reader_source_kinds.py +0 -0
  401. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_reader_streaming.py +0 -0
  402. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_reference_parity.py +0 -0
  403. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_rgbe.py +0 -0
  404. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_scaled_pyramid.py +0 -0
  405. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_segment_encoder_binding.py +0 -0
  406. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_shared_io_regressions.py +0 -0
  407. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_sidecar_header.py +0 -0
  408. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_snappy.py +0 -0
  409. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_source_pool_lifetime.py +0 -0
  410. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_streaming_means_streaming.py +0 -0
  411. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_tier1_scientific.py +0 -0
  412. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_tiff_codec_encode.py +0 -0
  413. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_tiff_http.py +0 -0
  414. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_tiff_native.py +0 -0
  415. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_tiff_parallel_decode.py +0 -0
  416. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_tiff_pipeline.py +0 -0
  417. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_tiff_pyramid.py +0 -0
  418. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_tiff_read_path.py +0 -0
  419. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_tiff_reader.py +0 -0
  420. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_tiff_spool.py +0 -0
  421. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_tiff_writer.py +0 -0
  422. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_tiff_writer_float_predictor.py +0 -0
  423. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_tiff_writer_verification.py +0 -0
  424. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_tifffile_patch.py +0 -0
  425. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_tile_cache.py +0 -0
  426. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_uhdr.py +0 -0
  427. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_unified_api.py +0 -0
  428. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_url_sources.py +0 -0
  429. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_vendor_provenance.py +0 -0
  430. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_verified_encoding.py +0 -0
  431. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_version_consistency.py +0 -0
  432. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_vsi_oir.py +0 -0
  433. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_vsi_pyramid.py +0 -0
  434. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_webp_animation.py +0 -0
  435. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_writer_contract.py +0 -0
  436. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_writer_pipeline_completion.py +0 -0
  437. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_writer_verification.py +0 -0
  438. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_zarr_adapters.py +0 -0
  439. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_zarr_region_fast_path.py +0 -0
  440. {opencodecs-0.3.0 → opencodecs-0.3.1}/tests/test_zfp_blocks.py +0 -0
  441. {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
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: opencodecs
3
- Version: 0.3.0
3
+ Version: 0.3.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
@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
7
7
 
8
8
  [project]
9
9
  name = "opencodecs"
10
- version = "0.3.0"
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"
@@ -342,4 +342,4 @@ __all__ = [
342
342
  "CziWriter", "CziPyramidWriter",
343
343
  ]
344
344
 
345
- __version__ = "0.3.0"
345
+ __version__ = "0.3.1"
@@ -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. Measured on a 20-core arm64 Mac and a 128-core x86-64 Linux host with
64
- #: the batched persistent-pool dispatch below and a FRESH output each read
65
- #: (a sweep that recycled the output favored 16, which does not survive
66
- #: contact with real page faults): 8 and 12 tie on both machines, 16 doubles
67
- #: the time on Linux.
68
- _READ_MAX_WORKERS = 8
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
- # And never one task per tile: with decode writing straight
1342
- # into the fresh output, too many threads first-touching one
1343
- # large allocation measured 44% slower on macOS than a cap of
1344
- # eight; Linux prefers more. A thread count is a budget:
1345
- # resolve it from the output size under the platform's
1346
- # measured cap, and give each worker a contiguous run.
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), n))
1397
+ workers = max(1, min(int(n_workers), len(tasks)))
1352
1398
  else:
1353
1399
  workers = resolve_workers(
1354
- None, n, output_bytes=out.nbytes,
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
- if workers <= 1:
1358
- for i in range(n):
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
- step = (n + workers - 1) // workers
1362
- batches = [range(i, min(i + step, n)) for i in range(0, n, step)]
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 _run_batch(batch):
1365
- for i in batch:
1366
- _worker(i)
1421
+ def _gated(batch):
1422
+ with gate:
1423
+ _run_task(batch)
1367
1424
 
1368
- list(_get_pool().map(_run_batch, batches))
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: opencodecs
3
- Version: 0.3.0
3
+ Version: 0.3.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
@@ -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)