Main Content

Simulate Radar Detections of Surface Targets in Clutter

This example shows how to simulate the detection of targets in surface clutter. You will first see how to use a statistical radar model to examine the effect of Doppler separation on detectability, then use terrain data to see how line-of-sight occlusions can be simulated in a radar scenario.

rng default

Detecting Surface Targets With Clutter Doppler Separation

This section demonstrates how Doppler separation allows moving targets to be detected even when surface reflections are significant. You will configure a radar scenario to simulate a typical side-looking airborne radar that is pointing at a surface target. You will first let the target be stationary, then calculate a velocity vector that separates the target from clutter, and examine the resulting detections.

Stationary Target

Start by creating a radarDataGenerator. Set MountingAngles so that the radar is pointing to the right of the platform (a -90 degree mounting yaw angle) and pointing down at 15 degrees (a 15 degree depression angle). Use a frequency of 1 GHz, 100 meter range resolution, a 5 kHz PRF, and 128 pulses. The beam is symmetric with a 4 degree two-sided beamwidth in azimuth and elevation.

mountingYPR = [-90 15 0];
fc = 1e9;
rangeRes = 100;
prf = 5e3;
numPulses = 128;
beamwidth = 4;

Use the PRF and number of pulses specification to calculate the nominal Doppler and range-rate resolution.

dopRes = prf/numPulses;
lambda = freq2wavelen(fc);
rangeRateRes = dop2speed(dopRes,lambda)/2;

Pulse-Doppler radars have both range and range-rate ambiguities. Calculate the unambiguous range and radial speed.

unambRange = time2range(1/prf);
unambRadialSpd = dop2speed(prf/4,lambda);

Construct the radarDataGenerator. Let the sensor index equal 1 and disable scanning functionality. The radar will update once per coherent processing interval (CPI). Configure the operating mode to be monostatic, with detections output in scenario coordinates. Enable elevation angle measurements, range-rate measurements, and the range and range-rate ambiguities. Disable false alarms and detection noise so that the effect of Doppler separation can be seen clearly. Lastly, set the reference target used to compute the loop gain to a 0 dBsm target at 20 km range, with a detection probability of 90%.

cpiTime = numPulses/prf;
rdr = radarDataGenerator(1,'No scanning','UpdateRate',1/cpiTime,'MountingAngles',mountingYPR,...
    'DetectionMode','Monostatic','TargetReportFormat','Detections','DetectionCoordinates','Scenario',...
    'HasINS',true,'HasElevation',true,'HasFalseAlarms',false,'HasNoise',false,'HasRangeRate',true,...
    'HasRangeAmbiguities',true,'HasRangeRateAmbiguities',true,'MaxUnambiguousRadialSpeed',unambRadialSpd,...
    'MaxUnambiguousRange',unambRange,'CenterFrequency',fc,'FieldOfView',beamwidth,...
    'AzimuthResolution',beamwidth,'ElevationResolution',beamwidth,'RangeResolution',rangeRes,'RangeRateResolution',rangeRateRes,...
    'ReferenceRange',20e3,'ReferenceRCS',0,'DetectionProbability',0.9);

Create the scenario, setting the update rate to 0 to let the update intervals be derived from sensors in the scene.

scene = radarScenario('UpdateRate',0,'IsEarthCentered',false);

The landSurface scenario method is used to define properties of regions of the scenario surface. You will use it here to specify an unbounded homogeneous surface with an associated reflectivity model. Use a constant-gamma reflectivity with a gamma value appropriate for flatland, which can be found with the surfacegamma function. surfaceReflectivityLand is used to create the reflectivity model that will be associated with the scenario surface through the RadarReflectivity property.

gammaDB = surfacegamma('Flatland');
refl = surfaceReflectivityLand('Model','ConstantGamma','Gamma',gammaDB);
landSurface(scene,'RadarReflectivity',refl);

Now define the radar platform. The platform will start 4 km above the origin, and fly in the +Y direction at 200 m/s. Set the platform orientation such that the platform-frame X is aligned with the direction of motion. With the mounting angles specified on the radar, this means the radar will be pointed in the +X direction.

