opencodecs 0.1.3__tar.gz → 0.1.5__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 (208) hide show
  1. opencodecs-0.1.5/3rdparty/cfitsio/pliocomp.c +331 -0
  2. {opencodecs-0.1.3 → opencodecs-0.1.5}/CHANGES.rst +148 -0
  3. {opencodecs-0.1.3/src/opencodecs.egg-info → opencodecs-0.1.5}/PKG-INFO +1 -1
  4. {opencodecs-0.1.3 → opencodecs-0.1.5}/pyproject.toml +6 -3
  5. {opencodecs-0.1.3 → opencodecs-0.1.5}/setup.py +100 -9
  6. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/__init__.py +1 -1
  7. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/_eer_reader.py +35 -6
  8. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/_fits_compressed.py +34 -4
  9. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/_pyramid_build.py +66 -1
  10. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/_tiff_http.py +83 -0
  11. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/_tiff_pyramid.py +38 -2
  12. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/_tiff_writer.py +51 -2
  13. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/codecs/__init__.py +15 -3
  14. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/codecs/_bcdec.c +152 -152
  15. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/codecs/_bmp.c +152 -152
  16. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/codecs/_eer.c +152 -152
  17. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/codecs/_hcomp.c +152 -152
  18. opencodecs-0.1.5/src/opencodecs/codecs/_plio.c +30037 -0
  19. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/codecs/_png.c +152 -152
  20. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/codecs/_qoi.c +152 -152
  21. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/codecs/_rcomp.c +152 -152
  22. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/codecs/_rgbe.c +152 -152
  23. opencodecs-0.1.5/src/opencodecs/uhdr.py +209 -0
  24. {opencodecs-0.1.3 → opencodecs-0.1.5/src/opencodecs.egg-info}/PKG-INFO +1 -1
  25. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs.egg-info/SOURCES.txt +4 -1
  26. {opencodecs-0.1.3 → opencodecs-0.1.5}/tests/test_eer.py +61 -9
  27. {opencodecs-0.1.3 → opencodecs-0.1.5}/tests/test_fits.py +14 -5
  28. {opencodecs-0.1.3 → opencodecs-0.1.5}/tests/test_omezarr.py +13 -0
  29. {opencodecs-0.1.3 → opencodecs-0.1.5}/tests/test_tiff_http.py +74 -0
  30. {opencodecs-0.1.3 → opencodecs-0.1.5}/tests/test_tiff_writer.py +68 -0
  31. opencodecs-0.1.5/tests/test_uhdr.py +302 -0
  32. opencodecs-0.1.3/tests/test_ultrahdr.py +0 -130
  33. {opencodecs-0.1.3 → opencodecs-0.1.5}/3rdparty/bcdec/bcdec.h +0 -0
  34. {opencodecs-0.1.3 → opencodecs-0.1.5}/3rdparty/bitshuffle/bitshuffle_core.c +0 -0
  35. {opencodecs-0.1.3 → opencodecs-0.1.5}/3rdparty/bitshuffle/iochain.c +0 -0
  36. {opencodecs-0.1.3 → opencodecs-0.1.5}/3rdparty/cfitsio/fits_hdecompress.c +0 -0
  37. {opencodecs-0.1.3 → opencodecs-0.1.5}/3rdparty/cfitsio/ricecomp.c +0 -0
  38. {opencodecs-0.1.3 → opencodecs-0.1.5}/3rdparty/imcd_eer/eer.c +0 -0
  39. {opencodecs-0.1.3 → opencodecs-0.1.5}/3rdparty/imcd_eer/eer.h +0 -0
  40. {opencodecs-0.1.3 → opencodecs-0.1.5}/3rdparty/imcd_lzw/lzw.c +0 -0
  41. {opencodecs-0.1.3 → opencodecs-0.1.5}/3rdparty/imcd_lzw/lzw.h +0 -0
  42. {opencodecs-0.1.3 → opencodecs-0.1.5}/3rdparty/libspng/spng.c +0 -0
  43. {opencodecs-0.1.3 → opencodecs-0.1.5}/3rdparty/oc_tifflzw/oc_tifflzw.c +0 -0
  44. {opencodecs-0.1.3 → opencodecs-0.1.5}/3rdparty/oc_tifflzw/oc_tifflzw.h +0 -0
  45. {opencodecs-0.1.3 → opencodecs-0.1.5}/3rdparty/rgbe/rgbe.c +0 -0
  46. {opencodecs-0.1.3 → opencodecs-0.1.5}/MANIFEST.in +0 -0
  47. {opencodecs-0.1.3 → opencodecs-0.1.5}/README.md +0 -0
  48. {opencodecs-0.1.3 → opencodecs-0.1.5}/bench/build_libjxl.sh +0 -0
  49. {opencodecs-0.1.3 → opencodecs-0.1.5}/patches/libjxl/README.md +0 -0
  50. {opencodecs-0.1.3 → opencodecs-0.1.5}/setup.cfg +0 -0
  51. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/_aec_codec.py +0 -0
  52. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/_avif_codec.py +0 -0
  53. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/_b2nd_codec.py +0 -0
  54. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/_bcn_codec.py +0 -0
  55. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/_bitshuffle_codec.py +0 -0
  56. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/_blosc2_codec.py +0 -0
  57. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/_bmp_codec.py +0 -0
  58. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/_brotli_codec.py +0 -0
  59. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/_brunsli_codec.py +0 -0
  60. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/_byteshuffle_codec.py +0 -0
  61. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/_bz2_codec.py +0 -0
  62. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/_cms_codec.py +0 -0
  63. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/_czi_codec.py +0 -0
  64. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/_czi_reader.py +0 -0
  65. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/_czi_writer.py +0 -0
  66. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/_deflate_codec.py +0 -0
  67. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/_dicomrle_codec.py +0 -0
  68. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/_dicomweb.py +0 -0
  69. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/_ets.py +0 -0
  70. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/_fits.py +0 -0
  71. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/_fits_codec.py +0 -0
  72. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/_gif_codec.py +0 -0
  73. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/_gzip_codec.py +0 -0
  74. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/_hdf5_codec.py +0 -0
  75. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/_hdf5_http.py +0 -0
  76. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/_heif_codec.py +0 -0
  77. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/_htj2k_codec.py +0 -0
  78. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/_jpeg2k_codec.py +0 -0
  79. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/_jpeg_codec.py +0 -0
  80. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/_jpegls_codec.py +0 -0
  81. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/_jxl_codec.py +0 -0
  82. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/_lerc_codec.py +0 -0
  83. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/_lif_codec.py +0 -0
  84. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/_lif_native.py +0 -0
  85. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/_lz4_codec.py +0 -0
  86. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/_lzma_codec.py +0 -0
  87. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/_mozjpeg_codec.py +0 -0
  88. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/_nd2_codec.py +0 -0
  89. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/_nd2_native.py +0 -0
  90. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/_ndtiff.py +0 -0
  91. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/_ndtiff_pyramid.py +0 -0
  92. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/_ndtiff_writer.py +0 -0
  93. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/_none_codec.py +0 -0
  94. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/_numpy_codec.py +0 -0
  95. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/_oib_codec.py +0 -0
  96. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/_oib_native.py +0 -0
  97. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/_oir_codec.py +0 -0
  98. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/_ole2.py +0 -0
  99. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/_ome_xml.py +0 -0
  100. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/_omezarr.py +0 -0
  101. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/_omezarr_writer.py +0 -0
  102. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/_packints_codec.py +0 -0
  103. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/_pcodec_codec.py +0 -0
  104. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/_png_codec.py +0 -0
  105. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/_predictor_codec.py +0 -0
  106. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/_qoi_codec.py +0 -0
  107. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/_quantize_codec.py +0 -0
  108. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/_rcomp_codec.py +0 -0
  109. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/_rgbe.py +0 -0
  110. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/_rgbe_codec.py +0 -0
  111. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/_snappy_codec.py +0 -0
  112. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/_sperr_codec.py +0 -0
  113. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/_sz3_codec.py +0 -0
  114. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/_tiff_codec.py +0 -0
  115. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/_ultrahdr_codec.py +0 -0
  116. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/_vsi_codec.py +0 -0
  117. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/_webp_codec.py +0 -0
  118. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/_zarr_codecs.py +0 -0
  119. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/_zfp_codec.py +0 -0
  120. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/_zstd_codec.py +0 -0
  121. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/codecs/_bitshuffle.c +0 -0
  122. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/codecs/_bytetools.c +0 -0
  123. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/codecs/_deflate.c +0 -0
  124. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/codecs/_lz4.c +0 -0
  125. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/codecs/_ndtiff.c +0 -0
  126. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/codecs/_registry.py +0 -0
  127. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/codecs/_tiff.c +0 -0
  128. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/codecs/_zstd.c +0 -0
  129. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/core/__init__.py +0 -0
  130. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/core/_io_helpers.py +0 -0
  131. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/core/_optional_backend.py +0 -0
  132. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/core/codec.py +0 -0
  133. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/core/color.py +0 -0
  134. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/core/errors.py +0 -0
  135. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/core/io.py +0 -0
  136. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/core/pyramid.py +0 -0
  137. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/core/segment_compression.py +0 -0
  138. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/jxl.py +0 -0
  139. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/parallel.py +0 -0
  140. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/tiff_reader.py +0 -0
  141. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/tifffile_patch.py +0 -0
  142. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs/zarr.py +0 -0
  143. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs.egg-info/dependency_links.txt +0 -0
  144. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs.egg-info/requires.txt +0 -0
  145. {opencodecs-0.1.3 → opencodecs-0.1.5}/src/opencodecs.egg-info/top_level.txt +0 -0
  146. {opencodecs-0.1.3 → opencodecs-0.1.5}/tests/test_auto_pyramid.py +0 -0
  147. {opencodecs-0.1.3 → opencodecs-0.1.5}/tests/test_bcdec.py +0 -0
  148. {opencodecs-0.1.3 → opencodecs-0.1.5}/tests/test_brunsli.py +0 -0
  149. {opencodecs-0.1.3 → opencodecs-0.1.5}/tests/test_charls.py +0 -0
  150. {opencodecs-0.1.3 → opencodecs-0.1.5}/tests/test_core_codec_api.py +0 -0
  151. {opencodecs-0.1.3 → opencodecs-0.1.5}/tests/test_corpus.py +0 -0
  152. {opencodecs-0.1.3 → opencodecs-0.1.5}/tests/test_corpus_codec_decode.py +0 -0
  153. {opencodecs-0.1.3 → opencodecs-0.1.5}/tests/test_corpus_other.py +0 -0
  154. {opencodecs-0.1.3 → opencodecs-0.1.5}/tests/test_corpus_png.py +0 -0
  155. {opencodecs-0.1.3 → opencodecs-0.1.5}/tests/test_corpus_tiff.py +0 -0
  156. {opencodecs-0.1.3 → opencodecs-0.1.5}/tests/test_corpus_vendors.py +0 -0
  157. {opencodecs-0.1.3 → opencodecs-0.1.5}/tests/test_coverage_final.py +0 -0
  158. {opencodecs-0.1.3 → opencodecs-0.1.5}/tests/test_coverage_finishing.py +0 -0
  159. {opencodecs-0.1.3 → opencodecs-0.1.5}/tests/test_coverage_last.py +0 -0
  160. {opencodecs-0.1.3 → opencodecs-0.1.5}/tests/test_coverage_more.py +0 -0
  161. {opencodecs-0.1.3 → opencodecs-0.1.5}/tests/test_coverage_push.py +0 -0
  162. {opencodecs-0.1.3 → opencodecs-0.1.5}/tests/test_czi_fixture.py +0 -0
  163. {opencodecs-0.1.3 → opencodecs-0.1.5}/tests/test_czi_pyramid.py +0 -0
  164. {opencodecs-0.1.3 → opencodecs-0.1.5}/tests/test_czi_writer.py +0 -0
  165. {opencodecs-0.1.3 → opencodecs-0.1.5}/tests/test_data_source.py +0 -0
  166. {opencodecs-0.1.3 → opencodecs-0.1.5}/tests/test_deflate.py +0 -0
  167. {opencodecs-0.1.3 → opencodecs-0.1.5}/tests/test_dicomweb.py +0 -0
  168. {opencodecs-0.1.3 → opencodecs-0.1.5}/tests/test_edge_cases.py +0 -0
  169. {opencodecs-0.1.3 → opencodecs-0.1.5}/tests/test_encode_comprehensive.py +0 -0
  170. {opencodecs-0.1.3 → opencodecs-0.1.5}/tests/test_error_handling.py +0 -0
  171. {opencodecs-0.1.3 → opencodecs-0.1.5}/tests/test_gif.py +0 -0
  172. {opencodecs-0.1.3 → opencodecs-0.1.5}/tests/test_hdf5_http.py +0 -0
  173. {opencodecs-0.1.3 → opencodecs-0.1.5}/tests/test_hdf5_local.py +0 -0
  174. {opencodecs-0.1.3 → opencodecs-0.1.5}/tests/test_heif_avif_features.py +0 -0
  175. {opencodecs-0.1.3 → opencodecs-0.1.5}/tests/test_http_byte_savings.py +0 -0
  176. {opencodecs-0.1.3 → opencodecs-0.1.5}/tests/test_http_prefetch.py +0 -0
  177. {opencodecs-0.1.3 → opencodecs-0.1.5}/tests/test_http_unified.py +0 -0
  178. {opencodecs-0.1.3 → opencodecs-0.1.5}/tests/test_io_chunked.py +0 -0
  179. {opencodecs-0.1.3 → opencodecs-0.1.5}/tests/test_isal.py +0 -0
  180. {opencodecs-0.1.3 → opencodecs-0.1.5}/tests/test_jxl.py +0 -0
  181. {opencodecs-0.1.3 → opencodecs-0.1.5}/tests/test_lif_native.py +0 -0
  182. {opencodecs-0.1.3 → opencodecs-0.1.5}/tests/test_live_archives.py +0 -0
  183. {opencodecs-0.1.3 → opencodecs-0.1.5}/tests/test_mozjpeg.py +0 -0
  184. {opencodecs-0.1.3 → opencodecs-0.1.5}/tests/test_native_parity.py +0 -0
  185. {opencodecs-0.1.3 → opencodecs-0.1.5}/tests/test_nd2_native.py +0 -0
  186. {opencodecs-0.1.3 → opencodecs-0.1.5}/tests/test_ndtiff.py +0 -0
  187. {opencodecs-0.1.3 → opencodecs-0.1.5}/tests/test_oib_native.py +0 -0
  188. {opencodecs-0.1.3 → opencodecs-0.1.5}/tests/test_ome_xml.py +0 -0
  189. {opencodecs-0.1.3 → opencodecs-0.1.5}/tests/test_omezarr_sharded_range.py +0 -0
  190. {opencodecs-0.1.3 → opencodecs-0.1.5}/tests/test_open_pyramid.py +0 -0
  191. {opencodecs-0.1.3 → opencodecs-0.1.5}/tests/test_openjph.py +0 -0
  192. {opencodecs-0.1.3 → opencodecs-0.1.5}/tests/test_optional_backend.py +0 -0
  193. {opencodecs-0.1.3 → opencodecs-0.1.5}/tests/test_parallel_jxl.py +0 -0
  194. {opencodecs-0.1.3 → opencodecs-0.1.5}/tests/test_phase4_filters.py +0 -0
  195. {opencodecs-0.1.3 → opencodecs-0.1.5}/tests/test_phase5_icc.py +0 -0
  196. {opencodecs-0.1.3 → opencodecs-0.1.5}/tests/test_phase6_codecs.py +0 -0
  197. {opencodecs-0.1.3 → opencodecs-0.1.5}/tests/test_phase7_cms.py +0 -0
  198. {opencodecs-0.1.3 → opencodecs-0.1.5}/tests/test_rgbe.py +0 -0
  199. {opencodecs-0.1.3 → opencodecs-0.1.5}/tests/test_snappy.py +0 -0
  200. {opencodecs-0.1.3 → opencodecs-0.1.5}/tests/test_tier1_scientific.py +0 -0
  201. {opencodecs-0.1.3 → opencodecs-0.1.5}/tests/test_tiff_codec_encode.py +0 -0
  202. {opencodecs-0.1.3 → opencodecs-0.1.5}/tests/test_tiff_native.py +0 -0
  203. {opencodecs-0.1.3 → opencodecs-0.1.5}/tests/test_tiff_pyramid.py +0 -0
  204. {opencodecs-0.1.3 → opencodecs-0.1.5}/tests/test_tiff_reader.py +0 -0
  205. {opencodecs-0.1.3 → opencodecs-0.1.5}/tests/test_tifffile_patch.py +0 -0
  206. {opencodecs-0.1.3 → opencodecs-0.1.5}/tests/test_unified_api.py +0 -0
  207. {opencodecs-0.1.3 → opencodecs-0.1.5}/tests/test_vsi_oir.py +0 -0
  208. {opencodecs-0.1.3 → opencodecs-0.1.5}/tests/test_zarr_adapters.py +0 -0
@@ -0,0 +1,331 @@
1
+ /* stdlib is needed for the abs function */
2
+ #include <stdlib.h>
3
+ /*
4
+ The following prototype code was provided by Doug Tody, NRAO, for
5
+ performing conversion between pixel arrays and line lists. The
6
+ compression technique is used in IRAF.
7
+ */
8
+ int pl_p2li (int *pxsrc, int xs, short *lldst, int npix);
9
+ int pl_l2pi (short *ll_src, int xs, int *px_dst, int npix);
10
+
11
+
12
+ /*
13
+ * PL_P2L -- Convert a pixel array to a line list. The length of the list is
14
+ * returned as the function value.
15
+ *
16
+ * Translated from the SPP version using xc -f, f2c. 8Sep99 DCT.
17
+ */
18
+
19
+ #ifndef min
20
+ #define min(a,b) (((a)<(b))?(a):(b))
21
+ #endif
22
+ #ifndef max
23
+ #define max(a,b) (((a)>(b))?(a):(b))
24
+ #endif
25
+
26
+ int pl_p2li (int *pxsrc, int xs, short *lldst, int npix)
27
+ /* int *pxsrc; input pixel array */
28
+ /* int xs; starting index in pxsrc (?) */
29
+ /* short *lldst; encoded line list */
30
+ /* int npix; number of pixels to convert */
31
+ {
32
+ /* System generated locals */
33
+ int ret_val, i__1, i__2, i__3;
34
+
35
+ /* Local variables */
36
+ int zero, v, x1, hi, ip, dv, xe, np, op, iz, nv = 0, pv, nz;
37
+
38
+ /* Parameter adjustments */
39
+ --lldst;
40
+ --pxsrc;
41
+
42
+ /* Function Body */
43
+ if (! (npix <= 0)) {
44
+ goto L110;
45
+ }
46
+ ret_val = 0;
47
+ goto L100;
48
+ L110:
49
+ lldst[3] = -100;
50
+ lldst[2] = 7;
51
+ lldst[1] = 0;
52
+ lldst[6] = 0;
53
+ lldst[7] = 0;
54
+ xe = xs + npix - 1;
55
+ op = 8;
56
+ zero = 0;
57
+ /* Computing MAX */
58
+ i__1 = zero, i__2 = pxsrc[xs];
59
+ pv = max(i__1,i__2);
60
+ x1 = xs;
61
+ iz = xs;
62
+ hi = 1;
63
+ i__1 = xe;
64
+ for (ip = xs; ip <= i__1; ++ip) {
65
+ if (! (ip < xe)) {
66
+ goto L130;
67
+ }
68
+ /* Computing MAX */
69
+ i__2 = zero, i__3 = pxsrc[ip + 1];
70
+ nv = max(i__2,i__3);
71
+ if (! (nv == pv)) {
72
+ goto L140;
73
+ }
74
+ goto L120;
75
+ L140:
76
+ if (! (pv == 0)) {
77
+ goto L150;
78
+ }
79
+ pv = nv;
80
+ x1 = ip + 1;
81
+ goto L120;
82
+ L150:
83
+ goto L131;
84
+ L130:
85
+ if (! (pv == 0)) {
86
+ goto L160;
87
+ }
88
+ x1 = xe + 1;
89
+ L160:
90
+ L131:
91
+ np = ip - x1 + 1;
92
+ nz = x1 - iz;
93
+ if (! (pv > 0)) {
94
+ goto L170;
95
+ }
96
+ dv = pv - hi;
97
+ if (! (dv != 0)) {
98
+ goto L180;
99
+ }
100
+ hi = pv;
101
+ if (! (abs(dv) > 4095)) {
102
+ goto L190;
103
+ }
104
+ lldst[op] = (short) ((pv & 4095) + 4096);
105
+ ++op;
106
+ lldst[op] = (short) (pv / 4096);
107
+ ++op;
108
+ goto L191;
109
+ L190:
110
+ if (! (dv < 0)) {
111
+ goto L200;
112
+ }
113
+ lldst[op] = (short) (-dv + 12288);
114
+ goto L201;
115
+ L200:
116
+ lldst[op] = (short) (dv + 8192);
117
+ L201:
118
+ ++op;
119
+ if (! (np == 1 && nz == 0)) {
120
+ goto L210;
121
+ }
122
+ v = lldst[op - 1];
123
+ lldst[op - 1] = (short) (v | 16384);
124
+ goto L91;
125
+ L210:
126
+ L191:
127
+ L180:
128
+ L170:
129
+ if (! (nz > 0)) {
130
+ goto L220;
131
+ }
132
+ L230:
133
+ if (! (nz > 0)) {
134
+ goto L232;
135
+ }
136
+ lldst[op] = (short) min(4095,nz);
137
+ ++op;
138
+ /* L231: */
139
+ nz += -4095;
140
+ goto L230;
141
+ L232:
142
+ if (! (np == 1 && pv > 0)) {
143
+ goto L240;
144
+ }
145
+ lldst[op - 1] = (short) (lldst[op - 1] + 20481);
146
+ goto L91;
147
+ L240:
148
+ L220:
149
+ L250:
150
+ if (! (np > 0)) {
151
+ goto L252;
152
+ }
153
+ lldst[op] = (short) (min(4095,np) + 16384);
154
+ ++op;
155
+ /* L251: */
156
+ np += -4095;
157
+ goto L250;
158
+ L252:
159
+ L91:
160
+ x1 = ip + 1;
161
+ iz = x1;
162
+ pv = nv;
163
+ L120:
164
+ ;
165
+ }
166
+ /* L121: */
167
+ lldst[4] = (short) ((op - 1) % 32768);
168
+ lldst[5] = (short) ((op - 1) / 32768);
169
+ ret_val = op - 1;
170
+ goto L100;
171
+ L100:
172
+ return ret_val;
173
+ } /* plp2li_ */
174
+
175
+ /*
176
+ * PL_L2PI -- Translate a PLIO line list into an integer pixel array.
177
+ * The number of pixels output (always npix) is returned as the function
178
+ * value.
179
+ *
180
+ * Translated from the SPP version using xc -f, f2c. 8Sep99 DCT.
181
+ */
182
+
183
+ int pl_l2pi (short *ll_src, int xs, int *px_dst, int npix)
184
+ /* short *ll_src; encoded line list */
185
+ /* int xs; starting index in ll_src */
186
+ /* int *px_dst; output pixel array */
187
+ /* int npix; number of pixels to convert */
188
+ {
189
+ /* System generated locals */
190
+ int ret_val, i__1, i__2;
191
+
192
+ /* Local variables */
193
+ int data, sw0001, otop, i__, lllen, i1, i2, x1, x2, ip, xe, np,
194
+ op, pv, opcode, llfirt;
195
+ int skipwd;
196
+
197
+ /* Parameter adjustments */
198
+ --px_dst;
199
+ --ll_src;
200
+
201
+ /* Function Body */
202
+ if (! (ll_src[3] > 0)) {
203
+ goto L110;
204
+ }
205
+ lllen = ll_src[3];
206
+ llfirt = 4;
207
+ goto L111;
208
+ L110:
209
+ lllen = (ll_src[5] << 15) + ll_src[4];
210
+ llfirt = ll_src[2] + 1;
211
+ L111:
212
+ if (! (npix <= 0 || lllen <= 0)) {
213
+ goto L120;
214
+ }
215
+ ret_val = 0;
216
+ goto L100;
217
+ L120:
218
+ xe = xs + npix - 1;
219
+ skipwd = 0;
220
+ op = 1;
221
+ x1 = 1;
222
+ pv = 1;
223
+ i__1 = lllen;
224
+ for (ip = llfirt; ip <= i__1; ++ip) {
225
+ if (! skipwd) {
226
+ goto L140;
227
+ }
228
+ skipwd = 0;
229
+ goto L130;
230
+ L140:
231
+ opcode = ll_src[ip] / 4096;
232
+ data = ll_src[ip] & 4095;
233
+ sw0001 = opcode;
234
+ goto L150;
235
+ L160:
236
+ x2 = x1 + data - 1;
237
+ i1 = max(x1,xs);
238
+ i2 = min(x2,xe);
239
+ np = i2 - i1 + 1;
240
+ if (! (np > 0)) {
241
+ goto L170;
242
+ }
243
+ otop = op + np - 1;
244
+ if (! (opcode == 4)) {
245
+ goto L180;
246
+ }
247
+ i__2 = otop;
248
+ for (i__ = op; i__ <= i__2; ++i__) {
249
+ px_dst[i__] = pv;
250
+ /* L190: */
251
+ }
252
+ /* L191: */
253
+ goto L181;
254
+ L180:
255
+ i__2 = otop;
256
+ for (i__ = op; i__ <= i__2; ++i__) {
257
+ px_dst[i__] = 0;
258
+ /* L200: */
259
+ }
260
+ /* L201: */
261
+ if (! (opcode == 5 && i2 == x2)) {
262
+ goto L210;
263
+ }
264
+ px_dst[otop] = pv;
265
+ L210:
266
+ L181:
267
+ op = otop + 1;
268
+ L170:
269
+ x1 = x2 + 1;
270
+ goto L151;
271
+ L220:
272
+ pv = (ll_src[ip + 1] << 12) + data;
273
+ skipwd = 1;
274
+ goto L151;
275
+ L230:
276
+ pv += data;
277
+ goto L151;
278
+ L240:
279
+ pv -= data;
280
+ goto L151;
281
+ L250:
282
+ pv += data;
283
+ goto L91;
284
+ L260:
285
+ pv -= data;
286
+ L91:
287
+ if (! (x1 >= xs && x1 <= xe)) {
288
+ goto L270;
289
+ }
290
+ px_dst[op] = pv;
291
+ ++op;
292
+ L270:
293
+ ++x1;
294
+ goto L151;
295
+ L150:
296
+ ++sw0001;
297
+ if (sw0001 < 1 || sw0001 > 8) {
298
+ goto L151;
299
+ }
300
+ switch ((int)sw0001) {
301
+ case 1: goto L160;
302
+ case 2: goto L220;
303
+ case 3: goto L230;
304
+ case 4: goto L240;
305
+ case 5: goto L160;
306
+ case 6: goto L160;
307
+ case 7: goto L250;
308
+ case 8: goto L260;
309
+ }
310
+ L151:
311
+ if (! (x1 > xe)) {
312
+ goto L280;
313
+ }
314
+ goto L131;
315
+ L280:
316
+ L130:
317
+ ;
318
+ }
319
+ L131:
320
+ i__1 = npix;
321
+ for (i__ = op; i__ <= i__1; ++i__) {
322
+ px_dst[i__] = 0;
323
+ /* L290: */
324
+ }
325
+ /* L291: */
326
+ ret_val = npix;
327
+ goto L100;
328
+ L100:
329
+ return ret_val;
330
+ } /* pll2pi_ */
331
+
@@ -10,6 +10,154 @@ 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.1.5 (2026-05-31)
14
+ ------------------
15
+
16
+ **New codecs**
17
+
18
+ * ``opencodecs.uhdr`` — Ultra-HDR / ISO 21496-1 (gainmap JPEG) via a
19
+ direct Cython binding to Google's libultrahdr. ``encode(hdr, ...)``
20
+ wraps libuhdr's full pipeline; ``encode_native(hdr, sdr=None, ...)``
21
+ is a fused-Cython fast path that computes SDR base + gain map in
22
+ ``nogil`` kernels (IEEE-754 polynomial log2/pow, cross-platform —
23
+ no Apple Accelerate intrinsics), JPEG-encodes both layers in
24
+ parallel via a 3-worker ThreadPoolExecutor, then hands the
25
+ pre-encoded JPEGs to libuhdr's api-4 for container assembly.
26
+ Measured 31 ms median for a 2000² float HDR on M-series Mac vs
27
+ ~173 ms for the libuhdr reference path — 5.5× faster. The
28
+ ``sdr=`` argument lets callers (notably tilescan pipelines) supply
29
+ their own SDR base instead of accepting the peak-normalised default.
30
+
31
+ * ``PLIO_1`` — IRAF run-length mask coding closes the last FITS
32
+ tile-compression gap. Vendors cfitsio's ``pliocomp.c`` (Doug Tody /
33
+ NRAO, public-domain) and adds the tiny
34
+ ``opencodecs.codecs._plio.decode_raw`` shim. Round-trip tested
35
+ against astropy. The ``COMPRESSED_DATA`` BINTABLE column walker
36
+ now tracks per-column element byte width so the ``1PI`` (int16
37
+ opcodes) layout PLIO uses works alongside the ``1PB`` byte VLA the
38
+ other compressors use.
39
+
40
+ **New API**
41
+
42
+ * ``EerReader.sum(start, stop, *, weights=None, dtype=...)`` —
43
+ per-frame dose curve. ``weights[k]`` multiplies the k-th frame in
44
+ the requested range; output promotes to float64 when weights are
45
+ given so fractional contributions don't truncate. Use case:
46
+ beam-induced-motion correction and temporal-binning schemes that
47
+ emphasise different exposures across the acquisition.
48
+
49
+ **Bug fixes**
50
+
51
+ * HEIF encode against libheif ≥ 1.18 — newer libheif calls
52
+ ``strlen()`` on the user write-callback's ``heif_error.message``
53
+ even on success and rejects a ``NULL`` pointer with *"heif_writer
54
+ callback returned a null error text"*. The callback now hands back
55
+ a static empty string on success and a descriptive string on OOM.
56
+ Six tests (``test_heif_*``, ``test_phase5_icc.py[heif]``) go back
57
+ to green across every CI matrix entry.
58
+
59
+ **CI / build**
60
+
61
+ * ``bench/build_codec_libs.sh`` — the ``is_built`` cache-marker now
62
+ records the install dir alongside the version and re-verifies the
63
+ install dir exists before short-circuiting a recipe. Without this,
64
+ cibuildwheel's manylinux cache (which only covers ``/cibw-jxl-prefix``)
65
+ preserved the marker for recipes that install to
66
+ ``~/.cache/opencodecs/<lib>/`` outside the cached prefix —
67
+ ``lerc``, ``mozjpeg``, ``brotli``, ``zstd``, ``giflib`` — so the
68
+ recipe thought it was done while the actual library files were
69
+ gone. ``_lerc`` + ``_mozjpeg`` had been silently dropped from
70
+ Linux wheels for this reason; the fix restores them.
71
+
72
+ * ``test_omezarr.py`` skips at module level on zarr-python < 3 —
73
+ fixtures use the v3 API surface, which doesn't exist on the v2
74
+ branch pip resolves on Python 3.10.
75
+
76
+ * ``test_eer_imagecodecs_cross_validate`` is now symmetric on
77
+ decoder error: if either implementation raises, both must raise
78
+ for the combo to count. Tolerates imagecodecs ≥ 2026.5's tighter
79
+ output-buffer sizing.
80
+
81
+ 0.1.4 (2026-05-25)
82
+ ------------------
83
+
84
+ **libvips-inspired streaming improvements**
85
+
86
+ * ``HTTPDataSource(access='sequential', sequential_chunk_bytes=4*1024*1024)``
87
+ — opt-in libvips-style sequential read mode. Replaces the LRU +
88
+ adaptive read-ahead with a single rolling buffer that slides forward
89
+ as the caller reads. Memory stays bounded to one chunk regardless of
90
+ file size. Target workload: tile-by-tile raster scan over a huge
91
+ COG / OME-TIFF served over HTTP. Backward seeks still work but
92
+ invalidate the buffer; ``stats['sequential_backward_seeks']`` tracks
93
+ the count so users can spot a workload that's actually random and
94
+ would benefit from ``access='random'``.
95
+
96
+ * ``TiffPyramidReader.read_region`` now parallelises tile decode
97
+ across a thread pool (default ``min(cpu_count(), 8)``). The
98
+ compressed-tile decoders (JPEG, JPEG-2000, deflate, zstd, LZW, WebP,
99
+ LERC) all release the GIL in their C path, so threads scale across
100
+ cores on CPython. Kicks in only when the region covers 4+ tiles —
101
+ fewer than that and thread-pool spin-up cost dominates. Pass
102
+ ``num_decode_workers=1`` to force the serial path for benchmarks /
103
+ regression-diff.
104
+
105
+ * ``TiffWriter.write_pyramid_auto`` gained ``stream_levels=True``
106
+ (default for COG layout). Computes and writes one level at a time
107
+ via a new ``iter_pyramid_levels`` generator, dropping each finished
108
+ level before computing the next. Peak memory drops from
109
+ ``~1.33 × level0`` (entire geometric series materialised) to
110
+ ``~2 × current_level``. Useful when level 0 already dominates RAM
111
+ (whole-slide pathology, cryo-EM tomograms). Output file is
112
+ byte-identical to the materialize-all path. SubIFD layout
113
+ (``subifds=True``) still uses the materialize path because that
114
+ layout writes sub-resolution IFD offsets into the main IFD's tag
115
+ 330 — sub-level offsets have to be known up front.
116
+
117
+ Validated via tests:
118
+ ``test_http_sequential_serves_from_rolling_buffer``,
119
+ ``test_http_sequential_backward_seek_counted``,
120
+ ``test_pyramid_reader_parallel_decode_matches_serial``,
121
+ ``test_pyramid_auto_stream_matches_materialize``,
122
+ ``test_iter_pyramid_levels_matches_make_pyramid_levels``.
123
+
124
+ **MozJPEG ships on every wheel**
125
+
126
+ * Added MozJPEG (Mozilla's libjpeg-turbo fork) to the cibuildwheel
127
+ codec-lib build set on Linux, macOS, and Windows. MozJPEG produces
128
+ JPEG files ~10-15% smaller than baseline libjpeg-turbo at the same
129
+ quality setting (progressive encoding + trellis quantization +
130
+ better quantization tables). The ``_mozjpeg`` extension has shipped
131
+ on macOS wheels for a while via brew; v0.1.4 brings it to
132
+ Linux + Windows so the codec is available everywhere.
133
+ * New ``build_mozjpeg`` recipe in ``bench/build_codec_libs.sh``
134
+ installs into a keg-style ``mozjpeg/`` subdir under the prefix
135
+ (rather than the shared ``$PREFIX/{include,lib}``) to avoid the
136
+ libturbojpeg / libjpeg name collision with the libjpeg-turbo 3.x
137
+ install. setup.py's mozjpeg probe was extended to recognise the
138
+ new candidate paths on Linux (``~/.cache/opencodecs/mozjpeg``),
139
+ Windows (``$CONDA_PREFIX/Library/mozjpeg``), and the Windows
140
+ fallback (no ``nm``: trust the keg-style directory name).
141
+ * Locally validated on win-vm (MSVC 14.44 — same as CI) and
142
+ kevin-tower (Linux x86_64): ``bash bench/build_codec_libs.sh
143
+ --only=mozjpeg`` followed by ``setup.py build_ext --inplace``
144
+ produces ``_mozjpeg.{pyd,so}`` cleanly. Per the new lesson in
145
+ CLAUDE.md, this iteration was validated end-to-end through the
146
+ same script CI runs, with the same env vars, before any push.
147
+
148
+ **Post-publish wheel coverage check (CI hardening)**
149
+
150
+ * New ``ci/check_wheel_contents.py`` runs in the wheel-build job
151
+ immediately after ``cibuildwheel`` produces each wheel. It unzips
152
+ the wheel, walks ``opencodecs/codecs/``, and asserts every codec
153
+ in the ``MUST_SHIP_ALL_PLATFORMS`` set has a ``.pyd``/``.so``
154
+ present. Missing codec → matrix job fails → publish step gated.
155
+ * Catches the v0.1.2-style "changelog overclaim" silent-drop class
156
+ of bugs at build time instead of after publish. v0.1.2's Windows
157
+ wheels shipped without ``_sperr``/``_brunsli`` despite the
158
+ changelog claim; this check would have failed that build.
159
+
160
+
13
161
  0.1.3 (2026-05-22)
14
162
  ------------------
15
163
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: opencodecs
3
- Version: 0.1.3
3
+ Version: 0.1.5
4
4
  Summary: Streaming, network-aware image codecs for scientific imaging (prototype: JPEG XL)
5
5
  Author: Kevin Cutler
6
6
  License: BSD-3-Clause
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "opencodecs"
7
- version = "0.1.3"
7
+ version = "0.1.5"
8
8
  description = "Streaming, network-aware image codecs for scientific imaging (prototype: JPEG XL)"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -142,7 +142,7 @@ before-all = [
142
142
  # bench/build_codec_libs.sh into the cached /cibw-jxl-prefix mount.
143
143
  # Skips encoders (aom, x265) — wheel ships decode-only AVIF/HEIF to
144
144
  # keep build < 12 min cold + avoid GPL x265.
145
- "bash -c 'PATH=$HOME/.cargo/bin:$PATH bash bench/build_codec_libs.sh --only=libjpeg-turbo,c-blosc2,libde265,dav1d,libavif,libheif,libaec,lerc,zfp,SZ3,pcodec,SPERR,brunsli'",
145
+ "bash -c 'PATH=$HOME/.cargo/bin:$PATH bash bench/build_codec_libs.sh --only=libjpeg-turbo,c-blosc2,libde265,dav1d,libavif,libheif,libaec,lerc,zfp,SZ3,pcodec,SPERR,brunsli,mozjpeg'",
146
146
  "bash bench/build_libjxl.sh",
147
147
  ]
148
148
  # Don't set OPENCODECS_*_PREFIX here — build_wheels.yml's
@@ -159,8 +159,11 @@ before-all = [
159
159
  # cache by bench/build_codec_libs.sh. brunsli vendors brotli as a
160
160
  # submodule so the brew brotli isn't required at link time.
161
161
  before-all = [
162
+ # mozjpeg removed from this brew install — the formula ships libjpeg
163
+ # only, not libturbojpeg, so the _mozjpeg probe couldn't find what it
164
+ # needed. Source-build via build_codec_libs.sh produces both libs.
162
165
  "brew install jpeg-xl libavif libheif webp libpng jpeg-turbo openjpeg zstd brotli c-blosc2 lz4 libdeflate libaec liblerc zfp rust cmake ninja",
163
- "bash bench/build_codec_libs.sh --only=SZ3,pcodec,SPERR,brunsli",
166
+ "bash bench/build_codec_libs.sh --only=SZ3,pcodec,SPERR,brunsli,mozjpeg",
164
167
  ]
165
168
  # macos-latest is now macOS 15 (Sequoia) on arm64. Homebrew formulas are
166
169
  # compiled for 15.x, so any wheel target lower than 15 fails the delocate
@@ -638,12 +638,30 @@ def _maybe_build_mozjpeg_ext() -> list[Extension]:
638
638
  # by symbol absence alone. For generic system prefixes like /usr or
639
639
  # /usr/local we additionally require a ``mozjpeg/`` include subdir
640
640
  # so we don't misidentify vanilla libjpeg-turbo as MozJPEG.
641
- candidates = [
641
+ # Keg-style per-user cache install from
642
+ # bench/build_codec_libs.sh::build_mozjpeg (Linux/macOS) and the
643
+ # cibuildwheel Windows install (under $OPENCODECS_CODEC_LIBS_PREFIX
644
+ # or $CONDA_PREFIX/Library). All "keg-style" — no mozjpeg/ include
645
+ # subdir needed because the prefix itself is mozjpeg-only.
646
+ candidates: list[tuple[Path, bool]] = [
647
+ (_OC_USER_CACHE / "mozjpeg", False),
642
648
  (Path("/opt/homebrew/opt/mozjpeg"), False),
643
649
  (Path("/usr/local/opt/mozjpeg"), False),
650
+ ]
651
+ _libs_prefix_env = os.environ.get("OPENCODECS_CODEC_LIBS_PREFIX")
652
+ if _libs_prefix_env:
653
+ _libs_prefix_path = Path(_libs_prefix_env)
654
+ candidates.append((_libs_prefix_path / "mozjpeg", False))
655
+ # Conda Library/ layout: $CONDA_PREFIX/Library/mozjpeg
656
+ candidates.append((_libs_prefix_path / "Library" / "mozjpeg", False))
657
+ _conda = os.environ.get("CONDA_PREFIX")
658
+ if _conda:
659
+ candidates.append((Path(_conda) / "mozjpeg", False))
660
+ candidates.append((Path(_conda) / "Library" / "mozjpeg", False))
661
+ candidates.extend([
644
662
  (Path("/usr"), True),
645
663
  (Path("/usr/local"), True),
646
- ]
664
+ ])
647
665
  prefix = None
648
666
  lib_subdir = None # "lib" or "lib/x86_64-linux-gnu" etc.
649
667
  lib_filename = None
@@ -653,24 +671,41 @@ def _maybe_build_mozjpeg_ext() -> list[Extension]:
653
671
  if not (c / "include" / "turbojpeg.h").exists():
654
672
  continue
655
673
  # Find the actual lib file. Linux multiarch ships under
656
- # lib/<triple>/, plain /usr/local installs use lib/.
674
+ # lib/<triple>/, plain /usr/local installs use lib/, and
675
+ # AlmaLinux/RHEL (manylinux_2_28) puts 64-bit libs under lib64/
676
+ # by default — that's the GNU autoconf convention on those
677
+ # distros, and mozjpeg's CMake picks it up.
657
678
  lib_candidates = [
658
679
  ("lib", "libturbojpeg.dylib"),
659
680
  ("lib", "libturbojpeg.so"),
660
681
  ("lib", "libturbojpeg.so.0"),
682
+ ("lib64", "libturbojpeg.so"),
683
+ ("lib64", "libturbojpeg.so.0"),
661
684
  ("lib/x86_64-linux-gnu", "libturbojpeg.so"),
662
685
  ("lib/x86_64-linux-gnu", "libturbojpeg.so.0"),
663
686
  ("lib/aarch64-linux-gnu", "libturbojpeg.so"),
664
687
  ("lib/aarch64-linux-gnu", "libturbojpeg.so.0"),
688
+ # Windows MSVC build (mozjpeg's CMake produces turbojpeg.lib
689
+ # import library next to turbojpeg.dll in bin/).
690
+ ("lib", "turbojpeg.lib"),
665
691
  ]
666
692
  for subdir, name in lib_candidates:
667
693
  libpath = c / subdir / name
668
694
  if not libpath.exists():
669
695
  continue
670
696
  # MozJPEG branches off libjpeg-turbo 1.x — it lacks the v3
671
- # tj3* API. If we're already constrained to a mozjpeg subdir,
672
- # accept any libturbojpeg under it. Otherwise probe symbols.
673
- if require_mozjpeg_subdir:
697
+ # tj3* API. Three cases for accepting a candidate:
698
+ # (a) require_mozjpeg_subdir=True (generic /usr or /usr/local
699
+ # with a mozjpeg/ include subdir) — trust the layout.
700
+ # (b) prefix path is named '.../mozjpeg' or '.../mozjpeg/'
701
+ # (homebrew keg, our per-user cache build, conda env
702
+ # subdir) — the dedicated dir name is the signal; skip
703
+ # symbol probing. This is required on Windows where nm
704
+ # isn't on PATH and on minimal Linux runners that don't
705
+ # ship binutils.
706
+ # (c) otherwise: probe libturbojpeg's symbol table with nm
707
+ # to confirm it's a MozJPEG build (lacks tj3Compress8).
708
+ if require_mozjpeg_subdir or c.name == "mozjpeg":
674
709
  prefix = c
675
710
  lib_subdir = subdir
676
711
  lib_filename = name
@@ -702,9 +737,18 @@ def _maybe_build_mozjpeg_ext() -> list[Extension]:
702
737
  ],
703
738
  library_dirs=[str(prefix / lib_subdir)],
704
739
  # Use absolute path on macOS to dodge the SDK-stub issue we
705
- # hit with zlib-ng-compat.
740
+ # hit with zlib-ng-compat. On macOS we ALSO add the lib dir
741
+ # to LC_RPATH because mozjpeg's libturbojpeg has
742
+ # install_name=@rpath/libturbojpeg.0.dylib; without an rpath
743
+ # to a keg-only prefix like /opt/homebrew/opt/mozjpeg/lib,
744
+ # delocate can't resolve the LC_LOAD_DYLIB and the wheel
745
+ # repair fails with "@rpath/libturbojpeg.0.dylib not found".
706
746
  libraries=[],
707
- extra_link_args=[str(prefix / lib_subdir / lib_filename)],
747
+ extra_link_args=(
748
+ [str(prefix / lib_subdir / lib_filename)]
749
+ + ([f"-Wl,-rpath,{prefix / lib_subdir}"]
750
+ if sys.platform == "darwin" else [])
751
+ ),
708
752
  define_macros=[("NPY_NO_DEPRECATED_API", "NPY_1_7_API_VERSION")],
709
753
  language="c",
710
754
  )]
