Skip to content

Commit 7a98e5f

Browse files
Add polygon primitive
Signed-off-by: Ashwin Vaidya <[email protected]>
1 parent f8a55c9 commit 7a98e5f

File tree

4 files changed

+76
-4
lines changed

4 files changed

+76
-4
lines changed

model_api/python/model_api/visualizer/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
# SPDX-License-Identifier: Apache-2.0
55

66
from .layout import Flatten, HStack, Layout
7-
from .primitive import BoundingBox, Overlay
7+
from .primitive import BoundingBox, Overlay, Polygon
88
from .scene import Scene
99
from .visualizer import Visualizer
1010

11-
__all__ = ["BoundingBox", "Overlay", "Scene", "Visualizer", "Layout", "Flatten", "HStack"]
11+
__all__ = ["BoundingBox", "Overlay", "Polygon", "Scene", "Visualizer", "Layout", "Flatten", "HStack"]

model_api/python/model_api/visualizer/primitive/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
from .bounding_box import BoundingBox
77
from .overlay import Overlay
8+
from .polygon import Polygon
89
from .primitive import Primitive
910

10-
__all__ = ["Primitive", "BoundingBox", "Overlay"]
11+
__all__ = ["Primitive", "BoundingBox", "Overlay", "Polygon"]
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
"""Polygon primitive."""
2+
3+
# Copyright (C) 2025 Intel Corporation
4+
# SPDX-License-Identifier: Apache-2.0
5+
6+
from __future__ import annotations
7+
8+
from typing import TYPE_CHECKING
9+
10+
import cv2
11+
from PIL import Image, ImageDraw
12+
13+
from .primitive import Primitive
14+
15+
if TYPE_CHECKING:
16+
import numpy as np
17+
18+
19+
class Polygon(Primitive):
20+
"""Polygon primitive."""
21+
22+
def __init__(
23+
self,
24+
points: list[tuple[int, int]] | None = None,
25+
mask: np.ndarray | None = None,
26+
color: str | tuple[int, int, int] = "blue",
27+
) -> None:
28+
self.points = self._get_points(points, mask)
29+
self.color = color
30+
31+
def _get_points(self, points: list[tuple[int, int]] | None, mask: np.ndarray | None) -> list[tuple[int, int]]:
32+
if points is not None and mask is not None:
33+
msg = "Either points or mask should be provided, not both."
34+
raise ValueError(msg)
35+
if points is not None:
36+
points_ = points
37+
elif mask is not None:
38+
points_ = self._get_points_from_mask(mask)
39+
else:
40+
msg = "Either points or mask should be provided."
41+
raise ValueError(msg)
42+
return points_
43+
44+
def _get_points_from_mask(self, mask: np.ndarray) -> list[tuple[int, int]]:
45+
contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
46+
points_ = contours[0].squeeze().tolist()
47+
return [tuple(point) for point in points_]
48+
49+
def compute(self, image: Image) -> Image:
50+
draw = ImageDraw.Draw(image)
51+
draw.polygon(self.points, fill=self.color)
52+
return image

tests/python/unit/visualizer/test_primitive.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import PIL
88
from PIL import ImageDraw
99

10-
from model_api.visualizer import BoundingBox, Overlay
10+
from model_api.visualizer import BoundingBox, Overlay, Polygon
1111

1212

1313
def test_overlay(mock_image: PIL.Image):
@@ -32,3 +32,22 @@ def test_bounding_box(mock_image: PIL.Image):
3232
draw.rectangle((10, 10, 100, 100), outline="blue", width=2)
3333
bounding_box = BoundingBox(x1=10, y1=10, x2=100, y2=100)
3434
assert bounding_box.compute(mock_image) == expected_image
35+
36+
37+
def test_polygon(mock_image: PIL.Image):
38+
"""Test if the polygon is created correctly."""
39+
# Test from points
40+
expected_image = mock_image.copy()
41+
draw = ImageDraw.Draw(expected_image)
42+
draw.polygon([(10, 10), (100, 10), (100, 100), (10, 100)], fill="red")
43+
polygon = Polygon(points=[(10, 10), (100, 10), (100, 100), (10, 100)], color="red")
44+
assert polygon.compute(mock_image) == expected_image
45+
46+
# Test from mask
47+
mask = np.zeros((100, 100), dtype=np.uint8)
48+
mask[10:100, 10:100] = 255
49+
expected_image = mock_image.copy()
50+
draw = ImageDraw.Draw(expected_image)
51+
draw.polygon([(10, 10), (100, 10), (100, 100), (10, 100)], fill="red")
52+
polygon = Polygon(mask=mask, color="red")
53+
assert polygon.compute(mock_image) == expected_image

0 commit comments

Comments
 (0)