roboflow/supervision · critical · RuntimeError
Contour border tracing did not converge
Error message
Contour border tracing did not converge
What it means
The fallback contour tracer implements Moore-neighbor/Suzuki-style border following; each border walk is bounded by `4 * image.size` steps at src/supervision/_cv2/_contours.py:85. If the walk has not returned to its start point by then, the trace is considered non-convergent and a RuntimeError is raised — this is an internal invariant, not a user-input error, and indicates either a bug in the tracer or memory corruption of the label array.
Source
Thrown at src/supervision/_cv2/_contours.py:85
if is_foreground(row, column):
next_point = (row, column)
break
if candidate_direction == 0:
east_zero = True
if east_zero:
labels[current] = -border_number
elif labels[current] == 0:
labels[current] = border_number
contour.append(current)
if next_point == start and current == first_neighbor and len(contour) > 1:
return contour
if next_point is None:
return contour
previous_point, current = current, next_point
if len(contour) > 4 * image.size:
raise RuntimeError("Contour border tracing did not converge")
def _trace_borders(mask: npt.NDArray[np.bool_]) -> list[np.ndarray]:
"""Trace all foreground and hole borders in raster candidate order."""
image = np.ascontiguousarray(mask, dtype=bool)
labels = np.zeros(image.shape, dtype=np.int32)
left_zero = image & ~np.pad(image[:, :-1], ((0, 0), (1, 0)))
right_zero = image & ~np.pad(image[:, 1:], ((0, 0), (0, 1)))
candidates = np.argwhere(left_zero | right_zero)
borders: list[np.ndarray] = []
border_number = 1
for row, column in candidates:
row, column = int(row), int(column)
if left_zero[row, column] and labels[row, column] == 0:
border_number += 1
border = _follow_border(
image, labels, (row, column), (row, column - 1), border_number
)View on GitHub (pinned to 7f254d9784)
Solutions
- Capture the input mask when the error occurs and reduce it to a minimal reproducer
- Install `opencv-python` so the C++ Suzuki-Abe implementation is used instead of the fallback
- Report the reproducer to the supervision maintainers (internal invariant violation)
Defensive patterns
Strategy: try-catch
Try / catch
try:
contours, _ = cv2.findContours(mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
except RuntimeError as err:
if "did not converge" in str(err):
np.save("/tmp/nonconvergent_mask.npy", mask) # preserve the reproducer
raise RuntimeError(f"contour tracer failed on mask {mask.shape}; saved for bug report") from err
raise Prevention
- Treat this error as a bug report trigger — save the input mask and report it upstream
- Install opencv-python in production to use the battle-tested C++ tracer
- Avoid sharing/mutating the mask array across threads while tracing
When it happens
Trigger: Essentially unreachable through normal API use; conceivably triggered by pathological masks (e.g. extremely complex checkerboards), a corrupted boolean mask (non-contiguous views mutated concurrently), or a bug in the neighbor-selection logic for a specific border configuration.
Common situations: Long-running processes with threaded mutation of shared numpy arrays; unusual synthetic masks stress-testing the fallback backend; a genuine supervision bug — check the issue tracker with a reproducing mask.
Related errors
- Only None hierarchy is supported by the fallback
- Only RETR_TREE is supported by the fallback
- Only CHAIN_APPROX_SIMPLE is supported by the fallback
- Contour input must be a two-dimensional image
- Empty group detected when non-max-merging detections: {merge
AI-assisted analysis of roboflow/supervision@7f254d9784 (2026-08-15).
Data as JSON: /api/errors/ec338ebb2d484df4.
Report an issue: GitHub.