@@ -1081,6 +1125,36 @@ extensions = [
1081
1125
  extra_link_args=extra_link_args,
1082
1126
  language="c",
1083
1127
  ),
1128
+ # libultrahdr (Ultra-HDR / ISO 21496-1). Links against system libuhdr.
1129
+ # macOS: `brew install libultrahdr` -> /opt/homebrew/{include,lib}.
1130
+ # Linux: build from https://github.com/google/libultrahdr.
1131
+ # If the header/dylib aren't present, the Extension still appears in
1132
+ # the wheel build plan -- the public opencodecs.uhdr module catches
1133
+ # ImportError and substitutes a clear "install libultrahdr" stub.
1134
+ Extension(
1135
+ name="opencodecs.codecs._uhdr",
1136
+ sources=["src/opencodecs/codecs/_uhdr.pyx"],
1137
+ include_dirs=[
1138
+ str(PKG_CODECS),
1139
+ numpy.get_include(),
1140
+ *_resolve_include_dirs("ultrahdr_api.h"),
1141
+ ],
1142
+ library_dirs=[],
1143
+ libraries=["uhdr"],
1144
+ define_macros=[
1145
+ ("NPY_NO_DEPRECATED_API", "NPY_1_7_API_VERSION"),
1146
+ ],
1147
+ # Aggressive optimisation is critical for the gain-map kernel
1148
+ # to auto-vectorise (NEON on Apple Silicon, AVX2 on x86). The
1149
+ # polynomial log2 in _gain_map_kernel only beats numpy's
1150
+ # vectorised log2 when the compiler turns it into SIMD.
1151
+ extra_compile_args=[
1152
+ "-O3",
1153
+ "-ffast-math", # fine for gain-map output (uint8, ~1% tolerance)
1154
+ "-fno-math-errno",
1155
+ ],
1156
+ language="c",
1157
+ ),
1084
1158
  # QOI: vendored single-header (3rdparty/qoi/qoi.h). Compile the impl
