from operator import attrgetter
from types import FunctionType
from typing import Sequence, Tuple
from .base import DetectionFeeder, GroundTruthFeeder
from ..base import Property
from ..buffered_generator import BufferedGenerator
class BoundingBoxReducer(DetectionFeeder, GroundTruthFeeder):
""" Reduce data by selecting only data placed within the limits of a
n-dimensional bounding box, defined on the data coordinate space.
When provided with the limit coordinates of a given n-dimensional
bounding box (expressed in the form of min/max bounds on each
dimension), the feeder will apply a filter to the incoming data,
allowing to pass through only data that falls within the desired
limits, and discarding the rest.
Assuming a 2D Cartesian data space, the feeder operation is
equivalent to drawing an imaginary bounding box tangential to the plane
defined by the XY axes, and only feeding data whose state vector
falls within the bounds of the box.
* For the time being, the bounding box limits must be defined on
the same coordinate axes as the received data, which is in
turn assumed to all share a common coordinate frame.
* For example, assume we are tracking some targets in Lat/Lon, but
receive detections in the form of polar coordinates (e.g. relative to
a single Radar sensor), then the bounding box MUST be defined
on (a subset of) the polar coordinate system, and NOT the
* Thus, it is worth noting that in its current version the feeder is
not recommended for use in filtering data coming from multiple
sources/sensors, unless a common coordinate frame is guaranteed.
* Finally, for simplicity purposes, the bounding box is not allowed to
rotate around any axis.
limits: Sequence[Tuple[float, float]] = Property(
doc="Array of points that define the bounds of the desired bounding "
"box. Expressed as a 2D array of min/max coordinate pairs (e.g. "
":code:`limits = [[x_min, x_max], [y_min, y_max], ...]`), where "
"the n-th row corresponds to the n-th bounding box dimension "
"limits. Points that fall ON or WITHIN the box's bounds are "
"considered as valid and are thus forwarded through the feeder, "
"whereas points that fall OUTSIDE the box will be filtered out.")
mapping: Sequence[int] = Property(
doc="Mapping between the state and bounding box coordinates. "
"Should be specified as a vector of length equal to the number of "
"bounding box dimensions, whose elements correspond to row indices"
" in the state vector. E.g. :code:`mapping = [2, 0]` "
"dictates that the first bounding box dimension (i.e. row 0 in "
":py:attr:`~limits`), defines the limits that correspond to the "
"element with (row) index 2 in the data state vector, while "
"the second row (i.e. row 1) in :py:attr:`~limits` relates to the "
"element with index 0. Default is `None`, where the dimensions of "
"the state vector will be used in order, up to length to limits."
def __init__(self, *args, **kwargs):
if self.mapping is None:
self.mapping = tuple(range(len(self.limits)))
num_dims = len(self.limits)
for time, states in self.reader:
outlier_data = set()
for state in states:
state_vector = state.state_vector
for i in range(num_dims):
min = self.limits[i]
max = self.limits[i]
value = state_vector[self.mapping[i]]
if value < min or value > max:
yield time, states - outlier_data