Skip to content

Commit

Permalink
Harden the trace boundary code
Browse files Browse the repository at this point in the history
A one pixel object could get stuck in an infinite loop in the moore
boundary call.
  • Loading branch information
manthey committed Dec 6, 2024
1 parent d978797 commit 7d023cc
Show file tree
Hide file tree
Showing 2 changed files with 47 additions and 11 deletions.
46 changes: 36 additions & 10 deletions histomicstk/segmentation/label/_trace_object_boundaries_cython.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ def _trace_object_boundaries_cython(long[:, :] im_label not None, long connectiv
# find starting x and y points if not defined
if (x_start == -1) & (y_start == -1):

for i in range(nrows):
for j in range(ncols):
for i in range(1, nrows - 1):
for j in range(1, ncols - 1):

if (im_label[i, j] > 0) & (flag == 0):
# check if the number of points is one
Expand All @@ -41,16 +41,17 @@ def _trace_object_boundaries_cython(long[:, :] im_label not None, long connectiv

bx = []
by = []
if x_start >= 0 and y_start >= 0:

if connectivity == 4:
bx, by = _isbf(
im_label, im_label_90, im_label_180, im_label_270,
x_start, y_start, max_length);
if connectivity == 4:
bx, by = _isbf(
im_label, im_label_90, im_label_180, im_label_270,
x_start, y_start, max_length);

else:
bx, by = _moore(
im_label, im_label_90, im_label_180, im_label_270,
x_start, y_start, max_length);
else:
bx, by = _moore(
im_label, im_label_90, im_label_180, im_label_270,
x_start, y_start, max_length);

return np.asarray(bx), np.asarray(by)

Expand Down Expand Up @@ -126,11 +127,25 @@ def _moore(long[:, :] mask, long[:, :] mask_90, long[:, :] mask_180, long[:, :]

cdef long i, j

cdef long itercount = 0
cdef long last_len = 0

if sum > 1:

# loop until true
while(True):

if len(list_bx) > last_len:
last_len = len(list_bx)
# itercount = 0
itercount += 1
else:
itercount += 1
if itercount > 10:
list_bx.clear()
list_by.clear()
break

h = np.zeros((row_isbf, col_isbf), dtype=int)

with nogil:
Expand Down Expand Up @@ -277,6 +292,8 @@ def _isbf(long[:, :] mask, long[:, :] mask_90, long[:, :] mask_180, long[:, :] m

# check degenerate case where mask contains 1 pixel
cdef long sum = np.sum(mask)
cdef long itercount = 0
cdef long last_len = 0

if sum > 1:
with nogil:
Expand All @@ -286,6 +303,15 @@ def _isbf(long[:, :] mask, long[:, :] mask_90, long[:, :] mask_180, long[:, :] m

while(True):

if len(list_bx) > last_len:
last_len = len(list_bx)
itercount = 0
else:
itercount += 1
if itercount > 10:
list_bx.clear()
list_by.clear()
break
h = np.zeros((row_isbf, col_isbf), dtype=int)

with nogil:
Expand Down
12 changes: 11 additions & 1 deletion tests/test_segmentation_label.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def test_trace_boundary(self):
[0, 0, 0, 0, 0, 0, 0, 1, 1, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=bool)

# refenece neighbors for isbf
# reference neighbors for isbf
rx_isbf = [1, 1, 2, 2, 3, 4, 5, 6, 7, 8, 8, 9, 9, 8, 7, 7, 7,
7, 6, 6, 5, 5, 5, 4, 4, 3, 3, 3, 3, 2, 1]
ry_isbf = [7, 8, 8, 7, 6, 6, 6, 6, 6, 7, 8, 8, 7, 7, 6, 5, 4,
Expand All @@ -46,6 +46,16 @@ def test_trace_boundary(self):
np.testing.assert_allclose(rx_moore, x_moore[0])
np.testing.assert_allclose(ry_moore, y_moore[0])

def test_trace_boundary_bad(self):
# Make sure the algorithms terminate work in a degenerate case
m_neighbor = np.array([[0, 0, 0, 0, 0],
[0, 1, 0, 1, 0],
[0, 0, 0, 0, 0]], dtype=bool)
x, y = trace_object_boundaries(m_neighbor, simplify_colinear_spurs=False)
assert not len(x)
x, y = trace_object_boundaries(m_neighbor, 8, simplify_colinear_spurs=False)
assert not len(x)


class TestDeleteBorderLabel:

Expand Down

0 comments on commit 7d023cc

Please sign in to comment.