from __future__ import annotations import torch from torch import Tensor def box_cxcywh_to_xyxy(boxes: Tensor) -> Tensor: cx, cy, width, height = boxes.unbind(-1) return torch.stack( (cx - 0.5 * width, cy - 0.5 * height, cx + 0.5 * width, cy + 0.5 * height), dim=-1, ) def box_xyxy_to_cxcywh(boxes: Tensor) -> Tensor: x0, y0, x1, y1 = boxes.unbind(-1) return torch.stack( ((x0 + x1) * 0.5, (y0 + y1) * 0.5, x1 - x0, y1 - y0), dim=-1 ) def box_area(boxes: Tensor) -> Tensor: return (boxes[..., 2] - boxes[..., 0]).clamp(min=0) * ( boxes[..., 3] - boxes[..., 1] ).clamp(min=0) def box_iou(boxes1: Tensor, boxes2: Tensor) -> tuple[Tensor, Tensor]: area1 = box_area(boxes1) area2 = box_area(boxes2) top_left = torch.maximum(boxes1[:, None, :2], boxes2[:, :2]) bottom_right = torch.minimum(boxes1[:, None, 2:], boxes2[:, 2:]) intersection = (bottom_right - top_left).clamp(min=0).prod(dim=-1) union = area1[:, None] + area2 - intersection return intersection / union.clamp(min=1e-7), union def generalized_box_iou(boxes1: Tensor, boxes2: Tensor) -> Tensor: """Pairwise generalized IoU for boxes in x0, y0, x1, y1 format.""" iou, union = box_iou(boxes1, boxes2) top_left = torch.minimum(boxes1[:, None, :2], boxes2[:, :2]) bottom_right = torch.maximum(boxes1[:, None, 2:], boxes2[:, 2:]) enclosing = (bottom_right - top_left).clamp(min=0).prod(dim=-1) return iou - (enclosing - union) / enclosing.clamp(min=1e-7) def generalized_box_iou_batched(boxes1: Tensor, boxes2: Tensor) -> Tensor: """Batched pairwise generalized IoU, boxes in x0, y0, x1, y1 format. boxes1: [B, N, 4], boxes2: [B, M, 4] -> [B, N, M] """ area1 = box_area(boxes1) area2 = box_area(boxes2) top_left = torch.maximum(boxes1[:, :, None, :2], boxes2[:, None, :, :2]) bottom_right = torch.minimum(boxes1[:, :, None, 2:], boxes2[:, None, :, 2:]) intersection = (bottom_right - top_left).clamp(min=0).prod(dim=-1) union = area1[:, :, None] + area2[:, None, :] - intersection iou = intersection / union.clamp(min=1e-7) enc_top_left = torch.minimum(boxes1[:, :, None, :2], boxes2[:, None, :, :2]) enc_bottom_right = torch.maximum(boxes1[:, :, None, 2:], boxes2[:, None, :, 2:]) enclosing = (enc_bottom_right - enc_top_left).clamp(min=0).prod(dim=-1) return iou - (enclosing - union) / enclosing.clamp(min=1e-7) def inverse_sigmoid(value: Tensor, eps: float = 1e-5) -> Tensor: value = value.clamp(min=0.0, max=1.0) return torch.log(value.clamp(min=eps) / (1.0 - value).clamp(min=eps))