1# -*- coding: utf-8 -*-
2# imageio is distributed under the terms of the (new) BSD License.
3
4""" Read/Write TIFF files.
5
6Backend: internal
7
8Provides support for a wide range of Tiff images using the tifffile
9backend.
10
11Parameters for reading
12----------------------
13offset : int
14 Optional start position of embedded file. By default this is
15 the current file position.
16size : int
17 Optional size of embedded file. By default this is the number
18 of bytes from the 'offset' to the end of the file.
19multifile : bool
20 If True (default), series may include pages from multiple files.
21 Currently applies to OME-TIFF only.
22multifile_close : bool
23 If True (default), keep the handles of other files in multifile
24 series closed. This is inefficient when few files refer to
25 many pages. If False, the C runtime may run out of resources.
26
27Parameters for saving
28---------------------
29bigtiff : bool
30 If True, the BigTIFF format is used.
31byteorder : {'<', '>'}
32 The endianness of the data in the file.
33 By default this is the system's native byte order.
34software : str
35 Name of the software used to create the image.
36 Saved with the first page only.
37
38Metadata for reading
39--------------------
40planar_configuration : {'contig', 'planar'}
41 Specifies if samples are stored contiguous or in separate planes.
42 By default this setting is inferred from the data shape.
43 'contig': last dimension contains samples.
44 'planar': third last dimension contains samples.
45resolution_unit : int
46 The resolution unit stored in the TIFF tag. Usually 1 means no/unknown unit,
47 2 means dpi (inch), 3 means dpc (centimeter).
48resolution : (float, float, str)
49 A tuple formatted as (X_resolution, Y_resolution, unit). The unit is a
50 string representing one of the following units::
51
52 NONE # No unit or unit unknown
53 INCH # dpi
54 CENTIMETER # cpi
55 MILLIMETER
56 MICROMETER
57
58compression : int
59 Value indicating the compression algorithm used, e.g. 5 is LZW,
60 7 is JPEG, 8 is deflate.
61 If 1, data are uncompressed.
62predictor : int
63 Value 2 indicates horizontal differencing was used before compression,
64 while 3 indicates floating point horizontal differencing.
65 If 1, no prediction scheme was used before compression.
66orientation : {'top_left', 'bottom_right', ...}
67 Oriented of image array.
68is_rgb : bool
69 True if page contains a RGB image.
70is_contig : bool
71 True if page contains a contiguous image.
72is_tiled : bool
73 True if page contains tiled image.
74is_palette : bool
75 True if page contains a palette-colored image and not OME or STK.
76is_reduced : bool
77 True if page is a reduced image of another image.
78is_shaped : bool
79 True if page contains shape in image_description tag.
80is_fluoview : bool
81 True if page contains FluoView MM_STAMP tag.
82is_nih : bool
83 True if page contains NIH image header.
84is_micromanager : bool
85 True if page contains Micro-Manager metadata.
86is_ome : bool
87 True if page contains OME-XML in image_description tag.
88is_sgi : bool
89 True if page contains SGI image and tile depth tags.
90is_mdgel : bool
91 True if page contains md_file_tag tag.
92is_mediacy : bool
93 True if page contains Media Cybernetics Id tag.
94is_stk : bool
95 True if page contains UIC2Tag tag.
96is_lsm : bool
97 True if page contains LSM CZ_LSM_INFO tag.
98description : str
99 Image description
100description1 : str
101 Additional description
102is_imagej : None or str
103 ImageJ metadata
104software : str
105 Software used to create the TIFF file
106datetime : datetime.datetime
107 Creation date and time
108
109Metadata for writing
110--------------------
111photometric : {'minisblack', 'miniswhite', 'rgb'}
112 The color space of the image data.
113 By default this setting is inferred from the data shape.
114planarconfig : {'contig', 'planar'}
115 Specifies if samples are stored contiguous or in separate planes.
116 By default this setting is inferred from the data shape.
117 'contig': last dimension contains samples.
118 'planar': third last dimension contains samples.
119resolution : (float, float) or ((int, int), (int, int))
120 X and Y resolution in dots per inch as float or rational numbers.
121description : str
122 The subject of the image. Saved with the first page only.
123compress : int
124 Values from 0 to 9 controlling the level of zlib (deflate) compression.
125 If 0, data are written uncompressed (default).
126compression : str, (int, int)
127 Compression scheme used while writing the image. If omitted (default) the
128 image is not uncompressed. Compression cannot be used to write contiguous
129 series. Compressors may require certain data shapes, types or value ranges.
130 For example, JPEG compression requires grayscale or RGB(A), uint8 or 12-bit
131 uint16. JPEG compression is experimental. JPEG markers and TIFF tags may not
132 match. Only a limited set of compression schemes are implemented. 'ZLIB' is
133 short for ADOBE_DEFLATE. The value is written to the Compression tag.
134compressionargs:
135 Extra arguments passed to compression codec, e.g., compression level. Refer
136 to the Imagecodecs implementation for supported arguments.
137predictor : bool
138 If True, horizontal differencing is applied before compression.
139 Note that using an int literal 1 actually means no prediction scheme
140 will be used.
141volume : bool
142 If True, volume data are stored in one tile (if applicable) using
143 the SGI image_depth and tile_depth tags.
144 Image width and depth must be multiple of 16.
145 Few software can read this format, e.g. MeVisLab.
146writeshape : bool
147 If True, write the data shape to the image_description tag
148 if necessary and no other description is given.
149extratags: sequence of tuples
150 Additional tags as [(code, dtype, count, value, writeonce)].
151
152 code : int
153 The TIFF tag Id.
154 dtype : str
155 Data type of items in 'value' in Python struct format.
156 One of B, s, H, I, 2I, b, h, i, f, d, Q, or q.
157 count : int
158 Number of data values. Not used for string values.
159 value : sequence
160 'Count' values compatible with 'dtype'.
161 writeonce : bool
162 If True, the tag is written to the first page only.
163
164Notes
165-----
166Global metadata is stored with the first frame in a TIFF file.
167Thus calling :py:meth:`Format.Writer.set_meta_data` after the first frame
168was written has no effect. Also, global metadata is ignored if metadata is
169provided via the `meta` argument of :py:meth:`Format.Writer.append_data`.
170
171If you have installed tifffile as a Python package, imageio will attempt
172to use that as backend instead of the bundled backend. Doing so can
173provide access to new performance improvements and bug fixes.
174
175"""
176
177import datetime
178
179from ..core import Format
180from ..core.request import URI_BYTES, URI_FILE
181
182import numpy as np
183import warnings
184
185
186try:
187 import tifffile as _tifffile
188except ImportError:
189 warnings.warn(
190 "ImageIO's vendored tifffile backend is deprecated and will be"
191 " removed in ImageIO v3. Install the tifffile directly:"
192 " `pip install imageio[tifffile]`",
193 DeprecationWarning,
194 )
195 from . import _tifffile
196
197
198TIFF_FORMATS = (".tif", ".tiff", ".stk", ".lsm")
199WRITE_METADATA_KEYS = (
200 "photometric",
201 "planarconfig",
202 "resolution",
203 "description",
204 "compress",
205 "compression",
206 "compressionargs",
207 "predictor",
208 "volume",
209 "writeshape",
210 "extratags",
211 "datetime",
212)
213READ_METADATA_KEYS = (
214 "planar_configuration",
215 "is_fluoview",
216 "is_nih",
217 "is_contig",
218 "is_micromanager",
219 "is_ome",
220 "is_lsm",
221 "is_palette",
222 "is_reduced",
223 "is_rgb",
224 "is_sgi",
225 "is_shaped",
226 "is_stk",
227 "is_tiled",
228 "is_mdgel",
229 "resolution_unit",
230 "compression",
231 "predictor",
232 "is_mediacy",
233 "orientation",
234 "description",
235 "description1",
236 "is_imagej",
237 "software",
238)
239
240
241class TiffFormat(Format):
242 """Provides support for a wide range of Tiff images using the tifffile
243 backend.
244
245 Images that contain multiple pages can be read using ``imageio.mimread()``
246 to read the individual pages, or ``imageio.volread()`` to obtain a
247 single (higher dimensional) array.
248
249 Note that global metadata is stored with the first frame in a TIFF file.
250 Thus calling :py:meth:`Format.Writer.set_meta_data` after the first frame
251 was written has no effect. Also, global metadata is ignored if metadata is
252 provided via the `meta` argument of :py:meth:`Format.Writer.append_data`.
253
254 If you have installed tifffile as a Python package, imageio will attempt
255 to use that as backend instead of the bundled backend. Doing so can
256 provide access to new performance improvements and bug fixes.
257
258 Parameters for reading
259 ----------------------
260 offset : int
261 Optional start position of embedded file. By default this is
262 the current file position.
263 size : int
264 Optional size of embedded file. By default this is the number
265 of bytes from the 'offset' to the end of the file.
266 multifile : bool
267 If True (default), series may include pages from multiple files.
268 Currently applies to OME-TIFF only.
269 multifile_close : bool
270 If True (default), keep the handles of other files in multifile
271 series closed. This is inefficient when few files refer to
272 many pages. If False, the C runtime may run out of resources.
273
274 Parameters for saving
275 ---------------------
276 bigtiff : bool
277 If True, the BigTIFF format is used.
278 byteorder : {'<', '>'}
279 The endianness of the data in the file.
280 By default this is the system's native byte order.
281 software : str
282 Name of the software used to create the image.
283 Saved with the first page only.
284
285 Metadata for reading
286 --------------------
287 planar_configuration : {'contig', 'planar'}
288 Specifies if samples are stored contiguous or in separate planes.
289 By default this setting is inferred from the data shape.
290 'contig': last dimension contains samples.
291 'planar': third last dimension contains samples.
292 resolution_unit : (float, float) or ((int, int), (int, int))
293 X and Y resolution in dots per inch as float or rational numbers.
294 compression : int
295 Value indicating the compression algorithm used, e.g. 5 is LZW,
296 7 is JPEG, 8 is deflate.
297 If 1, data are uncompressed.
298 predictor : int
299 Value 2 indicates horizontal differencing was used before compression,
300 while 3 indicates floating point horizontal differencing.
301 If 1, no prediction scheme was used before compression.
302 orientation : {'top_left', 'bottom_right', ...}
303 Oriented of image array.
304 is_rgb : bool
305 True if page contains a RGB image.
306 is_contig : bool
307 True if page contains a contiguous image.
308 is_tiled : bool
309 True if page contains tiled image.
310 is_palette : bool
311 True if page contains a palette-colored image and not OME or STK.
312 is_reduced : bool
313 True if page is a reduced image of another image.
314 is_shaped : bool
315 True if page contains shape in image_description tag.
316 is_fluoview : bool
317 True if page contains FluoView MM_STAMP tag.
318 is_nih : bool
319 True if page contains NIH image header.
320 is_micromanager : bool
321 True if page contains Micro-Manager metadata.
322 is_ome : bool
323 True if page contains OME-XML in image_description tag.
324 is_sgi : bool
325 True if page contains SGI image and tile depth tags.
326 is_stk : bool
327 True if page contains UIC2Tag tag.
328 is_mdgel : bool
329 True if page contains md_file_tag tag.
330 is_mediacy : bool
331 True if page contains Media Cybernetics Id tag.
332 is_stk : bool
333 True if page contains UIC2Tag tag.
334 is_lsm : bool
335 True if page contains LSM CZ_LSM_INFO tag.
336 description : str
337 Image description
338 description1 : str
339 Additional description
340 is_imagej : None or str
341 ImageJ metadata
342 software : str
343 Software used to create the TIFF file
344 datetime : datetime.datetime
345 Creation date and time
346
347 Metadata for writing
348 --------------------
349 photometric : {'minisblack', 'miniswhite', 'rgb'}
350 The color space of the image data.
351 By default this setting is inferred from the data shape.
352 planarconfig : {'contig', 'planar'}
353 Specifies if samples are stored contiguous or in separate planes.
354 By default this setting is inferred from the data shape.
355 'contig': last dimension contains samples.
356 'planar': third last dimension contains samples.
357 resolution : (float, float) or ((int, int), (int, int))
358 X and Y resolution in dots per inch as float or rational numbers.
359 description : str
360 The subject of the image. Saved with the first page only.
361 compress : int
362 Values from 0 to 9 controlling the level of zlib (deflate) compression.
363 If 0, data are written uncompressed (default).
364 predictor : bool
365 If True, horizontal differencing is applied before compression.
366 Note that using an int literal 1 actually means no prediction scheme
367 will be used.
368 volume : bool
369 If True, volume data are stored in one tile (if applicable) using
370 the SGI image_depth and tile_depth tags.
371 Image width and depth must be multiple of 16.
372 Few software can read this format, e.g. MeVisLab.
373 writeshape : bool
374 If True, write the data shape to the image_description tag
375 if necessary and no other description is given.
376 extratags: sequence of tuples
377 Additional tags as [(code, dtype, count, value, writeonce)].
378
379 code : int
380 The TIFF tag Id.
381 dtype : str
382 Data type of items in 'value' in Python struct format.
383 One of B, s, H, I, 2I, b, h, i, f, d, Q, or q.
384 count : int
385 Number of data values. Not used for string values.
386 value : sequence
387 'Count' values compatible with 'dtype'.
388 writeonce : bool
389 If True, the tag is written to the first page only.
390 """
391
392 def _can_read(self, request):
393 try:
394 _tifffile.TiffFile(request.get_file(), **request.kwargs)
395 except ValueError:
396 # vendored backend raises value exception
397 return False
398 except _tifffile.TiffFileError: # pragma: no-cover
399 # current version raises custom exception
400 return False
401 finally:
402 request.get_file().seek(0)
403
404 return True
405
406 def _can_write(self, request):
407 if request._uri_type in [URI_FILE, URI_BYTES]:
408 pass # special URI
409 elif request.extension not in self.extensions:
410 return False
411
412 try:
413 _tifffile.TiffWriter(request.get_file(), **request.kwargs)
414 except ValueError:
415 # vendored backend raises value exception
416 return False
417 except _tifffile.TiffFileError: # pragma: no-cover
418 # current version raises custom exception
419 return False
420 finally:
421 request.get_file().seek(0)
422 return True
423
424 # -- reader
425
426 class Reader(Format.Reader):
427 def _open(self, **kwargs):
428 # Allow loading from http; tifffile uses seek, so download first
429 if self.request.filename.startswith(("http://", "https://")):
430 self._f = f = open(self.request.get_local_filename(), "rb")
431 else:
432 self._f = None
433 f = self.request.get_file()
434 self._tf = _tifffile.TiffFile(f, **kwargs)
435
436 def _close(self):
437 self._tf.close()
438 if self._f is not None:
439 self._f.close()
440
441 def _get_length(self):
442 return len(self._tf.series)
443
444 def _get_data(self, index):
445 if index < 0 or index >= self._get_length():
446 raise IndexError("Index out of range while reading from tiff file")
447
448 im = self._tf.asarray(series=index)
449 meta = self._get_meta_data(index)
450
451 return im, meta
452
453 def _get_meta_data(self, index):
454 meta = {}
455 page = self._tf.pages[index or 0]
456 for key in READ_METADATA_KEYS:
457 try:
458 meta[key] = getattr(page, key)
459 except Exception:
460 pass
461
462 # tifffile <= 0.12.1 use datetime, newer use DateTime
463 for key in ("datetime", "DateTime"):
464 try:
465 meta["datetime"] = datetime.datetime.strptime(
466 page.tags[key].value, "%Y:%m:%d %H:%M:%S"
467 )
468 break
469 except Exception:
470 pass
471
472 if 296 in page.tags:
473 meta["resolution_unit"] = page.tags[296].value.value
474
475 if 282 in page.tags and 283 in page.tags and 296 in page.tags:
476 resolution_x = page.tags[282].value
477 resolution_y = page.tags[283].value
478 if resolution_x[1] == 0 or resolution_y[1] == 0:
479 warnings.warn(
480 "Ignoring resolution metadata, "
481 "because at least one direction has a 0 denominator.",
482 RuntimeWarning,
483 )
484 else:
485 meta["resolution"] = (
486 resolution_x[0] / resolution_x[1],
487 resolution_y[0] / resolution_y[1],
488 page.tags[296].value.name,
489 )
490
491 return meta
492
493 # -- writer
494 class Writer(Format.Writer):
495 def _open(self, bigtiff=None, byteorder=None, software=None):
496 try:
497 self._tf = _tifffile.TiffWriter(
498 self.request.get_file(),
499 bigtiff=bigtiff,
500 byteorder=byteorder,
501 software=software,
502 )
503 self._software = None
504 except TypeError:
505 # In tifffile >= 0.15, the `software` arg is passed to
506 # TiffWriter.save
507 self._tf = _tifffile.TiffWriter(
508 self.request.get_file(), bigtiff=bigtiff, byteorder=byteorder
509 )
510 self._software = software
511
512 self._meta = {}
513 self._frames_written = 0
514
515 def _close(self):
516 self._tf.close()
517
518 def _append_data(self, im, meta):
519 if meta is not None:
520 meta = self._sanitize_meta(meta)
521 else:
522 # Use global metadata for first frame
523 meta = self._meta if self._frames_written == 0 else {}
524 if self._software is not None and self._frames_written == 0:
525 meta["software"] = self._software
526 # No need to check self.request.mode; tifffile figures out whether
527 # this is a single page, or all page data at once.
528 try:
529 # TiffWriter.save has been deprecated in version 2020.9.30
530 write_meth = self._tf.write
531 except AttributeError:
532 write_meth = self._tf.save
533 write_meth(np.asanyarray(im), contiguous=False, **meta)
534 self._frames_written += 1
535
536 @staticmethod
537 def _sanitize_meta(meta):
538 ret = {}
539 for key, value in meta.items():
540 if key in WRITE_METADATA_KEYS:
541 # Special case of previously read `predictor` int value
542 # 1(=NONE) translation to False expected by TiffWriter.save
543 if key == "predictor" and not isinstance(value, bool):
544 ret[key] = value > 1
545 elif key == "compress" and value != 0:
546 warnings.warn(
547 "The use of `compress` is deprecated. Use `compression` and `compressionargs` instead.",
548 DeprecationWarning,
549 )
550
551 if _tifffile.__version__ < "2022":
552 ret["compression"] = (8, value)
553 else:
554 ret["compression"] = "zlib"
555 ret["compressionargs"] = {"level": value}
556 else:
557 ret[key] = value
558 return ret
559
560 def set_meta_data(self, meta):
561 self._meta = self._sanitize_meta(meta)