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

  1. Capture the input mask when the error occurs and reduce it to a minimal reproducer
  2. Install `opencv-python` so the C++ Suzuki-Abe implementation is used instead of the fallback
  3. 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

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


AI-assisted analysis of roboflow/supervision@7f254d9784 (2026-08-15). Data as JSON: /api/errors/ec338ebb2d484df4. Report an issue: GitHub.