Skip to content

Boxes Utils

supervision.detection.utils.boxes.move_boxes(xyxy: npt.NDArray[np.number], offset: npt.NDArray[np.integer]) -> npt.NDArray[np.number]

Move axis-aligned boxes by an (x, y) offset.

Parameters:

Name Type Description Default

xyxy

NDArray[number]

An array of shape (n, 4) containing the bounding boxes coordinates in format [x1, y1, x2, y2]

required

offset

NDArray[integer]

An array of shape (2,) containing offset values in format is [dx, dy].

required

Returns:

Type Description
NDArray[number]

Repositioned bounding boxes. Integer inputs produce int64 output when

NDArray[number]

the translated coordinates fit; otherwise, they produce float64 output.

NDArray[number]

Floating-point inputs preserve NumPy's existing addition behavior.

Examples:

>>> import numpy as np
>>> import supervision as sv
>>> xyxy = np.array([
...     [10, 10, 20, 20],
...     [30, 30, 40, 40]
... ])
>>> offset = np.array([5, 5])
>>> sv.move_boxes(xyxy=xyxy, offset=offset)
array([[15, 15, 25, 25],
       [35, 35, 45, 45]])
Source code in src/supervision/detection/utils/boxes.py
def move_boxes(
    xyxy: npt.NDArray[np.number], offset: npt.NDArray[np.integer]
) -> npt.NDArray[np.number]:
    """Move axis-aligned boxes by an `(x, y)` offset.

    Args:
        xyxy: An array of shape `(n, 4)` containing the
            bounding boxes coordinates in format `[x1, y1, x2, y2]`
        offset: An array of shape `(2,)` containing offset values in format
            is `[dx, dy]`.

    Returns:
        Repositioned bounding boxes. Integer inputs produce `int64` output when
        the translated coordinates fit; otherwise, they produce `float64` output.
        Floating-point inputs preserve NumPy's existing addition behavior.

    Examples:
        ```pycon
        >>> import numpy as np
        >>> import supervision as sv
        >>> xyxy = np.array([
        ...     [10, 10, 20, 20],
        ...     [30, 30, 40, 40]
        ... ])
        >>> offset = np.array([5, 5])
        >>> sv.move_boxes(xyxy=xyxy, offset=offset)
        array([[15, 15, 25, 25],
               [35, 35, 45, 45]])

        ```
    """
    offset = np.asarray(offset)
    return _add_integer_offset(xyxy, np.hstack([offset, offset]))

supervision.detection.utils.boxes.scale_boxes(xyxy: npt.NDArray[np.number], factor: float) -> npt.NDArray[np.floating]

Scale the dimensions of bounding boxes.

Parameters:

Name Type Description Default

xyxy

NDArray[number]

An integer or floating-point array of shape (n, 4) containing bounding box coordinates in format [x1, y1, x2, y2].

required

factor

float

A float value representing the factor by which the box dimensions are scaled. A factor greater than 1 enlarges the boxes, while a factor less than 1 shrinks them.

required

Returns:

Type Description
NDArray[floating]

Scaled bounding boxes. Integer input arrays produce float64 output;

NDArray[floating]

floating-point inputs preserve their existing dtype.

Note

Finite corners whose full span overflows the coordinate dtype are scaled corner by corner, which keeps representable corners finite unless the scaling also overflows float64.

Examples:

>>> import numpy as np
>>> import supervision as sv
>>> xyxy = np.array([
...     [10, 10, 20, 20],
...     [30, 30, 40, 40]
... ])
>>> sv.scale_boxes(xyxy=xyxy, factor=1.5)
array([[ 7.5,  7.5, 22.5, 22.5],
       [27.5, 27.5, 42.5, 42.5]])
