roboflow/supervision · error · ValueError

epsilon_step must be positive.

Error message

epsilon_step must be positive.

What it means

Raised by approximate_polygon when the epsilon_step argument is zero or negative. epsilon_step controls how much the Douglas-Peucker tolerance grows on each iteration of the loop that simplifies the polygon toward the target point count. A non-positive step would make no progress (infinite loop) or move backward, so it is rejected up front.

Source

Thrown at src/supervision/detection/utils/polygons.py:114

        >>> result = approximate_polygon(polygon, percentage=0.5)
        >>> result.shape[1]
        2
        >>> len(result) <= max(int(len(polygon) * 0.5), 3)
        True

        Polygon already at or below target — returned unchanged:

        >>> tiny = np.array([[0, 0], [5, 0], [2, 4]])
        >>> approximate_polygon(tiny, percentage=0.5) is tiny
        True

        ```
    """

    if percentage < 0 or percentage >= 1:
        raise ValueError("Percentage must be in the range [0, 1).")
    if epsilon_step <= 0:
        raise ValueError("epsilon_step must be positive.")

    target_points = max(int(len(polygon) * (1 - percentage)), 3)

    if len(polygon) <= target_points:
        return polygon

    epsilon: float = 0
    approximated_points = polygon
    while len(approximated_points) > target_points:
        epsilon += epsilon_step
        candidate = np.squeeze(cv2.approxPolyDP(polygon, epsilon, closed=True), axis=1)
        # Stop before the approximation collapses below a valid polygon; keep the
        # last result with at least three points.
        if len(candidate) < 3:
            break
        approximated_points = candidate

    return approximated_points

View on GitHub (pinned to 7f254d9784)

Solutions

  1. Pass a small positive step such as epsilon_step=0.1 (the loop increments by this value each iteration).
  2. If the step is computed dynamically, clamp it: epsilon_step = max(epsilon_step, 0.1).
  3. If you do not want simplification at all, skip the call or pass percentage=0 (with a valid positive step) — the function already returns the polygon unchanged when it is at or below the target point count.

Example fix

// before
approximate_polygon(poly, percentage=0.5, epsilon_step=0)

// after
approximate_polygon(poly, percentage=0.5, epsilon_step=0.1)
Defensive patterns

Strategy: validation

Validate before calling

epsilon_step = max(float(epsilon_step), 0.1)
result = approximate_polygon(polygon, percentage=0.5, epsilon_step=epsilon_step)

Type guard

def is_valid_epsilon_step(v: float) -> bool:
    return float(v) > 0

Try / catch

try:
    simplified = approximate_polygon(poly, percentage=p, epsilon_step=e)
except ValueError as err:
    if 'epsilon_step' in str(err):
        simplified = approximate_polygon(poly, percentage=p, epsilon_step=0.1)
    else:
        raise

Prevention

When it happens

Trigger: Calling supervision.detection.utils.polygons.approximate_polygon(polygon, percentage=0.5, epsilon_step=0) or with a negative epsilon_step. Also reachable via any pipeline that computes epsilon_step dynamically (e.g. proportional to box area) and lets it round down to 0 for tiny polygons.

Common situations: Passing 0 expecting 'no simplification' semantics; deriving epsilon_step from image scale so that very small regions produce 0; copy-pasting a default of 0 from another API where 0 means 'auto'.

Related errors


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