From 7d023ccfe3e8e75974880720dfd1406acf08617b Mon Sep 17 00:00:00 2001 From: David Manthey Date: Fri, 6 Dec 2024 14:43:52 -0500 Subject: [PATCH] Harden the trace boundary code A one pixel object could get stuck in an infinite loop in the moore boundary call. --- .../label/_trace_object_boundaries_cython.pyx | 46 +++++++++++++++---- tests/test_segmentation_label.py | 12 ++++- 2 files changed, 47 insertions(+), 11 deletions(-) diff --git a/histomicstk/segmentation/label/_trace_object_boundaries_cython.pyx b/histomicstk/segmentation/label/_trace_object_boundaries_cython.pyx index 236a599fe..5f13c7618 100644 --- a/histomicstk/segmentation/label/_trace_object_boundaries_cython.pyx +++ b/histomicstk/segmentation/label/_trace_object_boundaries_cython.pyx @@ -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 @@ -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) @@ -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: @@ -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: @@ -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: diff --git a/tests/test_segmentation_label.py b/tests/test_segmentation_label.py index 560d39c33..4803541c0 100644 --- a/tests/test_segmentation_label.py +++ b/tests/test_segmentation_label.py @@ -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, @@ -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: