kwimage.structs.boxes

Vectorized Bounding Boxes

kwimage.Boxes is a tool for efficiently transporting a set of bounding boxes within python as well as methods for operating on bounding boxes. It is a VERY thin wrapper around a pure numpy/torch array/tensor representation, and thus it is very fast.

Raw bounding boxes come in lots of different formats. There are lots of ways to parameterize two points! Because of this THE USER MUST ALWAYS BE EXPLICIT ABOUT THE BOX FORMAT.

There are 3 main bounding box formats:

xywh: top left xy-coordinates and width height offsets cxywh: center xy-coordinates and width height offsets ltrb: top left and bottom right xy coordinates

Here is some example usage

Example

>>> from kwimage.structs.boxes import Boxes
>>> data = np.array([[ 0,  0, 10, 10],
>>>                  [ 5,  5, 50, 50],
>>>                  [10,  0, 20, 10],
>>>                  [20,  0, 30, 10]])
>>> # Note that the format of raw data is ambiguous, so you must specify
>>> boxes = Boxes(data, 'ltrb')
>>> print('boxes = {!r}'.format(boxes))
boxes = <Boxes(ltrb,
    array([[ 0,  0, 10, 10],
           [ 5,  5, 50, 50],
           [10,  0, 20, 10],
           [20,  0, 30, 10]]))>
>>> # Now you can operate on those boxes easily
>>> print(boxes.translate((10, 10)))
<Boxes(ltrb,
    array([[10., 10., 20., 20.],
           [15., 15., 60., 60.],
           [20., 10., 30., 20.],
           [30., 10., 40., 20.]]))>
>>> print(boxes.to_cxywh())
<Boxes(cxywh,
    array([[ 5. ,  5. , 10. , 10. ],
           [27.5, 27.5, 45. , 45. ],
           [15. ,  5. , 10. , 10. ],
           [25. ,  5. , 10. , 10. ]]))>
>>> print(ub.repr2(boxes.ious(boxes), precision=2, with_dtype=False))
np.array([[1.  , 0.01, 0.  , 0.  ],
          [0.01, 1.  , 0.02, 0.02],
          [0.  , 0.02, 1.  , 0.  ],
          [0.  , 0.02, 0.  , 1.  ]])

Module Contents

Classes

Boxes

Converts boxes between different formats as long as the last dimension

class kwimage.structs.boxes.Boxes(data, format=None, check=True)[source]

Bases: _BoxConversionMixins, _BoxPropertyMixins, _BoxTransformMixins, _BoxDrawMixins, ubelt.NiceRepr

Converts boxes between different formats as long as the last dimension contains 4 coordinates and the format is specified.

This is a convinience class, and should not not store the data for very long. The general idiom should be create class, convert data, and then get the raw data and let the class be garbage collected. This will help ensure that your code is portable and understandable if this class is not available.

Example

>>> # xdoctest: +IGNORE_WHITESPACE
>>> import kwimage
>>> import numpy as np
>>> # Given an array / tensor that represents one or more boxes
>>> data = np.array([[ 0,  0, 10, 10],
>>>                  [ 5,  5, 50, 50],
>>>                  [20,  0, 30, 10]])
>>> # The kwimage.Boxes data structure is a thin fast wrapper
>>> # that provides methods for operating on the boxes.
>>> # It requires that the user explicitly provide a code that denotes
>>> # the format of the boxes (i.e. what each column represents)
>>> boxes = kwimage.Boxes(data, 'ltrb')
>>> # This means that there is no ambiguity about box format
>>> # The representation string of the Boxes object demonstrates this
>>> print('boxes = {!r}'.format(boxes))
boxes = <Boxes(ltrb,
    array([[ 0,  0, 10, 10],
           [ 5,  5, 50, 50],
           [20,  0, 30, 10]]))>
