Skip to content

Commit

Permalink
tifffile plugin: Various fixes (#567)
Browse files Browse the repository at this point in the history
* tiffile plugin: Don't assume metadata is the same for all frames

* FEI SEM plugin: Fix after changes to tifffile plugin

* tiffile plugin: Write global metadata only to first frame

and only if no metadata was given for first frame.

* tifffile plugin: Set `contiguous=False` when writing frames

This has been tifffile's default since version 2020.9.3 and allows for
e.g. per-frame metadata.

* tiffile plugin: Use TiffWriter.write instead of .save

if available. `save` was deprecated in tifffile version 2020.9.30.

Co-authored-by: Lukas Schrangl <schrangl@iap.tuwien.ac.at>
  • Loading branch information
lschr and Lukas Schrangl committed Nov 20, 2020
1 parent 182129b commit 6d8a0b7
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 39 deletions.
6 changes: 5 additions & 1 deletion imageio/plugins/feisem.py
Expand Up @@ -52,6 +52,9 @@ def _get_meta_data(self, index=None):
metadata : dict
Dictionary of metadata.
"""
if hasattr(self, "_fei_meta"):
return self._fei_meta

md = {"root": {}}
current_tag = "root"
reading_metadata = False
Expand Down Expand Up @@ -80,7 +83,8 @@ def _get_meta_data(self, index=None):
md[current_tag][key] = val
if not md["root"] and len(md) == 1:
raise ValueError("Input file %s contains no FEI metadata." % filename)
self._meta.update(md)

self._fei_meta = md
return md


Expand Down
56 changes: 36 additions & 20 deletions imageio/plugins/tifffile.py
Expand Up @@ -69,6 +69,11 @@ class TiffFormat(Format):
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`.
Parameters for reading
----------------------
offset : int
Expand Down Expand Up @@ -225,9 +230,6 @@ def _open(self, **kwargs):
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:
Expand Down Expand Up @@ -257,35 +259,36 @@ def _get_data(self, index):
im = np.stack(ims, 0)
else:
im = self._tf.asarray()
meta = self._meta
meta = self._get_meta_data(0)
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)
meta = self._get_meta_data(index)
# Return array and empty meta data
return im, meta

def _get_meta_data(self, index):
meta = {}
page = self._tf.pages[index or 0]
for key in READ_METADATA_KEYS:
try:
self._meta[key] = getattr(page, key)
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(
meta["datetime"] = datetime.datetime.strptime(
page.tags[key].value, "%Y:%m:%d %H:%M:%S"
)
break
except Exception:
pass

return self._meta
return meta

# -- writer
class Writer(Format.Writer):
Expand All @@ -307,31 +310,44 @@ def _open(self, bigtiff=None, byteorder=None, software=None):
self._software = software

self._meta = {}
self._frames_written = 0

def _close(self):
self._tf.close()

def _append_data(self, im, meta):
if meta:
self.set_meta_data(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.
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 = {}
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):
self._meta[key] = value > 1
ret[key] = value > 1
else:
self._meta[key] = value
ret[key] = value
return ret

def set_meta_data(self, meta):
self._meta = self._sanitize_meta(meta)


# Register
Expand Down
40 changes: 22 additions & 18 deletions tests/test_tifffile.py
Expand Up @@ -24,9 +24,9 @@ def test_tifffile_format():

def test_tifffile_reading_writing():
""" Test reading and saving tiff """

need_internet() # We keep a test image in the imageio-binary repo

im2 = np.ones((10, 10, 3), np.uint8) * 2

filename1 = os.path.join(test_dir, 'test_tiff.tiff')
Expand All @@ -38,7 +38,7 @@ def test_tifffile_reading_writing():
assert im.shape == im2.shape
assert (im == im2).all()
assert len(ims) == 1

# Multiple images
imageio.mimsave(filename1, [im2, im2, im2])
im = imageio.imread(filename1)
Expand All @@ -57,7 +57,7 @@ def test_tifffile_reading_writing():
assert len(vols) == 1 and vol.shape == vols[0].shape
for i in range(3):
assert (vol[i] == im2).all()

# remote multipage rgb file
filename2 = get_remote_file('images/multipage_rgb.tif')
img = imageio.mimread(filename2)
Expand All @@ -81,7 +81,7 @@ def test_tifffile_reading_writing():
# Fail
raises(IndexError, R.get_data, -1)
raises(IndexError, R.get_data, 3)

# Ensure imread + imwrite works round trip
filename3 = os.path.join(test_dir, 'test_tiff2.tiff')
im1 = imageio.imread(filename1)
Expand All @@ -90,7 +90,7 @@ def test_tifffile_reading_writing():
assert im1.ndim == 3
assert im1.shape == im3.shape
assert (im1 == im3).all()

# Ensure imread + imwrite works round trip - volume like
filename3 = os.path.join(test_dir, 'test_tiff2.tiff')
im1 = imageio.volread(filename1)
Expand All @@ -110,18 +110,22 @@ def test_tifffile_reading_writing():

# Write metadata
dt = datetime.datetime(2018, 8, 6, 15, 35, 5)
w = imageio.get_writer(filename1, software='testsoftware')
w.append_data(np.zeros((10, 10)), meta={'description': 'test desc',
'datetime': dt})
w.close()
r = imageio.get_reader(filename1)
md = r.get_meta_data()
assert 'datetime' in md
assert md['datetime'] == dt
assert 'software' in md
assert md['software'] == 'testsoftware'
assert 'description' in md
assert md['description'] == 'test desc'
with imageio.get_writer(filename1, software='testsoftware') as w:
w.append_data(np.zeros((10, 10)), meta={'description': 'test desc',
'datetime': dt})
w.append_data(np.zeros((10, 10)), meta={'description': 'another desc'})
with imageio.get_reader(filename1) as r:
for md in r.get_meta_data(), r.get_meta_data(0):
assert 'datetime' in md
assert md['datetime'] == dt
assert 'software' in md
assert md['software'] == 'testsoftware'
assert 'description' in md
assert md['description'] == 'test desc'

md = r.get_meta_data(1)
assert 'description' in md
assert md['description'] == 'another desc'


run_tests_if_main()

0 comments on commit 6d8a0b7

Please sign in to comment.