_imagecodecs.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481
  1. # tifffile/_imagecodecs.py
  2. # Copyright (c) 2008-2026, Christoph Gohlke
  3. # All rights reserved.
  4. #
  5. # Redistribution and use in source and binary forms, with or without
  6. # modification, are permitted provided that the following conditions are met:
  7. #
  8. # 1. Redistributions of source code must retain the above copyright notice,
  9. # this list of conditions and the following disclaimer.
  10. #
  11. # 2. Redistributions in binary form must reproduce the above copyright notice,
  12. # this list of conditions and the following disclaimer in the documentation
  13. # and/or other materials provided with the distribution.
  14. #
  15. # 3. Neither the name of the copyright holder nor the names of its
  16. # contributors may be used to endorse or promote products derived from
  17. # this software without specific prior written permission.
  18. #
  19. # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  20. # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  21. # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  22. # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
  23. # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  24. # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  25. # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  26. # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  27. # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  28. # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  29. # POSSIBILITY OF SUCH DAMAGE.
  30. """Fallback imagecodecs codecs.
  31. This module provides alternative, pure Python and NumPy implementations of
  32. some functions of the `imagecodecs`_ package. The functions may raise
  33. `NotImplementedError`.
  34. .. _imagecodecs: https://github.com/cgohlke/imagecodecs
  35. """
  36. from __future__ import annotations
  37. __all__ = [
  38. 'bitorder_decode',
  39. 'delta_decode',
  40. 'delta_encode',
  41. 'float24_decode',
  42. 'lzma_decode',
  43. 'lzma_encode',
  44. 'packbits_decode',
  45. 'packints_decode',
  46. 'packints_encode',
  47. 'zlib_decode',
  48. 'zlib_encode',
  49. 'zstd_decode',
  50. 'zstd_encode',
  51. ]
  52. from typing import TYPE_CHECKING, overload
  53. import numpy
  54. if TYPE_CHECKING:
  55. from typing import Any, Literal
  56. from numpy.typing import ArrayLike, DTypeLike, NDArray
  57. try:
  58. import lzma
  59. def lzma_encode(
  60. data: bytes | NDArray[Any],
  61. /,
  62. level: int | None = None,
  63. *,
  64. out: Any = None,
  65. ) -> bytes:
  66. """Compress LZMA."""
  67. del level, out # unused
  68. if isinstance(data, numpy.ndarray):
  69. data = data.tobytes()
  70. return lzma.compress(data)
  71. def lzma_decode(data: bytes, /, *, out: Any = None) -> bytes:
  72. """Decompress LZMA."""
  73. del out # unused
  74. return lzma.decompress(data)
  75. except ImportError:
  76. # Python was built without lzma
  77. def lzma_encode(
  78. data: bytes | NDArray[Any],
  79. /,
  80. level: int | None = None,
  81. *,
  82. out: Any = None,
  83. ) -> bytes:
  84. """Raise ImportError."""
  85. del data, level, out # unused
  86. import lzma # noqa: F401
  87. return b''
  88. def lzma_decode(data: bytes, /, *, out: Any = None) -> bytes:
  89. """Raise ImportError."""
  90. del data, out # unused
  91. import lzma # noqa: F401
  92. return b''
  93. try:
  94. import zlib
  95. def zlib_encode(
  96. data: bytes | NDArray[Any],
  97. /,
  98. level: int | None = None,
  99. *,
  100. out: Any = None,
  101. ) -> bytes:
  102. """Compress Zlib DEFLATE."""
  103. del out # unused
  104. if isinstance(data, numpy.ndarray):
  105. data = data.tobytes()
  106. return zlib.compress(data, 6 if level is None else level)
  107. def zlib_decode(data: bytes, /, *, out: Any = None) -> bytes:
  108. """Decompress Zlib DEFLATE."""
  109. del out # unused
  110. return zlib.decompress(data)
  111. except ImportError:
  112. # Python was built without zlib
  113. def zlib_encode(
  114. data: bytes | NDArray[Any],
  115. /,
  116. level: int | None = None,
  117. *,
  118. out: Any = None,
  119. ) -> bytes:
  120. """Raise ImportError."""
  121. del data, level, out # unused
  122. import zlib # noqa: F401
  123. return b''
  124. def zlib_decode(data: bytes, /, *, out: Any = None) -> bytes:
  125. """Raise ImportError."""
  126. del data, out # unused
  127. import zlib # noqa: F401
  128. return b''
  129. try:
  130. from compression import zstd
  131. def zstd_encode(
  132. data: bytes | NDArray[Any],
  133. /,
  134. level: int | None = None,
  135. *,
  136. out: Any = None,
  137. ) -> bytes:
  138. """Compress ZSTD."""
  139. del out # unused
  140. if isinstance(data, numpy.ndarray):
  141. data = data.tobytes()
  142. return zstd.compress(data, level=level)
  143. def zstd_decode(data: bytes, /, *, out: Any = None) -> bytes:
  144. """Decompress ZSTD."""
  145. del out # unused
  146. return zstd.decompress(data)
  147. except ImportError:
  148. # Python was built without zstd
  149. def zstd_encode(
  150. data: bytes | NDArray[Any],
  151. /,
  152. level: int | None = None,
  153. *,
  154. out: Any = None,
  155. ) -> bytes:
  156. """Raise ImportError."""
  157. del data, level, out # unused
  158. from compression import zstd # noqa: F401
  159. return b''
  160. def zstd_decode(data: bytes, /, *, out: Any = None) -> bytes:
  161. """Raise ImportError."""
  162. del data, out # unused
  163. from compression import zstd # noqa: F401
  164. return b''
  165. def packbits_decode(encoded: bytes, /, *, out: Any = None) -> bytes:
  166. r"""Decompress PackBits encoded byte string.
  167. >>> packbits_decode(b'\x80\x80') # NOP
  168. b''
  169. >>> packbits_decode(b'\x02123')
  170. b'123'
  171. >>> packbits_decode(
  172. ... b'\xfe\xaa\x02\x80\x00\x2a\xfd\xaa\x03\x80\x00\x2a\x22\xf7\xaa'
  173. ... )[:-5]
  174. b'\xaa\xaa\xaa\x80\x00*\xaa\xaa\xaa\xaa\x80\x00*"\xaa\xaa\xaa\xaa\xaa'
  175. """
  176. out = []
  177. out_extend = out.extend
  178. i = 0
  179. try:
  180. while True:
  181. n = ord(encoded[i : i + 1]) + 1
  182. i += 1
  183. if n > 129:
  184. # replicate
  185. out_extend(encoded[i : i + 1] * (258 - n))
  186. i += 1
  187. elif n < 129:
  188. # literal
  189. out_extend(encoded[i : i + n])
  190. i += n
  191. except TypeError:
  192. pass
  193. return bytes(out)
  194. @overload
  195. def delta_encode(
  196. data: bytes | bytearray,
  197. /,
  198. axis: int = -1,
  199. dist: int = 1,
  200. *,
  201. out: Any = None,
  202. ) -> bytes: ...
  203. @overload
  204. def delta_encode(
  205. data: NDArray[Any], /, axis: int = -1, dist: int = 1, *, out: Any = None
  206. ) -> NDArray[Any]: ...
  207. def delta_encode(
  208. data: bytes | bytearray | NDArray[Any],
  209. /,
  210. axis: int = -1,
  211. dist: int = 1,
  212. *,
  213. out: Any = None,
  214. ) -> bytes | NDArray[Any]:
  215. """Encode Delta."""
  216. del out # unused
  217. if dist != 1:
  218. msg = f"delta_encode with {dist=} requires the 'imagecodecs' package"
  219. raise NotImplementedError(msg)
  220. if isinstance(data, (bytes, bytearray)):
  221. data = numpy.frombuffer(data, dtype=numpy.uint8)
  222. diff = numpy.diff(data, axis=0)
  223. return numpy.insert(diff, 0, data[0]).tobytes()
  224. dtype = data.dtype
  225. if dtype.kind == 'f':
  226. data = data.view(f'{dtype.byteorder}u{dtype.itemsize}')
  227. diff = numpy.diff(data, axis=axis)
  228. key: list[int | slice] = [slice(None)] * data.ndim
  229. key[axis] = 0
  230. diff = numpy.insert(diff, 0, data[tuple(key)], axis=axis)
  231. if not data.dtype.isnative:
  232. diff = diff.byteswap(inplace=True)
  233. diff = diff.view(diff.dtype.newbyteorder())
  234. if dtype.kind == 'f':
  235. return diff.view(dtype)
  236. return diff
  237. @overload
  238. def delta_decode(
  239. data: bytes | bytearray, /, axis: int, dist: int, *, out: Any
  240. ) -> bytes: ...
  241. @overload
  242. def delta_decode(
  243. data: NDArray[Any], /, axis: int, dist: int, *, out: Any
  244. ) -> NDArray[Any]: ...
  245. def delta_decode(
  246. data: bytes | bytearray | NDArray[Any],
  247. /,
  248. axis: int = -1,
  249. dist: int = 1,
  250. *,
  251. out: Any = None,
  252. ) -> bytes | NDArray[Any]:
  253. """Decode Delta."""
  254. if dist != 1:
  255. msg = f"delta_decode with {dist=} requires the 'imagecodecs' package"
  256. raise NotImplementedError(msg)
  257. if out is not None and not out.flags.writeable:
  258. out = None
  259. if isinstance(data, (bytes, bytearray)):
  260. data = numpy.frombuffer(data, dtype=numpy.uint8)
  261. return numpy.cumsum( # type: ignore[no-any-return]
  262. data, axis=0, dtype=numpy.uint8, out=out
  263. ).tobytes()
  264. if data.dtype.kind == 'f':
  265. if not data.dtype.isnative:
  266. msg = (
  267. f'delta_decode with {data.dtype!r} '
  268. "requires the 'imagecodecs' package"
  269. )
  270. raise NotImplementedError(msg)
  271. view = data.view(f'{data.dtype.byteorder}u{data.dtype.itemsize}')
  272. view = numpy.cumsum(view, axis=axis, dtype=view.dtype)
  273. return view.view(data.dtype)
  274. return numpy.cumsum( # type: ignore[no-any-return]
  275. data, axis=axis, dtype=data.dtype, out=out
  276. )
  277. @overload
  278. def bitorder_decode(
  279. data: bytearray,
  280. /,
  281. *,
  282. out: Any = None,
  283. _bitorder: list[Any] = [], # noqa: B006
  284. ) -> bytearray: ...
  285. @overload
  286. def bitorder_decode(
  287. data: bytes,
  288. /,
  289. *,
  290. out: Any = None,
  291. _bitorder: list[Any] = [], # noqa: B006
  292. ) -> bytes: ...
  293. @overload
  294. def bitorder_decode(
  295. data: NDArray[Any],
  296. /,
  297. *,
  298. out: Any = None,
  299. _bitorder: list[Any] = [], # noqa: B006
  300. ) -> NDArray[Any]: ...
  301. def bitorder_decode(
  302. data: bytes | bytearray | NDArray[Any],
  303. /,
  304. *,
  305. out: Any = None,
  306. _bitorder: list[Any] = [], # noqa: B006
  307. ) -> bytes | bytearray | NDArray[Any]:
  308. r"""Reverse bits in each byte of bytes or numpy array.
  309. Decode data where pixels with lower column values are stored in the
  310. lower-order bits of the bytes (TIFF FillOrder is LSB2MSB).
  311. Parameters:
  312. data:
  313. Data to bit-reversed. If bytes type, a new bit-reversed
  314. bytes is returned. NumPy arrays are bit-reversed in-place.
  315. Examples:
  316. >>> bitorder_decode(b'\x01\x64')
  317. b'\x80&'
  318. >>> data = numpy.array([1, 666], dtype='uint16')
  319. >>> _ = bitorder_decode(data)
  320. >>> data
  321. array([ 128, 16473], dtype=uint16)
  322. """
  323. del out # unused
  324. if not _bitorder:
  325. _bitorder.append(
  326. b'\x00\x80@\xc0 \xa0`\xe0\x10\x90P\xd00\xb0p\xf0\x08\x88H'
  327. b'\xc8(\xa8h\xe8\x18\x98X\xd88\xb8x\xf8\x04\x84D\xc4$\xa4d'
  328. b'\xe4\x14\x94T\xd44\xb4t\xf4\x0c\x8cL\xcc,\xacl\xec\x1c\x9c'
  329. b'\\\xdc<\xbc|\xfc\x02\x82B\xc2"\xa2b\xe2\x12\x92R\xd22'
  330. b'\xb2r\xf2\n\x8aJ\xca*\xaaj\xea\x1a\x9aZ\xda:\xbaz\xfa'
  331. b'\x06\x86F\xc6&\xa6f\xe6\x16\x96V\xd66\xb6v\xf6\x0e\x8eN'
  332. b'\xce.\xaen\xee\x1e\x9e^\xde>\xbe~\xfe\x01\x81A\xc1!\xa1a'
  333. b'\xe1\x11\x91Q\xd11\xb1q\xf1\t\x89I\xc9)\xa9i\xe9\x19'
  334. b'\x99Y\xd99\xb9y\xf9\x05\x85E\xc5%\xa5e\xe5\x15\x95U\xd55'
  335. b'\xb5u\xf5\r\x8dM\xcd-\xadm\xed\x1d\x9d]\xdd=\xbd}\xfd'
  336. b'\x03\x83C\xc3#\xa3c\xe3\x13\x93S\xd33\xb3s\xf3\x0b\x8bK'
  337. b"\xcb+\xabk\xeb\x1b\x9b[\xdb;\xbb{\xfb\x07\x87G\xc7'\xa7g"
  338. b'\xe7\x17\x97W\xd77\xb7w\xf7\x0f\x8fO\xcf/\xafo\xef\x1f\x9f_'
  339. b'\xdf?\xbf\x7f\xff'
  340. )
  341. _bitorder.append(numpy.frombuffer(_bitorder[0], dtype=numpy.uint8))
  342. if isinstance(data, (bytes, bytearray)):
  343. return data.translate(_bitorder[0])
  344. try:
  345. view = data.view('uint8')
  346. numpy.take(_bitorder[1], view, out=view)
  347. except ValueError as exc:
  348. msg = "bitorder_decode of slices requires the 'imagecodecs' package"
  349. raise NotImplementedError(msg) from exc
  350. return data
  351. def packints_decode(
  352. data: bytes,
  353. /,
  354. dtype: DTypeLike | None,
  355. bitspersample: int,
  356. runlen: int = 0,
  357. *,
  358. out: Any = None,
  359. ) -> NDArray[Any]:
  360. """Decompress bytes to array of integers.
  361. This implementation only handles itemsizes 1, 8, 16, 32, and 64 bits.
  362. Install the Imagecodecs package for decoding other integer sizes.
  363. Parameters:
  364. data:
  365. Data to decompress.
  366. dtype:
  367. Numpy boolean or integer type.
  368. bitspersample:
  369. Number of bits per integer.
  370. runlen:
  371. Number of consecutive integers after which to start at next byte.
  372. Examples:
  373. >>> packints_decode(b'a', 'B', 1)
  374. array([0, 1, 1, 0, 0, 0, 0, 1], dtype=uint8)
  375. """
  376. del out # unused
  377. if bitspersample == 1: # bitarray
  378. data_array = numpy.frombuffer(data, '|B')
  379. data_array = numpy.unpackbits(data_array)
  380. if runlen % 8:
  381. data_array = data_array.reshape((-1, runlen + (8 - runlen % 8)))
  382. data_array = data_array[:, :runlen].reshape(-1)
  383. return data_array.astype(dtype)
  384. if bitspersample in (8, 16, 32, 64):
  385. return numpy.frombuffer(data, dtype)
  386. msg = (
  387. f'packints_decode of {bitspersample}-bit integers '
  388. "requires the 'imagecodecs' package"
  389. )
  390. raise NotImplementedError(msg)
  391. def packints_encode(
  392. data: ArrayLike,
  393. bitspersample: int,
  394. /,
  395. *,
  396. axis: int = -1,
  397. out: Any = None,
  398. ) -> bytes | bytearray:
  399. """Tightly pack integers."""
  400. msg = "packints_encode requires the 'imagecodecs' package"
  401. raise NotImplementedError(msg)
  402. def float24_decode(
  403. data: bytes, /, byteorder: Literal['>', '<']
  404. ) -> NDArray[Any]:
  405. """Return float32 array from float24."""
  406. msg = "float24_decode requires the 'imagecodecs' package"
  407. raise NotImplementedError(msg)