def bboxes_iou(xyxy1, xyxy2) -> float:
    """
    Calculate IoU for two bounding boxes
    :param xyxy1: array-like, contains (x1, y1, x2, y2)
    :param xyxy2: array-like, contains (x1, y1, x2, y2)
    :return: float, IoU(xyxy1, xyxy2)
    """
    x1_d, y1_d, x2_d, y2_d = xyxy1
    x1_e, y1_e, x2_e, y2_e = xyxy2

    # determine the coordinates of the intersection rectangle
    x_left = max(x1_d, x1_e)
    y_top = max(y1_d, y1_e)
    x_right = min(x2_d, x2_e)
    y_bottom = min(y2_d, y2_e)

    intersection_area = max(0, x_right - x_left + 1) * max(0, y_bottom - y_top + 1)

    bb1_area = (x2_d - x1_d + 1) * (y2_d - y1_d + 1)
    bb2_area = (x2_e - x1_e + 1) * (y2_e - y1_e + 1)

    # compute the intersection over union by taking the intersection
    # area and dividing it by the sum of prediction + ground-truth
    # areas - the intersection area
    iou = intersection_area / float(bb1_area + bb2_area - intersection_area)

    assert 0 <= iou <= 1, f'expected value in range [0, 1], got {iou}'  # double-check ourselves

    return iou