diff --git a/TODO.txt b/TODO.txt index d1ceea93..a6dc649e 100644 --- a/TODO.txt +++ b/TODO.txt @@ -37,7 +37,3 @@ Version 0.13 _shared/interpolation.pyx, transform/_geometric.py, and transform/_warps.py -Version 0.12 ------------- -* Change `label` to mark background as 0, not -1, which is consistent with - SciPy's labelling. diff --git a/doc/examples/segmentation/plot_label.py b/doc/examples/segmentation/plot_label.py index f80271ce..310c70a1 100644 --- a/doc/examples/segmentation/plot_label.py +++ b/doc/examples/segmentation/plot_label.py @@ -37,8 +37,6 @@ clear_border(cleared) # label image regions label_image = label(cleared) -borders = np.logical_xor(bw, cleared) -label_image[borders] = -1 image_label_overlay = label2rgb(label_image, image=image) fig, ax = plt.subplots(ncols=1, nrows=1, figsize=(6, 6)) diff --git a/doc/release/release_0.12.txt b/doc/release/release_0.12.txt index 84a8dfae..f1f042ef 100644 --- a/doc/release/release_0.12.txt +++ b/doc/release/release_0.12.txt @@ -29,6 +29,9 @@ include: - Synthetic 2-D and 3-D binary data with rounded blobs (#1485) - Plugin for ``imageio`` library (#1575) - Inpainting algorithm (#1804) +- New handling of background pixels for ``measure.label``: 0-valued + pixels are considered as background by default, and the label of + background pixels is 0. - Partial support of 3-D images for ``skimage.measure.regionprops`` (#1505) - Multi-block local binary patterns (MB-LBP) for texture classification (#1536) diff --git a/skimage/measure/_ccomp.pyx b/skimage/measure/_ccomp.pyx index e7088d24..77d237eb 100644 --- a/skimage/measure/_ccomp.pyx +++ b/skimage/measure/_ccomp.pyx @@ -47,17 +47,14 @@ ctypedef struct bginfo: cdef void get_bginfo(background_val, bginfo *ret) except *: if background_val is None: - warn(DeprecationWarning( - 'The default value for `background` will change to 0 in v0.12' - )) - ret.background_val = -1 + ret.background_val = 0 else: ret.background_val = background_val # The node -999 doesn't exist, it will get subsituted by a meaningful value # upon the first background pixel occurence ret.background_node = -999 - ret.background_label = -1 + ret.background_label = 0 # A pixel has neighbors that have already been scanned. @@ -361,7 +358,7 @@ def undo_reshape_array(arr, swaps): # Connected components search as described in Fiorio et al. -def label(input, neighbors=None, background=None, return_num=False, +def label(input, neighbors=None, background=0, return_num=False, connectivity=None): r"""Label connected regions of an integer array. @@ -389,14 +386,15 @@ def label(input, neighbors=None, background=None, return_num=False, **Deprecated, use ``connectivity`` instead.** background : int, optional Consider all pixels with this value as background pixels, and label - them as -1. (Note: background pixels will be labeled as 0 starting with - version 0.12). + them as 0. By default, 0-valued pixels are considered as background + pixels. return_num : bool, optional Whether to return the number of assigned labels. connectivity : int, optional Maximum number of orthogonal hops to consider a pixel/voxel as a neighbor. - Accepted values are ranging from 1 to input.ndim. + Accepted values are ranging from 1 to input.ndim. If ``None``, a full + connectivity of ``input.ndim`` is used. Returns ------- @@ -417,24 +415,28 @@ def label(input, neighbors=None, background=None, return_num=False, [0 0 1]] >>> from skimage.measure import label >>> print(label(x, connectivity=1)) - [[0 1 1] - [2 3 1] - [2 2 4]] + [[1 0 0] + [0 2 0] + [0 0 3]] >>> print(label(x, connectivity=2)) - [[0 1 1] - [1 0 1] - [1 1 0]] + [[1 0 0] + [0 1 0] + [0 0 1]] + + >>> print(label(x, background=-1)) + [[1 2 2] + [2 1 2] + [2 2 1]] >>> x = np.array([[1, 0, 0], ... [1, 1, 5], ... [0, 0, 0]]) - >>> print(label(x, background=0)) - [[ 0 -1 -1] - [ 0 0 1] - [-1 -1 -1]] - + >>> print(label(x)) + [[1 0 0] + [1 1 2] + [0 0 0]] """ # We have to ensure that the shape of the input can be handled by the # algorithm the input if it is the case @@ -515,7 +517,7 @@ cdef DTYPE_t resolve_labels(DTYPE_t *data_p, DTYPE_t *forest_p, our knowledge of prov. labels relationship. We also track how many distinct final labels we have. """ - cdef DTYPE_t counter = bg.background_label + 1, i + cdef DTYPE_t counter = 1, i for i in range(shapeinfo.numels): if i == bg.background_node: @@ -527,7 +529,7 @@ cdef DTYPE_t resolve_labels(DTYPE_t *data_p, DTYPE_t *forest_p, counter += 1 else: data_p[i] = data_p[forest_p[i]] - return counter + return counter - 1 cdef void scanBG(DTYPE_t *data_p, DTYPE_t *forest_p, shape_info *shapeinfo, diff --git a/skimage/morphology/convex_hull.py b/skimage/morphology/convex_hull.py index 31b92a47..bb372476 100644 --- a/skimage/morphology/convex_hull.py +++ b/skimage/morphology/convex_hull.py @@ -117,7 +117,7 @@ def convex_hull_object(image, neighbors=8): convex_obj = np.zeros(image.shape, dtype=bool) convex_img = np.zeros(image.shape, dtype=bool) - for i in range(0, labeled_im.max() + 1): + for i in range(1, labeled_im.max() + 1): convex_obj = convex_hull_image(labeled_im == i) convex_img = np.logical_or(convex_img, convex_obj) diff --git a/skimage/morphology/tests/test_ccomp.py b/skimage/morphology/tests/test_ccomp.py index 901a18b9..a5b1baeb 100644 --- a/skimage/morphology/tests/test_ccomp.py +++ b/skimage/morphology/tests/test_ccomp.py @@ -5,10 +5,8 @@ from skimage.measure import label import skimage.measure._ccomp as ccomp from skimage._shared._warnings import expected_warnings - -# The background label value -# is supposed to be changed to 0 soon -BG = -1 +# Background value +BG = 0 class TestConnectedComponents: @@ -21,20 +19,17 @@ class TestConnectedComponents: self.labels = np.array([[0, 0, 1, 2, 3, 4], [0, 5, 5, 4, 2, 4], [0, 0, 5, 4, 4, 4], - [6, 5, 5, 7, 8, 9]]) + [6, 5, 5, 7, 8, 0]]) def test_basic(self): - with expected_warnings(['`background`']): - assert_array_equal(label(self.x), self.labels) + assert_array_equal(label(self.x), self.labels) # Make sure data wasn't modified assert self.x[0, 2] == 3 def test_random(self): x = (np.random.rand(20, 30) * 5).astype(np.int) - - with expected_warnings(['`background`']): - labels = label(x) + labels = label(x) n = labels.max() for i in range(n): @@ -45,34 +40,31 @@ class TestConnectedComponents: x = np.array([[0, 0, 1], [0, 1, 0], [1, 0, 0]]) - with expected_warnings(['`background`']): - assert_array_equal(label(x), x) + assert_array_equal(label(x), x) def test_4_vs_8(self): x = np.array([[0, 1], [1, 0]], dtype=int) - with expected_warnings(['`background`']): - assert_array_equal(label(x, 4), - [[0, 1], - [2, 3]]) - assert_array_equal(label(x, 8), - [[0, 1], - [1, 0]]) + assert_array_equal(label(x, 4), + [[0, 1], + [2, 0]]) + assert_array_equal(label(x, 8), + [[0, 1], + [1, 0]]) def test_background(self): x = np.array([[1, 0, 0], [1, 1, 5], [0, 0, 0]]) - with expected_warnings(['`background`']): - assert_array_equal(label(x), [[0, 1, 1], - [0, 0, 2], - [3, 3, 3]]) + assert_array_equal(label(x), [[1, 0, 0], + [1, 1, 2], + [0, 0, 0]]) assert_array_equal(label(x, background=0), - [[0, -1, -1], - [0, 0, 1], - [-1, -1, -1]]) + [[1, 0, 0], + [1, 1, 2], + [0, 0, 0]]) def test_background_two_regions(self): x = np.array([[0, 0, 6], @@ -81,9 +73,9 @@ class TestConnectedComponents: res = label(x, background=0) assert_array_equal(res, - [[-1, -1, 0], - [-1, -1, 0], - [+1, 1, 1]]) + [[0, 0, 1], + [0, 0, 1], + [2, 2, 2]]) def test_background_one_region_center(self): x = np.array([[0, 0, 0], @@ -91,19 +83,18 @@ class TestConnectedComponents: [0, 0, 0]]) assert_array_equal(label(x, neighbors=4, background=0), - [[-1, -1, -1], - [-1, 0, -1], - [-1, -1, -1]]) + [[0, 0, 0], + [0, 1, 0], + [0, 0, 0]]) def test_return_num(self): x = np.array([[1, 0, 6], [0, 0, 6], [5, 5, 5]]) - with expected_warnings(['`background`']): - assert_array_equal(label(x, return_num=True)[1], 4) + assert_array_equal(label(x, return_num=True)[1], 3) - assert_array_equal(label(x, background=0, return_num=True)[1], 3) + assert_array_equal(label(x, background=-1, return_num=True)[1], 4) class TestConnectedComponents3d: @@ -129,21 +120,20 @@ class TestConnectedComponents3d: self.labels[0] = np.array([[0, 1, 2, 3, 4], [0, 5, 4, 2, 4], [0, 5, 4, 4, 4], - [1, 5, 6, 1, 7]]) + [1, 5, 6, 1, 0]]) self.labels[1] = np.array([[1, 1, 2, 3, 4], [0, 1, 4, 2, 3], [0, 1, 1, 3, 3], - [1, 5, 1, 1, 7]]) + [1, 5, 1, 1, 0]]) - self.labels[2] = np.array([[1, 1, 8, 8, 9], - [10, 1, 4, 8, 8], - [10, 1, 7, 8, 7], - [10, 5, 7, 7, 7]]) + self.labels[2] = np.array([[1, 1, 7, 7, 0], + [8, 1, 4, 7, 7], + [8, 1, 0, 7, 0], + [8, 5, 0, 0, 0]]) def test_basic(self): - with expected_warnings(['`background`']): - labels = label(self.x) + labels = label(self.x) assert_array_equal(labels, self.labels) assert self.x[0, 0, 2] == 2, \ @@ -151,9 +141,7 @@ class TestConnectedComponents3d: def test_random(self): x = (np.random.rand(20, 30) * 5).astype(np.int) - - with expected_warnings(['`background`']): - labels = label(x) + labels = label(x) n = labels.max() for i in range(n): @@ -165,8 +153,7 @@ class TestConnectedComponents3d: x[0, 2, 2] = 1 x[1, 1, 1] = 1 x[2, 0, 0] = 1 - with expected_warnings(['`background`']): - assert_array_equal(label(x), x) + assert_array_equal(label(x), x) def test_4_vs_8(self): x = np.zeros((2, 2, 2), int) @@ -174,9 +161,8 @@ class TestConnectedComponents3d: x[1, 0, 0] = 1 label4 = x.copy() label4[1, 0, 0] = 2 - with expected_warnings(['`background`']): - assert_array_equal(label(x, 4), label4) - assert_array_equal(label(x, 8), x) + assert_array_equal(label(x, 4), label4) + assert_array_equal(label(x, 8), x) def test_background(self): x = np.zeros((2, 3, 3), int) @@ -188,24 +174,22 @@ class TestConnectedComponents3d: [0, 0, 0]]) lnb = x.copy() - lnb[0] = np.array([[0, 1, 1], - [0, 1, 1], - [1, 1, 1]]) - lnb[1] = np.array([[1, 1, 1], - [1, 0, 2], - [1, 1, 1]]) + lnb[0] = np.array([[1, 2, 2], + [1, 2, 2], + [2, 2, 2]]) + lnb[1] = np.array([[2, 2, 2], + [2, 1, 3], + [2, 2, 2]]) lb = x.copy() - lb[0] = np.array([[0, BG, BG], - [0, BG, BG], + lb[0] = np.array([[1, BG, BG], + [1, BG, BG], [BG, BG, BG]]) lb[1] = np.array([[BG, BG, BG], - [BG, 0, 1], + [BG, 1, 2], [BG, BG, BG]]) - with expected_warnings(['`background`']): - assert_array_equal(label(x), lnb) - - assert_array_equal(label(x, background=0), lb) + assert_array_equal(label(x), lb) + assert_array_equal(label(x, background=-1), lnb) def test_background_two_regions(self): x = np.zeros((2, 3, 3), int) @@ -216,11 +200,11 @@ class TestConnectedComponents3d: [5, 0, 0], [0, 0, 0]]) lb = x.copy() - lb[0] = np.array([[BG, BG, 0], - [BG, BG, 0], - [1, 1, 1]]) - lb[1] = np.array([[0, 0, BG], - [1, BG, BG], + lb[0] = np.array([[BG, BG, 1], + [BG, BG, 1], + [2, 2, 2]]) + lb[1] = np.array([[1, 1, BG], + [2, BG, BG], [BG, BG, BG]]) res = label(x, background=0) @@ -231,7 +215,7 @@ class TestConnectedComponents3d: x[1, 1, 1] = 1 lb = np.ones_like(x) * BG - lb[1, 1, 1] = 0 + lb[1, 1, 1] = 1 assert_array_equal(label(x, neighbors=4, background=0), lb) @@ -240,22 +224,19 @@ class TestConnectedComponents3d: [0, 0, 6], [5, 5, 5]]) - with expected_warnings(['`background`']): - assert_array_equal(label(x, return_num=True)[1], 4) - - assert_array_equal(label(x, background=0, return_num=True)[1], 3) + assert_array_equal(label(x, return_num=True)[1], 3) + assert_array_equal(label(x, background=-1, return_num=True)[1], 4) def test_1D(self): x = np.array((0, 1, 2, 2, 1, 1, 0, 0)) xlen = len(x) - y = np.array((0, 1, 2, 2, 3, 3, 4, 4)) + y = np.array((0, 1, 2, 2, 3, 3, 0, 0)) reshapes = ((xlen,), (1, xlen), (xlen, 1), (1, xlen, 1), (xlen, 1, 1), (1, 1, xlen)) for reshape in reshapes: x2 = x.reshape(reshape) - with expected_warnings(['`background`']): - labelled = label(x2) + labelled = label(x2) assert_array_equal(y, labelled.flatten()) def test_nd(self): diff --git a/skimage/segmentation/_clear_border.py b/skimage/segmentation/_clear_border.py index bc972754..a44f0785 100644 --- a/skimage/segmentation/_clear_border.py +++ b/skimage/segmentation/_clear_border.py @@ -1,5 +1,4 @@ import numpy as np -#from scipy.ndimage import label from ..measure import label @@ -60,7 +59,7 @@ def clear_border(labels, buffer_size=0, bgval=0, in_place=False): # Re-label, in case we are dealing with a binary image # and to get consistent labeling - labels = label(image, background=0) + 1 + labels = label(image, background=0) number = np.max(labels) + 1 # determine all objects that are connected to borders diff --git a/skimage/transform/tests/test_hough_transform.py b/skimage/transform/tests/test_hough_transform.py index 0babc769..2f7a6b96 100644 --- a/skimage/transform/tests/test_hough_transform.py +++ b/skimage/transform/tests/test_hough_transform.py @@ -82,8 +82,7 @@ def test_hough_line_peaks(): out, angles, d = tf.hough_line(img) - with expected_warnings(['`background`']): - out, theta, dist = tf.hough_line_peaks(out, angles, d) + out, theta, dist = tf.hough_line_peaks(out, angles, d) assert_equal(len(dist), 1) assert_almost_equal(dist[0], 80.723, 1) @@ -101,9 +100,8 @@ def test_hough_line_peaks_ordered(): hough_space, angles, dists = tf.hough_line(testim) - with expected_warnings(['`background`']): - hspace, _, _ = tf.hough_line_peaks(hough_space, angles, dists) - assert hspace[0] > hspace[1] + hspace, _, _ = tf.hough_line_peaks(hough_space, angles, dists) + assert hspace[0] > hspace[1] def test_hough_line_peaks_dist(): @@ -111,16 +109,14 @@ def test_hough_line_peaks_dist(): img[:, 30] = True img[:, 40] = True hspace, angles, dists = tf.hough_line(img) - with expected_warnings(['`background`']): - assert len(tf.hough_line_peaks(hspace, angles, dists, - min_distance=5)[0]) == 2 - assert len(tf.hough_line_peaks(hspace, angles, dists, + assert len(tf.hough_line_peaks(hspace, angles, dists, + min_distance=5)[0]) == 2 + assert len(tf.hough_line_peaks(hspace, angles, dists, min_distance=15)[0]) == 1 def test_hough_line_peaks_angle(): - with expected_warnings(['`background`']): - check_hough_line_peaks_angle() + check_hough_line_peaks_angle() def check_hough_line_peaks_angle(): @@ -154,9 +150,8 @@ def test_hough_line_peaks_num(): img[:, 30] = True img[:, 40] = True hspace, angles, dists = tf.hough_line(img) - with expected_warnings(['`background`']): - assert len(tf.hough_line_peaks(hspace, angles, dists, min_distance=0, - min_angle=0, num_peaks=1)[0]) == 1 + assert len(tf.hough_line_peaks(hspace, angles, dists, min_distance=0, + min_angle=0, num_peaks=1)[0]) == 1 @test_parallel()