# box-diff
Utilities for comparing bounding boxes
Author: Kevin Barnard, [kbarnard@mbari.org](mailto:kbarnard@mbari.org)
[![tests](https://github.com/kevinsbarnard/box-diff/workflows/tests/badge.svg)](https://github.com/kevinsbarnard/box-diff/actions/workflows/tests.yml)
## Installation
box-diff is available on PyPI as [boxdiff](https://pypi.org/project/boxdiff/):
```bash
pip install boxdiff
```
## Usage
All of box-diff is housed within the `boxdiff` package.
```python
import boxdiff
```
The core data models are defined in the `boxdiff.models` module. These are split into three groups:
- `BoundingBox`: ID + labeled 2D bounding box
- `Image`: ID + collection of bounding boxes
- `ImageSet`: ID + collection of images
*Note: IDs may be integers, UUIDs, or strings.*
Each group has a data model (defined in `boxdiff.models.core`), a delta (`boxdiff.models.deltas`), and a difference flag (`boxdiff.models.flags`).
The data model represents the object and its attributes, whereas a delta represents the difference in attribute values between two objects.
Difference flags represent the presence of attribute differences between two objects as derived from a delta object.
All of the data models are serializable to JSON. For example,
```python
json_str = bounding_box.to_json(indent=2)
print(json_str)
```
might give
```json
{
"id": 0,
"label": "label",
"x": 0.0,
"y": 0.0,
"width": 1.0,
"height": 1.0
}
```
Similarly, data model objects may be parsed from JSON. For example,
```python
bounding_box = BoundingBox.from_json(json_str)
print(bounding_box)
# BoundingBox(id=0, label='label', x=0.0, y=0.0, width=1.0, height=1.0)
```
### Bounding Boxes
A `BoundingBox` is defined by an ID, a label, and a 2D box (x, y, width, height).
```python
from boxdiff import BoundingBox
car_box = BoundingBox(
id=0,
label='car',
x=100, y=200,
width=300, height=80
)
```
Equality can be checked using the `==` operator:
```python
same_car_box = BoundingBox(
id=0,
label='car',
x=100, y=200,
width=300, height=80
)
print(car_box == same_car_box)
# True
corrected_car_box = BoundingBox(
id=0,
label='car',
x=90, y=210,
width=320, height=85
)
print(car_box == corrected_car_box)
# False
```
A `BoundingBoxDelta` between two boxes can be computed with the `-` operator:
```python
box_delta = corrected_car_box - car_box
print(box_delta)
# BoundingBoxDelta(id=1, label_old='car', label_new='car', x_delta=-10.0, y_delta=10.0, width_delta=20.0, height_delta=5.0)
```
`BoundingBoxDifference` flags may then be computed from the delta:
```python
print(box_delta.flags)
# BoundingBoxDifference.RESIZED|MOVED
```
Deltas may be applied to a bounding box using the `+` operator:
```python
new_car_box = car_box + box_delta
print(new_car_box)
# BoundingBox(id=1, label='car', x=90.0, y=210.0, width=320.0, height=85.0)
print(new_car_box == corrected_car_box)
# True
```
Area may be computed from a bounding box:
```python
print(car_box.area)
# 24000.0
```
Intersection over union between bounding boxes can be computed using the `iou` method:
```python
car_iou = car_box.iou(corrected_car_box)
print(car_iou)
# 0.695364238410596
```
### Images
An `Image` is defined by an ID and a collection of bounding boxes.
```python
from boxdiff import Image
from uuid import UUID
image = Image(
id=UUID('78d76772-4664-467c-ae88-a25496234966'),
bounding_boxes=[car_box]
)
```
Likewise, equality can be checked using the `==` operator and deltas computed using the `-` operator:
```python
corrected_image = Image(
id=UUID('78d76772-4664-467c-ae88-a25496234966'),
bounding_boxes=[corrected_car_box]
)
image_delta = corrected_image - image
print(image_delta)
# ImageDelta(
# id=0,
# boxes_added=[],
# boxes_removed=[],
# box_deltas=[
# BoundingBoxDelta(
# id=0,
# label_old='car',
# label_new='car',
# x_delta=-10.0,
# y_delta=10.0,
# width_delta=20.0,
# height_delta=5.0
# )
# ]
# )
```
Similarly, flags may be computed from the delta:
```python
print(image_delta.flags)
# ImageDifference.BOXES_MODIFIED
```
### Image Sets
An `ImageSet` is defined by an ID and a collection of images.
```python
from boxdiff import ImageSet
image_set = ImageSet(
id='my_image_set',
images=[image, ...]
)
```
Its syntax and structure is analogous to that of an `Image`.
Raw data
{
"_id": null,
"home_page": "https://github.com/kevinsbarnard/box-diff",
"name": "boxdiff",
"maintainer": "",
"docs_url": null,
"requires_python": ">=3.10,<4.0",
"maintainer_email": "",
"keywords": "",
"author": "Kevin Barnard",
"author_email": "kbarnard@mbari.org",
"download_url": "https://files.pythonhosted.org/packages/5e/4e/3946ae2c95d81034513d8c4e9dde55abb59e0ac0d9770e9d04c9069d368c/boxdiff-0.2.0.tar.gz",
"platform": null,
"description": "# box-diff\nUtilities for comparing bounding boxes\n\nAuthor: Kevin Barnard, [kbarnard@mbari.org](mailto:kbarnard@mbari.org)\n\n[![tests](https://github.com/kevinsbarnard/box-diff/workflows/tests/badge.svg)](https://github.com/kevinsbarnard/box-diff/actions/workflows/tests.yml)\n\n\n## Installation\n\nbox-diff is available on PyPI as [boxdiff](https://pypi.org/project/boxdiff/):\n\n```bash\npip install boxdiff\n```\n\n## Usage\n\nAll of box-diff is housed within the `boxdiff` package.\n\n```python\nimport boxdiff\n```\n\nThe core data models are defined in the `boxdiff.models` module. These are split into three groups:\n- `BoundingBox`: ID + labeled 2D bounding box\n- `Image`: ID + collection of bounding boxes\n- `ImageSet`: ID + collection of images\n\n*Note: IDs may be integers, UUIDs, or strings.*\n\nEach group has a data model (defined in `boxdiff.models.core`), a delta (`boxdiff.models.deltas`), and a difference flag (`boxdiff.models.flags`).\nThe data model represents the object and its attributes, whereas a delta represents the difference in attribute values between two objects.\nDifference flags represent the presence of attribute differences between two objects as derived from a delta object.\n\nAll of the data models are serializable to JSON. For example,\n```python\njson_str = bounding_box.to_json(indent=2)\n\nprint(json_str)\n```\nmight give\n```json\n{\n \"id\": 0,\n \"label\": \"label\",\n \"x\": 0.0,\n \"y\": 0.0,\n \"width\": 1.0,\n \"height\": 1.0\n}\n```\n\nSimilarly, data model objects may be parsed from JSON. For example,\n```python\nbounding_box = BoundingBox.from_json(json_str)\n\nprint(bounding_box)\n# BoundingBox(id=0, label='label', x=0.0, y=0.0, width=1.0, height=1.0)\n```\n\n### Bounding Boxes\n\nA `BoundingBox` is defined by an ID, a label, and a 2D box (x, y, width, height).\n\n```python\nfrom boxdiff import BoundingBox\n\ncar_box = BoundingBox(\n id=0,\n label='car',\n x=100, y=200,\n width=300, height=80\n)\n```\n\nEquality can be checked using the `==` operator:\n\n```python\nsame_car_box = BoundingBox(\n id=0,\n label='car',\n x=100, y=200,\n width=300, height=80\n)\n\nprint(car_box == same_car_box)\n# True\n\ncorrected_car_box = BoundingBox(\n id=0,\n label='car',\n x=90, y=210,\n width=320, height=85\n)\n\nprint(car_box == corrected_car_box)\n# False\n```\n\nA `BoundingBoxDelta` between two boxes can be computed with the `-` operator:\n\n```python\nbox_delta = corrected_car_box - car_box\n\nprint(box_delta)\n# BoundingBoxDelta(id=1, label_old='car', label_new='car', x_delta=-10.0, y_delta=10.0, width_delta=20.0, height_delta=5.0)\n```\n\n`BoundingBoxDifference` flags may then be computed from the delta:\n\n```python\nprint(box_delta.flags)\n# BoundingBoxDifference.RESIZED|MOVED\n```\n\nDeltas may be applied to a bounding box using the `+` operator:\n\n```python\nnew_car_box = car_box + box_delta\n\nprint(new_car_box)\n# BoundingBox(id=1, label='car', x=90.0, y=210.0, width=320.0, height=85.0)\n\nprint(new_car_box == corrected_car_box)\n# True\n```\n\nArea may be computed from a bounding box:\n\n```python\nprint(car_box.area)\n# 24000.0\n```\n\nIntersection over union between bounding boxes can be computed using the `iou` method:\n\n```python\ncar_iou = car_box.iou(corrected_car_box)\n\nprint(car_iou)\n# 0.695364238410596\n```\n\n### Images\n\nAn `Image` is defined by an ID and a collection of bounding boxes.\n\n```python\nfrom boxdiff import Image\nfrom uuid import UUID\n\nimage = Image(\n id=UUID('78d76772-4664-467c-ae88-a25496234966'),\n bounding_boxes=[car_box]\n)\n```\n\nLikewise, equality can be checked using the `==` operator and deltas computed using the `-` operator:\n\n```python\ncorrected_image = Image(\n id=UUID('78d76772-4664-467c-ae88-a25496234966'),\n bounding_boxes=[corrected_car_box]\n)\n\nimage_delta = corrected_image - image\n\nprint(image_delta)\n# ImageDelta(\n# id=0, \n# boxes_added=[], \n# boxes_removed=[], \n# box_deltas=[\n# BoundingBoxDelta(\n# id=0, \n# label_old='car', \n# label_new='car', \n# x_delta=-10.0, \n# y_delta=10.0, \n# width_delta=20.0, \n# height_delta=5.0\n# )\n# ]\n# )\n```\n\nSimilarly, flags may be computed from the delta:\n\n```python\nprint(image_delta.flags)\n# ImageDifference.BOXES_MODIFIED\n```\n\n### Image Sets\n\nAn `ImageSet` is defined by an ID and a collection of images.\n\n```python\nfrom boxdiff import ImageSet\n\nimage_set = ImageSet(\n id='my_image_set',\n images=[image, ...]\n)\n```\n\nIts syntax and structure is analogous to that of an `Image`.\n",
"bugtrack_url": null,
"license": "",
"summary": "Utilities for comparing bounding boxes",
"version": "0.2.0",
"project_urls": {
"Homepage": "https://github.com/kevinsbarnard/box-diff",
"Repository": "https://github.com/kevinsbarnard/box-diff"
},
"split_keywords": [],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "dcbeacbd8b41e0d45cffe37ad341d9ef99464f61018a2aae41f61d8e4db00ab0",
"md5": "52bd56ab23e7bede8b1c3daf15b241b4",
"sha256": "e771da66a7b6c5cbeb7582a5e35eb0a43b814415d86b62fd5aa35c066f40f7ed"
},
"downloads": -1,
"filename": "boxdiff-0.2.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "52bd56ab23e7bede8b1c3daf15b241b4",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.10,<4.0",
"size": 5626,
"upload_time": "2024-02-01T22:19:39",
"upload_time_iso_8601": "2024-02-01T22:19:39.169146Z",
"url": "https://files.pythonhosted.org/packages/dc/be/acbd8b41e0d45cffe37ad341d9ef99464f61018a2aae41f61d8e4db00ab0/boxdiff-0.2.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "5e4e3946ae2c95d81034513d8c4e9dde55abb59e0ac0d9770e9d04c9069d368c",
"md5": "0ea59bca178ee66d700dcf608744f968",
"sha256": "02f1fbead0f9710b4260b71ed69be6b31d066c614efff439b0af6697ce7fef1a"
},
"downloads": -1,
"filename": "boxdiff-0.2.0.tar.gz",
"has_sig": false,
"md5_digest": "0ea59bca178ee66d700dcf608744f968",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.10,<4.0",
"size": 4403,
"upload_time": "2024-02-01T22:19:40",
"upload_time_iso_8601": "2024-02-01T22:19:40.341201Z",
"url": "https://files.pythonhosted.org/packages/5e/4e/3946ae2c95d81034513d8c4e9dde55abb59e0ac0d9770e9d04c9069d368c/boxdiff-0.2.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-02-01 22:19:40",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "kevinsbarnard",
"github_project": "box-diff",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"lcname": "boxdiff"
}