Source code for evalutils.annotations

import logging

logger = logging.getLogger(__name__)


[docs]class BoundingBox:
[docs] def __init__(self, *, x1: float, x2: float, y1: float, y2: float): """ A bounding box is a face defined by 4 edges on a 2D plane. It must have a non-zero width and height. Parameters ---------- x1 Left edge of the bounding box x2 Right edge of the bounding box y1 Bottom edge of the bounding box y2 Top edge of the bounding box Raises ------ ValueError If the bounding box has zero width or height """ # Force the provided values to the correct edges self.x_left = float(min(x1, x2)) self.x_right = float(max(x1, x2)) self.y_bottom = float(min(y1, y2)) self.y_top = float(max(y1, y2)) if self.x_right <= self.x_left: raise ValueError("Bounding box has zero width") elif self.y_top <= self.y_bottom: raise ValueError("Bounding box has zero height")
def __repr__(self): return ( f"{self.__class__.__qualname__}" f"(" f"x1={self.x_left}, " f"x2={self.x_right}, " f"y1={self.y_bottom}, " f"y2={self.y_top}" f")" ) def __hash__(self): return hash((self.x_left, self.x_right, self.y_bottom, self.y_top)) def __eq__(self, other: object) -> bool: if not isinstance(other, BoundingBox): return NotImplemented return ( self.x_left == other.x_left and self.x_right == other.x_right and self.y_bottom == other.y_bottom and self.y_top == other.y_top ) @property def area(self) -> float: """Return the area of the bounding box in natural units""" return (self.x_right - self.x_left) * (self.y_top - self.y_bottom)
[docs] def intersection(self, *, other: "BoundingBox") -> float: """ Calculates the intersection area between this bounding box and another, axis aligned, bounding box. Parameters ---------- other The other bounding box Returns ------- float The intersection area in natural units if the two bounding boxes overlap, zero otherwise. """ # Get the face that is the intersection between the 8 edges x_left = max(self.x_left, other.x_left) x_right = min(self.x_right, other.x_right) y_bottom = max(self.y_bottom, other.y_bottom) y_top = min(self.y_top, other.y_top) if x_right <= x_left or y_top <= y_bottom: logger.warning( f"The bounding boxes do not intersect. bb1={self}, " f"other={other}." ) return 0.0 else: return BoundingBox( x1=x_left, x2=x_right, y1=y_bottom, y2=y_top ).area
[docs] def union(self, *, other: "BoundingBox") -> float: """ Calculates the union between this bounding box and another, axis aligned, bounding box. Parameters ---------- other The other bounding box Returns ------- float The union area in natural units """ return self.area + other.area - self.intersection(other=other)
[docs] def jaccard_index(self, *, other: "BoundingBox") -> float: """ Calculates the intersection over union between this bounding box and a second, axis aligned, bounding box. Parameters ---------- other The other bounding box Returns ------- float The intersection over union in natural units """ return self.intersection(other=other) / self.union(other=other)