diff --git a/skimage/feature/corner.py b/skimage/feature/corner.py index e1dd519b..f18e1407 100644 --- a/skimage/feature/corner.py +++ b/skimage/feature/corner.py @@ -624,6 +624,13 @@ def corner_fast(image, n=12, threshold=0.15): def corner_subpix(image, corners, window_size=11, alpha=0.99): """Determine subpixel position of corners. + A statistical test decides whether the corner is defined as the + intersection of two edges or a single peak. Depending on the classification + result, the subpixel corner location is determined based on the local + covariance of the grey-values. If the significance level for either + statistical test is not sufficient, the corner cannot be classified, and + the output subpixel position is set to NaN. + Parameters ---------- image : ndarray @@ -633,7 +640,7 @@ def corner_subpix(image, corners, window_size=11, alpha=0.99): window_size : int, optional Search window size for subpixel estimation. alpha : float, optional - Significance level for point classification. + Significance level for corner classification. Returns ------- @@ -737,8 +744,13 @@ def corner_subpix(image, corners, window_size=11, alpha=0.99): b_edge[:] = byy_y + bxy_x, bxx_x + bxy_y # estimated positions - est_dot = np.linalg.solve(N_dot, b_dot) - est_edge = np.linalg.solve(N_edge, b_edge) + try: + est_dot = np.linalg.solve(N_dot, b_dot) + est_edge = np.linalg.solve(N_edge, b_edge) + except np.linalg.LinAlgError: + # if image is constant the system is singular + corners_subpix[i, :] = np.nan, np.nan + continue # residuals ry_dot = y - est_dot[0] @@ -759,12 +771,19 @@ def corner_subpix(image, corners, window_size=11, alpha=0.99): + winy_winy * rxx_dot) var_edge = np.sum(winy_winy * ryy_edge + 2 * winx_winy * rxy_edge \ + winx_winx * rxx_edge) - # test value (F-distributed) - t = var_edge / var_dot - # 1 for edge, -1 for dot, 0 for "not classified" - corner_class = (t < t_crit_edge) - (t > t_crit_dot) - if corner_class == - 1: + # test value (F-distributed) + if var_dot < np.spacing(1) and var_edge < np.spacing(1): + t = np.nan + elif var_dot == 0: + t = np.inf + else: + t = var_edge / var_dot + + # 1 for edge, -1 for dot, 0 for "not classified" + corner_class = int(t < t_crit_edge) - int(t > t_crit_dot) + + if corner_class == -1: corners_subpix[i, :] = y0 + est_dot[0], x0 + est_dot[1] elif corner_class == 0: corners_subpix[i, :] = np.nan, np.nan diff --git a/skimage/feature/tests/test_corner.py b/skimage/feature/tests/test_corner.py index 7d2af0df..1c39dd2d 100644 --- a/skimage/feature/tests/test_corner.py +++ b/skimage/feature/tests/test_corner.py @@ -188,7 +188,7 @@ def test_rotated_lena(): assert (np.sort(results[:, 1]) == np.sort(results_rotated[:, 0])).all() -def test_subpix(): +def test_subpix_edge(): img = np.zeros((50, 50)) img[:25, :25] = 255 img[25:, 25:] = 255 @@ -197,6 +197,25 @@ def test_subpix(): assert_array_equal(subpix[0], (24.5, 24.5)) +def test_subpix_dot(): + img = np.zeros((50, 50)) + img[25, 25] = 255 + corner = peak_local_max(corner_harris(img), num_peaks=1) + subpix = corner_subpix(img, corner) + assert_array_equal(subpix[0], (25, 25)) + + +def test_subpix_no_class(): + img = np.zeros((50, 50)) + subpix = corner_subpix(img, np.array([[25, 25]])) + assert_array_equal(subpix[0], (np.nan, np.nan)) + + img[25, 25] = 1e-10 + corner = peak_local_max(corner_harris(img), num_peaks=1) + subpix = corner_subpix(img, np.array([[25, 25]])) + assert_array_equal(subpix[0], (np.nan, np.nan)) + + def test_subpix_border(): img = np.zeros((50, 50)) img[1:25,1:25] = 255 diff --git a/skimage/filter/rank/_percentile.py b/skimage/filter/rank/_percentile.py index f8ace8a4..01dd0b49 100644 --- a/skimage/filter/rank/_percentile.py +++ b/skimage/filter/rank/_percentile.py @@ -384,10 +384,10 @@ def threshold_percentile(image, selem, out=None, mask=None, shift_x=False, p0 : float in [0, ..., 1] Set the percentile value. - out : 2-D array (same dtype as input image) + Returns + ------- + out : 2-D array (same dtype as input image) Output image. - local threshold : ndarray (same dtype as input) - The result of the local threshold. """ diff --git a/skimage/morphology/greyreconstruct.py b/skimage/morphology/greyreconstruct.py index 3fffd28e..d302c2c0 100644 --- a/skimage/morphology/greyreconstruct.py +++ b/skimage/morphology/greyreconstruct.py @@ -131,11 +131,11 @@ def reconstruction(seed, mask, method='dilation', selem=None, offset=None): if selem is None: selem = np.ones([3] * seed.ndim, dtype=bool) else: - selem = selem.copy() + selem = selem.astype(bool, copy=True) if offset is None: if not all([d % 2 == 1 for d in selem.shape]): - ValueError("Footprint dimensions must all be odd") + raise ValueError("Footprint dimensions must all be odd") offset = np.array([d // 2 for d in selem.shape]) # Cross out the center of the selem selem[[slice(d, d + 1) for d in offset]] = False diff --git a/skimage/morphology/tests/test_reconstruction.py b/skimage/morphology/tests/test_reconstruction.py index 8e40ac67..f5678c08 100644 --- a/skimage/morphology/tests/test_reconstruction.py +++ b/skimage/morphology/tests/test_reconstruction.py @@ -8,7 +8,8 @@ All rights reserved. Original author: Lee Kamentsky """ import numpy as np -from numpy.testing import assert_array_almost_equal as assert_close +from numpy.testing import (assert_array_almost_equal as assert_close, + assert_raises) from skimage.morphology.greyreconstruct import reconstruction @@ -77,6 +78,25 @@ def test_fill_hole(): assert_close(result, np.array([0, 3, 6, 4, 4, 4, 4, 4, 2, 0])) +def test_invalid_seed(): + seed = np.ones((5, 5)) + mask = np.ones((5, 5)) + assert_raises(ValueError, reconstruction, seed * 2, mask, + method='dilation') + assert_raises(ValueError, reconstruction, seed * 0.5, mask, + method='erosion') + + +def test_invalid_selem(): + seed = np.ones((5, 5)) + mask = np.ones((5, 5)) + assert_raises(ValueError, reconstruction, seed, mask, + selem=np.ones((4, 4))) + assert_raises(ValueError, reconstruction, seed, mask, + selem=np.ones((3, 4))) + reconstruction(seed, mask, selem=np.ones((3, 3))) + + if __name__ == '__main__': from numpy import testing testing.run_module_suite() diff --git a/skimage/restoration/tests/test_unwrap.py b/skimage/restoration/tests/test_unwrap.py index 3d548fc3..fca63724 100644 --- a/skimage/restoration/tests/test_unwrap.py +++ b/skimage/restoration/tests/test_unwrap.py @@ -137,5 +137,12 @@ def test_mask(): assert_array_almost_equal(image_unwrapped_3d[:, :, -1], image[i, -1]) +def test_invalid_input(): + assert_raises(ValueError, unwrap_phase, np.zeros([])) + assert_raises(ValueError, unwrap_phase, np.zeros((1, 1, 1, 1))) + assert_raises(ValueError, unwrap_phase, np.zeros((1, 1)), 3 * [False]) + assert_raises(ValueError, unwrap_phase, np.zeros((1, 1)), 'False') + + if __name__ == "__main__": run_module_suite() diff --git a/skimage/transform/_geometric.py b/skimage/transform/_geometric.py index 6eb0f70a..a134c9fa 100644 --- a/skimage/transform/_geometric.py +++ b/skimage/transform/_geometric.py @@ -620,9 +620,9 @@ class SimilarityTransform(ProjectiveTransform): @property def scale(self): - if math.cos(self.rotation) == 0: + if abs(math.cos(self.rotation)) < np.spacing(1): # sin(self.rotation) == 1 - scale = self.params[0, 1] + scale = self.params[1, 0] else: scale = self.params[0, 0] / math.cos(self.rotation) return scale diff --git a/skimage/transform/tests/test_geometric.py b/skimage/transform/tests/test_geometric.py index 2a6a5c7e..14a9305c 100644 --- a/skimage/transform/tests/test_geometric.py +++ b/skimage/transform/tests/test_geometric.py @@ -99,6 +99,17 @@ def test_similarity_init(): assert_array_almost_equal(tform.translation, translation) + # test special case for scale if rotation=90deg + scale = 0.1 + rotation = np.pi / 2 + translation = (1, 1) + tform = SimilarityTransform(scale=scale, rotation=rotation, + translation=translation) + assert_array_almost_equal(tform.scale, scale) + assert_array_almost_equal(tform.rotation, rotation) + assert_array_almost_equal(tform.translation, translation) + + def test_affine_estimation(): # exact solution tform = estimate_transform('affine', SRC[:3, :], DST[:3, :]) @@ -208,6 +219,12 @@ def test_union(): assert tform.__class__ == ProjectiveTransform +def test_union_differing_types(): + tform1 = SimilarityTransform() + tform2 = PolynomialTransform() + assert_raises(TypeError, tform1.__add__, tform2) + + def test_geometric_tform(): tform = GeometricTransform() assert_raises(NotImplementedError, tform, 0)