class Bbox:
    """Class for manipulating bounding boxes.
    All bounding boxes are converted to internally the VOC format: [xmin, ymin, xmax, ymax], and can be exported to the
    VOC, COCO and YOLO formats.
    """
    def __init__(self, bbox: list[float], width: float, height: float, bbox_id: int) -> None:
        self.bbox = bbox
        self.width = width
        self.height = height
        self.bbox_id = bbox_id
        self._validate_bbox()
    @classmethod
    def from_voc(cls, bbox: list[float], width: float, height: float, bbox_id: int) -> Bbox:
        """Keep the bbox in VOC format: xmin, ymin, xmax, ymax."""
        return Bbox(bbox, width, height, bbox_id)
    @classmethod
    def from_coco(cls, bbox: list[float], width: float, height: float, bbox_id: int) -> Bbox:
        """Convert the bbox from COCO format: xmin, ymin, w, h."""
        bbox = [bbox[0], bbox[1], bbox[0] + bbox[2], bbox[1] + bbox[3]]
        return Bbox(bbox, width, height, bbox_id)
    @classmethod
    def from_yolo(cls, bbox: list[float], width: float, height: float, bbox_id: int) -> Bbox:
        """Convert the bbox from YOLO format: relative xc, yc, w, h."""
        assert bbox[0] < 1 and bbox[1] < 1 and bbox[2] < 1 and bbox[3] < 1, "yolo bbox must be relative"
        bbox = [bbox[0] - bbox[2] / 2, bbox[1] - bbox[3] / 2, bbox[0] + bbox[2] / 2, bbox[1] + bbox[3] / 2]
        bbox = [bbox[0] * width, bbox[1] * height, bbox[2] * width, bbox[3] * height]
        return Bbox(bbox, width, height, bbox_id)
    def to_voc(self) -> list[float]:
        """Bbox is already in VOC format internally."""
        return self.bbox
    def to_coco(self) -> list[float]:
        """Convert the bbox to COCO format: xmin, ymin, w, h."""
        return [self.bbox[0], self.bbox[1], self.bbox[2] - self.bbox[0], self.bbox[3] - self.bbox[1]]
    def to_yolo(self) -> list[float]:
        """Convert the bbox to YOLO format: relative xc, yc, w, h."""
        bbox = self.to_coco()
        bbox = [bbox[0] / self.width, bbox[1] / self.height, bbox[2] / self.width, bbox[3] / self.height]
        return bbox
    def _validate_bbox(self) -> None:
        """Asserts that the bbox to the correct size."""
        assert self.bbox[2] >= self.bbox[0] and self.bbox[3] >= self.bbox[1], "bbox must be a rectangle"
        # assert self.bbox[2] <= self.width and self.bbox[3] <= self.height, "bbox must be inside the image"
        if not self.bbox[2] <= self.width:
            print(f"Warning: incorrect bbox_id {self.bbox_id}: x_max {self.bbox[2]} > image width {self.width}")
        if not self.bbox[3] <= self.height:
            print(f"Warning: incorrect bbox_id {self.bbox_id}: y_max {self.bbox[3]} > image height {self.height}")
    def __repr__(self):
        return f"Bbox id {self.bbox_id} {self.bbox}"
    def __print__(self):
        return self.__repr__()