Source code in src/supervision/detection/utils/boxes.py
def scale_boxes(
    xyxy: npt.NDArray[np.number], factor: float
) -> npt.NDArray[np.floating]:
    """Scale the dimensions of bounding boxes.

    Args:
        xyxy: An integer or floating-point array of shape `(n, 4)` containing
            bounding box coordinates in format `[x1, y1, x2, y2]`.
        factor: A float value representing the factor by which the box
            dimensions are scaled. A factor greater than 1 enlarges the boxes, while a
            factor less than 1 shrinks them.

    Returns:
        Scaled bounding boxes. Integer input arrays produce `float64` output;
        floating-point inputs preserve their existing dtype.

    Note:
        Finite corners whose full span overflows the coordinate dtype are scaled
        corner by corner, which keeps representable corners finite unless the
        scaling also overflows float64.

    Examples:
        ```pycon
        >>> import numpy as np
        >>> import supervision as sv
        >>> xyxy = np.array([
        ...     [10, 10, 20, 20],
        ...     [30, 30, 40, 40]
        ... ])
        >>> sv.scale_boxes(xyxy=xyxy, factor=1.5)
        array([[ 7.5,  7.5, 22.5, 22.5],
               [27.5, 27.5, 42.5, 42.5]])

        ```
    """
    if np.issubdtype(xyxy.dtype, np.integer):
        exact_integer_limit = 2**52
        is_safe_to_cast = np.all(
            xyxy <= exact_integer_limit
            if np.issubdtype(xyxy.dtype, np.unsignedinteger)
            else (xyxy >= -exact_integer_limit) & (xyxy <= exact_integer_limit)
        )
        if is_safe_to_cast:
            xyxy = xyxy.astype(np.float64)
        elif np.isfinite(factor):
            # Preserve 64-bit integer precision through both final-corner
            # expressions. Converting the center first can change the final
            # float64 rounding near 2**53.
            factor_numerator, factor_denominator = factor.as_integer_ratio()
            integer_coordinates = xyxy.astype(object)
            coordinate_sum = integer_coordinates[:, :2] + integer_coordinates[:, 2:]
            coordinate_size = integer_coordinates[:, 2:] - integer_coordinates[:, :2]
            denominator = 2 * factor_denominator
            lower = np.asarray(
                (
                    coordinate_sum * factor_denominator
                    - coordinate_size * factor_numerator
                )
                / denominator,
                dtype=np.float64,
            )
            upper = np.asarray(
                (
                    coordinate_sum * factor_denominator
                    + coordinate_size * factor_numerator
                )
                / denominator,
                dtype=np.float64,
            )
            return cast(
                npt.NDArray[np.floating], np.concatenate((lower, upper), axis=1)
            )
        else:
            xyxy = xyxy.astype(np.float64)

    centers = cast(npt.NDArray[np.floating], _box_midpoint(xyxy[:, :2], xyxy[:, 2:]))
    with np.errstate(over="ignore", invalid="ignore"):
        new_sizes = cast(npt.NDArray[np.floating], (xyxy[:, 2:] - xyxy[:, :2]) * factor)
    lower = centers - new_sizes / 2
    upper = centers + new_sizes / 2
    overflow = (
        ~np.isfinite(new_sizes) & np.isfinite(xyxy[:, :2]) & np.isfinite(xyxy[:, 2:])
    )
    if np.any(overflow) and np.isfinite(factor):
        # Subtracting or scaling the full span can overflow while the final
        # corners still fit the input dtype. Weight each corner directly, so a
        # separately rounded midpoint and half-span cannot push a representable
        # corner past the dtype limit. Narrower dtypes are computed in float64.
        wide_dtype = np.promote_types(xyxy.dtype, np.float64)
        first = xyxy[:, :2][overflow].astype(wide_dtype) / 2
        second = xyxy[:, 2:][overflow].astype(wide_dtype) / 2
        with np.errstate(over="ignore", invalid="ignore"):
            new_lower = first * (1 + factor) + second * (1 - factor)
            new_upper = second * (1 + factor) + first * (1 - factor)
        # A large factor can overflow both weighted terms with opposite signs.
        # Scaling the half-span still gives the right corner or infinity there.
        for corners, sign in ((new_lower, -1), (new_upper, 1)):
            unresolved = ~np.isfinite(corners)
            if np.any(unresolved):
                half_spans = (second - first)[unresolved] * factor
                corners[unresolved] = (first + second)[unresolved] + sign * half_spans
        lower[overflow] = new_lower
        upper[overflow] = new_upper
    return cast(npt.NDArray[np.floating], np.concatenate((lower, upper), axis=1))

supervision.detection.utils.boxes.clip_boxes(xyxy: npt.NDArray[np.number], resolution_wh: tuple[int, int]) -> npt.NDArray[np.number]

Clips bounding boxes coordinates to fit within the frame resolution.

Parameters:

Name Type Description Default

xyxy

NDArray[number]

A numpy array of shape (N, 4) where each row corresponds to a bounding box in the format (x_min, y_min, x_max, y_max).

