Open name-used opened 1 month ago
@FattanePourakpour can you take a look at this? This is clearly wrong - a function like trace_object_boundaries
or opencv should be used to trace boundaries as recommended above (this would be bwboundaries
if you are coming from Matlab).
The current code uses np.argwhere
, which scans the boundaries in raster format and doesn’t accurately trace them. I explored both cv2.findContours
and trace_object_boundaries
. While both functions trace boundaries correctly, I found that trace_object_boundaries
is about 1.6 times faster. Another key difference is that the trace_object_boundaries
function treats the starting point as the endpoint twice. This can easily be fixed by simply dropping the last coordinate.
np.argwhere
labels = np.array([[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 1, 1, 1, 1, 0, 0],
[0, 0, 1, 1, 1, 1, 1, 0, 0],
[0, 0, 0, 0, 1, 1, 1, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=np.uint8)
X = find_boundaries(labels, mode='inner').astype(np.uint8)
Bounds= np.argwhere(X==1)
print(Bounds.T)
print('---------')
print(X)
--->>>
[[2 2 2 2 2 3 3 3 4 4 4]
[2 3 4 5 6 2 3 6 4 5 6]]
---------
[[0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0]
[0 0 1 1 1 1 1 0 0]
[0 0 1 1 0 0 1 0 0]
[0 0 0 0 1 1 1 0 0]
[0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0]]
cv2.findContours
%%time
for _ in range(100000): # Run cv2.findContours to estimate the step time
[Bounds], _ = cv2.findContours(
(find_boundaries(labels, mode='inner').astype(np.uint8) == 1).astype(np.uint8),
# cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE,
cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE,
)
Bounds = Bounds[:, 0, :]
print(Bounds.T)
print('---------')
print(X)
--->>>
[[2 2 3 4 5 6 6 6 5 4 3]
[2 3 3 4 4 4 3 2 2 2 2]]
---------
[[0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0]
[0 0 1 1 1 1 1 0 0]
[0 0 1 1 0 0 1 0 0]
[0 0 0 0 1 1 1 0 0]
[0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0]]
CPU times: user 26.5 s, sys: 86.4 ms, total: 26.6 s
Wall time: 26.9 s
trace_object_boundaries
%%time
for i in range(100000): #run trace_object_boundaries to estimate the step time
Bounds = trace_object_boundaries(labels,
conn=8, trace_all=False,
x_start=None, y_start=None,
max_length=None,
simplify_colinear_spurs=False,
eps_colinear_area=0.01,
region_props=None)
Bounds = np.stack([Bounds[0][0][:-1], Bounds[1][0][:-1]], axis=-1)
print(Bounds.T)
print('---------')
print(X)
--->>>
[[2 2 2 2 2 3 4 4 4 3 3]
[2 3 4 5 6 6 6 5 4 3 2]]
---------
[[0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0]
[0 0 1 1 1 1 1 0 0]
[0 0 1 1 0 0 1 0 0]
[0 0 0 0 1 1 1 0 0]
[0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0]]
CPU times: user 16.9 s, sys: 39.7 ms, total: 16.9 s
Wall time: 17.2 s
I think the second suggestion to use complex curvature is logical because it retains the directional information of the curvature, ensuring that changes in angle are accurately represented.
@FattanePourakpour What are your thoughts on the actual contour output? It seems like our trace is producing a more sensible result.
I agree, the trace from trace_object_boundaries
seems to capture the contours more accurately and matches the object’s boundaries better. Plus, in my tests, it performed faster than cv2.findContours
.
I agree, the trace from
trace_object_boundaries
seems to capture the contours more accurately and matches the object’s boundaries better. Plus, in my tests, it performed faster thancv2.findContours
.
At least in this case it seems to follow the most obvious path around the object.
Go ahead and prepare a pull request and we'll see if the tests pass. Some functions have stored expected values that they test against, so if that's the case, we will have to update these values.
Sure, I’ll prepare the pull request and check for any tests with stored expected values. If needed, I’ll update those values.
Version: bf0f18ecea93087dfbf541d8c3c63fed1de9a283 Commits on May 13, 2024 Branch master
There are two errors at file histomicstk.features.compute_fsd_features.py: line 77 and line 188.
For Correction Suggestions: