328 lines
11 KiB
Python
328 lines
11 KiB
Python
|
# -*- coding: utf-8 -*-
|
||
|
# imageio is distributed under the terms of the (new) BSD License.
|
||
|
|
||
|
""" Storage of image data in tiff format.
|
||
|
"""
|
||
|
|
||
|
import datetime
|
||
|
|
||
|
from .. import formats
|
||
|
from ..core import Format
|
||
|
|
||
|
import numpy as np
|
||
|
|
||
|
_tifffile = None # Defer loading to lib() function.
|
||
|
|
||
|
|
||
|
def load_lib():
|
||
|
global _tifffile
|
||
|
try:
|
||
|
import tifffile as _tifffile
|
||
|
except ImportError:
|
||
|
from . import _tifffile
|
||
|
return _tifffile
|
||
|
|
||
|
|
||
|
TIFF_FORMATS = (".tif", ".tiff", ".stk", ".lsm")
|
||
|
WRITE_METADATA_KEYS = (
|
||
|
"photometric",
|
||
|
"planarconfig",
|
||
|
"resolution",
|
||
|
"description",
|
||
|
"compress",
|
||
|
"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.
|
||
|
|
||
|
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.
|
||
|
|
||
|
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):
|
||
|
# We support any kind of image data
|
||
|
return request.extension in self.extensions
|
||
|
|
||
|
def _can_write(self, request):
|
||
|
# We support any kind of image data
|
||
|
return request.extension in self.extensions
|
||
|
|
||
|
# -- reader
|
||
|
|
||
|
class Reader(Format.Reader):
|
||
|
def _open(self, **kwargs):
|
||
|
if not _tifffile:
|
||
|
load_lib()
|
||
|
# 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)
|
||
|
|
||
|
# metadata is the same for all images
|
||
|
self._meta = {}
|
||
|
|
||
|
def _close(self):
|
||
|
self._tf.close()
|
||
|
if self._f is not None:
|
||
|
self._f.close()
|
||
|
|
||
|
def _get_length(self):
|
||
|
if self.request.mode[1] in "vV":
|
||
|
return 1 # or can there be pages in pages or something?
|
||
|
else:
|
||
|
return len(self._tf.pages)
|
||
|
|
||
|
def _get_data(self, index):
|
||
|
if self.request.mode[1] in "vV":
|
||
|
# Read data as single 3D (+ color channels) array
|
||
|
if index != 0:
|
||
|
raise IndexError('Tiff support no more than 1 "volume" per file')
|
||
|
im = self._tf.asarray() # request as singleton image
|
||
|
meta = self._meta
|
||
|
else:
|
||
|
# Read as 2D image
|
||
|
if index < 0 or index >= self._get_length():
|
||
|
raise IndexError("Index out of range while reading from tiff file")
|
||
|
im = self._tf.pages[index].asarray()
|
||
|
meta = self._meta or self._get_meta_data(index)
|
||
|
# Return array and empty meta data
|
||
|
return im, meta
|
||
|
|
||
|
def _get_meta_data(self, index):
|
||
|
page = self._tf.pages[index or 0]
|
||
|
for key in READ_METADATA_KEYS:
|
||
|
try:
|
||
|
self._meta[key] = getattr(page, key)
|
||
|
except Exception:
|
||
|
pass
|
||
|
|
||
|
# tifffile <= 0.12.1 use datetime, newer use DateTime
|
||
|
for key in ("datetime", "DateTime"):
|
||
|
try:
|
||
|
self._meta["datetime"] = datetime.datetime.strptime(
|
||
|
page.tags[key].value, "%Y:%m:%d %H:%M:%S"
|
||
|
)
|
||
|
break
|
||
|
except Exception:
|
||
|
pass
|
||
|
|
||
|
return self._meta
|
||
|
|
||
|
# -- writer
|
||
|
class Writer(Format.Writer):
|
||
|
def _open(self, bigtiff=None, byteorder=None, software=None):
|
||
|
if not _tifffile:
|
||
|
load_lib()
|
||
|
|
||
|
try:
|
||
|
self._tf = _tifffile.TiffWriter(
|
||
|
self.request.get_file(), bigtiff, 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, byteorder
|
||
|
)
|
||
|
self._software = software
|
||
|
|
||
|
self._meta = {}
|
||
|
|
||
|
def _close(self):
|
||
|
self._tf.close()
|
||
|
|
||
|
def _append_data(self, im, meta):
|
||
|
if meta:
|
||
|
self.set_meta_data(meta)
|
||
|
# No need to check self.request.mode; tifffile figures out whether
|
||
|
# this is a single page, or all page data at once.
|
||
|
if self._software is None:
|
||
|
self._tf.save(np.asanyarray(im), **self._meta)
|
||
|
else:
|
||
|
# tifffile >= 0.15
|
||
|
self._tf.save(np.asanyarray(im), software=self._software, **self._meta)
|
||
|
|
||
|
def set_meta_data(self, meta):
|
||
|
self._meta = {}
|
||
|
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):
|
||
|
self._meta[key] = value > 1
|
||
|
else:
|
||
|
self._meta[key] = value
|
||
|
|
||
|
|
||
|
# Register
|
||
|
format = TiffFormat("tiff", "TIFF format", TIFF_FORMATS, "iIvV")
|
||
|
formats.add_format(format)
|