diff --git a/TODO.txt b/TODO.txt index bec8efe6..47d4ec90 100644 --- a/TODO.txt +++ b/TODO.txt @@ -1,3 +1,12 @@ +Remember to list any API changes below in `doc/source/api_changes.txt`. + +Version 0.12 +------------ +- Change `label` to mark background as 0, not -1, which is consistent with + SciPy's labelling. +- Remove `skimage.morphology.label` from `skimage.morphology.__init__`--it now + lives in `skimage.measure.label`. + Version 0.11 ------------ * Remove deprecated `reverse_map` parameter of `skimage.transform.warp` diff --git a/bento.info b/bento.info index f1cbabc1..d3dfd440 100644 --- a/bento.info +++ b/bento.info @@ -64,9 +64,9 @@ Library: Extension: skimage.filter._ctmf Sources: skimage/filter/_ctmf.pyx - Extension: skimage.morphology.ccomp + Extension: skimage.measure._ccomp Sources: - skimage/morphology/ccomp.pyx + skimage/measure/_ccomp.pyx Extension: skimage.morphology._watershed Sources: skimage/morphology/_watershed.pyx diff --git a/doc/source/api_changes.txt b/doc/source/api_changes.txt index 527c9fb0..0271bec0 100644 --- a/doc/source/api_changes.txt +++ b/doc/source/api_changes.txt @@ -24,4 +24,3 @@ Version 0.3 - Remove ``as_grey``, ``dtype`` keyword from ImageCollection - Remove ``dtype`` from imread - Generalise ImageCollection to accept a load_func - diff --git a/skimage/measure/__init__.py b/skimage/measure/__init__.py index d76e7a67..e07b9789 100755 --- a/skimage/measure/__init__.py +++ b/skimage/measure/__init__.py @@ -8,6 +8,7 @@ from ._moments import moments, moments_central, moments_normalized, moments_hu from .profile import profile_line from .fit import LineModel, CircleModel, EllipseModel, ransac from .block import block_reduce +from ._label import label __all__ = ['find_contours', @@ -28,4 +29,5 @@ __all__ = ['find_contours', 'marching_cubes', 'mesh_surface_area', 'correct_mesh_orientation', - 'profile_line'] + 'profile_line', + 'label'] diff --git a/skimage/morphology/ccomp.pxd b/skimage/measure/_ccomp.pxd similarity index 100% rename from skimage/morphology/ccomp.pxd rename to skimage/measure/_ccomp.pxd diff --git a/skimage/morphology/ccomp.pyx b/skimage/measure/_ccomp.pyx similarity index 87% rename from skimage/morphology/ccomp.pyx rename to skimage/measure/_ccomp.pyx index 91e2611c..26b9645a 100644 --- a/skimage/morphology/ccomp.pyx +++ b/skimage/measure/_ccomp.pyx @@ -4,6 +4,7 @@ #cython: wraparound=False import numpy as np +import warnings cimport numpy as cnp @@ -82,7 +83,7 @@ cdef inline void link_bg(DTYPE_t *forest, DTYPE_t n, DTYPE_t *background_node): # Connected components search as described in Fiorio et al. -def label(input, DTYPE_t neighbors=8, DTYPE_t background=-1, return_num=False): +def label(input, DTYPE_t neighbors=8, background=None, return_num=False): """Label connected regions of an integer array. Two pixels are connected when they are neighbors and have the same value. @@ -100,11 +101,14 @@ def label(input, DTYPE_t neighbors=8, DTYPE_t background=-1, return_num=False): ---------- input : ndarray of dtype int Image to label. - neighbors : {4, 8}, int + neighbors : {4, 8}, int, optional Whether to use 4- or 8-connectivity. - background : int + background : int, optional Consider all pixels with this value as background pixels, and label - them as -1. + them as -1. (Note: background pixels will be labeled as 0 starting with + version 0.12). + return_num : bool, optional + Whether to return the number of assigned labels. Returns ------- @@ -157,17 +161,27 @@ def label(input, DTYPE_t neighbors=8, DTYPE_t background=-1, return_num=False): cdef DTYPE_t i, j + cdef DTYPE_t background_val + + if background is None: + background_val = -1 + warnings.warn(DeprecationWarning( + 'The default value for `background` will change to 0 in v0.12' + )) + else: + background_val = background + cdef DTYPE_t background_node = -999 if neighbors != 4 and neighbors != 8: raise ValueError('Neighbors must be either 4 or 8.') # Initialize the first row - if data[0, 0] == background: + if data[0, 0] == background_val: link_bg(forest_p, 0, &background_node) for j in range(1, cols): - if data[0, j] == background: + if data[0, j] == background_val: link_bg(forest_p, j, &background_node) if data[0, j] == data[0, j-1]: @@ -175,7 +189,7 @@ def label(input, DTYPE_t neighbors=8, DTYPE_t background=-1, return_num=False): for i in range(1, rows): # Handle the first column - if data[i, 0] == background: + if data[i, 0] == background_val: link_bg(forest_p, i * cols, &background_node) if data[i, 0] == data[i-1, 0]: @@ -186,7 +200,7 @@ def label(input, DTYPE_t neighbors=8, DTYPE_t background=-1, return_num=False): join_trees(forest_p, i*cols, (i-1)*cols + 1) for j in range(1, cols): - if data[i, j] == background: + if data[i, j] == background_val: link_bg(forest_p, i * cols + j, &background_node) if neighbors == 8: diff --git a/skimage/measure/_label.py b/skimage/measure/_label.py new file mode 100644 index 00000000..47e8af74 --- /dev/null +++ b/skimage/measure/_label.py @@ -0,0 +1,6 @@ +from ._ccomp import label as _label + +def label(input, neighbors=8, background=None, return_num=False): + return _label(input, neighbors, background, return_num) + +label.__doc__ = _label.__doc__ diff --git a/skimage/measure/_regionprops.py b/skimage/measure/_regionprops.py index 7bcfcf81..c6e1fa03 100644 --- a/skimage/measure/_regionprops.py +++ b/skimage/measure/_regionprops.py @@ -4,8 +4,8 @@ from math import sqrt, atan2, pi as PI import numpy as np from scipy import ndimage -from skimage.morphology import convex_hull_image, label -from skimage.measure import _moments +from ._label import label +from . import _moments __all__ = ['regionprops', 'perimeter'] @@ -135,6 +135,7 @@ class _RegionProperties(object): @_cached_property def convex_image(self): + from ..morphology.convex_hull import convex_hull_image return convex_hull_image(self.image) @_cached_property diff --git a/skimage/measure/setup.py b/skimage/measure/setup.py index af8a4be1..0fab0787 100644 --- a/skimage/measure/setup.py +++ b/skimage/measure/setup.py @@ -12,10 +12,13 @@ def configuration(parent_package='', top_path=None): config = Configuration('measure', parent_package, top_path) config.add_data_dir('tests') + cython(['_ccomp.pyx'], working_path=base_path) cython(['_find_contours_cy.pyx'], working_path=base_path) cython(['_moments.pyx'], working_path=base_path) cython(['_marching_cubes_cy.pyx'], working_path=base_path) + config.add_extension('_ccomp', sources=['_ccomp.c'], + include_dirs=[get_numpy_include_dirs()]) config.add_extension('_find_contours_cy', sources=['_find_contours_cy.c'], include_dirs=[get_numpy_include_dirs()]) config.add_extension('_moments', sources=['_moments.c'], diff --git a/skimage/morphology/__init__.py b/skimage/morphology/__init__.py index bee5ac13..5b659093 100644 --- a/skimage/morphology/__init__.py +++ b/skimage/morphology/__init__.py @@ -4,13 +4,16 @@ from .grey import (erosion, dilation, opening, closing, white_tophat, black_tophat) from .selem import (square, rectangle, diamond, disk, cube, octahedron, ball, octagon, star) -from .ccomp import label from .watershed import watershed from ._skeletonize import skeletonize, medial_axis from .convex_hull import convex_hull_image, convex_hull_object from .greyreconstruct import reconstruction from .misc import remove_small_objects +from ..measure._label import label +from skimage._shared.utils import deprecated as _deprecated +label = _deprecated('skimage.measure.label')(label) + __all__ = ['binary_erosion', 'binary_dilation', diff --git a/skimage/morphology/convex_hull.py b/skimage/morphology/convex_hull.py index 4d54498d..c5b9eb3f 100644 --- a/skimage/morphology/convex_hull.py +++ b/skimage/morphology/convex_hull.py @@ -3,7 +3,7 @@ __all__ = ['convex_hull_image', 'convex_hull_object'] import numpy as np from ._pnpoly import grid_points_inside_poly from ._convex_hull import possible_hull -from skimage.morphology import label +from ..measure._label import label from skimage.util import unique_rows diff --git a/skimage/morphology/setup.py b/skimage/morphology/setup.py index 1936377b..f0f69764 100644 --- a/skimage/morphology/setup.py +++ b/skimage/morphology/setup.py @@ -12,7 +12,6 @@ def configuration(parent_package='', top_path=None): config = Configuration('morphology', parent_package, top_path) config.add_data_dir('tests') - cython(['ccomp.pyx'], working_path=base_path) cython(['cmorph.pyx'], working_path=base_path) cython(['_watershed.pyx'], working_path=base_path) cython(['_skeletonize_cy.pyx'], working_path=base_path) @@ -20,8 +19,6 @@ def configuration(parent_package='', top_path=None): cython(['_convex_hull.pyx'], working_path=base_path) cython(['_greyreconstruct.pyx'], working_path=base_path) - config.add_extension('ccomp', sources=['ccomp.c'], - include_dirs=[get_numpy_include_dirs()]) config.add_extension('cmorph', sources=['cmorph.c'], include_dirs=[get_numpy_include_dirs()]) config.add_extension('_watershed', sources=['_watershed.c'], diff --git a/skimage/morphology/tests/test_ccomp.py b/skimage/morphology/tests/test_ccomp.py index 1169be85..e934a5b7 100644 --- a/skimage/morphology/tests/test_ccomp.py +++ b/skimage/morphology/tests/test_ccomp.py @@ -2,7 +2,8 @@ import numpy as np from numpy.testing import assert_array_equal, run_module_suite from skimage.morphology import label - +from warnings import catch_warnings +from skimage._shared.utils import skimage_deprecation class TestConnectedComponents: def setup(self): @@ -25,7 +26,9 @@ class TestConnectedComponents: def test_random(self): x = (np.random.random((20, 30)) * 5).astype(np.int) - labels = label(x) + with catch_warnings(): + labels = label(x) + n = labels.max() for i in range(n): values = x[labels == i] @@ -35,27 +38,29 @@ class TestConnectedComponents: x = np.array([[0, 0, 1], [0, 1, 0], [1, 0, 0]]) - assert_array_equal(label(x), - x) + with catch_warnings(): + assert_array_equal(label(x), x) def test_4_vs_8(self): x = np.array([[0, 1], [1, 0]], dtype=int) - assert_array_equal(label(x, 4), - [[0, 1], - [2, 3]]) - assert_array_equal(label(x, 8), - [[0, 1], - [1, 0]]) + with catch_warnings(): + assert_array_equal(label(x, 4), + [[0, 1], + [2, 3]]) + 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]]) - assert_array_equal(label(x), [[0, 1, 1], - [0, 0, 2], - [3, 3, 3]]) + with catch_warnings(): + assert_array_equal(label(x), [[0, 1, 1], + [0, 0, 2], + [3, 3, 3]]) assert_array_equal(label(x, background=0), [[0, -1, -1], @@ -87,7 +92,9 @@ class TestConnectedComponents: [0, 0, 6], [5, 5, 5]]) - assert_array_equal(label(x, return_num=True)[1], 4) + with catch_warnings(): + assert_array_equal(label(x, return_num=True)[1], 4) + assert_array_equal(label(x, background=0, return_num=True)[1], 3) diff --git a/skimage/segmentation/_felzenszwalb_cy.pyx b/skimage/segmentation/_felzenszwalb_cy.pyx index c7961084..80afaedb 100644 --- a/skimage/segmentation/_felzenszwalb_cy.pyx +++ b/skimage/segmentation/_felzenszwalb_cy.pyx @@ -7,7 +7,7 @@ import scipy cimport cython cimport numpy as cnp -from skimage.morphology.ccomp cimport find_root, join_trees +from skimage.measure._ccomp cimport find_root, join_trees from ..util import img_as_float diff --git a/skimage/transform/hough_transform.py b/skimage/transform/hough_transform.py index 35a9cea8..cbb4caa6 100644 --- a/skimage/transform/hough_transform.py +++ b/skimage/transform/hough_transform.py @@ -71,7 +71,7 @@ def hough_line_peaks(hspace, angles, dists, min_distance=9, min_angle=10, hspace *= mask hspace_t = hspace > threshold - label_hspace = morphology.label(hspace_t) + label_hspace = measure.label(hspace_t) props = measure.regionprops(label_hspace) coords = np.array([np.round(p.centroid) for p in props], dtype=int)