Main Content

How to Simulate Out-of-Sequence Measurements

This example shows how to simulate out-of-sequence measurements (OOSMs). OOSMs occur in multisensor systems when detections from some sensors arrive at the tracker after the tracker has processed detections of the same or earlier time from other sensors. Several reasons, mostly sensor processing lag and sensor-to-tracker connection delays, can cause this problem. For more about OOSMs, see Introduction to Out-of-Sequence Measurement Handling

Handling OOSMs is a key requirement from the trackers. There are several ways to handle OOSMs, including neglecting them, erroring out, or including them using a technique like retrodiction. You have to make a choice based on the multisensor system, its update rate, the reasons of OOSMs, and the data contained in the OOSMs. Whichever OOSM technique you choose, it is critical to test and verify that the tracker behaves correctly when OOSMs are encountered. Thus, it is necessary to simulate OOSMs under various conditions of the multisensor system.

In this example, you learn how to simulate OOSMs using the objectDetectionDelay object, which serves as a buffer that holds measurements in the format of objectDetection objects and simulates time delay before they reach the tracker.

Simulate Constant Delay from All Sensors

First, define a default objectDetectionDelay object.

allDelayer = objectDetectionDelay;
disp(allDelayer)
  objectDetectionDelay with properties:

        SensorIndices: "All"
             Capacity: Inf
          DelaySource: 'Property'
    DelayDistribution: 'Constant'
      DelayParameters: 1

In this setting, the allDelayer object adds a constant time delay of 1 second to all sensors. Create an objectDetection object.

timeDelays = [];
detection = objectDetection(0,[100;10;1]);
disp(detection);
  objectDetection with properties:

                     Time: 0
              Measurement: [3x1 double]
         MeasurementNoise: [3x3 double]
              SensorIndex: 1
            ObjectClassID: 0
    ObjectClassParameters: []
    MeasurementParameters: {}
         ObjectAttributes: {}

Delay the detection and observe that the output is empty, because the current time is 0 seconds, and the detection should be delivered from the delay object at time = 1 second.

outDet = allDelayer({detection}, 0)
outDet =

  0x1 empty cell array

Call the delay object again with time = 0.99 seconds, right before time = 1 second. Observe that the output is still empty. You can use an empty cell to pass no new detections to the delay object.

outDet = allDelayer({}, 0.99); % There are still no detections in the output

Now, call the delay object with a time of 1 second and observe the delayed detection in the output.

currentTime = 1;
outDet = allDelayer({}, currentTime);
timeDelays(end+1) = currentTime - outDet{1}.Time;

% Display that this is the detection added at time = 0
disp(outDet{1})
  objectDetection with properties:

                     Time: 0
              Measurement: [3x1 double]
         MeasurementNoise: [3x3 double]
              SensorIndex: 1
            ObjectClassID: 0
    ObjectClassParameters: []
    MeasurementParameters: {}
         ObjectAttributes: {}

You can control the delay time by setting the DelayParameters property.

allDelayer.DelayParameters = 2;
detection = objectDetection(1.5,[100;10;1]);
disp(detection);
  objectDetection with properties:

                     Time: 1.5000
              Measurement: [3x1 double]
         MeasurementNoise: [3x3 double]
              SensorIndex: 1
            ObjectClassID: 0
    ObjectClassParameters: []
    MeasurementParameters: {}
         ObjectAttributes: {}

The delay object will only output the detection when the it is stepped beyond time = 3.5. You can verify that using the DeliveryTime field of the information structure.

[~, ~, info] = allDelayer({detection}, 3);
disp(info)
    DetectionTime: 1.5000
            Delay: 2
     DeliveryTime: 3.5000

Now add a new detection to the buffer:

detection = objectDetection(3.2,[-100;-10;-1]);
[~, ~, info] = allDelayer({detection}, 3.2);
disp(info)
    DetectionTime: [1.5000 3.2000]
            Delay: [2 2]
     DeliveryTime: [3.5000 5.2000]

As shown in the following code, the delay object outputs each detection at their corresponding delivery time.

