{"record":{"id":"be83756d42fd2538","repo":"roboflow/supervision","slug":"epsilon-must-be-non-negative","errorCode":null,"errorMessage":"epsilon must be non-negative","messagePattern":"epsilon must be non-negative","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"src/supervision/_cv2/_geometry.py","lineNumber":167,"sourceCode":"            continue\n\n        destination[write_position] = point\n        start_point = point\n        write_position = (write_position + 1) % count\n        point = end_point\n        index += 1\n\n    if not closed:\n        destination[write_position] = point\n    return np.asarray(destination[:new_count], dtype=np.float64)\n\n\ndef _approx_poly_dp(\n    contour: npt.NDArray[Any], epsilon: float, closed: bool\n) -> npt.NDArray[Any]:\n    \"\"\"Approximate a contour with the supported OpenCV polygon contract.\"\"\"\n    if epsilon < 0:\n        raise ValueError(\"epsilon must be non-negative\")\n    points = _as_points(contour)\n    if len(points) == 0:\n        dtype = np.asarray(contour).dtype\n        return np.empty((0, 1, 2), dtype=dtype)\n\n    epsilon_squared = float(epsilon) ** 2\n    simplified = _simplify_slices(points, epsilon_squared, closed)\n    simplified = _cleanup_approximation(simplified, epsilon_squared, closed)\n\n    dtype = np.asarray(contour).dtype\n    return simplified.astype(dtype, copy=False).reshape(-1, 1, 2)\n\n\ndef _cross(edge: npt.NDArray[np.float64], point: npt.NDArray[np.float64]) -> float:\n    \"\"\"Return the two-dimensional cross product of two vectors.\"\"\"\n    return float(edge[0] * point[1] - edge[1] * point[0])\n\n","sourceCodeStart":149,"sourceCodeEnd":185,"githubUrl":"https://github.com/roboflow/supervision/blob/7f254d9784d4c37e0f03cd89ddee164c8db099c0/src/supervision/_cv2/_geometry.py#L149-L185","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Pass a non-negative epsilon; the common idiom is epsilon = 0.02 * cv2.arcLength(contour, True).","Resolve 'unset' sentinels to a computed default before calling.","Clamp or validate config-supplied epsilon: if epsilon < 0: raise/config error early."],"exampleFix":"# before\nepsilon = -0.03 * cv2.arcLength(contour, True)  # sign typo\napprox = cv2.approxPolyDP(contour, epsilon, True)\n\n# after\nepsilon = 0.03 * cv2.arcLength(contour, True)\napprox = cv2.approxPolyDP(contour, epsilon, True)","handlingStrategy":"validation","validationCode":"epsilon = max(0.0, 0.02 * cv2.arcLength(contour, True))\napprox = cv2.approxPolyDP(contour, epsilon, True)","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Compute epsilon from arcLength with a positive ratio","Resolve -1 'unset' sentinels before the call","Validate epsilon >= 0 when it comes from config"],"tags":["opencv-fallback","approx-poly-dp","epsilon","input-validation"],"backgroundTag":null,"analyzedSha":"7f254d9784d4c37e0f03cd89ddee164c8db099c0","analyzedAt":"2026-08-15T05:13:01.950Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}