1import numpy as np
2
3from ..measure import label
4from .._shared.utils import remove_arg
5
6
7@remove_arg("in_place", changed_version="1.0",
8            help_msg="Please use out argument instead.")
9def clear_border(labels, buffer_size=0, bgval=0, in_place=False, mask=None,
10                 *, out=None):
11    """Clear objects connected to the label image border.
12
13    Parameters
14    ----------
15    labels : (M[, N[, ..., P]]) array of int or bool
16        Imaging data labels.
17    buffer_size : int, optional
18        The width of the border examined.  By default, only objects
19        that touch the outside of the image are removed.
20    bgval : float or int, optional
21        Cleared objects are set to this value.
22    in_place : bool, optional
23        Whether or not to manipulate the labels array in-place.
24        Deprecated since version 0.19. Please use `out` instead.
25    mask : ndarray of bool, same shape as `image`, optional.
26        Image data mask. Objects in labels image overlapping with
27        False pixels of mask will be removed. If defined, the
28        argument buffer_size will be ignored.
29    out : ndarray
30        Array of the same shape as `labels`, into which the
31        output is placed. By default, a new array is created.
32
33    Returns
34    -------
35    out : (M[, N[, ..., P]]) array
36        Imaging data labels with cleared borders
37
38    Examples
39    --------
40    >>> import numpy as np
41    >>> from skimage.segmentation import clear_border
42    >>> labels = np.array([[0, 0, 0, 0, 0, 0, 0, 1, 0],
43    ...                    [1, 1, 0, 0, 1, 0, 0, 1, 0],
44    ...                    [1, 1, 0, 1, 0, 1, 0, 0, 0],
45    ...                    [0, 0, 0, 1, 1, 1, 1, 0, 0],
46    ...                    [0, 1, 1, 1, 1, 1, 1, 1, 0],
47    ...                    [0, 0, 0, 0, 0, 0, 0, 0, 0]])
48    >>> clear_border(labels)
49    array([[0, 0, 0, 0, 0, 0, 0, 0, 0],
50           [0, 0, 0, 0, 1, 0, 0, 0, 0],
51           [0, 0, 0, 1, 0, 1, 0, 0, 0],
52           [0, 0, 0, 1, 1, 1, 1, 0, 0],
53           [0, 1, 1, 1, 1, 1, 1, 1, 0],
54           [0, 0, 0, 0, 0, 0, 0, 0, 0]])
55    >>> mask = np.array([[0, 0, 1, 1, 1, 1, 1, 1, 1],
56    ...                  [0, 0, 1, 1, 1, 1, 1, 1, 1],
57    ...                  [1, 1, 1, 1, 1, 1, 1, 1, 1],
58    ...                  [1, 1, 1, 1, 1, 1, 1, 1, 1],
59    ...                  [1, 1, 1, 1, 1, 1, 1, 1, 1],
60    ...                  [1, 1, 1, 1, 1, 1, 1, 1, 1]]).astype(bool)
61    >>> clear_border(labels, mask=mask)
62    array([[0, 0, 0, 0, 0, 0, 0, 1, 0],
63           [0, 0, 0, 0, 1, 0, 0, 1, 0],
64           [0, 0, 0, 1, 0, 1, 0, 0, 0],
65           [0, 0, 0, 1, 1, 1, 1, 0, 0],
66           [0, 1, 1, 1, 1, 1, 1, 1, 0],
67           [0, 0, 0, 0, 0, 0, 0, 0, 0]])
68
69    """
70    if any((buffer_size >= s for s in labels.shape)) and mask is None:
71        # ignore buffer_size if mask
72        raise ValueError("buffer size may not be greater than labels size")
73
74    if out is not None:
75        np.copyto(out, labels, casting='no')
76        in_place = True
77
78    if not in_place:
79        out = labels.copy()
80    elif out is None:
81        out = labels
82
83    if mask is not None:
84        err_msg = (f'labels and mask should have the same shape but '
85                   f'are {out.shape} and {mask.shape}')
86        if out.shape != mask.shape:
87            raise(ValueError, err_msg)
88        if mask.dtype != bool:
89            raise TypeError("mask should be of type bool.")
90        borders = ~mask
91    else:
92        # create borders with buffer_size
93        borders = np.zeros_like(out, dtype=bool)
94        ext = buffer_size + 1
95        slstart = slice(ext)
96        slend = slice(-ext, None)
97        slices = [slice(None) for _ in out.shape]
98        for d in range(out.ndim):
99            slices[d] = slstart
100            borders[tuple(slices)] = True
101            slices[d] = slend
102            borders[tuple(slices)] = True
103            slices[d] = slice(None)
104
105    # Re-label, in case we are dealing with a binary out
106    # and to get consistent labeling
107    labels, number = label(out, background=0, return_num=True)
108
109    # determine all objects that are connected to borders
110    borders_indices = np.unique(labels[borders])
111    indices = np.arange(number + 1)
112    # mask all label indices that are connected to borders
113    label_mask = np.in1d(indices, borders_indices)
114    # create mask for pixels to clear
115    mask = label_mask[labels.reshape(-1)].reshape(labels.shape)
116
117    # clear border pixels
118    out[mask] = bgval
119
120    return out
121