% Get the detection from time 1.5
currentTime = 3.5;
outDet = allDelayer({}, currentTime);
timeDelays(end+1) = currentTime - outDet{1}.Time;
disp(outDet{1})
  objectDetection with properties:

                     Time: 1.5000
              Measurement: [3x1 double]
         MeasurementNoise: [3x3 double]
              SensorIndex: 1
            ObjectClassID: 0
    ObjectClassParameters: []
    MeasurementParameters: {}
         ObjectAttributes: {}
% Get the detection from time 3.2
currentTime = 5.2;
outDet = allDelayer({}, currentTime);
timeDelays(end+1) = currentTime - outDet{1}.Time;
disp(outDet{1})
  objectDetection with properties:

                     Time: 3.2000
              Measurement: [3x1 double]
         MeasurementNoise: [3x3 double]
              SensorIndex: 1
            ObjectClassID: 0
    ObjectClassParameters: []
    MeasurementParameters: {}
         ObjectAttributes: {}
bar(timeDelays);
title("Time Delay for Each Detection")
xlabel("Detection");
ylabel("Time Delay");

Figure contains an axes object. The axes object with title Time Delay for Each Detection contains an object of type bar.

Simulate Constant Delay from Specific Sensors

In multisensor systems the time delay for each sensor is usually different, because of the sensor-specific processing time or the sensor-to-tracker communication lag. To simulate these differences, you can define the delay object to work on detections from specific sensors.

% Sensor 1 is delayed by 1 second.
sensor1Delayer = objectDetectionDelay(SensorIndices = 1, DelayParameters = 1);
sensor1TimeDelays = [];

% Sensors 2 and 3 are delayed by 2.5 seconds.
sensor23Delayer = objectDetectionDelay(SensorIndices = [2 3], DelayParameters = 2.5);
sensor23TimeDelays = [];
sensor4TimeDelays = [];

The object does not delay detections from any sensor that is not listed in the SensorIndices property. Therefore, you can use both detection delay objects in series to get the delayed objects. In this block of code, notice that the fourth detection, from sensor 4, is not delayed.

allDetections = {...
    objectDetection(0, [10;10;10], SensorIndex = 1); ...
    objectDetection(0, [20;20;20], SensorIndex = 2); ...
    objectDetection(0, [30;30;30], SensorIndex = 3); ...
    objectDetection(0, [40;40;40], SensorIndex = 4)};
out1 = sensor1Delayer(allDetections, 0);
out2 = sensor23Delayer(out1, 0);
sensor4TimeDelays(end+1) = allDetections{4}.Time - out2{1}.Time;
disp(out2{:})
  objectDetection with properties:

                     Time: 0
              Measurement: [3x1 double]
         MeasurementNoise: [3x3 double]
              SensorIndex: 4
            ObjectClassID: 0
    ObjectClassParameters: []
    MeasurementParameters: {}
         ObjectAttributes: {}

You use the following loop to step the delay objects and receive the detections after their corresponding delays. The detection from sensor 1 should be released at t = 1 second and the detections from sensors 2 and 3 should be released at t = 2.5 seconds.

for time = 0.5:0.5:3
    out1 = sensor1Delayer({}, time);
    out2 = sensor23Delayer(out1, time);
    if isempty(out2)
        disp("The time is: " + time + " and there are no delivered detections.")
    else
        disp("The time is: " + time + " and delivered detections after both delay objects are:");
        out2{:}
        for i = 1:numel(out2)
            if out2{i}.SensorIndex == 1
                sensor1TimeDelays(end+1) = time - out2{i}.Time;
            else
                sensor23TimeDelays(end+1) = time - out2{i}.Time;
            end
        end
    end
end
The time is: 0.5 and there are no delivered detections.
The time is: 1 and delivered detections after both delay objects are:
ans = 
  objectDetection with properties:

                     Time: 0
              Measurement: [3x1 double]
         MeasurementNoise: [3x3 double]
              SensorIndex: 1
            ObjectClassID: 0
    ObjectClassParameters: []
    MeasurementParameters: {}
         ObjectAttributes: {}

The time is: 1.5 and there are no delivered detections.
The time is: 2 and there are no delivered detections.
The time is: 2.5 and delivered detections after both delay objects are:
ans = 
  objectDetection with properties:

                     Time: 0
              Measurement: [3x1 double]
         MeasurementNoise: [3x3 double]
              SensorIndex: 2
            ObjectClassID: 0
    ObjectClassParameters: []
    MeasurementParameters: {}
         ObjectAttributes: {}

ans = 
  objectDetection with properties:

                     Time: 0
              Measurement: [3x1 double]
         MeasurementNoise: [3x3 double]
              SensorIndex: 3
            ObjectClassID: 0
    ObjectClassParameters: []
    MeasurementParameters: {}
         ObjectAttributes: {}

The time is: 3 and there are no delivered detections.
bar([mean(sensor1TimeDelays), mean(sensor23TimeDelays), mean(sensor23TimeDelays), mean(sensor4TimeDelays)]);
title("Time Delays by Sensor Index");
xlabel("Sensor Index");
ylabel("Time Delay");

Figure contains an axes object. The axes object with title Time Delays by Sensor Index contains an object of type bar.

Simulate Random Delay

In some cases, for example random network delays, the lag from the moment the sensor creates the measurement until it arrives at the tracker is random. You can use the objectDetectionDelay object to simulate random time delays by setting the DelayDistribution property to "Uniform" or "Normal". You can further specify the delay distribution by setting the DelayParameters property. The code below shows how to specify the objectDetectionDelay object to simulate random time delays that are normally distributed with a mean of 0.5 seconds and a standard deviation of 0.1 seconds.

uniformDelayer = objectDetectionDelay(SensorIndices = 1, DelayDistribution = "Normal", DelayParameters = [0.5 0.1]);
timeDelays = [];
disp(uniformDelayer)
  objectDetectionDelay with properties:

        SensorIndices: 1
             Capacity: Inf
          DelaySource: 'Property'
    DelayDistribution: 'Normal'
      DelayParameters: [0.5000 0.1000]

Use the uniform delay in a loop and observe the time delays using the info output. In the first second, there will be an input detection to the uniformDelayer object and after that there will be no input.

for time = 0:1e-3:2
    if time < 1
        inDet = {objectDetection(time, [1;1;1], SensorIndex = 1)};
    else
        inDet = {};
    end
    outDet = uniformDelayer(inDet, time);
    for i = 1:numel(outDet)
        timeDelays(end+1) = time - outDet{i}.Time;
    end
end
histogram(timeDelays)
title("Histogram of Detection Time Delays")
xlabel("Time Delay")
ylabel("Number of Detections");

Figure contains an axes object. The axes object with title Histogram of Detection Time Delays contains an object of type histogram.

Use Time Delay Input

To have the maximum flexibility of adding time delay to OOSMs, you set the DelaySource property to "Input" and use the third input of the delay object to add a time delay. You can delay all the detections with the same time delay or each one with a different time delay. The delay is applied only to detections with SensorIndex property value matching one of the SensorIndices.

inputDelayer = objectDetectionDelay(DelaySource = "Input");
disp(inputDelayer);
  objectDetectionDelay with properties:

    SensorIndices: "All"
         Capacity: Inf
      DelaySource: 'Input'

The following code shows how to delay a detection by 2 seconds.

time = 0;
timeDelay = 2;
det1 = objectDetection(time, [1;1;1]);
[~,~,info] = inputDelayer(det1, time, timeDelay);
disp(info)
    DetectionTime: 0
            Delay: 2
     DeliveryTime: 2

You delay multiple detections by 3 seconds

time = 1;
timeDelay = 3;
detections = [objectDetection(time, [1;1;1]), objectDetection(time, [2;2;2])];
[~,~,info] = inputDelayer(detections, time, timeDelay);
disp(info)
    DetectionTime: [0 1 1]
            Delay: [2 3 3]
     DeliveryTime: [2 4 4]

Then, you delay three additional detections by different time delays.

