{"record":{"id":"abc8d823ecac1550","repo":"roboflow/supervision","slug":"percentage-must-be-in-the-range-0-1","errorCode":null,"errorMessage":"Percentage must be in the range [0, 1).","messagePattern":"Percentage must be in the range \\[0, 1\\)\\.","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"src/supervision/detection/utils/polygons.py","lineNumber":112,"sourceCode":"        >>> polygon = np.array([[0, 0], [10, 0], [10, 10], [0, 10],\n        ...                     [5, 10], [5, 5], [3, 7], [1, 9]])\n        >>> result = approximate_polygon(polygon, percentage=0.5)\n        >>> result.shape[1]\n        2\n        >>> len(result) <= max(int(len(polygon) * 0.5), 3)\n        True\n\n        Polygon already at or below target — returned unchanged:\n\n        >>> tiny = np.array([[0, 0], [5, 0], [2, 4]])\n        >>> approximate_polygon(tiny, percentage=0.5) is tiny\n        True\n\n        ```\n    \"\"\"\n\n    if percentage < 0 or percentage >= 1:\n        raise ValueError(\"Percentage must be in the range [0, 1).\")\n    if epsilon_step <= 0:\n        raise ValueError(\"epsilon_step must be positive.\")\n\n    target_points = max(int(len(polygon) * (1 - percentage)), 3)\n\n    if len(polygon) <= target_points:\n        return polygon\n\n    epsilon: float = 0\n    approximated_points = polygon\n    while len(approximated_points) > target_points:\n        epsilon += epsilon_step\n        candidate = np.squeeze(cv2.approxPolyDP(polygon, epsilon, closed=True), axis=1)\n        # Stop before the approximation collapses below a valid polygon; keep the\n        # last result with at least three points.\n        if len(candidate) < 3:\n            break\n        approximated_points = candidate","sourceCodeStart":94,"sourceCodeEnd":130,"githubUrl":"https://github.com/roboflow/supervision/blob/7f254d9784d4c37e0f03cd89ddee164c8db099c0/src/supervision/detection/utils/polygons.py#L94-L130","documentation":"`sv.approximate_polygon(polygon, percentage, epsilon_step)` simplifies a polygon by removing a fraction of its points via Ramer-Douglas-Peucker; `percentage` is that fraction and must lie in [0, 1). This ValueError fires for negative values or values >= 1 — a percentage of 1 would mean removing all points (a polygon needs at least 3), and negative removal is meaningless. Note the value is a fraction, not 0-100.","triggerScenarios":"Calling `approximate_polygon(poly, percentage=50)` intending 50 percent (50 is far outside [0,1)); passing `percentage=1.0` to drop every point; computing the fraction dynamically and letting rounding push it to exactly 1.0 or below 0.","commonSituations":"Confusing the 0-1 fraction convention with a 0-100 scale; UI sliders that emit percentages 0-100 forwarded verbatim; derived values like `1 - keep_ratio` where keep_ratio rounds to 0.","solutions":["Express the fraction in [0, 1): use 0.5 for 'remove half the points', not 50.","Clamp computed values: `percentage = min(max(percentage, 0.0), 0.999)`.","Convert from a 0-100 scale: `percentage = pct_100 / 100.0`."],"exampleFix":"# before\nsmall = sv.approximate_polygon(polygon, percentage=30)  # meant 30%\n\n# after\nsmall = sv.approximate_polygon(polygon, percentage=0.3)","handlingStrategy":"validation","validationCode":"if not 0 <= percentage < 1:\n    percentage = min(max(percentage / 100.0 if percentage > 1 else percentage, 0.0), 0.999)\nresult = sv.approximate_polygon(polygon, percentage=percentage)","typeGuard":"def is_valid_percentage(v) -> bool:\n    return isinstance(v, (int, float)) and 0 <= v < 1","tryCatchPattern":null,"preventionTips":["Treat percentage as a 0-1 fraction; divide by 100 when it comes from a UI slider.","Clamp computed fractions to [0, 0.999] to absorb rounding."],"tags":["argument-validation","polygons","geometry","range-check"],"backgroundTag":null,"analyzedSha":"7f254d9784d4c37e0f03cd89ddee164c8db099c0","analyzedAt":"2026-08-15T05:13:01.950Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}