required

resolution_wh

tuple[int, int]

A tuple of the form (width, height) representing the resolution of the frame.

required

Returns:

Type Description
NDArray[number]

A numpy array of shape (N, 4) where each row corresponds to a bounding box with coordinates clipped to fit within the frame resolution.

Examples:

>>> import numpy as np
>>> import supervision as sv
>>> xyxy = np.array([
...     [10, 20, 300, 200],
...     [15, 25, 350, 450],
...     [-10, -20, 30, 40]
... ])
>>> sv.clip_boxes(xyxy=xyxy, resolution_wh=(320, 240))
array([[ 10,  20, 300, 200],
       [ 15,  25, 320, 240],
       [  0,   0,  30,  40]])
Source code in src/supervision/detection/utils/boxes.py
def clip_boxes(
    xyxy: npt.NDArray[np.number],
    resolution_wh: tuple[int, int],
) -> npt.NDArray[np.number]:
    """Clips bounding boxes coordinates to fit within the frame resolution.

    Args:
        xyxy: A numpy array of shape `(N, 4)` where each
            row corresponds to a bounding box in
            the format `(x_min, y_min, x_max, y_max)`.
        resolution_wh: A tuple of the form
            `(width, height)` representing the resolution of the frame.

    Returns:
        A numpy array of shape `(N, 4)` where each row
            corresponds to a bounding box with coordinates clipped to fit
            within the frame resolution.

    Examples:
        ```pycon
        >>> import numpy as np
        >>> import supervision as sv
        >>> xyxy = np.array([
        ...     [10, 20, 300, 200],
        ...     [15, 25, 350, 450],
        ...     [-10, -20, 30, 40]
        ... ])
        >>> sv.clip_boxes(xyxy=xyxy, resolution_wh=(320, 240))
        array([[ 10,  20, 300, 200],
               [ 15,  25, 320, 240],
               [  0,   0,  30,  40]])

        ```
    """
    result: npt.NDArray[np.number] = np.copy(xyxy)
    width, height = resolution_wh
    result[:, [0, 2]] = result[:, [0, 2]].clip(0, width)
    result[:, [1, 3]] = result[:, [1, 3]].clip(0, height)
    return result

supervision.detection.utils.boxes.pad_boxes(xyxy: npt.NDArray[np.number], px: int, py: int | None = None) -> npt.NDArray[np.number]

Pads bounding boxes coordinates with a constant padding.

Parameters:

Name Type Description Default

xyxy

NDArray[number]

A numpy array of shape (N, 4) where each row corresponds to a bounding box in the format (x_min, y_min, x_max, y_max).

required

px

int

The padding value to be added to both the left and right sides of each bounding box.

required

py

int | None

The padding value to be added to both the top and bottom sides of each bounding box. If not provided, px will be used for both dimensions.

None

Returns:

Type Description
NDArray[number]

A numpy array of shape (N, 4) where each row corresponds to a bounding box with coordinates padded according to the provided padding values. Integer inputs produce int64 output, or float64 if a padded coordinate exceeds the int64 range. Floating-point inputs preserve their dtype.

Examples:

>>> import numpy as np
>>> import supervision as sv
>>> xyxy = np.array([
...     [10, 20, 30, 40],
...     [15, 25, 35, 45]
... ])
>>> sv.pad_boxes(xyxy=xyxy, px=5, py=10)
array([[ 5, 10, 35, 50],
       [10, 15, 40, 55]])
