roboflow/supervision · error · ValueError

epsilon must be non-negative

Error message

epsilon must be non-negative

What it means

cv2.approxPolyDP's epsilon is a distance tolerance in pixels; negative values are meaningless and OpenCV's own implementation requires epsilon >= 0. The fallback validates this up front before running its Ramer-Douglas-Pecker-style simplification.

Source

Thrown at src/supervision/_cv2/_geometry.py:167

            continue

        destination[write_position] = point
        start_point = point
        write_position = (write_position + 1) % count
        point = end_point
        index += 1

    if not closed:
        destination[write_position] = point
    return np.asarray(destination[:new_count], dtype=np.float64)


def _approx_poly_dp(
    contour: npt.NDArray[Any], epsilon: float, closed: bool
) -> npt.NDArray[Any]:
    """Approximate a contour with the supported OpenCV polygon contract."""
    if epsilon < 0:
        raise ValueError("epsilon must be non-negative")
    points = _as_points(contour)
    if len(points) == 0:
        dtype = np.asarray(contour).dtype
        return np.empty((0, 1, 2), dtype=dtype)

    epsilon_squared = float(epsilon) ** 2
    simplified = _simplify_slices(points, epsilon_squared, closed)
    simplified = _cleanup_approximation(simplified, epsilon_squared, closed)

    dtype = np.asarray(contour).dtype
    return simplified.astype(dtype, copy=False).reshape(-1, 1, 2)


def _cross(edge: npt.NDArray[np.float64], point: npt.NDArray[np.float64]) -> float:
    """Return the two-dimensional cross product of two vectors."""
    return float(edge[0] * point[1] - edge[1] * point[0])

View on GitHub (pinned to 7f254d9784)

Solutions

  1. Pass a non-negative epsilon; the common idiom is epsilon = 0.02 * cv2.arcLength(contour, True).
  2. Resolve 'unset' sentinels to a computed default before calling.
  3. Clamp or validate config-supplied epsilon: if epsilon < 0: raise/config error early.

Example fix

# before
epsilon = -0.03 * cv2.arcLength(contour, True)  # sign typo
approx = cv2.approxPolyDP(contour, epsilon, True)

# after
epsilon = 0.03 * cv2.arcLength(contour, True)
approx = cv2.approxPolyDP(contour, epsilon, True)
Defensive patterns

Strategy: validation

Validate before calling

epsilon = max(0.0, 0.02 * cv2.arcLength(contour, True))
approx = cv2.approxPolyDP(contour, epsilon, True)

Prevention

When it happens

Trigger: Passing a negative epsilon, typically from a computed value like -0.02 * perimeter due to a sign error, or from a config default set to -1 as an 'unset' sentinel.

Common situations: Sentinel patterns (epsilon = -1 meaning 'auto') that are never replaced before the call; arithmetic bugs producing negative tolerances; copying formulas with a typo'd minus sign.

Related errors


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