Areas of interest based Reward Function

This notebook introduces sensor management which factors in environmental information related to the location the sensor is operating in.

We use the AreaOfInterest object, which has functionality for setting levels of “interest” within defined x, y boundaries, and the AOIAccessRewardFunction to define different behaviour for the sensor platform by switching between different reward functions.

Here we will define three areas with different levels of interest, to simulate a scenario where the sensor platform gets closer to the target to make observations as the target moves into a higher interest area.

Setting Up the Scenario

First we generate a ground truth of a target following a constant velocity with some noise and plot this.

import numpy as np
from datetime import datetime, timedelta


from stonesoup.models.transition.linear import (CombinedLinearGaussianTransitionModel,
                                                ConstantVelocity)
from stonesoup.types.groundtruth import GroundTruthPath, GroundTruthState

np.random.seed(1990)

start_time = datetime.now().replace(second=0, microsecond=0)

transition_model = CombinedLinearGaussianTransitionModel(
    [ConstantVelocity(10), ConstantVelocity(10)])

truths = []
truth = GroundTruthPath([GroundTruthState([-450, 5, 450, -5], timestamp=start_time)])
duration = 120
timesteps = [start_time]

for k in range(1, duration):
    timesteps.append(start_time+timedelta(seconds=k))
    truth.append(GroundTruthState(
        transition_model.function(truth[k-1], noise=True, time_interval=timedelta(seconds=1)),
        timestamp=timesteps[k]))
truths.append(truth)

from stonesoup.plotter import AnimatedPlotterly
plotter = AnimatedPlotterly(timesteps, tail_length=1)
plotter.plot_ground_truths(truth, [0, 2])
plotter.fig


Creating the Platform and Sensor

Next we create the taskable sensor platform and attach a RadarRotatingBearingRange sensor. We also create a target sensor, for the purpose of modelling the target’s field of view, for use in the FOVInteractionRewardFunction.

from stonesoup.platform import MovingPlatform
from stonesoup.movable.max_speed import MaxSpeedActionableMovable
from stonesoup.sensor.radar.radar import RadarRotatingBearingRange
from stonesoup.types.angle import Angle
from stonesoup.types.state import State, StateVector

sensor = RadarRotatingBearingRange(
    position_mapping=(0, 2),
    noise_covar=np.array([[np.radians(5)**2, 0],
                          [0, 20**2]]),
    ndim_state=4,
    rpm=30,
    fov_angle=np.radians(360),
    dwell_centre=StateVector([np.radians(0)]),
    max_range=300,

    resolution=Angle(np.radians(360)))

target_sensor = RadarRotatingBearingRange(
    position_mapping=(0, 2),
    noise_covar=np.array([[np.radians(1)**2, 0], [0, 1**2]]),
    ndim_state=4,
    rpm=30,
    fov_angle=np.radians(360),
    dwell_centre=StateVector([np.radians(90)]),
    max_range=100,
    resolution=Angle(np.radians(180)))

platform = MovingPlatform(
    movement_controller=MaxSpeedActionableMovable(
        states=[State([-500, 700],
                      timestamp=start_time)],
        position_mapping=(0, 1),
        action_mapping=(0, 1),
        resolution=10,
        angle_resolution=np.pi/2,
        max_speed=100),
    sensors=[sensor])

Creating a Predictor and Updater

Next we need some tracking components: a predictor and an updater.

from stonesoup.predictor.kalman import KalmanPredictor
predictor = KalmanPredictor(transition_model)

from stonesoup.updater.kalman import ExtendedKalmanUpdater
updater = ExtendedKalmanUpdater(measurement_model=None)

Creating Areas of Interest

Different areas of interest can be defined by setting minimum/maximum values for x and y using the AreaOfInterest. Here we have 3 areas of different levels of interest.

Area 1 is defined as being everything to the left of the origin (x = 0), with an interest level of 4. Area 2 is defined as being between x = 0 and x = 1500, with an interest level of 7. Area 3 is defined as being everything to the right of x = 1500, with an interest level of 10.

from stonesoup.types.shape import AreaOfInterest

area1 = AreaOfInterest(xmax=0, interest=4)
area2 = AreaOfInterest(xmin=0, xmax=1500, interest=7)
area3 = AreaOfInterest(xmin=1500, interest=10)

Creating a Sensor Manager

Now we create a sensor manager, providing it with the sensor, the sensor platform, and a reward function.

We define a different reward function for each area of interest. In areas of low interest we use a combination of the FOVInteractionRewardFunction and the UncertaintyRewardFunction, ensuring the sensor platform stays outside a set distance from the target while continuing to track it. In areas of medium interest this distance is reduced, and in areas of high interest the FOVInteractionRewardFunction is not used, so the sensor platform can get as close as it likes.

The AOIAccessRewardFunction is used to switch between these reward functions as the target moves through the different areas of interest, based on the target’s location and the defined interest thresholds for each area.

Creating a Tracker

Next we initialise a track and build a tracker.

from stonesoup.types.state import GaussianState
from stonesoup.types.track import Track

prior = GaussianState(truths[0][0].state_vector,
                      covar=np.diag([0.5, 0.5, 0.5, 0.5] + np.random.normal(0, 5e-4, 4)),
                      timestamp=start_time)

tracks = {Track([prior])}

from stonesoup.hypothesiser.distance import DistanceHypothesiser
from stonesoup.measures import Mahalanobis

hypothesiser = DistanceHypothesiser(predictor, updater, measure=Mahalanobis(), missed_distance=5)

from stonesoup.dataassociator.neighbour import GNNWith2DAssignment
data_associator = GNNWith2DAssignment(hypothesiser)

