Skip to content

Commit

Permalink
Added smart positioning (non overlapping labels) to VertexLabelAnnotator
Browse files Browse the repository at this point in the history
  • Loading branch information
kshitijaucharmal committed Oct 28, 2024
1 parent 0e7f3cd commit ba559c2
Show file tree
Hide file tree
Showing 2 changed files with 94 additions and 1 deletion.
83 changes: 83 additions & 0 deletions supervision/detection/utils.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import math
from itertools import chain
from typing import Dict, List, Optional, Tuple, Union

Expand Down Expand Up @@ -1039,3 +1040,85 @@ def cross_product(anchors: np.ndarray, vector: Vector) -> np.ndarray:
)
vector_start = np.array([vector.start.x, vector.start.y])
return np.cross(vector_at_zero, anchors - vector_start)


# Intelligent padding functions
def get_intersection_center(
xyxy_1: np.ndarray, xyxy_2: np.ndarray
) -> Optional[Tuple[float, float]]:
overlap_xmin = max(xyxy_1[0], xyxy_2[0])
overlap_ymin = max(xyxy_1[1], xyxy_2[1])
overlap_xmax = min(xyxy_1[2], xyxy_2[2])
overlap_ymax = min(xyxy_1[3], xyxy_2[3])

if overlap_xmin < overlap_xmax and overlap_ymin < overlap_ymax:
x_center = (overlap_xmin + overlap_xmax) / 2
y_center = (overlap_ymin + overlap_ymax) / 2
return (x_center, y_center)
else:
return None


def get_box_center(xyxy: np.ndarray) -> Tuple[float, float]:
x_center = (xyxy[0] + xyxy[2]) / 2
y_center = (xyxy[1] + xyxy[3]) / 2
return (x_center, y_center)


def vector_with_length(
xy_1: Tuple[float, float], xy_2: Tuple[float, float], n: float
) -> Tuple[float, float]:
x1, y1 = xy_1
x2, y2 = xy_2

dx = x2 - x1
dy = y2 - y1

if dx == 0 and dy == 0:
return 0, 0

magnitude = math.sqrt(dx**2 + dy**2)

unit_dx = dx / magnitude
unit_dy = dy / magnitude

v1 = unit_dx * n
v2 = unit_dy * n

return (v1, v2)


def pad(xyxy: np.ndarray, px: int, py: Optional[int] = None):
if py is None:
py = px

result = xyxy.copy()
result[:, [0, 1]] -= [px, py]
result[:, [2, 3]] += [px, py]

return result


def spread_out(xyxy: np.ndarray, step) -> np.ndarray:
xyxy_padded = pad(xyxy, px=step)
while True:
iou = box_iou_batch(xyxy_padded, xyxy_padded)
np.fill_diagonal(iou, 0)

if np.all(iou == 0):
return pad(xyxy_padded, px=-step)

i, j = np.unravel_index(np.argmax(iou), iou.shape)

xyxy_i, xyxy_j = xyxy_padded[i], xyxy_padded[j]
intersection_center = get_intersection_center(xyxy_i, xyxy_j)
xyxy_i_center = get_box_center(xyxy_i)
xyxy_j_center = get_box_center(xyxy_j)

vector_i = vector_with_length(intersection_center, xyxy_i_center, step)
vector_j = vector_with_length(intersection_center, xyxy_j_center, step)

xyxy_padded[i, [0, 2]] += int(vector_i[0])
xyxy_padded[i, [1, 3]] += int(vector_i[1])
xyxy_padded[j, [0, 2]] += int(vector_j[0])
xyxy_padded[j, [1, 3]] += int(vector_j[1])
12 changes: 11 additions & 1 deletion supervision/keypoint/annotators.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from supervision import Rect, pad_boxes
from supervision.annotators.base import ImageType
from supervision.detection.utils import pad, spread_out
from supervision.draw.color import Color
from supervision.draw.utils import draw_rounded_rectangle
from supervision.keypoint.core import KeyPoints
Expand Down Expand Up @@ -201,6 +202,7 @@ def __init__(
text_thickness: int = 1,
text_padding: int = 10,
border_radius: int = 0,
use_smart_positioning: bool = False,
):
"""
Args:
Expand All @@ -215,7 +217,10 @@ def __init__(
text_padding (int): The padding around the text.
border_radius (int): The radius of the rounded corners of the
boxes. Set to a high value to produce circles.
use_smart_positioning (bool): Whether to use smart positioning to prevent
label overlapping or not.
"""
self.use_smart_positioning = use_smart_positioning
self.border_radius: int = border_radius
self.color: Union[Color, List[Color]] = color
self.text_color: Union[Color, List[Color]] = text_color
Expand Down Expand Up @@ -357,7 +362,12 @@ def annotate(
]
)

xyxy_padded = pad_boxes(xyxy=xyxy, px=self.text_padding)
if self.use_smart_positioning:
xyxy_padded = pad(xyxy=xyxy, px=self.text_padding)
xyxy_padded = spread_out(xyxy_padded, step=2)
xyxy = pad(xyxy=xyxy_padded, px=-self.text_padding)
else:
xyxy_padded = pad_boxes(xyxy=xyxy, px=self.text_padding)

for text, color, text_color, box, box_padded in zip(
labels, colors, text_colors, xyxy, xyxy_padded
Expand Down

0 comments on commit ba559c2

Please sign in to comment.