Moving iou_with_jaccard and jaccard from mean_average_precision.py to detection/utils.py

This commit is contained in:
rafaelpadilla committed 2025-05-08 01:46:46 +00:00
1 parent 19c4e44e2c
commit 8f72e49080
2 files changed
+76 -72

No files matched your search

+70
View File
@@ -1325,3 +1325,73 @@ def spread_out_boxes(
xyxy_padded[:, [2, 3]] += force_vectors
return pad_boxes(xyxy_padded, px=-1)
def _jaccard(box_a: List[float], box_b: List[float], is_crowd: bool) -> float:
"""
Calculate the Jaccard index (intersection over union) between two bounding boxes.
If a gt object is marked as "iscrowd", a dt is allowed to match any subregion
of the gt. Choosing gt' in the crowd gt that best matches the dt can be done using
gt'=intersect(dt,gt). Since by definition union(gt',dt)=dt, computing
iou(gt,dt,iscrowd) = iou(gt',dt) = area(intersect(gt,dt)) / area(dt)
Args:
box_a (List[float]): Box coordinates in the format [x, y, width, height].
box_b (List[float]): Box coordinates in the format [x, y, width, height].
iscrowd (bool): Flag indicating if the second box is a crowd region or not.
Returns:
float: Jaccard index between the two bounding boxes.
"""
# Smallest number to avoid division by zero
EPS = np.spacing(1)
xa, ya, x2a, y2a = box_a[0], box_a[1], box_a[0] + box_a[2], box_a[1] + box_a[3]
xb, yb, x2b, y2b = box_b[0], box_b[1], box_b[0] + box_b[2], box_b[1] + box_b[3]
# Innermost left x
xi = max(xa, xb)
# Innermost right x
x2i = min(x2a, x2b)
# Same for y
yi = max(ya, yb)
y2i = min(y2a, y2b)
# Calculate areas
Aa = max(x2a - xa, 0.0) * max(y2a - ya, 0.0)
Ab = max(x2b - xb, 0.0) * max(y2b - yb, 0.0)
Ai = max(x2i - xi, 0.0) * max(y2i - yi, 0.0)
if is_crowd:
return Ai / (Aa + EPS)
return Ai / (Aa + Ab - Ai + EPS)
def iou_with_jaccard(
dt: List[List[float]], gt: List[List[float]], is_crowd: List[bool]
) -> np.ndarray:
"""
Calculate the intersection over union (IoU) between detection bounding boxes (dt)
and ground-truth bounding boxes (gt).
Reference: https://github.com/rafaelpadilla/review_object_detection_metrics
Args:
dt (List[List[float]]): List of detection bounding boxes in the \
format [x, y, width, height].
gt (List[List[float]]): List of ground-truth bounding boxes in the \
format [x, y, width, height].
is_crowd (List[bool]): List indicating if each ground-truth bounding box \
is a crowd region or not.
Returns:
np.ndarray: Array of IoU values of shape (len(dt), len(gt)).
"""
assert len(is_crowd) == len(gt), "iou(iscrowd=) must have the same length as gt"
if len(dt) == 0 or len(gt) == 0:
return np.array([])
ious = np.zeros((len(dt), len(gt)), dtype=np.float64)
for g_idx, g in enumerate(gt):
for d_idx, d in enumerate(dt):
ious[d_idx, g_idx] = _jaccard(d, g, is_crowd[g_idx])
return ious
+6 -72
View File
@@ -16,6 +16,7 @@ from supervision.detection.core import Detections
from supervision.draw.color import LEGACY_COLOR_PALETTE
from supervision.metrics.core import Metric, MetricTarget
from supervision.metrics.utils.utils import ensure_pandas_installed
from supervision.detection.utils import iou_with_jaccard
if TYPE_CHECKING:
import pandas as pd
@@ -99,10 +100,10 @@ class MeanAveragePrecisionResult:
f"maxDets=100 ] = {self.map75:.3f}\n"
f"Average Precision (AP) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] "
f"= {self.small_objects.map50_95:.3f}\n"
f"Average Precision (AP) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] "
f"= {self.medium_objects.map50_95:.3f}\n"
f"Average Precision (AP) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] "
f"= {self.large_objects.map50_95:.3f}"
f"Average Precision (AP) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] \
= {self.medium_objects.map50_95:.3f}\n"
f"Average Precision (AP) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] \
= {self.large_objects.map50_95:.3f}"
)
def to_pandas(self) -> "pd.DataFrame":
@@ -485,73 +486,6 @@ class ObjectSize(Enum):
LARGE = "large"
def _iou_with_jaccard(
dt: List[List[float]], gt: List[List[float]], is_crowd: List[bool]
) -> np.ndarray:
"""
Calculate the intersection over union (IoU) between detection bounding boxes (dt)
and ground-truth bounding boxes (gt).
Reference: https://github.com/rafaelpadilla/review_object_detection_metrics
Args:
dt (List[List[float]]): List of detection bounding boxes in the \
format [x, y, width, height].
gt (List[List[float]]): List of ground-truth bounding boxes in the \
format [x, y, width, height].
is_crowd (List[bool]): List indicating if each ground-truth bounding box \
is a crowd region or not.
Returns:
np.ndarray: Array of IoU values of shape (len(dt), len(gt)).
"""
assert len(is_crowd) == len(gt), "iou(iscrowd=) must have the same length as gt"
if len(dt) == 0 or len(gt) == 0:
return np.array([])
ious = np.zeros((len(dt), len(gt)), dtype=np.float64)
for g_idx, g in enumerate(gt):
for d_idx, d in enumerate(dt):
ious[d_idx, g_idx] = _jaccard(d, g, is_crowd[g_idx])
return ious
def _jaccard(box_a: List[float], box_b: List[float], is_crowd: bool) -> float:
"""
Calculate the Jaccard index (intersection over union) between two bounding boxes.
If a gt object is marked as "iscrowd", a dt is allowed to match any subregion
of the gt. Choosing gt' in the crowd gt that best matches the dt can be done using
gt'=intersect(dt,gt). Since by definition union(gt',dt)=dt, computing
iou(gt,dt,iscrowd) = iou(gt',dt) = area(intersect(gt,dt)) / area(dt)
Args:
box_a (List[float]): Box coordinates in the format [x, y, width, height].
box_b (List[float]): Box coordinates in the format [x, y, width, height].
iscrowd (bool): Flag indicating if the second box is a crowd region or not.
Returns:
float: Jaccard index between the two bounding boxes.
"""
xa, ya, x2a, y2a = box_a[0], box_a[1], box_a[0] + box_a[2], box_a[1] + box_a[3]
xb, yb, x2b, y2b = box_b[0], box_b[1], box_b[0] + box_b[2], box_b[1] + box_b[3]
# Innermost left x
xi = max(xa, xb)
# Innermost right x
x2i = min(x2a, x2b)
# Same for y
yi = max(ya, yb)
y2i = min(y2a, y2b)
# Calculate areas
Aa = max(x2a - xa, 0.0) * max(y2a - ya, 0.0)
Ab = max(x2b - xb, 0.0) * max(y2b - yb, 0.0)
Ai = max(x2i - xi, 0.0) * max(y2i - yi, 0.0)
if is_crowd:
return Ai / (Aa + EPS)
return Ai / (Aa + Ab - Ai + EPS)
class COCOEvaluatorParameters:
"""
Parameters for COCOEvaluator
@@ -689,7 +623,7 @@ class COCOEvaluator:
# Get the iscrowd flag for each gt
is_crowd = [int(o["iscrowd"]) for o in gt]
# Compute iou between each prediction a and gt region
iou = _iou_with_jaccard(dt_boxes, gt_boxes, is_crowd)
iou = iou_with_jaccard(dt_boxes, gt_boxes, is_crowd)
return iou
def _evaluate_image(