1 /*
2 * Copyright (C) 2020 Alberto Irurueta Carro (alberto@irurueta.com)
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 package com.irurueta.navigation.inertial.calibration.generators;
17
18 import com.irurueta.navigation.LockedException;
19 import com.irurueta.navigation.inertial.BodyKinematics;
20 import com.irurueta.navigation.inertial.calibration.AngularSpeedTriad;
21 import com.irurueta.navigation.inertial.calibration.BodyKinematicsSequence;
22 import com.irurueta.navigation.inertial.calibration.GyroscopeNoiseRootPsdSource;
23 import com.irurueta.navigation.inertial.calibration.StandardDeviationTimedBodyKinematics;
24 import com.irurueta.navigation.inertial.calibration.TimedBodyKinematics;
25 import com.irurueta.navigation.inertial.calibration.intervals.TriadStaticIntervalDetector;
26 import com.irurueta.navigation.inertial.calibration.noise.AccumulatedAngularSpeedTriadNoiseEstimator;
27 import com.irurueta.units.AngularSpeed;
28 import com.irurueta.units.AngularSpeedUnit;
29
30 import java.util.ArrayList;
31 import java.util.List;
32
33 /**
34 * Generates measurements for the calibration of gyroscopes by alternating
35 * static and dynamic intervals where device is kept static or moved.
36 * Generated measurements must be used with easy gyroscope calibrators.
37 * Notice that accuracy of the gyroscope calibration is very sensitive to the
38 * accuracy of detected dynamic intervals respect the average specific forces
39 * during static intervals.
40 * In order to increase the accuracy, calibration should be repeated trying different
41 * threshold factors {@link #getThresholdFactor()}.
42 * Such calibrators are the following ones:
43 * - {@link com.irurueta.navigation.inertial.calibration.gyroscope.EasyGyroscopeCalibrator}
44 * - {@link com.irurueta.navigation.inertial.calibration.gyroscope.KnownBiasEasyGyroscopeCalibrator}
45 * - {@link com.irurueta.navigation.inertial.calibration.gyroscope.RobustEasyGyroscopeCalibrator} and all its
46 * implementations.
47 * - {@link com.irurueta.navigation.inertial.calibration.gyroscope.RobustKnownBiasEasyGyroscopeCalibrator} and all its
48 * implementations.
49 */
50 public class GyroscopeMeasurementsGenerator extends
51 MeasurementsGenerator<BodyKinematicsSequence<StandardDeviationTimedBodyKinematics>,
52 GyroscopeMeasurementsGenerator, GyroscopeMeasurementsGeneratorListener, TimedBodyKinematics>
53 implements GyroscopeNoiseRootPsdSource {
54
55 /**
56 * An angular speed triad.
57 * This is reused for memory efficiency.
58 */
59 protected final AngularSpeedTriad angularSpeedTriad = new AngularSpeedTriad();
60
61 /**
62 * Items to be added to a generated sequence when next static period occurs.
63 */
64 private List<StandardDeviationTimedBodyKinematics> currentSequenceItems;
65
66 /**
67 * Accumulated noise estimator for angular speed measurements.
68 */
69 private final AccumulatedAngularSpeedTriadNoiseEstimator accumulatedEstimator =
70 new AccumulatedAngularSpeedTriadNoiseEstimator();
71
72 /**
73 * Estimated acceleration standard deviation during initialization expressed
74 * in meters per squared second (m/s^2).
75 */
76 private double accelerationStandardDeviation;
77
78 /**
79 * Estimated angular speed standard deviation during initialization expressed
80 * in radians per second (rad/s).
81 */
82 private double angularSpeedStandardDeviation;
83
84 /**
85 * Estimated norm of gyroscope noise root PSD (Power Spectral Density)
86 * expressed as (rad * s^-0.5).
87 */
88 private double angularSpeedNoiseRootPsd;
89
90 /**
91 * Previous average x-coordinate of measurements expressed in meters
92 * per squared second (m/s^2).
93 */
94 private Double previousAvgX;
95
96 /**
97 * Previous average y-coordinate of measurements expressed in meters
98 * per squared second (m/s^2).
99 */
100 private Double previousAvgY;
101
102 /**
103 * Previous average z-coordinate of measurements expressed in meters
104 * per squared second (m/s^2).
105 */
106 private Double previousAvgZ;
107
108 /**
109 * Current average x-coordinate of measurements expressed in meters
110 * per squared second (m/s^2).
111 */
112 private Double currentAvgX;
113
114 /**
115 * Current average y-coordinate of measurements expressed in meters
116 * per squared second (m/s^2).
117 */
118 private Double currentAvgY;
119
120 /**
121 * Current average z-coordinate of measurements expressed in meters
122 * per squared second (m/s^2).
123 */
124 private Double currentAvgZ;
125
126 /**
127 * Contains previous status while processing samples.
128 */
129 private TriadStaticIntervalDetector.Status previousStatus;
130
131 /**
132 * Constructor.
133 */
134 public GyroscopeMeasurementsGenerator() {
135 super();
136 }
137
138 /**
139 * Constructor.
140 *
141 * @param listener listener to handle events raised by this generator.
142 */
143 public GyroscopeMeasurementsGenerator(final GyroscopeMeasurementsGeneratorListener listener) {
144 super(listener);
145 }
146
147 /**
148 * Sets time interval between input samples expressed in seconds (s).
149 *
150 * @param timeInterval time interval between input samples.
151 * @throws IllegalArgumentException if provided value is negative.
152 * @throws LockedException if generator is currently running.
153 */
154 @Override
155 public void setTimeInterval(final double timeInterval) throws LockedException {
156 super.setTimeInterval(timeInterval);
157 accumulatedEstimator.setTimeInterval(timeInterval);
158 }
159
160 /**
161 * Resets this generator.
162 *
163 * @throws LockedException if generator is busy.
164 */
165 @Override
166 public void reset() throws LockedException {
167 super.reset();
168
169 currentSequenceItems = null;
170
171 accelerationStandardDeviation = 0.0;
172 angularSpeedStandardDeviation = 0.0;
173
174 previousAvgX = null;
175 previousAvgY = null;
176 previousAvgZ = null;
177
178 currentAvgX = null;
179 currentAvgY = null;
180 currentAvgZ = null;
181
182 accumulatedEstimator.reset();
183
184 previousStatus = null;
185 }
186
187 /**
188 * Gets estimated average angular rate during initialization phase.
189 *
190 * @return estimated average angular rate during initialization phase.
191 */
192 public AngularSpeedTriad getInitialAvgAngularSpeedTriad() {
193 return accumulatedEstimator.getAvgTriad();
194 }
195
196 /**
197 * Gets estimated average angular rate during initialization phase.
198 *
199 * @param result instance where result will be stored.
200 */
201 public void getInitialAvgAngularSpeedTriad(final AngularSpeedTriad result) {
202 accumulatedEstimator.getAvgTriad(result);
203 }
204
205 /**
206 * Gets estimated standard deviation of angular rate during initialization phase.
207 *
208 * @return estimated standard deviation of angular rate during initialization phase.
209 */
210 public AngularSpeedTriad getInitialAngularSpeedTriadStandardDeviation() {
211 return accumulatedEstimator.getStandardDeviationTriad();
212 }
213
214 /**
215 * Gets estimated standard deviation of angular rate during initialization phase.
216 *
217 * @param result instance where result will be stored.
218 */
219 public void getInitialAngularSpeedTriadStandardDeviation(final AngularSpeedTriad result) {
220 accumulatedEstimator.getStandardDeviationTriad(result);
221 }
222
223 /**
224 * Gets gyroscope base noise level that has been detected during
225 * initialization expressed in radians per second (rad/s).
226 * This is equal to the standard deviation of the gyroscope measurements
227 * during initialization phase.
228 *
229 * @return gyroscope base noise level.
230 */
231 public double getGyroscopeBaseNoiseLevel() {
232 return angularSpeedStandardDeviation;
233 }
234
235 /**
236 * Gets gyroscope base noise level that has been detected during
237 * initialization.
238 * This is equal to the standard deviation of the gyroscope measurements
239 * during initialization phase.
240 *
241 * @return gyroscope base noise level.
242 */
243 public AngularSpeed getGyroscopeBaseNoiseLevelAsMeasurement() {
244 return new AngularSpeed(angularSpeedStandardDeviation, AngularSpeedUnit.RADIANS_PER_SECOND);
245 }
246
247 /**
248 * Gets gyroscope base noise level that has been detected during
249 * initialization.
250 * This is equal to the standard deviation of the gyroscope measurements
251 * during initialization phase.
252 *
253 * @param result instance where result will be stored.
254 */
255 public void getGyroscopeBaseNoiseLevelAsMeasurement(final AngularSpeed result) {
256 result.setValue(angularSpeedStandardDeviation);
257 result.setUnit(AngularSpeedUnit.RADIANS_PER_SECOND);
258 }
259
260 /**
261 * Gets gyroscope base noise level PSD (Power Spectral Density)
262 * expressed in (rad^2/s).
263 *
264 * @return gyroscope base noise level PSD.
265 */
266 public double getGyroscopeBaseNoiseLevelPsd() {
267 return angularSpeedNoiseRootPsd * angularSpeedNoiseRootPsd;
268 }
269
270 /**
271 * Gets gyroscope base noise level root PSD (Power Spectral Density)
272 * expressed in (rad * s^-0.5)
273 *
274 * @return gyroscope base noise level root PSD.
275 */
276 @Override
277 public double getGyroscopeBaseNoiseLevelRootPsd() {
278 return angularSpeedNoiseRootPsd;
279 }
280
281 /**
282 * Post process provided input sample.
283 *
284 * @param sample an input sample.
285 * @throws LockedException if generator is busy.
286 */
287 @Override
288 protected void postProcess(final TimedBodyKinematics sample) throws LockedException {
289 final var status = staticIntervalDetector.getStatus();
290
291 if (status == TriadStaticIntervalDetector.Status.INITIALIZING) {
292 sample.getKinematics().getAngularRateTriad(angularSpeedTriad);
293 accumulatedEstimator.addTriad(angularSpeedTriad);
294 }
295
296 // while we are in a dynamic interval, we must record all timed kinematics
297 // along with accelerometer and gyroscope standard deviations
298 if (status == TriadStaticIntervalDetector.Status.DYNAMIC_INTERVAL) {
299 if (isDynamicIntervalSkipped()) {
300 // dynamic interval has been skipped because there were too many
301 // items in the sequence.
302 currentSequenceItems = null;
303 } else {
304 if (previousStatus == TriadStaticIntervalDetector.Status.STATIC_INTERVAL) {
305 previousAvgX = staticIntervalDetector.getAccumulatedAvgX();
306 previousAvgY = staticIntervalDetector.getAccumulatedAvgY();
307 previousAvgZ = staticIntervalDetector.getAccumulatedAvgZ();
308 }
309
310 addSequenceItem(sample);
311 }
312 } else if (status == TriadStaticIntervalDetector.Status.STATIC_INTERVAL
313 && previousStatus == TriadStaticIntervalDetector.Status.DYNAMIC_INTERVAL
314 && currentSequenceItems != null && !currentSequenceItems.isEmpty()) {
315
316 currentAvgX = staticIntervalDetector.getInstantaneousAvgX();
317 currentAvgY = staticIntervalDetector.getInstantaneousAvgY();
318 currentAvgZ = staticIntervalDetector.getInstantaneousAvgZ();
319
320 // we have all required data to generate a sequence
321 BodyKinematicsSequence<StandardDeviationTimedBodyKinematics> sequence = null;
322 if (listener != null) {
323 sequence = new BodyKinematicsSequence<>();
324 sequence.setBeforeMeanSpecificForceCoordinates(previousAvgX, previousAvgY, previousAvgZ);
325 sequence.setItems(currentSequenceItems);
326 sequence.setAfterMeanSpecificForceCoordinates(currentAvgX, currentAvgY, currentAvgZ);
327 }
328
329 currentSequenceItems = null;
330
331 if (listener != null) {
332 listener.onGeneratedMeasurement(this, sequence);
333 }
334 }
335 }
336
337 /**
338 * Gets corresponding acceleration triad from provided input sample.
339 * This method must store the result into {@link #triad}.
340 *
341 * @param sample input sample.
342 */
343 @Override
344 protected void getAccelerationTriadFromInputSample(final TimedBodyKinematics sample) {
345 sample.getKinematics().getSpecificForceTriad(triad);
346 }
347
348 /**
349 * Handles a static-to-dynamic interval change.
350 *
351 * @param accumulatedAvgX average x-coordinate of measurements during last
352 * static period expressed in meters per squared
353 * second (m/s^2).
354 * @param accumulatedAvgY average y-coordinate of specific force during last
355 * static period expressed in meters per squared
356 * second (m/s^2).
357 * @param accumulatedAvgZ average z-coordinate of specific force during last
358 * static period expressed in meters per squared
359 * second (m/s^2).
360 * @param accumulatedStdX standard deviation of x-coordinate of measurements
361 * during last static period expressed in meters per
362 * squared second (m/s^2).
363 * @param accumulatedStdY standard deviation of y-coordinate of measurements
364 * during last static period expressed in meters per
365 * squared second (m/s^2).
366 * @param accumulatedStdZ standard deviation of z-coordinate of measurements
367 * during last static period expressed in meters per
368 * squared second (m/s^2).
369 */
370 @Override
371 protected void handleStaticToDynamicChange(
372 final double accumulatedAvgX, final double accumulatedAvgY, final double accumulatedAvgZ,
373 final double accumulatedStdX, final double accumulatedStdY, final double accumulatedStdZ) {
374 previousStatus = TriadStaticIntervalDetector.Status.STATIC_INTERVAL;
375 }
376
377 /**
378 * Handles a dynamic-to-static interval change.
379 */
380 @Override
381 protected void handleDynamicToStaticChange() {
382 previousStatus = TriadStaticIntervalDetector.Status.DYNAMIC_INTERVAL;
383 }
384
385 /**
386 * Handles an initialization completion.
387 */
388 @Override
389 protected void handleInitializationCompleted() {
390 accelerationStandardDeviation = staticIntervalDetector.getBaseNoiseLevel();
391 angularSpeedStandardDeviation = accumulatedEstimator.getStandardDeviationNorm();
392 angularSpeedNoiseRootPsd = accumulatedEstimator.getNoiseRootPsdNorm();
393
394 previousStatus = staticIntervalDetector.getStatus();
395 }
396
397 /**
398 * Handles an error during initialization.
399 */
400 @Override
401 protected void handleInitializationFailed() {
402 previousStatus = null;
403
404 try {
405 accumulatedEstimator.reset();
406 } catch (final LockedException ignore) {
407 // no action needed
408 }
409 }
410
411 /**
412 * Adds an item to current sequence items.
413 *
414 * @param sample sample to generate a sequence item from.
415 */
416 private void addSequenceItem(final TimedBodyKinematics sample) {
417 if (currentSequenceItems == null) {
418 currentSequenceItems = new ArrayList<>();
419 }
420
421 final var kinematics = new BodyKinematics(sample.getKinematics());
422 final var timestampSeconds = sample.getTimestampSeconds();
423 final var stdTimedKinematics = new StandardDeviationTimedBodyKinematics(kinematics, timestampSeconds,
424 accelerationStandardDeviation, angularSpeedStandardDeviation);
425 currentSequenceItems.add(stdTimedKinematics);
426 }
427 }