From 238d4eb4adc2881f043791394a1e1285085fed45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Scho=CC=88nberger?= Date: Tue, 24 Apr 2012 14:13:08 +0200 Subject: [PATCH 01/19] added adaptive threshold --- CONTRIBUTORS.txt | 1 + doc/examples/plot_otsu.py | 45 ------------------ doc/examples/plot_thresholding.py | 56 +++++++++++++++++++++++ skimage/filter/__init__.py | 2 +- skimage/filter/_thresholding.pyx | 28 ++++++++++++ skimage/filter/setup.py | 3 ++ skimage/filter/tests/test_thresholding.py | 25 +++++++++- skimage/filter/thresholding.py | 39 +++++++++++++++- 8 files changed, 150 insertions(+), 49 deletions(-) delete mode 100644 doc/examples/plot_otsu.py create mode 100644 doc/examples/plot_thresholding.py create mode 100644 skimage/filter/_thresholding.pyx diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 8258eada..a7a7e83e 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -101,3 +101,4 @@ - Johannes Schönberger Polygon, circle and ellipse drawing functions + Adaptive thresholding diff --git a/doc/examples/plot_otsu.py b/doc/examples/plot_otsu.py deleted file mode 100644 index f2335fc0..00000000 --- a/doc/examples/plot_otsu.py +++ /dev/null @@ -1,45 +0,0 @@ -""" -============ -Thresholding -============ - -Thresholding is used to create a binary image. This example uses Otsu's method -to calculate the threshold value. - -Otsu's method calculates an "optimal" threshold (marked by a red line in the -histogram below) by maximizing the variance between two classes of pixels, -which are separated by the threshold. Equivalently, this threshold minimizes -the intra-class variance. - -.. [1] http://en.wikipedia.org/wiki/Otsu's_method - -""" - -import matplotlib.pyplot as plt - -from skimage.data import camera -from skimage.filter import threshold_otsu - - -image = camera() -thresh = threshold_otsu(image) -binary = image > thresh - -plt.figure(figsize=(8, 2.5)) -plt.subplot(1, 3, 1) -plt.imshow(image, cmap=plt.cm.gray) -plt.title('Original') -plt.axis('off') - -plt.subplot(1, 3, 2, aspect='equal') -plt.hist(image) -plt.title('Histogram') -plt.axvline(thresh, color='r') - -plt.subplot(1, 3, 3) -plt.imshow(binary, cmap=plt.cm.gray) -plt.title('Thresholded') -plt.axis('off') - -plt.show() - diff --git a/doc/examples/plot_thresholding.py b/doc/examples/plot_thresholding.py new file mode 100644 index 00000000..e860f243 --- /dev/null +++ b/doc/examples/plot_thresholding.py @@ -0,0 +1,56 @@ +""" +============ +Thresholding +============ + +Thresholding is used to create a binary image. + +This example uses Otsu's method to calculate the threshold value. Otsu's method +calculates an "optimal" threshold (marked by a red line in the histogram below) +by maximizing the variance between two classes of pixels, which are separated by +the threshold. Equivalently, this threshold minimizes the intra-class variance. + +Additionnally an adaptive thresholding is applied. Also known as local or +dynamic thresholding where the the threshold value is the weighted mean for the +local neighborhood of a pixel subtracted by a constant. + +.. [1] http://en.wikipedia.org/wiki/Otsu's_method + +""" + +import matplotlib.pyplot as plt +import numpy as np + +from skimage.data import camera +from skimage.filter import threshold_otsu, adaptive_threshold + + +image = camera() +thresh = threshold_otsu(image) +otsu_binary = image > thresh +adaptive_binary = np.invert(adaptive_threshold(image, 9, 5)) + +plt.figure(figsize=(8, 2.5)) +plt.subplot(2, 2, 1) +plt.imshow(image, cmap=plt.cm.gray) +plt.title('Original') +plt.axis('off') + +plt.subplot(2, 2, 2, aspect='equal') +plt.hist(image) +plt.title('Histogram') +plt.axvline(thresh, color='r') + +plt.subplot(2, 2, 3) +plt.imshow(otsu_binary, cmap=plt.cm.gray) +plt.title('Thresholded with Otsu') +plt.axis('off') + +plt.subplot(2, 2, 4) +plt.imshow(adaptive_binary, cmap=plt.cm.gray) +plt.title('Adaptively thresholded') +plt.axis('off') + +plt.show() + + diff --git a/skimage/filter/__init__.py b/skimage/filter/__init__.py index 748487ac..8a9cffaa 100644 --- a/skimage/filter/__init__.py +++ b/skimage/filter/__init__.py @@ -4,4 +4,4 @@ from .canny import canny from .edges import sobel, hsobel, vsobel, hprewitt, vprewitt, prewitt from .tv_denoise import tv_denoise from .rank_order import rank_order -from .thresholding import threshold_otsu +from .thresholding import threshold_otsu, adaptive_threshold diff --git a/skimage/filter/_thresholding.pyx b/skimage/filter/_thresholding.pyx new file mode 100644 index 00000000..303c1a5f --- /dev/null +++ b/skimage/filter/_thresholding.pyx @@ -0,0 +1,28 @@ +import numpy as np +import scipy.ndimage +cimport numpy as np +cimport cython + + +@cython.boundscheck(False) +@cython.wraparound(False) +def _adaptive_threshold( + np.ndarray[np.double_t, ndim=2] image, + int block_size, + double offset, + method +): + 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) + elif method == 'mean': + mean_image = scipy.ndimage.median_filter(image, block_size) + + 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) + + return mean_image.astype('bool') diff --git a/skimage/filter/setup.py b/skimage/filter/setup.py index 12cb84a7..f93f7143 100644 --- a/skimage/filter/setup.py +++ b/skimage/filter/setup.py @@ -12,9 +12,12 @@ def configuration(parent_package='', top_path=None): config.add_data_dir('tests') cython(['_ctmf.pyx'], working_path=base_path) + cython(['_thresholding.pyx'], working_path=base_path) config.add_extension('_ctmf', sources=['_ctmf.c'], include_dirs=[get_numpy_include_dirs()]) + config.add_extension('_thresholding', sources=['_thresholding.c'], + include_dirs=[get_numpy_include_dirs()]) return config diff --git a/skimage/filter/tests/test_thresholding.py b/skimage/filter/tests/test_thresholding.py index b0044a47..64eb48cc 100644 --- a/skimage/filter/tests/test_thresholding.py +++ b/skimage/filter/tests/test_thresholding.py @@ -1,8 +1,9 @@ import numpy as np +from numpy.testing import assert_array_equal import skimage from skimage import data -from skimage.filter.thresholding import threshold_otsu +from skimage.filter.thresholding import threshold_otsu, adaptive_threshold class TestSimpleImage(): @@ -24,6 +25,28 @@ class TestSimpleImage(): image = np.float64(self.image) assert 2 <= threshold_otsu(image) < 3 + def test_adaptive_threshold_gaussian(self): + 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 = adaptive_threshold(self.image, 3, 0, 'gaussian') + assert_array_equal(ref, out) + + def test_adaptive_threshold_mean(self): + ref = np.array( + [[False, False, False, False, True], + [False, False, True, False, False], + [False, False, True, False, False], + [False, False, True, True, False], + [False, True, False, False, False]] + ) + out = adaptive_threshold(self.image, 3, 0, 'mean') + assert_array_equal(ref, out) + def test_otsu_camera_image(): assert threshold_otsu(data.camera()) == 87 diff --git a/skimage/filter/thresholding.py b/skimage/filter/thresholding.py index 856bd78d..931aeaa2 100644 --- a/skimage/filter/thresholding.py +++ b/skimage/filter/thresholding.py @@ -1,11 +1,47 @@ import numpy as np from skimage.exposure import histogram +from ._thresholding import _adaptive_threshold -__all__ = ['threshold_otsu'] +__all__ = ['threshold_otsu', 'adaptive_threshold'] +def adaptive_threshold(image, block_size, offset, method='gaussian'): + """Applies an adaptive threshold to an array. + + Also known as local or dynamic thresholding where the the threshold value is + the weighted mean for the local neighborhood of a pixel subtracted by a + constant. + + Parameters + ---------- + image : NxM ndarray + Input image. + block_size : int + uneven size of pixel neighborhood which is used to calculate the + threshold value (e.g. 3, 5, 7, ..., 21, ...) + offset : float + constant subtracted from weighted mean of neighborhood to calculate + the local threshold value + method : string, optional + thresholding type which must be one of `gaussian` or `mean`. + By default the `gaussian` method is used. + + Returns + ------- + threshold : NxM ndarray + thresholded binary image + + References + ---------- + http://docs.opencv.org/modules/imgproc/doc/miscellaneous_transformations + .html?highlight=threshold#adaptivethreshold + """ + # not using img_as_float because threshold parameter wouldn't work + image = image.astype('double') + return _adaptive_threshold(image, block_size, offset, method) + def threshold_otsu(image, nbins=256): """Return threshold value based on Otsu's method. @@ -51,4 +87,3 @@ def threshold_otsu(image, nbins=256): idx = np.argmax(variance12) threshold = bin_centers[:-1][idx] return threshold - From d81650f9929af7bbd59a8eca9d27b58cc54c7453 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Scho=CC=88nberger?= Date: Tue, 24 Apr 2012 20:45:14 +0200 Subject: [PATCH 02/19] fixed some typos --- doc/examples/plot_thresholding.py | 4 ++-- skimage/filter/thresholding.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/examples/plot_thresholding.py b/doc/examples/plot_thresholding.py index e860f243..a0f7eb08 100644 --- a/doc/examples/plot_thresholding.py +++ b/doc/examples/plot_thresholding.py @@ -10,8 +10,8 @@ calculates an "optimal" threshold (marked by a red line in the histogram below) by maximizing the variance between two classes of pixels, which are separated by the threshold. Equivalently, this threshold minimizes the intra-class variance. -Additionnally an adaptive thresholding is applied. Also known as local or -dynamic thresholding where the the threshold value is the weighted mean for the +Additionally an adaptive thresholding is applied. 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. .. [1] http://en.wikipedia.org/wiki/Otsu's_method diff --git a/skimage/filter/thresholding.py b/skimage/filter/thresholding.py index 931aeaa2..d0d95a4a 100644 --- a/skimage/filter/thresholding.py +++ b/skimage/filter/thresholding.py @@ -10,8 +10,8 @@ __all__ = ['threshold_otsu', 'adaptive_threshold'] def adaptive_threshold(image, block_size, offset, method='gaussian'): """Applies an adaptive threshold to an array. - Also known as local or dynamic thresholding where the the threshold value is - the weighted mean for the local neighborhood of a pixel subtracted by a + 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. Parameters From d0d71427af3c7d0256d13f0d5f0baa967a2ca357 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Scho=CC=88nberger?= Date: Tue, 24 Apr 2012 20:53:26 +0200 Subject: [PATCH 03/19] added mean method to adaptive thresholding --- skimage/filter/_thresholding.pyx | 3 +++ skimage/filter/thresholding.py | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/skimage/filter/_thresholding.pyx b/skimage/filter/_thresholding.pyx index 303c1a5f..88badf31 100644 --- a/skimage/filter/_thresholding.pyx +++ b/skimage/filter/_thresholding.pyx @@ -19,6 +19,9 @@ def _adaptive_threshold( sigma = (block_size - 1) / 6.0 mean_image = scipy.ndimage.gaussian_filter(image, sigma) elif method == 'mean': + mask = 1. / block_size**2 * np.ones((block_size, block_size)) + mean_image = scipy.ndimage.convolve(image, mask) + elif method == 'median': mean_image = scipy.ndimage.median_filter(image, block_size) for r in range(image.shape[0]): diff --git a/skimage/filter/thresholding.py b/skimage/filter/thresholding.py index d0d95a4a..09e6eaea 100644 --- a/skimage/filter/thresholding.py +++ b/skimage/filter/thresholding.py @@ -25,8 +25,8 @@ def adaptive_threshold(image, block_size, offset, method='gaussian'): constant subtracted from weighted mean of neighborhood to calculate the local threshold value method : string, optional - thresholding type which must be one of `gaussian` or `mean`. - By default the `gaussian` method is used. + thresholding type which must be one of 'gaussian', 'mean' or 'median'. + By default the 'gaussian' method is used. Returns ------- From aca8522ac4211804242a5a0a085dd11c8edb31b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Scho=CC=88nberger?= Date: Tue, 24 Apr 2012 21:02:57 +0200 Subject: [PATCH 04/19] renamed adaptive_threshold to threshold_adaptive --- skimage/filter/__init__.py | 2 +- skimage/filter/_thresholding.pyx | 8 ++------ skimage/filter/thresholding.py | 8 ++++---- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/skimage/filter/__init__.py b/skimage/filter/__init__.py index 8a9cffaa..04c972cc 100644 --- a/skimage/filter/__init__.py +++ b/skimage/filter/__init__.py @@ -4,4 +4,4 @@ from .canny import canny from .edges import sobel, hsobel, vsobel, hprewitt, vprewitt, prewitt from .tv_denoise import tv_denoise from .rank_order import rank_order -from .thresholding import threshold_otsu, adaptive_threshold +from .thresholding import threshold_otsu, threshold_adaptive diff --git a/skimage/filter/_thresholding.pyx b/skimage/filter/_thresholding.pyx index 88badf31..57d67e9c 100644 --- a/skimage/filter/_thresholding.pyx +++ b/skimage/filter/_thresholding.pyx @@ -6,12 +6,8 @@ cimport cython @cython.boundscheck(False) @cython.wraparound(False) -def _adaptive_threshold( - 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, double offset, method): cdef int r, c cdef np.ndarray[np.float64_t, ndim=2] mean_image if method == 'gaussian': diff --git a/skimage/filter/thresholding.py b/skimage/filter/thresholding.py index 09e6eaea..68ba1482 100644 --- a/skimage/filter/thresholding.py +++ b/skimage/filter/thresholding.py @@ -1,13 +1,13 @@ import numpy as np from skimage.exposure import histogram -from ._thresholding import _adaptive_threshold +from ._thresholding import _threshold_adaptive -__all__ = ['threshold_otsu', 'adaptive_threshold'] +__all__ = ['threshold_otsu', 'threshold_adaptive'] -def adaptive_threshold(image, block_size, offset, method='gaussian'): +def threshold_adaptive(image, block_size, offset, method='gaussian'): """Applies an adaptive threshold to an array. Also known as local or dynamic thresholding where the threshold value is the @@ -40,7 +40,7 @@ def adaptive_threshold(image, block_size, offset, method='gaussian'): """ # not using img_as_float because threshold parameter wouldn't work image = image.astype('double') - return _adaptive_threshold(image, block_size, offset, method) + return _threshold_adaptive(image, block_size, offset, method) def threshold_otsu(image, nbins=256): """Return threshold value based on Otsu's method. From 90f840941208bf6eda5ab4b6c7616d1ae63f6d49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Scho=CC=88nberger?= Date: Tue, 24 Apr 2012 21:22:51 +0200 Subject: [PATCH 05/19] updated thresholding example script with more examples for adaptive thresholding --- doc/examples/plot_thresholding.py | 38 +++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/doc/examples/plot_thresholding.py b/doc/examples/plot_thresholding.py index a0f7eb08..4def3435 100644 --- a/doc/examples/plot_thresholding.py +++ b/doc/examples/plot_thresholding.py @@ -12,7 +12,9 @@ the threshold. Equivalently, this threshold minimizes the intra-class variance. Additionally an adaptive thresholding is applied. 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. +local neighborhood of a pixel subtracted by a constant. Small filter block sizes +are suitable for thresholding edges, large filter block sizes suitable for +thresholding larger homogeneous regions. .. [1] http://en.wikipedia.org/wiki/Otsu's_method @@ -22,35 +24,47 @@ import matplotlib.pyplot as plt import numpy as np from skimage.data import camera -from skimage.filter import threshold_otsu, adaptive_threshold +from skimage.filter import threshold_otsu, threshold_adaptive image = camera() + + +#: Otsu thresholding thresh = threshold_otsu(image) otsu_binary = image > thresh -adaptive_binary = np.invert(adaptive_threshold(image, 9, 5)) -plt.figure(figsize=(8, 2.5)) -plt.subplot(2, 2, 1) +plt.figure(figsize=(8, 6)) +plt.subplot(2, 3, 1) plt.imshow(image, cmap=plt.cm.gray) plt.title('Original') plt.axis('off') -plt.subplot(2, 2, 2, aspect='equal') +plt.subplot(2, 3, 2, aspect='equal') plt.hist(image) plt.title('Histogram') plt.axvline(thresh, color='r') -plt.subplot(2, 2, 3) +plt.subplot(2, 3, 3) plt.imshow(otsu_binary, cmap=plt.cm.gray) plt.title('Thresholded with Otsu') plt.axis('off') -plt.subplot(2, 2, 4) -plt.imshow(adaptive_binary, cmap=plt.cm.gray) -plt.title('Adaptively thresholded') + +#: Adaptive thresholding +plt.subplot(2, 3, 4) +plt.imshow(threshold_adaptive(image, 11, 5, 'gaussian'), 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.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.title('Adaptive Mean') plt.axis('off') plt.show() - - From 243a5ec1c6ec0611dd50a9161ba0aaa11a33a122 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Scho=CC=88nberger?= Date: Wed, 25 Apr 2012 18:10:36 +0200 Subject: [PATCH 06/19] clarified comment --- skimage/filter/thresholding.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skimage/filter/thresholding.py b/skimage/filter/thresholding.py index 68ba1482..2a21eb02 100644 --- a/skimage/filter/thresholding.py +++ b/skimage/filter/thresholding.py @@ -38,7 +38,7 @@ def threshold_adaptive(image, block_size, offset, method='gaussian'): http://docs.opencv.org/modules/imgproc/doc/miscellaneous_transformations .html?highlight=threshold#adaptivethreshold """ - # not using img_as_float because threshold parameter wouldn't work + # not using img_as_float because offset parameter wouldn't work image = image.astype('double') return _threshold_adaptive(image, block_size, offset, method) From 8c6b7910ccec6293a3d1ec737c2a875b4420cd82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Scho=CC=88nberger?= Date: Wed, 25 Apr 2012 18:13:16 +0200 Subject: [PATCH 07/19] removed unused import --- doc/examples/plot_thresholding.py | 1 - 1 file changed, 1 deletion(-) diff --git a/doc/examples/plot_thresholding.py b/doc/examples/plot_thresholding.py index 4def3435..df2a4b85 100644 --- a/doc/examples/plot_thresholding.py +++ b/doc/examples/plot_thresholding.py @@ -21,7 +21,6 @@ thresholding larger homogeneous regions. """ import matplotlib.pyplot as plt -import numpy as np from skimage.data import camera from skimage.filter import threshold_otsu, threshold_adaptive From f8e3ba42ef2587c1d1b633c9109df2f456c00825 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Scho=CC=88nberger?= Date: Wed, 25 Apr 2012 18:15:19 +0200 Subject: [PATCH 08/19] adapted new function name for adaptive threshold in test cases --- skimage/filter/tests/test_thresholding.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/skimage/filter/tests/test_thresholding.py b/skimage/filter/tests/test_thresholding.py index 64eb48cc..ec95e677 100644 --- a/skimage/filter/tests/test_thresholding.py +++ b/skimage/filter/tests/test_thresholding.py @@ -3,7 +3,7 @@ from numpy.testing import assert_array_equal import skimage from skimage import data -from skimage.filter.thresholding import threshold_otsu, adaptive_threshold +from skimage.filter.thresholding import threshold_otsu, threshold_adaptive class TestSimpleImage(): @@ -25,7 +25,7 @@ class TestSimpleImage(): image = np.float64(self.image) assert 2 <= threshold_otsu(image) < 3 - def test_adaptive_threshold_gaussian(self): + def test_threshold_adaptive_gaussian(self): ref = np.array( [[False, False, False, False, True], [False, False, True, False, True], @@ -33,10 +33,10 @@ class TestSimpleImage(): [False, True, True, False, False], [ True, True, False, False, False]] ) - out = adaptive_threshold(self.image, 3, 0, 'gaussian') + out = threshold_adaptive(self.image, 3, 0, 'gaussian') assert_array_equal(ref, out) - def test_adaptive_threshold_mean(self): + def test_threshold_adaptive_mean(self): ref = np.array( [[False, False, False, False, True], [False, False, True, False, False], @@ -44,7 +44,7 @@ class TestSimpleImage(): [False, False, True, True, False], [False, True, False, False, False]] ) - out = adaptive_threshold(self.image, 3, 0, 'mean') + out = threshold_adaptive(self.image, 3, 0, 'mean') assert_array_equal(ref, out) From bacde61e39526f080373fe8cf85add29c2a0ed13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Scho=CC=88nberger?= Date: Wed, 25 Apr 2012 18:25:19 +0200 Subject: [PATCH 09/19] updated test cases for adaptive thresholding --- skimage/filter/tests/test_thresholding.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/skimage/filter/tests/test_thresholding.py b/skimage/filter/tests/test_thresholding.py index ec95e677..4f3daf42 100644 --- a/skimage/filter/tests/test_thresholding.py +++ b/skimage/filter/tests/test_thresholding.py @@ -37,6 +37,17 @@ class TestSimpleImage(): assert_array_equal(ref, out) def test_threshold_adaptive_mean(self): + 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, 0, 'mean') + assert_array_equal(ref, out) + + def test_threshold_adaptive_median(self): ref = np.array( [[False, False, False, False, True], [False, False, True, False, False], @@ -44,7 +55,7 @@ class TestSimpleImage(): [False, False, True, True, False], [False, True, False, False, False]] ) - out = threshold_adaptive(self.image, 3, 0, 'mean') + out = threshold_adaptive(self.image, 3, 0, 'median') assert_array_equal(ref, out) 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 10/19] 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. From 4ad79cb73efb4ea8f2e9a9112adfdee2746c1303 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Scho=CC=88nberger?= Date: Wed, 25 Apr 2012 21:37:40 +0200 Subject: [PATCH 11/19] replaced unicode characters in doc string --- skimage/filter/thresholding.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/skimage/filter/thresholding.py b/skimage/filter/thresholding.py index 1c34d522..b26fcf0d 100644 --- a/skimage/filter/thresholding.py +++ b/skimage/filter/thresholding.py @@ -29,9 +29,9 @@ def threshold_adaptive(image, block_size, method='gaussian', offset=0, offset : float, optional constant subtracted from weighted mean of neighborhood to calculate the local threshold value. Default offset is 0. - mode : {‘reflect’,’constant’,’nearest’,’mirror’, ‘wrap’}, optional + 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’ + 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. From 829f6ad770e7b3fb63111c8f2043157753823bfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Scho=CC=88nberger?= Date: Thu, 26 Apr 2012 20:28:14 +0200 Subject: [PATCH 12/19] separated filter masks of mean filtering in adaptive thresholding --- skimage/filter/_thresholding.pyx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/skimage/filter/_thresholding.pyx b/skimage/filter/_thresholding.pyx index 02441abc..772aef78 100644 --- a/skimage/filter/_thresholding.pyx +++ b/skimage/filter/_thresholding.pyx @@ -20,8 +20,11 @@ def _threshold_adaptive(np.ndarray[np.double_t, ndim=2] image, int block_size, 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)) - thres_image = scipy.ndimage.convolve(image, mask, mode=mode) + mask = 1. / block_size * np.ones((block_size,)) + # separation of filters to speedup convolution + thres_image = scipy.ndimage.convolve1d(image, mask, axis=0, mode=mode) + thres_image = scipy.ndimage.convolve1d(thres_image, mask, axis=1, + mode=mode) elif method == 'median': thres_image = scipy.ndimage.median_filter(image, block_size, mode=mode) From 59376a7c200e8ef788861e07f8e894c4ff4cf09c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Scho=CC=88nberger?= Date: Fri, 27 Apr 2012 10:00:10 +0200 Subject: [PATCH 13/19] improved doc string of adaptive threshold with more detailed description and example --- skimage/filter/_thresholding.pyx | 4 ++-- skimage/filter/thresholding.py | 37 +++++++++++++++++++++++--------- 2 files changed, 29 insertions(+), 12 deletions(-) diff --git a/skimage/filter/_thresholding.pyx b/skimage/filter/_thresholding.pyx index 772aef78..498580da 100644 --- a/skimage/filter/_thresholding.pyx +++ b/skimage/filter/_thresholding.pyx @@ -9,14 +9,14 @@ cimport cython 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] thres_image + cdef np.ndarray[np.double_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 + # automatically determine sigma which covers > 99% of distribution sigma = (block_size - 1) / 6.0 thres_image = scipy.ndimage.gaussian_filter(image, sigma, mode=mode) elif method == 'mean': diff --git a/skimage/filter/thresholding.py b/skimage/filter/thresholding.py index b26fcf0d..77c6bf9d 100644 --- a/skimage/filter/thresholding.py +++ b/skimage/filter/thresholding.py @@ -21,30 +21,47 @@ def threshold_adaptive(image, block_size, method='gaussian', offset=0, image : NxM ndarray Input image. block_size : int - uneven size of pixel neighborhood which is used to calculate the - threshold value (e.g. 3, 5, 7, ..., 21, ...) + Uneven size of pixel neighborhood which is used to calculate the + threshold value (e.g. 3, 5, 7, ..., 21, ...). method : {'generic', 'gaussian', 'mean', 'median'}, optional - method used to determine adaptive threshold. By default the 'gaussian' - method is used. + Method used to determine adaptive threshold fpr local neighbourhood in + weighted mean image. + * 'generic': use custom function (see `param` parameter) + * 'gaussian': apply gaussian filter (see `param` parameter for custom + sigma value) + * 'mean': apply arithmetic mean filter + * 'median' apply median rank filter + By default the 'gaussian' method is used. offset : float, optional - constant subtracted from weighted mean of neighborhood to calculate + Constant subtracted from weighted mean of neighborhood to calculate the local threshold value. Default offset is 0. - mode : {'reflect','constant','nearest','mirror', 'wrap'}, optional + 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' + 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. + Either specify sigma for 'gaussian' method or function object for + 'generic' method. This functions takes the flat array of local + neighbourhood as a single argument and returns the calculated threshold + for the centre pixel. Returns ------- threshold : NxM ndarray - thresholded binary image + Thresholded binary image References ---------- http://docs.opencv.org/modules/imgproc/doc/miscellaneous_transformations .html?highlight=threshold#adaptivethreshold + + Examples + -------- + >>> from skimage.data import camera + >>> image = camera() + >>> binary_image1 = threshold_adaptive(image, 15, 'mean') + >>> func = lambda arr: arr.mean() + >>> binary_image2 = threshold_adaptive(image, 15, 'generic', param=func) """ # not using img_as_float because offset parameter wouldn't work image = image.astype('double') From 13c716f5eb00f81ac53553ad45d6c8af534f6078 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Scho=CC=88nberger?= Date: Fri, 27 Apr 2012 10:06:36 +0200 Subject: [PATCH 14/19] changed name of array variable --- skimage/filter/_thresholding.pyx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/skimage/filter/_thresholding.pyx b/skimage/filter/_thresholding.pyx index 498580da..c7a80478 100644 --- a/skimage/filter/_thresholding.pyx +++ b/skimage/filter/_thresholding.pyx @@ -9,27 +9,27 @@ cimport cython 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.double_t, ndim=2] thres_image + cdef np.ndarray[np.double_t, ndim=2] thresh_image if method == 'generic': - thres_image = scipy.ndimage.generic_filter(image, param, block_size, + thresh_image = scipy.ndimage.generic_filter(image, param, block_size, mode=mode) elif method == 'gaussian': if param is None: # automatically determine sigma which covers > 99% of distribution sigma = (block_size - 1) / 6.0 - thres_image = scipy.ndimage.gaussian_filter(image, sigma, mode=mode) + thresh_image = scipy.ndimage.gaussian_filter(image, sigma, mode=mode) elif method == 'mean': mask = 1. / block_size * np.ones((block_size,)) # separation of filters to speedup convolution - thres_image = scipy.ndimage.convolve1d(image, mask, axis=0, mode=mode) - thres_image = scipy.ndimage.convolve1d(thres_image, mask, axis=1, + thresh_image = scipy.ndimage.convolve1d(image, mask, axis=0, mode=mode) + thresh_image = scipy.ndimage.convolve1d(thresh_image, mask, axis=1, mode=mode) elif method == 'median': - thres_image = scipy.ndimage.median_filter(image, block_size, mode=mode) + thresh_image = scipy.ndimage.median_filter(image, block_size, mode=mode) for r in range(image.shape[0]): for c in range(image.shape[1]): - thres_image[r,c] = image[r,c] > (thres_image[r,c] - offset) + thresh_image[r,c] = image[r,c] > (thresh_image[r,c] - offset) - return thres_image.astype('bool') + return thresh_image.astype('bool') From 6b8c40848bc53516019558cd78c35e32f98ba8ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Scho=CC=88nberger?= Date: Fri, 27 Apr 2012 19:55:33 +0200 Subject: [PATCH 15/19] restored old otsu threshold example script --- doc/examples/plot_otsu.py | 45 +++++++++++++++++++ doc/examples/plot_thresholding.py | 72 ------------------------------- 2 files changed, 45 insertions(+), 72 deletions(-) create mode 100644 doc/examples/plot_otsu.py delete mode 100644 doc/examples/plot_thresholding.py diff --git a/doc/examples/plot_otsu.py b/doc/examples/plot_otsu.py new file mode 100644 index 00000000..f2335fc0 --- /dev/null +++ b/doc/examples/plot_otsu.py @@ -0,0 +1,45 @@ +""" +============ +Thresholding +============ + +Thresholding is used to create a binary image. This example uses Otsu's method +to calculate the threshold value. + +Otsu's method calculates an "optimal" threshold (marked by a red line in the +histogram below) by maximizing the variance between two classes of pixels, +which are separated by the threshold. Equivalently, this threshold minimizes +the intra-class variance. + +.. [1] http://en.wikipedia.org/wiki/Otsu's_method + +""" + +import matplotlib.pyplot as plt + +from skimage.data import camera +from skimage.filter import threshold_otsu + + +image = camera() +thresh = threshold_otsu(image) +binary = image > thresh + +plt.figure(figsize=(8, 2.5)) +plt.subplot(1, 3, 1) +plt.imshow(image, cmap=plt.cm.gray) +plt.title('Original') +plt.axis('off') + +plt.subplot(1, 3, 2, aspect='equal') +plt.hist(image) +plt.title('Histogram') +plt.axvline(thresh, color='r') + +plt.subplot(1, 3, 3) +plt.imshow(binary, cmap=plt.cm.gray) +plt.title('Thresholded') +plt.axis('off') + +plt.show() + diff --git a/doc/examples/plot_thresholding.py b/doc/examples/plot_thresholding.py deleted file mode 100644 index ada0552e..00000000 --- a/doc/examples/plot_thresholding.py +++ /dev/null @@ -1,72 +0,0 @@ -""" -============ -Thresholding -============ - -Thresholding is used to create a binary image. - -This example uses Otsu's method to calculate the threshold value. Otsu's method -calculates an "optimal" threshold (marked by a red line in the histogram below) -by maximizing the variance between two classes of pixels, which are separated by -the threshold. Equivalently, this threshold minimizes the intra-class variance. - -Additionally an adaptive thresholding is applied. 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. Small filter block sizes -are suitable for thresholding edges, large filter block sizes suitable for -thresholding larger homogeneous regions. - -.. [1] http://en.wikipedia.org/wiki/Otsu's_method - -""" - -import matplotlib.pyplot as plt - -from skimage.data import camera -from skimage.filter import threshold_otsu, threshold_adaptive - - -image = camera() - - -#: Otsu thresholding -thresh = threshold_otsu(image) -otsu_binary = image > thresh - -plt.figure(figsize=(8, 6)) -plt.subplot(2, 3, 1) -plt.imshow(image, cmap=plt.cm.gray) -plt.title('Original') -plt.axis('off') - -plt.subplot(2, 3, 2, aspect='equal') -plt.hist(image) -plt.title('Histogram') -plt.axvline(thresh, color='r') - -plt.subplot(2, 3, 3) -plt.imshow(otsu_binary, cmap=plt.cm.gray) -plt.title('Thresholded with Otsu') -plt.axis('off') - - -#: Adaptive thresholding -plt.subplot(2, 3, 4) -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, 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, method='mean', offset=7.5), - cmap=plt.cm.gray) -plt.title('Adaptive Mean') -plt.axis('off') - -plt.show() From a3d336f5606cff227c93c4ff683baa35b1c628df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Scho=CC=88nberger?= Date: Sun, 29 Apr 2012 17:49:33 +0200 Subject: [PATCH 16/19] cython implementation of adaptive thresholding replaced with pure python version --- skimage/filter/_thresholding.pyx | 35 -------------------------------- skimage/filter/setup.py | 3 --- skimage/filter/thresholding.py | 29 ++++++++++++++++++++++---- 3 files changed, 25 insertions(+), 42 deletions(-) delete mode 100644 skimage/filter/_thresholding.pyx diff --git a/skimage/filter/_thresholding.pyx b/skimage/filter/_thresholding.pyx deleted file mode 100644 index c7a80478..00000000 --- a/skimage/filter/_thresholding.pyx +++ /dev/null @@ -1,35 +0,0 @@ -import numpy as np -import scipy.ndimage -cimport numpy as np -cimport cython - - -@cython.boundscheck(False) -@cython.wraparound(False) -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.double_t, ndim=2] thresh_image - - if method == 'generic': - thresh_image = scipy.ndimage.generic_filter(image, param, block_size, - mode=mode) - elif method == 'gaussian': - if param is None: - # automatically determine sigma which covers > 99% of distribution - sigma = (block_size - 1) / 6.0 - thresh_image = scipy.ndimage.gaussian_filter(image, sigma, mode=mode) - elif method == 'mean': - mask = 1. / block_size * np.ones((block_size,)) - # separation of filters to speedup convolution - thresh_image = scipy.ndimage.convolve1d(image, mask, axis=0, mode=mode) - thresh_image = scipy.ndimage.convolve1d(thresh_image, mask, axis=1, - mode=mode) - elif method == 'median': - thresh_image = scipy.ndimage.median_filter(image, block_size, mode=mode) - - for r in range(image.shape[0]): - for c in range(image.shape[1]): - thresh_image[r,c] = image[r,c] > (thresh_image[r,c] - offset) - - return thresh_image.astype('bool') diff --git a/skimage/filter/setup.py b/skimage/filter/setup.py index f93f7143..12cb84a7 100644 --- a/skimage/filter/setup.py +++ b/skimage/filter/setup.py @@ -12,12 +12,9 @@ def configuration(parent_package='', top_path=None): config.add_data_dir('tests') cython(['_ctmf.pyx'], working_path=base_path) - cython(['_thresholding.pyx'], working_path=base_path) config.add_extension('_ctmf', sources=['_ctmf.c'], include_dirs=[get_numpy_include_dirs()]) - config.add_extension('_thresholding', sources=['_thresholding.c'], - include_dirs=[get_numpy_include_dirs()]) return config diff --git a/skimage/filter/thresholding.py b/skimage/filter/thresholding.py index 77c6bf9d..e34a7751 100644 --- a/skimage/filter/thresholding.py +++ b/skimage/filter/thresholding.py @@ -1,5 +1,5 @@ import numpy as np - +import scipy.ndimage from skimage.exposure import histogram from ._thresholding import _threshold_adaptive @@ -63,9 +63,30 @@ def threshold_adaptive(image, block_size, method='gaussian', offset=0, >>> func = lambda arr: arr.mean() >>> binary_image2 = threshold_adaptive(image, 15, 'generic', param=func) """ - # not using img_as_float because offset parameter wouldn't work - image = image.astype('double') - return _threshold_adaptive(image, block_size, method, offset, mode, param) + thresh_image = np.zeros(image.shape, 'double') + if method == 'generic': + scipy.ndimage.generic_filter(image, param, block_size, + output=thresh_image, mode=mode) + elif method == 'gaussian': + if param is None: + # automatically determine sigma which covers > 99% of distribution + sigma = (block_size - 1) / 6.0 + scipy.ndimage.gaussian_filter(image, sigma, output=thresh_image, + mode=mode) + elif method == 'mean': + mask = 1. / block_size * np.ones((block_size,)) + # separation of filters to speedup convolution + scipy.ndimage.convolve1d(image, mask, axis=0, output=thresh_image, + mode=mode) + scipy.ndimage.convolve1d(thresh_image, mask, axis=1, + output=thresh_image, mode=mode) + elif method == 'median': + scipy.ndimage.median_filter(image, block_size, output=thresh_image, + mode=mode) + + thresh_image = image > (thresh_image - offset) + + return thresh_image.astype('bool') def threshold_otsu(image, nbins=256): """Return threshold value based on Otsu's method. From ffcade8a251e14aa104d95056601918bde660249 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Scho=CC=88nberger?= Date: Mon, 30 Apr 2012 09:04:35 +0200 Subject: [PATCH 17/19] removed unnecessary cast step --- skimage/filter/thresholding.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/skimage/filter/thresholding.py b/skimage/filter/thresholding.py index e34a7751..2cb2d8e0 100644 --- a/skimage/filter/thresholding.py +++ b/skimage/filter/thresholding.py @@ -84,9 +84,7 @@ def threshold_adaptive(image, block_size, method='gaussian', offset=0, scipy.ndimage.median_filter(image, block_size, output=thresh_image, mode=mode) - thresh_image = image > (thresh_image - offset) - - return thresh_image.astype('bool') + return image > (thresh_image - offset) def threshold_otsu(image, nbins=256): """Return threshold value based on Otsu's method. From def7698cd74486dbd43c25c2b4630b467316e46e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Scho=CC=88nberger?= Date: Mon, 30 Apr 2012 09:05:04 +0200 Subject: [PATCH 18/19] fixed typo in doc string --- skimage/filter/thresholding.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skimage/filter/thresholding.py b/skimage/filter/thresholding.py index 2cb2d8e0..efef9a45 100644 --- a/skimage/filter/thresholding.py +++ b/skimage/filter/thresholding.py @@ -24,7 +24,7 @@ def threshold_adaptive(image, block_size, method='gaussian', offset=0, Uneven size of pixel neighborhood which is used to calculate the threshold value (e.g. 3, 5, 7, ..., 21, ...). method : {'generic', 'gaussian', 'mean', 'median'}, optional - Method used to determine adaptive threshold fpr local neighbourhood in + Method used to determine adaptive threshold for local neighbourhood in weighted mean image. * 'generic': use custom function (see `param` parameter) * 'gaussian': apply gaussian filter (see `param` parameter for custom From 7b4fea2db4c318e20a182b535eb5383964f8ed40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Scho=CC=88nberger?= Date: Mon, 30 Apr 2012 16:33:58 +0200 Subject: [PATCH 19/19] fixed bug when sigma is set manually --- skimage/filter/thresholding.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/skimage/filter/thresholding.py b/skimage/filter/thresholding.py index efef9a45..51907347 100644 --- a/skimage/filter/thresholding.py +++ b/skimage/filter/thresholding.py @@ -71,6 +71,8 @@ def threshold_adaptive(image, block_size, method='gaussian', offset=0, if param is None: # automatically determine sigma which covers > 99% of distribution sigma = (block_size - 1) / 6.0 + else: + sigma = param scipy.ndimage.gaussian_filter(image, sigma, output=thresh_image, mode=mode) elif method == 'mean':