time = 2;
timeDelays = [0.5;1;2];
detections = [objectDetection(time, [1;1;1]), objectDetection(time, [2;2;2]), objectDetection(time, [3;3;3])];
[outDet,~,info] = inputDelayer(detections, time, timeDelays);
disp("After two seconds the first detection is delivered")
After two seconds the first detection is delivered
disp(outDet)
  objectDetection with properties:

                     Time: 0
              Measurement: [3x1 double]
         MeasurementNoise: [3x3 double]
              SensorIndex: 1
            ObjectClassID: 0
    ObjectClassParameters: []
    MeasurementParameters: {}
         ObjectAttributes: {}
disp("The rest of the detections are delayed and their time delays are listed in the info:")
The rest of the detections are delayed and their time delays are listed in the info:
disp(info)
    DetectionTime: [1 1 2 2 2]
            Delay: [3 3 0.5000 1 2]
     DeliveryTime: [4 4 2.5000 3 4]

Use Delay in Simulated Scenario

So far, you have learned how to simulate OOSMs using the objectDetectionDelay object with various ways to control the time delay. In this section, you integrate the objectDetectionDelay object into a scenario that includes target and sensor simulation. Create a tracking scenario and the visualization using the createScenario and createVisualization helper functions, respectively.

scenario = createScenario();
[tp1, tp2] = createVisualization(scenario);

You define the objectDetectionDelay object to only delay detections from sensor 1. In this case, you simulate the time delay as a combination of two delays: the sensor always lags by 1 or 2 simulation steps and the network adds a lag that is normally distributed with zero mean and a standard deviation of 0.03. To combine the two delays, you define the DelaySource as "Input".

delayer = objectDetectionDelay(SensorIndices = 1, DelaySource = "Input");

Run the following code and observe that the detections from sensor 1 (in red) always lag behind the object. You can also compare the detections with time delay (top) and without time delay (bottom).

while advance(scenario)
    simTime = scenario.SimulationTime;

    % Simulate all the detections from the scenario.
    simDets = detect(scenario);

    % Add a time delay of 1 or 2 simulation steps plus random delay.
    timeDelay = randi(2,1) / scenario.UpdateRate + 0.03 * randn;

    % Apply time delay to the detections.
    delayedDets = delayer(simDets, simTime, timeDelay);

    % Update visualization.
    updateVisualization(tp1, scenario, simDets);
    updateVisualization(tp2, scenario, delayedDets);
end

{"String":"Figure contains 2 axes objects and other objects of type uipanel. Axes object 1 contains 5 objects of type line, patch. These objects represent Target, Sensor 1 Detections, Sensor 2 Detections, Trajectory, Sensor Coverages. Axes object 2 contains 5 objects of type line, patch. These objects represent Target, Sensor 1 Detections, Sensor 2 Detections, Trajectory, Sensor Coverages.","Tex":[],"LaTex":[]}

Summary

In this example, you used the objectDetectionDelay object to simulate out-of-sequence measurements (OOSM). You learned how to control which sensors are affected by the time delay, how to define various random distributions to the delay, and how to use the time delay input argument. You can use the objectDetectionDelay object within a scenario simulation or with recorded data to simulate detections delays.

Supporting Functions

createScenario create a tracking scenario that has one platform with a waypoint trajectory and two radar sensors.

function scenario = createScenario
scenario = trackingScenario(UpdateRate = 10);
% Define a target
platform(scenario, Trajectory = waypointTrajectory([0 0 0; 100 0 0], [0;10]));

% Define a radar with SensorIndex = 1 mounted on one platform
radar1 = fusionRadarSensor(1, "No scanning", MountingAngles = [90 0 0], FieldOfView = [90 5], ...
    RangeResolution = 1, UpdateRate = 10, RangeLimits = [0 200], ...
    DetectionCoordinates  = "Scenario", HasINS = true, HasFalseAlarms = false);
r1p = platform(scenario, Sensors = radar1, Position = [50 -50 0]);
r1p.Signatures{1} = rcsSignature(Pattern = -1e5); % To avoid detection by other radar

