From fb74a9d8ae75dc75e979c6e4b36f17c3cbafd489 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Wed, 1 May 2013 13:59:01 +0200 Subject: [PATCH] Add new stop criteria to ransac and improve doc string --- skimage/measure/fit.py | 44 ++++++++++++++++++++++++++++----- skimage/transform/_geometric.py | 2 +- 2 files changed, 39 insertions(+), 7 deletions(-) diff --git a/skimage/measure/fit.py b/skimage/measure/fit.py index ecd0f6d7..ac55534e 100644 --- a/skimage/measure/fit.py +++ b/skimage/measure/fit.py @@ -502,9 +502,28 @@ class EllipseModel(BaseModel): def ransac(data, model_class, min_samples, residual_threshold, - max_trials=100): + max_trials=100, stop_sample_num=np.inf, stop_residuals_sum=0): """Fits a model to data with the RANSAC (random sample consensus) algorithm. + RANSAC is an iterative algorithm for the robust estimation of parameters + from a subset of inliers from the complete data set. Each iteration performs + the following tasks: + + 1. Select `min_samples` random samples from the original data. + 2. Estimate a model to the random subset (`estimate(*data[random_subset]`). + 3. Classify all data as inliers or outliers by calculating the residuals to + the estimated model (`residuals(*data)`) - all data samples with + residuals smaller than the `residual_threshold` are considered as + inliers. + 4. Save estimated model as best model if number of inlier samples is + maximal. In case the current estimated model has the same number of + inliers, it is only considered as the best model if it has less sum of + residuals. + + These steps are performed either a maximum number of times or until one of + the special stop criteria are met. The final model is estimated using all + inlier samples of the previously determined best model. + Parameters ---------- data : [list, tuple of] (N, D) array @@ -512,13 +531,16 @@ def ransac(data, model_class, min_samples, residual_threshold, points and D the dimensionality of the data. If the model class requires multiple input data arrays (e.g. source and destination coordinates of ``skimage.transform.AffineTransform``), they - can be optionally passed as tuple or list. + can be optionally passed as tuple or list. Note, that in this case the + functions `estimate(*data)`, `residuals(*data)` and + `is_degenerate(*data)` must all take each data array as separate + arguments. model_class : object Object with the following methods implemented: - * `estimate(data)` - * `residuals(data)` - * `is_degenerate(data)` + * `estimate(*data)` + * `residuals(*data)` + * `is_degenerate(*data)` min_samples : int The minimum number of data points to fit a model. @@ -526,6 +548,10 @@ def ransac(data, model_class, min_samples, residual_threshold, Maximum distance for a data point to be classified as an inlier. max_trials : int, optional Maximum number of iterations for random sample selection. + stop_sample_num : int, optional + Stop iteration if at least this number of inliers are found. + stop_residuals_sum : float, optional + Stop iteration if sum of residuals is less equal than this threshold. Returns ------- @@ -613,7 +639,7 @@ def ransac(data, model_class, min_samples, residual_threshold, # consensus set / inliers sample_model_inliers = data_idxs[sample_model_residuals < residual_threshold] - sample_model_residuals_sum = np.sum(sample_model_residuals) + sample_model_residuals_sum = np.sum(sample_model_residuals**2) # choose as new best model if number of inliers is maximal sample_inlier_num = sample_model_inliers.shape[0] @@ -628,9 +654,15 @@ def ransac(data, model_class, min_samples, residual_threshold, best_inlier_num = sample_inlier_num best_inlier_residuals_sum = sample_model_residuals_sum best_inliers = sample_model_inliers + if ( + best_inlier_num >= stop_sample_num + or best_inlier_residuals_sum <= stop_residuals_sum + ): + break # estimate final model using all inliers if best_inliers is not None: + # select inliers for each data array for i in range(len(data)): data[i] = data[i][best_inliers] best_model.estimate(*data) diff --git a/skimage/transform/_geometric.py b/skimage/transform/_geometric.py index 12983e6d..54b0cb61 100644 --- a/skimage/transform/_geometric.py +++ b/skimage/transform/_geometric.py @@ -83,7 +83,7 @@ class GeometricTransform(object): """ - return np.sqrt(np.sum((self(src) - dst) ** 2, axis=1)) + return np.sqrt(np.sum((self(src) - dst)**2, axis=1)) def __add__(self, other): """Combine this transformation with another.