Repository: scikit-image/scikit-image

Issue:
skimage.external.tifffile fails to use the compiled tifffile extension
**Update:** As noted below, #1549 fixes the original problem described here, but we've run into a second issue which also prevents the `_tifffile.so` extension from loading properly. Explanation below.

---

The version of `tifffile.py` included in `skimage.external` is slightly out-of-date with respect to the [version on the author's website](http://www.lfd.uci.edu/~gohlke/code/tifffile.py.html).

Critically, the version in scikit-image includes a typo in the [`_replace_by`](https://github.com/scikit-image/scikit-image/blob/1b38c172800b45c04fcf958c4e8712ae7374ea8a/skimage/external/tifffile/tifffile_local.py#L3117) function, which prevents the C-extension code in `_tifffile.so` from getting imported.  Thus, although the `_tifffile.so` C-extension is built properly, it isn't ever used.

Fortunately, a pure-python implementation of `decodelzw()` exists as a fallback. Unfortunately, it is an order of magnitude slower than the C implementation.



Discussion:
Thanks for the report, @stuarteberg - I believe I just pushed a PR with a fix. Can you verify #1549 resolves the problem?

@JDWarner, sorry for the delayed response.  I took a look at your fix today (which I see was already merged).  To my surprise, your change wasn't enough to fix the problem.

Or rather, there is actually a second problem.  The tifffile code assumes that `_tifffile.so` can be found on `sys.path`.  Since `skimage.external.tifffile` is not on `sys.path`, the `_tifffile.so` module is not found.

I just pushed a PR (#1551) with a potential fix, but I'm not sure if it's a good idea to get out-of-sync with the original `tifffile` module.

Interesting - ping @blink1073 

I've got an eye infection, I'll be out of commission for a bit. 

—
Sent from Mailbox

On Wed, Jun 10, 2015 at 4:51 PM, Josh Warner notifications@github.com
wrote:

> ## Interesting - ping @blink1073 
> 
> Reply to this email directly or view it on GitHub:
> https://github.com/scikit-image/scikit-image/issues/1548#issuecomment-110926516

@stuarteberg, you raise a good point.  In the PyPI package, both modules are copied to site-packages, so `_tifffile.so` is available on the path.  I will adjust the `skimage` version to be a package to avoid this problem.


Resolving patch:
diff --git a/skimage/external/tifffile/__init__.py b/skimage/external/tifffile/__init__.py
--- a/skimage/external/tifffile/__init__.py
+++ b/skimage/external/tifffile/__init__.py
@@ -1,4 +1,5 @@
-try:
-    from tifffile import imread, imsave, TiffFile
-except ImportError:
-    from .tifffile_local import imread, imsave, TiffFile
+from .tifffile import imsave, imread, imshow, TiffFile, TiffWriter, TiffSequence
+
+__version__ = '0.6.2'
+__all__ = ('imsave', 'imread', 'imshow', 'TiffFile', 'TiffWriter',
+           'TiffSequence')
diff --git a/skimage/external/tifffile/tifffile_local.py b/skimage/external/tifffile/tifffile.py
similarity index 99%
rename from skimage/external/tifffile/tifffile_local.py
rename to skimage/external/tifffile/tifffile.py
--- a/skimage/external/tifffile/tifffile_local.py
+++ b/skimage/external/tifffile/tifffile.py
@@ -149,9 +149,15 @@
 
 import numpy
 
-from . import _tifffile
-
-__version__ = '0.4.1'
+try:
+    from . import _tifffile
+except ImportError:
+    warnings.warn(
+        "failed to import the optional _tifffile C extension module.\n"
+        "Loading of some compressed images will be slow.\n"
+        "Tifffile.c can be obtained at http://www.lfd.uci.edu/~gohlke/")
+
+__version__ = '2014.08.24'
 __docformat__ = 'restructuredtext en'
 __all__ = ('imsave', 'imread', 'imshow', 'TiffFile', 'TiffWriter',
            'TiffSequence')
@@ -179,8 +185,8 @@ def imsave(filename, data, **kwargs):
     Examples
     --------
     >>> data = numpy.random.rand(2, 5, 3, 301, 219)
-    >>> description = '{"shape": %s}' % str(list(data.shape))
-    >>> imsave('temp.tif', data, compress=6,
+    >>> description = u'{"shape": %s}' % str(list(data.shape))  # doctest: +SKIP
+    >>> imsave('temp.tif', data, compress=6,  # doctest: +SKIP
     ...        extratags=[(270, 's', 0, description, True)])
 
     """
@@ -661,12 +667,12 @@ def imread(files, **kwargs):
 
     Examples
     --------
-    >>> im = imread('temp.tif', key=0)
-    >>> im.shape
-    (3, 301, 219)
-    >>> ims = imread(['temp.tif', 'temp.tif'])
-    >>> ims.shape
-    (2, 10, 3, 301, 219)
+    >>> im = imread('test.tif', key=0)  # doctest: +SKIP
+    >>> im.shape  # doctest: +SKIP
+    (256, 256, 4)
+    >>> ims = imread(['test.tif', 'test.tif'])  # doctest: +SKIP
+    >>> ims.shape  # doctest: +SKIP
+    (2, 256, 256, 4)
 
     """
     kwargs_file = {}
@@ -731,10 +737,10 @@ class TiffFile(object):
 
     Examples
     --------
-    >>> with TiffFile('temp.tif') as tif:
+    >>> with TiffFile('test.tif') as tif:  # doctest: +SKIP
     ...     data = tif.asarray()
     ...     data.shape
-    (5, 301, 219)
+    (256, 256, 4)
 
     """
     def __init__(self, arg, name=None, offset=None, size=None,
@@ -3112,8 +3118,11 @@ def decorate(func, module_function=module_function, warn=warn):
                 full_name = modname
             else:
                 full_name = package + '.' + modname
-            module = __import__(full_name, fromlist=[modname])
-            func, oldfunc = getattr(module, function), func
+            if modname == '_tifffile':
+                func = getattr(_tifffile, function)
+            else:
+                module = __import__(full_name, fromlist=[modname])
+                func, oldfunc = getattr(module, function), func
             globals()['__old_' + func.__name__] = oldfunc
         except Exception:
             if warn:


Test patch:
diff --git a/skimage/external/test_tifffile.py b/skimage/external/test_tifffile.py
new file mode 100644
--- /dev/null
+++ b/skimage/external/test_tifffile.py
@@ -0,0 +1,66 @@
+import os
+import numpy as np
+
+try:
+    import skimage as si
+except Exception:
+    si = None
+
+from numpy.testing import (
+    assert_array_equal, assert_array_almost_equal, run_module_suite)
+from numpy.testing.decorators import skipif
+
+from tempfile import NamedTemporaryFile
+from .tifffile import imread, imsave
+
+
+np.random.seed(0)
+
+
+@skipif(si is None)
+def test_imread_uint16():
+    expected = np.load(os.path.join(si.data_dir, 'chessboard_GRAY_U8.npy'))
+    img = imread(os.path.join(si.data_dir, 'chessboard_GRAY_U16.tif'))
+    assert img.dtype == np.uint16
+    assert_array_almost_equal(img, expected)
+
+
+@skipif(si is None)
+def test_imread_uint16_big_endian():
+    expected = np.load(os.path.join(si.data_dir, 'chessboard_GRAY_U8.npy'))
+    img = imread(os.path.join(si.data_dir, 'chessboard_GRAY_U16B.tif'))
+    assert img.dtype == np.uint16
+    assert_array_almost_equal(img, expected)
+
+
+def test_extension():
+    from .tifffile.tifffile import decodelzw
+    import types
+    assert isinstance(decodelzw, types.BuiltinFunctionType), type(decodelzw)
+
+
+class TestSave:
+
+    def roundtrip(self, dtype, x):
+        f = NamedTemporaryFile(suffix='.tif')
+        fname = f.name
+        f.close()
+        imsave(fname, x)
+        y = imread(fname)
+        assert_array_equal(x, y)
+
+    def test_imsave_roundtrip(self):
+        for shape in [(10, 10), (10, 10, 3), (10, 10, 4)]:
+            for dtype in (np.uint8, np.uint16, np.float32, np.int16,
+                          np.float64):
+                x = np.random.rand(*shape)
+
+                if not np.issubdtype(dtype, float):
+                    x = (x * np.iinfo(dtype).max).astype(dtype)
+                else:
+                    x = x.astype(dtype)
+                yield self.roundtrip, dtype, x
+
+
+if __name__ == "__main__":
+    run_module_suite()