Repository: scikit-image/scikit-image

Issue:
skimage.image's ransac function does not handle well when best_inliers are all False
## Description

<!--
(Note: for guidance on how to use `scikit-image`, please post instead on https://forum.image.sc/tag/scikit-image)
-->

`ransac` routine is supposed to handle well on arbitrary data input.
However, when the best sample model regards all data points as all outliers, `ransac` routine will fail immediately after this line because the `best_inliers` are all `False`:
https://github.com/scikit-image/scikit-image/blob/34c2809ab509acd4e34a0a8c5530588adf0e68e7/skimage/measure/fit.py#L873

## Way to reproduce
```python
# Place the full code we need to recreate your issue here
# upload all necessary images to github too!

import numpy as np
from numpy.polynomial import Polynomial as Poly
from skimage.measure import ransac

class LinearXModel(object):
    def estimate(self, data):
        self.params = Poly.fit(data[:, 1], data[:, 0], 1, domain=[-1, 1])
        return True

    def residuals(self, data):
        return abs(self.params(data[:, 1]) - data[:, 0])

def main():
    data = np.array(
      [[ 943. , 1374.5],
       [1518. , 3293.5],
       [ 749. , 3450.5],
       [2237. , 3578.5],
       [1022. , 3668.5]], dtype=np.float64)
    model, inliers = ransac(data, LinearXModel, 3, 2.2, random_state=1523427)

if __name__ == "__main__":
    main()
```

The output:
```
Traceback (most recent call last):
  File "ransac_bug.py", line 23, in <module>
    main()
  File "ransac_bug.py", line 20, in main
    model, inliers = ransac(data, LinearXModel, 3, 2.2, random_state=1523427)
  File "/home/user/miniconda3/envs/rebook/lib/python3.6/site-packages/skimage/measure/fit.py", line 874, in ransac
    best_model.estimate(*data_inliers)
  File "ransac_bug.py", line 7, in estimate
    self.params = Poly.fit(data[:, 1], data[:, 0], 1, domain=[-1, 1])
  File "/home/user/miniconda3/envs/rebook/lib/python3.6/site-packages/numpy/polynomial/_polybase.py", line 892, in fit
    res = cls._fit(xnew, y, deg, w=w, rcond=rcond, full=full)
  File "/home/user/miniconda3/envs/rebook/lib/python3.6/site-packages/numpy/polynomial/polynomial.py", line 1336, in polyfit
    return pu._fit(polyvander, x, y, deg, rcond, full, w)
  File "/home/user/miniconda3/envs/rebook/lib/python3.6/site-packages/numpy/polynomial/polyutils.py", line 668, in _fit
    raise TypeError("expected non-empty vector for x")
TypeError: expected non-empty vector for x
```


## Version information
```python
# Paste the output of the following python commands
from __future__ import print_function
import sys; print(sys.version)
import platform; print(platform.platform())
import skimage; print("scikit-image version: {}".format(skimage.__version__))
import numpy; print("numpy version: {}".format(numpy.__version__))
```

```python
# your output here
3.6.10 |Anaconda, Inc.| (default, May  8 2020, 02:54:21)
[GCC 7.3.0]
Linux-4.15.0-70-generic-x86_64-with-debian-buster-sid
scikit-image version: 0.17.2
numpy version: 1.18.5
```



Discussion:
Good catch @jimlinntu! We'll need to fix this. If you have the time and inclination, a pull request would be appreciated, but otherwise this might make a good topic for the upcoming SciPy sprint! 😊 
Would be good to check this as well: #2698

Resolving patch:
diff --git a/skimage/measure/fit.py b/skimage/measure/fit.py
--- a/skimage/measure/fit.py
+++ b/skimage/measure/fit.py
@@ -2,6 +2,7 @@
 import numpy as np
 from numpy.linalg import inv, pinv
 from scipy import optimize
+from warnings import warn
 from .._shared.utils import check_random_state
 
 
@@ -868,9 +869,13 @@ def ransac(data, model_class, min_samples, residual_threshold,
                 break
 
     # estimate final model using all inliers
-    if best_inliers is not None:
+    if best_inliers is not None and any(best_inliers):
         # select inliers for each data array
         data_inliers = [d[best_inliers] for d in data]
         best_model.estimate(*data_inliers)
+    else:
+        best_model = None
+        best_inliers = None
+        warn("No inliers found. Model not fitted")
 
     return best_model, best_inliers


Test patch:
diff --git a/skimage/measure/tests/test_fit.py b/skimage/measure/tests/test_fit.py
--- a/skimage/measure/tests/test_fit.py
+++ b/skimage/measure/tests/test_fit.py
@@ -6,6 +6,7 @@
 from skimage._shared import testing
 from skimage._shared.testing import (assert_equal, assert_almost_equal,
                                      assert_array_less, xfail, arch32)
+from skimage._shared._warnings import expected_warnings
 
 
 def test_line_model_invalid_input():
@@ -386,3 +387,12 @@ def residuals(self, data):
     data = np.arange(4)
     ransac(data, DummyModel, min_samples=3, residual_threshold=0.0,
            max_trials=10)
+
+
+def test_ransac_with_no_final_inliers():
+    data = np.random.rand(5, 2)
+    with expected_warnings(['No inliers found. Model not fitted']):
+        model, inliers = ransac(data, model_class=LineModelND, min_samples=3,
+                                residual_threshold=0, random_state=1523427)
+    assert inliers is None
+    assert model is None