562 lines
20 KiB
Python
562 lines
20 KiB
Python
|
# -*- coding: utf-8 -*-
|
||
|
# imageio is distributed under the terms of the (new) BSD License.
|
||
|
|
||
|
""" Read/Write TIFF files.
|
||
|
|
||
|
Backend: internal
|
||
|
|
||
|
Provides support for a wide range of Tiff images using the tifffile
|
||
|
backend.
|
||
|
|
||
|
Parameters for reading
|
||
|
----------------------
|
||
|
offset : int
|
||
|
Optional start position of embedded file. By default this is
|
||
|
the current file position.
|
||
|
size : int
|
||
|
Optional size of embedded file. By default this is the number
|
||
|
of bytes from the 'offset' to the end of the file.
|
||
|
multifile : bool
|
||
|
If True (default), series may include pages from multiple files.
|
||
|
Currently applies to OME-TIFF only.
|
||
|
multifile_close : bool
|
||
|
If True (default), keep the handles of other files in multifile
|
||
|
series closed. This is inefficient when few files refer to
|
||
|
many pages. If False, the C runtime may run out of resources.
|
||
|
|
||
|
Parameters for saving
|
||
|
---------------------
|
||
|
bigtiff : bool
|
||
|
If True, the BigTIFF format is used.
|
||
|
byteorder : {'<', '>'}
|
||
|
The endianness of the data in the file.
|
||
|
By default this is the system's native byte order.
|
||
|
software : str
|
||
|
Name of the software used to create the image.
|
||
|
Saved with the first page only.
|
||
|
|
||
|
Metadata for reading
|
||
|
--------------------
|
||
|
planar_configuration : {'contig', 'planar'}
|
||
|
Specifies if samples are stored contiguous or in separate planes.
|
||
|
By default this setting is inferred from the data shape.
|
||
|
'contig': last dimension contains samples.
|
||
|
'planar': third last dimension contains samples.
|
||
|
resolution_unit : int
|
||
|
The resolution unit stored in the TIFF tag. Usually 1 means no/unknown unit,
|
||
|
2 means dpi (inch), 3 means dpc (centimeter).
|
||
|
resolution : (float, float, str)
|
||
|
A tuple formatted as (X_resolution, Y_resolution, unit). The unit is a
|
||
|
string representing one of the following units::
|
||
|
|
||
|
NONE # No unit or unit unknown
|
||
|
INCH # dpi
|
||
|
CENTIMETER # cpi
|
||
|
MILLIMETER
|
||
|
MICROMETER
|
||
|
|
||
|
compression : int
|
||
|
Value indicating the compression algorithm used, e.g. 5 is LZW,
|
||
|
7 is JPEG, 8 is deflate.
|
||
|
If 1, data are uncompressed.
|
||
|
predictor : int
|
||
|
Value 2 indicates horizontal differencing was used before compression,
|
||
|
while 3 indicates floating point horizontal differencing.
|
||
|
If 1, no prediction scheme was used before compression.
|
||
|
orientation : {'top_left', 'bottom_right', ...}
|
||
|
Oriented of image array.
|
||
|
is_rgb : bool
|
||
|
True if page contains a RGB image.
|
||
|
is_contig : bool
|
||
|
True if page contains a contiguous image.
|
||
|
is_tiled : bool
|
||
|
True if page contains tiled image.
|
||
|
is_palette : bool
|
||
|
True if page contains a palette-colored image and not OME or STK.
|
||
|
is_reduced : bool
|
||
|
True if page is a reduced image of another image.
|
||
|
is_shaped : bool
|
||
|
True if page contains shape in image_description tag.
|
||
|
is_fluoview : bool
|
||
|
True if page contains FluoView MM_STAMP tag.
|
||
|
is_nih : bool
|
||
|
True if page contains NIH image header.
|
||
|
is_micromanager : bool
|
||
|
True if page contains Micro-Manager metadata.
|
||
|
is_ome : bool
|
||
|
True if page contains OME-XML in image_description tag.
|
||
|
is_sgi : bool
|
||
|
True if page contains SGI image and tile depth tags.
|
||
|
is_mdgel : bool
|
||
|
True if page contains md_file_tag tag.
|
||
|
is_mediacy : bool
|
||
|
True if page contains Media Cybernetics Id tag.
|
||
|
is_stk : bool
|
||
|
True if page contains UIC2Tag tag.
|
||
|
is_lsm : bool
|
||
|
True if page contains LSM CZ_LSM_INFO tag.
|
||
|
description : str
|
||
|
Image description
|
||
|
description1 : str
|
||
|
Additional description
|
||
|
is_imagej : None or str
|
||
|
ImageJ metadata
|
||
|
software : str
|
||
|
Software used to create the TIFF file
|
||
|
datetime : datetime.datetime
|
||
|
Creation date and time
|
||
|
|
||
|
Metadata for writing
|
||
|
--------------------
|
||
|
photometric : {'minisblack', 'miniswhite', 'rgb'}
|
||
|
The color space of the image data.
|
||
|
By default this setting is inferred from the data shape.
|
||
|
planarconfig : {'contig', 'planar'}
|
||
|
Specifies if samples are stored contiguous or in separate planes.
|
||
|
By default this setting is inferred from the data shape.
|
||
|
'contig': last dimension contains samples.
|
||
|
'planar': third last dimension contains samples.
|
||
|
resolution : (float, float) or ((int, int), (int, int))
|
||
|
X and Y resolution in dots per inch as float or rational numbers.
|
||
|
description : str
|
||
|
The subject of the image. Saved with the first page only.
|
||
|
compress : int
|
||
|
Values from 0 to 9 controlling the level of zlib (deflate) compression.
|
||
|
If 0, data are written uncompressed (default).
|
||
|
compression : str, (int, int)
|
||
|
Compression scheme used while writing the image. If omitted (default) the
|
||
|
image is not uncompressed. Compression cannot be used to write contiguous
|
||
|
series. Compressors may require certain data shapes, types or value ranges.
|
||
|
For example, JPEG compression requires grayscale or RGB(A), uint8 or 12-bit
|
||
|
uint16. JPEG compression is experimental. JPEG markers and TIFF tags may not
|
||
|
match. Only a limited set of compression schemes are implemented. 'ZLIB' is
|
||
|
short for ADOBE_DEFLATE. The value is written to the Compression tag.
|
||
|
compressionargs:
|
||
|
Extra arguments passed to compression codec, e.g., compression level. Refer
|
||
|
to the Imagecodecs implementation for supported arguments.
|
||
|
predictor : bool
|
||
|
If True, horizontal differencing is applied before compression.
|
||
|
Note that using an int literal 1 actually means no prediction scheme
|
||
|
will be used.
|
||
|
volume : bool
|
||
|
If True, volume data are stored in one tile (if applicable) using
|
||
|
the SGI image_depth and tile_depth tags.
|
||
|
Image width and depth must be multiple of 16.
|
||
|
Few software can read this format, e.g. MeVisLab.
|
||
|
writeshape : bool
|
||
|
If True, write the data shape to the image_description tag
|
||
|
if necessary and no other description is given.
|
||
|
extratags: sequence of tuples
|
||
|
Additional tags as [(code, dtype, count, value, writeonce)].
|
||
|
|
||
|
code : int
|
||
|
The TIFF tag Id.
|
||
|
dtype : str
|
||
|
Data type of items in 'value' in Python struct format.
|
||
|
One of B, s, H, I, 2I, b, h, i, f, d, Q, or q.
|
||
|
count : int
|
||
|
Number of data values. Not used for string values.
|
||
|
value : sequence
|
||
|
'Count' values compatible with 'dtype'.
|
||
|
writeonce : bool
|
||
|
If True, the tag is written to the first page only.
|
||
|
|
||
|
Notes
|
||
|
-----
|
||
|
Global metadata is stored with the first frame in a TIFF file.
|
||
|
Thus calling :py:meth:`Format.Writer.set_meta_data` after the first frame
|
||
|
was written has no effect. Also, global metadata is ignored if metadata is
|
||
|
provided via the `meta` argument of :py:meth:`Format.Writer.append_data`.
|
||
|
|
||
|
If you have installed tifffile as a Python package, imageio will attempt
|
||
|
to use that as backend instead of the bundled backend. Doing so can
|
||
|
provide access to new performance improvements and bug fixes.
|
||
|
|
||
|
"""
|
||
|
|
||
|
import datetime
|
||
|
|
||
|
from ..core import Format
|
||
|
from ..core.request import URI_BYTES, URI_FILE
|
||
|
|
||
|
import numpy as np
|
||
|
import warnings
|
||
|
|
||
|
|
||
|
try:
|
||
|
import tifffile as _tifffile
|
||
|
except ImportError:
|
||
|
warnings.warn(
|
||
|
"ImageIO's vendored tifffile backend is deprecated and will be"
|
||
|
" removed in ImageIO v3. Install the tifffile directly:"
|
||
|
" `pip install imageio[tifffile]`",
|
||
|
DeprecationWarning,
|
||
|
)
|
||
|
from . import _tifffile
|
||
|
|
||
|
|
||
|
TIFF_FORMATS = (".tif", ".tiff", ".stk", ".lsm")
|
||
|
WRITE_METADATA_KEYS = (
|
||
|
"photometric",
|
||
|
"planarconfig",
|
||
|
"resolution",
|
||
|
"description",
|
||
|
"compress",
|
||
|
"compression",
|
||
|
"compressionargs",
|
||
|
"predictor",
|
||
|
"volume",
|
||
|
"writeshape",
|
||
|
"extratags",
|
||
|
"datetime",
|
||
|
)
|
||
|
READ_METADATA_KEYS = (
|
||
|
"planar_configuration",
|
||
|
"is_fluoview",
|
||
|
"is_nih",
|
||
|
"is_contig",
|
||
|
"is_micromanager",
|
||
|
"is_ome",
|
||
|
"is_lsm",
|
||
|
"is_palette",
|
||
|
"is_reduced",
|
||
|
"is_rgb",
|
||
|
"is_sgi",
|
||
|
"is_shaped",
|
||
|
"is_stk",
|
||
|
"is_tiled",
|
||
|
"is_mdgel",
|
||
|
"resolution_unit",
|
||
|
"compression",
|
||
|
"predictor",
|
||
|
"is_mediacy",
|
||
|
"orientation",
|
||
|
"description",
|
||
|
"description1",
|
||
|
"is_imagej",
|
||
|
"software",
|
||
|
)
|
||
|
|
||
|
|
||
|
class TiffFormat(Format):
|
||
|
"""Provides support for a wide range of Tiff images using the tifffile
|
||
|
backend.
|
||
|
|
||
|
Images that contain multiple pages can be read using ``imageio.mimread()``
|
||
|
to read the individual pages, or ``imageio.volread()`` to obtain a
|
||
|
single (higher dimensional) array.
|
||
|
|
||
|
Note that global metadata is stored with the first frame in a TIFF file.
|
||
|
Thus calling :py:meth:`Format.Writer.set_meta_data` after the first frame
|
||
|
was written has no effect. Also, global metadata is ignored if metadata is
|
||
|
provided via the `meta` argument of :py:meth:`Format.Writer.append_data`.
|
||
|
|
||
|
If you have installed tifffile as a Python package, imageio will attempt
|
||
|
to use that as backend instead of the bundled backend. Doing so can
|
||
|
provide access to new performance improvements and bug fixes.
|
||
|
|
||
|
Parameters for reading
|
||
|
----------------------
|
||
|
offset : int
|
||
|
Optional start position of embedded file. By default this is
|
||
|
the current file position.
|
||
|
size : int
|
||
|
Optional size of embedded file. By default this is the number
|
||
|
of bytes from the 'offset' to the end of the file.
|
||
|
multifile : bool
|
||
|
If True (default), series may include pages from multiple files.
|
||
|
Currently applies to OME-TIFF only.
|
||
|
multifile_close : bool
|
||
|
If True (default), keep the handles of other files in multifile
|
||
|
series closed. This is inefficient when few files refer to
|
||
|
many pages. If False, the C runtime may run out of resources.
|
||
|
|
||
|
Parameters for saving
|
||
|
---------------------
|
||
|
bigtiff : bool
|
||
|
If True, the BigTIFF format is used.
|
||
|
byteorder : {'<', '>'}
|
||
|
The endianness of the data in the file.
|
||
|
By default this is the system's native byte order.
|
||
|
software : str
|
||
|
Name of the software used to create the image.
|
||
|
Saved with the first page only.
|
||
|
|
||
|
Metadata for reading
|
||
|
--------------------
|
||
|
planar_configuration : {'contig', 'planar'}
|
||
|
Specifies if samples are stored contiguous or in separate planes.
|
||
|
By default this setting is inferred from the data shape.
|
||
|
'contig': last dimension contains samples.
|
||
|
'planar': third last dimension contains samples.
|
||
|
resolution_unit : (float, float) or ((int, int), (int, int))
|
||
|
X and Y resolution in dots per inch as float or rational numbers.
|
||
|
compression : int
|
||
|
Value indicating the compression algorithm used, e.g. 5 is LZW,
|
||
|
7 is JPEG, 8 is deflate.
|
||
|
If 1, data are uncompressed.
|
||
|
predictor : int
|
||
|
Value 2 indicates horizontal differencing was used before compression,
|
||
|
while 3 indicates floating point horizontal differencing.
|
||
|
If 1, no prediction scheme was used before compression.
|
||
|
orientation : {'top_left', 'bottom_right', ...}
|
||
|
Oriented of image array.
|
||
|
is_rgb : bool
|
||
|
True if page contains a RGB image.
|
||
|
is_contig : bool
|
||
|
True if page contains a contiguous image.
|
||
|
is_tiled : bool
|
||
|
True if page contains tiled image.
|
||
|
is_palette : bool
|
||
|
True if page contains a palette-colored image and not OME or STK.
|
||
|
is_reduced : bool
|
||
|
True if page is a reduced image of another image.
|
||
|
is_shaped : bool
|
||
|
True if page contains shape in image_description tag.
|
||
|
is_fluoview : bool
|
||
|
True if page contains FluoView MM_STAMP tag.
|
||
|
is_nih : bool
|
||
|
True if page contains NIH image header.
|
||
|
is_micromanager : bool
|
||
|
True if page contains Micro-Manager metadata.
|
||
|
is_ome : bool
|
||
|
True if page contains OME-XML in image_description tag.
|
||
|
is_sgi : bool
|
||
|
True if page contains SGI image and tile depth tags.
|
||
|
is_stk : bool
|
||
|
True if page contains UIC2Tag tag.
|
||
|
is_mdgel : bool
|
||
|
True if page contains md_file_tag tag.
|
||
|
is_mediacy : bool
|
||
|
True if page contains Media Cybernetics Id tag.
|
||
|
is_stk : bool
|
||
|
True if page contains UIC2Tag tag.
|
||
|
is_lsm : bool
|
||
|
True if page contains LSM CZ_LSM_INFO tag.
|
||
|
description : str
|
||
|
Image description
|
||
|
description1 : str
|
||
|
Additional description
|
||
|
is_imagej : None or str
|
||
|
ImageJ metadata
|
||
|
software : str
|
||
|
Software used to create the TIFF file
|
||
|
datetime : datetime.datetime
|
||
|
Creation date and time
|
||
|
|
||
|
Metadata for writing
|
||
|
--------------------
|
||
|
photometric : {'minisblack', 'miniswhite', 'rgb'}
|
||
|
The color space of the image data.
|
||
|
By default this setting is inferred from the data shape.
|
||
|
planarconfig : {'contig', 'planar'}
|
||
|
Specifies if samples are stored contiguous or in separate planes.
|
||
|
By default this setting is inferred from the data shape.
|
||
|
'contig': last dimension contains samples.
|
||
|
'planar': third last dimension contains samples.
|
||
|
resolution : (float, float) or ((int, int), (int, int))
|
||
|
X and Y resolution in dots per inch as float or rational numbers.
|
||
|
description : str
|
||
|
The subject of the image. Saved with the first page only.
|
||
|
compress : int
|
||
|
Values from 0 to 9 controlling the level of zlib (deflate) compression.
|
||
|
If 0, data are written uncompressed (default).
|
||
|
predictor : bool
|
||
|
If True, horizontal differencing is applied before compression.
|
||
|
Note that using an int literal 1 actually means no prediction scheme
|
||
|
will be used.
|
||
|
volume : bool
|
||
|
If True, volume data are stored in one tile (if applicable) using
|
||
|
the SGI image_depth and tile_depth tags.
|
||
|
Image width and depth must be multiple of 16.
|
||
|
Few software can read this format, e.g. MeVisLab.
|
||
|
writeshape : bool
|
||
|
If True, write the data shape to the image_description tag
|
||
|
if necessary and no other description is given.
|
||
|
extratags: sequence of tuples
|
||
|
Additional tags as [(code, dtype, count, value, writeonce)].
|
||
|
|
||
|
code : int
|
||
|
The TIFF tag Id.
|
||
|
dtype : str
|
||
|
Data type of items in 'value' in Python struct format.
|
||
|
One of B, s, H, I, 2I, b, h, i, f, d, Q, or q.
|
||
|
count : int
|
||
|
Number of data values. Not used for string values.
|
||
|
value : sequence
|
||
|
'Count' values compatible with 'dtype'.
|
||
|
writeonce : bool
|
||
|
If True, the tag is written to the first page only.
|
||
|
"""
|
||
|
|
||
|
def _can_read(self, request):
|
||
|
try:
|
||
|
_tifffile.TiffFile(request.get_file(), **request.kwargs)
|
||
|
except ValueError:
|
||
|
# vendored backend raises value exception
|
||
|
return False
|
||
|
except _tifffile.TiffFileError: # pragma: no-cover
|
||
|
# current version raises custom exception
|
||
|
return False
|
||
|
finally:
|
||
|
request.get_file().seek(0)
|
||
|
|
||
|
return True
|
||
|
|
||
|
def _can_write(self, request):
|
||
|
if request._uri_type in [URI_FILE, URI_BYTES]:
|
||
|
pass # special URI
|
||
|
elif request.extension not in self.extensions:
|
||
|
return False
|
||
|
|
||
|
try:
|
||
|
_tifffile.TiffWriter(request.get_file(), **request.kwargs)
|
||
|
except ValueError:
|
||
|
# vendored backend raises value exception
|
||
|
return False
|
||
|
except _tifffile.TiffFileError: # pragma: no-cover
|
||
|
# current version raises custom exception
|
||
|
return False
|
||
|
finally:
|
||
|
request.get_file().seek(0)
|
||
|
return True
|
||
|
|
||
|
# -- reader
|
||
|
|
||
|
class Reader(Format.Reader):
|
||
|
def _open(self, **kwargs):
|
||
|
# Allow loading from http; tifffile uses seek, so download first
|
||
|
if self.request.filename.startswith(("http://", "https://")):
|
||
|
self._f = f = open(self.request.get_local_filename(), "rb")
|
||
|
else:
|
||
|
self._f = None
|
||
|
f = self.request.get_file()
|
||
|
self._tf = _tifffile.TiffFile(f, **kwargs)
|
||
|
|
||
|
def _close(self):
|
||
|
self._tf.close()
|
||
|
if self._f is not None:
|
||
|
self._f.close()
|
||
|
|
||
|
def _get_length(self):
|
||
|
return len(self._tf.series)
|
||
|
|
||
|
def _get_data(self, index):
|
||
|
if index < 0 or index >= self._get_length():
|
||
|
raise IndexError("Index out of range while reading from tiff file")
|
||
|
|
||
|
im = self._tf.asarray(series=index)
|
||
|
meta = self._get_meta_data(index)
|
||
|
|
||
|
return im, meta
|
||
|
|
||
|
def _get_meta_data(self, index):
|
||
|
meta = {}
|
||
|
page = self._tf.pages[index or 0]
|
||
|
for key in READ_METADATA_KEYS:
|
||
|
try:
|
||
|
meta[key] = getattr(page, key)
|
||
|
except Exception:
|
||
|
pass
|
||
|
|
||
|
# tifffile <= 0.12.1 use datetime, newer use DateTime
|
||
|
for key in ("datetime", "DateTime"):
|
||
|
try:
|
||
|
meta["datetime"] = datetime.datetime.strptime(
|
||
|
page.tags[key].value, "%Y:%m:%d %H:%M:%S"
|
||
|
)
|
||
|
break
|
||
|
except Exception:
|
||
|
pass
|
||
|
|
||
|
if 296 in page.tags:
|
||
|
meta["resolution_unit"] = page.tags[296].value.value
|
||
|
|
||
|
if 282 in page.tags and 283 in page.tags and 296 in page.tags:
|
||
|
resolution_x = page.tags[282].value
|
||
|
resolution_y = page.tags[283].value
|
||
|
if resolution_x[1] == 0 or resolution_y[1] == 0:
|
||
|
warnings.warn(
|
||
|
"Ignoring resolution metadata, "
|
||
|
"because at least one direction has a 0 denominator.",
|
||
|
RuntimeWarning,
|
||
|
)
|
||
|
else:
|
||
|
meta["resolution"] = (
|
||
|
resolution_x[0] / resolution_x[1],
|
||
|
resolution_y[0] / resolution_y[1],
|
||
|
page.tags[296].value.name,
|
||
|
)
|
||
|
|
||
|
return meta
|
||
|
|
||
|
# -- writer
|
||
|
class Writer(Format.Writer):
|
||
|
def _open(self, bigtiff=None, byteorder=None, software=None):
|
||
|
try:
|
||
|
self._tf = _tifffile.TiffWriter(
|
||
|
self.request.get_file(),
|
||
|
bigtiff=bigtiff,
|
||
|
byteorder=byteorder,
|
||
|
software=software,
|
||
|
)
|
||
|
self._software = None
|
||
|
except TypeError:
|
||
|
# In tifffile >= 0.15, the `software` arg is passed to
|
||
|
# TiffWriter.save
|
||
|
self._tf = _tifffile.TiffWriter(
|
||
|
self.request.get_file(), bigtiff=bigtiff, byteorder=byteorder
|
||
|
)
|
||
|
self._software = software
|
||
|
|
||
|
self._meta = {}
|
||
|
self._frames_written = 0
|
||
|
|
||
|
def _close(self):
|
||
|
self._tf.close()
|
||
|
|
||
|
def _append_data(self, im, meta):
|
||
|
if meta is not None:
|
||
|
meta = self._sanitize_meta(meta)
|
||
|
else:
|
||
|
# Use global metadata for first frame
|
||
|
meta = self._meta if self._frames_written == 0 else {}
|
||
|
if self._software is not None and self._frames_written == 0:
|
||
|
meta["software"] = self._software
|
||
|
# No need to check self.request.mode; tifffile figures out whether
|
||
|
# this is a single page, or all page data at once.
|
||
|
try:
|
||
|
# TiffWriter.save has been deprecated in version 2020.9.30
|
||
|
write_meth = self._tf.write
|
||
|
except AttributeError:
|
||
|
write_meth = self._tf.save
|
||
|
write_meth(np.asanyarray(im), contiguous=False, **meta)
|
||
|
self._frames_written += 1
|
||
|
|
||
|
@staticmethod
|
||
|
def _sanitize_meta(meta):
|
||
|
ret = {}
|
||
|
for key, value in meta.items():
|
||
|
if key in WRITE_METADATA_KEYS:
|
||
|
# Special case of previously read `predictor` int value
|
||
|
# 1(=NONE) translation to False expected by TiffWriter.save
|
||
|
if key == "predictor" and not isinstance(value, bool):
|
||
|
ret[key] = value > 1
|
||
|
elif key == "compress" and value != 0:
|
||
|
warnings.warn(
|
||
|
"The use of `compress` is deprecated. Use `compression` and `compressionargs` instead.",
|
||
|
DeprecationWarning,
|
||
|
)
|
||
|
|
||
|
if _tifffile.__version__ < "2022":
|
||
|
ret["compression"] = (8, value)
|
||
|
else:
|
||
|
ret["compression"] = "zlib"
|
||
|
ret["compressionargs"] = {"level": value}
|
||
|
else:
|
||
|
ret[key] = value
|
||
|
return ret
|
||
|
|
||
|
def set_meta_data(self, meta):
|
||
|
self._meta = self._sanitize_meta(meta)
|