_imagecodecs.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484
  1. # tifffile/_imagecodecs.py
  2. # Copyright (c) 2008-2024, 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. raise NotImplementedError(
  219. f"delta_encode with {dist=} requires the 'imagecodecs' package"
  220. )
  221. if isinstance(data, (bytes, bytearray)):
  222. data = numpy.frombuffer(data, dtype=numpy.uint8)
  223. diff = numpy.diff(data, axis=0)
  224. return numpy.insert(diff, 0, data[0]).tobytes()
  225. dtype = data.dtype
  226. if dtype.kind == 'f':
  227. data = data.view(f'{dtype.byteorder}u{dtype.itemsize}')
  228. diff = numpy.diff(data, axis=axis)
  229. key: list[int | slice] = [slice(None)] * data.ndim
  230. key[axis] = 0
  231. diff = numpy.insert(diff, 0, data[tuple(key)], axis=axis)
  232. if not data.dtype.isnative:
  233. diff = diff.byteswap(inplace=True)
  234. diff = diff.view(diff.dtype.newbyteorder())
  235. if dtype.kind == 'f':
  236. return diff.view(dtype)
  237. return diff
  238. @overload
  239. def delta_decode(
  240. data: bytes | bytearray, /, axis: int, dist: int, *, out: Any
  241. ) -> bytes: ...
  242. @overload
  243. def delta_decode(
  244. data: NDArray[Any], /, axis: int, dist: int, *, out: Any
  245. ) -> NDArray[Any]: ...
  246. def delta_decode(
  247. data: bytes | bytearray | NDArray[Any],
  248. /,
  249. axis: int = -1,
  250. dist: int = 1,
  251. *,
  252. out: Any = None,
  253. ) -> bytes | NDArray[Any]:
  254. """Decode Delta."""
  255. if dist != 1:
  256. raise NotImplementedError(
  257. f"delta_decode with {dist=} requires the 'imagecodecs' package"
  258. )
  259. if out is not None and not out.flags.writeable:
  260. out = None
  261. if isinstance(data, (bytes, bytearray)):
  262. data = numpy.frombuffer(data, dtype=numpy.uint8)
  263. return numpy.cumsum( # type: ignore[no-any-return]
  264. data, axis=0, dtype=numpy.uint8, out=out
  265. ).tobytes()
  266. if data.dtype.kind == 'f':
  267. if not data.dtype.isnative:
  268. raise NotImplementedError(
  269. f'delta_decode with {data.dtype!r} '
  270. "requires the 'imagecodecs' package"
  271. )
  272. view = data.view(f'{data.dtype.byteorder}u{data.dtype.itemsize}')
  273. view = numpy.cumsum(view, axis=axis, dtype=view.dtype)
  274. return view.view(data.dtype)
  275. return numpy.cumsum( # type: ignore[no-any-return]
  276. data, axis=axis, dtype=data.dtype, out=out
  277. )
  278. @overload
  279. def bitorder_decode(
  280. data: bytearray,
  281. /,
  282. *,
  283. out: Any = None,
  284. _bitorder: list[Any] = [], # noqa: B006
  285. ) -> bytearray: ...
  286. @overload
  287. def bitorder_decode(
  288. data: bytes,
  289. /,
  290. *,
  291. out: Any = None,
  292. _bitorder: list[Any] = [], # noqa: B006
  293. ) -> bytes: ...
  294. @overload
  295. def bitorder_decode(
  296. data: NDArray[Any],
  297. /,
  298. *,
  299. out: Any = None,
  300. _bitorder: list[Any] = [], # noqa: B006
  301. ) -> NDArray[Any]: ...
  302. def bitorder_decode(
  303. data: bytes | bytearray | NDArray[Any],
  304. /,
  305. *,
  306. out: Any = None,
  307. _bitorder: list[Any] = [], # noqa: B006
  308. ) -> bytes | bytearray | NDArray[Any]:
  309. r"""Reverse bits in each byte of bytes or numpy array.
  310. Decode data where pixels with lower column values are stored in the
  311. lower-order bits of the bytes (TIFF FillOrder is LSB2MSB).
  312. Parameters:
  313. data:
  314. Data to bit-reversed. If bytes type, a new bit-reversed
  315. bytes is returned. NumPy arrays are bit-reversed in-place.
  316. Examples:
  317. >>> bitorder_decode(b'\x01\x64')
  318. b'\x80&'
  319. >>> data = numpy.array([1, 666], dtype='uint16')
  320. >>> _ = bitorder_decode(data)
  321. >>> data
  322. array([ 128, 16473], dtype=uint16)
  323. """
  324. del out # unused
  325. if not _bitorder:
  326. _bitorder.append(
  327. b'\x00\x80@\xc0 \xa0`\xe0\x10\x90P\xd00\xb0p\xf0\x08\x88H'
  328. b'\xc8(\xa8h\xe8\x18\x98X\xd88\xb8x\xf8\x04\x84D\xc4$\xa4d'
  329. b'\xe4\x14\x94T\xd44\xb4t\xf4\x0c\x8cL\xcc,\xacl\xec\x1c\x9c'
  330. b'\\\xdc<\xbc|\xfc\x02\x82B\xc2"\xa2b\xe2\x12\x92R\xd22'
  331. b'\xb2r\xf2\n\x8aJ\xca*\xaaj\xea\x1a\x9aZ\xda:\xbaz\xfa'
  332. b'\x06\x86F\xc6&\xa6f\xe6\x16\x96V\xd66\xb6v\xf6\x0e\x8eN'
  333. b'\xce.\xaen\xee\x1e\x9e^\xde>\xbe~\xfe\x01\x81A\xc1!\xa1a'
  334. b'\xe1\x11\x91Q\xd11\xb1q\xf1\t\x89I\xc9)\xa9i\xe9\x19'
  335. b'\x99Y\xd99\xb9y\xf9\x05\x85E\xc5%\xa5e\xe5\x15\x95U\xd55'
  336. b'\xb5u\xf5\r\x8dM\xcd-\xadm\xed\x1d\x9d]\xdd=\xbd}\xfd'
  337. b'\x03\x83C\xc3#\xa3c\xe3\x13\x93S\xd33\xb3s\xf3\x0b\x8bK'
  338. b"\xcb+\xabk\xeb\x1b\x9b[\xdb;\xbb{\xfb\x07\x87G\xc7'\xa7g"
  339. b'\xe7\x17\x97W\xd77\xb7w\xf7\x0f\x8fO\xcf/\xafo\xef\x1f\x9f_'
  340. b'\xdf?\xbf\x7f\xff'
  341. )
  342. _bitorder.append(numpy.frombuffer(_bitorder[0], dtype=numpy.uint8))
  343. if isinstance(data, (bytes, bytearray)):
  344. return data.translate(_bitorder[0])
  345. try:
  346. view = data.view('uint8')
  347. numpy.take(_bitorder[1], view, out=view)
  348. except ValueError as exc:
  349. raise NotImplementedError(
  350. "bitorder_decode of slices requires the 'imagecodecs' package"
  351. ) from exc
  352. return data
  353. def packints_decode(
  354. data: bytes,
  355. /,
  356. dtype: DTypeLike | None,
  357. bitspersample: int,
  358. runlen: int = 0,
  359. *,
  360. out: Any = None,
  361. ) -> NDArray[Any]:
  362. """Decompress bytes to array of integers.
  363. This implementation only handles itemsizes 1, 8, 16, 32, and 64 bits.
  364. Install the Imagecodecs package for decoding other integer sizes.
  365. Parameters:
  366. data:
  367. Data to decompress.
  368. dtype:
  369. Numpy boolean or integer type.
  370. bitspersample:
  371. Number of bits per integer.
  372. runlen:
  373. Number of consecutive integers after which to start at next byte.
  374. Examples:
  375. >>> packints_decode(b'a', 'B', 1)
  376. array([0, 1, 1, 0, 0, 0, 0, 1], dtype=uint8)
  377. """
  378. del out # unused
  379. if bitspersample == 1: # bitarray
  380. data_array = numpy.frombuffer(data, '|B')
  381. data_array = numpy.unpackbits(data_array)
  382. if runlen % 8:
  383. data_array = data_array.reshape((-1, runlen + (8 - runlen % 8)))
  384. data_array = data_array[:, :runlen].reshape(-1)
  385. return data_array.astype(dtype)
  386. if bitspersample in (8, 16, 32, 64):
  387. return numpy.frombuffer(data, dtype)
  388. raise NotImplementedError(
  389. f'packints_decode of {bitspersample}-bit integers '
  390. "requires the 'imagecodecs' package"
  391. )
  392. def packints_encode(
  393. data: ArrayLike,
  394. bitspersample: int,
  395. /,
  396. *,
  397. axis: int = -1,
  398. out: Any = None,
  399. ) -> bytes | bytearray:
  400. """Tightly pack integers."""
  401. raise NotImplementedError(
  402. "packints_encode requires the 'imagecodecs' package"
  403. )
  404. def float24_decode(
  405. data: bytes, /, byteorder: Literal['>', '<']
  406. ) -> NDArray[Any]:
  407. """Return float32 array from float24."""
  408. raise NotImplementedError(
  409. "float24_decode requires the 'imagecodecs' package"
  410. )