>>> # if you pass this data around. You can convert to other formats
>>> # For docs on available format codes see :class:`BoxFormat`.
>>> # In this example we will convert (left, top, right, bottom)
>>> # to (left-x, top-y, width, height).
>>> boxes.toformat('xywh')
<Boxes(xywh,
    array([[ 0,  0, 10, 10],
           [ 5,  5, 45, 45],
           [20,  0, 10, 10]]))>
>>> # In addition to format conversion there are other operations
>>> # We can quickly (using a C-backend) find IoUs
>>> ious = boxes.ious(boxes)
>>> print('{}'.format(ub.repr2(ious, nl=1, precision=2, with_dtype=False)))
np.array([[1.  , 0.01, 0.  ],
          [0.01, 1.  , 0.02],
          [0.  , 0.02, 1.  ]])
>>> # We can ask for the area of each box
>>> print('boxes.area = {}'.format(ub.repr2(boxes.area, nl=0, with_dtype=False)))
boxes.area = np.array([[ 100],[2025],[ 100]])
>>> # We can ask for the center of each box
>>> print('boxes.center = {}'.format(ub.repr2(boxes.center, nl=1, with_dtype=False)))
boxes.center = (
    np.array([[ 5. ],[27.5],[25. ]]),
    np.array([[ 5. ],[27.5],[ 5. ]]),
)
>>> # We can translate / scale the boxes
>>> boxes.translate((10, 10)).scale(100)
<Boxes(ltrb,
    array([[1000., 1000., 2000., 2000.],
           [1500., 1500., 6000., 6000.],
           [3000., 1000., 4000., 2000.]]))>
>>> # We can clip the bounding boxes
>>> boxes.translate((10, 10)).scale(100).clip(1200, 1200, 1700, 1800)
<Boxes(ltrb,
    array([[1200., 1200., 1700., 1800.],
           [1500., 1500., 1700., 1800.],
           [1700., 1200., 1700., 1800.]]))>
>>> # We can perform arbitrary warping of the boxes
>>> # (note that if the transform is not axis aligned, the axis aligned
>>> #  bounding box of the transform result will be returned)
>>> transform = np.array([[-0.83907153,  0.54402111,  0. ],
>>>                       [-0.54402111, -0.83907153,  0. ],
>>>                       [ 0.        ,  0.        ,  1. ]])
>>> boxes.warp(transform)
<Boxes(ltrb,
    array([[ -8.3907153 , -13.8309264 ,   5.4402111 ,   0.        ],
           [-39.23347095, -69.154632  ,  23.00569785,  -6.9154632 ],
           [-25.1721459 , -24.7113486 , -11.3412195 , -10.8804222 ]]))>
