Skip to content

Commit f41adca

Browse files
authored
Merge pull request #1173 from roboflow/vertex_label_annotator
`VertexLabelAnnotator`
2 parents acfabc2 + d64bb38 commit f41adca

File tree

7 files changed

+399
-9
lines changed

7 files changed

+399
-9
lines changed

docs/detection/utils.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,3 +70,15 @@ status: new
7070
</div>
7171

7272
:::supervision.detection.utils.scale_boxes
73+
74+
<div class="md-typeset">
75+
<h2><a href="#supervision.detection.utils.clip_boxes">clip_boxes</a></h2>
76+
</div>
77+
78+
:::supervision.detection.utils.clip_boxes
79+
80+
<div class="md-typeset">
81+
<h2><a href="#supervision.detection.utils.pad_boxes">pad_boxes</a></h2>
82+
</div>
83+
84+
:::supervision.detection.utils.pad_boxes

docs/keypoint/annotators.md

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@ status: new
1313
image = ...
1414
key_points = sv.KeyPoints(...)
1515

16-
vertex_annotator = sv.VertexAnnotator(color=sv.Color.GREEN, radius=10)
16+
vertex_annotator = sv.VertexAnnotator(
17+
color=sv.Color.GREEN,
18+
radius=10
19+
)
1720
annotated_frame = vertex_annotator.annotate(
1821
scene=image.copy(),
1922
key_points=key_points
@@ -34,7 +37,10 @@ status: new
3437
image = ...
3538
key_points = sv.KeyPoints(...)
3639

37-
edge_annotator = sv.EdgeAnnotator(color=sv.Color.GREEN, thickness=5)
40+
edge_annotator = sv.EdgeAnnotator(
41+
color=sv.Color.GREEN,
42+
thickness=5
43+
)
3844
annotated_frame = edge_annotator.annotate(
3945
scene=image.copy(),
4046
key_points=key_points
@@ -47,6 +53,31 @@ status: new
4753

4854
</div>
4955

56+
=== "VertexLabelAnnotator"
57+
58+
```python
59+
import supervision as sv
60+
61+
image = ...
62+
key_points = sv.KeyPoints(...)
63+
64+
vertex_label_annotator = sv.VertexLabelAnnotator(
65+
color=sv.Color.GREEN,
66+
text_color=sv.Color.BLACK,
67+
border_radius=5
68+
)
69+
annotated_frame = vertex_label_annotator.annotate(
70+
scene=image.copy(),
71+
key_points=key_points
72+
)
73+
```
74+
75+
<div class="result" markdown>
76+
77+
![vertex-label-annotator-example](https://media.roboflow.com/supervision-annotator-examples/vertex-label-annotator-example.png){ align=center width="800" }
78+
79+
</div>
80+
5081
<div class="md-typeset">
5182
<h2><a href="#supervision.keypoint.annotators.VertexAnnotator">VertexAnnotator</a></h2>
5283
</div>
@@ -58,3 +89,9 @@ status: new
5889
</div>
5990

6091
:::supervision.keypoint.annotators.EdgeAnnotator
92+
93+
<div class="md-typeset">
94+
<h2><a href="#supervision.keypoint.annotators.VertexLabelAnnotator">VertexLabelAnnotator</a></h2>
95+
</div>
96+
97+
:::supervision.keypoint.annotators.VertexLabelAnnotator

supervision/__init__.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,14 @@
4646
box_iou_batch,
4747
box_non_max_suppression,
4848
calculate_masks_centroids,
49+
clip_boxes,
4950
filter_polygons_by_area,
5051
mask_iou_batch,
5152
mask_non_max_suppression,
5253
mask_to_polygons,
5354
mask_to_xyxy,
5455
move_boxes,
56+
pad_boxes,
5557
polygon_to_mask,
5658
polygon_to_xyxy,
5759
scale_boxes,
@@ -69,7 +71,11 @@
6971
)
7072
from supervision.geometry.core import Point, Position, Rect
7173
from supervision.geometry.utils import get_polygon_center
72-
from supervision.keypoint.annotators import EdgeAnnotator, VertexAnnotator
74+
from supervision.keypoint.annotators import (
75+
EdgeAnnotator,
76+
VertexAnnotator,
77+
VertexLabelAnnotator,
78+
)
7379
from supervision.keypoint.core import KeyPoints
7480
from supervision.metrics.detection import ConfusionMatrix, MeanAveragePrecision
7581
from supervision.tracker.byte_tracker.core import ByteTrack

supervision/detection/utils.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,35 @@ def clip_boxes(xyxy: np.ndarray, resolution_wh: Tuple[int, int]) -> np.ndarray:
297297
return result
298298

299299

300+
def pad_boxes(xyxy: np.ndarray, px: int, py: Optional[int] = None) -> np.ndarray:
301+
"""
302+
Pads bounding boxes coordinates with a constant padding.
303+
304+
Args:
305+
xyxy (np.ndarray): A numpy array of shape `(N, 4)` where each
306+
row corresponds to a bounding box in the format
307+
`(x_min, y_min, x_max, y_max)`.
308+
px (int): The padding value to be added to both the left and right sides of
309+
each bounding box.
310+
py (Optional[int]): The padding value to be added to both the top and bottom
311+
sides of each bounding box. If not provided, `px` will be used for both
312+
dimensions.
313+
314+
Returns:
315+
np.ndarray: A numpy array of shape `(N, 4)` where each row corresponds to a
316+
bounding box with coordinates padded according to the provided padding
317+
values.
318+
"""
319+
if py is None:
320+
py = px
321+
322+
result = xyxy.copy()
323+
result[:, [0, 1]] -= [px, py]
324+
result[:, [2, 3]] += [px, py]
325+
326+
return result
327+
328+
300329
def xywh_to_xyxy(boxes_xywh: np.ndarray) -> np.ndarray:
301330
xyxy = boxes_xywh.copy()
302331
xyxy[:, 2] = boxes_xywh[:, 0] + boxes_xywh[:, 2]

supervision/draw/utils.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,58 @@ def draw_filled_rectangle(scene: np.ndarray, rect: Rect, color: Color) -> np.nda
8181
return scene
8282

8383

84+
def draw_rounded_rectangle(
85+
scene: np.ndarray,
86+
rect: Rect,
87+
color: Color,
88+
border_radius: int,
89+
) -> np.ndarray:
90+
"""
91+
Draws a rounded rectangle on an image.
92+
93+
Parameters:
94+
scene (np.ndarray): The image on which the rounded rectangle will be drawn.
95+
rect (Rect): The rectangle to be drawn.
96+
color (Color): The color of the rounded rectangle.
97+
border_radius (int): The radius of the corner rounding.
98+
99+
Returns:
100+
np.ndarray: The image with the rounded rectangle drawn on it.
101+
"""
102+
x1, y1, x2, y2 = rect.as_xyxy_int_tuple()
103+
width, height = x2 - x1, y2 - y1
104+
border_radius = min(border_radius, min(width, height) // 2)
105+
106+
rectangle_coordinates = [
107+
((x1 + border_radius, y1), (x2 - border_radius, y2)),
108+
((x1, y1 + border_radius), (x2, y2 - border_radius)),
109+
]
110+
circle_centers = [
111+
(x1 + border_radius, y1 + border_radius),
112+
(x2 - border_radius, y1 + border_radius),
113+
(x1 + border_radius, y2 - border_radius),
114+
(x2 - border_radius, y2 - border_radius),
115+
]
116+
117+
for coordinates in rectangle_coordinates:
118+
cv2.rectangle(
119+
img=scene,
120+
pt1=coordinates[0],
121+
pt2=coordinates[1],
122+
color=color.as_bgr(),
123+
thickness=-1,
124+
)
125+
for center in circle_centers:
126+
cv2.circle(
127+
img=scene,
128+
center=center,
129+
radius=border_radius,
130+
color=color.as_bgr(),
131+
thickness=-1,
132+
)
133+
return scene
134+
135+
84136
def draw_polygon(
85137
scene: np.ndarray, polygon: np.ndarray, color: Color, thickness: int = 2
86138
) -> np.ndarray:

supervision/geometry/core.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,11 @@ class Rect:
9898
width: float
9999
height: float
100100

101+
@classmethod
102+
def from_xyxy(cls, xyxy: Tuple[float, float, float, float]) -> Rect:
103+
x1, y1, x2, y2 = xyxy
104+
return cls(x=x1, y=y1, width=x2 - x1, height=y2 - y1)
105+
101106
@property
102107
def top_left(self) -> Point:
103108
return Point(x=self.x, y=self.y)
@@ -113,3 +118,11 @@ def pad(self, padding) -> Rect:
113118
width=self.width + 2 * padding,
114119
height=self.height + 2 * padding,
115120
)
121+
122+
def as_xyxy_int_tuple(self) -> Tuple[int, int, int, int]:
123+
return (
124+
int(self.x),
125+
int(self.y),
126+
int(self.x + self.width),
127+
int(self.y + self.height),
128+
)

0 commit comments

Comments
 (0)