Manipulate bounding boxes as objects
Project description
bbox-objected
Mutable bounding boxes for computer vision. Zero required dependencies.
Contents
- Install
- At A Glance
- Quickstart
- Conversion
- Geometry And Attributes
- Editing
- Codecs
- Adapters
- Metrics
- Docs As Tests
At A Glance
AbsBBox <-> RelBBox
| |
| +-- as_abs(img_w, img_h)
+-- as_rel(img_w, img_h)
Codecs: coco | pascal_voc | easyocr | winocr | mss
Adapters: numpy crop | opencv draw
Metrics: IoU | distance | angle | NMS | sort_clockwise
Install
Use uv:
uv add bbox-objected
Optional dependencies
numpy and opencv are optional. Install them manually or use extras:
uv add "bbox-objected[opencv]"(opencv + numpy)uv add "bbox-objected[headless]"(opencv-headless + numpy)uv add "bbox-objected[contrib]"(opencv-contrib + numpy)uv add "bbox-objected[contrib-headless]"(opencv-contrib-headless + numpy)
Note: different
opencvvariants are mutually incompatible.
Quickstart
AbsBBox stores absolute integer coordinates:
from bbox_objected import AbsBBox
# Create a bbox with absolute coordinates.
bbox = AbsBBox(35, 45, 135, 125, text="abs_sample")
assert repr(bbox) == "<AbsBBox(x1=35, y1=45, x2=135, y2=125) - abs_sample>"
assert bbox.as_tuple() == (35, 45, 135, 125)
RelBBox stores relative coordinates in [0.0, 1.0]:
from bbox_objected import RelBBox
# Create a bbox with relative coordinates.
bbox = RelBBox(0.1, 0.5, 0.2, 0.6, text="rel_sample")
assert repr(bbox) == "<RelBBox(x1=0.1, y1=0.5, x2=0.2, y2=0.6) - rel_sample>"
assert bbox.tl == (0.1, 0.5)
Conversion
Convert between absolute and relative forms when image size is known:
from bbox_objected import RelBBox
# Convert between coordinate systems.
bbox = RelBBox(0.1, 0.2, 0.5, 0.6, text="sample")
assert repr(bbox) == "<RelBBox(x1=0.1, y1=0.2, x2=0.5, y2=0.6) - sample>"
assert repr(bbox.as_abs(1920, 1080)) == "<AbsBBox(x1=192, y1=216, x2=960, y2=648) - sample>"
assert bbox.as_abs(1920, 1080).as_rel(1920, 1080) is not bbox
Geometry And Attributes
Each bbox exposes geometry helpers and derived attributes.
Need mutation? Jump to Editing.
from bbox_objected import AbsBBox
# Access coordinate and geometry helpers.
bbox = AbsBBox(40, 40, 60, 60)
assert (bbox.x1, bbox.y1, bbox.x2, bbox.y2) == (40, 40, 60, 60)
assert (bbox.w, bbox.h) == (20, 20)
assert (bbox.tl, bbox.tr, bbox.br, bbox.bl) == ((40, 40), (60, 40), (60, 60), (40, 60))
assert (bbox.center, bbox.area) == ((50.0, 50.0), 400)
assert (bbox.xc, bbox.yc) == (50.0, 50.0)
Editing
Mutate existing boxes in-place for long-lived objects:
Need format conversions? See Codecs.
from bbox_objected import AbsBBox
# Edit coords in-place to avoid new objects.
bbox = AbsBBox(100, 200, 300, 400)
assert repr(bbox) == "<AbsBBox(x1=100, y1=200, x2=300, y2=400)>"
bbox.zero_basis()
assert repr(bbox) == "<AbsBBox(x1=0, y1=0, x2=200, y2=200)>"
bbox.move(25, 45)
assert repr(bbox) == "<AbsBBox(x1=25, y1=45, x2=225, y2=245)>"
other_bbox = AbsBBox(200, 300, 400, 500)
# Expands current box to cover both.
bbox.update_from(other_bbox)
assert repr(bbox) == "<AbsBBox(x1=25, y1=45, x2=400, y2=500)>"
# Replaces current coords with another box.
bbox.replace_from(other_bbox)
assert repr(bbox) == "<AbsBBox(x1=200, y1=300, x2=400, y2=500)>"
Codecs
Use codecs to translate between bbox formats:
Want to crop or draw on images? See Adapters.
from bbox_objected import AbsBBox
from bbox_objected.codecs import coco, easyocr, mss, pascal_voc, winocr
# COCO: x, y, w, h
assert coco.from_abs_bbox(AbsBBox(10, 20, 30, 40)) == (10, 20, 20, 20)
# Pascal VOC: x1, y1, x2, y2
assert pascal_voc.from_abs_bbox(AbsBBox(10, 20, 30, 40)) == (10, 20, 30, 40)
# EasyOCR: tl, tr, br, bl
assert easyocr.from_abs_bbox(AbsBBox(10, 20, 30, 40))[0] == (10, 20)
# WinOCR: mapping
assert winocr.from_abs_bbox(AbsBBox(10, 20, 30, 40))["width"] == 20
# MSS: mapping
assert mss.from_abs_bbox(AbsBBox(10, 20, 30, 40))["height"] == 20
Adapters
Crop using the numpy adapter (requires numpy):
import numpy as np
from bbox_objected import AbsBBox
from bbox_objected.adapters import crop_from_image
# Crop using numpy-backed images.
bbox = AbsBBox(100, 200, 300, 400)
img = np.empty((512, 512, 3), dtype=np.uint8)
cropped = crop_from_image(bbox, img)
assert cropped.shape == (200, 200, 3)
Draw using the OpenCV adapter (requires opencv-python with GUI support):
import cv2
import numpy as np
from bbox_objected import AbsBBox
from bbox_objected.adapters import show_on_image
# Requires a GUI-capable OpenCV build.
bbox = AbsBBox(100, 200, 300, 400)
img = np.empty((512, 512, 3), dtype=np.uint8)
show_on_image(bbox, img, text="sample")
cv2.waitKey(1)
cv2.destroyAllWindows()
Metrics
Compute distance, overlap, and angular relations on absolute boxes:
import pytest
from bbox_objected import get_cos_between, get_distance, get_IoU, sort_clockwise
from bbox_objected.codecs import coco, pascal_voc
bbox_1 = coco.to_abs_bbox((100, 200, 300, 400))
bbox_2 = pascal_voc.to_abs_bbox((100, 400, 200, 800))
assert get_distance(bbox_1, bbox_2) == pytest.approx(223.60679774997897)
assert get_IoU(bbox_1, bbox_2) == pytest.approx(0.14285714285714285)
assert get_cos_between(bbox_1, bbox_2, 450, 350) == pytest.approx(0.9005516363645784)
sorted_boxes = sort_clockwise([bbox_1, bbox_2], 450, 350)
assert len(sorted_boxes) == 2
Non-max suppression works with tuples and optional scores:
from bbox_objected import non_max_suppression
# Picks indices of boxes with higher scores and low overlap.
boxes = [(10, 10, 20, 20), (11, 11, 19, 19), (50, 50, 70, 70)]
picks = non_max_suppression(boxes, thr=0.5, scores=[0.4, 0.9, 0.8])
assert picks == [1, 2]
EasyOCR quad conversion example:
from bbox_objected.codecs import easyocr
bbox = easyocr.to_abs_bbox(((10, 20), (30, 20), (30, 40), (10, 40)))
assert bbox.as_tuple() == (10, 20, 30, 40)
Docs As Tests
README examples are executed as tests via pytest-markdown-docs.
- Run locally:
uv run pytest - The pytest config enables
--markdown-docsby default.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file bbox_objected-1.0.0.tar.gz.
File metadata
- Download URL: bbox_objected-1.0.0.tar.gz
- Upload date:
- Size: 8.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.1 {"installer":{"name":"uv","version":"0.11.1","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
782ae55bb7b6cffb2fccb7b41f141bf2a3f12b6de46ee1c7ed842d9f33a5ffaf
|
|
| MD5 |
7c405b9cfcd8b3eb17a126c7ed28f075
|
|
| BLAKE2b-256 |
04eaf7f25c65fdfbc766f064a9c9f09fd581f4f160d9bb62672b0d5ff5b7f27d
|
File details
Details for the file bbox_objected-1.0.0-py3-none-any.whl.
File metadata
- Download URL: bbox_objected-1.0.0-py3-none-any.whl
- Upload date:
- Size: 15.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.1 {"installer":{"name":"uv","version":"0.11.1","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1c9eb8a5ba2fbceef5799bae2174593de03128be0c85e5819cd3138137a5dfff
|
|
| MD5 |
ac61b35ac8974b61172044a0d91addc0
|
|
| BLAKE2b-256 |
7034eadef6509ac42881a099abdc89139c9ca0db53cc6fd102d5f35653fc4051
|