Skip to content

Commit 10fec1e

Browse files
committed
Fix test coverage, fix typos
1 parent 5ef882c commit 10fec1e

File tree

7 files changed

+272
-75
lines changed

7 files changed

+272
-75
lines changed

bbox_visualizer/bbox_visualizer.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ def add_label(
133133

134134
cv2.putText(img, label, (bbox[0] + 5, bbox[1] - (15 * size)), font, size, text_color, thickness)
135135
else:
136-
label_bg = [bbox[0], bbox[1], bbox[0] + label_width, bbox[1] + label_height + (15 * size)]
136+
label_bg = [bbox[0], bbox[1], bbox[0] + text_width, bbox[1] + text_height + (15 * size)]
137137
if draw_bg:
138138

139139
cv2.rectangle(img, (label_bg[0], label_bg[1]),
@@ -184,7 +184,7 @@ def add_T_label(img,
184184

185185
# draw rectangle with label
186186
y_bottom = y_top
187-
y_top = y_bottom - text_height - 5
187+
y_top = y_bottom - label_height - 5
188188

189189
if y_top < 0:
190190
logging.warning(
@@ -193,14 +193,14 @@ def add_T_label(img,
193193
return add_label(img, label, bbox)
194194

195195
cv2.line(img, (x_center, bbox[1]), (x_center, line_top), text_bg_color, 3)
196-
x_left = x_center - (text_width // 2) - 5
197-
x_right = x_center + (text_width // 2) + 5
196+
x_left = x_center - (label_width // 2) - 5
197+
x_right = x_center + (label_width // 2) + 5
198198
if draw_bg:
199199
cv2.rectangle(img, (x_left, y_top - 30), (x_right, y_bottom),
200200
text_bg_color, -1)
201201
cv2.putText(img, label, (x_left + 5, y_bottom - (8 * size)),
202202
font, size, text_color, thickness)
203-
)
203+
204204

205205

206206
return img
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
"""
2+
Example script demonstrating bbox-visualizer usage with multiple objects.
3+
This script shows how to:
4+
1. Load an image and its annotations
5+
2. Draw multiple bounding boxes
6+
3. Add different types of labels (normal, T-label, and flags) for multiple objects
7+
"""
8+
9+
import json
10+
import bbox_visualizer as bbv
11+
import cv2
12+
13+
def main():
14+
# Load image and annotation
15+
img = cv2.imread('../images/test_images/source_multiple_cats.jpg')
16+
if img is None:
17+
print("Error: Could not load image. Please check the path.")
18+
return
19+
20+
try:
21+
annotation = json.load(open('../images/test_images/source_multiple_cats.json'))
22+
except FileNotFoundError:
23+
print("Error: Could not load annotation file. Please check the path.")
24+
return
25+
26+
# Extract labels and bounding boxes from annotation
27+
labels = []
28+
bboxes = []
29+
for shape in annotation['shapes']:
30+
labels.append(shape['label'])
31+
mins = shape['points'][0] # [xmin, ymin]
32+
maxs = shape['points'][1] # [xmax, ymax]
33+
bboxes.append(mins + maxs) # [xmin, ymin, xmax, ymax]
34+
35+
# Draw different visualizations
36+
img_with_boxes = bbv.draw_multiple_rectangles(img, bboxes)
37+
img_with_boxes_2 = img_with_boxes.copy()
38+
39+
# Add different types of labels
40+
img_with_labels = bbv.add_multiple_labels(img_with_boxes, labels, bboxes)
41+
img_with_T_labels = bbv.add_multiple_T_labels(img_with_boxes_2, labels, bboxes)
42+
img_with_flags = bbv.draw_multiple_flags_with_labels(img, labels, bboxes)
43+
44+
# Display results
45+
cv2.imshow("Boxes with Labels", img_with_labels)
46+
cv2.imshow("Boxes with T-labels", img_with_T_labels)
47+
cv2.imshow("Boxes with Flags", img_with_flags)
48+
49+
print("Press any key to close the windows...")
50+
cv2.waitKey(0)
51+
cv2.destroyAllWindows()
52+
53+
if __name__ == "__main__":
54+
main()
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
"""
2+
Example script demonstrating bbox-visualizer usage with a single object.
3+
This script shows how to:
4+
1. Load an image and its annotation
5+
2. Draw a bounding box
6+
3. Add different types of labels (normal, T-label, and flag)
7+
"""
8+
9+
import json
10+
import bbox_visualizer as bbv
11+
import cv2
12+
13+
def main():
14+
# Load image and annotation
15+
img = cv2.imread('../images/test_images/source_bird.jpg')
16+
if img is None:
17+
print("Error: Could not load image. Please check the path.")
18+
return
19+
20+
try:
21+
annotation = json.load(open('../images/test_images/source_bird.json'))
22+
except FileNotFoundError:
23+
print("Error: Could not load annotation file. Please check the path.")
24+
return
25+
26+
# Convert to RGB for better visualization
27+
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
28+
29+
# Extract bounding box and label from annotation
30+
points = annotation['shapes'][0]['points']
31+
label = annotation['shapes'][0]['label']
32+
(xmin, ymin), (xmax, ymax) = points
33+
bbox = [xmin, ymin, xmax, ymax]
34+
35+
# Draw different visualizations
36+
img_with_box = bbv.draw_rectangle(img, bbox)
37+
img_with_box_2 = img_with_box.copy()
38+
39+
img_label = bbv.add_label(img_with_box, label, bbox)
40+
img_T_label = bbv.add_T_label(img_with_box_2, label, bbox)
41+
img_flag = bbv.draw_flag_with_label(img, label, bbox)
42+
43+
# Display results
44+
cv2.imshow("Bounding Box", img_with_box)
45+
cv2.imshow("With T-label", img_T_label)
46+
cv2.imshow("With Flag", img_flag)
47+
48+
print("Press any key to close the windows...")
49+
cv2.waitKey(0)
50+
cv2.destroyAllWindows()
51+
52+
if __name__ == "__main__":
53+
main()

requirements_dev.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,6 @@ flake8==3.7.8
55
Sphinx==1.8.5
66
twine==1.14.0
77
pytest==7.4.0
8+
pytest-cov>=7.0.0
89
numpy>=1.19.0
910
opencv-python>=4.1.0.25

tests/test_bbox_visualizer.py

Lines changed: 159 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import pytest
22
import numpy as np
3-
import cv2
3+
import logging
44
from bbox_visualizer import bbox_visualizer
55

66
@pytest.fixture
@@ -18,6 +18,28 @@ def sample_label():
1818
"""Create a sample label for testing."""
1919
return "test"
2020

21+
def test_check_and_modify_bbox(sample_image):
22+
"""Test bbox validation and modification."""
23+
# Test negative coordinates
24+
bbox = [-10, -10, 50, 50]
25+
result = bbox_visualizer.check_and_modify_bbox(bbox, sample_image.shape)
26+
assert result[0] >= 0 and result[1] >= 0
27+
28+
# Test coordinates exceeding image size
29+
bbox = [10, 10, 200, 200]
30+
result = bbox_visualizer.check_and_modify_bbox(bbox, sample_image.shape)
31+
assert result[2] <= sample_image.shape[1]
32+
assert result[3] <= sample_image.shape[0]
33+
34+
# Test with margin
35+
bbox = [-10, -10, 200, 200]
36+
margin = 5
37+
result = bbox_visualizer.check_and_modify_bbox(bbox, sample_image.shape, margin)
38+
assert result[0] == margin
39+
assert result[1] == margin
40+
assert result[2] == sample_image.shape[1] - margin
41+
assert result[3] == sample_image.shape[0] - margin
42+
2143
def test_draw_rectangle_basic(sample_image, sample_bbox):
2244
"""Test basic rectangle drawing functionality."""
2345
result = bbox_visualizer.draw_rectangle(sample_image, sample_bbox)
@@ -35,32 +57,155 @@ def test_draw_rectangle_opaque(sample_image, sample_bbox):
3557
assert result.shape == sample_image.shape
3658
assert np.sum(result) > 0
3759

38-
def test_add_label(sample_image, sample_bbox, sample_label):
60+
def test_add_label(sample_image, sample_bbox, sample_label, caplog):
3961
"""Test adding label to bounding box."""
62+
# Test normal case with top=True and enough space
63+
bbox_with_space = [10, 30, 50, 70] # Plenty of space above and below
4064
result = bbox_visualizer.add_label(
41-
sample_image, sample_label, sample_bbox
65+
sample_image, sample_label, bbox_with_space, top=True
66+
)
67+
assert isinstance(result, np.ndarray)
68+
assert result.shape == sample_image.shape
69+
assert np.sum(result) > 0
70+
71+
# Test normal case with top=False and enough space
72+
result = bbox_visualizer.add_label(
73+
sample_image, sample_label, bbox_with_space, top=False
4274
)
4375
assert isinstance(result, np.ndarray)
4476
assert result.shape == sample_image.shape
4577
assert np.sum(result) > 0
78+
79+
# Test with draw_bg=False for both top and bottom positions
80+
result = bbox_visualizer.add_label(
81+
sample_image, sample_label, bbox_with_space, draw_bg=False, top=True
82+
)
83+
assert isinstance(result, np.ndarray)
84+
assert result.shape == sample_image.shape
85+
assert np.sum(result) > 0
86+
87+
result = bbox_visualizer.add_label(
88+
sample_image, sample_label, bbox_with_space, draw_bg=False, top=False
89+
)
90+
assert isinstance(result, np.ndarray)
91+
assert result.shape == sample_image.shape
92+
assert np.sum(result) > 0
93+
94+
# Test label at top when there's not enough space
95+
bbox_at_top = [10, 5, 50, 20] # Very close to top of image
96+
result = bbox_visualizer.add_label(
97+
sample_image, sample_label, bbox_at_top, top=True
98+
)
99+
assert isinstance(result, np.ndarray)
100+
assert result.shape == sample_image.shape
101+
102+
# Test label at bottom when there's not enough space
103+
bbox_at_bottom = [10, 80, 50, 99] # Very close to bottom of image
104+
result = bbox_visualizer.add_label(
105+
sample_image, sample_label, bbox_at_bottom, top=False
106+
)
107+
assert isinstance(result, np.ndarray)
108+
assert result.shape == sample_image.shape
46109

47-
def test_add_T_label(sample_image, sample_bbox, sample_label):
110+
def test_add_T_label(sample_image, sample_bbox, sample_label, caplog):
48111
"""Test adding T-label to bounding box."""
112+
# Test normal case with enough space and background
113+
bbox_with_space = [40, 80, 60, 95] # Plenty of space above (at least 50 pixels)
49114
result = bbox_visualizer.add_T_label(
50-
sample_image, sample_label, sample_bbox
115+
sample_image, sample_label, bbox_with_space,
116+
draw_bg=True, text_bg_color=(255, 0, 0), # Red background to ensure visibility
117+
size=1, thickness=2 # Explicit size and thickness
51118
)
52119
assert isinstance(result, np.ndarray)
53120
assert result.shape == sample_image.shape
54121
assert np.sum(result) > 0
122+
123+
# Test with draw_bg=False
124+
result = bbox_visualizer.add_T_label(
125+
sample_image, sample_label, bbox_with_space,
126+
draw_bg=False, text_color=(0, 255, 0), # Green text to ensure visibility
127+
size=1, thickness=2 # Explicit size and thickness
128+
)
129+
assert isinstance(result, np.ndarray)
130+
assert result.shape == sample_image.shape
131+
assert np.sum(result) > 0
132+
133+
# Test with custom size and thickness
134+
result = bbox_visualizer.add_T_label(
135+
sample_image, sample_label, bbox_with_space,
136+
size=2, thickness=3, text_bg_color=(0, 0, 255), # Blue background
137+
draw_bg=True # Explicit background drawing
138+
)
139+
assert isinstance(result, np.ndarray)
140+
assert result.shape == sample_image.shape
141+
assert np.sum(result) > 0
142+
143+
# Test with a long label to ensure text width calculation
144+
long_label = "This is a very long label"
145+
result = bbox_visualizer.add_T_label(
146+
sample_image, long_label, bbox_with_space,
147+
draw_bg=True, text_bg_color=(255, 255, 0), # Yellow background
148+
size=1, thickness=2 # Explicit size and thickness
149+
)
150+
assert isinstance(result, np.ndarray)
151+
assert result.shape == sample_image.shape
152+
assert np.sum(result) > 0
153+
154+
# Test with a bbox that has enough vertical space but is close to image edges
155+
bbox_near_edge = [5, 80, 25, 95] # Close to left edge but plenty of space above
156+
result = bbox_visualizer.add_T_label(
157+
sample_image, sample_label, bbox_near_edge,
158+
draw_bg=True, text_bg_color=(255, 0, 255), # Magenta background
159+
size=1, thickness=2 # Explicit size and thickness
160+
)
161+
assert isinstance(result, np.ndarray)
162+
assert result.shape == sample_image.shape
163+
assert np.sum(result) > 0
164+
165+
# Test fallback when label would go out of frame at top
166+
bbox_at_top = [10, 5, 50, 20] # Very close to top of image
167+
caplog.clear()
168+
with caplog.at_level(logging.WARNING):
169+
result = bbox_visualizer.add_T_label(
170+
sample_image, sample_label, bbox_at_top,
171+
draw_bg=True, text_bg_color=(128, 128, 128) # Gray background
172+
)
173+
assert "Labelling style 'T' going out of frame. Falling back to normal labeling." in caplog.text
174+
assert isinstance(result, np.ndarray)
175+
assert result.shape == sample_image.shape
176+
177+
# Test fallback when label would go out of frame on the right side
178+
bbox_at_right = [80, 30, 99, 50] # Very close to right edge of image
179+
caplog.clear()
180+
with caplog.at_level(logging.WARNING):
181+
result = bbox_visualizer.add_T_label(
182+
sample_image, sample_label, bbox_at_right,
183+
draw_bg=True, text_bg_color=(128, 128, 128) # Gray background
184+
)
185+
assert "Labelling style 'T' going out of frame. Falling back to normal labeling." in caplog.text
186+
assert isinstance(result, np.ndarray)
187+
assert result.shape == sample_image.shape
55188

56-
def test_draw_flag_with_label(sample_image, sample_bbox, sample_label):
189+
def test_draw_flag_with_label(sample_image, sample_bbox, sample_label, caplog):
57190
"""Test drawing flag with label."""
191+
# Test normal case
58192
result = bbox_visualizer.draw_flag_with_label(
59193
sample_image, sample_label, sample_bbox
60194
)
61195
assert isinstance(result, np.ndarray)
62196
assert result.shape == sample_image.shape
63197
assert np.sum(result) > 0
198+
199+
# Test fallback when flag would go out of frame
200+
bbox_at_top = [10, 0, 50, 10] # At the very top of image
201+
caplog.clear() # Clear any previous log messages
202+
with caplog.at_level(logging.WARNING):
203+
result = bbox_visualizer.draw_flag_with_label(
204+
sample_image, sample_label, bbox_at_top
205+
)
206+
assert "Labelling style 'Flag' going out of frame. Falling back to normal labeling." in caplog.text
207+
assert isinstance(result, np.ndarray)
208+
assert result.shape == sample_image.shape
64209

65210
def test_draw_multiple_rectangles(sample_image):
66211
"""Test drawing multiple rectangles."""
@@ -87,7 +232,6 @@ def test_add_multiple_T_labels(sample_image):
87232
"""Test adding multiple T-labels."""
88233
bboxes = [[10, 10, 30, 30], [40, 40, 60, 60]]
89234
labels = ["obj1", "obj2"]
90-
# Note: The function internally sets size=1 and thickness=2
91235
result = bbox_visualizer.add_multiple_T_labels(
92236
sample_image, labels, bboxes
93237
)
@@ -99,7 +243,6 @@ def test_draw_multiple_flags_with_labels(sample_image):
99243
"""Test drawing multiple flags with labels."""
100244
bboxes = [[10, 10, 30, 30], [40, 40, 60, 60]]
101245
labels = ["obj1", "obj2"]
102-
# Note: The function internally sets size=1 and thickness=2
103246
result = bbox_visualizer.draw_multiple_flags_with_labels(
104247
sample_image, labels, bboxes
105248
)
@@ -109,12 +252,17 @@ def test_draw_multiple_flags_with_labels(sample_image):
109252

110253
def test_invalid_bbox_values(sample_image):
111254
"""Test handling of invalid bbox values."""
112-
# Testing with invalid bbox coordinates (negative values)
113-
invalid_bbox = [-10, -10, 20, 20]
255+
# Test with coordinates outside image boundaries
256+
invalid_bbox = [-10, -10, 150, 150]
114257
result = bbox_visualizer.draw_rectangle(sample_image, invalid_bbox)
115-
# The function should handle invalid coordinates by clipping them
116258
assert isinstance(result, np.ndarray)
117259
assert result.shape == sample_image.shape
260+
# Check that bbox was clipped to image boundaries
261+
modified_bbox = bbox_visualizer.check_and_modify_bbox(invalid_bbox, sample_image.shape)
262+
assert modified_bbox[0] >= 0
263+
assert modified_bbox[1] >= 0
264+
assert modified_bbox[2] <= sample_image.shape[1]
265+
assert modified_bbox[3] <= sample_image.shape[0]
118266

119267
def test_empty_inputs():
120268
"""Test handling of empty inputs for multiple object functions."""

0 commit comments

Comments
 (0)