From 0bd1d4490ee63baa5f65d2d181e6bd98b158edcc Mon Sep 17 00:00:00 2001 From: Michal Romaniuk Date: Wed, 27 Nov 2013 17:07:48 +0000 Subject: [PATCH] Added SLIC-zero to SLIC and changed SLIC implementation slightly --- skimage/segmentation/_slic.pyx | 95 ++++++++++++++++++++---- skimage/segmentation/slic_superpixels.py | 20 +++-- 2 files changed, 95 insertions(+), 20 deletions(-) diff --git a/skimage/segmentation/_slic.pyx b/skimage/segmentation/_slic.pyx index 64d09520..2c4d3199 100644 --- a/skimage/segmentation/_slic.pyx +++ b/skimage/segmentation/_slic.pyx @@ -3,6 +3,7 @@ #cython: nonecheck=False #cython: wraparound=False from libc.float cimport DBL_MAX +from cpython cimport bool import numpy as np cimport numpy as cnp @@ -11,8 +12,11 @@ from skimage.util import regular_grid def _slic_cython(double[:, :, :, ::1] image_zyx, double[:, ::1] segments, + float step, Py_ssize_t max_iter, - double[::1] spacing): + double[::1] spacing, + bool slic_zero, + ): """Helper function for SLIC segmentation. Parameters @@ -21,12 +25,17 @@ def _slic_cython(double[:, :, :, ::1] image_zyx, The input image. segments : 2D array of double, shape (N, 3 + C) The initial centroids obtained by SLIC as [Z, Y, X, C...]. + step : double + The size of the step between two seeds in voxels. 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. + k-means clustering. + slic_zero : bool + True to run SLIC-ZERO, False to run original SLIC. + Returns ------- @@ -85,6 +94,14 @@ def _slic_cython(double[:, :, :, ::1] image_zyx, sy = spacing[1] sx = spacing[2] + # The colors are scaled before being passed to _slic_cython so + # max_color_sq can be initialised as all ones + cdef double[::1] max_dist_color = np.ones(n_segments, dtype=np.double) + cdef double dist_color + + # The reference implementation calls this invxywt + cdef double zyx_wt = float(1) / (step ** 2) + for i in range(max_iter): change = 0 distance[:, :, :] = DBL_MAX @@ -105,19 +122,42 @@ def _slic_cython(double[:, :, :, ::1] image_zyx, x_min = max(cx - 2 * step_x, 0) x_max = min(cx + 2 * step_x + 1, width) - for z in range(z_min, z_max): - dz = (sz * (cz - z)) ** 2 - for y in range(y_min, y_max): - dy = (sy * (cy - y)) ** 2 - for x in range(x_min, x_max): - 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 - if distance[z, y, x] > dist_center: - nearest_segments[z, y, x] = k - distance[z, y, x] = dist_center - change = 1 + # The loop is duplicated to avoid looking up slic_zero in every + # iteration but perhaps it's better to improve readability at + # the cost of performance. + if slic_zero: + for z in range(z_min, z_max): + dz = (sz * (cz - z)) ** 2 + for y in range(y_min, y_max): + dy = (sy * (cy - y)) ** 2 + for x in range(x_min, x_max): + dist_center = (dz + dy + (sx * (cx - x)) ** 2) * zyx_wt + dist_color = 0 + for c in range(3, n_features): + dist_color += (image_zyx[z, y, x, c - 3] + - segments[k, c]) ** 2 + + dist_center += dist_color / max_dist_color[k] + + if distance[z, y, x] > dist_center: + nearest_segments[z, y, x] = k + distance[z, y, x] = dist_center + change = 1 + else: + for z in range(z_min, z_max): + dz = (sz * (cz - z)) ** 2 + for y in range(y_min, y_max): + dy = (sy * (cy - y)) ** 2 + for x in range(x_min, x_max): + dist_center = (dz + dy + (sx * (cx - x)) ** 2) * zyx_wt + for c in range(3, n_features): + 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 + distance[z, y, x] = dist_center + change = 1 # stop if no pixel changed its segment if change == 0: @@ -144,6 +184,31 @@ def _slic_cython(double[:, :, :, ::1] image_zyx, for c in range(n_features): segments[k, c] /= n_segment_elems[k] + # If in SLICO mode, update the color distance maxima + if slic_zero: + for z in range(depth): + for y in range(height): + for x in range(width): + + k = nearest_segments[z, y, x] + dist_color = 0 + + for c in range(3, n_features): + dist_color += (image_zyx[z, y, x, c - 3] + - segments[k, c]) ** 2 + + # The reference implementation seems to only change + # the color if it increases from previous iteration + if max_dist_color[k] < dist_color: + max_dist_color[k] = dist_color + + ## DEBUG + print('Iter %d' % (i,)) + print(str(np.asarray(max_dist_color))) + + print('Image: ') + print(str(np.asarray(image_zyx))) + return np.asarray(nearest_segments) diff --git a/skimage/segmentation/slic_superpixels.py b/skimage/segmentation/slic_superpixels.py index 9be5aa62..3c447ff8 100644 --- a/skimage/segmentation/slic_superpixels.py +++ b/skimage/segmentation/slic_superpixels.py @@ -25,7 +25,8 @@ def slic(image, n_segments=100, compactness=10., max_iter=10, sigma=None, compactness : float, optional Balances color-space proximity and image-space proximity. Higher values give more weight to image-space. As `compactness` tends to - infinity, superpixel shapes become square/cubic. + infinity, superpixel shapes become square/cubic. In SLICO mode, this + is the initial compactness. max_iter : int, optional Maximum number of iterations of k-means. sigma : float or (3,) array-like of floats, optional @@ -46,6 +47,8 @@ def slic(image, n_segments=100, compactness=10., max_iter=10, sigma=None, Whether the input should be converted to Lab colorspace prior to segmentation. For this purpose, the input is assumed to be RGB. Highly recommended. + slic_zero: bool, optional + True to run SLIC-zero, False to run original SLIC. ratio : float, optional Synonym for `compactness`. This keyword is deprecated. enforce_connectivity: bool, optional (default False) @@ -171,10 +174,17 @@ def slic(image, n_segments=100, compactness=10., max_iter=10, sigma=None, # we do the scaling of ratio in the same way as in the SLIC paper # so the values have the same meaning - ratio = float(max((step_z, step_y, step_x))) / compactness - image = np.ascontiguousarray(image * ratio) + step = float(max((step_z, step_y, step_x))) + ratio = float(1) / compactness + + if slic_zero: + image = np.ascontiguousarray(image * ratio) + else: + image = np.ascontiguousarray(image * ratio) - labels = _slic_cython(image, segments, max_iter, spacing) + # _slic_cython expects the image in zyx format... but isn't image in xyz + # format??? + labels = _slic_cython(image, segments, step, max_iter, spacing, slic_zero) if enforce_connectivity: segment_size = depth * height * width / n_segments @@ -188,4 +198,4 @@ def slic(image, n_segments=100, compactness=10., max_iter=10, sigma=None, if is_2d: labels = labels[0] - return labels + return labels \ No newline at end of file