Skip to content

Commit a8c1431

Browse files
authored
Merge pull request #651 from roboflow/feature/scale_boxes_utility
Add `scale_boxes` function to detection utils
2 parents a112292 + 58c8620 commit a8c1431

File tree

4 files changed

+106
-2
lines changed

4 files changed

+106
-2
lines changed

docs/detection/utils.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,11 @@
2525
## filter_polygons_by_area
2626

2727
:::supervision.detection.utils.filter_polygons_by_area
28+
29+
## move_boxes
30+
31+
:::supervision.detection.utils.move_boxes
32+
33+
## scale_boxes
34+
35+
:::supervision.detection.utils.scale_boxes

supervision/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,11 @@
4040
filter_polygons_by_area,
4141
mask_to_polygons,
4242
mask_to_xyxy,
43+
move_boxes,
4344
non_max_suppression,
4445
polygon_to_mask,
4546
polygon_to_xyxy,
47+
scale_boxes,
4648
)
4749
from supervision.draw.color import Color, ColorPalette
4850
from supervision.draw.utils import (

supervision/detection/utils.py

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -383,18 +383,65 @@ def process_roboflow_result(
383383

384384
def move_boxes(xyxy: np.ndarray, offset: np.ndarray) -> np.ndarray:
385385
"""
386-
Args:
386+
Parameters:
387387
xyxy (np.ndarray): An array of shape `(n, 4)` containing the bounding boxes
388388
coordinates in format `[x1, y1, x2, y2]`
389389
offset (np.array): An array of shape `(2,)` containing offset values in format
390390
is `[dx, dy]`.
391391
392392
Returns:
393-
(np.ndarray) repositioned bounding boxes
393+
np.ndarray: Repositioned bounding boxes.
394+
395+
Example:
396+
```python
397+
>>> import numpy as np
398+
>>> import supervision as sv
399+
400+
>>> boxes = np.array([[10, 10, 20, 20], [30, 30, 40, 40]])
401+
>>> offset = np.array([5, 5])
402+
>>> sv.move_boxes(boxes, offset)
403+
... array([
404+
... [15, 15, 25, 25],
405+
... [35, 35, 45, 45]
406+
... ])
407+
```
394408
"""
395409
return xyxy + np.hstack([offset, offset])
396410

397411

412+
def scale_boxes(xyxy: np.ndarray, factor: float) -> np.ndarray:
413+
"""
414+
Scale the dimensions of bounding boxes.
415+
416+
Parameters:
417+
xyxy (np.ndarray): An array of shape `(n, 4)` containing the bounding boxes
418+
coordinates in format `[x1, y1, x2, y2]`
419+
factor (float): A float value representing the factor by which the box
420+
dimensions are scaled. A factor greater than 1 enlarges the boxes, while a
421+
factor less than 1 shrinks them.
422+
423+
Returns:
424+
np.ndarray: Scaled bounding boxes.
425+
426+
Example:
427+
```python
428+
>>> import numpy as np
429+
>>> import supervision as sv
430+
431+
>>> boxes = np.array([[10, 10, 20, 20], [30, 30, 40, 40]])
432+
>>> factor = 1.5
433+
>>> sv.scale_boxes(boxes, factor)
434+
... array([
435+
... [ 7.5, 7.5, 22.5, 22.5],
436+
... [27.5, 27.5, 42.5, 42.5]
437+
... ])
438+
```
439+
"""
440+
centers = (xyxy[:, :2] + xyxy[:, 2:]) / 2
441+
new_sizes = (xyxy[:, 2:] - xyxy[:, :2]) * factor
442+
return np.concatenate((centers - new_sizes / 2, centers + new_sizes / 2), axis=1)
443+
444+
398445
def calculate_masks_centroids(masks: np.ndarray) -> np.ndarray:
399446
"""
400447
Calculate the centroids of binary masks in a tensor.

test/detection/test_utils.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
move_boxes,
1212
non_max_suppression,
1313
process_roboflow_result,
14+
scale_boxes,
1415
)
1516

1617
TEST_MASK = np.zeros((1, 1000, 1000), dtype=bool)
@@ -504,6 +505,52 @@ def test_move_boxes(
504505
assert np.array_equal(result, expected_result)
505506

506507

508+
@pytest.mark.parametrize(
509+
"xyxy, factor, expected_result, exception",
510+
[
511+
(
512+
np.empty(shape=(0, 4)),
513+
2.0,
514+
np.empty(shape=(0, 4)),
515+
DoesNotRaise(),
516+
), # empty xyxy array
517+
(
518+
np.array([[0, 0, 10, 10]]),
519+
1.0,
520+
np.array([[0, 0, 10, 10]]),
521+
DoesNotRaise(),
522+
), # single box with factor equal to 1.0
523+
(
524+
np.array([[0, 0, 10, 10]]),
525+
2.0,
526+
np.array([[-5, -5, 15, 15]]),
527+
DoesNotRaise(),
528+
), # single box with factor equal to 2.0
529+
(
530+
np.array([[0, 0, 10, 10]]),
531+
0.5,
532+
np.array([[2.5, 2.5, 7.5, 7.5]]),
533+
DoesNotRaise(),
534+
), # single box with factor equal to 0.5
535+
(
536+
np.array([[0, 0, 10, 10], [10, 10, 30, 30]]),
537+
2.0,
538+
np.array([[-5, -5, 15, 15], [0, 0, 40, 40]]),
539+
DoesNotRaise(),
540+
), # two boxes with factor equal to 2.0
541+
],
542+
)
543+
def test_scale_boxes(
544+
xyxy: np.ndarray,
545+
factor: float,
546+
expected_result: np.ndarray,
547+
exception: Exception,
548+
) -> None:
549+
with exception:
550+
result = scale_boxes(xyxy=xyxy, factor=factor)
551+
assert np.array_equal(result, expected_result)
552+
553+
507554
@pytest.mark.parametrize(
508555
"masks, expected_result, exception",
509556
[

0 commit comments

Comments
 (0)