Repository: imageio/imageio

Issue:
Incorrect image dimensions after reading in 3D+c TIFF
I'm trying to read the following file:

https://www.dropbox.com/s/0ynbscx4cmd5k91/E_z2_512_1um_CONTROL.tif?dl=1

skimage (ie tifffile) reads this correctly, but imageio doesn't — even though I thought it also used tifffile as a backend for tiffs?

```python
In [1]: import imageio
In [2]: from skimage import io
In [3]: im_iio = imageio.imread('E_z2_512_1um_CONTROL.tif')
In [4]: im_iio.shape
Out[4]: (159, 320, 512)

In [5]: im_ski = io.imread('E_z2_512_1um_CONTROL.tif')
In [6]: im_ski.shape
Out[6]: (53, 320, 512, 3)
```

What imageio is doing is reading the file in the array order present in the file (plane, channel, row, column), and squashing pln and ch dimensions together, while skimage is behaving as expected and producing two separate dimensions for pln and ch and (maybe a bit more than expected) moving ch to the last dimension, which is the dimension order expected by most scipy packages.

Any ideas about what is going wrong here?

Discussion:

Patch:
diff --git a/imageio/plugins/tifffile.py b/imageio/plugins/tifffile.py
index f6d25ad..a26ea79 100644
--- a/imageio/plugins/tifffile.py
+++ b/imageio/plugins/tifffile.py
@@ -37,8 +37,11 @@ READ_METADATA_KEYS = ('planar_configuration', 'is_fluoview', 'is_nih',
 
 
 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
     ----------------------
@@ -177,17 +180,28 @@ class TiffFormat(Format):
 
         def _close(self):
             self._tf.close()
-
+        
         def _get_length(self):
-            return len(self._tf)
-
+            if self.request.mode[1] in 'vV':
+                return 1  # or can there be pages in pages or something?
+            else:
+                return len(self._tf)
+        
         def _get_data(self, index):
-            # Get data
-            if index < 0 or index >= len(self._tf):
-                raise IndexError(
-                    'Index out of range while reading from tiff file')
-            im = self._tf[index].asarray()
-            meta = self._meta or self._get_meta_data(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 >= len(self._tf):
+                    raise IndexError(
+                        'Index out of range while reading from tiff file')
+                im = self._tf[index].asarray()
+                meta = self._meta or self._get_meta_data(index)
             # Return array and empty meta data
             return im, meta
 
@@ -216,6 +230,8 @@ class TiffFormat(Format):
         def _append_data(self, im, meta):
             if meta:
                 self.set_meta_data(meta)
+            # No need to check self.request.mode; tiffile figures out whether
+            # this is a single page, or all page data at once.
             self._tf.save(np.asanyarray(im), **self._meta)
 
         def set_meta_data(self, meta):


Test patch:
diff --git a/tests/test_tifffile.py b/tests/test_tifffile.py
index 5ab5ce7..ea7ad7d 100644
--- a/tests/test_tifffile.py
+++ b/tests/test_tifffile.py
@@ -34,16 +34,29 @@ def test_tifffile_reading_writing():
     imageio.imsave(filename1, im2)
     im = imageio.imread(filename1)
     ims = imageio.mimread(filename1)
+    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)
     ims = imageio.mimread(filename1)
-    assert (im == im2).all()
-    assert len(ims) == 3, ims[0].shape
-
+    assert im.shape == im2.shape
+    assert (im == im2).all()  # note: this does not imply that the shape match!
+    assert len(ims) == 3
+    for i in range(3):
+        assert ims[i].shape == im2.shape
+        assert (ims[i] == im2).all()
+
+    # Read all planes as one array - we call it a volume for clarity
+    vol = imageio.volread(filename1)
+    vols = imageio.mvolread(filename1)
+    assert vol.shape == (3, ) + im2.shape
+    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)
@@ -67,13 +80,24 @@ def test_tifffile_reading_writing():
     # Fail
     raises(IndexError, R.get_data, -1)
     raises(IndexError, R.get_data, 3)
-
-    # Ensure imwrite write works round trip
+    
+    # Ensure imread + imwrite works round trip
+    filename3 = os.path.join(test_dir, 'test_tiff2.tiff')
+    im1 = imageio.imread(filename1)
+    imageio.imwrite(filename3, im1)
+    im3 = imageio.imread(filename3)
+    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')
-    R = imageio.imread(filename1)
-    imageio.imwrite(filename3, R)
-    R2 = imageio.imread(filename3)
-    assert (R == R2).all()
+    im1 = imageio.volread(filename1)
+    imageio.volwrite(filename3, im1)
+    im3 = imageio.volread(filename3)
+    assert im1.ndim == 4
+    assert im1.shape == im3.shape
+    assert (im1 == im3).all()
 
 
 run_tests_if_main()