altitude = 4e3;
rdrpos = [0 0 altitude];
rdrspd = 200;
rdrtraj = kinematicTrajectory('Position',rdrpos,'Velocity',[0 rdrspd 0],'Orientation',rotz(90).');
rdrplat = platform(scene,'Sensors',rdr,'Trajectory',rdrtraj);

The figure below shows a visualization of this scenario geometry, including the homogeneous land surface.

BroadsideGeom.png

Place the target on the surface along the radar boresight direction. The required ground range can be found from the radar altitude and depression angle.

gndrng = altitude/tand(mountingYPR(2));
tgtpos = [gndrng 0 0];

Choose the target RCS such that the return signal would be buried in clutter without any Doppler separation. Do this by calculating an approximate RCS of clutter in a broadside range-Doppler resolution cell, and then setting the target RCS to one quarter of that. The reflectivity model defined previously can be used to get the NRCS, and the approximate area of the resolution cell can be found with the provided helper function. Express the RCS in dBsm.

clutterCellNRCS = refl(mountingYPR(2),fc);
clutterCellArea = helperBroadsideResCellArea(rangeRes,rangeRateRes,gndrng,mountingYPR(2),rdrspd);
clutterCellRCS = clutterCellNRCS*clutterCellArea;
tgtrcs = clutterCellRCS/4;
tgtrcs = 10*log10(tgtrcs);

Calculate the expected SNR of the target for later comparison to the SNR of the resulting detections. The RadarLoopGain property of the radar gives the relationship between target RCS, range, and SNR.

tgtrng = sqrt(altitude^2 + gndrng^2);
tgtsnr =  tgtrcs - 40*log10(tgtrng) + rdr.RadarLoopGain
tgtsnr = 32.3399

Use the scenario platform method to create a stationary target platform and add it to the scene. The rcsSignature class can be used to define a constant-RCS signature by using a scalar RCS value for the Pattern property.

tgtplat = platform(scene,'Position',tgtpos,'Signatures',rcsSignature('Pattern',tgtrcs));

Now, use the scenario clutterGenerator method to construct a clutter generator and enable clutter generation for your radar. The Resolution property defines the nominal spacing of clutter patches. Set this to be 1/5th of the range resolution to get multiple clutter patches per range gate. Set the range limit to 20 km to encompass the entire beam. UseBeam indicates if clutter generation should be performed automatically for the mainlobe of the antenna pattern, or for the field of view of a radarDataGenerator, as defined by the FieldOfView property.

clut = clutterGenerator(scene,rdr,'Resolution',rangeRes/5,'RangeLimit',20e3,'UseBeam',true);

Now the simulation is fully configured. Run the simulation for a single frame, collecting detections from the radar you constructed. This can be done by calling the scenario detect method.

dets = detect(scene);

Examine one of these detections.

dets{1}
ans = 
  objectDetection with properties:

                     Time: 0
              Measurement: [6×1 double]
         MeasurementNoise: [6×6 double]
              SensorIndex: 1
            ObjectClassID: 0
    ObjectClassParameters: []
    MeasurementParameters: [1×1 struct]
         ObjectAttributes: {[1×1 struct]}

Each objectDetection contains information about a detection made in a particular resolution cell. The Measurement property will give the position and velocity of the detection in scenario coordinates, and SensorIndex refers to the index assigned to the radarDataGenerator upon construction. Useful metadata can be found by inspecting the contents of the ObjectAttributes property.

dets{1}.ObjectAttributes{1}
ans = struct with fields:
    TargetIndex: 14
            SNR: 38.8084

The TargetIndex field gives the index of the target that generated the detection. If multiple targets fall inside the same resolution cell, this will be the index of the target in that cell with the greatest signal power. This index corresponds to the PlatformID property of the target platforms. This ID is assigned automatically upon creating a platform, and increments by 1 with each new platform. When clutter is being simulated, the target index may exceed the number of platforms added to the scenario, which indicates that the detection came from a resolution cell where clutter return was dominant.

The low SNR of the first detection shown above is due to the sorted output from the radarDataGenerator. Within the field of view of the radar, clutter power in cells on the low side of the range swath are smaller than those at the center, near the intersection of the radar boresight with the ground plane, where the target platform is located. Plot the SNR across all clutter detections to verify the clutter power statistics are as expected.

helperPlotDetectionSNR(dets)
title('Clutter Detection SNR')

Figure contains an axes object. The axes object with title Clutter Detection SNR, xlabel Detection Index, ylabel SNR (dB) contains a line object which displays its values using only markers.

The distinct lines correspond to different Doppler bins. The mean SNR of detections in the 0 Hz Doppler bin (corresponding to the higher trend line) is about 6 dB higher than the target SNR, as expected.

Since the target platform of interest was the second platform added to the scene, it will have an ID of 2.

tgtplat.PlatformID
ans = 2

This can be used to find the object detection corresponding to the target platform, if the target was detected. Get the TargetIndex across all detections and see if any correspond to the target platform ID.

tgtidx = cellfun(@(t) t.ObjectAttributes{1}.TargetIndex,dets);
tgtdet = dets(tgtidx == tgtplat.PlatformID);
detectedPlatform = ~isempty(tgtdet)
detectedPlatform = logical

   0

In this scenario, the target platform was not detected because the target return signal fell in the same resolution cell as clutter return. Use the provided helper function to plot a visualization of the target and clutter patches in range-Doppler space with an overlay of the boundaries of the Doppler resolution cells.

helperPlotRangeDoppler(clut,rdrplat,tgtplat,lambda,prf,dopRes)

Figure contains an axes object. The axes object with xlabel Doppler (Hz), ylabel Range (km) contains 4 objects of type line. One or more of the lines displays its values using only markers These objects represent Clutter Patches, Target, Doppler Bin Edges.

Since the target was placed on the surface along the antenna boresight and is stationary, it appears at the same point in range-Doppler space as the centroid of the mainlobe clutter return, and is undetectable.

Moving Target

Now you will calculate a velocity vector for the target so it moves along the surface towards the radar (in the -X direction) at a speed such that the return signal will fall outside of the clutter return in Doppler.

First calculate the total range-rate spread of the mainlobe clutter return. This equation is valid for broadside clutter, where the antenna boresight vector is perpendicular to the radar velocity.

clutSpread = 2*rdrspd*sind(beamwidth/2);

Calculate the required closing rate such that the target is one Doppler resolution cell outside of the mainlobe clutter return. Also calculate the total required speed, which can be found by dividing by the magnitude of the X component of the line of sight to the radar position.

los = (rdrpos - tgtpos)/tgtrng;
tgtClosingRate = clutSpread/2 + rangeRateRes;
tgtspd = tgtClosingRate/abs(los(1));

Set the target velocity by accessing the platform trajectory, which is a kinematicTrajectory by default.

tgtplat.Trajectory.Velocity = [-tgtspd 0 0];

Run the simulation for another frame.

dets = detect(scene);

Once again, get the target index for each detection and search for one that corresponds to the target platform.

tgtidx = cellfun(@(t) t.ObjectAttributes{1}.TargetIndex,dets);
tgtdet = dets(tgtidx == tgtplat.PlatformID);
detectedPlatform = ~isempty(tgtdet)
detectedPlatform = logical

   0

This time, the target was detected. Inspect the measured position and object attributes of the target detection.

if detectedPlatform
    tgtdet{1}.Measurement(1:3)
    tgtdet{1}.ObjectAttributes{:}
end

The measured target position is accurate to within a few meters, and the reported SNR is as found earlier via the radar loop gain.

View the returns in range-Doppler space.

helperPlotRangeDoppler(clut,rdrplat,tgtplat,lambda,prf,dopRes)

Figure contains an axes object. The axes object with xlabel Doppler (Hz), ylabel Range (km) contains 6 objects of type line. One or more of the lines displays its values using only markers These objects represent Clutter Patches, Target, Doppler Bin Edges.

The target return has a Doppler frequency that is one Doppler resolution cell greater than the maximum Doppler of the mainlobe clutter return. Despite the small RCS of the target relative to clutter, it was detectable thanks to Doppler separation.

Detecting Surface Targets in the Presence of Terrain

Terrain Clutter and Occlusions

In this section, you will again construct a radar scenario and examine the detectability of a target in clutter. This time you will use terrain data to place the target on the surface, generate clutter returns, and simulate line-of-sight occlusions between the radar and target.

Start by loading the sample terrain data. This contains the variables terrain and boundary, which will be passed to the landSurface method.

load MountainousTerrain.mat

Create the scenario as before. Use the landSurface method to define a rectangular region of terrain by specifying a matrix of height samples and a boundary for the region in two-point form.

scene = radarScenario('UpdateRate',0,'IsEarthCentered',false);
srf = landSurface(scene,'RadarReflectivity',refl,'Terrain',terrain,'Boundary',boundary);

The terrain data covers a region of about 23 km in the X direction and 17 km in the Y direction. This can be seen by inspecting the Boundary property.

srf.Boundary
ans = 2×2
104 ×

         0    2.2920
   -0.8544    0.8544

The radar will again be placed above the origin while viewing a target platform that is moving along the surface towards the radar from the +X direction. Re-create the radar platform, this time using the height method of the surface object to place the radar above the surface at the same altitude used previously.

rdrpos(3) = height(srf,rdrpos) + altitude;
rdrtraj = kinematicTrajectory('Position',rdrpos,'Velocity',[0 rdrspd 0],'Orientation',rotz(90).');
rdrplat = platform(scene,'Sensors',rdr,'Trajectory',rdrtraj);

Re-create the target platform, using the height method to place the target platform on the surface.

tgtpos(3) = height(srf,tgtpos);
tgttraj = kinematicTrajectory('Position',tgtpos,'Velocity',[-tgtspd 0 0]);
tgtplat = platform(scene,'Trajectory',tgttraj,'Signatures',rcsSignature('Pattern',tgtrcs));

Enable clutter generation. This time, since terrain data is being used, the Resolution property is not relevant. Clutter patches will correspond to facets of the terrain data.

clut = clutterGenerator(scene,rdr,'RangeLimit',20e3,'UseBeam',true);

The actual spacing of clutter patches in the X and Y directions can be found by dividing the extent of the surface in each direction by the number of height samples. Note that the X and Y directions are defined for the matrix of height sampled following the meshgrid convention, where the first dimension of the matrix (columns) corresponds to Y and the second dimension (rows) to X.

terrainClutterSpacing = diff(boundary,1,2)./flip(size(terrain)).'
terrainClutterSpacing = 2×1

   29.9608
   25.6188

The terrain data is sampled at 25 to 30 meter resolution, which is sufficient to yield a few clutter patches per resolution cell.

Collect one frame of detections.

dets = detect(scene);

Since the target is moving with the same velocity vector as before, a detection of the target platform might be expected. Plot the clutter patches and target in range-Doppler space again to verify.

helperPlotRangeDoppler(clut,rdrplat,tgtplat,lambda,prf,dopRes)

Figure contains an axes object. The axes object with xlabel Doppler (Hz), ylabel Range (km) contains 6 objects of type line. One or more of the lines displays its values using only markers These objects represent Clutter Patches, Target, Doppler Bin Edges.

Notice that this time, the target is separated from clutter in both range and Doppler. Notice also that the clutter return is closer in range than in the flat-surface case by about 3 km. The overall extent of clutter in Doppler is the same since that depends only on beamwidth, not the shape of the surface.

Check to see if an object detection corresponding to the target platform was generated.

tgtidx = cellfun(@(t) t.ObjectAttributes{1}.TargetIndex,dets);
tgtdet = dets(tgtidx == tgtplat.PlatformID);
detectedPlatform = ~isempty(tgtdet)
detectedPlatform = logical

   0

The target was not detected. Plot the scenario overview to see why, adjusting the view so the effect of terrain can be seen more easily.

helperPlotClutterScenario(scene)
set(gca,'view',[-115 22])

Figure contains an axes object. The axes object with xlabel X, ylabel Y contains 10 objects of type surface, line, quiver, text. One or more of the lines displays its values using only markers

The same geometry parameters were used here as in the homogeneous surface scenario. The target still lies directly along the antenna boresight, but the mainlobe intersects a region of terrain that is at a lower range than the target. The target is thus occluded by the terrain and is not detectable. The occlusion of the LOS to the target can be checked explicitly by using the occlusion method on the surface object. This method will take two points as input and indicate whether or not the LOS between them is occluded by the surface. The platform positions can be passed in directly.

occluded = occlusion(srf,rdrplat.Position,tgtplat.Position)
occluded = logical

   1

This verifies the observation from the scenario overview plot that the target is occluded from the radar by the terrain.

Surface objects in the scenario are managed by a surface manager, which can be accessed through the SurfaceManager property of the scenario. Inspect this property.

scene.SurfaceManager
ans = 
  SurfaceManager with properties:

    UseOcclusion: 1
        Surfaces: [1×1 radar.scenario.LandSurface]

The manager contains a read-only vector of surface objects present in the scenario, and a logical indicator that can be used to enable/disable line-of-sight occlusions by surface features. Try disabling this now, collecting detections for another frame, and checking for a target platform detection.

scene.SurfaceManager.UseOcclusion = false;
dets = detect(scene);
tgtidx = cellfun(@(t) t.ObjectAttributes{1}.TargetIndex,dets);
tgtdet = dets(tgtidx == tgtplat.PlatformID);
detectedPlatform = ~isempty(tgtdet)
detectedPlatform = logical

   1

With surface occlusion disabled, the platform is detectable.

Simulate Multiple Surface Targets

In this next section, you will place multiple platforms at random positions on the surface and simulate detections while observing terrain occlusions, included automatically as part of the scenario detect method. Clutter detections will not be simulated here in order to simplify the simulation and highlight the terrain occlusion effects (that is, assume all targets are detectable given that they are visible to the radar). To include interference from clutter, the clutterGenerator method could be used as before, though the targets would need to be in motion or have large RCS signatures to be detectable.

Re-create the scenario, using the same terrain data.

scene = radarScenario('UpdateRate',0,'IsEarthCentered',false);
srf = landSurface(scene,'RadarReflectivity',refl,'Terrain',terrain,'Boundary',boundary);

Release the radar object so that it may be reconfigured. Using the same field of view, enable electronic scanning over 90 degrees in azimuth and 16 degrees in elevation in order to cover most of the surface. All targets will be illuminated over the course of a full scan.

release(rdr)
rdr.ScanMode = 'Electronic';
rdr.ElectronicAzimuthLimits = [-45 45];
rdr.ElectronicElevationLimits = [-8 8];

Modify the trajectory created above to place the radar platform at one corner of the loaded terrain data. The same straight-line trajectory will be re-used. Set the scenario stop time as needed to give the radar time to travel from one end of the terrain to the other, in the Y direction. This will allow for the line of sight to some targets to transition between occluded and unoccluded.

rdrtraj.Position(2) = boundary(2,1);
rdrplat = platform(scene,'Sensors',rdr,'Trajectory',rdrtraj);
scene.StopTime = diff(srf.Boundary(2,:))/rdrspd;

Place down targets at random positions on the surface. The minimum value of the position in the X direction is chosen so that all targets will fall within the field of view of the radar at some point during the scan. The targets are uniformly distributed in the Y direction, and the height method is again used to place them on the surface.

numtgt = 14;
tgtpos(1,1:numtgt) = 1e4 + rand(1,numtgt)*(srf.Boundary(1,2) - 1e4);
tgtpos(2,:) = srf.Boundary(2,1) + rand(1,numtgt)*diff(srf.Boundary(2,:),1,2);
tgtpos(3,:) = height(srf,tgtpos);
for ind = 1:numtgt
   platform(scene,'Position',tgtpos(:,ind).');
end

Each iteration of the simulation loop constitutes one scan position. The second output from the scenario detect method, config, returns some information on the configuration of the radarDataGenerator that produced the detections. The IsScanDone field is a logical scalar that indicates if the radar was on its final scan position on the last call to detect. The detections from each scan position will be saved and when the scan pattern is completed, a visualization of the occlusion states and resulting detections will be plotted.

figure;
p = get(gcf,'Position');
set(gcf,'Position',[p(1:2) 2*p(3:4)])
fullScanDets = [];
while advance(scene)
    [dets,config] = detect(scene);
    fullScanDets = [fullScanDets; dets];
    if config.IsScanDone
        helperPlotOcclusions(scene,fullScanDets)
        drawnow
        fullScanDets = [];
    end
end

Figure contains an axes object. The axes object with title Simulation Time: 85.4016 contains 30 objects of type surface, line. One or more of the lines displays its values using only markers These objects represent Unoccluded LOS, Target Position, Occluded LOS.

The below gif shows the resulting animation. Since all visible targets are illuminated at some point during a complete scan, detections are made whenever a target is visible, while targets with occluded lines of sight are not detected.

occlusions.gif

Conclusion

In this example, you saw how to configure a radarScenario and radarDataGenerator to simulate detections of a surface target in clutter. You used a ClutterGenerator with a simple homogeneous surface to examine the effect of Doppler separation, then used terrain data to simulate the effect of line-of-sight occlusions on detectability.

Supporting Functions

helperBroadsideResCellArea

function A = helperBroadsideResCellArea( rangeRes,rangeRateRes,groundRange,graze,speed )
% Calculate the approximate area of a broadside range-Doppler resolution
% cell

% Ground range resolution
groundRangeRes = rangeRes/cosd(graze);

% Broadside angular variation corresponding to Doppler resolution
dtheta = 2*asin(rangeRateRes/(2*speed));

% Multiply by ground range to get cross-range extent
crossRangeWidth = groundRange*dtheta;

% Cell area
A = crossRangeWidth*groundRangeRes;

end

helperPlotDetectionSNR

function helperPlotDetectionSNR( dets )
% Plot the SNR of a set of objectDetections

detsnr = cellfun(@(t) t.ObjectAttributes{1}.SNR,dets);
plot(detsnr,'.')
grid on
xlabel('Detection Index')
ylabel('SNR (dB)')

end

helperPlotRangeDoppler

function helperPlotRangeDoppler( clut,rdrplat,tgtplat,lambda,prf,dopRes )
% Plot the range-Doppler truth of clutter patches and a target, along with
% an indication of the edges of the Doppler resolution cells.

% Get range and Doppler to target
los = rdrplat.Position - tgtplat.Position;
tgtRange = norm(los);
los = los/tgtRange;
tgtDoppler = 2/lambda*sum((tgtplat.Trajectory.Velocity-rdrplat.Trajectory.Velocity).*los);

% Get range and Doppler to clutter patches
patches = clut.LastPatchData.Centers;
los = patches - rdrplat.Position(:);
clutterRange = sqrt(sum(los.^2,1));
los = los ./ clutterRange;
clutterDoppler = 2/lambda*sum(rdrplat.Trajectory.Velocity(:).*los,1);

% Plot clutter and target in range-Doppler space
plot(clutterDoppler,clutterRange/1e3,'.','markersize',.1)
hold on
plot(tgtDoppler,tgtRange/1e3,'*r','markersize',15)

% Plot the Doppler bin edges
xl = xlim;
yl = ylim;
dopBins = -prf/2:dopRes:(prf/2-dopRes);
dopBinEdges = [dopBins(1)-dopRes/2, dopBins+dopRes/2];
dopBinEdges = dopBinEdges(dopBinEdges >= xl(1) & dopBinEdges <= xl(2));
line([1 1].'*dopBinEdges,repmat(yl,numel(dopBinEdges),1).','color','magenta','linestyle','--')
hold off

legend('Clutter Patches','Target','Doppler Bin Edges','Location','northwest')

xlabel('Doppler (Hz)')
ylabel('Range (km)')

end

helperPlotOcclusions

function helperPlotOcclusions( scene,dets )
% Plot the scenario surface along with target and radar positions and the
% lines of sight between them, color coding to indicate the occlusion state
% of each line of sight. Also plot the position of any detections.

% Plot the surface
helperPlotScenarioSurface(scene.SurfaceManager.Surfaces(1))
hold on

% Find radar platform and get current position
plats = [scene.Platforms{:}];
hasSensor = ~cellfun(@isempty,{plats.Sensors});
rdrplat = plats(find(hasSensor,1));
rdrpos = rdrplat.sensorPosition(1);

% Target platforms
tgtplats = plats(plats ~= rdrplat);

for ind = 1:numel(tgtplats) % for each target

    % Position of current target
    tgtpos = tgtplats(ind).Position;
    
    hndls(1) = plot3(tgtpos(1),tgtpos(2),tgtpos(3),'oblue','MarkerSize',10);
    
    % Get occlusion state by querying the scenario surface manager
    occ = occlusion(scene.SurfaceManager,rdrpos,tgtpos,scene.SimulationTime);

    % Plot color-coded line of sight
    if occ
        hndls(2) = line([rdrpos(1) tgtpos(1)],[rdrpos(2) tgtpos(2)],[rdrpos(3) tgtpos(3)],'color','red');
    else
        hndls(3) = line([rdrpos(1) tgtpos(1)],[rdrpos(2) tgtpos(2)],[rdrpos(3) tgtpos(3)],'color','green');
    end

end

% Keep only detections originating from target platforms
tgtidx = cellfun(@(t) t.ObjectAttributes{1}.TargetIndex,dets);
targetDetection = any(tgtidx == [plats.PlatformID],2);
dets = dets(targetDetection);

% Plot detections
if ~isempty(dets)
    detpos = cell2mat(cellfun(@(t) t.Measurement(1:3),dets.','UniformOutput',0));
    plot3(detpos(1,:),detpos(2,:),detpos(3,:),'omagenta','MarkerSize',9,'MarkerFaceColor','magenta');
end

hold off
legendstr = ["Target Position" "Occluded LOS" "Unoccluded LOS" "Detection Positions"];
I = ishghandle(hndls);
hndls = hndls(I);
legendstr = legendstr(I);
legend(hndls,legendstr,'Location','Northwest');
title(sprintf('Simulation Time: %.4f',scene.SimulationTime))
view([120 28])

end