1085
1159
  # via QOI_IMPLEMENTATION; no external library needed.
1086
1160
  Extension(
@@ -1771,6 +1845,23 @@ extensions = [
1771
1845
  define_macros=[("NPY_NO_DEPRECATED_API", "NPY_1_7_API_VERSION")],
1772
1846
  language="c",
1773
1847
  ),
1848
+ # PLIO_1 decode for IRAF segmentation-mask tiles (ZCMPTYPE=
1849
+ # 'PLIO_1'). Vendored cfitsio pliocomp.c (Doug Tody / NRAO,
1850
+ # public-domain — see 3rdparty/cfitsio/License.txt).
1851
+ Extension(
1852
+ name="opencodecs.codecs._plio",
1853
+ sources=[
1854
+ "src/opencodecs/codecs/_plio.pyx",
1855
+ "3rdparty/cfitsio/pliocomp.c",
1856
+ ],
1857
+ include_dirs=[
1858
+ str(PKG_CODECS),
1859
+ numpy.get_include(),
1860
+ str(HERE / "3rdparty" / "cfitsio"),
1861
+ ],
1862
+ define_macros=[("NPY_NO_DEPRECATED_API", "NPY_1_7_API_VERSION")],
1863
+ language="c",
1864
+ ),
1774
1865
  # Radiance HDR (RGBE) — Bruce Walter / Greg Ward C library, vendored
1775
1866
  # as a single .c/.h pair. Public-domain origin (Greg Ward, LBL); the
1776
1867
  # vendored port carries the "USE AT YOUR OWN RISK" disclaimer in
@@ -1837,7 +1928,7 @@ _REQUIRED_HEADERS = {
1837
1928
  "opencodecs.codecs._snappy": ("snappy-c.h",),
1838
1929
  "opencodecs.codecs._pcodec": ("cpcodec.h",),
1839
1930
  "opencodecs.codecs._jpeg": ("turbojpeg.h",),
1840
- "opencodecs.codecs._ultrahdr": ("ultrahdr_api.h",),
1931
+ "opencodecs.codecs._uhdr": ("ultrahdr_api.h",),
1841
1932
  "opencodecs.codecs._isal": ("isa-l/igzip_lib.h",),
1842
1933
  "opencodecs.codecs._webp": ("webp/encode.h",),
1843
1934
  "opencodecs.codecs._jpeg2k": ("openjpeg-2.5/openjpeg.h", "openjpeg-2.4/openjpeg.h"),