Repository: scikit-image/scikit-image

Issue:
Test failures with NumPy 1.17.0rc1
## Description

Some of our tests are failing with NumPy 1.17.0rc1

https://travis-ci.org/scikit-image/scikit-image/jobs/552547744#L2635

They appear to be related to `__array_function__`.


Discussion:
```
_____________________________ test_dask_histogram ______________________________
    def test_dask_histogram():
        pytest.importorskip('dask', reason="dask python library is not installed")
        import dask.array as da
        dask_array = da.from_array(np.array([[0, 1], [1, 2]]), chunks=(1, 2))
>       output_hist, output_bins = exposure.histogram(dask_array)
../../venv/lib/python3.6/site-packages/skimage/exposure/tests/test_exposure.py:566: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../venv/lib/python3.6/site-packages/skimage/exposure/exposure.py:131: in histogram
    hist, bin_centers = _bincount_histogram(image, source_range)
../../venv/lib/python3.6/site-packages/skimage/exposure/exposure.py:64: in _bincount_histogram
    image_min = int(np.min(image).astype(np.int64))
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
args = (dask.array<reshape, shape=(4,), dtype=int64, chunksize=(2,)>,)
kwargs = {}
relevant_args = (dask.array<reshape, shape=(4,), dtype=int64, chunksize=(2,)>, None)
>   ???
E   TypeError: no implementation found for 'numpy.amin' on types that implement __array_function__: [<class 'dask.array.core.Array'>]
<__array_function__ internals>:6: TypeError
```

this seems like a numpy + dask issue. Probably should report it upstream.
You're right, it's a failure of dask.array with `__array_function__` and np.min. That should definitely work! Using `image.min()` instead works fine. Reporting now.
Is this code intended to work by coercing a dask array into a NumPy array? That's definitely what it's doing right now, pre-NumPy 1.17.
Yes, that's right, we are merely testing for *basic* compatibility, ie that we won't barf with an error if a dask array is given as input. Which is exactly what we are en-route to doing at this point. :joy: 
In response to a question by @hmaarrfk in #3987 about what *we* can do here: we could replace `np.min` by `np.amin` and it should go back to working as expected.
(Or we can wait for `rc2` which might fix the problem for us.)
> That's definitely what it's doing right now, pre-NumPy 1.17.

This comment from @shoyer makes it seem like it was a deliberate change.
I think this will be fixed shortly in a bug-fix release of dask -- see the linked issue for discussion. This operation will work (by using dask array operations), and other, non-implemented operations (e.g., `np.median`) will work but dask will issue a warning.

But it is definitely true that at least some of these operations will start to be parallelized with dask rather than executed by automatic coercion into NumPy arrays. That will probably be a welcome change for skimage users, but it's definitely a change.
@shoyer as I commented on the dask issue, that's only a partial fix, as it will still leave a whole bunch of incompatible NumPy/dask combinations out in the wild. The real fix is to have 1.17 issue a warning instead of a TypeError.

> That will probably be a welcome change for skimage users, but it's definitely a change.

Yes, this is totally fine. The `int()` was actually added to make sure that it would continue to work in a future in which `__array_function__` became a thing. It would be *ideal* for `np.min` to work correctly with array-function, but it should *also* work with intermediate versions of dask.

Resolving patch:
diff --git a/skimage/exposure/exposure.py b/skimage/exposure/exposure.py
--- a/skimage/exposure/exposure.py
+++ b/skimage/exposure/exposure.py
@@ -61,8 +61,8 @@ def _bincount_histogram(image, source_range):
     if source_range not in ['image', 'dtype']:
         raise ValueError('Incorrect value for `source_range` argument: {}'.format(source_range))
     if source_range == 'image':
-        image_min = int(np.min(image).astype(np.int64))
-        image_max = int(np.max(image).astype(np.int64))
+        image_min = int(image.min().astype(np.int64))
+        image_max = int(image.max().astype(np.int64))
     elif source_range == 'dtype':
         image_min, image_max = dtype_limits(image, clip_negative=False)
     image, offset = _offset_array(image, image_min, image_max)
diff --git a/skimage/io/_plugins/tifffile_plugin.py b/skimage/io/_plugins/tifffile_plugin.py
--- a/skimage/io/_plugins/tifffile_plugin.py
+++ b/skimage/io/_plugins/tifffile_plugin.py
@@ -41,7 +41,7 @@ def imread(fname, dtype=None, **kwargs):
 
     # parse_kwargs will extract keyword arguments intended for the TiffFile
     # class and remove them from the kwargs dictionary in-place
-    tiff_keys = ['multifile', 'multifile_close', 'pages', 'fastij', 'is_ome']
+    tiff_keys = ['multifile', 'multifile_close', 'fastij', 'is_ome']
     kwargs_tiff = parse_kwargs(kwargs, *tiff_keys)
 
     # read and return tiff as numpy array


Test patch:
diff --git a/skimage/io/tests/test_tifffile.py b/skimage/io/tests/test_tifffile.py
--- a/skimage/io/tests/test_tifffile.py
+++ b/skimage/io/tests/test_tifffile.py
@@ -38,7 +38,7 @@ def test_imread_multipage_rgb_tif():
     assert img.shape == (2, 10, 10, 3), img.shape
 
 def test_tifffile_kwarg_passthrough ():
-    img = imread(os.path.join(data_dir, 'multipage.tif'), pages=[1], 
+    img = imread(os.path.join(data_dir, 'multipage.tif'), key=[1],
                  multifile=False, multifile_close=True, fastij=True, 
                  is_ome=True)
     assert img.shape == (15, 10), img.shape
diff --git a/skimage/util/tests/test_arraypad.py b/skimage/util/tests/test_arraypad.py
--- a/skimage/util/tests/test_arraypad.py
+++ b/skimage/util/tests/test_arraypad.py
@@ -1,5 +1,7 @@
+from distutils.version import LooseVersion
 
 import numpy as np
+# note: skimage.util.pad is just numpy.pad
 from skimage.util import pad
 
 from skimage._shared import testing
@@ -989,6 +991,8 @@ def test_check_kwarg_not_allowed(self):
         with testing.raises(ValueError):
             pad(arr, 4, mode='mean', reflect_type='odd')
 
+    @testing.skipif(LooseVersion(np.__version__) >= LooseVersion('1.17'),
+                    reason='Error removed in NumPy 1.17')
     def test_mode_not_set(self):
         arr = np.arange(30).reshape(5, 6)
         with testing.raises(TypeError):