>>> # Note, that we can transform the box to a Polygon for more
>>> # accurate warping.
>>> transform = np.array([[-0.83907153,  0.54402111,  0. ],
>>>                       [-0.54402111, -0.83907153,  0. ],
>>>                       [ 0.        ,  0.        ,  1. ]])
>>> warped_polys = boxes.to_polygons().warp(transform)
>>> print(ub.repr2(warped_polys.data, sv=1))
[
    <Polygon({
        'exterior': <Coords(data=
                        array([[  0.       ,   0.       ],
                               [  5.4402111,  -8.3907153],
                               [ -2.9505042, -13.8309264],
                               [ -8.3907153,  -5.4402111],
                               [  0.       ,   0.       ]]))>,
        'interiors': [],
    })>,
    <Polygon({
        'exterior': <Coords(data=
                        array([[ -1.4752521 ,  -6.9154632 ],
                               [ 23.00569785, -44.67368205],
                               [-14.752521  , -69.154632  ],
                               [-39.23347095, -31.39641315],
                               [ -1.4752521 ,  -6.9154632 ]]))>,
        'interiors': [],
    })>,
    <Polygon({
        'exterior': <Coords(data=
                        array([[-16.7814306, -10.8804222],
                               [-11.3412195, -19.2711375],
                               [-19.7319348, -24.7113486],
                               [-25.1721459, -16.3206333],
                               [-16.7814306, -10.8804222]]))>,
        'interiors': [],
    })>,
]
>>> # The kwimage.Boxes data structure is also convertable to
>>> # several alternative data structures, like shapely, coco, and imgaug.
>>> print(ub.repr2(boxes.to_shapely(), sv=1))
[
    POLYGON ((0 0, 0 10, 10 10, 10 0, 0 0)),
    POLYGON ((5 5, 5 50, 50 50, 50 5, 5 5)),
    POLYGON ((20 0, 20 10, 30 10, 30 0, 20 0)),
]
>>> # xdoctest: +REQUIRES(module:imgaug)
>>> print(ub.repr2(boxes[0:1].to_imgaug(shape=(100, 100)), sv=1))
BoundingBoxesOnImage([BoundingBox(x1=0.0000, y1=0.0000, x2=10.0000, y2=10.0000, label=None)], shape=(100, 100))
>>> # xdoctest: -REQUIRES(module:imgaug)
>>> print(ub.repr2(list(boxes.to_coco()), sv=1))
[
    [0, 0, 10, 10],
    [5, 5, 45, 45],
    [20, 0, 10, 10],
]
>>> # Finally, when you are done with your boxes object, you can
>>> # unwrap the raw data by using the ``.data`` attribute
>>> # all operations are done on this data, which gives the
>>> # kwiamge.Boxes data structure almost no overhead when
>>> # inserted into existing code.
>>> print('boxes.data =\n{}'.format(ub.repr2(boxes.data, nl=1)))
boxes.data =
np.array([[ 0,  0, 10, 10],
          [ 5,  5, 50, 50],
          [20,  0, 30, 10]], dtype=np.int64)
>>> # xdoctest: +REQUIRES(module:torch)
>>> # This data structure was designed for use with both torch
>>> # and numpy, the underlying data can be either an array or tensor.
>>> boxes.tensor()
<Boxes(ltrb,
    tensor([[ 0,  0, 10, 10],
            [ 5,  5, 50, 50],
            [20,  0, 30, 10]]))>
>>> boxes.numpy()
<Boxes(ltrb,
    array([[ 0,  0, 10, 10],
           [ 5,  5, 50, 50],
           [20,  0, 30, 10]]))>

Example

>>> # xdoctest: +IGNORE_WHITESPACE
>>> from kwimage.structs.boxes import *  # NOQA
>>> # Demo of conversion methods
>>> import kwimage
>>> kwimage.Boxes([[25, 30, 15, 10]], 'xywh')
<Boxes(xywh, array([[25, 30, 15, 10]]))>
>>> kwimage.Boxes([[25, 30, 15, 10]], 'xywh').to_xywh()
<Boxes(xywh, array([[25, 30, 15, 10]]))>
>>> kwimage.Boxes([[25, 30, 15, 10]], 'xywh').to_cxywh()
<Boxes(cxywh, array([[32.5, 35. , 15. , 10. ]]))>
>>> kwimage.Boxes([[25, 30, 15, 10]], 'xywh').to_ltrb()
<Boxes(ltrb, array([[25, 30, 40, 40]]))>
>>> kwimage.Boxes([[25, 30, 15, 10]], 'xywh').scale(2).to_ltrb()
<Boxes(ltrb, array([[50., 60., 80., 80.]]))>
>>> # xdoctest: +REQUIRES(module:torch)
>>> kwimage.Boxes(torch.FloatTensor([[25, 30, 15, 20]]), 'xywh').scale(.1).to_ltrb()
<Boxes(ltrb, tensor([[ 2.5000,  3.0000,  4.0000,  5.0000]]))>

Notes

In the following examples we show cases where Boxes can hold a single 1-dimensional box array. This is a holdover from an older codebase, and some functions may assume that the input is at least 2-D. Thus when representing a single bounding box it is best practice to view it as a list of 1 box. While many function will work in the 1-D case, not all functions have been tested and thus we cannot gaurentee correctness.

