Skip to content

Commit 5296361

Browse files
committed
refactor: optimize color validation and label drawing with caching and vectorized operations
1 parent a5c396d commit 5296361

File tree

3 files changed

+60
-16
lines changed

3 files changed

+60
-16
lines changed

bbox_visualizer/core/_utils.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from contextlib import contextmanager
44
from typing import Generator
5+
from functools import lru_cache
56

67
# Global flag to track warning suppression state
78
_warnings_suppressed: bool = False
@@ -63,18 +64,20 @@ def _validate_bbox(bbox: list[int]) -> None:
6364
)
6465

6566

67+
@lru_cache(maxsize=128)
6668
def _validate_color(color: tuple[int, int, int]) -> None:
67-
"""Validate BGR color values.
69+
"""Validate that a color tuple is valid BGR format.
6870
6971
Args:
70-
color: BGR color tuple (blue, green, red)
72+
color: BGR color tuple to validate
7173
7274
Raises:
73-
ValueError: If any color component is outside [0, 255]
74-
75+
ValueError: If color is not a valid BGR tuple
7576
"""
76-
if not all(0 <= c <= 255 for c in color):
77-
raise ValueError("Color values must be between 0 and 255")
77+
if not isinstance(color, tuple) or len(color) != 3:
78+
raise ValueError("Color must be a tuple of 3 integers (BGR)")
79+
if not all(isinstance(c, int) and 0 <= c <= 255 for c in color):
80+
raise ValueError("Color values must be integers between 0 and 255")
7881

7982

8083
def _check_and_modify_bbox(
@@ -101,4 +104,4 @@ def _check_and_modify_bbox(
101104
bbox = [value if value > 0 else margin for value in bbox]
102105
bbox[2] = bbox[2] if bbox[2] < img_size[1] else img_size[1] - margin
103106
bbox[3] = bbox[3] if bbox[3] < img_size[0] else img_size[0] - margin
104-
return bbox
107+
return bbox

bbox_visualizer/core/labels.py

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,18 @@
33
import cv2
44
import numpy as np
55
from numpy.typing import NDArray
6+
from functools import lru_cache
67

78
from ._utils import _check_and_modify_bbox, _validate_color
89

910
font = cv2.FONT_HERSHEY_SIMPLEX
1011

12+
# Cache text size calculations
13+
@lru_cache(maxsize=128)
14+
def _get_text_size(label: str, size: float, thickness: int) -> tuple[tuple[int, int], int]:
15+
"""Get text size with caching for better performance."""
16+
return cv2.getTextSize(label, font, size, thickness)
17+
1118

1219
def add_label(
1320
img: NDArray[np.uint8],
@@ -44,7 +51,8 @@ def add_label(
4451
_validate_color(text_color)
4552
bbox = _check_and_modify_bbox(bbox, img.shape)
4653

47-
(text_width, text_height), baseline = cv2.getTextSize(label, font, size, thickness)
54+
# Use cached text size calculation
55+
(text_width, text_height), baseline = _get_text_size(label, size, thickness)
4856
padding = 5 # Padding around text
4957

5058
if top and bbox[1] - text_height > text_height:
@@ -54,7 +62,7 @@ def add_label(
5462

5563
# Calculate background rectangle position
5664
bg_x1 = bbox[0]
57-
bg_y1 = bbox[1] - bg_height # Removed the gap by removing (5 * size)
65+
bg_y1 = bbox[1] - bg_height
5866
bg_x2 = bg_x1 + bg_width
5967
bg_y2 = bg_y1 + bg_height
6068

@@ -127,7 +135,7 @@ def add_multiple_labels(
127135
text_color: tuple[int, int, int] = (0, 0, 0),
128136
top: bool = True,
129137
) -> NDArray[np.uint8]:
130-
"""Add multiple labels to their corresponding bounding boxes.
138+
"""Add multiple labels to their corresponding bounding boxes using optimized operations.
131139
132140
Args:
133141
img: Input image array
@@ -151,8 +159,25 @@ def add_multiple_labels(
151159

152160
_validate_color(text_bg_color)
153161
_validate_color(text_color)
162+
163+
# Convert bboxes to numpy array for vectorized operations
164+
bboxes = np.array(bboxes)
165+
166+
# Validate and modify all bboxes at once
167+
bboxes = np.array([_check_and_modify_bbox(bbox, img.shape) for bbox in bboxes])
168+
169+
# Draw all labels using add_label
170+
output = img.copy()
154171
for label, bbox in zip(labels, bboxes):
155-
img = add_label(
156-
img, label, bbox, size, thickness, draw_bg, text_bg_color, text_color, top
172+
output = add_label(
173+
output,
174+
label,
175+
bbox.tolist(),
176+
size,
177+
thickness,
178+
draw_bg,
179+
text_bg_color,
180+
text_color,
181+
top
157182
)
158-
return img
183+
return output

bbox_visualizer/core/rectangle.py

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ def draw_multiple_rectangles(
5353
is_opaque: bool = False,
5454
alpha: float = 0.5,
5555
) -> NDArray[np.uint8]:
56-
"""Draws multiple rectangles on the image.
56+
"""Draws multiple rectangles on the image using optimized vectorized operations.
5757
5858
Args:
5959
img: Input image array
@@ -70,6 +70,22 @@ def draw_multiple_rectangles(
7070
if not bboxes:
7171
raise ValueError("List of bounding boxes cannot be empty")
7272
_validate_color(bbox_color)
73+
74+
# Convert bboxes to numpy array for vectorized operations
75+
bboxes = np.array(bboxes)
76+
77+
# Validate and modify all bboxes at once
78+
bboxes = np.array([_check_and_modify_bbox(bbox, img.shape) for bbox in bboxes])
79+
80+
# Draw all rectangles using draw_rectangle
81+
output = img.copy()
7382
for bbox in bboxes:
74-
img = draw_rectangle(img, bbox, bbox_color, thickness, is_opaque, alpha)
75-
return img
83+
output = draw_rectangle(
84+
output,
85+
bbox.tolist(),
86+
bbox_color,
87+
thickness,
88+
is_opaque,
89+
alpha
90+
)
91+
return output

0 commit comments

Comments
 (0)