From 8b8b6d0d60520a965c3b7c7ade6f72a47b8eb18d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Scho=CC=88nberger?= Date: Wed, 25 Apr 2012 20:41:10 +0200 Subject: [PATCH] added generic method to adaptive thresholding --- doc/examples/plot_thresholding.py | 9 +++++--- skimage/filter/_thresholding.pyx | 27 ++++++++++++++--------- skimage/filter/tests/test_thresholding.py | 19 +++++++++++++--- skimage/filter/thresholding.py | 24 +++++++++++++------- 4 files changed, 54 insertions(+), 25 deletions(-) diff --git a/doc/examples/plot_thresholding.py b/doc/examples/plot_thresholding.py index df2a4b85..ada0552e 100644 --- a/doc/examples/plot_thresholding.py +++ b/doc/examples/plot_thresholding.py @@ -52,17 +52,20 @@ plt.axis('off') #: Adaptive thresholding plt.subplot(2, 3, 4) -plt.imshow(threshold_adaptive(image, 11, 5, 'gaussian'), cmap=plt.cm.gray) +plt.imshow(threshold_adaptive(image, 11, method='gaussian', offset=5), + cmap=plt.cm.gray) plt.title('Adaptive edge thresholding') plt.axis('off') plt.subplot(2, 3, 5) -plt.imshow(threshold_adaptive(image, 125, 7.5, 'gaussian'), cmap=plt.cm.gray) +plt.imshow(threshold_adaptive(image, 125, method='gaussian', offset=7.5), + cmap=plt.cm.gray) plt.title('Adaptive Gaussian') plt.axis('off') plt.subplot(2, 3, 6) -plt.imshow(threshold_adaptive(image, 125, 7.5, 'mean'), cmap=plt.cm.gray) +plt.imshow(threshold_adaptive(image, 125, method='mean', offset=7.5), + cmap=plt.cm.gray) plt.title('Adaptive Mean') plt.axis('off') diff --git a/skimage/filter/_thresholding.pyx b/skimage/filter/_thresholding.pyx index 57d67e9c..02441abc 100644 --- a/skimage/filter/_thresholding.pyx +++ b/skimage/filter/_thresholding.pyx @@ -6,22 +6,27 @@ cimport cython @cython.boundscheck(False) @cython.wraparound(False) -def _threshold_adaptive(np.ndarray[np.double_t, ndim=2] image, - int block_size, double offset, method): +def _threshold_adaptive(np.ndarray[np.double_t, ndim=2] image, int block_size, + method, double offset, mode, param): cdef int r, c - cdef np.ndarray[np.float64_t, ndim=2] mean_image - if method == 'gaussian': - # covers > 99% of distribution - sigma = (block_size - 1) / 6.0 - mean_image = scipy.ndimage.gaussian_filter(image, sigma) + cdef np.ndarray[np.float64_t, ndim=2] thres_image + + if method == 'generic': + thres_image = scipy.ndimage.generic_filter(image, param, block_size, + mode=mode) + elif method == 'gaussian': + if param is None: + # automatically determine sigme which covers > 99% of distribution + sigma = (block_size - 1) / 6.0 + thres_image = scipy.ndimage.gaussian_filter(image, sigma, mode=mode) elif method == 'mean': mask = 1. / block_size**2 * np.ones((block_size, block_size)) - mean_image = scipy.ndimage.convolve(image, mask) + thres_image = scipy.ndimage.convolve(image, mask, mode=mode) elif method == 'median': - mean_image = scipy.ndimage.median_filter(image, block_size) + thres_image = scipy.ndimage.median_filter(image, block_size, mode=mode) for r in range(image.shape[0]): for c in range(image.shape[1]): - mean_image[r,c] = image[r,c] > (mean_image[r,c] - offset) + thres_image[r,c] = image[r,c] > (thres_image[r,c] - offset) - return mean_image.astype('bool') + return thres_image.astype('bool') diff --git a/skimage/filter/tests/test_thresholding.py b/skimage/filter/tests/test_thresholding.py index 4f3daf42..6177b9f5 100644 --- a/skimage/filter/tests/test_thresholding.py +++ b/skimage/filter/tests/test_thresholding.py @@ -25,6 +25,19 @@ class TestSimpleImage(): image = np.float64(self.image) assert 2 <= threshold_otsu(image) < 3 + def test_threshold_adaptive_generic(self): + def func(arr): + return arr.sum() / arr.shape[0] + ref = np.array( + [[False, False, False, False, True], + [False, False, True, False, True], + [False, False, True, True, False], + [False, True, True, False, False], + [ True, True, False, False, False]] + ) + out = threshold_adaptive(self.image, 3, method='generic', param=func) + assert_array_equal(ref, out) + def test_threshold_adaptive_gaussian(self): ref = np.array( [[False, False, False, False, True], @@ -33,7 +46,7 @@ class TestSimpleImage(): [False, True, True, False, False], [ True, True, False, False, False]] ) - out = threshold_adaptive(self.image, 3, 0, 'gaussian') + out = threshold_adaptive(self.image, 3, method='gaussian') assert_array_equal(ref, out) def test_threshold_adaptive_mean(self): @@ -44,7 +57,7 @@ class TestSimpleImage(): [False, True, True, False, False], [ True, True, False, False, False]] ) - out = threshold_adaptive(self.image, 3, 0, 'mean') + out = threshold_adaptive(self.image, 3, method='mean') assert_array_equal(ref, out) def test_threshold_adaptive_median(self): @@ -55,7 +68,7 @@ class TestSimpleImage(): [False, False, True, True, False], [False, True, False, False, False]] ) - out = threshold_adaptive(self.image, 3, 0, 'median') + out = threshold_adaptive(self.image, 3, method='median') assert_array_equal(ref, out) diff --git a/skimage/filter/thresholding.py b/skimage/filter/thresholding.py index 2a21eb02..1c34d522 100644 --- a/skimage/filter/thresholding.py +++ b/skimage/filter/thresholding.py @@ -7,12 +7,14 @@ from ._thresholding import _threshold_adaptive __all__ = ['threshold_otsu', 'threshold_adaptive'] -def threshold_adaptive(image, block_size, offset, method='gaussian'): +def threshold_adaptive(image, block_size, method='gaussian', offset=0, + mode='reflect', param=None): """Applies an adaptive threshold to an array. Also known as local or dynamic thresholding where the threshold value is the weighted mean for the local neighborhood of a pixel subtracted by a - constant. + constant. Alternatively the threshold can be determined dynamically by a + a given function using the 'generic' method. Parameters ---------- @@ -21,12 +23,18 @@ def threshold_adaptive(image, block_size, offset, method='gaussian'): block_size : int uneven size of pixel neighborhood which is used to calculate the threshold value (e.g. 3, 5, 7, ..., 21, ...) - offset : float + method : {'generic', 'gaussian', 'mean', 'median'}, optional + method used to determine adaptive threshold. By default the 'gaussian' + method is used. + offset : float, optional constant subtracted from weighted mean of neighborhood to calculate - the local threshold value - method : string, optional - thresholding type which must be one of 'gaussian', 'mean' or 'median'. - By default the 'gaussian' method is used. + the local threshold value. Default offset is 0. + mode : {‘reflect’,’constant’,’nearest’,’mirror’, ‘wrap’}, optional + The mode parameter determines how the array borders are handled, where + cval is the value when mode is equal to ‘constant’. Default is ‘reflect’ + param : {int, function}, optional + either specify sigma for 'gaussian' method or function object for + 'generic' method. Returns ------- @@ -40,7 +48,7 @@ def threshold_adaptive(image, block_size, offset, method='gaussian'): """ # not using img_as_float because offset parameter wouldn't work image = image.astype('double') - return _threshold_adaptive(image, block_size, offset, method) + return _threshold_adaptive(image, block_size, method, offset, mode, param) def threshold_otsu(image, nbins=256): """Return threshold value based on Otsu's method.