Source code in src/supervision/detection/utils/boxes.py
def pad_boxes(
    xyxy: npt.NDArray[np.number],
    px: int,
    py: int | None = None,
) -> npt.NDArray[np.number]:
    """Pads bounding boxes coordinates with a constant padding.

    Args:
        xyxy: A numpy array of shape `(N, 4)` where each
            row corresponds to a bounding box in the format
            `(x_min, y_min, x_max, y_max)`.
        px: The padding value to be added to both the left and right sides of
            each bounding box.
        py: The padding value to be added to both the top and bottom
            sides of each bounding box. If not provided, `px` will be used for both
            dimensions.

    Returns:
        A numpy array of shape `(N, 4)` where each row corresponds to a
            bounding box with coordinates padded according to the provided padding
            values. Integer inputs produce `int64` output, or `float64` if a padded
            coordinate exceeds the `int64` range. Floating-point inputs preserve
            their dtype.

    Examples:
        ```pycon
        >>> import numpy as np
        >>> import supervision as sv
        >>> xyxy = np.array([
        ...     [10, 20, 30, 40],
        ...     [15, 25, 35, 45]
        ... ])
        >>> sv.pad_boxes(xyxy=xyxy, px=5, py=10)
        array([[ 5, 10, 35, 50],
               [10, 15, 40, 55]])

        ```
    """
    if py is None:
        py = px

    integer_input = np.issubdtype(xyxy.dtype, np.integer)
    padding: list[int] | npt.NDArray[Any] = [px, py]
    if integer_input:
        px, py = int(px), int(py)
        exact_integer_limit = np.iinfo(np.int64).max // 2
        is_safe_to_cast = max(abs(px), abs(py)) <= exact_integer_limit and np.all(
            xyxy <= exact_integer_limit
            if np.issubdtype(xyxy.dtype, np.unsignedinteger)
            else (xyxy >= -exact_integer_limit) & (xyxy <= exact_integer_limit)
        )
        # Use exact Python integers only when int64 intermediates could overflow.
        result = xyxy.astype(np.int64 if is_safe_to_cast else object)
        padding = np.asarray([px, py], dtype=result.dtype)
    else:
        result = cast(npt.NDArray[Any], xyxy.copy())
    result[:, [0, 1]] -= padding
    result[:, [2, 3]] += padding

    if integer_input and result.dtype == object:
        limits = np.iinfo(np.int64)
        fits_int64 = np.all((result >= limits.min) & (result <= limits.max))
        return cast(
            npt.NDArray[np.number],
            np.asarray(result, dtype=np.int64 if fits_int64 else np.float64),
        )
    return cast(npt.NDArray[np.number], result)

supervision.detection.utils.boxes.denormalize_boxes(xyxy: npt.NDArray[np.number], resolution_wh: tuple[int, int], normalization_factor: float = 1.0) -> npt.NDArray[np.number]

Convert normalized bounding box coordinates to absolute pixel coordinates.

Multiplies each bounding box coordinate by image size and divides by normalization_factor, mapping values from normalized [0, normalization_factor] to absolute pixel values for a given resolution.

Parameters:

Name Type Description Default

xyxy

NDArray[number]

Normalized bounding boxes of shape (N, 4), where each row is (x_min, y_min, x_max, y_max), values in [0, normalization_factor].

required

resolution_wh

tuple[int, int]

Target image resolution as (width, height).

required

normalization_factor

float

Maximum value of input coordinate range. Defaults to 1.0.

1.0

Returns:

Type Description
NDArray[number]

Array of shape (N, 4) with absolute coordinates in (x_min, y_min, x_max, y_max) format.

Examples:

>>> import numpy as np
>>> import supervision as sv
>>> xyxy = np.array([
...     [0.1, 0.2, 0.5, 0.6],
...     [0.3, 0.4, 0.7, 0.8],
...     [0.2, 0.1, 0.6, 0.5]
... ])
>>> sv.denormalize_boxes(xyxy, (1280, 720))
array([[128., 144., 640., 432.],
       [384., 288., 896., 576.],
       [256.,  72., 768., 360.]])
>>> xyxy = np.array([
...     [256., 128., 768., 640.]
... ])
>>> sv.denormalize_boxes(xyxy, (1280, 720), normalization_factor=1024.0)
array([[320.,  90., 960., 450.]])
Source code in src/supervision/detection/utils/boxes.py
def denormalize_boxes(
    xyxy: npt.NDArray[np.number],
    resolution_wh: tuple[int, int],
    normalization_factor: float = 1.0,
) -> npt.NDArray[np.number]:
    """Convert normalized bounding box coordinates to absolute pixel coordinates.

    Multiplies each bounding box coordinate by image size and divides by
    `normalization_factor`, mapping values from normalized `[0, normalization_factor]`
    to absolute pixel values for a given resolution.

    Args:
        xyxy: Normalized bounding boxes of shape `(N, 4)`,
            where each row is `(x_min, y_min, x_max, y_max)`, values in
            `[0, normalization_factor]`.
        resolution_wh: Target image resolution as `(width, height)`.
        normalization_factor: Maximum value of input coordinate range.
            Defaults to `1.0`.

    Returns:
        Array of shape `(N, 4)` with absolute coordinates in
            `(x_min, y_min, x_max, y_max)` format.

    Examples:
        ```pycon
        >>> import numpy as np
        >>> import supervision as sv
        >>> xyxy = np.array([
        ...     [0.1, 0.2, 0.5, 0.6],
        ...     [0.3, 0.4, 0.7, 0.8],
        ...     [0.2, 0.1, 0.6, 0.5]
        ... ])
        >>> sv.denormalize_boxes(xyxy, (1280, 720))
        array([[128., 144., 640., 432.],
               [384., 288., 896., 576.],
               [256.,  72., 768., 360.]])

        ```

        ```pycon
        >>> xyxy = np.array([
        ...     [256., 128., 768., 640.]
        ... ])
        >>> sv.denormalize_boxes(xyxy, (1280, 720), normalization_factor=1024.0)
        array([[320.,  90., 960., 450.]])

        ```
    """
    width, height = resolution_wh

    # Preserve floating caller dtypes; integer inputs need float64 to retain
    # fractional pixel coordinates rather than silently truncating them.
    scale_dtype = (
        xyxy.dtype if np.issubdtype(xyxy.dtype, np.inexact) else np.dtype(np.float64)
    )
    scale = np.array([width, height, width, height], dtype=scale_dtype)
    scale /= normalization_factor

    return xyxy * scale

