roboflow/supervision · error · ValueError

Triggering anchors cannot be empty.

Error message

Triggering anchors cannot be empty.

What it means

Raised by PolygonZone.__init__ when triggering_anchors materializes to an empty list. The zone decides in/out membership purely by testing the configured anchor points of each box; with zero anchors there is no predicate to evaluate, so construction fails fast rather than silently matching nothing.

Solutions

  1. Pass at least one anchor, the typical default being triggering_anchors=[Position.BOTTOM_CENTER].
  2. Validate the config source before constructing the zone: raise a clear config error when the anchors field is empty.
  3. If the emptiness is legitimate in your flow (zone disabled), skip constructing the PolygonZone entirely.

Example fix

# before
zone = sv.PolygonZone(polygon=polygon, triggering_anchors=[])

# after
zone = sv.PolygonZone(polygon=polygon, triggering_anchors=[sv.Position.BOTTOM_CENTER])
Defensive patterns

Strategy: validation

Validate before calling

anchors = list(triggering_anchors) or [sv.Position.BOTTOM_CENTER]
if not anchors:
    raise ValueError('config error: triggering_anchors is empty')
zone = sv.PolygonZone(polygon=polygon, triggering_anchors=anchors)

Type guard

def has_triggering_anchor(items) -> bool:
    return bool(list(items))

Prevention

When it happens

Trigger: Passing triggering_anchors=[] to PolygonZone; passing a generator that is already exhausted; building the anchor list programmatically (e.g. from CLI flags or a config filter) that ends up empty.

Common situations: Config-driven applications where an anchor list comes from a YAML/JSON file and the user leaves it empty or filters out all entries; refactoring that accidentally passes an empty tuple default.

Related errors


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

Appendix: source

Thrown at src/supervision/detection/tools/polygon_zone.py:85

        ... )
        >>> detections = sv.Detections(xyxy=np.array([[80, 80, 120, 120]]))
        >>> polygon_zone.trigger(detections)
        array([ True])

        ```
    """

    def __init__(
        self,
        polygon: npt.NDArray[np.int64],
        triggering_anchors: Iterable[Position] = (Position.BOTTOM_CENTER,),
        require_all_anchors: bool = True,
    ) -> None:
        self.polygon = polygon.astype(int)
        # Materialize once so we can safely accept generators without exhausting them.
        self.triggering_anchors = list(triggering_anchors)
        if not self.triggering_anchors:
            raise ValueError("Triggering anchors cannot be empty.")
        self.require_all_anchors = require_all_anchors

        self.current_count = 0

        x_max, y_max = np.max(polygon, axis=0)
        self.mask = polygon_to_mask(
            polygon=polygon, resolution_wh=(x_max + 2, y_max + 2)
        )

    def trigger(self, detections: Detections) -> npt.NDArray[np.bool_]:
        """
        Determines if the detections are within the polygon zone.

        Anchor points are calculated from original (unclipped) detection boxes to
        avoid per-zone clipping shifting anchor positions. This prevents a single
        detection from being counted in multiple non-overlapping zones due to
        clipping artifacts, although overlapping zones may still legitimately
        contain the same detection.

View on GitHub (pinned to 7f254d9784)