Repository: scikit-image/scikit-image

Issue:
More general clear_border?
A colleague suggested that it might be interesting to have in ``segmentation`` a more general version of ``clear_border``, which would take a mask as optional argument and remove objects touching the edge of the mask (with respect to the border of the whole image as it is now). It should be straightforward to adapt the existing function. Would there be enough interest to include this feature or not? 





Discussion:
+1 from me
On Mon, 17 Dec 2018 13:48:01 -0800, Emmanuelle Gouillart wrote:
> A colleague suggested that it might be interesting to have in
> ``segmentation`` a more general version of ``clear_border``, which
> would take a mask as optional argument and remove objects touching the
> edge of the mask (with respect to the border of the whole image as it
> is now). It should be straightforward to adapt the existing
> function. Would there be enough interest to include this feature or
> not?

That's a neat idea!

Another use case that cropped up today: return the removed objects,
instead of the cleaned up mask.  This is pretty easy to do with a `mask
!= cleaned_mask`, so I'm not even sure if it's worth adding in.

@emmanuelle I implemented a version for cleaning the "right" and "down" borders of an input image, for the papers I'm working on:
```
def clear_rd_border(image):
    """
    Not documented yet. Sorry!
    """

    aux = np.pad(image, ([1, 0], [1, 0]), mode='constant')
    aux = clear_border(aux)
    
    return aux[:-2, 1:]
```
Such general function could be easily implemented making the `([1, 0], [1, 0])` argument on `np.pad` a variable, I think. 
You can also imagine passing in a function that generates the mask from the 
image.


Is anyone working on this? I would like to try and contribute to this issue.

Resolving patch:
diff --git a/skimage/segmentation/_clear_border.py b/skimage/segmentation/_clear_border.py
--- a/skimage/segmentation/_clear_border.py
+++ b/skimage/segmentation/_clear_border.py
@@ -2,7 +2,7 @@
 from ..measure import label
 
 
-def clear_border(labels, buffer_size=0, bgval=0, in_place=False):
+def clear_border(labels, buffer_size=0, bgval=0, in_place=False, mask=None):
     """Clear objects connected to the label image border.
 
     Parameters
@@ -16,7 +16,10 @@ def clear_border(labels, buffer_size=0, bgval=0, in_place=False):
         Cleared objects are set to this value.
     in_place : bool, optional
         Whether or not to manipulate the labels array in-place.
-
+    mask : ndarray of bool, same shape as `image`, optional.
+        Image data mask. Objects in labels image overlapping with
+        False pixels of mask will be removed. If defined, the 
+        argument buffer_size will be ignored.
     Returns
     -------
     out : (M[, N[, ..., P]]) array
@@ -27,38 +30,59 @@ def clear_border(labels, buffer_size=0, bgval=0, in_place=False):
     >>> import numpy as np
     >>> from skimage.segmentation import clear_border
     >>> labels = np.array([[0, 0, 0, 0, 0, 0, 0, 1, 0],
-    ...                    [0, 0, 0, 0, 1, 0, 0, 0, 0],
-    ...                    [1, 0, 0, 1, 0, 1, 0, 0, 0],
-    ...                    [0, 0, 1, 1, 1, 1, 1, 0, 0],
+    ...                    [1, 1, 0, 0, 1, 0, 0, 1, 0],
+    ...                    [1, 1, 0, 1, 0, 1, 0, 0, 0],
+    ...                    [0, 0, 0, 1, 1, 1, 1, 0, 0],
     ...                    [0, 1, 1, 1, 1, 1, 1, 1, 0],
     ...                    [0, 0, 0, 0, 0, 0, 0, 0, 0]])
     >>> clear_border(labels)
     array([[0, 0, 0, 0, 0, 0, 0, 0, 0],
            [0, 0, 0, 0, 1, 0, 0, 0, 0],
            [0, 0, 0, 1, 0, 1, 0, 0, 0],
-           [0, 0, 1, 1, 1, 1, 1, 0, 0],
+           [0, 0, 0, 1, 1, 1, 1, 0, 0],
+           [0, 1, 1, 1, 1, 1, 1, 1, 0],
+           [0, 0, 0, 0, 0, 0, 0, 0, 0]])
+    >>> mask = np.array([[0, 0, 1, 1, 1, 1, 1, 1, 1],
+    ...                  [0, 0, 1, 1, 1, 1, 1, 1, 1],
+    ...                  [1, 1, 1, 1, 1, 1, 1, 1, 1],
+    ...                  [1, 1, 1, 1, 1, 1, 1, 1, 1],
+    ...                  [1, 1, 1, 1, 1, 1, 1, 1, 1],
+    ...                  [1, 1, 1, 1, 1, 1, 1, 1, 1]]).astype(np.bool)
+    >>> clear_border(labels, mask=mask)
+    array([[0, 0, 0, 0, 0, 0, 0, 1, 0],
+           [0, 0, 0, 0, 1, 0, 0, 1, 0],
+           [0, 0, 0, 1, 0, 1, 0, 0, 0],
+           [0, 0, 0, 1, 1, 1, 1, 0, 0],
            [0, 1, 1, 1, 1, 1, 1, 1, 0],
            [0, 0, 0, 0, 0, 0, 0, 0, 0]])
 
     """
     image = labels
 
