Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Progress Trackers Manually: Option C #888

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 22 additions & 44 deletions docs/examples/Track2Track_Fusion_Example.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,8 @@
# * Create a GM-PHD tracker that will perform track fusion via covariance intersection using
# the :class:`ChernoffUpdater` class.
# * Create a metric manager to generate metrics for each of the four trackers
# * Set up the detection feeders. Each tracker will receive measurements using a custom
# :class:`DummyDetector` class. The track fusion tracker will also use the
# :class:`Tracks2GaussianDetectionFeeder` class.
# * The track fusion tracker will use the :class:`Tracks2GaussianDetectionFeeder` class to
# receive tracks as detections.
# * Run the simulation
# * Plot the resulting tracks and the metrics over time

Expand Down Expand Up @@ -545,33 +544,21 @@
#
# The track fusion tracker will also use the :class:`~.Tracks2GaussianDetectionFeeder` class to
# feed the tracks as measurements. At each time step, the resultant live tracks from the JPDA and
# GM-LCC trackers will be put into a :class:`~.Tracks2GaussianDetectionFeeder` (using the
# :class:`~.DummyDetector` we write below). The feeder will take the most recent state from each
# GM-LCC trackers will be put into a :class:`~.Tracks2GaussianDetectionFeeder`.
# The feeder will take the most recent state from each
# track and turn it into a :class:`~.GaussianDetection` object. The set of detection objects will
# be returned and passed into the tracker.

# %%
from stonesoup.feeder.track import Tracks2GaussianDetectionFeeder
from stonesoup.buffered_generator import BufferedGenerator
from stonesoup.reader.base import DetectionReader


class DummyDetector(DetectionReader):
def __init__(self, *args, **kwargs):
self.current = kwargs['current']

@BufferedGenerator.generator_method
def detections_gen(self):
yield self.current


# %%
# 8: Run Simulation
# -----------------

# %%

sensor1_detections, sensor2_detections = [], []
sensor1_all_detections, sensor2_all_detections = [], []
jpda_tracks, gmlcc_tracks = set(), set()
meas_fusion_tracks, track_fusion_tracks = set(), set()

Expand All @@ -582,47 +569,38 @@ def detections_gen(self):
for t in range(num_steps):

# Run JPDA tracker from sensor 1
s1d = next(sim_generator)
sensor1_detections.extend(s1d[1]) # hold in list for plotting
# Pass the detections into a DummyDetector and set it up as an iterable
jpda_tracker.detector = DummyDetector(current=s1d)
jpda_tracker.__iter__()
# Run the tracker and store the resulting tracks
_, sensor1_tracks = next(jpda_tracker)
time, sensor1_detections = next(sim_generator)
sensor1_all_detections.extend(sensor1_detections) # hold in list for plotting
# Pass the detections into the tracker and store the resulting tracks
time, sensor1_tracks = jpda_tracker.update_tracker(time, sensor1_detections)
jpda_tracks.update(sensor1_tracks)

# Run GM-LCC tracker from sensor 2
s2d = next(sim_generator)
sensor2_detections.extend(s2d[1]) # hold in list for plotting
# Pass the detections into a DummyDetector and set it up as an iterable
gmlcc_tracker.detector = DummyDetector(current=s2d)
gmlcc_tracker.__iter__()
# Run the tracker and store results
time, sensor2_tracks = next(gmlcc_tracker)
time, sensor2_detections = next(sim_generator)
sensor2_all_detections.extend(sensor2_detections) # hold in list for plotting
# Pass the detections into the tracker and store the resulting tracks
time, sensor2_tracks = gmlcc_tracker.update_tracker(time, sensor2_detections)
gmlcc_tracks.update(sensor2_tracks)

# Run the GM-PHD for measurement fusion. This one gets called twice, once for each set of
# detections. This ensures there is only one detection per target.
for detections in [s1d, s2d]:
meas_fusion_tracker.detector = DummyDetector(current=detections)
meas_fusion_tracker.__iter__()
_, tracks = next(meas_fusion_tracker)
for detections in [sensor1_detections, sensor2_detections]:
time, tracks = meas_fusion_tracker.update_tracker(time, detections)
meas_fusion_tracks.update(tracks)

