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

Sensor performance improvement #1084

Merged
merged 3 commits into from
Oct 1, 2024
Merged
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
21 changes: 13 additions & 8 deletions stonesoup/functions/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Mathematical functions used within Stone Soup"""
import copy
import warnings
from functools import lru_cache

import numpy as np

Expand Down Expand Up @@ -707,10 +708,12 @@ def build_rotation_matrix(angle_vector: np.ndarray):
:class:`numpy.ndarray` of shape (3, 3)
The model (3D) rotation matrix.
"""
theta_x = -angle_vector[0, 0] # roll
theta_y = angle_vector[1, 0] # pitch#elevation
theta_z = -angle_vector[2, 0] # yaw#azimuth
return rotx(theta_x) @ roty(theta_y) @ rotz(theta_z)
return _build_rotation_matrix(angle_vector[0, 0], angle_vector[1, 0], angle_vector[2, 0])


@lru_cache()
def _build_rotation_matrix(theta_x, theta_y, theta_z):
return rotx(-theta_x) @ roty(theta_y) @ rotz(-theta_z)


def build_rotation_matrix_xyz(angle_vector: np.ndarray):
Expand All @@ -734,10 +737,12 @@ def build_rotation_matrix_xyz(angle_vector: np.ndarray):
:class:`numpy.ndarray` of shape (3, 3)
The model (3D) rotation matrix.
"""
theta_x = -angle_vector[0, 0] # roll
theta_y = angle_vector[1, 0] # pitch#elevation
theta_z = -angle_vector[2, 0] # yaw#azimuth
return rotz(theta_z) @ roty(theta_y) @ rotx(theta_x)
return _build_rotation_matrix_xyz(angle_vector[0, 0], angle_vector[1, 0], angle_vector[2, 0])


@lru_cache()
def _build_rotation_matrix_xyz(theta_x, theta_y, theta_z):
return rotz(-theta_z) @ roty(theta_y) @ rotx(-theta_x)


def dotproduct(a, b):
Expand Down
2 changes: 1 addition & 1 deletion stonesoup/sensor/passive.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ def measurement_model(self):
translation_offset=self.position,
rotation_offset=self.orientation)

def is_detectable(self, state: GroundTruthState) -> bool:
def is_detectable(self, state: GroundTruthState, measurement_model=None) -> bool:
return True

def is_clutter_detectable(self, state: Detection) -> bool:
Expand Down
94 changes: 61 additions & 33 deletions stonesoup/sensor/radar/radar.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,10 @@ def measurement_model(self):
translation_offset=self.position,
rotation_offset=self.orientation)

def is_detectable(self, state: GroundTruthState) -> bool:
measurement_vector = self.measurement_model.function(state, noise=False)
def is_detectable(self, state: GroundTruthState, measurement_model=None) -> bool:
if measurement_model is None:
measurement_model = self.measurement_model
measurement_vector = measurement_model.function(state, noise=False)
true_range = measurement_vector[1, 0] # Bearing(0), Range(1)
return true_range <= self.max_range

Expand Down Expand Up @@ -102,14 +104,23 @@ def measurement_model(self):
translation_offset=self.position,
rotation_offset=self.orientation)

def is_detectable(self, state: GroundTruthState) -> bool:
tmp_meas_model = CartesianToBearingRange(
ndim_state=self.ndim_state,
mapping=self.position_mapping,
noise_covar=self.noise_covar,
translation_offset=self.position,
rotation_offset=self.orientation
)
def is_detectable(self, state: GroundTruthState, measurement_model=None) -> bool:
if measurement_model is None:
tmp_meas_model = CartesianToBearingRange(
ndim_state=self.ndim_state,
mapping=self.position_mapping,
noise_covar=self.noise_covar,
translation_offset=self.position,
rotation_offset=self.orientation
)
else:
tmp_meas_model = CartesianToBearingRange(
ndim_state=measurement_model.ndim_state,
mapping=measurement_model.mapping,
noise_covar=measurement_model.noise_covar,
translation_offset=measurement_model.translation_offset,
rotation_offset=measurement_model.rotation_offset
)
measurement_vector = tmp_meas_model.function(state, noise=False)
true_range = measurement_vector[1, 0] # Bearing(0), Range(1)
return true_range <= self.max_range
Expand Down Expand Up @@ -180,8 +191,10 @@ def measure(self, ground_truths: Set[GroundTruthState], noise: Union[np.ndarray,

return super().measure(ground_truths, noise, **kwargs)

def is_detectable(self, state: GroundTruthState) -> bool:
measurement_vector = self.measurement_model.function(state, noise=False)
def is_detectable(self, state: GroundTruthState, measurement_model=None) -> bool:
if measurement_model is None:
measurement_model = self.measurement_model
measurement_vector = measurement_model.function(state, noise=False)

# Check if state falls within sensor's FOV
fov_min = -self.fov_angle / 2
Expand Down Expand Up @@ -265,21 +278,30 @@ def measure(self, ground_truths: Set[GroundTruthState], noise: Union[np.ndarray,

return super().measure(ground_truths, noise, **kwargs)

def is_detectable(self, state: GroundTruthState) -> bool:
antenna_heading = self.orientation[2, 0] + self.dwell_centre[0, 0]
# Set rotation offset of underlying measurement model
rot_offset = \
StateVector(
[[self.orientation[0, 0]],
[self.orientation[1, 0]],
[antenna_heading]])
tmp_meas_model = CartesianToBearingRange(
ndim_state=self.position_mapping,
mapping=self.position_mapping,
noise_covar=self.noise_covar,
translation_offset=self.position,
rotation_offset=rot_offset
)
def is_detectable(self, state: GroundTruthState, measurement_model=None) -> bool:
if measurement_model is None:
antenna_heading = self.orientation[2, 0] + self.dwell_centre[0, 0]
# Set rotation offset of underlying measurement model
rot_offset = \
StateVector(
[[self.orientation[0, 0]],
[self.orientation[1, 0]],
[antenna_heading]])
tmp_meas_model = CartesianToBearingRange(
ndim_state=self.ndim_state,
mapping=self.position_mapping,
noise_covar=self.noise_covar,
translation_offset=self.position,
rotation_offset=rot_offset
)
else:
tmp_meas_model = CartesianToBearingRange(
ndim_state=measurement_model.ndim_state,
mapping=measurement_model.mapping,
noise_covar=measurement_model.noise_covar,
translation_offset=measurement_model.translation_offset,
rotation_offset=measurement_model.rotation_offset
)
measurement_vector = tmp_meas_model.function(state, noise=False)

# Check if state falls within sensor's FOV
Expand Down Expand Up @@ -322,8 +344,10 @@ def measurement_model(self):
translation_offset=self.position,
rotation_offset=self.orientation)

def is_detectable(self, state: GroundTruthState) -> bool:
measurement_vector = self.measurement_model.function(state, noise=False)
def is_detectable(self, state: GroundTruthState, measurement_model=None) -> bool:
if measurement_model is None:
measurement_model = self.measurement_model
measurement_vector = measurement_model.function(state, noise=False)
true_range = measurement_vector[2, 0] # Elevation(0), Bearing(1), Range(2)
return true_range <= self.max_range

Expand Down Expand Up @@ -363,8 +387,10 @@ def measurement_model(self):
velocity=self.velocity,
rotation_offset=self.orientation)

def is_detectable(self, state: GroundTruthState) -> bool:
measurement_vector = self.measurement_model.function(state, noise=False)
def is_detectable(self, state: GroundTruthState, measurement_model=None) -> bool:
if measurement_model is None:
measurement_model = self.measurement_model
measurement_vector = measurement_model.function(state, noise=False)
true_range = measurement_vector[1, 0] # Bearing(0), Range(1), Range-Rate(2)
return true_range <= self.max_range

Expand Down Expand Up @@ -403,8 +429,10 @@ def measurement_model(self):
velocity=self.velocity,
rotation_offset=self.orientation)

def is_detectable(self, state: GroundTruthState) -> bool:
measurement_vector = self.measurement_model.function(state, noise=False)
def is_detectable(self, state: GroundTruthState, measurement_model=None) -> bool:
if measurement_model is None:
measurement_model = self.measurement_model
measurement_vector = measurement_model.function(state, noise=False)
true_range = measurement_vector[2, 0] # Elevation(0), Bearing(1), Range(2), Range-Rate(3)
return true_range <= self.max_range

Expand Down
13 changes: 13 additions & 0 deletions stonesoup/sensor/radar/tests/test_radar.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,8 @@ def test_simple_radar(h, sensorclass, ndim_state, pos_mapping, noise_covar, posi

truth = {target_truth}

assert radar.is_detectable(target_truth)

# Generate a noiseless measurement for the given target
measurement = radar.measure(truth, noise=False)
measurement = next(iter(measurement)) # Get measurement from set
Expand Down Expand Up @@ -146,6 +148,7 @@ def test_simple_radar(h, sensorclass, ndim_state, pos_mapping, noise_covar, posi

# Generate a noiseless measurement for each of the given target states
measurements = radar.measure(truth)
assert all(radar.is_detectable(t) for t in truth)

# Two measurements for 2 truth states
assert len(measurements) == 2
Expand All @@ -165,6 +168,7 @@ def test_simple_radar(h, sensorclass, ndim_state, pos_mapping, noise_covar, posi

# Check no detection have been made when target is out of range
assert (len(measurement3) == 0)
assert not radar.is_detectable(target3_truth)


def h2d_rr(state, pos_map, vel_map, translation_offset, rotation_offset, velocity):
Expand Down Expand Up @@ -259,6 +263,8 @@ def test_range_rate_radar(h, sensorclass, pos_mapping, vel_mapping, noise_covar,
target_truth = GroundTruthPath([target_state])
truth = {target_truth}

assert radar.is_detectable(target_truth)

# Generate a noiseless measurement for the given target
measurement = radar.measure(truth, noise=False)
measurement = next(iter(measurement)) # Get measurement from set
Expand Down Expand Up @@ -288,6 +294,7 @@ def test_range_rate_radar(h, sensorclass, pos_mapping, vel_mapping, noise_covar,

# Two measurements for 2 truth states
assert len(measurements) == 2
assert all(radar.is_detectable(t) for t in truth)

# Measurements store ground truth paths
for measurement in measurements:
Expand Down Expand Up @@ -391,6 +398,7 @@ def test_rotating_radar(sensorclass, radar_position, radar_orientation, state,

# Assert no measurements since target is not in FOV
assert len(measurement) == 0
assert not radar.is_detectable(target_truth)

# Rotate radar such that the target is in FOV
timestamp = timestamp + datetime.timedelta(seconds=0.5)
Expand All @@ -417,6 +425,7 @@ def test_rotating_radar(sensorclass, radar_position, radar_orientation, state,
assert (np.equal(measurement.state_vector, eval_m[0]).all())
else:
assert (np.equal(measurement.state_vector, eval_m).all())
assert radar.is_detectable(target_truth)

# Assert is TrueDetection type
assert isinstance(measurement, TrueDetection)
Expand All @@ -433,6 +442,7 @@ def test_rotating_radar(sensorclass, radar_position, radar_orientation, state,

# Two measurements for 2 truth states
assert len(measurements) == 2
assert all(radar.is_detectable(t) for t in truth)

# Measurements store ground truth paths
for measurement in measurements:
Expand Down Expand Up @@ -491,6 +501,7 @@ def test_raster_scan_radar():

# Assert no measurements since target is not in FOV
assert len(measurement) == 0
assert not radar.is_detectable(target_truth)

# Rotate radar
timestamp = timestamp + datetime.timedelta(seconds=0.5)
Expand All @@ -505,6 +516,7 @@ def test_raster_scan_radar():

# Assert no measurements since target is not in FOV
assert len(measurement) == 0
assert not radar.is_detectable(target_truth)

# Rotate radar such that the target is in FOV
timestamp = timestamp + datetime.timedelta(seconds=1.0)
Expand All @@ -528,6 +540,7 @@ def test_raster_scan_radar():
# Assert correction of generated measurement
assert measurement.timestamp == target_state.timestamp
assert np.array_equal(measurement.state_vector, eval_m)
assert radar.is_detectable(target_truth)

# Assert is TrueDetection type
assert isinstance(measurement, TrueDetection)
Expand Down
4 changes: 2 additions & 2 deletions stonesoup/sensor/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ def measure(self, ground_truths: Set[GroundTruthState], noise: Union[np.ndarray,
measurement_model = self.measurement_model

detectable_ground_truths = [truth for truth in ground_truths
if self.is_detectable(truth)]
if self.is_detectable(truth, measurement_model)]

if noise is True:
if len(detectable_ground_truths) > 1:
Expand Down Expand Up @@ -126,7 +126,7 @@ def measure(self, ground_truths: Set[GroundTruthState], noise: Union[np.ndarray,
return detections

@abstractmethod
def is_detectable(self, state: GroundTruthState) -> bool:
def is_detectable(self, state: GroundTruthState, measurement_model=None) -> bool:
raise NotImplementedError

@abstractmethod
Expand Down