From bf5f08e89487983bbea771a5bc8c604066f90aaf Mon Sep 17 00:00:00 2001 From: Juan Nunez-Iglesias Date: Mon, 16 Sep 2013 17:30:48 +1000 Subject: [PATCH 1/6] Update SLIC docstring to remove deprecated example The `ratio` keyword has been deprecated but it was still being used in the example in the SLIC docstring. This replaces that usage by the new `compactness` keyword. --- skimage/segmentation/slic_superpixels.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/skimage/segmentation/slic_superpixels.py b/skimage/segmentation/slic_superpixels.py index 03017cf6..07bb54a5 100644 --- a/skimage/segmentation/slic_superpixels.py +++ b/skimage/segmentation/slic_superpixels.py @@ -75,9 +75,9 @@ def slic(image, n_segments=100, compactness=10., max_iter=10, sigma=None, >>> from skimage.segmentation import slic >>> from skimage.data import lena >>> img = lena() - >>> segments = slic(img, n_segments=100, ratio=10) - >>> # Increasing the ratio parameter yields more square regions - >>> segments = slic(img, n_segments=100, ratio=20) + >>> segments = slic(img, n_segments=100, compactness=10) + >>> # Increasing the compactness parameter yields more square regions + >>> segments = slic(img, n_segments=100, compactness=20) """ if sigma is None: From 9d86c9a1e1199c223ac435cb16a39c230c3de313 Mon Sep 17 00:00:00 2001 From: Juan Nunez-Iglesias Date: Mon, 16 Sep 2013 17:33:08 +1000 Subject: [PATCH 2/6] Ignore `convert2lab` keyword if not multichannel --- skimage/segmentation/slic_superpixels.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/skimage/segmentation/slic_superpixels.py b/skimage/segmentation/slic_superpixels.py index 07bb54a5..08ea7126 100644 --- a/skimage/segmentation/slic_superpixels.py +++ b/skimage/segmentation/slic_superpixels.py @@ -109,9 +109,8 @@ def slic(image, n_segments=100, compactness=10., max_iter=10, sigma=None, sigma = list(sigma) + [0] image = ndimage.gaussian_filter(image, sigma) - if convert2lab: - - if not multichannel or image.shape[3] != 3: + if convert2lab and multichannel: + if image.shape[3] != 3: raise ValueError("Lab colorspace conversion requires a RGB image.") image = rgb2lab(image) From 610a0d1793f1b3fb91544c1921311ecf20ff355d Mon Sep 17 00:00:00 2001 From: Juan Nunez-Iglesias Date: Mon, 16 Sep 2013 17:49:07 +1000 Subject: [PATCH 3/6] Add support for list sigma input in SLIC Previously, having a different `sigma` for different dimensions required an array input. This allows the user to use a simple list, which gets converted to an array internally. Importantly, it removes a very unhelpful error: ```python >>> im = np.random.rand(10, 20) >>> from skimage import segmentation as seg Exception AttributeError: "'UmfpackContext' object has no attribute '_symbolic'" in > ignored >>> s = seg.slic(im, 2, sigma=[2, 1]) --------------------------------------------------------------------------- AttributeError Traceback (most recent call last) in () ----> 1 s = seg.slic(im, 2, sigma=[2, 1]) /Users/nuneziglesiasj/venv/skimdev2/lib/python2.7/site-packages/scikit_image-0.9dev-py2.7-macosx-10.5-x86_64.egg/skimage/segmentation/slic_superpixels.pyc in slic(image, n_segments, compactness, max_iter, sigma, multichannel, convert2lab, ratio) 106 if not isinstance(sigma, coll.Iterable): 107 sigma = np.array([sigma, sigma, sigma]) --> 108 if (sigma > 0).any(): 109 sigma = list(sigma) + [0] 110 image = ndimage.gaussian_filter(image, sigma) AttributeError: 'bool' object has no attribute 'any' ``` --- skimage/segmentation/slic_superpixels.py | 6 ++++-- skimage/segmentation/tests/test_slic.py | 11 +++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/skimage/segmentation/slic_superpixels.py b/skimage/segmentation/slic_superpixels.py index 08ea7126..be07fafe 100644 --- a/skimage/segmentation/slic_superpixels.py +++ b/skimage/segmentation/slic_superpixels.py @@ -27,7 +27,7 @@ def slic(image, n_segments=100, compactness=10., max_iter=10, sigma=None, infinity, superpixel shapes become square/cubic. max_iter : int, optional Maximum number of iterations of k-means. - sigma : float or (3,) array of floats, optional + sigma : float or (3,) array-like of floats, optional Width of Gaussian smoothing kernel for pre-processing for each dimension of the image. The same sigma is applied to each dimension in case of a scalar value. Zero means no smoothing. @@ -104,7 +104,9 @@ def slic(image, n_segments=100, compactness=10., max_iter=10, sigma=None, image = image[..., np.newaxis] if not isinstance(sigma, coll.Iterable): - sigma = np.array([sigma, sigma, sigma]) + sigma = np.array([sigma, sigma, sigma], float) + elif type(sigma) in [list, tuple]: + sigma = np.array(sigma, float) if (sigma > 0).any(): sigma = list(sigma) + [0] image = ndimage.gaussian_filter(image, sigma) diff --git a/skimage/segmentation/tests/test_slic.py b/skimage/segmentation/tests/test_slic.py index 6d00716f..2a190de8 100644 --- a/skimage/segmentation/tests/test_slic.py +++ b/skimage/segmentation/tests/test_slic.py @@ -90,6 +90,17 @@ def test_gray_3d(): assert_array_equal(seg[s], c) +def test_list_sigma(): + rnd = np.random.RandomState(0) + img = np.array([[1, 1, 1, 0, 0, 0], + [0, 0, 0, 1, 1, 1]], np.float) + img += 0.1 * rnd.normal(size=img.shape) + result_sigma = np.array([[0, 0, 0, 1, 1, 1], + [0, 0, 0, 1, 1, 1]], np.int) + seg_sigma = slic(img, n_segments=2, sigma=[1, 50, 1], multichannel=False) + assert_equal(seg_sigma, result_sigma) + + if __name__ == '__main__': from numpy import testing testing.run_module_suite() From 846765e5f9e35cd9be24d0a391a4fde106ba6b9e Mon Sep 17 00:00:00 2001 From: Juan Nunez-Iglesias Date: Mon, 16 Sep 2013 17:52:25 +1000 Subject: [PATCH 4/6] Add spacing support for new, speeded-up SLIC --- skimage/segmentation/_slic.pyx | 15 +++++++++++---- skimage/segmentation/slic_superpixels.py | 12 ++++++++++-- skimage/segmentation/tests/test_slic.py | 18 ++++++++++++++++++ 3 files changed, 39 insertions(+), 6 deletions(-) diff --git a/skimage/segmentation/_slic.pyx b/skimage/segmentation/_slic.pyx index d818a501..57c9990a 100644 --- a/skimage/segmentation/_slic.pyx +++ b/skimage/segmentation/_slic.pyx @@ -12,7 +12,8 @@ from skimage.util import regular_grid def _slic_cython(double[:, :, :, ::1] image_zyx, double[:, ::1] segments, - Py_ssize_t max_iter): + Py_ssize_t max_iter, + double[:] spacing): """Helper function for SLIC segmentation. Parameters @@ -23,6 +24,7 @@ def _slic_cython(double[:, :, :, ::1] image_zyx, The initial centroids obtained by SLIC as [Z, Y, X, C...]. max_iter : int The maximum number of k-means iterations. + spacing : 1D array of double, shape (3,) Returns ------- @@ -55,6 +57,11 @@ def _slic_cython(double[:, :, :, ::1] image_zyx, cdef char change cdef double dist_center, cx, cy, cz, dy, dz + cdef double sz, sy, sx + sz = spacing[0] + sy = spacing[1] + sx = spacing[2] + for i in range(max_iter): change = 0 distance[:, :, :] = DBL_MAX @@ -76,11 +83,11 @@ def _slic_cython(double[:, :, :, ::1] image_zyx, x_max = min(cx + 2 * step_x + 1, width) for z in range(z_min, z_max): - dz = (cz - z) ** 2 + dz = (sz * (cz - z)) ** 2 for y in range(y_min, y_max): - dy = (cy - y) ** 2 + dy = (sy * (cy - y)) ** 2 for x in range(x_min, x_max): - dist_center = dz + dy + (cx - x) ** 2 + dist_center = dz + dy + (sx * (cx - x)) ** 2 for c in range(3, n_features): dist_center += (image_zyx[z, y, x, c - 3] - segments[k, c]) ** 2 diff --git a/skimage/segmentation/slic_superpixels.py b/skimage/segmentation/slic_superpixels.py index be07fafe..e9ae8ee7 100644 --- a/skimage/segmentation/slic_superpixels.py +++ b/skimage/segmentation/slic_superpixels.py @@ -11,7 +11,7 @@ from skimage.color import rgb2lab def slic(image, n_segments=100, compactness=10., max_iter=10, sigma=None, - multichannel=True, convert2lab=True, ratio=None): + spacing=None, multichannel=True, convert2lab=True, ratio=None): """Segments image using k-means clustering in Color-(x,y,z) space. Parameters @@ -31,6 +31,9 @@ def slic(image, n_segments=100, compactness=10., max_iter=10, sigma=None, Width of Gaussian smoothing kernel for pre-processing for each dimension of the image. The same sigma is applied to each dimension in case of a scalar value. Zero means no smoothing. + spacing : (3,) array-like of floats, optional + The voxel spacing along each image dimension. By default, `slic` + assumes uniform spacing (same voxel resolution along z, y and x). multichannel : bool, optional Whether the last axis of the image is to be interpreted as multiple channels or another spatial dimension. @@ -103,11 +106,16 @@ def slic(image, n_segments=100, compactness=10., max_iter=10, sigma=None, # Add channel as single last dimension image = image[..., np.newaxis] + if spacing is None: + spacing = np.ones(3) + elif type(spacing) in [list, tuple]: + spacing = np.array(spacing, float) if not isinstance(sigma, coll.Iterable): sigma = np.array([sigma, sigma, sigma], float) elif type(sigma) in [list, tuple]: sigma = np.array(sigma, float) if (sigma > 0).any(): + sigma /= spacing.astype(float) sigma = list(sigma) + [0] image = ndimage.gaussian_filter(image, sigma) @@ -139,7 +147,7 @@ def slic(image, n_segments=100, compactness=10., max_iter=10, sigma=None, ratio = float(max((step_z, step_y, step_x))) / compactness image = np.ascontiguousarray(image * ratio) - labels = _slic_cython(image, segments, max_iter) + labels = _slic_cython(image, segments, max_iter, spacing) if is2d: labels = labels[0] diff --git a/skimage/segmentation/tests/test_slic.py b/skimage/segmentation/tests/test_slic.py index 2a190de8..a4657785 100644 --- a/skimage/segmentation/tests/test_slic.py +++ b/skimage/segmentation/tests/test_slic.py @@ -101,6 +101,24 @@ def test_list_sigma(): assert_equal(seg_sigma, result_sigma) +def test_spacing(): + rnd = np.random.RandomState(0) + img = np.array([[1, 1, 1, 0, 0], + [1, 1, 0, 0, 0]], np.float) + result_non_spaced = np.array([[0, 0, 0, 1, 1], + [0, 0, 1, 1, 1]], np.int) + result_spaced = np.array([[0, 0, 0, 0, 0], + [1, 1, 1, 1, 1]], np.int) + img += 0.1 * rnd.normal(size=img.shape) + seg_non_spaced = slic(img, n_segments=2, sigma=0, multichannel=False, + compactness=1.0) + seg_spaced = slic(img, n_segments=2, sigma=0, spacing=[1, 500, 1], + compactness=1.0, multichannel=False) + assert_equal(seg_non_spaced, result_non_spaced) + assert_equal(seg_spaced, result_spaced) + + + if __name__ == '__main__': from numpy import testing testing.run_module_suite() From 00e5ff263bc1f32b41f02f9ca8dbafbb05459668 Mon Sep 17 00:00:00 2001 From: Juan Nunez-Iglesias Date: Mon, 16 Sep 2013 21:26:44 +1000 Subject: [PATCH 5/6] Add `spacing` descriptions; use np.double Implement @ahojnnes's comments on pull request. Use `np.double` as dtype for arrays because in Cython, `float` is not `np.double`. And add further clarification about the `spacing` parameter. --- skimage/segmentation/_slic.pyx | 3 +++ skimage/segmentation/slic_superpixels.py | 19 +++++++++++++------ 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/skimage/segmentation/_slic.pyx b/skimage/segmentation/_slic.pyx index 57c9990a..3247dd79 100644 --- a/skimage/segmentation/_slic.pyx +++ b/skimage/segmentation/_slic.pyx @@ -25,6 +25,9 @@ def _slic_cython(double[:, :, :, ::1] image_zyx, max_iter : int The maximum number of k-means iterations. spacing : 1D array of double, shape (3,) + The voxel spacing along each image dimension. This parameter + controls the weights of the distances along z, y, and x during + k-means clustering. Returns ------- diff --git a/skimage/segmentation/slic_superpixels.py b/skimage/segmentation/slic_superpixels.py index e9ae8ee7..3cb67874 100644 --- a/skimage/segmentation/slic_superpixels.py +++ b/skimage/segmentation/slic_superpixels.py @@ -34,6 +34,8 @@ def slic(image, n_segments=100, compactness=10., max_iter=10, sigma=None, spacing : (3,) array-like of floats, optional The voxel spacing along each image dimension. By default, `slic` assumes uniform spacing (same voxel resolution along z, y and x). + This parameter controls the weights of the distances along z, y, + and x during k-means clustering. multichannel : bool, optional Whether the last axis of the image is to be interpreted as multiple channels or another spatial dimension. @@ -61,6 +63,11 @@ def slic(image, n_segments=100, compactness=10., max_iter=10, sigma=None, If `sigma > 0`, the image is smoothed using a Gaussian kernel prior to segmentation. + If `sigma > 0` and `spacing` is provided, the kernel width is divided + along each dimension by the spacing. For example, if `sigma=1` and + `spacing=[5, 1, 1]`, the effective `sigma` is `[0.2, 1, 1]`. This + ensures sensible smoothing for anisotropic images. + The image is rescaled to be in [0, 1] prior to processing. Images of shape (M, N, 3) are interpreted as 2D RGB images by default. To @@ -108,14 +115,14 @@ def slic(image, n_segments=100, compactness=10., max_iter=10, sigma=None, if spacing is None: spacing = np.ones(3) - elif type(spacing) in [list, tuple]: - spacing = np.array(spacing, float) + elif isinstance(spacing, (list, tuple)): + spacing = np.array(spacing, np.double) if not isinstance(sigma, coll.Iterable): - sigma = np.array([sigma, sigma, sigma], float) - elif type(sigma) in [list, tuple]: - sigma = np.array(sigma, float) + sigma = np.array([sigma, sigma, sigma], np.double) + elif isinstance(spacing, (list, tuple)): + sigma = np.array(sigma, np.double) if (sigma > 0).any(): - sigma /= spacing.astype(float) + sigma /= spacing.astype(np.double) sigma = list(sigma) + [0] image = ndimage.gaussian_filter(image, sigma) From 05fbc3fbfcc00157841c18b15b171d6477bfb5da Mon Sep 17 00:00:00 2001 From: Juan Nunez-Iglesias Date: Mon, 16 Sep 2013 22:42:48 +1000 Subject: [PATCH 6/6] Bug fix: typo: wrote spacing instead of sigma --- skimage/segmentation/slic_superpixels.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skimage/segmentation/slic_superpixels.py b/skimage/segmentation/slic_superpixels.py index 3cb67874..422dff98 100644 --- a/skimage/segmentation/slic_superpixels.py +++ b/skimage/segmentation/slic_superpixels.py @@ -119,7 +119,7 @@ def slic(image, n_segments=100, compactness=10., max_iter=10, sigma=None, spacing = np.array(spacing, np.double) if not isinstance(sigma, coll.Iterable): sigma = np.array([sigma, sigma, sigma], np.double) - elif isinstance(spacing, (list, tuple)): + elif isinstance(sigma, (list, tuple)): sigma = np.array(sigma, np.double) if (sigma > 0).any(): sigma /= spacing.astype(np.double)