From 9659d26294b83b3e45767e892d5a3fc62462df56 Mon Sep 17 00:00:00 2001 From: Kevin Keraudren Date: Fri, 6 May 2016 09:17:53 +0100 Subject: [PATCH 1/6] allowing to pass a np.random.RandomState to ransac --- skimage/measure/fit.py | 30 ++++++++++++++++++++++++++++-- skimage/measure/tests/test_fit.py | 10 ++++------ 2 files changed, 32 insertions(+), 8 deletions(-) diff --git a/skimage/measure/fit.py b/skimage/measure/fit.py index 37de2721..2b186aa1 100644 --- a/skimage/measure/fit.py +++ b/skimage/measure/fit.py @@ -1,4 +1,5 @@ import math +import numbers import numpy as np from scipy import optimize from .._shared.utils import skimage_deprecation, warn @@ -687,12 +688,29 @@ def _dynamic_max_trials(n_inliers, n_samples, min_samples, probability): return 0 return int(np.ceil(nom / denom)) + + +def check_random_state(seed): + """Turn seed into a np.random.RandomState instance + If seed is None, return the RandomState singleton used by np.random. + If seed is an int, return a new RandomState instance seeded with seed. + If seed is already a RandomState instance, return it. + Otherwise raise ValueError. + """ + if seed is None or seed is np.random: + return np.random.mtrand._rand + if isinstance(seed, (numbers.Integral, np.integer)): + return np.random.RandomState(seed) + if isinstance(seed, np.random.RandomState): + return seed + raise ValueError('%r cannot be used to seed a numpy.random.RandomState' + ' instance' % seed) def ransac(data, model_class, min_samples, residual_threshold, is_data_valid=None, is_model_valid=None, max_trials=100, stop_sample_num=np.inf, stop_residuals_sum=0, - stop_probability=1): + stop_probability=1, random_state=None): """Fit a model to data with the RANSAC (random sample consensus) algorithm. RANSAC is an iterative algorithm for the robust estimation of parameters @@ -765,6 +783,12 @@ def ransac(data, model_class, min_samples, residual_threshold, where the probability (confidence) is typically set to a high value such as 0.99, and e is the current fraction of inliers w.r.t. the total number of samples. + random_state : int, RandomState instance or None, optional (default=None) + If int, random_state is the seed used by the random number generator; + If RandomState instance, random_state is the random number generator; + If None, the random number generator is the RandomState instance used + by `np.random`. + Returns ------- @@ -849,6 +873,8 @@ def ransac(data, model_class, min_samples, residual_threshold, best_inlier_num = 0 best_inlier_residuals_sum = np.inf best_inliers = None + + random_state = check_random_state(random_state) if min_samples < 0: raise ValueError("`min_samples` must be greater than zero") @@ -871,7 +897,7 @@ def ransac(data, model_class, min_samples, residual_threshold, # choose random sample set samples = [] - random_idxs = np.random.randint(0, num_samples, min_samples) + random_idxs = random_state.randint(0, num_samples, min_samples) for d in data: samples.append(d[random_idxs]) diff --git a/skimage/measure/tests/test_fit.py b/skimage/measure/tests/test_fit.py index 6bb8c7b4..9bfb975e 100644 --- a/skimage/measure/tests/test_fit.py +++ b/skimage/measure/tests/test_fit.py @@ -193,8 +193,6 @@ def test_ellipse_model_residuals(): def test_ransac_shape(): - np.random.seed(1) - # generate original data without noise model0 = CircleModel() model0.params = (10, 12, 3) @@ -208,7 +206,7 @@ def test_ransac_shape(): data0[outliers[2], :] = (-100, -10) # estimate parameters of corrupted data - model_est, inliers = ransac(data0, CircleModel, 3, 5) + model_est, inliers = ransac(data0, CircleModel, 3, 5, random_state=np.random.RandomState(1)) # test whether estimated parameters equal original parameters assert_equal(model0.params, model_est.params) @@ -217,10 +215,10 @@ def test_ransac_shape(): def test_ransac_geometric(): - np.random.seed(1) + random_state = np.random.RandomState(1) # generate original data without noise - src = 100 * np.random.random((50, 2)) + src = 100 * random_state.random_sample((50, 2)) model0 = AffineTransform(scale=(0.5, 0.3), rotation=1, translation=(10, 20)) dst = model0(src) @@ -232,7 +230,7 @@ def test_ransac_geometric(): dst[outliers[2]] = (50, 50) # estimate parameters of corrupted data - model_est, inliers = ransac((src, dst), AffineTransform, 2, 20) + model_est, inliers = ransac((src, dst), AffineTransform, 2, 20, random_state=random_state) # test whether estimated parameters equal original parameters assert_almost_equal(model0.params, model_est.params) From 4ff20b83aa5f17c7e5f58a0c9e2f02d0bddcb2b1 Mon Sep 17 00:00:00 2001 From: Kevin Keraudren Date: Sun, 8 May 2016 08:13:01 +0100 Subject: [PATCH 2/6] moving check_random_state() to skimage._shared.util --- skimage/_shared/utils.py | 18 ++++++++++++++++++ skimage/measure/fit.py | 20 +------------------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/skimage/_shared/utils.py b/skimage/_shared/utils.py index 96bc0fd7..db741814 100644 --- a/skimage/_shared/utils.py +++ b/skimage/_shared/utils.py @@ -3,6 +3,7 @@ import functools import sys import numpy as np import types +import numbers import six @@ -191,3 +192,20 @@ def copy_func(f, name=None): return types.FunctionType(six.get_function_code(f), six.get_function_globals(f), name or f.__name__, six.get_function_defaults(f), six.get_function_closure(f)) + + +def check_random_state(seed): + """Turn seed into a np.random.RandomState instance + If seed is None, return the RandomState singleton used by np.random. + If seed is an int, return a new RandomState instance seeded with seed. + If seed is already a RandomState instance, return it. + Otherwise raise ValueError. + """ + if seed is None or seed is np.random: + return np.random.mtrand._rand + if isinstance(seed, (numbers.Integral, np.integer)): + return np.random.RandomState(seed) + if isinstance(seed, np.random.RandomState): + return seed + raise ValueError('%r cannot be used to seed a numpy.random.RandomState' + ' instance' % seed) diff --git a/skimage/measure/fit.py b/skimage/measure/fit.py index 2b186aa1..7ec96a15 100644 --- a/skimage/measure/fit.py +++ b/skimage/measure/fit.py @@ -1,8 +1,7 @@ import math -import numbers import numpy as np from scipy import optimize -from .._shared.utils import skimage_deprecation, warn +from .._shared.utils import check_random_state, skimage_deprecation, warn def _check_data_dim(data, dim): @@ -688,23 +687,6 @@ def _dynamic_max_trials(n_inliers, n_samples, min_samples, probability): return 0 return int(np.ceil(nom / denom)) - - -def check_random_state(seed): - """Turn seed into a np.random.RandomState instance - If seed is None, return the RandomState singleton used by np.random. - If seed is an int, return a new RandomState instance seeded with seed. - If seed is already a RandomState instance, return it. - Otherwise raise ValueError. - """ - if seed is None or seed is np.random: - return np.random.mtrand._rand - if isinstance(seed, (numbers.Integral, np.integer)): - return np.random.RandomState(seed) - if isinstance(seed, np.random.RandomState): - return seed - raise ValueError('%r cannot be used to seed a numpy.random.RandomState' - ' instance' % seed) def ransac(data, model_class, min_samples, residual_threshold, From 9fa6548f7d6a8136101b00b9143ada1dd13d95ce Mon Sep 17 00:00:00 2001 From: Kevin Keraudren Date: Sun, 8 May 2016 08:22:00 +0100 Subject: [PATCH 3/6] added mention of sklearn.utils.validation.check_random_state as a comment --- skimage/_shared/utils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/skimage/_shared/utils.py b/skimage/_shared/utils.py index db741814..4e947a0c 100644 --- a/skimage/_shared/utils.py +++ b/skimage/_shared/utils.py @@ -201,6 +201,7 @@ def check_random_state(seed): If seed is already a RandomState instance, return it. Otherwise raise ValueError. """ + # Function originally from scikit-learn's module sklearn.utils.validation if seed is None or seed is np.random: return np.random.mtrand._rand if isinstance(seed, (numbers.Integral, np.integer)): From 307a4e9936044c5f63e49aac5a2928b46bb11558 Mon Sep 17 00:00:00 2001 From: Kevin Keraudren Date: Sun, 8 May 2016 08:30:47 +0100 Subject: [PATCH 4/6] updating tests --- skimage/measure/tests/test_fit.py | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/skimage/measure/tests/test_fit.py b/skimage/measure/tests/test_fit.py index 9bfb975e..d8e0056a 100644 --- a/skimage/measure/tests/test_fit.py +++ b/skimage/measure/tests/test_fit.py @@ -32,7 +32,8 @@ def test_line_model_estimate(): model_est.estimate(data) # test whether estimated parameters almost equal original parameters - x = np.random.rand(100, 2) + random_state = np.random.RandomState(1234) + x = random_state.rand(100, 2) assert_almost_equal(model0.predict(x), model_est.predict(x), 1) @@ -75,8 +76,8 @@ def test_line_modelND_estimate(): 10 * np.arange(-100,100)[...,np.newaxis] * model0.params[1]) # add gaussian noise to data - np.random.seed(1234) - data = data0 + np.random.normal(size=data0.shape) + random_state = np.random.RandomState(1234) + data = data0 + random_state.normal(size=data0.shape) # estimate parameters of noisy data model_est = LineModelND() @@ -130,8 +131,8 @@ def test_circle_model_estimate(): data0 = model0.predict_xy(t) # add gaussian noise to data - np.random.seed(1234) - data = data0 + np.random.normal(size=data0.shape) + random_state = np.random.RandomState(1234) + data = data0 + random_state.normal(size=data0.shape) # estimate parameters of noisy data model_est = CircleModel() @@ -172,8 +173,8 @@ def test_ellipse_model_estimate(): data0 = model0.predict_xy(t) # add gaussian noise to data - np.random.seed(1234) - data = data0 + np.random.normal(size=data0.shape) + random_state = np.random.RandomState(1234) + data = data0 + random_state.normal(size=data0.shape) # estimate parameters of noisy data model_est = EllipseModel() @@ -206,7 +207,8 @@ def test_ransac_shape(): data0[outliers[2], :] = (-100, -10) # estimate parameters of corrupted data - model_est, inliers = ransac(data0, CircleModel, 3, 5, random_state=np.random.RandomState(1)) + model_est, inliers = ransac(data0, CircleModel, 3, 5, + random_state=1) # test whether estimated parameters equal original parameters assert_equal(model0.params, model_est.params) @@ -230,7 +232,8 @@ def test_ransac_geometric(): dst[outliers[2]] = (50, 50) # estimate parameters of corrupted data - model_est, inliers = ransac((src, dst), AffineTransform, 2, 20, random_state=random_state) + model_est, inliers = ransac((src, dst), AffineTransform, 2, 20, + random_state=random_state) # test whether estimated parameters equal original parameters assert_almost_equal(model0.params, model_est.params) @@ -238,22 +241,20 @@ def test_ransac_geometric(): def test_ransac_is_data_valid(): - np.random.seed(1) is_data_valid = lambda data: data.shape[0] > 2 model, inliers = ransac(np.empty((10, 2)), LineModelND, 2, np.inf, - is_data_valid=is_data_valid) + is_data_valid=is_data_valid, random_state=1) assert_equal(model, None) assert_equal(inliers, None) def test_ransac_is_model_valid(): - np.random.seed(1) def is_model_valid(model, data): return False model, inliers = ransac(np.empty((10, 2)), LineModelND, 2, np.inf, - is_model_valid=is_model_valid) + is_model_valid=is_model_valid, random_state=1) assert_equal(model, None) assert_equal(inliers, None) From 6f4cbebd6d81cc2aa69dfddf354a01a8b5969a54 Mon Sep 17 00:00:00 2001 From: Kevin Keraudren Date: Tue, 10 May 2016 19:31:51 +0100 Subject: [PATCH 5/6] remove default value in docstring --- skimage/measure/fit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skimage/measure/fit.py b/skimage/measure/fit.py index 7ec96a15..660b86f6 100644 --- a/skimage/measure/fit.py +++ b/skimage/measure/fit.py @@ -765,7 +765,7 @@ def ransac(data, model_class, min_samples, residual_threshold, where the probability (confidence) is typically set to a high value such as 0.99, and e is the current fraction of inliers w.r.t. the total number of samples. - random_state : int, RandomState instance or None, optional (default=None) + random_state : int, RandomState instance or None, optional If int, random_state is the seed used by the random number generator; If RandomState instance, random_state is the random number generator; If None, the random number generator is the RandomState instance used From 45b450d8bfa391ae17fd504e75884de4edcfb1ca Mon Sep 17 00:00:00 2001 From: Kevin Keraudren Date: Tue, 10 May 2016 19:44:50 +0100 Subject: [PATCH 6/6] reformat docstring --- skimage/_shared/utils.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/skimage/_shared/utils.py b/skimage/_shared/utils.py index 4e947a0c..20032f39 100644 --- a/skimage/_shared/utils.py +++ b/skimage/_shared/utils.py @@ -195,11 +195,20 @@ def copy_func(f, name=None): def check_random_state(seed): - """Turn seed into a np.random.RandomState instance - If seed is None, return the RandomState singleton used by np.random. - If seed is an int, return a new RandomState instance seeded with seed. - If seed is already a RandomState instance, return it. - Otherwise raise ValueError. + """Turn seed into a `np.random.RandomState` instance. + + Parameters + ---------- + seed : None, int or np.random.RandomState + If `seed` is None, return the RandomState singleton used by `np.random`. + If `seed` is an int, return a new RandomState instance seeded with `seed`. + If `seed` is already a RandomState instance, return it. + + Raises + ------ + ValueError + If `seed` is of the wrong type. + """ # Function originally from scikit-learn's module sklearn.utils.validation if seed is None or seed is np.random: