186 lines
6.3 KiB
Python
186 lines
6.3 KiB
Python
from scipy import ndimage as ndi
|
|
from skimage import data
|
|
|
|
import numpy as np
|
|
|
|
from skimage import measure
|
|
from skimage.segmentation._expand_labels import expand_labels
|
|
|
|
from skimage._shared import testing
|
|
from skimage._shared.testing import assert_array_equal
|
|
|
|
SAMPLE1D = np.array([0, 0, 4, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0])
|
|
SAMPLE1D_EXPANDED_3 = np.array([4, 4, 4, 4, 4, 4, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0])
|
|
|
|
# Some pixels are important edge cases with undefined behaviour:
|
|
# these are the pixels that are at the same distance from
|
|
# multiple labels. Ideally the label would be chosen at random
|
|
# to avoid bias, but as we are relying on the index map returned
|
|
# by the scipy.ndimage distance transform, what actually happens
|
|
# is determined by the upstream implementation of the distance
|
|
# tansform, thus we don't give any guarantees for the edge case pixels.
|
|
#
|
|
# Regardless, it seems prudent to have a test including an edge case
|
|
# so we can detect whether future upstream changes in scipy.ndimage
|
|
# modify the behaviour.
|
|
|
|
EDGECASE1D = np.array([0, 0, 4, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0])
|
|
EDGECASE1D_EXPANDED_3 = np.array([4, 4, 4, 4, 4, 4, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0])
|
|
|
|
SAMPLE2D = np.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, 1, 1, 0, 0, 0, 0, 0, 0, 0],
|
|
[0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0],
|
|
[0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
[0, 0, 1, 0, 0, 0, 0, 0, 0, 2, 0],
|
|
[0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 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, 0, 0, 0, 0, 0, 0, 0]]
|
|
)
|
|
|
|
SAMPLE2D_EXPANDED_3 = np.array(
|
|
[[1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0],
|
|
[1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0],
|
|
[1, 1, 1, 1, 1, 1, 1, 0, 0, 2, 0],
|
|
[1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2],
|
|
[1, 1, 1, 1, 1, 1, 0, 2, 2, 2, 2],
|
|
[1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2],
|
|
[1, 1, 1, 1, 1, 0, 2, 2, 2, 2, 2],
|
|
[1, 1, 1, 1, 1, 0, 0, 2, 2, 2, 2],
|
|
[0, 0, 1, 0, 0, 0, 0, 2, 2, 2, 2],
|
|
[0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0]]
|
|
)
|
|
|
|
# non-integer expansion
|
|
SAMPLE2D_EXPANDED_1_5 = np.array(
|
|
[[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
[0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0],
|
|
[1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0],
|
|
[1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0],
|
|
[1, 1, 1, 1, 1, 0, 0, 0, 2, 2, 2],
|
|
[1, 1, 1, 1, 0, 0, 0, 0, 2, 2, 2],
|
|
[0, 1, 1, 1, 0, 0, 0, 0, 2, 2, 2],
|
|
[0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2],
|
|
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]])
|
|
|
|
|
|
EDGECASE2D = np.array(
|
|
[[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
[0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0],
|
|
[0, 0, 1, 1, 0, 2, 2, 0, 0, 0, 0],
|
|
[0, 1, 1, 1, 0, 2, 0, 0, 0, 0, 0],
|
|
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0]]
|
|
)
|
|
|
|
EDGECASE2D_EXPANDED_4 = np.array(
|
|
[[1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 0],
|
|
[1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2],
|
|
[1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2],
|
|
[1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 0],
|
|
[1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 0]])
|
|
|
|
SAMPLE3D = np.array(
|
|
[[[0, 0, 0, 0],
|
|
[0, 3, 0, 0],
|
|
[0, 0, 0, 0],
|
|
[0, 0, 0, 0]],
|
|
|
|
[[0, 0, 0, 0],
|
|
[0, 3, 3, 0],
|
|
[0, 0, 0, 0],
|
|
[0, 0, 0, 0]],
|
|
|
|
[[0, 0, 0, 0],
|
|
[0, 3, 0, 0],
|
|
[0, 0, 0, 0],
|
|
[0, 0, 5, 0]],
|
|
|
|
[[0, 0, 0, 0],
|
|
[0, 0, 0, 0],
|
|
[0, 0, 0, 0],
|
|
[0, 0, 5, 0]]])
|
|
|
|
SAMPLE3D_EXPANDED_2 =np.array(
|
|
[[[3, 3, 3, 3],
|
|
[3, 3, 3, 3],
|
|
[3, 3, 3, 3],
|
|
[0, 3, 5, 0]],
|
|
|
|
[[3, 3, 3, 3],
|
|
[3, 3, 3, 3],
|
|
[3, 3, 3, 3],
|
|
[0, 5, 5, 5]],
|
|
|
|
[[3, 3, 3, 3],
|
|
[3, 3, 3, 3],
|
|
[3, 3, 5, 5],
|
|
[5, 5, 5, 5]],
|
|
|
|
[[3, 3, 3, 0],
|
|
[3, 3, 3, 0],
|
|
[3, 3, 5, 5],
|
|
[5, 5, 5, 5]]])
|
|
|
|
SAMPLE_EDGECASE_BEHAVIOUR = np.array([[0, 1, 0, 0], [2, 0, 0, 0], [0, 3, 0, 0]])
|
|
|
|
@testing.parametrize(
|
|
"input_array, expected_output, expand_distance",
|
|
[
|
|
(SAMPLE1D, SAMPLE1D_EXPANDED_3, 3),
|
|
(SAMPLE2D, SAMPLE2D_EXPANDED_3, 3),
|
|
(SAMPLE2D, SAMPLE2D_EXPANDED_1_5, 1.5),
|
|
(EDGECASE1D, EDGECASE1D_EXPANDED_3, 3),
|
|
(EDGECASE2D, EDGECASE2D_EXPANDED_4, 4),
|
|
(SAMPLE3D, SAMPLE3D_EXPANDED_2, 2)
|
|
]
|
|
)
|
|
def test_expand_labels(input_array, expected_output, expand_distance):
|
|
expanded = expand_labels(input_array, expand_distance)
|
|
assert_array_equal(expanded, expected_output)
|
|
|
|
|
|
@testing.parametrize('ndim', [2, 3])
|
|
@testing.parametrize('distance', range(6))
|
|
def test_binary_blobs(ndim, distance):
|
|
"""Check some invariants with label expansion.
|
|
|
|
- New labels array should exactly contain the original labels array.
|
|
- Distance to old labels array within new labels should never exceed input
|
|
distance.
|
|
- Distance beyond the expanded labels should always exceed the input
|
|
distance.
|
|
"""
|
|
img = data.binary_blobs(length=64, blob_size_fraction=0.05, n_dim=ndim)
|
|
labels = measure.label(img)
|
|
expanded = expand_labels(labels, distance=distance)
|
|
original_mask = labels != 0
|
|
assert_array_equal(labels[original_mask], expanded[original_mask])
|
|
expanded_only_mask = (expanded - labels).astype(bool)
|
|
distance_map = ndi.distance_transform_edt(~original_mask)
|
|
expanded_distances = distance_map[expanded_only_mask]
|
|
if expanded_distances.size > 0:
|
|
assert np.all(expanded_distances <= distance)
|
|
beyond_expanded_distances = distance_map[~expanded.astype(bool)]
|
|
if beyond_expanded_distances.size > 0:
|
|
assert np.all(beyond_expanded_distances > distance)
|
|
|
|
|
|
def test_edge_case_behaviour():
|
|
""" Check edge case behavior to detect upstream changes
|
|
|
|
For edge cases where a pixel has the same distance to several regions,
|
|
lexicographical order seems to determine which region gets to expand
|
|
into this pixel given the current upstream behaviour in
|
|
scipy.ndimage.distance_map_edt.
|
|
|
|
As a result, we expect different results when transposing the array.
|
|
If this test fails, something has changed upstream.
|
|
"""
|
|
expanded = expand_labels(SAMPLE_EDGECASE_BEHAVIOUR, 1)
|
|
expanded_transpose = expand_labels(SAMPLE_EDGECASE_BEHAVIOUR.T, 1)
|
|
assert not np.all(expanded == expanded_transpose.T)
|
|
|