Example

>>> # xdoctest: +IGNORE_WHITESPACE
>>> Boxes([25, 30, 15, 10], 'xywh')
<Boxes(xywh, array([25, 30, 15, 10]))>
>>> Boxes([25, 30, 15, 10], 'xywh').to_xywh()
<Boxes(xywh, array([25, 30, 15, 10]))>
>>> Boxes([25, 30, 15, 10], 'xywh').to_cxywh()
<Boxes(cxywh, array([32.5, 35. , 15. , 10. ]))>
>>> Boxes([25, 30, 15, 10], 'xywh').to_ltrb()
<Boxes(ltrb, array([25, 30, 40, 40]))>
>>> Boxes([25, 30, 15, 10], 'xywh').scale(2).to_ltrb()
<Boxes(ltrb, array([50., 60., 80., 80.]))>
>>> # xdoctest: +REQUIRES(module:torch)
>>> Boxes(torch.FloatTensor([[25, 30, 15, 20]]), 'xywh').scale(.1).to_ltrb()
<Boxes(ltrb, tensor([[ 2.5000,  3.0000,  4.0000,  5.0000]]))>

Example

>>> datas = [
>>>     [1, 2, 3, 4],
>>>     [[1, 2, 3, 4], [4, 5, 6, 7]],
>>>     [[[1, 2, 3, 4], [4, 5, 6, 7]]],
>>> ]
>>> formats = BoxFormat.cannonical
>>> for format1 in formats:
>>>     for data in datas:
>>>         self = box1 = Boxes(data, format1)
>>>         for format2 in formats:
>>>             box2 = box1.toformat(format2)
>>>             back = box2.toformat(format1)
>>>             assert box1 == back
__getitem__(self, index)[source]
__eq__(self, other)[source]

Tests equality of two Boxes objects

Example

>>> box0 = box1 = Boxes([[1, 2, 3, 4]], 'xywh')
>>> box2 = Boxes(box0.data, 'ltrb')
>>> box3 = Boxes([[0, 2, 3, 4]], box0.format)
>>> box4 = Boxes(box0.data, box2.format)
>>> assert box0 == box1
>>> assert not box0 == box2
>>> assert not box2 == box3
>>> assert box2 == box4
__len__(self)[source]
__nice__(self)[source]
__repr__(self)[source]

Return repr(self).

classmethod random(Boxes, num=1, scale=1.0, format=BoxFormat.XYWH, anchors=None, anchor_std=1.0 / 6, tensor=False, rng=None)[source]

Makes random boxes; typically for testing purposes

Parameters
  • num (int) – number of boxes to generate

  • scale (float | Tuple[float, float]) – size of imgdims

  • format (str) – format of boxes to be created (e.g. ltrb, xywh)

  • anchors (ndarray) – normalized width / heights of anchor boxes to perterb and randomly place. (must be in range 0-1)

  • anchor_std (float) – magnitude of noise applied to anchor shapes

  • tensor (bool) – if True, returns boxes in tensor format

  • rng (None | int | RandomState) – initial random seed

Example

>>> # xdoctest: +IGNORE_WHITESPACE
>>> Boxes.random(3, rng=0, scale=100)
<Boxes(xywh,
    array([[54, 54,  6, 17],
           [42, 64,  1, 25],
           [79, 38, 17, 14]]))>
>>> # xdoctest: +REQUIRES(module:torch)
>>> Boxes.random(3, rng=0, scale=100).tensor()
<Boxes(xywh,
    tensor([[ 54,  54,   6,  17],
            [ 42,  64,   1,  25],
            [ 79,  38,  17,  14]]))>
>>> anchors = np.array([[.5, .5], [.3, .3]])
>>> Boxes.random(3, rng=0, scale=100, anchors=anchors)
<Boxes(xywh,
    array([[ 2, 13, 51, 51],
           [32, 51, 32, 36],
           [36, 28, 23, 26]]))>

