XmLLM / src /app /geometry /bbox.py
Claude
Code quality: fix all ruff warnings, add CI/CD, improve test coverage
bbbfba8 unverified
Raw
History Blame Contribute Delete
3.56 kB
"""BBox operations — pure functions operating on bbox tuples.
All bboxes follow the canonical convention: (x, y, width, height).
x = left edge, y = top edge, width > 0, height > 0.
"""
from __future__ import annotations
# Type alias for the canonical bbox tuple
BBoxTuple = tuple[float, float, float, float]
def x2(bbox: BBoxTuple) -> float:
"""Right edge of the bbox."""
return bbox[0] + bbox[2]
def y2(bbox: BBoxTuple) -> float:
"""Bottom edge of the bbox."""
return bbox[1] + bbox[3]
def area(bbox: BBoxTuple) -> float:
"""Area of the bbox."""
return bbox[2] * bbox[3]
def center(bbox: BBoxTuple) -> tuple[float, float]:
"""Center point (cx, cy) of the bbox."""
return (bbox[0] + bbox[2] / 2, bbox[1] + bbox[3] / 2)
def contains(outer: BBoxTuple, inner: BBoxTuple, tolerance: float = 0.0) -> bool:
"""Check if *outer* fully contains *inner*, with optional pixel tolerance.
A positive tolerance allows the inner bbox to exceed the outer bbox
by up to that many pixels on each side without failing the check.
"""
return (
inner[0] >= outer[0] - tolerance
and inner[1] >= outer[1] - tolerance
and x2(inner) <= x2(outer) + tolerance
and y2(inner) <= y2(outer) + tolerance
)
def intersects(a: BBoxTuple, b: BBoxTuple) -> bool:
"""Check if two bboxes overlap (share any area)."""
return not (
x2(a) <= b[0]
or x2(b) <= a[0]
or y2(a) <= b[1]
or y2(b) <= a[1]
)
def intersection(a: BBoxTuple, b: BBoxTuple) -> BBoxTuple | None:
"""Compute the intersection bbox, or None if they don't overlap."""
ix = max(a[0], b[0])
iy = max(a[1], b[1])
ix2 = min(x2(a), x2(b))
iy2 = min(y2(a), y2(b))
if ix2 <= ix or iy2 <= iy:
return None
return (ix, iy, ix2 - ix, iy2 - iy)
def union(a: BBoxTuple, b: BBoxTuple) -> BBoxTuple:
"""Compute the smallest bbox enclosing both a and b."""
ux = min(a[0], b[0])
uy = min(a[1], b[1])
ux2 = max(x2(a), x2(b))
uy2 = max(y2(a), y2(b))
return (ux, uy, ux2 - ux, uy2 - uy)
def union_all(bboxes: list[BBoxTuple]) -> BBoxTuple:
"""Compute the smallest bbox enclosing all given bboxes.
Raises ValueError if the list is empty.
"""
if not bboxes:
raise ValueError("Cannot compute union of an empty list of bboxes")
result = bboxes[0]
for b in bboxes[1:]:
result = union(result, b)
return result
def expand(bbox: BBoxTuple, margin: float) -> BBoxTuple:
"""Expand a bbox by *margin* pixels on each side.
The result is clamped so that x and y don't go below 0.
Width and height are always at least 1 pixel after expansion.
"""
new_x = max(0.0, bbox[0] - margin)
new_y = max(0.0, bbox[1] - margin)
new_w = max(1.0, bbox[2] + 2 * margin)
new_h = max(1.0, bbox[3] + 2 * margin)
return (new_x, new_y, new_w, new_h)
def iou(a: BBoxTuple, b: BBoxTuple) -> float:
"""Intersection over Union of two bboxes."""
inter = intersection(a, b)
if inter is None:
return 0.0
inter_area = area(inter)
union_area = area(a) + area(b) - inter_area
if union_area <= 0:
return 0.0
return inter_area / union_area
def overlap_ratio(inner: BBoxTuple, outer: BBoxTuple) -> float:
"""Fraction of *inner*'s area that overlaps with *outer*."""
inter = intersection(inner, outer)
if inter is None:
return 0.0
inner_area = area(inner)
if inner_area <= 0:
return 0.0
return area(inter) / inner_area