Skip to content

Polygon Zone

PolygonZone

supervision.detection.tools.polygon_zone.PolygonZone

A class for defining a polygon-shaped zone within a frame for detecting objects.

Warning

PolygonZone uses the tracker_id. Read here to learn how to plug tracking into your inference pipeline.

Attributes:

Name Type Description
polygon

A polygon represented by a numpy array of shape (N, 2), containing the x, y coordinates of the points.

triggering_anchors

A list of positions specifying which anchors of the detections bounding box to consider when deciding on whether the detection fits within the PolygonZone (default: (sv.Position.BOTTOM_CENTER,)).

current_count

The current count of detected objects within the zone

mask

The 2D bool mask for the polygon zone

Example
>>> import numpy as np
>>> import supervision as sv
>>> polygon = np.array([[100, 200], [200, 100], [300, 200], [200, 300]])
>>> polygon_zone = sv.PolygonZone(polygon=polygon)
>>> detections = sv.Detections(
...     xyxy=np.array([[180, 100, 220, 200], [400, 400, 450, 500]])
... )
>>> is_detections_in_zone = polygon_zone.trigger(detections)
>>> is_detections_in_zone
array([ True, False])
>>> polygon_zone.current_count
1
Source code in src/supervision/detection/tools/polygon_zone.py
class PolygonZone:
    """
    A class for defining a polygon-shaped zone within a frame for detecting objects.

    !!! warning

        PolygonZone uses the `tracker_id`. Read
        [here](/latest/trackers/) to learn how to plug
        tracking into your inference pipeline.

    Attributes:
        polygon: A polygon represented by a numpy array of shape
            `(N, 2)`, containing the `x`, `y` coordinates of the points.
        triggering_anchors: A list of positions specifying
            which anchors of the detections bounding box to consider when deciding on
            whether the detection fits within the PolygonZone
            (default: (sv.Position.BOTTOM_CENTER,)).
        current_count: The current count of detected objects within the zone
        mask: The 2D bool mask for the polygon zone

    Example:
        ```pycon
        >>> import numpy as np
        >>> import supervision as sv
        >>> polygon = np.array([[100, 200], [200, 100], [300, 200], [200, 300]])
        >>> polygon_zone = sv.PolygonZone(polygon=polygon)
        >>> detections = sv.Detections(
        ...     xyxy=np.array([[180, 100, 220, 200], [400, 400, 450, 500]])
        ... )
        >>> is_detections_in_zone = polygon_zone.trigger(detections)
        >>> is_detections_in_zone
        array([ True, False])
        >>> polygon_zone.current_count
        1

        ```
    """

    def __init__(
        self,
        polygon: npt.NDArray[np.int64],
        triggering_anchors: Iterable[Position] = (Position.BOTTOM_CENTER,),
    ):
        self.polygon = polygon.astype(int)
        self.triggering_anchors = triggering_anchors
        if not list(self.triggering_anchors):
            raise ValueError("Triggering anchors cannot be empty.")

        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.

        Args:
            detections: The detections to be checked against the polygon zone

        Returns:
            A boolean numpy array indicating
                if each detection is within the polygon zone
        """
        if len(detections) == 0:
            self.current_count = 0
            return np.array([], dtype=bool)

        all_anchors = np.array(
            [
                np.ceil(detections.get_anchors_coordinates(anchors)).astype(int)
                for anchors in self.triggering_anchors
            ]
        )

        mask_h, mask_w = self.mask.shape
        x, y = all_anchors[:, :, 0], all_anchors[:, :, 1]
        in_bounds = (x >= 0) & (y >= 0) & (x < mask_w) & (y < mask_h)
        x_safe = np.clip(x, 0, mask_w - 1)
        y_safe = np.clip(y, 0, mask_h - 1)
        is_in_zone = np.all(in_bounds & self.mask[y_safe, x_safe], axis=0)
        self.current_count = int(np.sum(is_in_zone))
        return is_in_zone.astype(bool)

Functions

trigger(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.

Parameters:

Name Type Description Default
detections
Detections

The detections to be checked against the polygon zone

required

Returns:

Type Description
NDArray[bool_]

A boolean numpy array indicating if each detection is within the polygon zone

Source code in src/supervision/detection/tools/polygon_zone.py
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.

    Args:
        detections: The detections to be checked against the polygon zone

    Returns:
        A boolean numpy array indicating
            if each detection is within the polygon zone
    """
    if len(detections) == 0:
        self.current_count = 0
        return np.array([], dtype=bool)

    all_anchors = np.array(
        [
            np.ceil(detections.get_anchors_coordinates(anchors)).astype(int)
            for anchors in self.triggering_anchors
        ]
    )

    mask_h, mask_w = self.mask.shape
    x, y = all_anchors[:, :, 0], all_anchors[:, :, 1]
    in_bounds = (x >= 0) & (y >= 0) & (x < mask_w) & (y < mask_h)
    x_safe = np.clip(x, 0, mask_w - 1)
    y_safe = np.clip(y, 0, mask_h - 1)
    is_in_zone = np.all(in_bounds & self.mask[y_safe, x_safe], axis=0)
    self.current_count = int(np.sum(is_in_zone))
    return is_in_zone.astype(bool)

PolygonZoneAnnotator

supervision.detection.tools.polygon_zone.PolygonZoneAnnotator

A class for annotating a polygon-shaped zone within a frame with a count of detected objects.

Attributes:

Name Type Description
zone

The polygon zone to be annotated

color

