From 8db9096b20334e0acf45cd712cdd00804f00016b Mon Sep 17 00:00:00 2001 From: Guillem Palou Date: Fri, 20 Dec 2013 09:18:14 +0100 Subject: [PATCH 01/11] Enforce SLIC superpixels connectivity --- skimage/segmentation/_slic.pyx | 71 +++++++++++++++++++++++++++++++++- 1 file changed, 70 insertions(+), 1 deletion(-) diff --git a/skimage/segmentation/_slic.pyx b/skimage/segmentation/_slic.pyx index c3d95ee0..9cb96ab9 100644 --- a/skimage/segmentation/_slic.pyx +++ b/skimage/segmentation/_slic.pyx @@ -145,4 +145,73 @@ def _slic_cython(double[:, :, :, ::1] image_zyx, for c in range(n_features): segments[k, c] /= n_segment_elems[k] - return np.asarray(nearest_segments) + #return np.asarray(nearest_segments) + + #enforce segment connectivity + cdef Py_ssize_t[:] ddx = np.array((1,-1,0,0,0,0)) + cdef Py_ssize_t[:] ddy = np.array((0,0,1,-1,0,0)) + cdef Py_ssize_t[:] ddz = np.array((0,0,0,0,1,-1)) + + cdef double factor = 0.25 + cdef double size = height*width*depth / n_segments + cdef double min_size = factor * size + cdef double max_size = 3*size + + #new object with connected segments + cdef Py_ssize_t[:, :, ::1] new_nearest_segments \ + = np.zeros((depth, height, width), dtype=np.intp) + + cdef Py_ssize_t current_new_label = 0 + cdef Py_ssize_t label = 0 + + #variables for the breadth first search + cdef Py_ssize_t count = 1 + cdef Py_ssize_t p = 0 + cdef Py_ssize_t adjacent + + cdef Py_ssize_t zz,yy,xx + + cdef Py_ssize_t[:, :] coord_list \ + = np.zeros((max_size,3), dtype=np.intp) + + #loop through all image + for z in range(depth): + for y in range(height): + for x in range(width): + if (new_nearest_segments[z,y,x] > 0): + continue + #find the component size + adjacent = 0 + label = nearest_segments[z,y,x] + current_new_label = current_new_label+1 + new_nearest_segments[z,y,x] = current_new_label + + count = 1 + p = 0 + coord_list[p,0] = z + coord_list[p,1] = y + coord_list[p,2] = x + + #perform a breadth first search to find the size of the connected component + while (p != count): + for i in range(6): + zz = coord_list[p,0] + ddz[i] + yy = coord_list[p,1] + ddy[i] + xx = coord_list[p,2] + ddx[i] + if (xx >= 0 and xx < width and yy >= 0 and yy < height and zz >= 0 and zz < depth): + if (nearest_segments[zz,yy,xx] == label and new_nearest_segments[zz,yy,xx] == 0): + new_nearest_segments[zz,yy,xx] = current_new_label + coord_list[count,0] = zz + coord_list[count,1] = yy + coord_list[count,2] = xx + count = count + 1 + elif (new_nearest_segments[zz,yy,xx] > 0 and new_nearest_segments[zz,yy,xx] != current_new_label): + adjacent = new_nearest_segments[zz,yy,xx] + p = p + 1 + + #change to an adjacent one, like in the original paper + if (count < min_size): + for i in range(count): + new_nearest_segments[coord_list[i,0],coord_list[i,1],coord_list[i,2]] = adjacent + + return np.asarray(new_nearest_segments) From 0d6540a82c76a113d022bd89b0a5e875ce482e1a Mon Sep 17 00:00:00 2001 From: Guillem Palou Date: Mon, 23 Dec 2013 00:49:06 +0100 Subject: [PATCH 02/11] Changed SLIC superpixels so that connectivity is enforced optionally. A separate, private function is created that implements the postprocessing step --- skimage/segmentation/_slic.pyx | 171 +++++++++++++---------- skimage/segmentation/slic_superpixels.py | 15 +- 2 files changed, 112 insertions(+), 74 deletions(-) diff --git a/skimage/segmentation/_slic.pyx b/skimage/segmentation/_slic.pyx index 9cb96ab9..b3a00a52 100644 --- a/skimage/segmentation/_slic.pyx +++ b/skimage/segmentation/_slic.pyx @@ -9,11 +9,107 @@ cimport numpy as cnp from skimage.util import regular_grid +def _enforce_label_connectivity_cython(Py_ssize_t[:, :, ::1] nearest_segments, + Py_ssize_t n_segments, + int min_size): + """ Helper function to remove small disconnected regions from the labels + + Parameters + ---------- + nearest_segments : 3D array of int, shape (Z, Y, X) + The label field/superpixels found by SLIC. + n_segments: int + number of specified segments + min_size: int + minimum size of the segment + + Returns + ------- + connected_nearest_segments : 3D array of int, shape (Z, Y, X) + A label field with connected labels starting at label=1 + """ + + #get image dimensions + cdef Py_ssize_t depth, height, width + depth = nearest_segments.shape[0] + height = nearest_segments.shape[1] + width = nearest_segments.shape[2] + + #neighborhood arrays + cdef Py_ssize_t[:] ddx = np.array((1,-1,0,0,0,0)) + cdef Py_ssize_t[:] ddy = np.array((0,0,1,-1,0,0)) + cdef Py_ssize_t[:] ddz = np.array((0,0,0,0,1,-1)) + + cdef double size = height*width*depth / n_segments + cdef double max_size = 3*size + + #new object with connected segments + cdef Py_ssize_t[:, :, ::1] new_nearest_segments \ + = np.zeros((depth, height, width), dtype=np.intp) + + cdef Py_ssize_t current_new_label = 0 + cdef Py_ssize_t label = 0 + + #variables for the breadth first search + cdef Py_ssize_t count = 1 + cdef Py_ssize_t p = 0 + cdef Py_ssize_t adjacent + + cdef Py_ssize_t zz,yy,xx + + cdef Py_ssize_t[:, :] coord_list \ + = np.zeros((max_size,3), dtype=np.intp) + + #loop through all image + for z in range(depth): + for y in range(height): + for x in range(width): + if (new_nearest_segments[z,y,x] > 0): + continue + #find the component size + adjacent = 0 + label = nearest_segments[z,y,x] + current_new_label = current_new_label+1 + new_nearest_segments[z,y,x] = current_new_label + + count = 1 + p = 0 + coord_list[p,0] = z + coord_list[p,1] = y + coord_list[p,2] = x + + #perform a breadth first search to find the size of the connected component + while (p != count): + for i in range(6): + zz = coord_list[p,0] + ddz[i] + yy = coord_list[p,1] + ddy[i] + xx = coord_list[p,2] + ddx[i] + if (xx >= 0 and xx < width and yy >= 0 and yy < height and zz >= 0 and zz < depth): + if (nearest_segments[zz,yy,xx] == label and new_nearest_segments[zz,yy,xx] == 0): + new_nearest_segments[zz,yy,xx] = current_new_label + coord_list[count,0] = zz + coord_list[count,1] = yy + coord_list[count,2] = xx + count = count + 1 + elif (new_nearest_segments[zz,yy,xx] > 0 and new_nearest_segments[zz,yy,xx] != current_new_label): + adjacent = new_nearest_segments[zz,yy,xx] + p = p + 1 + + + #change to an adjacent one, like in the original paper + if (count < min_size): + #print("Changing segment {0} label {1} ".format(current_new_label, label)) + for i in range(count): + new_nearest_segments[coord_list[i,0],coord_list[i,1],coord_list[i,2]] = adjacent + + return np.asarray(new_nearest_segments) def _slic_cython(double[:, :, :, ::1] image_zyx, double[:, ::1] segments, Py_ssize_t max_iter, - double[::1] spacing): + double[::1] spacing, + int enforce_connectivity, + Py_ssize_t min_size = True): """Helper function for SLIC segmentation. Parameters @@ -28,6 +124,8 @@ def _slic_cython(double[:, :, :, ::1] image_zyx, The voxel spacing along each image dimension. This parameter controls the weights of the distances along z, y, and x during k-means clustering. + enforce_connectivity: int indicating whether the returned label + field must have connected labels Returns ------- @@ -145,73 +243,4 @@ def _slic_cython(double[:, :, :, ::1] image_zyx, for c in range(n_features): segments[k, c] /= n_segment_elems[k] - #return np.asarray(nearest_segments) - - #enforce segment connectivity - cdef Py_ssize_t[:] ddx = np.array((1,-1,0,0,0,0)) - cdef Py_ssize_t[:] ddy = np.array((0,0,1,-1,0,0)) - cdef Py_ssize_t[:] ddz = np.array((0,0,0,0,1,-1)) - - cdef double factor = 0.25 - cdef double size = height*width*depth / n_segments - cdef double min_size = factor * size - cdef double max_size = 3*size - - #new object with connected segments - cdef Py_ssize_t[:, :, ::1] new_nearest_segments \ - = np.zeros((depth, height, width), dtype=np.intp) - - cdef Py_ssize_t current_new_label = 0 - cdef Py_ssize_t label = 0 - - #variables for the breadth first search - cdef Py_ssize_t count = 1 - cdef Py_ssize_t p = 0 - cdef Py_ssize_t adjacent - - cdef Py_ssize_t zz,yy,xx - - cdef Py_ssize_t[:, :] coord_list \ - = np.zeros((max_size,3), dtype=np.intp) - - #loop through all image - for z in range(depth): - for y in range(height): - for x in range(width): - if (new_nearest_segments[z,y,x] > 0): - continue - #find the component size - adjacent = 0 - label = nearest_segments[z,y,x] - current_new_label = current_new_label+1 - new_nearest_segments[z,y,x] = current_new_label - - count = 1 - p = 0 - coord_list[p,0] = z - coord_list[p,1] = y - coord_list[p,2] = x - - #perform a breadth first search to find the size of the connected component - while (p != count): - for i in range(6): - zz = coord_list[p,0] + ddz[i] - yy = coord_list[p,1] + ddy[i] - xx = coord_list[p,2] + ddx[i] - if (xx >= 0 and xx < width and yy >= 0 and yy < height and zz >= 0 and zz < depth): - if (nearest_segments[zz,yy,xx] == label and new_nearest_segments[zz,yy,xx] == 0): - new_nearest_segments[zz,yy,xx] = current_new_label - coord_list[count,0] = zz - coord_list[count,1] = yy - coord_list[count,2] = xx - count = count + 1 - elif (new_nearest_segments[zz,yy,xx] > 0 and new_nearest_segments[zz,yy,xx] != current_new_label): - adjacent = new_nearest_segments[zz,yy,xx] - p = p + 1 - - #change to an adjacent one, like in the original paper - if (count < min_size): - for i in range(count): - new_nearest_segments[coord_list[i,0],coord_list[i,1],coord_list[i,2]] = adjacent - - return np.asarray(new_nearest_segments) + return np.asarray(nearest_segments) diff --git a/skimage/segmentation/slic_superpixels.py b/skimage/segmentation/slic_superpixels.py index 21df45fd..920d0e48 100644 --- a/skimage/segmentation/slic_superpixels.py +++ b/skimage/segmentation/slic_superpixels.py @@ -6,12 +6,13 @@ from scipy import ndimage import warnings from skimage.util import img_as_float, regular_grid -from skimage.segmentation._slic import _slic_cython +from skimage.segmentation._slic import _slic_cython, _enforce_label_connectivity_cython from skimage.color import rgb2lab def slic(image, n_segments=100, compactness=10., max_iter=10, sigma=None, - spacing=None, multichannel=True, convert2lab=True, ratio=None): + spacing=None, multichannel=True, convert2lab=True, ratio=None, + enforce_connectivity=True, min_size_factor=0.5): """Segments image using k-means clustering in Color-(x,y,z) space. Parameters @@ -47,6 +48,11 @@ def slic(image, n_segments=100, compactness=10., max_iter=10, sigma=None, recommended. ratio : float, optional Synonym for `compactness`. This keyword is deprecated. + enforce_connectivity: bool, optional + Whether the generated segments are connected or not + min_size_factor: float + proportion of the minimum segment size to be removed with respect + to the supposed segment size (depth*width*height/n_segments) Returns ------- @@ -161,7 +167,10 @@ 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, spacing) + labels = _slic_cython(image, segments, max_iter, spacing, enforce_connectivity) + + if (enforce_connectivity): + labels = _enforce_label_connectivity_cython(labels, n_segments, min_size_factor*depth*height*width/n_segments) if is_2d: labels = labels[0] From e68ab76b2411870bb6b6a798bd44ff2194af04ec Mon Sep 17 00:00:00 2001 From: Guillem Palou Date: Mon, 23 Dec 2013 01:17:51 +0100 Subject: [PATCH 03/11] Added max_size as a parameter --- skimage/segmentation/_slic.pyx | 10 +++++----- skimage/segmentation/slic_superpixels.py | 12 +++++++++--- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/skimage/segmentation/_slic.pyx b/skimage/segmentation/_slic.pyx index b3a00a52..6f97abff 100644 --- a/skimage/segmentation/_slic.pyx +++ b/skimage/segmentation/_slic.pyx @@ -11,7 +11,8 @@ from skimage.util import regular_grid def _enforce_label_connectivity_cython(Py_ssize_t[:, :, ::1] nearest_segments, Py_ssize_t n_segments, - int min_size): + int min_size, + int max_size): """ Helper function to remove small disconnected regions from the labels Parameters @@ -22,7 +23,9 @@ def _enforce_label_connectivity_cython(Py_ssize_t[:, :, ::1] nearest_segments, number of specified segments min_size: int minimum size of the segment - + max_size: int + maximum size of the segment. This is done for performance reasons, + to pre-allocate a sufficiently large array for the breadth first search Returns ------- connected_nearest_segments : 3D array of int, shape (Z, Y, X) @@ -40,9 +43,6 @@ def _enforce_label_connectivity_cython(Py_ssize_t[:, :, ::1] nearest_segments, cdef Py_ssize_t[:] ddy = np.array((0,0,1,-1,0,0)) cdef Py_ssize_t[:] ddz = np.array((0,0,0,0,1,-1)) - cdef double size = height*width*depth / n_segments - cdef double max_size = 3*size - #new object with connected segments cdef Py_ssize_t[:, :, ::1] new_nearest_segments \ = np.zeros((depth, height, width), dtype=np.intp) diff --git a/skimage/segmentation/slic_superpixels.py b/skimage/segmentation/slic_superpixels.py index 920d0e48..60d4ac13 100644 --- a/skimage/segmentation/slic_superpixels.py +++ b/skimage/segmentation/slic_superpixels.py @@ -12,7 +12,7 @@ from skimage.color import rgb2lab def slic(image, n_segments=100, compactness=10., max_iter=10, sigma=None, spacing=None, multichannel=True, convert2lab=True, ratio=None, - enforce_connectivity=True, min_size_factor=0.5): + enforce_connectivity=True, min_size_factor=0.5, max_size_factor=3): """Segments image using k-means clustering in Color-(x,y,z) space. Parameters @@ -53,7 +53,9 @@ def slic(image, n_segments=100, compactness=10., max_iter=10, sigma=None, min_size_factor: float proportion of the minimum segment size to be removed with respect to the supposed segment size (depth*width*height/n_segments) - + max_size_factor: float + proportion of the maximum connected segment size. A value of 3 works + in most of the cases. Returns ------- labels : 2D or 3D array @@ -170,7 +172,11 @@ def slic(image, n_segments=100, compactness=10., max_iter=10, sigma=None, labels = _slic_cython(image, segments, max_iter, spacing, enforce_connectivity) if (enforce_connectivity): - labels = _enforce_label_connectivity_cython(labels, n_segments, min_size_factor*depth*height*width/n_segments) + segment_size = depth*height*width/n_segments + labels = _enforce_label_connectivity_cython(labels, + n_segments, + min_size_factor*segment_size, + max_size_factor*segment_size) if is_2d: labels = labels[0] From 53a4388e266f8b1154d426c00097d11a9272e957 Mon Sep 17 00:00:00 2001 From: Guillem Palou Date: Mon, 23 Dec 2013 14:44:23 +0100 Subject: [PATCH 04/11] Changed tests to begin segment label at 1 instead of 0 --- skimage/segmentation/tests/test_slic.py | 28 ++++++++++++------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/skimage/segmentation/tests/test_slic.py b/skimage/segmentation/tests/test_slic.py index 15d1fe6d..0965e351 100644 --- a/skimage/segmentation/tests/test_slic.py +++ b/skimage/segmentation/tests/test_slic.py @@ -21,10 +21,10 @@ def test_color_2d(): # we expect 4 segments assert_equal(len(np.unique(seg)), 4) assert_equal(seg.shape, img.shape[:-1]) - assert_equal(seg[:10, :10], 0) - assert_equal(seg[10:, :10], 2) - assert_equal(seg[:10, 10:], 1) - assert_equal(seg[10:, 10:], 3) + assert_equal(seg[:10, :10], 1) + assert_equal(seg[10:, :10], 3) + assert_equal(seg[:10, 10:], 2) + assert_equal(seg[10:, 10:], 4) def test_gray_2d(): @@ -41,10 +41,10 @@ def test_gray_2d(): assert_equal(len(np.unique(seg)), 4) assert_equal(seg.shape, img.shape) - assert_equal(seg[:10, :10], 0) - assert_equal(seg[10:, :10], 2) - assert_equal(seg[:10, 10:], 1) - assert_equal(seg[10:, 10:], 3) + assert_equal(seg[:10, :10], 1) + assert_equal(seg[10:, :10], 3) + assert_equal(seg[:10, 10:], 2) + assert_equal(seg[10:, 10:], 4) def test_color_3d(): @@ -65,7 +65,7 @@ def test_color_3d(): assert_equal(len(np.unique(seg)), 8) for s, c in zip(slices, range(8)): - assert_equal(seg[s], c) + assert_equal(seg[s], c+1) def test_gray_3d(): @@ -87,7 +87,7 @@ def test_gray_3d(): assert_equal(len(np.unique(seg)), 8) for s, c in zip(slices, range(8)): - assert_equal(seg[s], c) + assert_equal(seg[s], c+1) def test_list_sigma(): @@ -96,7 +96,7 @@ def test_list_sigma(): [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) + [0, 0, 0, 1, 1, 1]], np.int) + 1 seg_sigma = slic(img, n_segments=2, sigma=[1, 50, 1], multichannel=False) assert_equal(seg_sigma, result_sigma) @@ -106,9 +106,9 @@ def test_spacing(): 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) + [0, 0, 1, 1, 1]], np.int) +1 result_spaced = np.array([[0, 0, 0, 0, 0], - [1, 1, 1, 1, 1]], np.int) + [1, 1, 1, 1, 1]], np.int) + 1 img += 0.1 * rnd.normal(size=img.shape) seg_non_spaced = slic(img, n_segments=2, sigma=0, multichannel=False, compactness=1.0) @@ -120,7 +120,7 @@ def test_spacing(): def test_invalid_lab_conversion(): img = np.array([[1, 1, 1, 0, 0], - [1, 1, 0, 0, 0]], np.float) + [1, 1, 0, 0, 0]], np.float) + 1 assert_raises(ValueError, slic, img, multichannel=True, convert2lab=True) From 08dc3a33d031590d00d68685874ddc8bc852899e Mon Sep 17 00:00:00 2001 From: Guillem Palou Visa Date: Tue, 24 Dec 2013 11:35:21 +0100 Subject: [PATCH 05/11] Changed coding style to be compliant with PEP8 --- skimage/segmentation/_slic.pyx | 201 +++++++++++------------ skimage/segmentation/slic_superpixels.py | 12 +- 2 files changed, 105 insertions(+), 108 deletions(-) diff --git a/skimage/segmentation/_slic.pyx b/skimage/segmentation/_slic.pyx index 6f97abff..8f5af582 100644 --- a/skimage/segmentation/_slic.pyx +++ b/skimage/segmentation/_slic.pyx @@ -9,107 +9,10 @@ cimport numpy as cnp from skimage.util import regular_grid -def _enforce_label_connectivity_cython(Py_ssize_t[:, :, ::1] nearest_segments, - Py_ssize_t n_segments, - int min_size, - int max_size): - """ Helper function to remove small disconnected regions from the labels - - Parameters - ---------- - nearest_segments : 3D array of int, shape (Z, Y, X) - The label field/superpixels found by SLIC. - n_segments: int - number of specified segments - min_size: int - minimum size of the segment - max_size: int - maximum size of the segment. This is done for performance reasons, - to pre-allocate a sufficiently large array for the breadth first search - Returns - ------- - connected_nearest_segments : 3D array of int, shape (Z, Y, X) - A label field with connected labels starting at label=1 - """ - - #get image dimensions - cdef Py_ssize_t depth, height, width - depth = nearest_segments.shape[0] - height = nearest_segments.shape[1] - width = nearest_segments.shape[2] - - #neighborhood arrays - cdef Py_ssize_t[:] ddx = np.array((1,-1,0,0,0,0)) - cdef Py_ssize_t[:] ddy = np.array((0,0,1,-1,0,0)) - cdef Py_ssize_t[:] ddz = np.array((0,0,0,0,1,-1)) - - #new object with connected segments - cdef Py_ssize_t[:, :, ::1] new_nearest_segments \ - = np.zeros((depth, height, width), dtype=np.intp) - - cdef Py_ssize_t current_new_label = 0 - cdef Py_ssize_t label = 0 - - #variables for the breadth first search - cdef Py_ssize_t count = 1 - cdef Py_ssize_t p = 0 - cdef Py_ssize_t adjacent - - cdef Py_ssize_t zz,yy,xx - - cdef Py_ssize_t[:, :] coord_list \ - = np.zeros((max_size,3), dtype=np.intp) - - #loop through all image - for z in range(depth): - for y in range(height): - for x in range(width): - if (new_nearest_segments[z,y,x] > 0): - continue - #find the component size - adjacent = 0 - label = nearest_segments[z,y,x] - current_new_label = current_new_label+1 - new_nearest_segments[z,y,x] = current_new_label - - count = 1 - p = 0 - coord_list[p,0] = z - coord_list[p,1] = y - coord_list[p,2] = x - - #perform a breadth first search to find the size of the connected component - while (p != count): - for i in range(6): - zz = coord_list[p,0] + ddz[i] - yy = coord_list[p,1] + ddy[i] - xx = coord_list[p,2] + ddx[i] - if (xx >= 0 and xx < width and yy >= 0 and yy < height and zz >= 0 and zz < depth): - if (nearest_segments[zz,yy,xx] == label and new_nearest_segments[zz,yy,xx] == 0): - new_nearest_segments[zz,yy,xx] = current_new_label - coord_list[count,0] = zz - coord_list[count,1] = yy - coord_list[count,2] = xx - count = count + 1 - elif (new_nearest_segments[zz,yy,xx] > 0 and new_nearest_segments[zz,yy,xx] != current_new_label): - adjacent = new_nearest_segments[zz,yy,xx] - p = p + 1 - - - #change to an adjacent one, like in the original paper - if (count < min_size): - #print("Changing segment {0} label {1} ".format(current_new_label, label)) - for i in range(count): - new_nearest_segments[coord_list[i,0],coord_list[i,1],coord_list[i,2]] = adjacent - - return np.asarray(new_nearest_segments) - def _slic_cython(double[:, :, :, ::1] image_zyx, double[:, ::1] segments, Py_ssize_t max_iter, - double[::1] spacing, - int enforce_connectivity, - Py_ssize_t min_size = True): + double[::1] spacing): """Helper function for SLIC segmentation. Parameters @@ -124,8 +27,6 @@ def _slic_cython(double[:, :, :, ::1] image_zyx, The voxel spacing along each image dimension. This parameter controls the weights of the distances along z, y, and x during k-means clustering. - enforce_connectivity: int indicating whether the returned label - field must have connected labels Returns ------- @@ -214,7 +115,8 @@ def _slic_cython(double[:, :, :, ::1] image_zyx, dist_center += (image_zyx[z, y, x, c - 3] - segments[k, c]) ** 2 if distance[z, y, x] > dist_center: - nearest_segments[z, y, x] = k + # segments start at 1 + nearest_segments[z, y, x] = k+1 distance[z, y, x] = dist_center change = 1 @@ -230,7 +132,8 @@ def _slic_cython(double[:, :, :, ::1] image_zyx, for z in range(depth): for y in range(height): for x in range(width): - k = nearest_segments[z, y, x] + #compensate the label offset 1 + k = nearest_segments[z, y, x] - 1 n_segment_elems[k] += 1 segments[k, 0] += z segments[k, 1] += y @@ -244,3 +147,97 @@ def _slic_cython(double[:, :, :, ::1] image_zyx, segments[k, c] /= n_segment_elems[k] return np.asarray(nearest_segments) + + +def _enforce_label_connectivity_cython(Py_ssize_t[:, :, ::1] segments, + Py_ssize_t n_segments, + int min_size, + int max_size): + """ Helper function to remove small disconnected regions from the labels + + Parameters + ---------- + segments : 3D array of int, shape (Z, Y, X) + The label field/superpixels found by SLIC. + n_segments: int + number of specified segments + min_size: int + minimum size of the segment + max_size: int + maximum size of the segment. This is done for performance reasons, + to pre-allocate a sufficiently large array for the breadth first search + Returns + ------- + connected_segments : 3D array of int, shape (Z, Y, X) + A label field with connected labels starting at label=1 + """ + + #get image dimensions + cdef Py_ssize_t depth, height, width + depth = segments.shape[0] + height = segments.shape[1] + width = segments.shape[2] + + #neighborhood arrays + cdef Py_ssize_t[:] ddx = np.array((1,-1,0,0,0,0)) + cdef Py_ssize_t[:] ddy = np.array((0,0,1,-1,0,0)) + cdef Py_ssize_t[:] ddz = np.array((0,0,0,0,1,-1)) + + #new object with connected segments + cdef Py_ssize_t[:, :, ::1] connected_segments = np.zeros_like(segments) + + cdef Py_ssize_t current_new_label = 0 + cdef Py_ssize_t label = 0 + + #variables for the breadth first search + cdef Py_ssize_t count = 1 + cdef Py_ssize_t p = 0 + cdef Py_ssize_t adjacent + + cdef Py_ssize_t zz,yy,xx + + cdef Py_ssize_t[:, :] coord_list = np.zeros((max_size,3), dtype=np.intp) + + #loop through all image + for z in range(depth): + for y in range(height): + for x in range(width): + if (new_segments[z,y,x] > 0): + continue + #find the component size + adjacent = 0 + label = segments[z,y,x] + current_new_label += 1 + connected_segments[z,y,x] = current_new_label + + count = 1 + p = 0 + coord_list[p,0] = z + coord_list[p,1] = y + coord_list[p,2] = x + + #perform a breadth first search to find the size of the connected component + while (p != count): + for i in range(6): + zz = coord_list[p,0] + ddz[i] + yy = coord_list[p,1] + ddy[i] + xx = coord_list[p,2] + ddx[i] + if (xx >= 0 and xx < width and yy >= 0 and yy < height and zz >= 0 and zz < depth): + if (segments[zz,yy,xx] == label and connected_segments[zz,yy,xx] == 0): + connected_segments[zz,yy,xx] = current_new_label + coord_list[count,0] = zz + coord_list[count,1] = yy + coord_list[count,2] = xx + count = count + 1 + elif (new_segments[zz,yy,xx] > 0 and connected_segments[zz,yy,xx] != current_new_label): + adjacent = connected_segments[zz,yy,xx] + p = p + 1 + + + #change to an adjacent one, like in the original paper + if (count < min_size): + connected_segments[*coords.T] = adjacent + #for i in range(count): + # connected_segments[coord_list[i,0],coord_list[i,1],coord_list[i,2]] = adjacent + + return np.asarray(connected_segments) \ No newline at end of file diff --git a/skimage/segmentation/slic_superpixels.py b/skimage/segmentation/slic_superpixels.py index 60d4ac13..98801c62 100644 --- a/skimage/segmentation/slic_superpixels.py +++ b/skimage/segmentation/slic_superpixels.py @@ -50,10 +50,10 @@ def slic(image, n_segments=100, compactness=10., max_iter=10, sigma=None, Synonym for `compactness`. This keyword is deprecated. enforce_connectivity: bool, optional Whether the generated segments are connected or not - min_size_factor: float + min_size_factor: float, optional proportion of the minimum segment size to be removed with respect to the supposed segment size (depth*width*height/n_segments) - max_size_factor: float + max_size_factor: float, optional proportion of the maximum connected segment size. A value of 3 works in most of the cases. Returns @@ -169,14 +169,14 @@ 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, spacing, enforce_connectivity) + labels = _slic_cython(image, segments, max_iter, spacing) if (enforce_connectivity): - segment_size = depth*height*width/n_segments + segment_size = depth * height * width / n_segments labels = _enforce_label_connectivity_cython(labels, n_segments, - min_size_factor*segment_size, - max_size_factor*segment_size) + min_size_factor * segment_size, + max_size_factor * segment_size) if is_2d: labels = labels[0] From a50b05eeb8fe2711302efd2bd773a818d343a6e9 Mon Sep 17 00:00:00 2001 From: Guillem Palou Visa Date: Tue, 24 Dec 2013 12:00:21 +0100 Subject: [PATCH 06/11] Fixing some code, minor revision --- skimage/segmentation/_slic.pyx | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/skimage/segmentation/_slic.pyx b/skimage/segmentation/_slic.pyx index 8f5af582..154de094 100644 --- a/skimage/segmentation/_slic.pyx +++ b/skimage/segmentation/_slic.pyx @@ -196,13 +196,13 @@ def _enforce_label_connectivity_cython(Py_ssize_t[:, :, ::1] segments, cdef Py_ssize_t zz,yy,xx - cdef Py_ssize_t[:, :] coord_list = np.zeros((max_size,3), dtype=np.intp) + cdef Py_ssize_t[:, ::1] coord_list = np.zeros((max_size,3), dtype=np.intp) #loop through all image for z in range(depth): for y in range(height): for x in range(width): - if (new_segments[z,y,x] > 0): + if (connected_segments[z,y,x] > 0): continue #find the component size adjacent = 0 @@ -229,15 +229,14 @@ def _enforce_label_connectivity_cython(Py_ssize_t[:, :, ::1] segments, coord_list[count,1] = yy coord_list[count,2] = xx count = count + 1 - elif (new_segments[zz,yy,xx] > 0 and connected_segments[zz,yy,xx] != current_new_label): + elif (connected_segments[zz,yy,xx] > 0 and connected_segments[zz,yy,xx] != current_new_label): adjacent = connected_segments[zz,yy,xx] p = p + 1 #change to an adjacent one, like in the original paper if (count < min_size): - connected_segments[*coords.T] = adjacent - #for i in range(count): - # connected_segments[coord_list[i,0],coord_list[i,1],coord_list[i,2]] = adjacent + for i in range(count): + connected_segments[coord_list[i,0],coord_list[i,1],coord_list[i,2]] = adjacent return np.asarray(connected_segments) \ No newline at end of file From 9178e77aef995f64fb8a656d746225cee9f2d839 Mon Sep 17 00:00:00 2001 From: Guillem Palou Visa Date: Tue, 24 Dec 2013 13:25:48 +0100 Subject: [PATCH 07/11] Changed variable names for readbility, fixed PEP8 compliance --- skimage/segmentation/_slic.pyx | 89 +++++++++++++------------ skimage/segmentation/tests/test_slic.py | 31 +++++++-- 2 files changed, 75 insertions(+), 45 deletions(-) diff --git a/skimage/segmentation/_slic.pyx b/skimage/segmentation/_slic.pyx index 154de094..21a5adc9 100644 --- a/skimage/segmentation/_slic.pyx +++ b/skimage/segmentation/_slic.pyx @@ -150,9 +150,9 @@ def _slic_cython(double[:, :, :, ::1] image_zyx, def _enforce_label_connectivity_cython(Py_ssize_t[:, :, ::1] segments, - Py_ssize_t n_segments, - int min_size, - int max_size): + Py_ssize_t n_segments, + int min_size, + int max_size): """ Helper function to remove small disconnected regions from the labels Parameters @@ -179,64 +179,71 @@ def _enforce_label_connectivity_cython(Py_ssize_t[:, :, ::1] segments, width = segments.shape[2] #neighborhood arrays - cdef Py_ssize_t[:] ddx = np.array((1,-1,0,0,0,0)) - cdef Py_ssize_t[:] ddy = np.array((0,0,1,-1,0,0)) - cdef Py_ssize_t[:] ddz = np.array((0,0,0,0,1,-1)) + cdef Py_ssize_t[::1] ddx = np.array((1, -1, 0, 0, 0, 0)) + cdef Py_ssize_t[::1] ddy = np.array((0, 0, 1, -1, 0, 0)) + cdef Py_ssize_t[::1] ddz = np.array((0, 0, 0, 0, 1, -1)) #new object with connected segments - cdef Py_ssize_t[:, :, ::1] connected_segments = np.zeros_like(segments) + cdef Py_ssize_t[:, :, ::1] connected_segments \ + = np.zeros_like(segments, dtype=np.intp) - cdef Py_ssize_t current_new_label = 0 + cdef Py_ssize_t current_new_label = 0 cdef Py_ssize_t label = 0 #variables for the breadth first search - cdef Py_ssize_t count = 1 - cdef Py_ssize_t p = 0 + cdef Py_ssize_t current_segment_size = 1 + cdef Py_ssize_t bfs_visited = 0 cdef Py_ssize_t adjacent - cdef Py_ssize_t zz,yy,xx + cdef Py_ssize_t zz, yy, xx - cdef Py_ssize_t[:, ::1] coord_list = np.zeros((max_size,3), dtype=np.intp) + cdef Py_ssize_t[:, ::1] coord_list = np.zeros((max_size, 3), dtype=np.intp) #loop through all image for z in range(depth): for y in range(height): for x in range(width): - if (connected_segments[z,y,x] > 0): + if connected_segments[z, y, x] > 0: continue #find the component size adjacent = 0 - label = segments[z,y,x] + label = segments[z, y, x] current_new_label += 1 - connected_segments[z,y,x] = current_new_label + connected_segments[z, y, x] = current_new_label + current_segment_size = 1 + bfs_visited = 0 + coord_list[bfs_visited, 0] = z + coord_list[bfs_visited, 1] = y + coord_list[bfs_visited, 2] = x - count = 1 - p = 0 - coord_list[p,0] = z - coord_list[p,1] = y - coord_list[p,2] = x - - #perform a breadth first search to find the size of the connected component - while (p != count): + #perform a breadth first search to find + # the size of the connected component + while bfs_visited != current_segment_size: for i in range(6): - zz = coord_list[p,0] + ddz[i] - yy = coord_list[p,1] + ddy[i] - xx = coord_list[p,2] + ddx[i] - if (xx >= 0 and xx < width and yy >= 0 and yy < height and zz >= 0 and zz < depth): - if (segments[zz,yy,xx] == label and connected_segments[zz,yy,xx] == 0): - connected_segments[zz,yy,xx] = current_new_label - coord_list[count,0] = zz - coord_list[count,1] = yy - coord_list[count,2] = xx - count = count + 1 - elif (connected_segments[zz,yy,xx] > 0 and connected_segments[zz,yy,xx] != current_new_label): - adjacent = connected_segments[zz,yy,xx] - p = p + 1 - + zz = coord_list[bfs_visited, 0] + ddz[i] + yy = coord_list[bfs_visited, 1] + ddy[i] + xx = coord_list[bfs_visited, 2] + ddx[i] + if (xx >= 0 and xx < width and + yy >= 0 and yy < height and + zz >= 0 and zz < depth): + if (segments[zz, yy, xx] == label and + connected_segments[zz, yy, xx] == 0): + connected_segments[zz, yy, xx] = \ + current_new_label + coord_list[current_segment_size, 0] = zz + coord_list[current_segment_size, 1] = yy + coord_list[current_segment_size, 2] = xx + current_segment_size += 1 + elif (connected_segments[zz, yy, xx] > 0 and + connected_segments[zz, yy, xx] != current_new_label): + adjacent = connected_segments[zz, yy, xx] + bfs_visited += 1 #change to an adjacent one, like in the original paper - if (count < min_size): - for i in range(count): - connected_segments[coord_list[i,0],coord_list[i,1],coord_list[i,2]] = adjacent + if current_segment_size < min_size: + for i in range(current_segment_size): + connected_segments[coord_list[i, 0], + coord_list[i, 1], + coord_list[i, 2]] = adjacent - return np.asarray(connected_segments) \ No newline at end of file + return np.asarray(connected_segments) diff --git a/skimage/segmentation/tests/test_slic.py b/skimage/segmentation/tests/test_slic.py index 0965e351..be0c2d79 100644 --- a/skimage/segmentation/tests/test_slic.py +++ b/skimage/segmentation/tests/test_slic.py @@ -65,7 +65,7 @@ def test_color_3d(): assert_equal(len(np.unique(seg)), 8) for s, c in zip(slices, range(8)): - assert_equal(seg[s], c+1) + assert_equal(seg[s], c + 1) def test_gray_3d(): @@ -76,7 +76,7 @@ def test_gray_3d(): midpoint = dim_size // 2 slices.append((slice(None, midpoint), slice(midpoint, None))) slices = list(it.product(*slices)) - shades = np.arange(0, 1.000001, 1.0/7) + shades = np.arange(0, 1.000001, 1.0 / 7) for s, sh in zip(slices, shades): img[s] = sh img += 0.001 * rnd.normal(size=img.shape) @@ -87,7 +87,7 @@ def test_gray_3d(): assert_equal(len(np.unique(seg)), 8) for s, c in zip(slices, range(8)): - assert_equal(seg[s], c+1) + assert_equal(seg[s], c + 1) def test_list_sigma(): @@ -106,7 +106,7 @@ def test_spacing(): 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) +1 + [0, 0, 1, 1, 1]], np.int) + 1 result_spaced = np.array([[0, 0, 0, 0, 0], [1, 1, 1, 1, 1]], np.int) + 1 img += 0.1 * rnd.normal(size=img.shape) @@ -124,7 +124,30 @@ def test_invalid_lab_conversion(): assert_raises(ValueError, slic, img, multichannel=True, convert2lab=True) +def test_enforce_connectivity(): + img = np.array([[0, 0, 0, 1, 1, 1], + [1, 0, 0, 1, 1, 0], + [0, 0, 0, 1, 1, 0]], np.float) + + segments_connected = slic(img, 2, compactness=0.0001, + enforce_connectivity=True, + convert2lab=False) + segments_disconnected = slic(img, 2, compactness=0.0001, + enforce_connectivity=False, + convert2lab=False) + + result_connected = np.array([[1, 1, 1, 2, 2, 2], + [1, 1, 1, 2, 2, 2], + [1, 1, 1, 2, 2, 2]], np.float) + + result_disconnected = np.array([[1, 1, 1, 2, 2, 2], + [2, 1, 1, 2, 2, 1], + [1, 1, 1, 2, 2, 1]], np.float) + + assert_equal(segments_connected, result_connected) + assert_equal(segments_disconnected, result_disconnected) if __name__ == '__main__': from numpy import testing + testing.run_module_suite() From 64d945da71a1be1451a370f3365938ff44ea3935 Mon Sep 17 00:00:00 2001 From: Guillem Palou Visa Date: Tue, 24 Dec 2013 16:18:57 +0100 Subject: [PATCH 08/11] fixing tests --- skimage/segmentation/_slic.pyx | 201 ++++++++++++----------- skimage/segmentation/slic_superpixels.py | 12 +- skimage/segmentation/tests/test_slic.py | 53 ++++-- 3 files changed, 145 insertions(+), 121 deletions(-) diff --git a/skimage/segmentation/_slic.pyx b/skimage/segmentation/_slic.pyx index 6f97abff..f88fa56f 100644 --- a/skimage/segmentation/_slic.pyx +++ b/skimage/segmentation/_slic.pyx @@ -9,107 +9,10 @@ cimport numpy as cnp from skimage.util import regular_grid -def _enforce_label_connectivity_cython(Py_ssize_t[:, :, ::1] nearest_segments, - Py_ssize_t n_segments, - int min_size, - int max_size): - """ Helper function to remove small disconnected regions from the labels - - Parameters - ---------- - nearest_segments : 3D array of int, shape (Z, Y, X) - The label field/superpixels found by SLIC. - n_segments: int - number of specified segments - min_size: int - minimum size of the segment - max_size: int - maximum size of the segment. This is done for performance reasons, - to pre-allocate a sufficiently large array for the breadth first search - Returns - ------- - connected_nearest_segments : 3D array of int, shape (Z, Y, X) - A label field with connected labels starting at label=1 - """ - - #get image dimensions - cdef Py_ssize_t depth, height, width - depth = nearest_segments.shape[0] - height = nearest_segments.shape[1] - width = nearest_segments.shape[2] - - #neighborhood arrays - cdef Py_ssize_t[:] ddx = np.array((1,-1,0,0,0,0)) - cdef Py_ssize_t[:] ddy = np.array((0,0,1,-1,0,0)) - cdef Py_ssize_t[:] ddz = np.array((0,0,0,0,1,-1)) - - #new object with connected segments - cdef Py_ssize_t[:, :, ::1] new_nearest_segments \ - = np.zeros((depth, height, width), dtype=np.intp) - - cdef Py_ssize_t current_new_label = 0 - cdef Py_ssize_t label = 0 - - #variables for the breadth first search - cdef Py_ssize_t count = 1 - cdef Py_ssize_t p = 0 - cdef Py_ssize_t adjacent - - cdef Py_ssize_t zz,yy,xx - - cdef Py_ssize_t[:, :] coord_list \ - = np.zeros((max_size,3), dtype=np.intp) - - #loop through all image - for z in range(depth): - for y in range(height): - for x in range(width): - if (new_nearest_segments[z,y,x] > 0): - continue - #find the component size - adjacent = 0 - label = nearest_segments[z,y,x] - current_new_label = current_new_label+1 - new_nearest_segments[z,y,x] = current_new_label - - count = 1 - p = 0 - coord_list[p,0] = z - coord_list[p,1] = y - coord_list[p,2] = x - - #perform a breadth first search to find the size of the connected component - while (p != count): - for i in range(6): - zz = coord_list[p,0] + ddz[i] - yy = coord_list[p,1] + ddy[i] - xx = coord_list[p,2] + ddx[i] - if (xx >= 0 and xx < width and yy >= 0 and yy < height and zz >= 0 and zz < depth): - if (nearest_segments[zz,yy,xx] == label and new_nearest_segments[zz,yy,xx] == 0): - new_nearest_segments[zz,yy,xx] = current_new_label - coord_list[count,0] = zz - coord_list[count,1] = yy - coord_list[count,2] = xx - count = count + 1 - elif (new_nearest_segments[zz,yy,xx] > 0 and new_nearest_segments[zz,yy,xx] != current_new_label): - adjacent = new_nearest_segments[zz,yy,xx] - p = p + 1 - - - #change to an adjacent one, like in the original paper - if (count < min_size): - #print("Changing segment {0} label {1} ".format(current_new_label, label)) - for i in range(count): - new_nearest_segments[coord_list[i,0],coord_list[i,1],coord_list[i,2]] = adjacent - - return np.asarray(new_nearest_segments) - def _slic_cython(double[:, :, :, ::1] image_zyx, double[:, ::1] segments, Py_ssize_t max_iter, - double[::1] spacing, - int enforce_connectivity, - Py_ssize_t min_size = True): + double[::1] spacing): """Helper function for SLIC segmentation. Parameters @@ -124,8 +27,6 @@ def _slic_cython(double[:, :, :, ::1] image_zyx, The voxel spacing along each image dimension. This parameter controls the weights of the distances along z, y, and x during k-means clustering. - enforce_connectivity: int indicating whether the returned label - field must have connected labels Returns ------- @@ -244,3 +145,103 @@ def _slic_cython(double[:, :, :, ::1] image_zyx, segments[k, c] /= n_segment_elems[k] return np.asarray(nearest_segments) + + +def _enforce_label_connectivity_cython(Py_ssize_t[:, :, ::1] segments, + Py_ssize_t n_segments, + int min_size, + int max_size): + """ Helper function to remove small disconnected regions from the labels + + Parameters + ---------- + segments : 3D array of int, shape (Z, Y, X) + The label field/superpixels found by SLIC. + n_segments: int + number of specified segments + min_size: int + minimum size of the segment + max_size: int + maximum size of the segment. This is done for performance reasons, + to pre-allocate a sufficiently large array for the breadth first search + Returns + ------- + connected_segments : 3D array of int, shape (Z, Y, X) + A label field with connected labels starting at label=1 + """ + + #get image dimensions + cdef Py_ssize_t depth, height, width + depth = segments.shape[0] + height = segments.shape[1] + width = segments.shape[2] + + #neighborhood arrays + cdef Py_ssize_t[::1] ddx = np.array((1, -1, 0, 0, 0, 0)) + cdef Py_ssize_t[::1] ddy = np.array((0, 0, 1, -1, 0, 0)) + cdef Py_ssize_t[::1] ddz = np.array((0, 0, 0, 0, 1, -1)) + + #new object with connected segments + cdef Py_ssize_t[:, :, ::1] connected_segments \ + = np.zeros_like(segments, dtype=np.intp) + + cdef Py_ssize_t current_new_label = 0 + cdef Py_ssize_t label = 0 + + #variables for the breadth first search + cdef Py_ssize_t current_segment_size = 1 + cdef Py_ssize_t bfs_visited = 0 + cdef Py_ssize_t adjacent + + cdef Py_ssize_t zz, yy, xx + + cdef Py_ssize_t[:, ::1] coord_list = np.zeros((max_size, 3), dtype=np.intp) + + #loop through all image + for z in range(depth): + for y in range(height): + for x in range(width): + if connected_segments[z, y, x] > 0: + continue + #find the component size + adjacent = 0 + label = segments[z, y, x] + current_new_label += 1 + connected_segments[z, y, x] = current_new_label + current_segment_size = 1 + bfs_visited = 0 + coord_list[bfs_visited, 0] = z + coord_list[bfs_visited, 1] = y + coord_list[bfs_visited, 2] = x + + #perform a breadth first search to find + # the size of the connected component + while bfs_visited != current_segment_size: + for i in range(6): + zz = coord_list[bfs_visited, 0] + ddz[i] + yy = coord_list[bfs_visited, 1] + ddy[i] + xx = coord_list[bfs_visited, 2] + ddx[i] + if (xx >= 0 and xx < width and + yy >= 0 and yy < height and + zz >= 0 and zz < depth): + if (segments[zz, yy, xx] == label and + connected_segments[zz, yy, xx] == 0): + connected_segments[zz, yy, xx] = \ + current_new_label + coord_list[current_segment_size, 0] = zz + coord_list[current_segment_size, 1] = yy + coord_list[current_segment_size, 2] = xx + current_segment_size += 1 + elif (connected_segments[zz, yy, xx] > 0 and + connected_segments[zz, yy, xx] != current_new_label): + adjacent = connected_segments[zz, yy, xx] + bfs_visited += 1 + + #change to an adjacent one, like in the original paper + if current_segment_size < min_size: + for i in range(current_segment_size): + connected_segments[coord_list[i, 0], + coord_list[i, 1], + coord_list[i, 2]] = adjacent + + return np.asarray(connected_segments) diff --git a/skimage/segmentation/slic_superpixels.py b/skimage/segmentation/slic_superpixels.py index 60d4ac13..98801c62 100644 --- a/skimage/segmentation/slic_superpixels.py +++ b/skimage/segmentation/slic_superpixels.py @@ -50,10 +50,10 @@ def slic(image, n_segments=100, compactness=10., max_iter=10, sigma=None, Synonym for `compactness`. This keyword is deprecated. enforce_connectivity: bool, optional Whether the generated segments are connected or not - min_size_factor: float + min_size_factor: float, optional proportion of the minimum segment size to be removed with respect to the supposed segment size (depth*width*height/n_segments) - max_size_factor: float + max_size_factor: float, optional proportion of the maximum connected segment size. A value of 3 works in most of the cases. Returns @@ -169,14 +169,14 @@ 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, spacing, enforce_connectivity) + labels = _slic_cython(image, segments, max_iter, spacing) if (enforce_connectivity): - segment_size = depth*height*width/n_segments + segment_size = depth * height * width / n_segments labels = _enforce_label_connectivity_cython(labels, n_segments, - min_size_factor*segment_size, - max_size_factor*segment_size) + min_size_factor * segment_size, + max_size_factor * segment_size) if is_2d: labels = labels[0] diff --git a/skimage/segmentation/tests/test_slic.py b/skimage/segmentation/tests/test_slic.py index 15d1fe6d..be0c2d79 100644 --- a/skimage/segmentation/tests/test_slic.py +++ b/skimage/segmentation/tests/test_slic.py @@ -21,10 +21,10 @@ def test_color_2d(): # we expect 4 segments assert_equal(len(np.unique(seg)), 4) assert_equal(seg.shape, img.shape[:-1]) - assert_equal(seg[:10, :10], 0) - assert_equal(seg[10:, :10], 2) - assert_equal(seg[:10, 10:], 1) - assert_equal(seg[10:, 10:], 3) + assert_equal(seg[:10, :10], 1) + assert_equal(seg[10:, :10], 3) + assert_equal(seg[:10, 10:], 2) + assert_equal(seg[10:, 10:], 4) def test_gray_2d(): @@ -41,10 +41,10 @@ def test_gray_2d(): assert_equal(len(np.unique(seg)), 4) assert_equal(seg.shape, img.shape) - assert_equal(seg[:10, :10], 0) - assert_equal(seg[10:, :10], 2) - assert_equal(seg[:10, 10:], 1) - assert_equal(seg[10:, 10:], 3) + assert_equal(seg[:10, :10], 1) + assert_equal(seg[10:, :10], 3) + assert_equal(seg[:10, 10:], 2) + assert_equal(seg[10:, 10:], 4) def test_color_3d(): @@ -65,7 +65,7 @@ def test_color_3d(): assert_equal(len(np.unique(seg)), 8) for s, c in zip(slices, range(8)): - assert_equal(seg[s], c) + assert_equal(seg[s], c + 1) def test_gray_3d(): @@ -76,7 +76,7 @@ def test_gray_3d(): midpoint = dim_size // 2 slices.append((slice(None, midpoint), slice(midpoint, None))) slices = list(it.product(*slices)) - shades = np.arange(0, 1.000001, 1.0/7) + shades = np.arange(0, 1.000001, 1.0 / 7) for s, sh in zip(slices, shades): img[s] = sh img += 0.001 * rnd.normal(size=img.shape) @@ -87,7 +87,7 @@ def test_gray_3d(): assert_equal(len(np.unique(seg)), 8) for s, c in zip(slices, range(8)): - assert_equal(seg[s], c) + assert_equal(seg[s], c + 1) def test_list_sigma(): @@ -96,7 +96,7 @@ def test_list_sigma(): [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) + [0, 0, 0, 1, 1, 1]], np.int) + 1 seg_sigma = slic(img, n_segments=2, sigma=[1, 50, 1], multichannel=False) assert_equal(seg_sigma, result_sigma) @@ -106,9 +106,9 @@ def test_spacing(): 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) + [0, 0, 1, 1, 1]], np.int) + 1 result_spaced = np.array([[0, 0, 0, 0, 0], - [1, 1, 1, 1, 1]], np.int) + [1, 1, 1, 1, 1]], np.int) + 1 img += 0.1 * rnd.normal(size=img.shape) seg_non_spaced = slic(img, n_segments=2, sigma=0, multichannel=False, compactness=1.0) @@ -120,11 +120,34 @@ def test_spacing(): def test_invalid_lab_conversion(): img = np.array([[1, 1, 1, 0, 0], - [1, 1, 0, 0, 0]], np.float) + [1, 1, 0, 0, 0]], np.float) + 1 assert_raises(ValueError, slic, img, multichannel=True, convert2lab=True) +def test_enforce_connectivity(): + img = np.array([[0, 0, 0, 1, 1, 1], + [1, 0, 0, 1, 1, 0], + [0, 0, 0, 1, 1, 0]], np.float) + + segments_connected = slic(img, 2, compactness=0.0001, + enforce_connectivity=True, + convert2lab=False) + segments_disconnected = slic(img, 2, compactness=0.0001, + enforce_connectivity=False, + convert2lab=False) + + result_connected = np.array([[1, 1, 1, 2, 2, 2], + [1, 1, 1, 2, 2, 2], + [1, 1, 1, 2, 2, 2]], np.float) + + result_disconnected = np.array([[1, 1, 1, 2, 2, 2], + [2, 1, 1, 2, 2, 1], + [1, 1, 1, 2, 2, 1]], np.float) + + assert_equal(segments_connected, result_connected) + assert_equal(segments_disconnected, result_disconnected) if __name__ == '__main__': from numpy import testing + testing.run_module_suite() From 4982b00f0c1362417834f0d944615c90a8858e7c Mon Sep 17 00:00:00 2001 From: Guillem Palou Visa Date: Tue, 24 Dec 2013 18:45:55 +0100 Subject: [PATCH 09/11] Labels start at 0, for backward compatibility Code is PEP8 compliant --- skimage/segmentation/_slic.pyx | 23 +++++++------- skimage/segmentation/slic_superpixels.py | 2 +- skimage/segmentation/tests/test_slic.py | 38 ++++++++++++------------ 3 files changed, 32 insertions(+), 31 deletions(-) diff --git a/skimage/segmentation/_slic.pyx b/skimage/segmentation/_slic.pyx index 21a5adc9..5ea3d832 100644 --- a/skimage/segmentation/_slic.pyx +++ b/skimage/segmentation/_slic.pyx @@ -116,7 +116,7 @@ def _slic_cython(double[:, :, :, ::1] image_zyx, - segments[k, c]) ** 2 if distance[z, y, x] > dist_center: # segments start at 1 - nearest_segments[z, y, x] = k+1 + nearest_segments[z, y, x] = k distance[z, y, x] = dist_center change = 1 @@ -133,7 +133,7 @@ def _slic_cython(double[:, :, :, ::1] image_zyx, for y in range(height): for x in range(width): #compensate the label offset 1 - k = nearest_segments[z, y, x] - 1 + k = nearest_segments[z, y, x] n_segment_elems[k] += 1 segments[k, 0] += z segments[k, 1] += y @@ -183,9 +183,9 @@ def _enforce_label_connectivity_cython(Py_ssize_t[:, :, ::1] segments, cdef Py_ssize_t[::1] ddy = np.array((0, 0, 1, -1, 0, 0)) cdef Py_ssize_t[::1] ddz = np.array((0, 0, 0, 0, 1, -1)) - #new object with connected segments + #new object with connected segments initialized to -1 cdef Py_ssize_t[:, :, ::1] connected_segments \ - = np.zeros_like(segments, dtype=np.intp) + = -1 * np.ones_like(segments, dtype=np.intp) cdef Py_ssize_t current_new_label = 0 cdef Py_ssize_t label = 0 @@ -203,12 +203,11 @@ def _enforce_label_connectivity_cython(Py_ssize_t[:, :, ::1] segments, for z in range(depth): for y in range(height): for x in range(width): - if connected_segments[z, y, x] > 0: + if connected_segments[z, y, x] >= 0: continue #find the component size adjacent = 0 label = segments[z, y, x] - current_new_label += 1 connected_segments[z, y, x] = current_new_label current_segment_size = 1 bfs_visited = 0 @@ -223,18 +222,18 @@ def _enforce_label_connectivity_cython(Py_ssize_t[:, :, ::1] segments, zz = coord_list[bfs_visited, 0] + ddz[i] yy = coord_list[bfs_visited, 1] + ddy[i] xx = coord_list[bfs_visited, 2] + ddx[i] - if (xx >= 0 and xx < width and - yy >= 0 and yy < height and - zz >= 0 and zz < depth): + if (0 <= xx < width and + 0 <= yy < height and + 0 <= zz < depth): if (segments[zz, yy, xx] == label and - connected_segments[zz, yy, xx] == 0): + connected_segments[zz, yy, xx] == -1): connected_segments[zz, yy, xx] = \ current_new_label coord_list[current_segment_size, 0] = zz coord_list[current_segment_size, 1] = yy coord_list[current_segment_size, 2] = xx current_segment_size += 1 - elif (connected_segments[zz, yy, xx] > 0 and + elif (connected_segments[zz, yy, xx] >= 0 and connected_segments[zz, yy, xx] != current_new_label): adjacent = connected_segments[zz, yy, xx] bfs_visited += 1 @@ -245,5 +244,7 @@ def _enforce_label_connectivity_cython(Py_ssize_t[:, :, ::1] segments, connected_segments[coord_list[i, 0], coord_list[i, 1], coord_list[i, 2]] = adjacent + else: + current_new_label += 1 return np.asarray(connected_segments) diff --git a/skimage/segmentation/slic_superpixels.py b/skimage/segmentation/slic_superpixels.py index 98801c62..2eb10c3a 100644 --- a/skimage/segmentation/slic_superpixels.py +++ b/skimage/segmentation/slic_superpixels.py @@ -171,7 +171,7 @@ def slic(image, n_segments=100, compactness=10., max_iter=10, sigma=None, labels = _slic_cython(image, segments, max_iter, spacing) - if (enforce_connectivity): + if enforce_connectivity: segment_size = depth * height * width / n_segments labels = _enforce_label_connectivity_cython(labels, n_segments, diff --git a/skimage/segmentation/tests/test_slic.py b/skimage/segmentation/tests/test_slic.py index be0c2d79..15f03437 100644 --- a/skimage/segmentation/tests/test_slic.py +++ b/skimage/segmentation/tests/test_slic.py @@ -21,10 +21,10 @@ def test_color_2d(): # we expect 4 segments assert_equal(len(np.unique(seg)), 4) assert_equal(seg.shape, img.shape[:-1]) - assert_equal(seg[:10, :10], 1) - assert_equal(seg[10:, :10], 3) - assert_equal(seg[:10, 10:], 2) - assert_equal(seg[10:, 10:], 4) + assert_equal(seg[:10, :10], 0) + assert_equal(seg[10:, :10], 2) + assert_equal(seg[:10, 10:], 1) + assert_equal(seg[10:, 10:], 3) def test_gray_2d(): @@ -41,10 +41,10 @@ def test_gray_2d(): assert_equal(len(np.unique(seg)), 4) assert_equal(seg.shape, img.shape) - assert_equal(seg[:10, :10], 1) - assert_equal(seg[10:, :10], 3) - assert_equal(seg[:10, 10:], 2) - assert_equal(seg[10:, 10:], 4) + assert_equal(seg[:10, :10], 0) + assert_equal(seg[10:, :10], 2) + assert_equal(seg[:10, 10:], 1) + assert_equal(seg[10:, 10:], 3) def test_color_3d(): @@ -65,7 +65,7 @@ def test_color_3d(): assert_equal(len(np.unique(seg)), 8) for s, c in zip(slices, range(8)): - assert_equal(seg[s], c + 1) + assert_equal(seg[s], c) def test_gray_3d(): @@ -87,7 +87,7 @@ def test_gray_3d(): assert_equal(len(np.unique(seg)), 8) for s, c in zip(slices, range(8)): - assert_equal(seg[s], c + 1) + assert_equal(seg[s], c) def test_list_sigma(): @@ -96,7 +96,7 @@ def test_list_sigma(): [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) + 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) @@ -106,9 +106,9 @@ def test_spacing(): 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) + 1 + [0, 0, 1, 1, 1]], np.int) result_spaced = np.array([[0, 0, 0, 0, 0], - [1, 1, 1, 1, 1]], np.int) + 1 + [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) @@ -136,13 +136,13 @@ def test_enforce_connectivity(): enforce_connectivity=False, convert2lab=False) - result_connected = np.array([[1, 1, 1, 2, 2, 2], - [1, 1, 1, 2, 2, 2], - [1, 1, 1, 2, 2, 2]], np.float) + result_connected = np.array([[0, 0, 0, 1, 1, 1], + [0, 0, 0, 1, 1, 1], + [0, 0, 0, 1, 1, 1]], np.float) - result_disconnected = np.array([[1, 1, 1, 2, 2, 2], - [2, 1, 1, 2, 2, 1], - [1, 1, 1, 2, 2, 1]], np.float) + result_disconnected = np.array([[0, 0, 0, 1, 1, 1], + [1, 0, 0, 1, 1, 0], + [0, 0, 0, 1, 1, 0]], np.float) assert_equal(segments_connected, result_connected) assert_equal(segments_disconnected, result_disconnected) From f461d7600487285dbe193c5dad108fd36d07c6fb Mon Sep 17 00:00:00 2001 From: Guillem Palou Visa Date: Sun, 12 Jan 2014 17:03:10 +0000 Subject: [PATCH 10/11] Fixed minor documentation issues --- skimage/_build.py | 2 +- skimage/segmentation/_slic.pyx | 26 +++++++++++------------- skimage/segmentation/slic_superpixels.py | 14 +++++++------ 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/skimage/_build.py b/skimage/_build.py index 38239e4b..dbff4818 100644 --- a/skimage/_build.py +++ b/skimage/_build.py @@ -86,4 +86,4 @@ def _changed(filename): with open(filename_cache, 'wb') as cf: cf.write(md5_new.encode('utf-8')) - return md5_cached != md5_new + return md5_cached != md5_new.encode('utf-8') diff --git a/skimage/segmentation/_slic.pyx b/skimage/segmentation/_slic.pyx index 5ea3d832..64d09520 100644 --- a/skimage/segmentation/_slic.pyx +++ b/skimage/segmentation/_slic.pyx @@ -115,7 +115,6 @@ def _slic_cython(double[:, :, :, ::1] image_zyx, dist_center += (image_zyx[z, y, x, c - 3] - segments[k, c]) ** 2 if distance[z, y, x] > dist_center: - # segments start at 1 nearest_segments[z, y, x] = k distance[z, y, x] = dist_center change = 1 @@ -132,7 +131,6 @@ def _slic_cython(double[:, :, :, ::1] image_zyx, for z in range(depth): for y in range(height): for x in range(width): - #compensate the label offset 1 k = nearest_segments[z, y, x] n_segment_elems[k] += 1 segments[k, 0] += z @@ -151,8 +149,8 @@ def _slic_cython(double[:, :, :, ::1] image_zyx, def _enforce_label_connectivity_cython(Py_ssize_t[:, :, ::1] segments, Py_ssize_t n_segments, - int min_size, - int max_size): + Py_ssize_t min_size, + Py_ssize_t max_size): """ Helper function to remove small disconnected regions from the labels Parameters @@ -160,11 +158,11 @@ def _enforce_label_connectivity_cython(Py_ssize_t[:, :, ::1] segments, segments : 3D array of int, shape (Z, Y, X) The label field/superpixels found by SLIC. n_segments: int - number of specified segments + Number of specified segments min_size: int - minimum size of the segment + Minimum size of the segment max_size: int - maximum size of the segment. This is done for performance reasons, + Maximum size of the segment. This is done for performance reasons, to pre-allocate a sufficiently large array for the breadth first search Returns ------- @@ -172,25 +170,25 @@ def _enforce_label_connectivity_cython(Py_ssize_t[:, :, ::1] segments, A label field with connected labels starting at label=1 """ - #get image dimensions + # get image dimensions cdef Py_ssize_t depth, height, width depth = segments.shape[0] height = segments.shape[1] width = segments.shape[2] - #neighborhood arrays + # neighborhood arrays cdef Py_ssize_t[::1] ddx = np.array((1, -1, 0, 0, 0, 0)) cdef Py_ssize_t[::1] ddy = np.array((0, 0, 1, -1, 0, 0)) cdef Py_ssize_t[::1] ddz = np.array((0, 0, 0, 0, 1, -1)) - #new object with connected segments initialized to -1 + # new object with connected segments initialized to -1 cdef Py_ssize_t[:, :, ::1] connected_segments \ = -1 * np.ones_like(segments, dtype=np.intp) cdef Py_ssize_t current_new_label = 0 cdef Py_ssize_t label = 0 - #variables for the breadth first search + # variables for the breadth first search cdef Py_ssize_t current_segment_size = 1 cdef Py_ssize_t bfs_visited = 0 cdef Py_ssize_t adjacent @@ -199,13 +197,13 @@ def _enforce_label_connectivity_cython(Py_ssize_t[:, :, ::1] segments, cdef Py_ssize_t[:, ::1] coord_list = np.zeros((max_size, 3), dtype=np.intp) - #loop through all image + # loop through all image for z in range(depth): for y in range(height): for x in range(width): if connected_segments[z, y, x] >= 0: continue - #find the component size + # find the component size adjacent = 0 label = segments[z, y, x] connected_segments[z, y, x] = current_new_label @@ -238,7 +236,7 @@ def _enforce_label_connectivity_cython(Py_ssize_t[:, :, ::1] segments, adjacent = connected_segments[zz, yy, xx] bfs_visited += 1 - #change to an adjacent one, like in the original paper + # change to an adjacent one, like in the original paper if current_segment_size < min_size: for i in range(current_segment_size): connected_segments[coord_list[i, 0], diff --git a/skimage/segmentation/slic_superpixels.py b/skimage/segmentation/slic_superpixels.py index 2eb10c3a..bea7c7e2 100644 --- a/skimage/segmentation/slic_superpixels.py +++ b/skimage/segmentation/slic_superpixels.py @@ -12,7 +12,7 @@ from skimage.color import rgb2lab def slic(image, n_segments=100, compactness=10., max_iter=10, sigma=None, spacing=None, multichannel=True, convert2lab=True, ratio=None, - enforce_connectivity=True, min_size_factor=0.5, max_size_factor=3): + enforce_connectivity=False, min_size_factor=0.5, max_size_factor=3): """Segments image using k-means clustering in Color-(x,y,z) space. Parameters @@ -51,10 +51,10 @@ def slic(image, n_segments=100, compactness=10., max_iter=10, sigma=None, enforce_connectivity: bool, optional Whether the generated segments are connected or not min_size_factor: float, optional - proportion of the minimum segment size to be removed with respect - to the supposed segment size (depth*width*height/n_segments) + Proportion of the minimum segment size to be removed with respect + to the supposed segment size ```depth*width*height/n_segments``` max_size_factor: float, optional - proportion of the maximum connected segment size. A value of 3 works + Proportion of the maximum connected segment size. A value of 3 works in most of the cases. Returns ------- @@ -173,10 +173,12 @@ def slic(image, n_segments=100, compactness=10., max_iter=10, sigma=None, if enforce_connectivity: segment_size = depth * height * width / n_segments + min_size = int(min_size_factor * segment_size) + max_size = int(max_size_factor * segment_size) labels = _enforce_label_connectivity_cython(labels, n_segments, - min_size_factor * segment_size, - max_size_factor * segment_size) + min_size, + max_size) if is_2d: labels = labels[0] From 15c298915871f5d7b88776274b2d63c22efd1f91 Mon Sep 17 00:00:00 2001 From: Guillem Palou Visa Date: Fri, 17 Jan 2014 16:52:44 +0100 Subject: [PATCH 11/11] Added deprecation warning --- TODO.txt | 3 ++- skimage/segmentation/slic_superpixels.py | 7 ++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/TODO.txt b/TODO.txt index 4fe8e90d..bbf248be 100644 --- a/TODO.txt +++ b/TODO.txt @@ -1,7 +1,8 @@ Version 0.11 ------------ * Remove deprecated `reverse_map` parameter of `skimage.transform.warp` - +* Change depecrated `enforce_connectivity=False`on skimage.segmentation.slic + and set it to True as default Version 0.10 ------------ diff --git a/skimage/segmentation/slic_superpixels.py b/skimage/segmentation/slic_superpixels.py index bea7c7e2..9be5aa62 100644 --- a/skimage/segmentation/slic_superpixels.py +++ b/skimage/segmentation/slic_superpixels.py @@ -48,7 +48,7 @@ def slic(image, n_segments=100, compactness=10., max_iter=10, sigma=None, recommended. ratio : float, optional Synonym for `compactness`. This keyword is deprecated. - enforce_connectivity: bool, optional + enforce_connectivity: bool, optional (default False) Whether the generated segments are connected or not min_size_factor: float, optional Proportion of the minimum segment size to be removed with respect @@ -112,6 +112,11 @@ def slic(image, n_segments=100, compactness=10., max_iter=10, sigma=None, 'instead.') compactness = ratio + if enforce_connectivity is None: + warnings.warn('Deprecation: enforce_connectivity will default to' + ' True in future versions.') + enforce_connectivity = False + image = img_as_float(image) is_2d = False if image.ndim == 2: