Repository: roboflow/supervision
File: supervision/detection/utils.py (Python)

Code before:
        #     [25.  , 40.  ,  0.75, 40.  ],
        #     [32.5 , 47.5 ,  0.77777778, 45.  ]
        # ])
        ```

    """
    if xyxy.size == 0:
        return np.empty((0, 4), dtype=float)

    x1, y1, x2, y2 = xyxy.T
    width = x2 - x1
    height = y2 - y1
    center_x = x1 + width / 2
    center_y = y1 + height / 2

    aspect_ratio = np.divide(
        width,
        height,
        out=np.zeros_like(width, dtype=float),
        where=height != 0,
    )
    result = np.column_stack((center_x, center_y, aspect_ratio, height))
    return result.astype(float)


def mask_to_xyxy(masks: np.ndarray) -> np.ndarray:
    """
    Converts a 3D `np.array` of 2D bool masks into a 2D `np.array` of bounding boxes.

    Parameters:
        masks (np.ndarray): A 3D `np.array` of shape `(N, W, H)`
            containing 2D bool masks

    Returns:
        np.ndarray: A 2D `np.array` of shape `(N, 4)` containing the bounding boxes
            `(x_min, y_min, x_max, y_max)` for each mask
    """
    n = masks.shape[0]
    xyxy = np.zeros((n, 4), dtype=int)

    for i, mask in enumerate(masks):
        rows, cols = np.where(mask)

        if len(rows) > 0 and len(cols) > 0:
            x_min, x_max = np.min(cols), np.max(cols)
            y_min, y_max = np.min(rows), np.max(rows)
            xyxy[i, :] = [x_min, y_min, x_max, y_max]

    return xyxy



Review comment (suggestion):
please rename this function to `denormalize_boxes` to match existing `clip_boxes`, `pad_boxes` and `scale_boxes`

Code after:
        #     [25.  , 40.  ,  0.75, 40.  ],
        #     [32.5 , 47.5 ,  0.77777778, 45.  ]
        # ])
        ```

    """
    if xyxy.size == 0:
        return np.empty((0, 4), dtype=float)

    x1, y1, x2, y2 = xyxy.T
    width = x2 - x1
    height = y2 - y1
    center_x = x1 + width / 2
    center_y = y1 + height / 2

    aspect_ratio = np.divide(
        width,
        height,
        out=np.zeros_like(width, dtype=float),
        where=height != 0,
    )
    result = np.column_stack((center_x, center_y, aspect_ratio, height))
    return result.astype(float)


def normalized_xyxy_to_absolute_xyxy(
    normalized_xyxy: np.ndarray,
    resolution_wh: Tuple[int, int],
    normalization_factor: float = 1.0,
) -> np.ndarray:
    """
    Convert normalized xyxy coordinates to absolute XYXY coordinates. By default, assumes
    normalized values are between 0 and 1, but supports custom ranges via normalization_factor parameter.
    Args:
        normalized_xyxy (np.ndarray): A numpy array of shape `(N, 4)` where each row contains
            normalized coordinates in format `(x1, y1, x2, y2)` with values between 0 and normalization_factor.
        resolution_wh (Tuple[int, int]): A tuple of the form `(width, height)` representing
            the target resolution.
        normalization_factor (float): The maximum value of the normalization range. For example:
            - normalization_factor=1.0 means input coordinates are normalized between 0 and 1
            - normalization_factor=100.0 means input coordinates are normalized between 0 and 100
            - normalization_factor=1000.0 means input coordinates are normalized between 0 and 1000
    Returns:
        np.ndarray: A numpy array of shape `(N, 4)` containing the absolute coordinates
            in format `(x1, y1, x2, y2)`.
    Examples:
        ```python
        import numpy as np
        import supervision as sv
        # Example with default normalization (0-1)
        normalized_xyxy = np.array([