Repository: scikit-image/scikit-image

Issue:
RANSAC: Check for updates
### Discussed in https://github.com/scikit-image/scikit-image/discussions/5938

<div type='discussions-op-text'>

<sup>Originally posted by **stefanv** July 11, 2017</sup>
RANSAC was implemented in scikit-image & scikit-learn around the same time, by the same @ahojnnes.  In the mean time, some improvements have landed on either side.

We should compare the two implementations, and port fixes to either side as needed.
</div>


Discussion:
See also https://github.com/scikit-image/scikit-image/discussions/5938 and https://github.com/scikit-image/scikit-image/issues/2485
@stefanv
Hello! I'm trying to contribute to the open source for the first time.
May I try this issue? I'll compare it to sklearn and try to modify it. Thanks!
@gmelon Yes, you are very welcome to work on it.  Thank you :)

Resolving patch:
diff --git a/skimage/measure/fit.py b/skimage/measure/fit.py
--- a/skimage/measure/fit.py
+++ b/skimage/measure/fit.py
@@ -5,6 +5,8 @@
 from numpy.linalg import inv
 from scipy import optimize, spatial
 
+_EPSILON = np.spacing(1)
+
 
 def _check_data_dim(data, dim):
     if data.ndim != 2 or data.shape[1] != dim:
@@ -610,19 +612,14 @@ def _dynamic_max_trials(n_inliers, n_samples, min_samples, probability):
     trials : int
         Number of trials.
     """
+    if probability == 0:
+        return 0
     if n_inliers == 0:
         return np.inf
-
-    if probability == 1:
-        return np.inf
-
-    if n_inliers == n_samples:
-        return 1
-
-    nom = math.log(1 - probability)
-    denom = math.log(1 - (n_inliers / n_samples) ** min_samples)
-
-    return int(np.ceil(nom / denom))
+    inlier_ratio = n_inliers / n_samples
+    nom = max(_EPSILON, 1 - probability)
+    denom = max(_EPSILON, 1 - inlier_ratio ** min_samples)
+    return np.ceil(np.log(nom) / np.log(denom))
 
 
 def ransac(data, model_class, min_samples, residual_threshold,
@@ -837,7 +834,11 @@ def ransac(data, model_class, min_samples, residual_threshold,
     # estimate model for current random sample set
     model = model_class()
 
-    for num_trials in range(max_trials):
+    num_trials = 0
+    # max_trials can be updated inside the loop, so this cannot be a for-loop
+    while num_trials < max_trials:
+        num_trials += 1
+
         # do sample selection according data pairs
         samples = [d[spl_idxs] for d in data]
 
@@ -874,13 +875,13 @@ def ransac(data, model_class, min_samples, residual_threshold,
             best_inlier_num = inliers_count
             best_inlier_residuals_sum = residuals_sum
             best_inliers = inliers
-            dynamic_max_trials = _dynamic_max_trials(best_inlier_num,
-                                                     num_samples,
-                                                     min_samples,
-                                                     stop_probability)
+            max_trials = min(max_trials,
+                             _dynamic_max_trials(best_inlier_num,
+                                                 num_samples,
+                                                 min_samples,
+                                                 stop_probability))
             if (best_inlier_num >= stop_sample_num
-                    or best_inlier_residuals_sum <= stop_residuals_sum
-                    or num_trials >= dynamic_max_trials):
+                    or best_inlier_residuals_sum <= stop_residuals_sum):
                 break
 
     # estimate final model using all 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
@@ -342,28 +342,37 @@ def test_ransac_dynamic_max_trials():
 
     # e = 0%, min_samples = X
     assert_equal(_dynamic_max_trials(100, 100, 2, 0.99), 1)
+    assert_equal(_dynamic_max_trials(100, 100, 2, 1), 1)
 
     # e = 5%, min_samples = 2
     assert_equal(_dynamic_max_trials(95, 100, 2, 0.99), 2)
+    assert_equal(_dynamic_max_trials(95, 100, 2, 1), 16)
     # e = 10%, min_samples = 2
     assert_equal(_dynamic_max_trials(90, 100, 2, 0.99), 3)
+    assert_equal(_dynamic_max_trials(90, 100, 2, 1), 22)
     # e = 30%, min_samples = 2
     assert_equal(_dynamic_max_trials(70, 100, 2, 0.99), 7)
+    assert_equal(_dynamic_max_trials(70, 100, 2, 1), 54)
     # e = 50%, min_samples = 2
     assert_equal(_dynamic_max_trials(50, 100, 2, 0.99), 17)
+    assert_equal(_dynamic_max_trials(50, 100, 2, 1), 126)
 
     # e = 5%, min_samples = 8
     assert_equal(_dynamic_max_trials(95, 100, 8, 0.99), 5)
+    assert_equal(_dynamic_max_trials(95, 100, 8, 1), 34)
     # e = 10%, min_samples = 8
     assert_equal(_dynamic_max_trials(90, 100, 8, 0.99), 9)
+    assert_equal(_dynamic_max_trials(90, 100, 8, 1), 65)
     # e = 30%, min_samples = 8
     assert_equal(_dynamic_max_trials(70, 100, 8, 0.99), 78)
+    assert_equal(_dynamic_max_trials(70, 100, 8, 1), 608)
     # e = 50%, min_samples = 8
     assert_equal(_dynamic_max_trials(50, 100, 8, 0.99), 1177)
+    assert_equal(_dynamic_max_trials(50, 100, 8, 1), 9210)
 
     # e = 0%, min_samples = 5
     assert_equal(_dynamic_max_trials(1, 100, 5, 0), 0)
-    assert_equal(_dynamic_max_trials(1, 100, 5, 1), np.inf)
+    assert_equal(_dynamic_max_trials(1, 100, 5, 1), 360436504051)
 
 
 def test_ransac_invalid_input():