supervision.detection.utils.boxes.xyxyxyxy_to_xyxy(xyxyxyxy: npt.NDArray[np.number]) -> npt.NDArray[np.number]

Convert oriented bounding box corners to axis-aligned bounding boxes.

Parameters:

Name Type Description Default

xyxyxyxy

NDArray[number]

OBB corner coordinates with shape (N, 4, 2) where each box is represented as [[x1, y1], [x2, y2], [x3, y3], [x4, y4]].

required

Returns:

Type Description
NDArray[number]

Axis-aligned bounding boxes as an array of shape (N, 4) in (x_min, y_min, x_max, y_max) format.

Raises:

Type Description
ValueError

If xyxyxyxy does not have shape (N, 4, 2).

Examples:

>>> import numpy as np
>>> import supervision as sv
>>> corners = np.array([
...     [[0, 0], [10, 0], [10, 5], [0, 5]],
...     [[5, 5], [15, 5], [15, 10], [5, 10]],
... ], dtype=np.float32)
>>> sv.xyxyxyxy_to_xyxy(corners)
array([[ 0.,  0., 10.,  5.],
       [ 5.,  5., 15., 10.]], dtype=float32)
Source code in src/supervision/detection/utils/boxes.py
def xyxyxyxy_to_xyxy(
    xyxyxyxy: npt.NDArray[np.number],
) -> npt.NDArray[np.number]:
    """Convert oriented bounding box corners to axis-aligned bounding boxes.

    Args:
        xyxyxyxy: OBB corner coordinates with shape `(N, 4, 2)` where each
            box is represented as `[[x1, y1], [x2, y2], [x3, y3], [x4, y4]]`.

    Returns:
        Axis-aligned bounding boxes as an array of shape `(N, 4)`
            in `(x_min, y_min, x_max, y_max)` format.

    Raises:
        ValueError: If `xyxyxyxy` does not have shape `(N, 4, 2)`.

    Examples:
        ```pycon
        >>> import numpy as np
        >>> import supervision as sv
        >>> corners = np.array([
        ...     [[0, 0], [10, 0], [10, 5], [0, 5]],
        ...     [[5, 5], [15, 5], [15, 10], [5, 10]],
        ... ], dtype=np.float32)
        >>> sv.xyxyxyxy_to_xyxy(corners)
        array([[ 0.,  0., 10.,  5.],
               [ 5.,  5., 15., 10.]], dtype=float32)

        ```
    """
    xyxyxyxy = cast(npt.NDArray[np.number], np.asarray(xyxyxyxy))
    if xyxyxyxy.ndim != 3 or xyxyxyxy.shape[-2:] != (4, 2):
        raise ValueError(f"xyxyxyxy must have shape (N, 4, 2); got {xyxyxyxy.shape}")
    x_min = xyxyxyxy[..., 0].min(axis=-1)
    y_min = xyxyxyxy[..., 1].min(axis=-1)
    x_max = xyxyxyxy[..., 0].max(axis=-1)
    y_max = xyxyxyxy[..., 1].max(axis=-1)
    return cast(npt.NDArray[np.number], np.stack([x_min, y_min, x_max, y_max], axis=-1))

Comments