# Run the GM-PHD for track fusion. Similar to the measurement fusion, this tracker gets run
# twice, once for each set of tracks.
for tracks_as_meas in [sensor1_tracks, sensor2_tracks]:
dummy_detector = DummyDetector(current=[time, tracks_as_meas])
track_fusion_tracker.detector = Tracks2GaussianDetectionFeeder(dummy_detector)
track_fusion_tracker.__iter__()
_, tracks = next(track_fusion_tracker)
for sensor_tracks in [sensor1_tracks, sensor2_tracks]:
time, tracks_as_detections = next(iter(
Tracks2GaussianDetectionFeeder([(time, sensor_tracks)])))
time, tracks = track_fusion_tracker.update_tracker(time, tracks_as_detections)
track_fusion_tracks.update(tracks)

# ----------------------------------------------------------------------

# Add ground truth data and measurements to metric manager
truths = radar_simulator.groundtruth.current
detections = s1d[1] | s2d[1]
detections = sensor1_detections | sensor2_detections
metric_manager.add_data({'truths': truths[1], 'detections': detections}, overwrite=False)

# Ensure that all tracks have been extracted from the trackers
Expand Down Expand Up @@ -661,9 +639,9 @@ def detections_gen(self):
for plotter in [plotter1, plotter2]:
plotter.plot_ground_truths(set(radar_simulator.groundtruth.groundtruth_paths), [0, 2],
color='black')
plotter.plot_measurements(sensor1_detections, [0, 2], color='orange', marker='*',
plotter.plot_measurements(sensor1_all_detections, [0, 2], color='orange', marker='*',
measurements_label='Measurements - Airborne Radar')
plotter.plot_measurements(sensor2_detections, [0, 2], color='blue', marker='*',
plotter.plot_measurements(sensor2_all_detections, [0, 2], color='blue', marker='*',
measurements_label='Measurements - Ground Radar')
plotter.plot_tracks(jpda_tracks, [0, 2], color='red',
track_label='Tracks - Airborne Radar (JPDAF)')
Expand Down
7 changes: 3 additions & 4 deletions stonesoup/reader/yaml.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,20 +70,19 @@ def sensor_data_gen(self):
class YAMLTrackReader(YAMLReader, Tracker):
"""YAML Track Reader"""

def data_gen(self):
@ property
def detector(self):
yield from super().data_gen()

def __iter__(self):
self.data_iter = iter(self.data_gen())
self._tracks = dict()
return super().__iter__()

@property
def tracks(self):
return self._tracks

def __next__(self):
time, document = next(self.data_iter)
def update_tracker(self, time, document):
updated_tracks = set()
for track in document.get('tracks', set()):
if track.id in self.tracks:
Expand Down
48 changes: 41 additions & 7 deletions stonesoup/tracker/base.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,51 @@
import datetime
from abc import abstractmethod
from typing import Tuple, Set, Iterator

from ..base import Base
from ..types.detection import Detection
from ..types.track import Track


class Tracker(Base):
"""Tracker base class"""
"""
Tracker base class

@property
@abstractmethod
def tracks(self):
raise NotImplementedError
Note
======
Sub-classes of :class:`~.Tracker` require a ``detector`` property to be iterators. A subclass
can be created without it, however it won't be an iterator. Its functionality will be
restricted to only using the :meth:`~.Tracker.update_tracker` method.
"""

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
try:
_ = self.detector
except AttributeError:
self.detector = None
self.detector_iter = None

def __iter__(self) -> Iterator[Tuple[datetime.datetime, Set[Track]]]:
if self.detector is None:
raise AttributeError("Detector has not been set. A detector attribute is required to"
"iterate over a tracker.")

if self.detector_iter is None:
self.detector_iter = iter(self.detector)

def __iter__(self):
return self

def __next__(self) -> Tuple[datetime.datetime, Set[Track]]:
time, detections = next(self.detector_iter)
return self.update_tracker(time, detections)

@abstractmethod
def __next__(self):
def update_tracker(self, time: datetime.datetime, detections: Set[Detection]) \
-> Tuple[datetime.datetime, Set[Track]]:
"""
This function updates the tracker with detections and outputs the time and tracks

Returns
-------
: :class:`datetime.datetime`
Expand All @@ -25,3 +54,8 @@ def __next__(self):
Tracks existing in the time step
"""
raise NotImplementedError

@property
@abstractmethod
def tracks(self) -> Set[Track]:
raise NotImplementedError
20 changes: 10 additions & 10 deletions stonesoup/tracker/pointprocess.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import datetime
from typing import Tuple, Set

from .base import Tracker
from ..base import Property
from ..hypothesiser.gaussianmixture import GaussianMixtureHypothesiser
from ..mixturereducer.gaussianmixture import GaussianMixtureReducer
from ..reader import DetectionReader
from ..types.state import TaggedWeightedGaussianState
from ..types.detection import Detection
from ..types.mixture import GaussianMixture
from ..types.numeric import Probability
from ..types.state import TaggedWeightedGaussianState
from ..types.track import Track
from ..updater import Updater
from ..hypothesiser.gaussianmixture import GaussianMixtureHypothesiser
from ..mixturereducer.gaussianmixture import GaussianMixtureReducer


class PointProcessMultiTargetTracker(Tracker):
Expand Down Expand Up @@ -40,16 +44,12 @@ def __init__(self, *args, **kwargs):
self.gaussian_mixture = GaussianMixture()

@property
def tracks(self):
def tracks(self) -> Set[Track]:
tracks = set()
for track in self.target_tracks.values():
tracks.add(track)
return tracks

def __iter__(self):
self.detector_iter = iter(self.detector)
return super().__iter__()

def update_tracks(self):
"""
Updates the tracks (:class:`Track`) associated with the filter.
Expand Down Expand Up @@ -77,8 +77,8 @@ def update_tracks(self):
self.extraction_threshold:
self.target_tracks[tag] = Track([component], id=tag)

def __next__(self):
time, detections = next(self.detector_iter)
def update_tracker(self, time: datetime.datetime, detections: Set[Detection]) \
-> Tuple[datetime.datetime, Set[Track]]:
# Add birth component
self.birth_component.timestamp = time
self.gaussian_mixture.append(self.birth_component)
Expand Down
48 changes: 19 additions & 29 deletions stonesoup/tracker/simple.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
import datetime
from typing import Set, Tuple

import numpy as np

from .base import Tracker
from ..base import Property
from ..dataassociator import DataAssociator
from ..deleter import Deleter
from ..reader import DetectionReader
from ..functions import gm_reduce_single
from ..initiator import Initiator
from ..updater import Updater
from ..reader import DetectionReader
from ..types.array import StateVectors
from ..types.detection import Detection
from ..types.prediction import GaussianStatePrediction
from ..types.track import Track
from ..types.update import GaussianStateUpdate
from ..functions import gm_reduce_single
from ..updater import Updater


class SingleTargetTracker(Tracker):
Expand Down Expand Up @@ -46,15 +51,12 @@ def __init__(self, *args, **kwargs):
self._track = None

@property
def tracks(self):
def tracks(self) -> Set[Track]:
return {self._track} if self._track else set()

def __iter__(self):
self.detector_iter = iter(self.detector)
return super().__iter__()
def update_tracker(self, time: datetime.datetime, detections: Set[Detection]) \
-> Tuple[datetime.datetime, Set[Track]]:

def __next__(self):
time, detections = next(self.detector_iter)
if self._track is not None:
associations = self.data_associator.associate(
self.tracks, detections, time)
Expand Down Expand Up @@ -107,12 +109,8 @@ def __init__(self, *args, **kwargs):
def tracks(self):
return {self._track} if self._track else set()

def __iter__(self):
self.detector_iter = iter(self.detector)
return super().__iter__()

def __next__(self):
time, detections = next(self.detector_iter)
def update_tracker(self, time: datetime.datetime, detections: Set[Detection]) \
-> Tuple[datetime.datetime, Set[Track]]:

if self._track is not None:
associations = self.data_associator.associate(
Expand Down Expand Up @@ -203,15 +201,11 @@ def __init__(self, *args, **kwargs):
self._tracks = set()

@property
def tracks(self):
def tracks(self) -> Set[Track]:
return self._tracks

def __iter__(self):
self.detector_iter = iter(self.detector)
return super().__iter__()

def __next__(self):
time, detections = next(self.detector_iter)
def update_tracker(self, time: datetime.datetime, detections: Set[Detection]) \
-> Tuple[datetime.datetime, Set[Track]]:

associations = self.data_associator.associate(
self.tracks, detections, time)
Expand Down Expand Up @@ -259,15 +253,11 @@ def __init__(self, *args, **kwargs):
self._tracks = set()

@property
def tracks(self):
def tracks(self) -> Set[Track]:
return self._tracks

def __iter__(self):
self.detector_iter = iter(self.detector)
return super().__iter__()

def __next__(self):
time, detections = next(self.detector_iter)
def update_tracker(self, time: datetime.datetime, detections: Set[Detection]) \
-> Tuple[datetime.datetime, Set[Track]]:

associations = self.data_associator.associate(
self.tracks, detections, time)
Expand Down
Loading