-    if any( ( buffer_size >= s for s in image.shape)):
+    if any((buffer_size >= s for s in image.shape)) and mask is None:
+        # ignore buffer_size if mask
         raise ValueError("buffer size may not be greater than image size")
 
-    # create borders with buffer_size
-    borders = np.zeros_like(image, dtype=np.bool_)
-    ext = buffer_size + 1
-    slstart = slice(ext)
-    slend   = slice(-ext, None)
-    slices  = [slice(s) for s in image.shape]
-    for d in range(image.ndim):
-        slicedim = list(slices)
-        slicedim[d] = slstart
-        borders[tuple(slicedim)] = True
-        slicedim[d] = slend
-        borders[tuple(slicedim)] = True
-
+    if mask is not None:
+        err_msg = "image and mask should have the same shape but are {} and {}"
+        assert image.shape == mask.shape, \
+               err_msg.format(image.shape, mask.shape)
+        if mask.dtype != np.bool_:
+            raise TypeError("mask should be of type bool.")
+        borders = ~mask
+    else:
+        # create borders with buffer_size
+        borders = np.zeros_like(image, dtype=np.bool_)
+        ext = buffer_size + 1
+        slstart = slice(ext)
+        slend = slice(-ext, None)
+        slices = [slice(s) for s in image.shape]
+        for d in range(image.ndim):
+            slicedim = list(slices)
+            slicedim[d] = slstart
+            borders[tuple(slicedim)] = True
+            slicedim[d] = slend
+            borders[tuple(slicedim)] = True
     # Re-label, in case we are dealing with a binary image
     # and to get consistent labeling
     labels = label(image, background=0)


Test patch:
diff --git a/skimage/segmentation/tests/test_clear_border.py b/skimage/segmentation/tests/test_clear_border.py
--- a/skimage/segmentation/tests/test_clear_border.py
+++ b/skimage/segmentation/tests/test_clear_border.py
@@ -7,17 +7,17 @@
 def test_clear_border():
     image = np.array(
         [[0, 0, 0, 0, 0, 0, 0, 1, 0],
-         [0, 0, 0, 0, 1, 0, 0, 0, 0],
-         [1, 0, 0, 1, 0, 1, 0, 0, 0],
-         [0, 0, 1, 1, 1, 1, 1, 0, 0],
+         [1, 1, 0, 0, 1, 0, 0, 1, 0],
+         [1, 1, 0, 1, 0, 1, 0, 0, 0],
+         [0, 0, 0, 1, 1, 1, 1, 0, 0],
          [0, 1, 1, 1, 1, 1, 1, 1, 0],
          [0, 0, 0, 0, 0, 0, 0, 0, 0]])
 
     # test default case
     result = clear_border(image.copy())
     ref = image.copy()
-    ref[2, 0] = 0
-    ref[0, -2] = 0
+    ref[1:3, 0:2] = 0
+    ref[0:2, -2] = 0
     assert_array_equal(result, ref)
 
     # test buffer
@@ -28,6 +28,17 @@ def test_clear_border():
     result = clear_border(image.copy(), buffer_size=1, bgval=2)
     assert_array_equal(result, 2 * np.ones_like(image))
 
+    # test mask
+    mask = np.array([[0, 0, 1, 1, 1, 1, 1, 1, 1],
+                     [0, 0, 1, 1, 1, 1, 1, 1, 1],
+                     [1, 1, 1, 1, 1, 1, 1, 1, 1],
+                     [1, 1, 1, 1, 1, 1, 1, 1, 1],
+                     [1, 1, 1, 1, 1, 1, 1, 1, 1],
+                     [1, 1, 1, 1, 1, 1, 1, 1, 1]]).astype(np.bool)
+    result = clear_border(image.copy(), mask=mask)
+    ref = image.copy()
+    ref[1:3, 0:2] = 0
+    assert_array_equal(result, ref)
 
 def test_clear_border_3d():
     image = np.array([