Source code for bbox.metrics

"""Functions for metrics related to 2D and 3D bounding boxes."""

# pylint: disable=invalid-name,missing-docstring,assignment-from-no-return,logging-format-interpolation

import logging

import numpy as np

from bbox import BBox2D, BBox2DList, BBox3D
from bbox.geometry import polygon_area, polygon_collision, polygon_intersection

logger = logging.getLogger(__name__)


[docs]def iou_2d(a: BBox2D, b: BBox2D): """ Compute the Intersection over Union (IoU) of a pair of 2D bounding boxes. Alias for `jaccard_index_2d`. """ return jaccard_index_2d(a, b)
[docs]def jaccard_index_2d(a: BBox2D, b: BBox2D): """ Compute the Jaccard Index / Intersection over Union (IoU) of a pair of 2D bounding boxes. Args: a (:py:class:`BBox2D`): 2D bounding box. b (:py:class:`BBox2D`): 2D bounding box. Returns: :py:class:`float`: The IoU of the 2 bounding boxes. """ xA = np.maximum(a.x1, b.x1) yA = np.maximum(a.y1, b.y1) xB = np.minimum(a.x2, b.x2) yB = np.minimum(a.y2, b.y2) logger.debug("xA={0} yA={1} xB={2} yB={3}".format(xA, yA, xB, yB)) inter_w = xB - xA + 1 inter_w = inter_w * (inter_w >= 0) inter_h = yB - yA + 1 inter_h = inter_h * (inter_h >= 0) intersection = inter_w * inter_h logger.debug("jaccard_index: intersection={0}".format(intersection)) a_area = a.width * a.height b_area = b.width * b.height logger.debug("jaccard_index: a_area: {0}, b_area: {1}".format(a_area, b_area)) iou = intersection / (a_area + b_area - intersection) # set nan and +/- inf to 0 if np.isinf(iou) or np.isnan(iou): iou = 0 return iou
[docs]def multi_iou_2d(a: BBox2DList, b: BBox2DList): """ Compute the Intersection over Union (IoU) of two sets of 2D bounding boxes. Alias for `multi_jaccard_index_2d`. """ return multi_jaccard_index_2d(a, b)
[docs]def multi_jaccard_index_2d(a: BBox2DList, b: BBox2DList): """ Compute the Jaccard Index (Intersection over Union) of two sets of 2D bounding boxes. Args: a (:py:class:`BBox2DList`): List of 2D bounding boxes. b (:py:class:`BBox2DList`): List of 2D bounding boxes. Returns: :py:class:`ndarray`: IoU Matrix """ # We need to add a trailing dimension so that max/min gives us a (N,N) matrix xA = np.maximum(a.x1[:, np.newaxis], b.x1[:, np.newaxis].T) yA = np.maximum(a.y1[:, np.newaxis], b.y1[:, np.newaxis].T) xB = np.minimum(a.x2[:, np.newaxis], b.x2[:, np.newaxis].T) yB = np.minimum(a.y2[:, np.newaxis], b.y2[:, np.newaxis].T) logger.debug("\nmulti_jaccard_index:\nxA\n{0}\nyA\n{1}\nxB\n{2}\nyB\n{3}".format( xA, yA, xB, yB)) inter_w = xB - xA + 1 inter_w[inter_w < 0] = 0 inter_h = yB - yA + 1 inter_h[inter_h < 0] = 0 # maximum generates a (N,N) matrix which consumes a lot of memory # thus we are aggressive about freeing memory up. del xA del yA del xB del yB intersection = inter_w * inter_h logger.debug( "\nmulti_jaccard_index intersection:\n {0}".format(intersection)) a_area = a.width[:, np.newaxis] * a.height[:, np.newaxis] b_area = b.width[:, np.newaxis] * b.height[:, np.newaxis] logger.debug( "\nmulti_jaccard_index:\n a_area:\n {0} \nb_area:\n {1}".format(a_area, b_area)) iou = intersection / (a_area + b_area.T - intersection) # set nan and +/- inf to 0 iou[np.isinf(iou)] = 0 iou[np.isnan(iou)] = 0 return iou
[docs]def iou_3d(a: BBox3D, b: BBox3D): """ Compute the Intersection over Union (IoU) of a pair of 3D bounding boxes. Alias for `jaccard_index_3d`. """ return jaccard_index_3d(a, b)
[docs]def jaccard_index_3d(a: BBox3D, b: BBox3D): """ Compute the Jaccard Index / Intersection over Union (IoU) of a pair of 3D bounding boxes. We compute the IoU using the top-down bird's eye view of the boxes. **Note**: We follow the KITTI format and assume only yaw rotations (along z-axis). Args: a (:py:class:`BBox3D`): 3D bounding box. b (:py:class:`BBox3D`): 3D bounding box. Returns: :py:class:`float`: The IoU of the 2 bounding boxes. """ # check if the two boxes don't overlap if not polygon_collision(a.p[0:4, 0:2], b.p[0:4, 0:2]): return np.round_(0, decimals=5) intersection_points = polygon_intersection(a.p[0:4, 0:2], b.p[0:4, 0:2]) inter_area = polygon_area(intersection_points) zmax = np.minimum(a.cz, b.cz) zmin = np.maximum(a.cz - a.h, b.cz - b.h) inter_vol = inter_area * np.maximum(0, zmax-zmin) a_vol = a.l * a.w * a.h b_vol = b.l * b.w * b.h union_vol = (a_vol + b_vol - inter_vol) iou = inter_vol / union_vol # set nan and +/- inf to 0 if np.isinf(iou) or np.isnan(iou): iou = 0 return np.round_(iou, decimals=5)