Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add missing cucim.skimage.segmentation.clear_border function #267

Merged
merged 5 commits into from
Jun 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions python/cucim/src/cucim/skimage/segmentation/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from ._clear_border import clear_border
from ._join import join_segmentations, relabel_sequential
from .boundaries import find_boundaries, mark_boundaries
from .morphsnakes import (checkerboard_level_set, disk_level_set,
Expand Down
120 changes: 120 additions & 0 deletions python/cucim/src/cucim/skimage/segmentation/_clear_border.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import cupy as cp

from ..measure import label

_clear_border_labels = cp.ElementwiseKernel(
in_params="raw X labels, raw X borders_indices, int32 nvals, Y bgval",
out_params="Y out",
operation="""
for (int j=0; j<nvals; j++)
{
if (labels[i] == borders_indices[j])
{
out = bgval;
break;
}
}
""",
name="cucim_skimage_clear_border_labels",
)


def clear_border(labels, buffer_size=0, bgval=0, mask=None, *, out=None):
"""Clear objects connected to the label image border.

Parameters
----------
labels : (M[, N[, ..., P]]) array of int or bool
Imaging data labels.
buffer_size : int, optional
The width of the border examined. By default, only objects
that touch the outside of the image are removed.
bgval : float or int, optional
Cleared objects are set to this value.
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.
out : ndarray
Array of the same shape as `labels`, into which the
output is placed. By default, a new array is created.

Returns
-------
out : (M[, N[, ..., P]]) array
Imaging data labels with cleared borders

Examples
--------
>>> import cupy as cp
>>> from cucim.skimage.segmentation import clear_border
>>> labels = cp.array([[0, 0, 0, 0, 0, 0, 0, 1, 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, 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 = cp.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(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]])

"""
if any((buffer_size >= s for s in labels.shape)) and mask is None:
# ignore buffer_size if mask
raise ValueError("buffer size may not be greater than labels size")

if out is not None:
cp.copyto(out, labels, casting='no')
else:
out = labels.copy()

if mask is not None:
err_msg = (f'labels and mask should have the same shape but '
f'are {out.shape} and {mask.shape}')
if out.shape != mask.shape:
raise(ValueError, err_msg)
if mask.dtype != bool:
raise TypeError("mask should be of type bool.")
borders = ~mask
else:
# create borders with buffer_size
borders = cp.zeros_like(out, dtype=bool)
ext = buffer_size + 1
slstart = slice(ext)
slend = slice(-ext, None)
slices = [slice(None) for _ in out.shape]
for d in range(out.ndim):
slices[d] = slstart
borders[tuple(slices)] = True
slices[d] = slend
borders[tuple(slices)] = True
slices[d] = slice(None)

# Re-label, in case we are dealing with a binary out
# and to get consistent labeling
labels, number = label(out, background=0, return_num=True)

# determine all objects that are connected to borders
borders_indices = cp.unique(labels[borders])

_clear_border_labels(
labels, borders_indices, borders_indices.size, bgval, out
)
return out
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
import cupy as cp
from cupy.testing import assert_array_equal

from cucim.skimage.segmentation import clear_border


def test_clear_border():
image = cp.array(
[[0, 0, 0, 0, 0, 0, 0, 1, 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[1:3, 0:2] = 0
ref[0:2, -2] = 0
assert_array_equal(result, ref)

# test buffer
result = clear_border(image.copy(), 1)
assert_array_equal(result, cp.zeros(result.shape))

# test background value
result = clear_border(image.copy(), buffer_size=1, bgval=2)
assert_array_equal(result, cp.full_like(image, 2))

# test mask
mask = cp.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(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 = cp.array(
[[[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0],
[1, 0, 0, 0]],
[[0, 0, 0, 0],
[0, 1, 1, 0],
[0, 0, 1, 0],
[0, 0, 0, 0]],
[[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0]]]
)
# test default case
result = clear_border(image.copy())
ref = image.copy()
ref[0, 3, 0] = 0
assert_array_equal(result, ref)

# test buffer
result = clear_border(image.copy(), 1)
assert_array_equal(result, cp.zeros(result.shape))

# test background value
result = clear_border(image.copy(), buffer_size=1, bgval=2)
assert_array_equal(result, cp.full_like(image, 2))

# test floating-point background value
image_f32 = image.astype(cp.float32)
result = clear_border(image_f32, buffer_size=1, bgval=2.5)
assert_array_equal(result, cp.full_like(image_f32, 2.5))


def test_clear_border_non_binary():
image = cp.array([[1, 2, 3, 1, 2],
[3, 3, 5, 4, 2],
[3, 4, 5, 4, 2],
[3, 3, 2, 1, 2]])

result = clear_border(image)
expected = cp.array([[0, 0, 0, 0, 0],
[0, 0, 5, 4, 0],
[0, 4, 5, 4, 0],
[0, 0, 0, 0, 0]])

assert_array_equal(result, expected)
assert not cp.all(image == result)


def test_clear_border_non_binary_3d():
image3d = cp.array(
[[[1, 2, 3, 1, 2],
[3, 3, 3, 4, 2],
[3, 4, 3, 4, 2],
[3, 3, 2, 1, 2]],
[[1, 2, 3, 1, 2],
[3, 3, 5, 4, 2],
[3, 4, 5, 4, 2],
[3, 3, 2, 1, 2]],
[[1, 2, 3, 1, 2],
[3, 3, 3, 4, 2],
[3, 4, 3, 4, 2],
[3, 3, 2, 1, 2]]]
)

result = clear_border(image3d)
expected = cp.array(
[[[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0]],
[[0, 0, 0, 0, 0],
[0, 0, 5, 0, 0],
[0, 0, 5, 0, 0],
[0, 0, 0, 0, 0]],
[[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0]]]
)

assert_array_equal(result, expected)
assert not cp.all(image3d == result)


def test_clear_border_non_binary_inplace():
image = cp.array([[1, 2, 3, 1, 2],
[3, 3, 5, 4, 2],
[3, 4, 5, 4, 2],
[3, 3, 2, 1, 2]])
result = clear_border(image, out=image)
expected = cp.array([[0, 0, 0, 0, 0],
[0, 0, 5, 4, 0],
[0, 4, 5, 4, 0],
[0, 0, 0, 0, 0]])

assert_array_equal(result, expected)
assert_array_equal(image, result)


def test_clear_border_non_binary_inplace_3d():
image3d = cp.array(
[[[1, 2, 3, 1, 2],
[3, 3, 3, 4, 2],
[3, 4, 3, 4, 2],
[3, 3, 2, 1, 2]],
[[1, 2, 3, 1, 2],
[3, 3, 5, 4, 2],
[3, 4, 5, 4, 2],
[3, 3, 2, 1, 2]],
[[1, 2, 3, 1, 2],
[3, 3, 3, 4, 2],
[3, 4, 3, 4, 2],
[3, 3, 2, 1, 2]]]
)

result = clear_border(image3d, out=image3d)
expected = cp.array(
[[[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0]],
[[0, 0, 0, 0, 0],
[0, 0, 5, 0, 0],
[0, 0, 5, 0, 0],
[0, 0, 0, 0, 0]],
[[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0]]]
)

assert_array_equal(result, expected)
assert_array_equal(image3d, result)


def test_clear_border_non_binary_out():
image = cp.array([[1, 2, 3, 1, 2],
[3, 3, 5, 4, 2],
[3, 4, 5, 4, 2],
[3, 3, 2, 1, 2]])
out = cp.empty_like(image)
result = clear_border(image, out=out)
expected = cp.array([[0, 0, 0, 0, 0],
[0, 0, 5, 4, 0],
[0, 4, 5, 4, 0],
[0, 0, 0, 0, 0]])

assert_array_equal(result, expected)
assert_array_equal(out, result)


def test_clear_border_non_binary_out_3d():
image3d = cp.array(
[[[1, 2, 3, 1, 2],
[3, 3, 3, 4, 2],
[3, 4, 3, 4, 2],
[3, 3, 2, 1, 2]],
[[1, 2, 3, 1, 2],
[3, 3, 5, 4, 2],
[3, 4, 5, 4, 2],
[3, 3, 2, 1, 2]],
[[1, 2, 3, 1, 2],
[3, 3, 3, 4, 2],
[3, 4, 3, 4, 2],
[3, 3, 2, 1, 2]]]
)
out = cp.empty_like(image3d)

result = clear_border(image3d, out=out)
expected = cp.array(
[[[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0]],
[[0, 0, 0, 0, 0],
[0, 0, 5, 0, 0],
[0, 0, 5, 0, 0],
[0, 0, 0, 0, 0]],
[[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0]]]
)

assert_array_equal(result, expected)
assert_array_equal(out, result)