Skip to content

Commit 6831635

Browse files
authored
Merge pull request #621 from dstl/sensor_suite
Add sensor suite type
2 parents 468cfcc + 9280e96 commit 6831635

File tree

2 files changed

+119
-7
lines changed

2 files changed

+119
-7
lines changed

stonesoup/sensor/sensor.py

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
from abc import abstractmethod, ABC
2-
from typing import Set, Union
2+
from typing import Set, Union, Sequence
33

44
import numpy as np
55

6-
from stonesoup.types.detection import TrueDetection
7-
from stonesoup.types.groundtruth import GroundTruthState
8-
from .base import PlatformMountable
6+
from ..base import Property
7+
from ..sensor.base import PlatformMountable
8+
from ..types.detection import TrueDetection
9+
from ..types.groundtruth import GroundTruthState
910

1011

1112
class Sensor(PlatformMountable, ABC):
@@ -38,3 +39,42 @@ def measure(self, ground_truths: Set[GroundTruthState], noise: Union[np.ndarray,
3839
from.
3940
"""
4041
raise NotImplementedError
42+
43+
44+
class SensorSuite(Sensor):
45+
"""Sensor composition type
46+
47+
Models a suite of sensors all returning detections at the same 'time'. Returns all detections
48+
in one go.
49+
Can append information of the sensors to the metadata of their corresponding detections.
50+
"""
51+
52+
sensors: Sequence[Sensor] = Property(doc="Suite of sensors to get detections from.")
53+
54+
attributes_inform: Set[str] = Property(
55+
doc="Names of attributes to store the value of at time of detection."
56+
)
57+
58+
def measure(self, ground_truths: Set[GroundTruthState], noise: Union[bool, np.ndarray] = True,
59+
**kwargs) -> Set[TrueDetection]:
60+
"""Call each sub-sensor's measure method in turn. Key word arguments are passed to the
61+
measure method of each sensor.
62+
63+
Append additional metadata to each sensor's set of detections. Which keys are appended is
64+
dictated by :attr:`attributes_inform`."""
65+
66+
all_detections = set()
67+
68+
for sensor in self.sensors:
69+
70+
detections = sensor.measure(ground_truths, noise, **kwargs)
71+
72+
attributes_dict = {attribute_name: sensor.__getattribute__(attribute_name)
73+
for attribute_name in self.attributes_inform}
74+
75+
for detection in detections:
76+
detection.metadata.update(attributes_dict)
77+
78+
all_detections.update(detections)
79+
80+
return all_detections

stonesoup/sensor/tests/test_sensor.py

Lines changed: 75 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,15 @@
33

44
import pytest
55

6-
from stonesoup.sensor.radar.radar import RadarElevationBearingRangeRate
6+
from stonesoup.models.measurement.nonlinear import CartesianToBearingRange, \
7+
CartesianToElevationBearingRange
8+
from stonesoup.movable import FixedMovable
9+
from stonesoup.sensor.radar.radar import RadarElevationBearingRangeRate, RadarBearingRange, \
10+
RadarElevationBearingRange
711
from stonesoup.platform import FixedPlatform
12+
from stonesoup.types.groundtruth import GroundTruthPath, GroundTruthState
813
from ..base import PlatformMountable
9-
from ..sensor import Sensor
14+
from ..sensor import Sensor, SensorSuite
1015
from ...types.array import StateVector, CovarianceMatrix
1116
from ...types.state import State
1217

@@ -68,7 +73,7 @@ def test_changing_platform_from_default():
6873
position = StateVector([0, 0, 1])
6974
sensor = DummySensor(position=StateVector([0, 0, 1]))
7075

71-
platform_state = State(state_vector=position+1, timestamp=datetime.datetime.now())
76+
platform_state = State(state_vector=position + 1, timestamp=datetime.datetime.now())
7277
platform = FixedPlatform(states=platform_state, position_mapping=[0, 1, 2])
7378
with pytest.raises(AttributeError):
7479
platform.add_sensor(sensor)
@@ -157,3 +162,70 @@ def test_sensor_assignment(radar_platform_target):
157162
platform.remove_sensor(radar_3)
158163
assert len(platform.sensors) == 2
159164
assert platform.sensors == (radar_1, radar_2)
165+
166+
167+
def test_informative():
168+
dummy_truth1 = GroundTruthPath([GroundTruthState([1, 1, 1, 1]),
169+
GroundTruthState([2, 1, 2, 1]),
170+
GroundTruthState([3, 1, 3, 1])])
171+
dummy_truth2 = GroundTruthPath([GroundTruthState([1, 0, -1, -1]),
172+
GroundTruthState([1, 0, -2, -1]),
173+
GroundTruthState([1, 0, -3, -1])])
174+
175+
position = State(StateVector([0, 0, 0, 0]))
176+
orientation = StateVector([np.radians(90), 0, 0])
177+
test_radar = RadarBearingRange(ndim_state=4,
178+
position_mapping=(0, 2),
179+
noise_covar=np.diag([np.radians(1) ** 2, 5 ** 2]),
180+
movement_controller=FixedMovable(states=[position],
181+
orientation=orientation,
182+
position_mapping=(0, 2))
183+
)
184+
test_sensor = SensorSuite(attributes_inform=['position', 'orientation'],
185+
sensors=[test_radar])
186+
187+
detections = test_sensor.measure({dummy_truth1, dummy_truth2})
188+
189+
assert len(detections) == 2
190+
191+
for detection in detections:
192+
assert isinstance(detection.measurement_model, CartesianToBearingRange)
193+
metadata_position = detection.metadata.get('position')
194+
assert np.allclose(metadata_position, position.state_vector[(0, 2), :])
195+
metadata_orientation = detection.metadata.get('orientation')
196+
assert np.allclose(metadata_orientation, orientation)
197+
198+
position2 = State(StateVector([50, 0, 50, 0]))
199+
orientation2 = StateVector([np.radians(-90), np.radians(10), 0])
200+
test_radar2 = RadarElevationBearingRange(ndim_state=4,
201+
position_mapping=(0, 2, 3),
202+
noise_covar=np.diag(
203+
[np.radians(10) ** 2,
204+
np.radians(2) ** 2,
205+
0.1 ** 2]
206+
),
207+
movement_controller=FixedMovable(
208+
states=[position2],
209+
orientation=orientation2,
210+
position_mapping=(0, 2, 3)
211+
)
212+
)
213+
214+
test_sensor = SensorSuite(attributes_inform=['position', 'orientation'],
215+
sensors=[test_radar, test_radar2])
216+
217+
detections = test_sensor.measure({dummy_truth1, dummy_truth2})
218+
219+
assert len(detections) == 4
220+
221+
for detection in detections:
222+
assert isinstance(detection.measurement_model, CartesianToBearingRange) \
223+
or isinstance(detection.measurement_model, CartesianToElevationBearingRange)
224+
metadata_position = detection.metadata.get('position')
225+
metadata_orientation = detection.metadata.get('orientation')
226+
if isinstance(detection.measurement_model, CartesianToBearingRange):
227+
assert np.allclose(metadata_position, position.state_vector[(0, 2), :])
228+
assert np.allclose(metadata_orientation, orientation)
229+
else:
230+
assert np.allclose(metadata_position, position2.state_vector[(0, 2, 3), :])
231+
assert np.allclose(metadata_orientation, orientation2)

0 commit comments

Comments
 (0)