|
| 1 | +#!/usr/bin/env python |
| 2 | +# coding: utf-8 |
| 3 | + |
| 4 | +""" |
| 5 | +======================================================== |
| 6 | +General Multi Hypotheses tracking implementation example |
| 7 | +======================================================== |
| 8 | +""" |
| 9 | + |
| 10 | +# %% |
| 11 | +# The multi hypotheses tracking (MHT) algorithm is considered one of the best tracking algorithms, |
| 12 | +# consisting of creating a tree of potential tracks for |
| 13 | +# each target candidate (in a multi-target scenario) and pruning such hypotheses |
| 14 | +# in the data association phase. It is particularly efficient in maintaining trajectories of |
| 15 | +# multiple objects and handling uncertainties and ambiguities of tracks (e.g. presence of |
| 16 | +# clutter). |
| 17 | +# |
| 18 | +# MHT, by definition, has several algorithms that fall under this definition, which |
| 19 | +# include Global Nearest Neighbour (GNN, |
| 20 | +# :doc:`tutorial <../../auto_tutorials/06_DataAssociation-MultiTargetTutorial>`), |
| 21 | +# Joint Probabilistic Data association (JPDA, |
| 22 | +# :doc:`tutorial <../../auto_tutorials/08_JPDATutorial>`), |
| 23 | +# Multi-frame assignment (MFA [#]_, :doc:`example <MFA_example>`), |
| 24 | +# Multi Bernoulli filter and Probabilistic multi hypotheses tracking (PMHT). |
| 25 | +# Some of these algorithms are already implemented the Stone Soup. |
| 26 | +# |
| 27 | +# In this example we employ the multi-frame assignment data associator and |
| 28 | +# hypothesiser using their Stone Soup implementation. |
| 29 | +# |
| 30 | +# This example follows this structure: |
| 31 | +# |
| 32 | +# 1. Create ground truth and detections; |
| 33 | +# 2. Instantiate the tracking components and tracker; |
| 34 | +# 3. Run the tracker and visualise the results. |
| 35 | +# |
| 36 | + |
| 37 | +# %% |
| 38 | +# General imports |
| 39 | +# ^^^^^^^^^^^^^^^ |
| 40 | +import numpy as np |
| 41 | +from datetime import datetime, timedelta |
| 42 | +from itertools import tee |
| 43 | + |
| 44 | +# %% |
| 45 | +# Stone Soup imports |
| 46 | +# ^^^^^^^^^^^^^^^^^^ |
| 47 | +from stonesoup.types.array import StateVector, CovarianceMatrix |
| 48 | +from stonesoup.types.state import GaussianState |
| 49 | +from stonesoup.models.transition.linear import CombinedLinearGaussianTransitionModel, \ |
| 50 | + ConstantVelocity |
| 51 | +from stonesoup.models.measurement.nonlinear import CartesianToBearingRange |
| 52 | +from stonesoup.simulator.simple import MultiTargetGroundTruthSimulator, SimpleDetectionSimulator |
| 53 | + |
| 54 | +# Simulation parameters |
| 55 | +np.random.seed(1908) # fix a random seed |
| 56 | +start_time = datetime.now().replace(microsecond=0) |
| 57 | +simulation_steps = 50 |
| 58 | +timestep_size = timedelta(seconds=2) |
| 59 | +prob_detection = 0.99 |
| 60 | +initial_state_mean = StateVector([[10], [0], [10], [0]]) |
| 61 | +initial_covariance = CovarianceMatrix(np.diag([30, 1, 40, 1])) |
| 62 | + |
| 63 | +# clutter will be generated uniformly in this area around the targets |
| 64 | +clutter_area = np.array([[-1, 1], [-1, 1]])*150 |
| 65 | +clutter_rate = 9 |
| 66 | +surveillance_area = ((clutter_area[0, 1] - clutter_area[0, 0]) * |
| 67 | + (clutter_area[1, 1] - clutter_area[1, 0])) |
| 68 | +clutter_spatial_density = clutter_rate/surveillance_area |
| 69 | + |
| 70 | +# %% |
| 71 | +# 1. Create ground truth and detections; |
| 72 | +# -------------------------------------- |
| 73 | +# We have prepared all the general parameters for the simulation, |
| 74 | +# including the clutter spatial density. In this example we set |
| 75 | +# the birth rate and the death probability as zero, using only the knowledge of the |
| 76 | +# prior states to generate the tracks so the number of targets is fixed (3 in this case). |
| 77 | +# |
| 78 | +# We can instantiate the transition model of the targets and the measurement model. |
| 79 | +# In this example we employ a :class:`~.CartesianToBearingRange` non-linear measurement model. |
| 80 | +# Then, we pass all these details to a :class:`~.MultiTargetGroundTruthSimulator` |
| 81 | +# and use a :class:`~.SimpleDetectionSimulator` |
| 82 | +# to obtain the target ground truth, detections and clutter. |
| 83 | +# |
| 84 | + |
| 85 | +# Create an initial state |
| 86 | +initial_state = GaussianState(state_vector=initial_state_mean, |
| 87 | + covar=initial_covariance, |
| 88 | + timestamp=start_time) |
| 89 | + |
| 90 | +# Instantiate the transition model |
| 91 | +transition_model = CombinedLinearGaussianTransitionModel([ConstantVelocity(0.005), |
| 92 | + ConstantVelocity(0.005)]) |
| 93 | + |
| 94 | +# Define the measurement model |
| 95 | +measurement_model = CartesianToBearingRange(ndim_state=4, |
| 96 | + mapping=(0, 2), |
| 97 | + noise_covar=np.diag([np.radians(1), 5])) |
| 98 | + |
| 99 | +# Instantiate the multi-target simulator |
| 100 | +ground_truth_simulator = MultiTargetGroundTruthSimulator( |
| 101 | + transition_model=transition_model, |
| 102 | + initial_state=initial_state, |
| 103 | + timestep=timestep_size, |
| 104 | + number_steps=simulation_steps, |
| 105 | + birth_rate=0, # no other targets more than the specified ones |
| 106 | + death_probability=0, # all targets will remain during the simulation |
| 107 | + preexisting_states=[[10, 1, 10, 1], [-10, -1, -10, -1], [-10, -1, 10, 1]]) |
| 108 | + |
| 109 | +# Create a detector |
| 110 | +detection_sim = SimpleDetectionSimulator( |
| 111 | + groundtruth=ground_truth_simulator, |
| 112 | + measurement_model=measurement_model, |
| 113 | + detection_probability=prob_detection, |
| 114 | + meas_range=clutter_area, |
| 115 | + clutter_rate=clutter_rate) |
| 116 | + |
| 117 | +# Instantiate a set for detections/clutter and ground truths |
| 118 | +detections = set() |
| 119 | +ground_truth = set() |
| 120 | +timestamps = [] |
| 121 | + |
| 122 | +# Duplicate the detection simulator |
| 123 | +plot, tracking = tee(detection_sim, 2) |
| 124 | + |
| 125 | +# Iterate in the detection simulator to generate the measurements |
| 126 | +for time, dets in plot: |
| 127 | + detections |= dets |
| 128 | + ground_truth |= ground_truth_simulator.groundtruth_paths |
| 129 | + timestamps.append(time) |
| 130 | + |
| 131 | +# Visualise the detections and tracks |
| 132 | +from stonesoup.plotter import AnimatedPlotterly |
| 133 | + |
| 134 | +plotter = AnimatedPlotterly(timestamps) |
| 135 | +plotter.plot_ground_truths(ground_truth, [0, 2]) |
| 136 | +plotter.plot_measurements(detections, [0, 2]) |
| 137 | +plotter.fig |
| 138 | + |
| 139 | +# %% |
| 140 | +# 2. Instantiate the tracking components and tracker; |
| 141 | +# --------------------------------------------------- |
| 142 | +# We need to prepare the tracker and its components. In this example we consider an |
| 143 | +# Unscented Kalman filter since we are dealing with non-linear measurements. |
| 144 | +# We consider a :class:`~.UnscentedKalmanPredictor` and :class:`~.UnscentedKalmanUpdater`. |
| 145 | +# |
| 146 | +# As said previously, we consider a multi-frame assignment data associator |
| 147 | +# which wraps a :class:`~.PDAHypothesiser` probability hypothesiser into a |
| 148 | +# :class:`~.MFAHypothesiser` to work with the :class:`~.MFADataAssociator`. |
| 149 | +# |
| 150 | +# To instantiate the tracks we could use :class:`~.GaussianMixtureInitiator` which |
| 151 | +# uses a Gaussian Initiator (such as :class:`~.MultiMeasurementInitiator`) to |
| 152 | +# create GaussianMixture prior states, however, its current implementation |
| 153 | +# is intended for a GM-PHD filter making it troublesome to adapt for our needs. |
| 154 | +# Therefore, we consider :class:`~.TaggedWeightedGaussianState` to create the track priors, |
| 155 | +# which can be handled by MFA components, using the pre-existing states fed to the |
| 156 | +# multi-groundtruth simulator. |
| 157 | +# Such prior states are, then, wrapped in :class:`~.GaussianMixture` states. |
| 158 | +# |
| 159 | +# As it is now, there is not a tracker wrapper (as for the |
| 160 | +# :class:`~.MultiTargetMixtureTracker`) that can be applied directly when dealing with MFA, |
| 161 | +# so we need to specify the tracking loop explicitly. |
| 162 | + |
| 163 | +# load tracker the components |
| 164 | +from stonesoup.predictor.kalman import UnscentedKalmanPredictor |
| 165 | +from stonesoup.updater.kalman import UnscentedKalmanUpdater |
| 166 | + |
| 167 | +predictor = UnscentedKalmanPredictor(transition_model) |
| 168 | +updater = UnscentedKalmanUpdater(measurement_model) |
| 169 | + |
| 170 | +# Data associator and hypothesiser |
| 171 | +from stonesoup.dataassociator.mfa import MFADataAssociator |
| 172 | +from stonesoup.hypothesiser.mfa import MFAHypothesiser |
| 173 | +from stonesoup.hypothesiser.probability import PDAHypothesiser |
| 174 | + |
| 175 | +hypothesiser = PDAHypothesiser(predictor, |
| 176 | + updater, |
| 177 | + clutter_spatial_density, |
| 178 | + prob_gate=0.9999, |
| 179 | + prob_detect=prob_detection) |
| 180 | + |
| 181 | +hypothesiser = MFAHypothesiser(hypothesiser) |
| 182 | +data_associator = MFADataAssociator(hypothesiser, |
| 183 | + slide_window=3) |
| 184 | + |
| 185 | +# Prepare the priors |
| 186 | +from stonesoup.types.state import TaggedWeightedGaussianState |
| 187 | +from stonesoup.types.track import Track |
| 188 | +from stonesoup.types.mixture import GaussianMixture |
| 189 | +from stonesoup.types.numeric import Probability |
| 190 | +from stonesoup.types.update import GaussianMixtureUpdate |
| 191 | + |
| 192 | +prior1 = GaussianMixture([TaggedWeightedGaussianState( |
| 193 | + StateVector([10, 1, 10, 1]), |
| 194 | + np.diag([10, 1, 10, 1]), |
| 195 | + timestamp=initial_state.timestamp, |
| 196 | + weight=Probability(1), |
| 197 | + tag=[])]) |
| 198 | + |
| 199 | +prior2 = GaussianMixture([TaggedWeightedGaussianState( |
| 200 | + StateVector([-10, -1, -10, -1]), |
| 201 | + np.diag([10, 1, 10, 1]), |
| 202 | + timestamp=initial_state.timestamp, |
| 203 | + weight=Probability(1), |
| 204 | + tag=[])]) |
| 205 | + |
| 206 | +prior3 = GaussianMixture([TaggedWeightedGaussianState( |
| 207 | + StateVector([-10, -1, 10, 1]), |
| 208 | + np.diag([10, 1, 10, 1]), |
| 209 | + timestamp=initial_state.timestamp, |
| 210 | + weight=Probability(1), |
| 211 | + tag=[])]) |
| 212 | + |
| 213 | +# instantiate the tracks |
| 214 | +tracks = {Track([prior1]), |
| 215 | + Track([prior2]), |
| 216 | + Track([prior3])} |
| 217 | + |
| 218 | +# %% |
| 219 | +# 3. Run the tracker and visualise the results; |
| 220 | +# --------------------------------------------- |
| 221 | +# We are ready to loop over the detections in the |
| 222 | +# simulation and obtain the final tracks. |
| 223 | + |
| 224 | + |
| 225 | +for time, detection in tracking: |
| 226 | + association = data_associator.associate(tracks, detection, time) |
| 227 | + |
| 228 | + for track, hypotheses in association.items(): |
| 229 | + components = [] |
| 230 | + for hypothesis in hypotheses: |
| 231 | + if not hypothesis: |
| 232 | + components.append(hypothesis.prediction) |
| 233 | + else: |
| 234 | + update = updater.update(hypothesis) |
| 235 | + components.append(update) |
| 236 | + track.append(GaussianMixtureUpdate(components=components, |
| 237 | + hypothesis=hypotheses)) |
| 238 | + |
| 239 | + tracks.add(track) |
| 240 | + |
| 241 | +plotter.plot_tracks(tracks, [0, 2], track_label="Tracks", line=dict(color="Green")) |
| 242 | +plotter.fig |
| 243 | + |
| 244 | +# %% |
| 245 | +# Conclusion |
| 246 | +# ---------- |
| 247 | +# In this example we have presented how to set up a multi-hypotheses tracking |
| 248 | +# (MHT) simulation, by employing the existing components present in Stone Soup |
| 249 | +# and perform the tracking in a heavy cluttered multi-target scenario. |
| 250 | + |
| 251 | +# %% |
| 252 | +# References |
| 253 | +# ---------- |
| 254 | +# .. [#] Xia, Y., Granström, K., Svensson, L., García-Fernández, Á.F., and Williams, J.L., |
| 255 | +# 2019. Multiscan Implementation of the Trajectory Poisson Multi-Bernoulli Mixture Filter. |
| 256 | +# J. Adv. Information Fusion, 14(2), pp. 213–235. |
0 commit comments