Example

>>> # Boxes position/shape within 0-1 space should be uniform.
>>> # xdoctest: +REQUIRES(--show)
>>> import kwplot
>>> kwplot.autompl()
>>> fig = kwplot.figure(fnum=1, doclf=True)
>>> fig.gca().set_xlim(0, 128)
>>> fig.gca().set_ylim(0, 128)
>>> import kwimage
>>> kwimage.Boxes.random(num=10).scale(128).draw()
copy(self)[source]
classmethod concatenate(cls, boxes, axis=0)[source]

Concatenates multiple boxes together

Parameters
  • boxes (Sequence[Boxes]) – list of boxes to concatenate

  • axis (int, default=0) – axis to stack on

Returns

stacked boxes

Return type

Boxes

Example

>>> boxes = [Boxes.random(3) for _ in range(3)]
>>> new = Boxes.concatenate(boxes)
>>> assert len(new) == 9
>>> assert np.all(new.data[3:6] == boxes[1].data)

Example

>>> boxes = [Boxes.random(3) for _ in range(3)]
>>> boxes[0].data = boxes[0].data[0]
>>> boxes[1].data = boxes[0].data[0:0]
>>> new = Boxes.concatenate(boxes)
>>> assert len(new) == 4
>>> # xdoctest: +REQUIRES(module:torch)
>>> new = Boxes.concatenate([b.tensor() for b in boxes])
>>> assert len(new) == 4
compress(self, flags, axis=0, inplace=False)[source]

Filters boxes based on a boolean criterion

Parameters
  • flags (ArrayLike[bool]) – true for items to be kept

  • axis (int) – you usually want this to be 0

  • inplace (bool) – if True, modifies this object

Example

>>> self = Boxes([[25, 30, 15, 10]], 'ltrb')
>>> self.compress([True])
<Boxes(ltrb, array([[25, 30, 15, 10]]))>
>>> self.compress([False])
<Boxes(ltrb, array([], shape=(0, 4), dtype=int64))>
take(self, idxs, axis=0, inplace=False)[source]

Takes a subset of items at specific indices

Parameters
  • indices (ArrayLike[int]) – indexes of items to take

  • axis (int) – you usually want this to be 0

  • inplace (bool) – if True, modifies this object

Example

>>> self = Boxes([[25, 30, 15, 10]], 'ltrb')
>>> self.take([0])
<Boxes(ltrb, array([[25, 30, 15, 10]]))>
>>> self.take([])
<Boxes(ltrb, array([], shape=(0, 4), dtype=int64))>
is_tensor(self)[source]

is the backend fueled by torch?

is_numpy(self)[source]

is the backend fueled by numpy?

_impl(self)[source]

returns the kwarray.ArrayAPI implementation for the data

Example

>>> assert Boxes.random().numpy()._impl.is_numpy
>>> # xdoctest: +REQUIRES(module:torch)
>>> assert Boxes.random().tensor()._impl.is_tensor
property device(self)[source]

If the backend is torch returns the data device, otherwise None

astype(self, dtype)[source]

Changes the type of the internal array used to represent the boxes

Notes

this operation is not inplace

Example

>>> # xdoctest: +IGNORE_WHITESPACE
>>> # xdoctest: +REQUIRES(module:torch)
>>> Boxes.random(3, 100, rng=0).tensor().astype('int32')
<Boxes(xywh,
    tensor([[54, 54,  6, 17],
            [42, 64,  1, 25],
            [79, 38, 17, 14]], dtype=torch.int32))>
>>> Boxes.random(3, 100, rng=0).numpy().astype('int32')
<Boxes(xywh,
    array([[54, 54,  6, 17],
           [42, 64,  1, 25],
           [79, 38, 17, 14]], dtype=int32))>
>>> Boxes.random(3, 100, rng=0).tensor().astype('float32')
>>> Boxes.random(3, 100, rng=0).numpy().astype('float32')
round(self, inplace=False)[source]

