import json
from fastcore.test import test_eq, test_fail, test_warns, ExceptionExpected
Basic objects
pybx
Anchor box coordinates of type list
/dict
/json
/array
can be converted to a Bx
instance. Once wrapped as a Bx
instance, some interesting properties can be calculated from the coordinates.
Bx
Bx (coords, label:list=None)
Interface for all future Bx’s
Initializing an empty Bx
class. It does a whole lot of things!
Generate random coordinates for one anchor boxes.
42)
np.random.seed(= [sorted([np.random.randint(100) for i in range(4)])]
annots annots
[[14, 51, 71, 92]]
If a single list is passed, Bx
will make it a list of list.
0]) Bx(annots[
Bx(coords=[[14, 51, 71, 92]], label=[])
So the correct way to do it would be to pass a list of list.
annots
[[14, 51, 71, 92]]
= Bx(annots)
b b
Bx(coords=[[14, 51, 71, 92]], label=[])
len(b)
1
b.cx
42.5
b.yolo()
(#1) [[42.5, 71.5, 57, 41]]
To get normalized coordinates wrt to the image dimensions.
224, 224, normalize=True) b.yolo(
(#1) [[0.18973214285714285, 0.31919642857142855, 0.2544642857142857, 0.18303571428571427]]
b.values
(#1) [[14, 51, 71, 92]]
Bx
is inherited by all other types in pybx
: BaseBx
, MultiBx
, ListBx
, JsonBx
, exposing the same properties.
BaseBx
works with other types of coordinates too. It accepts the coordinates and label for one anchor box in a list
or ndarray
format.
BaseBx
BaseBx (coords, label:list=None)
BaseBx is the most primitive form of representing a bounding box. Coordinates and label of a bounding box can be wrapped as a BaseBx using: bbx(coords, label)
.
:param coords: can be of type list
or array
representing a single box. - list
can be formatted with label
: [x_min, y_min, x_max, y_max, label]
or without label
: [x_min, y_min, x_max, y_max]
- array
should be a 1-dimensional array of shape (4,)
:param label: a list
or str
that has the class name or label for the object in the corresponding box.
Works with arrays and lists:
BaseBx(annots)
BaseBx(coords=[[14, 51, 71, 92]], label=[])
= BaseBx(annots, 'flower') b
b
BaseBx(coords=[[14, 51, 71, 92]], label=['flower'])
b.coords
[[14, 51, 71, 92]]
Calling the values
attribute returns the labels along with the coordinates.
b.values
(#1) [[14, 51, 71, 92, 'flower']]
BaseBx
also exposes a method to calculate the Intersection Over Union (IOU):
BaseBx.iou
BaseBx.iou (other)
Caclulates the Intersection Over Union (IOU) of the box w.r.t. another BaseBx
. Returns the IOU only if the box is considered valid
.
b
BaseBx(coords=[[14, 51, 71, 92]], label=['flower'])
BaseBx
is also pseudo-iterable (calling an iterator returns self
itself and not the coordinates or labels).
= BaseBx(annots, 'flower') b
next(b)
BaseBx(coords=[[14, 51, 71, 92]], label=['flower'])
for b_ in b:
print(b_)
Working with multiple bounding boxes and annotaions is usually done with the help of MultiBx
. MultiBx
allows iteration.
MultiBx
MultiBx (coords, label:list=None)
MultiBx
represents a collection of bounding boxes as ndarrays. Objects of type MultiBx
can be indexed into, which returns a BaseBx
exposing a suite of box-bound operations. Multiple coordinates and labels of bounding boxes can be wrapped as a MultiBx
using: mbx(coords, label)
. :param coords: can be nested coordinates of type list
of list
s/json
records (list
s of dict
s)/ndarray
s representing multiple boxes. If passing a list/json each index of the object should be of the following formats: - list
can be formatted with label
: [x_min, y_min, x_max, y_max, label]
or without label
: [x_min, y_min, x_max, y_max]
- dict
should be in pascal_voc
format using the keys {“x_min”: 0, “y_min”: 0, “x_max”: 1, “y_max”: 1, “label”: ‘none’} If passing an ndarray
, it should be of shape (N,4)
.
:param label: a list
of str
s that has the class name or label for the object in the corresponding box.
Generate random coordinates:
42)
np.random.seed(= [sorted([np.random.randint(100) for i in range(4)]) for j in range(3)]
annots annots
[[14, 51, 71, 92], [20, 60, 82, 86], [74, 74, 87, 99]]
All annotations are stored as a BaseBx
in a container called MultiBx
= MultiBx(annots, ['apple', 'coke', 'tree'])
bxs bxs
MultiBx(coords=[[14, 51, 71, 92], [20, 60, 82, 86], [74, 74, 87, 99]], label=['apple', 'coke', 'tree'])
Each index reveals the stored coordinate as a BaseBx
0] bxs[
BaseBx(coords=[[14, 51, 71, 92]], label=['apple'])
They can also be iterated:
next(bxs)
BaseBx(coords=[[14, 51, 71, 92]], label=['apple'])
Or using list comprehension, properties of individual boxes can be extracted
for b in bxs] [b.area
[2337, 1612, 325]
0].valid bxs[
True
[True, True]
1].yolo() bxs[
(#1) [[51.0, 73.0, 62, 26]]
0].area bxs[
2337
for b_ in bxs] [b_.area
[1612, 325]
Extending BaseBx
to also accept (json
, dict
) formatted coordinates and labels.
jbx
jbx (coords=None, labels=None, keys=None)
Alias of the JsonBx class to process json
records into MultiBx
or BaseBx
objects exposing many validation methods
Also accepts keys as a list, otherwise uses voc_keys
.
= json.load(open('../data/annots.json'))
annots annots
[{'x_min': 150, 'y_min': 70, 'x_max': 270, 'y_max': 220, 'label': 'clock'},
{'x_min': 10, 'y_min': 180, 'x_max': 115, 'y_max': 260, 'label': 'frame'}]
=voc_keys) jbx(annots, keys
__JsonBx(coords=[[150, 70, 270, 220], [10, 180, 115, 260]], label=['clock', 'frame'])
Also accepts keys (for the dict) as a list, otherwise uses voc_keys
.
voc_keys
['x_min', 'y_min', 'x_max', 'y_max', 'label']
Making MultiBx
work with lists with more than 4 items. It is a common practice to have the class label along with the coordinates. This classmethod is useful in such situations
ITER_TYPES
(numpy.ndarray, list, fastcore.foundation.L)
lbx
lbx (coords=None, labels=None)
Alias of the __ListBx class to process list
into MultiBx
or BaseBx
objects exposing many validation methods
= [[10, 20, 100, 200, 'apple'], [40, 50, 80, 90, 'coke'], ]
annots annots
[[10, 20, 100, 200, 'apple'], [40, 50, 80, 90, 'coke']]
lbx(annots)
__ListBx(coords=[[10, 20, 100, 200], [40, 50, 80, 90]], label=['apple', 'coke'])
0] lbx(annots)[
BaseBx(coords=[[10, 20, 100, 200]], label=['apple'])
Inserting classmethod to process lists and dicts in MultiBx
.
mbx
mbx (coords=None, label=None, keys=None)
Alias of the MultiBx
class.
MultiBx.multibox
MultiBx.multibox (coords, label:list=None, keys:list=None)
Classmethod for MultiBx
. Same as mbx(coords, label). Calls classmethods of JsonBx
and ListBx
based on the type of coords passed.
= annots
annots_list annots_list
[[10, 20, 100, 200, 'apple'], [40, 50, 80, 90, 'coke']]
= json.load(open('../data/annots.json'))
annots_json annots_json
[{'x_min': 150, 'y_min': 70, 'x_max': 270, 'y_max': 220, 'label': 'clock'},
{'x_min': 10, 'y_min': 180, 'x_max': 115, 'y_max': 260, 'label': 'frame'}]
How the class method works:
= explode_types(annots_list) # get all types
t t
{list: [{list: [int, int, int, int, str]}, {list: [int, int, int, int, str]}]}
list][0] # index into the nested list and call the right class t[
{list: [int, int, int, int, str]}
mbx(annots_json)
MultiBx(coords=[[150, 70, 270, 220], [10, 180, 115, 260]], label=['clock', 'frame'])
mbx(annots_list)
MultiBx(coords=[[10, 20, 100, 200], [40, 50, 80, 90]], label=['apple', 'coke'])
0]) mbx(annots_json[
MultiBx(coords=[[150, 70, 270, 220]], label=['clock'])
Checking if it works with ndarrays
42)
np.random.seed(= np.array([sorted([np.random.randint(100) for i in range(4)]) for j in range(3)])
annots annots
array([[14, 51, 71, 92],
[20, 60, 82, 86],
[74, 74, 87, 99]])
mbx(annots)
MultiBx(coords=[[14, 51, 71, 92], [20, 60, 82, 86], [74, 74, 87, 99]], label=[None, None, None])
Allowing BaseBx
to process a single dict
and list
directly.
bbx
bbx (coords=None, labels=None, keys=['x_min', 'y_min', 'x_max', 'y_max', 'label'])
Alias of the BaseBx
class.
BaseBx.basebx
BaseBx.basebx (coords, label:list=None, keys:list=['x_min', 'y_min', 'x_max', 'y_max', 'label'])
Classmethod for BaseBx
. Same as bbx(coords, label), made to work with other object types other than ndarray.
Remember that BaseBx
can only have one box coordinate and label at a time.
annots_list
[[10, 20, 100, 200, 'apple'], [40, 50, 80, 90, 'coke']]
What does make_single_iterable
do? It converts a single list or dict of coordinates into an iterable list that can be used by BaseBx
.
0] annots_list[
[10, 20, 100, 200, 'apple']
0], keys=voc_keys) make_single_iterable(annots_list[
((#1) [[10, 20, 100, 200]], ['apple'])
The class method makes it easier to directly call BaseBx
without making the coordinates a list of list.
0]) bbx(annots_list[
BaseBx(coords=[[10, 20, 100, 200]], label=['apple'])
0][:-1] annots_list[
[10, 20, 100, 200]
0][:-1]) # if label is not passed bbx(annots_list[
BaseBx(coords=[[10, 20, 100, 200]], label=[])
0]) bbx(annots_json[
BaseBx(coords=[[150, 70, 270, 220]], label=['clock'])
get_bx
When in doubt, use get_bx
.
ITER_TYPES
(numpy.ndarray, list, fastcore.foundation.L)
/mnt/data/projects/pybx/.venv/lib/python3.7/site-packages/fastcore/docscrape.py:225: UserWarning: Unknown section Raises
else: warn(msg)
get_bx
get_bx (coords, label=None)
Helper function to check and call the correct type of Bx instance.
Checks for the type of data passed and calls the respective class to generate a Bx instance. Currently only supports ndarray, list, dict, tuple, nested list, nested tuple.
Type | Default | Details | |
---|---|---|---|
coords | ndarray, list, dict, tuple, nested list, nested tuple | Coordinates of anchor boxes. | |
label | NoneType | None | Labels for anchor boxes in order, by default None |
Returns | Bx | An instance of MultiBx, ListBx, BaseBx or JsonBx |
get_bx
runs a bunch of if-else statements to call the right module when in doubt.
annots_json
[{'x_min': 150, 'y_min': 70, 'x_max': 270, 'y_max': 220, 'label': 'clock'},
{'x_min': 10, 'y_min': 180, 'x_max': 115, 'y_max': 260, 'label': 'frame'}]
get_bx(annots_json)
MultiBx(coords=[[150, 70, 270, 220], [10, 180, 115, 260]], label=['clock', 'frame'])
len(annots_json[0])
5
0]]) get_bx([annots_json[
MultiBx(coords=[[150, 70, 270, 220]], label=['clock'])
get_bx(annots_list)
MultiBx(coords=[[10, 20, 100, 200], [40, 50, 80, 90]], label=['apple', 'coke'])
0, 1, 0, 1]) get_bx([
BaseBx(coords=[[0, 1, 0, 1]], label=[])
Enabling stacking of different boxes.
add_bxs
add_bxs (b1, b2)
Alias of stack_bxs().
stack_bxs
stack_bxs (b1, b2)
Method to stack two Bx-types together. Similar to __add__
of BxTypes but avoids UserWarning. :param b1: :param b2: :return:
summary
Type | Details | |
---|---|---|
b1 | Bx, MultiBx | Anchor box coordinates Bx |
b2 | Bx, MultiBx | Anchor box coordinates Bx |
Returns | MultiBx | Stacked anchor box coordinates of MultiBx type. |
b
BaseBx(coords=[[14, 51, 71, 92]], label=['flower'])
Internally this is what is done to stack them:
+ b.coords, bxs.label + b.label bxs.coords
([[14, 51, 71, 92], [20, 60, 82, 86], [74, 74, 87, 99], [14, 51, 71, 92]],
(#4) ['apple','coke','tree','flower'])
+ b bxs
MultiBx(coords=[[14, 51, 71, 92], [20, 60, 82, 86], [74, 74, 87, 99], [14, 51, 71, 92]], label=['apple', 'coke', 'tree', 'flower'])
Adding a MultiBx
to a BaseBx
makes the new set of coordinates a MultiBx
, so a BxViolation
warning is issued if this was not intended.
+ bxs b
/home/gg/data/pybx/.venv/lib/python3.7/site-packages/ipykernel_launcher.py:19: BxViolation: Change of object type imminent if trying to add <class '__main__.BaseBx'>+<class '__main__.MultiBx'>. Use <class '__main__.MultiBx'>+<class '__main__.BaseBx'> instead or basics.stack_bxs().
MultiBx(coords=[[14, 51, 71, 92], [14, 51, 71, 92], [20, 60, 82, 86], [74, 74, 87, 99]], label=['flower', 'apple', 'coke', 'tree'])
stack_bxs(b, bxs)
MultiBx(coords=[[14, 51, 71, 92], [14, 51, 71, 92], [20, 60, 82, 86], [74, 74, 87, 99]], label=['flower', 'apple', 'coke', 'tree'])
To avoid the BxViolation
, use the method stack_bxs
.
stack_bxs(bxs, b)
MultiBx(coords=[[14, 51, 71, 92], [20, 60, 82, 86], [74, 74, 87, 99], [14, 51, 71, 92]], label=['apple', 'coke', 'tree', 'flower'])