import numpy as np def box_denormalize(boxes: np.ndarray, img_w: int, img_h: int) -> np.ndarray: """ Denormalizes boxes from [0, 1] to [0, img_w] and [0, img_h]. Args: boxes (Tensor[N, 4]): boxes which will be denormalized. img_w (int): Width of image. img_h (int): Height of image. Returns: Tensor[N, 4]: Denormalized boxes. """ if boxes.size == 0: return boxes # check if boxes are normalized if np.any(boxes > 1.0): return boxes boxes[:, 0::2] *= img_w boxes[:, 1::2] *= img_h return boxes def box_convert(boxes: np.ndarray, in_fmt: str, out_fmt: str) -> np.ndarray: """ Converts boxes from given in_fmt to out_fmt. Supported in_fmt and out_fmt are: 'xyxy': boxes are represented via corners, x1, y1 being top left and x2, y2 being bottom right. This is the format that torchvision utilities expect. 'xywh' : boxes are represented via corner, width and height, x1, y2 being top left, w, h being width and height. 'cxcywh' : boxes are represented via centre, width and height, cx, cy being center of box, w, h being width and height. Args: boxes (Tensor[N, 4]): boxes which will be converted. in_fmt (str): Input format of given boxes. Supported formats are ['xyxy', 'xywh', 'cxcywh']. out_fmt (str): Output format of given boxes. Supported formats are ['xyxy', 'xywh', 'cxcywh'] Returns: Tensor[N, 4]: Boxes into converted format. """ if boxes.size == 0: return boxes allowed_fmts = ("xyxy", "xywh", "cxcywh") if in_fmt not in allowed_fmts or out_fmt not in allowed_fmts: raise ValueError( "Unsupported Bounding Box Conversions for given in_fmt and out_fmt") if in_fmt == out_fmt: return boxes.copy() if in_fmt != "xyxy" and out_fmt != "xyxy": # convert to xyxy and change in_fmt xyxy if in_fmt == "xywh": boxes = _box_xywh_to_xyxy(boxes) elif in_fmt == "cxcywh": boxes = _box_cxcywh_to_xyxy(boxes) in_fmt = "xyxy" if in_fmt == "xyxy": if out_fmt == "xywh": boxes = _box_xyxy_to_xywh(boxes) elif out_fmt == "cxcywh": boxes = _box_xyxy_to_cxcywh(boxes) elif out_fmt == "xyxy": if in_fmt == "xywh": boxes = _box_xywh_to_xyxy(boxes) elif in_fmt == "cxcywh": boxes = _box_cxcywh_to_xyxy(boxes) return boxes def _box_xywh_to_xyxy(boxes): """ Converts bounding boxes from (x, y, w, h) format to (x1, y1, x2, y2) format. (x, y) refers to top left of bounding box. (w, h) refers to width and height of box. Args: boxes (ndarray[N, 4]): boxes in (x, y, w, h) which will be converted. Returns: boxes (ndarray[N, 4]): boxes in (x1, y1, x2, y2) format. """ x, y, w, h = np.split(boxes, 4, axis=-1) x1 = x y1 = y x2 = x + w y2 = y + h converted_boxes = np.concatenate([x1, y1, x2, y2], axis=-1) return converted_boxes def _box_cxcywh_to_xyxy(boxes): """ Converts bounding boxes from (cx, cy, w, h) format to (x1, y1, x2, y2) format. (cx, cy) refers to center of bounding box (w, h) are width and height of bounding box Args: boxes (ndarray[N, 4]): boxes in (cx, cy, w, h) format which will be converted. Returns: boxes (ndarray[N, 4]): boxes in (x1, y1, x2, y2) format. """ cx, cy, w, h = np.split(boxes, 4, axis=-1) x1 = cx - 0.5 * w y1 = cy - 0.5 * h x2 = cx + 0.5 * w y2 = cy + 0.5 * h converted_boxes = np.concatenate([x1, y1, x2, y2], axis=-1) return converted_boxes def _box_xyxy_to_xywh(boxes): """ Converts bounding boxes from (x1, y1, x2, y2) format to (x, y, w, h) format. (x1, y1) refer to top left of bounding box (x2, y2) refer to bottom right of bounding box Args: boxes (ndarray[N, 4]): boxes in (x1, y1, x2, y2) which will be converted. Returns: boxes (ndarray[N, 4]): boxes in (x, y, w, h) format. """ x1, y1, x2, y2 = np.split(boxes, 4, axis=-1) w = x2 - x1 h = y2 - y1 converted_boxes = np.concatenate([x1, y1, w, h], axis=-1) return converted_boxes def _box_xyxy_to_cxcywh(boxes): """ Converts bounding boxes from (x1, y1, x2, y2) format to (cx, cy, w, h) format. (x1, y1) refer to top left of bounding box (x2, y2) refer to bottom right of bounding box Args: boxes (ndarray[N, 4]): boxes in (x1, y1, x2, y2) format which will be converted. Returns: boxes (ndarray[N, 4]): boxes in (cx, cy, w, h) format. """ x1, y1, x2, y2 = np.split(boxes, 4, axis=-1) cx = (x1 + x2) / 2 cy = (y1 + y2) / 2 w = x2 - x1 h = y2 - y1 converted_boxes = np.concatenate([cx, cy, w, h], axis=-1) return converted_boxes def _fix_empty_arrays(boxes: np.ndarray) -> np.ndarray: """Empty tensors can cause problems, this methods corrects them.""" if boxes.size == 0 and boxes.ndim == 1: return np.expand_dims(boxes, axis=0) return boxes def _input_validator(preds, targets, iou_type="bbox"): """Ensure the correct input format of `preds` and `targets`.""" if iou_type == "bbox": item_val_name = "boxes" elif iou_type == "segm": item_val_name = "masks" else: raise Exception(f"IOU type {iou_type} is not supported") if not isinstance(preds, (list, tuple)): raise ValueError( f"Expected argument `preds` to be of type list or tuple, but got {type(preds)}") if not isinstance(targets, (list, tuple)): raise ValueError( f"Expected argument `targets` to be of type list or tuple, but got {type(targets)}") if len(preds) != len(targets): raise ValueError( f"Expected argument `preds` and `targets` to have the same length, but got {len(preds)} and {len(targets)}" ) for k in [item_val_name, "scores", "labels"]: if any(k not in p for p in preds): raise ValueError( f"Expected all dicts in `preds` to contain the `{k}` key") for k in [item_val_name, "labels"]: if any(k not in p for p in targets): raise ValueError( f"Expected all dicts in `targets` to contain the `{k}` key") if any(type(pred[item_val_name]) is not np.ndarray for pred in preds): raise ValueError( f"Expected all {item_val_name} in `preds` to be of type ndarray") if any(type(pred["scores"]) is not np.ndarray for pred in preds): raise ValueError( "Expected all scores in `preds` to be of type ndarray") if any(type(pred["labels"]) is not np.ndarray for pred in preds): raise ValueError( "Expected all labels in `preds` to be of type ndarray") if any(type(target[item_val_name]) is not np.ndarray for target in targets): raise ValueError( f"Expected all {item_val_name} in `targets` to be of type ndarray") if any(type(target["labels"]) is not np.ndarray for target in targets): raise ValueError( "Expected all labels in `targets` to be of type ndarray") for i, item in enumerate(targets): if item[item_val_name].shape[0] != item["labels"].shape[0]: raise ValueError( f"Input {item_val_name} and labels of sample {i} in targets have a" f" different length (expected {item[item_val_name].shape[0]} labels, got {item['labels'].shape[0]})" ) for i, item in enumerate(preds): if not (item[item_val_name].shape[0] == item["labels"].shape[0] == item["scores"].shape[0]): raise ValueError( f"Input {item_val_name}, labels and scores of sample {i} in predictions have a" f" different length (expected {item[item_val_name].shape[0]} labels and scores," f" got {item['labels'].shape[0]} labels and {item['scores'].shape[0]})" )