Rounds data coordinates to the nearest integer.

This operation is applied directly to the box coordinates, so its output will depend on the format the boxes are stored in.

Parameters

inplace (bool, default=False) – if True, modifies this object

SeeAlso:

:method:`Boxes.quantize`

Example

>>> import kwimage
>>> self = kwimage.Boxes.random(3, rng=0).scale(10)
>>> new = self.round()
>>> print('self = {!r}'.format(self))
>>> print('new = {!r}'.format(new))
self = <Boxes(xywh,
    array([[5.48813522, 5.44883192, 0.53949833, 1.70306146],
           [4.23654795, 6.4589411 , 0.13932407, 2.45878875],
           [7.91725039, 3.83441508, 1.71937704, 1.45453393]]))>
new = <Boxes(xywh,
    array([[5., 5., 1., 2.],
           [4., 6., 0., 2.],
           [8., 4., 2., 1.]]))>
quantize(self, inplace=False, dtype=np.int32)[source]

Converts the box to integer coordinates.

This operation takes the floor of the left side and the ceil of the right side. Thus the area of the box will never decreases.

Parameters
  • inplace (bool, default=False) – if True, modifies this object

  • dtype (type) – type to cast as

SeeAlso:

:method:`Boxes.round`

Example

>>> import kwimage
>>> self = kwimage.Boxes.random(3, rng=0).scale(10)
>>> new = self.quantize()
>>> print('self = {!r}'.format(self))
>>> print('new = {!r}'.format(new))
self = <Boxes(xywh,
    array([[5.48813522, 5.44883192, 0.53949833, 1.70306146],
           [4.23654795, 6.4589411 , 0.13932407, 2.45878875],
           [7.91725039, 3.83441508, 1.71937704, 1.45453393]]))>
new = <Boxes(xywh,
    array([[5, 5, 2, 3],
           [4, 6, 1, 3],
           [7, 3, 3, 3]], dtype=int32))>

Example

>>> import kwimage
>>> self = kwimage.Boxes.random(3, rng=0)
>>> orig = self.copy()
>>> self.quantize(inplace=True)
>>> assert np.any(self.data != orig.data)
numpy(self)[source]

Converts tensors to numpy. Does not change memory if possible.

Example

>>> # xdoctest: +REQUIRES(module:torch)
>>> self = Boxes.random(3).tensor()
>>> newself = self.numpy()
>>> self.data[0, 0] = 0
>>> assert newself.data[0, 0] == 0
>>> self.data[0, 0] = 1
>>> assert self.data[0, 0] == 1
tensor(self, device=ub.NoParam)[source]

Converts numpy to tensors. Does not change memory if possible.

Example

>>> # xdoctest: +REQUIRES(module:torch)
>>> self = Boxes.random(3)
>>> # xdoctest: +REQUIRES(module:torch)
>>> newself = self.tensor()
>>> self.data[0, 0] = 0
>>> assert newself.data[0, 0] == 0
>>> self.data[0, 0] = 1
>>> assert self.data[0, 0] == 1
ious(self, other, bias=0, impl='auto', mode=None)[source]

Intersection over union.

Compute IOUs (intersection area over union area) between these boxes and another set of boxes. This is a symmetric measure of similarity between boxes.

Todo

  • [ ] Add pairwise flag to toggle between one-vs-one and all-vs-all

    computation. I.E. Add option for componentwise calculation.

Parameters
  • other (Boxes) – boxes to compare IoUs against

  • bias (int, default=0) – either 0 or 1, does TL=BR have area of 0 or 1?

  • impl (str, default=’auto’) – code to specify implementation used to ious. Can be either torch, py, c, or auto. Efficiency and the exact result will vary by implementation, but they will always be close. Some implementations only accept certain data types (e.g. impl=’c’, only accepts float32 numpy arrays). See ~/code/kwimage/dev/bench_bbox.py for benchmark details. On my system the torch impl was fastest (when the data was on the GPU).

  • mode – depricated, use impl