% Define a radar with SensorIndex = 2 mounted on another platform
radar2 = fusionRadarSensor(2, "No scanning", MountingAngles = [-90 0 0], FieldOfView = [90 5], ...
    RangeResolution = 1, UpdateRate = 10, RangeLimits = [0 200], ...
    DetectionCoordinates  = "Scenario", HasINS = true, HasFalseAlarms = false);
r2p = platform(scenario, Sensors = radar2, Position = [50 50 0]);
r2p.Signatures{1} = rcsSignature(Pattern = -1e5); % To avoid detection by other radar
end

createVisualization create visualization for the tracking scenario

function [tp1, tp2] = createVisualization(scenario)
f = figure;
p1 = uipanel(f, Title = "Detections without Delay", Position = [0.01 0.01 0.98 0.48]);
p2 = uipanel(f, Title = "Detections with Delay", Position = [0.01 0.51 0.98 0.48]);
a1 = axes(p1);
a2 = axes(p2);
legend(a1, Location = "eastoutside", Orientation = "vertical");
legend(a2, Location = "eastoutside", Orientation = "vertical");
tp1 = theaterPlot(Parent = a1, XLimits = [-50 150], YLimits = [-50 50], ZLimits = [-50 50]);
tp2 = theaterPlot(Parent = a2, XLimits = [-50 150], YLimits = [-50 50], ZLimits = [-50 50]);
platformPlotter(tp1, DisplayName = "Target", Tag = "Target");
platformPlotter(tp2, DisplayName = "Target", Tag = "Target");
detectionPlotter(tp1, DisplayName = "Sensor 1 Detections", Tag = "Sensor1", MarkerEdgeColor = "r", MarkerFaceColor = "r");
detectionPlotter(tp1, DisplayName = "Sensor 2 Detections", Tag = "Sensor2", MarkerEdgeColor = "b", MarkerFaceColor = "b");
detectionPlotter(tp2, DisplayName = "Sensor 1 Detections", Tag = "Sensor1", MarkerEdgeColor = "r", MarkerFaceColor = "r");
detectionPlotter(tp2, DisplayName = "Sensor 2 Detections", Tag = "Sensor2", MarkerEdgeColor = "b", MarkerFaceColor = "b");
trp = trajectoryPlotter(tp1, DisplayName = "Trajectory");
traj = scenario.Platforms{1}.Trajectory;
sampledTimes = traj.TimeOfArrival(1):0.1:traj.TimeOfArrival(end);
pposition = lookupPose(traj,sampledTimes);
plotTrajectory(trp,{pposition});
trp = trajectoryPlotter(tp2, DisplayName = "Trajectory");
traj = scenario.Platforms{1}.Trajectory;
sampledTimes = traj.TimeOfArrival(1):0.1:traj.TimeOfArrival(end);
pposition = lookupPose(traj,sampledTimes);
plotTrajectory(trp,{pposition});
cvp = coveragePlotter(tp1, DisplayName = "Sensor Coverages", Alpha = [0.05 0.05]);
plotCoverage(cvp, coverageConfig(scenario));
cvp = coveragePlotter(tp2, DisplayName = "Sensor Coverages", Alpha = [0.05 0.05]);
plotCoverage(cvp, coverageConfig(scenario));
end

updateVisualization updates the visualization

function updateVisualization(tp, scenario, dets)
tarp = findPlotter(tp, Tag = "Target");
detp1 = findPlotter(tp, Tag = "Sensor1");
detp2 = findPlotter(tp, Tag = "Sensor2");
plotPlatform(tarp, scenario.Platforms{1}.Position);
dets = [dets{:}];
if ~isempty(dets)
    d1 = dets([dets.SensorIndex] == 1);
    plotDetection(detp1, detectionPositions(d1));
    d2 = dets([dets.SensorIndex] == 2);
    plotDetection(detp2, detectionPositions(d2));
end
end

detectionPositions extracts positions from the detections

function pos = detectionPositions(detections)
if ~isempty(detections)
    pos = [detections.Measurement]';
else
    pos = zeros(0,3);
end
end