Repository: scikit-image/scikit-image

Issue:
0.14.4 - threshold_li fails on array with 2 values in it
## Description
For an array with only 2 values in it, threshold_li fails; this worked in 0.14.2, but fails in 0.14.3 and 0.14.4 .  This was causing 3 tests in CellProfiler to fail; I've bypassed it for two tests that were not in fact supposed to use threshold_li (which was our bad), but the third is still failing.  It's unlikely such images would exist directly off a microscope, but if someone fed in a binary mask (ala example "a" below), it would fail, which is definitely an issue.

## Way to reproduce
```python
a = numpy.array([0, 0, 1, 0, 0, 1, 0, 1])
b = numpy.array([0, 0, 0.1, 0, 0, 0.1, 0, 0.1])
c = numpy.array([0, 0, 0.1, 0, 0, 0.1, 0.01, 0.1])
d = numpy.array([0, 0, 1, 0, 0, 1, 0.5, 1])
e = numpy.array([1,1])
f = numpy.array([1,2])
skimage.filters.threshold_li(a)
skimage.filters.threshold_li(b)
skimage.filters.threshold_li(c)
skimage.filters.threshold_li(d)
skimage.filters.threshold_li(e)
skimage.filters.threshold_li(f)
```
This comes out this way in 0.14.2:

```
>>> skimage.filters.threshold_li(a)
/Users/bcimini/.virtualenvs/skimageli/lib/python2.7/site-packages/skimage/filters/thresholding.py:601: RuntimeWarning: divide by zero encountered in log
  (np.log(mean_back) - np.log(mean_fore)))
0.0
>>> skimage.filters.threshold_li(b)
0.0
>>> skimage.filters.threshold_li(c)
0.02505097742626249
>>> skimage.filters.threshold_li(d)
0.0
>>> skimage.filters.threshold_li(e)
1
>>> skimage.filters.threshold_li(f)
1.0
```

But this way in 0.14.3/4:

```
>>> skimage.filters.threshold_li(a)
/Users/bcimini/.virtualenvs/skimageli/lib/python2.7/site-packages/numpy/core/fromnumeric.py:3118: RuntimeWarning: Mean of empty slice.
  out=out, **kwargs)
/Users/bcimini/.virtualenvs/skimageli/lib/python2.7/site-packages/numpy/core/_methods.py:85: RuntimeWarning: invalid value encountered in double_scalars
  ret = ret.dtype.type(ret / rcount)
nan
>>> skimage.filters.threshold_li(b)
nan
>>> skimage.filters.threshold_li(c)
0.02505097742626249
>>> skimage.filters.threshold_li(d)
/Users/bcimini/.virtualenvs/skimageli/lib/python2.7/site-packages/skimage/filters/thresholding.py:596: RuntimeWarning: divide by zero encountered in log
  (np.log(mean_back) - np.log(mean_fore)))
0.0
>>> skimage.filters.threshold_li(e)
1
>>> skimage.filters.threshold_li(f)
nan
```

## Version information
```python
2.7.12 (default, Aug 27 2019, 14:36:02) 
[GCC 4.2.1 Compatible Apple LLVM 10.0.0 (clang-1000.10.44.4)]
Darwin-18.7.0-x86_64-i386-64bit
scikit-image version: 0.14.4
numpy version: 1.16.5
```




Discussion:
Ok, that's it, I'm calling it: we are removing `threshold_li` altogether! :joy:

*sigh* sorry about this, will investigate! :man_facepalming: 
Ok, this doesn't seem *too* bad: when the background is all zeros, `np.log(mean_back)` is nan. The fix is to subtract not `image_min` but rather `image_min - np.eps`, so that all values are *slightly* positive. What's pathological is not that it's few values but that the background is all zeros. Working on this! It's kinda weird that we haven't caught this already, but we will in the future thanks to your tests! =)
amazing. `np.log(mean_back)` is actually `-inf`, which works fine, at least for recent versions of Python/NumPy. The outputs I get for your five arrays with skimage master are:

[0.0, 0.0, 0.025050977426262489, 0.0, 1, 1.0]

which is the same as 0.14.2... I'll check the 0.14.x branch next.
It's all good dude :)

Resolving patch:
diff --git a/setup.py b/setup.py
--- a/setup.py
+++ b/setup.py
@@ -130,8 +130,6 @@ def configuration(parent_package='', top_path=None):
             'Programming Language :: Python :: 2',
             'Programming Language :: Python :: 2.7',
             'Programming Language :: Python :: 3',
-            'Programming Language :: Python :: 3.4',
-            'Programming Language :: Python :: 3.5',
             'Programming Language :: Python :: 3.6',
             'Programming Language :: Python :: 3.7',
             'Topic :: Scientific/Engineering',
@@ -142,7 +140,7 @@ def configuration(parent_package='', top_path=None):
         ],
         install_requires=INSTALL_REQUIRES,
         requires=REQUIRES,
-        python_requires='>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*',
+        python_requires='>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*',
         packages=setuptools.find_packages(exclude=['doc']),
         include_package_data=True,
         zip_safe=False,  # the package can run out of an .egg file
diff --git a/skimage/filters/thresholding.py b/skimage/filters/thresholding.py
--- a/skimage/filters/thresholding.py
+++ b/skimage/filters/thresholding.py
@@ -581,8 +581,12 @@ def threshold_li(image):
     tolerance = np.min(np.diff(np.unique(image))) / 2
 
     # Initial estimate
-    t_curr = np.mean(image)
-    t_next = t_curr + 2 * tolerance
+    # it is important that t_next "straddles" some data, ie it should be
+    # greater than or equal to the min and less than or equal to the max.
+    # see https://github.com/scikit-image/scikit-image/issues/4140
+    # t_curr should be more than tolerance away from t_next.
+    t_next = np.mean(image)
+    t_curr = t_next - 2 * tolerance
 
     # Stop the iterations when the difference between the
     # new and old threshold values is less than the tolerance


Test patch:
diff --git a/skimage/filters/tests/test_thresholding.py b/skimage/filters/tests/test_thresholding.py
--- a/skimage/filters/tests/test_thresholding.py
+++ b/skimage/filters/tests/test_thresholding.py
@@ -290,6 +290,19 @@ def test_li_constant_image_with_nan():
     assert threshold_li(image) == 8
 
 
+def test_li_pathological_arrays():
+    # See https://github.com/scikit-image/scikit-image/issues/4140
+    a = np.array([0, 0, 1, 0, 0, 1, 0, 1])
+    b = np.array([0, 0, 0.1, 0, 0, 0.1, 0, 0.1])
+    c = np.array([0, 0, 0.1, 0, 0, 0.1, 0.01, 0.1])
+    d = np.array([0, 0, 1, 0, 0, 1, 0.5, 1])
+    e = np.array([1, 1])
+    f = np.array([1, 2])
+    arrays = [a, b, c, d, e, f]
+    thresholds = [threshold_li(arr) for arr in arrays]
+    assert np.all(np.isfinite(thresholds))
+
+
 def test_yen_camera_image():
     camera = skimage.img_as_ubyte(data.camera())
     assert 197 < threshold_yen(camera) < 199
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,6 +1,8 @@
 from __future__ import division, absolute_import, print_function
+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
@@ -990,6 +992,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):