SeeAlso:

iooas - for a measure of coverage between boxes

Examples

>>> import kwimage
>>> self = kwimage.Boxes(np.array([[ 0,  0, 10, 10],
>>>                                [10,  0, 20, 10],
>>>                                [20,  0, 30, 10]]), 'ltrb')
>>> other = kwimage.Boxes(np.array([6, 2, 20, 10]), 'ltrb')
>>> overlaps = self.ious(other, bias=1).round(2)
>>> assert np.all(np.isclose(overlaps, [0.21, 0.63, 0.04])), repr(overlaps)

Examples

>>> import kwimage
>>> boxes1 = kwimage.Boxes(np.array([[ 0,  0, 10, 10],
>>>                                  [10,  0, 20, 10],
>>>                                  [20,  0, 30, 10]]), 'ltrb')
>>> other = kwimage.Boxes(np.array([[6, 2, 20, 10],
>>>                                 [100, 200, 300, 300]]), 'ltrb')
>>> overlaps = boxes1.ious(other)
>>> print('{}'.format(ub.repr2(overlaps, precision=2, nl=1)))
np.array([[0.18, 0.  ],
          [0.61, 0.  ],
          [0.  , 0.  ]]...)

Examples

>>> # xdoctest: +IGNORE_WHITESPACE
>>> Boxes(np.empty(0), 'xywh').ious(Boxes(np.empty(4), 'xywh')).shape
(0,)
>>> #Boxes(np.empty(4), 'xywh').ious(Boxes(np.empty(0), 'xywh')).shape
>>> Boxes(np.empty((0, 4)), 'xywh').ious(Boxes(np.empty((0, 4)), 'xywh')).shape
(0, 0)
>>> Boxes(np.empty((1, 4)), 'xywh').ious(Boxes(np.empty((0, 4)), 'xywh')).shape
(1, 0)
>>> Boxes(np.empty((0, 4)), 'xywh').ious(Boxes(np.empty((1, 4)), 'xywh')).shape
(0, 1)

Examples

>>> # xdoctest: +REQUIRES(module:torch)
>>> formats = BoxFormat.cannonical
>>> istensors = [False, True]
>>> results = {}
>>> for format in formats:
>>>     for tensor in istensors:
>>>         boxes1 = Boxes.random(5, scale=10.0, rng=0, format=format, tensor=tensor)
>>>         boxes2 = Boxes.random(7, scale=10.0, rng=1, format=format, tensor=tensor)
>>>         ious = boxes1.ious(boxes2)
>>>         results[(format, tensor)] = ious
>>> results = {k: v.numpy() if torch.is_tensor(v) else v for k, v in results.items() }
>>> results = {k: v.tolist() for k, v in results.items()}
>>> print(ub.repr2(results, sk=True, precision=3, nl=2))
>>> from functools import partial
>>> assert ub.allsame(results.values(), partial(np.allclose, atol=1e-07))
Ignore:
>>> # does this work with backprop?
>>> # xdoctest: +REQUIRES(module:torch)
>>> import torch
>>> import kwimage
>>> num = 1000
>>> true_boxes = kwimage.Boxes.random(num).tensor()
>>> inputs = torch.rand(num, 10)
>>> regress = torch.nn.Linear(10, 4)
>>> energy = regress(inputs)
>>> energy.retain_grad()
>>> outputs = energy.sigmoid()
>>> outputs.retain_grad()
>>> out_boxes = kwimage.Boxes(outputs, 'cxywh')
>>> ious = out_boxes.ious(true_boxes)
>>> loss = ious.sum()
>>> loss.backward()
iooas(self, other, bias=0)[source]

Intersection over other area.

This is an asymetric measure of coverage. How much of the “other” boxes are covered by these boxes. It is the area of intersection between each pair of boxes and the area of the “other” boxes.