The color to draw the polygon lines, default is white

thickness

The thickness of the polygon lines, default is 2

text_color

The color of the text on the polygon, default is black

text_scale

The scale of the text on the polygon, default is 0.5

text_thickness

The thickness of the text on the polygon, default is 1

text_padding

The padding around the text on the polygon, default is 10

font

The font type for the text on the polygon, default is cv2.FONT_HERSHEY_SIMPLEX

center

The center of the polygon for text placement

display_in_zone_count

Show the label of the zone or not. Default is True

opacity

The opacity of zone filling when drawn on the scene. Default is 0

Source code in src/supervision/detection/tools/polygon_zone.py
class PolygonZoneAnnotator:
    """
    A class for annotating a polygon-shaped zone within a frame with a count of
    detected objects.

    Attributes:
        zone: The polygon zone to be annotated
        color: The color to draw the polygon lines, default is white
        thickness: The thickness of the polygon lines, default is 2
        text_color: The color of the text on the polygon, default is black
        text_scale: The scale of the text on the polygon, default is 0.5
        text_thickness: The thickness of the text on the polygon, default is 1
        text_padding: The padding around the text on the polygon, default is 10
        font: The font type for the text on the polygon,
            default is cv2.FONT_HERSHEY_SIMPLEX
        center: The center of the polygon for text placement
        display_in_zone_count: Show the label of the zone or not. Default is True
        opacity: The opacity of zone filling when drawn on the scene. Default is 0
    """

    def __init__(
        self,
        zone: PolygonZone,
        color: Color = Color.WHITE,
        thickness: int = 2,
        text_color: Color = Color.BLACK,
        text_scale: float = 0.5,
        text_thickness: int = 1,
        text_padding: int = 10,
        display_in_zone_count: bool = True,
        opacity: float = 0,
    ):
        self.zone = zone
        self.color = color
        self.thickness = thickness
        self.text_color = text_color
        self.text_scale = text_scale
        self.text_thickness = text_thickness
        self.text_padding = text_padding
        self.font = cv2.FONT_HERSHEY_SIMPLEX
        self.center = get_polygon_center(polygon=zone.polygon)
        self.display_in_zone_count = display_in_zone_count
        self.opacity = opacity

    def annotate(
        self, scene: npt.NDArray[Any], label: str | None = None
    ) -> npt.NDArray[Any]:
        """
        Annotates the polygon zone within a frame with a count of detected objects.

        Args:
            scene: The image on which the polygon zone will be annotated
            label: A label for the count of detected objects
                within the polygon zone (default: None)

        Returns:
            The image with the polygon zone and count of detected objects
        """
        if self.opacity == 0:
            annotated_frame = draw_polygon(
                scene=scene,
                polygon=self.zone.polygon,
                color=self.color,
                thickness=self.thickness,
            )
        else:
            annotated_frame = draw_filled_polygon(
                scene=scene.copy(),
                polygon=self.zone.polygon,
                color=self.color,
                opacity=self.opacity,
            )
            annotated_frame = draw_polygon(
                scene=annotated_frame,
                polygon=self.zone.polygon,
                color=self.color,
                thickness=self.thickness,
            )

        if self.display_in_zone_count:
            annotated_frame = draw_text(
                scene=annotated_frame,
                text=str(self.zone.current_count) if label is None else label,
                text_anchor=self.center,
                background_color=self.color,
                text_color=self.text_color,
                text_scale=self.text_scale,
                text_thickness=self.text_thickness,
                text_padding=self.text_padding,
                text_font=self.font,
            )

        return cast(npt.NDArray[Any], annotated_frame)

Functions

annotate(scene: npt.NDArray[Any], label: str | None = None) -> npt.NDArray[Any]

Annotates the polygon zone within a frame with a count of detected objects.

Parameters:

Name Type Description Default
scene
NDArray[Any]

The image on which the polygon zone will be annotated

required
label
str | None

A label for the count of detected objects within the polygon zone (default: None)

None

Returns:

Type Description
NDArray[Any]

The image with the polygon zone and count of detected objects

Source code in src/supervision/detection/tools/polygon_zone.py
def annotate(
    self, scene: npt.NDArray[Any], label: str | None = None
) -> npt.NDArray[Any]:
    """
    Annotates the polygon zone within a frame with a count of detected objects.

    Args:
        scene: The image on which the polygon zone will be annotated
        label: A label for the count of detected objects
            within the polygon zone (default: None)

    Returns:
        The image with the polygon zone and count of detected objects
    """
    if self.opacity == 0:
        annotated_frame = draw_polygon(
            scene=scene,
            polygon=self.zone.polygon,
            color=self.color,
            thickness=self.thickness,
        )
    else:
        annotated_frame = draw_filled_polygon(
            scene=scene.copy(),
            polygon=self.zone.polygon,
            color=self.color,
            opacity=self.opacity,
        )
        annotated_frame = draw_polygon(
            scene=annotated_frame,
            polygon=self.zone.polygon,
            color=self.color,
            thickness=self.thickness,
        )

    if self.display_in_zone_count:
        annotated_frame = draw_text(
            scene=annotated_frame,
            text=str(self.zone.current_count) if label is None else label,
            text_anchor=self.center,
            background_color=self.color,
            text_color=self.text_color,
            text_scale=self.text_scale,
            text_thickness=self.text_thickness,
            text_padding=self.text_padding,
            text_font=self.font,
        )

    return cast(npt.NDArray[Any], annotated_frame)

Comments