Source code for box

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from __future__ import division
from __future__ import print_function

import operator
from functools import wraps

__author__ = ['Mattia Ceccarelli', 'Nico Curti']
__email__ = ['mattia.ceccarelli3@studio.unibo.it', 'nico.curti2@unibo.it']


[docs]class Box (object): ''' Detection box class Parameters ---------- coords : tuple (default=None) Box Coordinates as (x, y, w, h) Example ------- >>> import pylab as plt >>> from matplotlib.patches import Rectangle >>> >>> b1 = Box((.5, .3, .2, .1)) >>> x_1, y_1, w_1, h_1 = b1.box >>> left_1, top_1, right_1, bottom_1 = b1.coords >>> >>> print('Box1: {}'.format(b1)) >>> >>> b2 = Box((.4, .5, .2, .5)) >>> x_2, y_2, w_2, h_2 = b2.box >>> left_2, top_2, right_2, bottom_2 = b2.coords >>> >>> print('Box2: {}'.format(b2)) >>> >>> print('Intersection: {:.3f}'.format(b1.intersection(b2))) >>> print('Union: {:.3f}'.format(b1.union(b2))) >>> print('IOU: {:.3f}'.format(b1.iou(b2))) >>> print('rmse: {:.3f}'.format(b1.rmse(b2))) >>> >>> plt.figure() >>> axis = plt.gca() >>> axis.add_patch(Rectangle(xy=(left_1, top_1), >>> width=w_1, height=h_1, >>> alpha=.5, linewidth=2, color='blue')) >>> axis.add_patch(Rectangle(xy=(left_2, top_2), >>> width=w_2, height=h_2, >>> alpha=.5, linewidth=2, color='red')) ''' def __init__ (self, coords=None): if coords is not None: try: self.x, self.y, self.w, self.h = coords except ValueError: class_name = self.__class__.__name__ raise ValueError('{0}: inconsistent input shape. Expected a 4D (x, y, w, h) shapes and given {1}'.format(class_name, coords)) else: self.x, self.y, self.w, self.h = (None, None, None, None) def _is_box (func): ''' Decorator function to check if the input variable is a Box object ''' @wraps(func) def _ (self, b): if isinstance(b, self.__class__): return func(self, b) else: raise ValueError('Box functions can be applied only on other Box objects') return _ @property def box(self): ''' Get the box coordinates Returns ------- coords : tuple Box coordinates as (x, y, w, h) ''' return (self.x, self.y, self.w, self.h) def __iter__ (self): ''' Iter over coordinates as (x, y, w, h) ''' yield self.x yield self.y yield self.w yield self.h def __eq__ (self, other): ''' Check if the box coordinates are equal ''' return isinstance(other, Box) and tuple(self) == tuple(other) def __ne__ (self, other): ''' Check if the box coordinates are NOT equal ''' return not (self == other) def __repr__ (self): ''' Object representation ''' return type(self).__name__ + repr(tuple(self)) def _overlap (self, x1, w1, x2, w2): ''' Compute the overlap between (left, top) | (right, bottom) of the coordinates Parameters ---------- x1 : float X coordinate w1 : float W coordinate x2 : float w2 : float Returns ------- overlap : float The overlapping are between the two boxes ''' half_w1, half_w2 = w1 * .5, w2 * .5 l1, l2 = x1 - half_w1, x2 - half_w2 r1, r2 = x1 + half_w1, x2 + half_w2 return min(r1, r2) - max(l1, l2)
[docs] @_is_box def intersection (self, other): ''' Common area between boxes Parameters ---------- other : Box 2nd term of the evaluation Returns ------- intersection : float Intersection area of two boxes ''' w = self._overlap(self.x, self.w, other.x, other.w) h = self._overlap(self.y, self.h, other.y, other.h) w = w if w > 0. else 0. h = h if h > 0. else 0. return w * h
__and__ = intersection
[docs] @_is_box def union (self, other): ''' Full area without intersection Parameters ---------- other : Box 2nd term of the evaluation Returns ------- union : float Union area of the two boxes ''' return self.area + other.area - self.intersection(other)
__add__ = union
[docs] @_is_box def iou (self, other): ''' Intersection over union Parameters ---------- other : Box 2nd term of the evaluation Returns ------- iou : float Intersection over union between boxes ''' union = self.union(other) return self.intersection(other) / union if union != 0. else float('nan')
__sub__ = iou
[docs] @_is_box def rmse (self, other): ''' Root mean square error of the boxes Parameters ---------- other : Box 2nd term of the evaluation Returns ------- rmse : float Root mean square error of the boxes ''' diffs = tuple(map(operator.sub, self, other)) dot = sum(map(operator.mul, diffs, diffs)) return dot**(.5)
@property def center(self): ''' In the current storage the x,y are the center of the box Returns ------- center : tuple Center of the current box. ''' x, y, _, _ = self._object.box return (x, y) @property def dimensions(self): ''' In the current storage the w,h are the dimensions of the rectangular box Returns ------- dims : tuple Dimensions of the current box as (width, height). ''' _, _, w, h = self._object.box return (w, h) @property def area(self): ''' Compute the are of the box Returns ------- area : float Area of the current box. ''' return self.w * self.h @property def coords(self): ''' Return box coordinates in clock order (left, top, right, bottom) Returns ------- coords : tuple Coordinates as (left, top, right, bottom) ''' x, y, w, h = self.box half_w, half_h = w * .5, h * .5 return (x - half_w, y - half_h, x + half_w, y + half_h) def __str__(self): ''' Printer ''' fmt = '(left={0:.3f}, bottom={1:.3f}, right={2:.3f}, top={3:.3f})'.format(*self.coords) return fmt
if __name__ == '__main__': import pylab as plt from matplotlib.patches import Rectangle b1 = Box((.5, .3, .2, .1)) x_1, y_1, w_1, h_1 = b1.box left_1, top_1, right_1, bottom_1 = b1.coords print('Box1: {}'.format(b1)) b2 = Box((.4, .5, .2, .5)) x_2, y_2, w_2, h_2 = b2.box left_2, top_2, right_2, bottom_2 = b2.coords print('Box2: {}'.format(b2)) print('Intersection: {:.3f}'.format(b1.intersection(b2))) print('Union: {:.3f}'.format(b1.union(b2))) print('IOU: {:.3f}'.format(b1.iou(b2))) print('rmse: {:.3f}'.format(b1.rmse(b2))) plt.figure() axis = plt.gca() axis.add_patch(Rectangle(xy=(left_1, top_1), width=w_1, height=h_1, alpha=.5, linewidth=2, color='blue')) axis.add_patch(Rectangle(xy=(left_2, top_2), width=w_2, height=h_2, alpha=.5, linewidth=2, color='red')) plt.show()