SeeAlso:

ious - for a measure of similarity between boxes

Parameters
  • other (Boxes) – boxes to compare IoOA against

  • bias (int, default=0) – either 0 or 1, does TL=BR have area of 0 or 1?

Examples

>>> self = Boxes(np.array([[ 0,  0, 10, 10],
>>>                        [10,  0, 20, 10],
>>>                        [20,  0, 30, 10]]), 'ltrb')
>>> other = Boxes(np.array([[6, 2, 20, 10], [0, 0, 0, 3]]), 'xywh')
>>> coverage = self.iooas(other, bias=0).round(2)
>>> print('coverage = {!r}'.format(coverage))
isect_area(self, other, bias=0)[source]

Intersection part of intersection over union computation

Examples

>>> # xdoctest: +IGNORE_WHITESPACE
>>> self = Boxes.random(5, scale=10.0, rng=0, format='ltrb')
>>> other = Boxes.random(3, scale=10.0, rng=1, format='ltrb')
>>> isect = self.isect_area(other, bias=0)
>>> ious_v1 = isect / ((self.area + other.area.T) - isect)
>>> ious_v2 = self.ious(other, bias=0)
>>> assert np.allclose(ious_v1, ious_v2)
intersection(self, other)[source]

Componentwise intersection between two sets of Boxes

intersections of boxes are always boxes, so this works

Returns

intersected boxes

Return type

Boxes

Examples

>>> # xdoctest: +IGNORE_WHITESPACE
>>> from kwimage.structs.boxes import *  # NOQA
>>> self = Boxes.random(5, rng=0).scale(10.)
>>> other = self.translate(1)
>>> new = self.intersection(other)
>>> new_area = np.nan_to_num(new.area).ravel()
>>> alt_area = np.diag(self.isect_area(other))
>>> close = np.isclose(new_area, alt_area)
>>> assert np.all(close)
union_hull(self, other)[source]

Componentwise hull union between two sets of Boxes

NOTE: convert to polygon to do a real union.

Returns

unioned boxes

Return type

Boxes

Examples

>>> # xdoctest: +IGNORE_WHITESPACE
>>> from kwimage.structs.boxes import *  # NOQA
>>> self = Boxes.random(5, rng=0).scale(10.)
>>> other = self.translate(1)
>>> new = self.union_hull(other)
>>> new_area = np.nan_to_num(new.area).ravel()
bounding_box(self)[source]

Returns the box that bounds all of the contained boxes

Returns

a single box

Return type

Boxes

Examples

>>> # xdoctest: +IGNORE_WHITESPACE
>>> from kwimage.structs.boxes import *  # NOQA
>>> self = Boxes.random(5, rng=0).scale(10.)
>>> other = self.translate(1)
>>> new = self.union_hull(other)
>>> new_area = np.nan_to_num(new.area).ravel()
contains(self, other)[source]

Determine of points are completely contained by these boxes

Parameters

other (Points) – points to test for containment. TODO: support generic data types

Returns

N x M boolean matrix indicating which box

contains which points, where N is the number of boxes and M is the number of points.

Return type

flags (ArrayLike)

Examples

>>> import kwimage
>>> self = kwimage.Boxes.random(10).scale(10).round()
>>> other = kwimage.Points.random(10).scale(10).round()
>>> flags = self.contains(other)
>>> flags = self.contains(self.xy_center)
>>> assert np.all(np.diag(flags))
view(self, *shape)[source]

Passthrough method to view or reshape

Example

>>> # xdoctest: +REQUIRES(module:torch)
>>> self = Boxes.random(6, scale=10.0, rng=0, format='xywh').tensor()
>>> assert list(self.view(3, 2, 4).data.shape) == [3, 2, 4]
>>> self = Boxes.random(6, scale=10.0, rng=0, format='ltrb').tensor()
>>> assert list(self.view(3, 2, 4).data.shape) == [3, 2, 4]