Running the Tracking Loop

At each timestep the sensor manager generates the optimal actions for our sensor and platform. These actions are taken before the sensor makes detections and the track is updated.

from ordered_set import OrderedSet
from collections import defaultdict
import copy
from stonesoup.measures import Euclidean
from stonesoup.sensor.sensor import Sensor

all_measurements = set()
sensor_history = defaultdict(dict)
for timestep in timesteps[1:]:

    chosen_actions = sensormanager.choose_actions(tracks, timestep)
    measurements = set()

    for chosen_action in chosen_actions:
        for actionable, actions in chosen_action.items():
            actionable.add_actions(actions)
            actionable.act(timestep)
            if isinstance(actionable, Sensor):
                measurements |= actionable.measure(OrderedSet(truth[timestep] for truth in truths),
                                                   noise=True)

    all_measurements.update(measurements)
    sensor_history[timestep][sensor] = copy.deepcopy(sensor)
    hypotheses = data_associator.associate(tracks, measurements, timestep)
    for track in tracks:
        hypothesis = hypotheses[track]
        if hypothesis.measurement:
            post = updater.update(hypothesis)
            track.append(post)
        else:
            track.append(hypothesis.prediction)

Plotting

Now we use the animated plotter to see what behaviour has been achieved.

import plotly.graph_objects as go
from stonesoup.platform.base import PathBasedPlatform

plotter = AnimatedPlotterly(timesteps, tail_length=0.1)
plotter.plot_ground_truths(truths, [0, 2], mode='lines', line=dict(dash=None))
plotter.plot_tracks(tracks, mapping=(0, 2))
plotter.plot_measurements(all_measurements, mapping=(0, 2))

track_list = list(tracks)
target_platform = PathBasedPlatform(path=track_list[0], sensors=[target_sensor],
                                    position_mapping=[0, 2])
target_sensor_history = defaultdict(dict)

for timestep in timesteps[1:]:
    target_platform.move(timestep)
    target_sensor_history[timestep][target_sensor] = copy.deepcopy(target_sensor)
target_sensor_set = {target_sensor}
sensor_set = {sensor}

plotter.plot_sensor_fov(sensor_set, sensor_history)

plotter.plot_sensor_fov(target_sensor_set, target_sensor_history, label="Target FOV", colour="red")
plotter.fig.add_trace(go.Scatter(x=[1500, 1500], y=[-300, 500], mode='lines',
                                 line=dict(color='#888'),
                                 name='Area Boundaries',
                                 showlegend=False))
plotter.fig.add_trace(go.Scatter(x=[0, 0], y=[-300, 500], mode='lines',
                                 line=dict(color='#888'),
                                 name='Area Boundaries',
                                 showlegend=False))
plotter.fig.add_trace(go.Scatter(x=[-400, 800, 2000],
                                 y=[0, 0, 0], mode="text", name="Areas of Interest",
                                 line=dict(color='#888'),
                                 text=["Interest: 4",
                                       "Interest: 7",
                                       "Interest: 10"]))
plotter.fig


The actionable platform is able to follow the target as it moves across the action space, initially remaining outside the distance set for areas of low interest, then getting closer as it moves into areas of higher interest.

This is a relatively simple example but demonstrates how the sensor manager can switch reward function, and therefore change behaviour, based on information about its environment.

Metrics

To check the impact this has on the tracking performance we compute some metrics.

from stonesoup.metricgenerator.ospametric import OSPAMetric
ospa_generatorA = OSPAMetric(c=40, p=1,
                             generator_name='3 areas of interest',
                             tracks_key='tracksA',
                             truths_key='truths')

from stonesoup.metricgenerator.tracktotruthmetrics import SIAPMetrics
siap_generatorA = SIAPMetrics(position_measure=Euclidean((0, 2)),
                              velocity_measure=Euclidean((1, 3)),
                              generator_name='3 areas of interest',
                              tracks_key='tracksA',
                              truths_key='truths')

from stonesoup.dataassociator.tracktotrack import TrackToTruth
associator = TrackToTruth(association_threshold=30)

from stonesoup.metricgenerator.uncertaintymetric import SumofCovarianceNormsMetric
uncertainty_generatorA = SumofCovarianceNormsMetric(generator_name='3 areas of interest',
                                                    tracks_key='tracksA')

from stonesoup.metricgenerator.manager import MultiManager

metric_manager = MultiManager([ospa_generatorA,
                               siap_generatorA,
                               uncertainty_generatorA],
                              associator=associator)

metric_manager.add_data({'truths': truths, 'tracksA': tracks})

metrics = metric_manager.generate_metrics()

First we plot Sum of Covariance Norms metric.

from stonesoup.plotter import MetricPlotter

fig1 = MetricPlotter()
fig1.plot_metrics(metrics, metric_names=['Sum of Covariance Norms Metric'])
Sum of Covariance Norms Metric

We can see from this that initially, when the sensor platform is staying further from the target, the track uncertainty is higher, and as it moves into an area of high interest, it gets closer to the target and the track uncertainty reduces.

Next we plot the OSPA and SIAP metrics

fig2 = MetricPlotter()
fig2.plot_metrics(metrics, metric_names=['OSPA distances'])

fig3 = MetricPlotter()
fig3.plot_metrics(metrics, metric_names=['SIAP Position Accuracy at times',
                                         'SIAP Velocity Accuracy at times'])
  • OSPA distances
  • SIAP Position Accuracy, SIAP Velocity Accuracy

These metrics show a similar tracking performance across the simulation, even where track uncertainty is greater.

Total running time of the script: (0 minutes 31.000 seconds)

Gallery generated by Sphinx-Gallery