1 /*
2 * Copyright (C) 2021 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.intervals.thresholdfactor;
17
18 import com.irurueta.algebra.Matrix;
19 import com.irurueta.navigation.LockedException;
20 import com.irurueta.navigation.NotReadyException;
21 import com.irurueta.navigation.inertial.INSLooselyCoupledKalmanInitializerConfig;
22 import com.irurueta.navigation.inertial.INSTightlyCoupledKalmanInitializerConfig;
23 import com.irurueta.navigation.inertial.calibration.AccelerometerNoiseRootPsdSource;
24 import com.irurueta.navigation.inertial.calibration.BodyKinematicsSequence;
25 import com.irurueta.navigation.inertial.calibration.CalibrationException;
26 import com.irurueta.navigation.inertial.calibration.GyroscopeNoiseRootPsdSource;
27 import com.irurueta.navigation.inertial.calibration.StandardDeviationBodyKinematics;
28 import com.irurueta.navigation.inertial.calibration.StandardDeviationBodyMagneticFluxDensity;
29 import com.irurueta.navigation.inertial.calibration.StandardDeviationTimedBodyKinematics;
30 import com.irurueta.navigation.inertial.calibration.TimedBodyKinematicsAndMagneticFluxDensity;
31 import com.irurueta.navigation.inertial.calibration.accelerometer.AccelerometerCalibratorMeasurementType;
32 import com.irurueta.navigation.inertial.calibration.accelerometer.AccelerometerNonLinearCalibrator;
33 import com.irurueta.navigation.inertial.calibration.accelerometer.KnownBiasAccelerometerCalibrator;
34 import com.irurueta.navigation.inertial.calibration.accelerometer.OrderedStandardDeviationBodyKinematicsAccelerometerCalibrator;
35 import com.irurueta.navigation.inertial.calibration.accelerometer.QualityScoredAccelerometerCalibrator;
36 import com.irurueta.navigation.inertial.calibration.accelerometer.UnknownBiasAccelerometerCalibrator;
37 import com.irurueta.navigation.inertial.calibration.accelerometer.UnorderedStandardDeviationBodyKinematicsAccelerometerCalibrator;
38 import com.irurueta.navigation.inertial.calibration.generators.AccelerometerGyroscopeAndMagnetometerMeasurementsGenerator;
39 import com.irurueta.navigation.inertial.calibration.generators.AccelerometerGyroscopeAndMagnetometerMeasurementsGeneratorListener;
40 import com.irurueta.navigation.inertial.calibration.gyroscope.AccelerometerDependentGyroscopeCalibrator;
41 import com.irurueta.navigation.inertial.calibration.gyroscope.GyroscopeCalibratorMeasurementOrSequenceType;
42 import com.irurueta.navigation.inertial.calibration.gyroscope.GyroscopeNonLinearCalibrator;
43 import com.irurueta.navigation.inertial.calibration.gyroscope.OrderedBodyKinematicsSequenceGyroscopeCalibrator;
44 import com.irurueta.navigation.inertial.calibration.gyroscope.QualityScoredGyroscopeCalibrator;
45 import com.irurueta.navigation.inertial.calibration.gyroscope.UnknownBiasGyroscopeCalibrator;
46 import com.irurueta.navigation.inertial.calibration.intervals.TriadStaticIntervalDetector;
47 import com.irurueta.navigation.inertial.calibration.magnetometer.KnownHardIronMagnetometerCalibrator;
48 import com.irurueta.navigation.inertial.calibration.magnetometer.MagnetometerCalibratorMeasurementType;
49 import com.irurueta.navigation.inertial.calibration.magnetometer.MagnetometerNonLinearCalibrator;
50 import com.irurueta.navigation.inertial.calibration.magnetometer.OrderedStandardDeviationBodyMagneticFluxDensityMagnetometerCalibrator;
51 import com.irurueta.navigation.inertial.calibration.magnetometer.QualityScoredMagnetometerCalibrator;
52 import com.irurueta.navigation.inertial.calibration.magnetometer.UnknownHardIronMagnetometerCalibrator;
53 import com.irurueta.navigation.inertial.calibration.magnetometer.UnorderedStandardDeviationBodyMagneticFluxDensityMagnetometerCalibrator;
54 import com.irurueta.units.Acceleration;
55 import com.irurueta.units.AccelerationUnit;
56 import com.irurueta.units.Time;
57
58 import java.util.ArrayList;
59 import java.util.List;
60
61 /**
62 * Optimizes the threshold factor for interval detection of accelerometer and gyroscope
63 * data based on results of calibration.
64 * Implementations of this class will attempt to find the best threshold factor
65 * between the provided range of values.
66 * Only accelerometer calibrators based on unknown orientation are supported (in other terms,
67 * calibrators must be {@link AccelerometerNonLinearCalibrator} and must support
68 * {@link AccelerometerCalibratorMeasurementType#STANDARD_DEVIATION_BODY_KINEMATICS}).
69 * Only gyroscope calibrators based on unknown orientation are supported (in other terms,
70 * calibrators must be {@link GyroscopeNonLinearCalibrator} and must support
71 * {@link GyroscopeCalibratorMeasurementOrSequenceType#BODY_KINEMATICS_SEQUENCE}).
72 * Only magnetometer calibrators based on unknown orientation are supported, in other terms,
73 * calibrators must be {@link MagnetometerNonLinearCalibrator} and must support
74 * {@link MagnetometerCalibratorMeasurementType#STANDARD_DEVIATION_BODY_MAGNETIC_FLUX_DENSITY}.
75 */
76 public abstract class AccelerometerGyroscopeAndMagnetometerIntervalDetectorThresholdFactorOptimizer extends
77 IntervalDetectorThresholdFactorOptimizer<TimedBodyKinematicsAndMagneticFluxDensity,
78 AccelerometerGyroscopeAndMagnetometerIntervalDetectorThresholdFactorOptimizerDataSource> implements
79 AccelerometerNoiseRootPsdSource, GyroscopeNoiseRootPsdSource {
80
81 /**
82 * Default minimum threshold factor.
83 */
84 public static final double DEFAULT_MIN_THRESHOLD_FACTOR = 2.0;
85
86 /**
87 * Default maximum threshold factor.
88 */
89 public static final double DEFAULT_MAX_THRESHOLD_FACTOR = 10.0;
90
91 /**
92 * Minimum threshold factor.
93 */
94 protected double minThresholdFactor = DEFAULT_MIN_THRESHOLD_FACTOR;
95
96 /**
97 * Maximum threshold factor.
98 */
99 protected double maxThresholdFactor = DEFAULT_MAX_THRESHOLD_FACTOR;
100
101 /**
102 * Accelerometer calibrator.
103 */
104 private AccelerometerNonLinearCalibrator accelerometerCalibrator;
105
106 /**
107 * Gyroscope calibrator.
108 */
109 private GyroscopeNonLinearCalibrator gyroscopeCalibrator;
110
111 /**
112 * Magnetometer calibrator.
113 */
114 private MagnetometerNonLinearCalibrator magnetometerCalibrator;
115
116 /**
117 * A measurement generator for accelerometer, gyroscope and magnetometer calibrators.
118 */
119 private AccelerometerGyroscopeAndMagnetometerMeasurementsGenerator generator;
120
121 /**
122 * Generated measurements to be used for accelerometer calibration.
123 */
124 private List<StandardDeviationBodyKinematics> accelerometerMeasurements;
125
126 /**
127 * Generated sequences to be used for gyroscope calibration.
128 */
129 private List<BodyKinematicsSequence<StandardDeviationTimedBodyKinematics>> gyroscopeSequences;
130
131 /**
132 * Generated measurements to be used for magnetometer calibration.
133 */
134 private List<StandardDeviationBodyMagneticFluxDensity> magnetometerMeasurements;
135
136 /**
137 * Mapper to convert {@link StandardDeviationBodyKinematics} measurements into
138 * quality scores.
139 */
140 private QualityScoreMapper<StandardDeviationBodyKinematics> accelerometerQualityScoreMapper =
141 new DefaultAccelerometerQualityScoreMapper();
142
143 /**
144 * Mapper to convert {@link BodyKinematicsSequence} sequences of {@link StandardDeviationTimedBodyKinematics}
145 * into quality scores.
146 */
147 private QualityScoreMapper<BodyKinematicsSequence<StandardDeviationTimedBodyKinematics>>
148 gyroscopeQualityScoreMapper = new DefaultGyroscopeQualityScoreMapper();
149
150 /**
151 * Mapper to convert {@link StandardDeviationBodyMagneticFluxDensity} measurements
152 * into quality scores.
153 */
154 private QualityScoreMapper<StandardDeviationBodyMagneticFluxDensity> magnetometerQualityScoreMapper =
155 new DefaultMagnetometerQualityScoreMapper();
156
157 /**
158 * Rule to convert accelerometer, gyroscope and magnetometer MSE
159 * values into a single global MSE value.
160 */
161 private AccelerometerGyroscopeAndMagnetometerMseRule mseRule =
162 new DefaultAccelerometerGyroscopeAndMagnetometerMseRule();
163
164 /**
165 * Estimated norm of gyroscope noise root PSD (Power Spectral Density)
166 * expressed as (rad * s^-0.5).
167 */
168 private double angularSpeedNoiseRootPsd;
169
170 /**
171 * Accelerometer base noise level that has been detected during
172 * initialization of the best solution that has been found expressed in
173 * meters per squared second (m/s^2).
174 * This is equal to the standard deviation of the accelerometer measurements
175 * during the initialization phase.
176 */
177 private double baseNoiseLevel;
178
179 /**
180 * Threshold to determine static/dynamic period changes expressed in
181 * meters per squared second (m/s^2) for the best calibration solution that
182 * has been found.
183 */
184 private double threshold;
185
186 /**
187 * Estimated covariance matrix for estimated accelerometer parameters.
188 */
189 private Matrix estimatedAccelerometerCovariance;
190
191 /**
192 * Estimated accelerometer scale factors and cross-coupling errors.
193 * This is the product of matrix Ta containing cross-coupling errors and Ka
194 * containing scaling factors.
195 * So that:
196 * <pre>
197 * Ma = [sx mxy mxz] = Ta*Ka
198 * [myx sy myz]
199 * [mzx mzy sz ]
200 * </pre>
201 * Where:
202 * <pre>
203 * Ka = [sx 0 0 ]
204 * [0 sy 0 ]
205 * [0 0 sz]
206 * </pre>
207 * and
208 * <pre>
209 * Ta = [1 -alphaXy alphaXz ]
210 * [alphaYx 1 -alphaYz]
211 * [-alphaZx alphaZy 1 ]
212 * </pre>
213 * Hence:
214 * <pre>
215 * Ma = [sx mxy mxz] = Ta*Ka = [sx -sy * alphaXy sz * alphaXz ]
216 * [myx sy myz] [sx * alphaYx sy -sz * alphaYz]
217 * [mzx mzy sz ] [-sx * alphaZx sy * alphaZy sz ]
218 * </pre>
219 * This instance allows any 3x3 matrix. However, typically alphaYx, alphaZx and alphaZy
220 * are considered to be zero if the accelerometer z-axis is assumed to be the same
221 * as the body z-axis. When this is assumed, myx = mzx = mzy = 0 and the Ma matrix
222 * becomes upper diagonal:
223 * <pre>
224 * Ma = [sx mxy mxz]
225 * [0 sy myz]
226 * [0 0 sz ]
227 * </pre>
228 * Values of this matrix are unit-less.
229 */
230 private Matrix estimatedAccelerometerMa;
231
232 /**
233 * Estimated accelerometer biases for each IMU axis expressed in meter per squared
234 * second (m/s^2).
235 */
236 private double[] estimatedAccelerometerBiases;
237
238 /**
239 * Estimated covariance matrix for estimated gyroscope parameters.
240 */
241 private Matrix estimatedGyroscopeCovariance;
242
243 /**
244 * Estimated angular rate biases for each IMU axis expressed in radians per
245 * second (rad/s).
246 */
247 private double[] estimatedGyroscopeBiases;
248
249 /**
250 * Estimated gyroscope scale factors and cross-coupling errors.
251 * This is the product of matrix Tg containing cross-coupling errors and Kg
252 * containing scaling factors.
253 * So that:
254 * <pre>
255 * Mg = [sx mxy mxz] = Tg*Kg
256 * [myx sy myz]
257 * [mzx mzy sz ]
258 * </pre>
259 * Where:
260 * <pre>
261 * Kg = [sx 0 0 ]
262 * [0 sy 0 ]
263 * [0 0 sz]
264 * </pre>
265 * and
266 * <pre>
267 * Tg = [1 -alphaXy alphaXz ]
268 * [alphaYx 1 -alphaYz]
269 * [-alphaZx alphaZy 1 ]
270 * </pre>
271 * Hence:
272 * <pre>
273 * Mg = [sx mxy mxz] = Tg*Kg = [sx -sy * alphaXy sz * alphaXz ]
274 * [myx sy myz] [sx * alphaYx sy -sz * alphaYz]
275 * [mzx mzy sz ] [-sx * alphaZx sy * alphaZy sz ]
276 * </pre>
277 * This instance allows any 3x3 matrix. However, typically alphaYx, alphaZx and alphaZy
278 * are considered to be zero if the gyroscope z-axis is assumed to be the same
279 * as the body z-axis. When this is assumed, myx = mzx = mzy = 0 and the Mg matrix
280 * becomes upper diagonal:
281 * <pre>
282 * Mg = [sx mxy mxz]
283 * [0 sy myz]
284 * [0 0 sz ]
285 * </pre>
286 * Values of this matrix are unit-less.
287 */
288 private Matrix estimatedGyroscopeMg;
289
290 /**
291 * Estimated G-dependent cross-biases introduced on the gyroscope by the
292 * specific forces sensed by the accelerometer.
293 * This instance allows any 3x3 matrix.
294 */
295 private Matrix estimatedGyroscopeGg;
296
297 /**
298 * Estimated covariance matrix for estimated magnetometer parameters.
299 */
300 private Matrix estimatedMagnetometerCovariance;
301
302 /**
303 * Estimated magnetometer soft-iron matrix containing scale factors
304 * and cross-coupling errors.
305 * This is the product of matrix Tm containing cross-coupling errors and Km
306 * containing scaling factors.
307 * So that:
308 * <pre>
309 * Mm = [sx mxy mxz] = Tm*Km
310 * [myx sy myz]
311 * [mzx mzy sz ]
312 * </pre>
313 * Where:
314 * <pre>
315 * Km = [sx 0 0 ]
316 * [0 sy 0 ]
317 * [0 0 sz]
318 * </pre>
319 * and
320 * <pre>
321 * Tm = [1 -alphaXy alphaXz ]
322 * [alphaYx 1 -alphaYz]
323 * [-alphaZx alphaZy 1 ]
324 * </pre>
325 * Hence:
326 * <pre>
327 * Mm = [sx mxy mxz] = Tm*Km = [sx -sy * alphaXy sz * alphaXz ]
328 * [myx sy myz] [sx * alphaYx sy -sz * alphaYz]
329 * [mzx mzy sz ] [-sx * alphaZx sy * alphaZy sz ]
330 * </pre>
331 * This instance allows any 3x3 matrix. However, typically alphaYx, alphaZx and alphaZy
332 * are considered to be zero if the accelerometer z-axis is assumed to be the same
333 * as the body z-axis. When this is assumed, myx = mzx = mzy = 0 and the Mm matrix
334 * becomes upper diagonal:
335 * <pre>
336 * Mm = [sx mxy mxz]
337 * [0 sy myz]
338 * [0 0 sz ]
339 * </pre>
340 * Values of this matrix are unit-less.
341 */
342 private Matrix estimatedMagnetometerMm;
343
344 /**
345 * Estimated magnetometer hard-iron biases for each magnetometer axis
346 * expressed in Teslas (T).
347 */
348 private double[] estimatedMagnetometerHardIron;
349
350 /**
351 * Constructor.
352 */
353 protected AccelerometerGyroscopeAndMagnetometerIntervalDetectorThresholdFactorOptimizer() {
354 initialize();
355 }
356
357 /**
358 * Constructor.
359 *
360 * @param dataSource instance in charge of retrieving data for this optimizer.
361 */
362 protected AccelerometerGyroscopeAndMagnetometerIntervalDetectorThresholdFactorOptimizer(
363 final AccelerometerGyroscopeAndMagnetometerIntervalDetectorThresholdFactorOptimizerDataSource dataSource) {
364 super(dataSource);
365 initialize();
366 }
367
368 /**
369 * Constructor.
370 *
371 * @param accelerometerCalibrator an accelerometer calibrator to be used to
372 * optimize its Mean Square Error (MSE).
373 * @param gyroscopeCalibrator a gyroscope calibrator to be used to optimize
374 * its Mean Square Error (MSE).
375 * @param magnetometerCalibrator a magnetometer calibrator to be used to
376 * optimize its Mean Square Error (MSE).
377 * @throws IllegalArgumentException if accelerometer calibrator does not use
378 * {@link StandardDeviationBodyKinematics} measurements,
379 * if gyroscope calibrator does not use
380 * {@link BodyKinematicsSequence} sequences of
381 * {@link StandardDeviationTimedBodyKinematics} or
382 * if magnetometer calibrator does not use
383 * {@link StandardDeviationBodyMagneticFluxDensity} measurements.
384 */
385 protected AccelerometerGyroscopeAndMagnetometerIntervalDetectorThresholdFactorOptimizer(
386 final AccelerometerNonLinearCalibrator accelerometerCalibrator,
387 final GyroscopeNonLinearCalibrator gyroscopeCalibrator,
388 final MagnetometerNonLinearCalibrator magnetometerCalibrator) {
389 try {
390 setAccelerometerCalibrator(accelerometerCalibrator);
391 setGyroscopeCalibrator(gyroscopeCalibrator);
392 setMagnetometerCalibrator(magnetometerCalibrator);
393 } catch (final LockedException ignore) {
394 // never happens
395 }
396 initialize();
397 }
398
399 /**
400 * Constructor.
401 *
402 * @param dataSource instance in charge of retrieving data for this optimizer.
403 * @param accelerometerCalibrator an accelerometer calibrator to be used to
404 * optimize its Mean Square Error (MSE).
405 * @param gyroscopeCalibrator a gyroscope calibrator to be used to optimize
406 * its Mean Square Error (MSE).
407 * @param magnetometerCalibrator a magnetometer calibrator to be used to
408 * optimize its Mean Square Error (MSE).
409 * @throws IllegalArgumentException if accelerometer calibrator does not use
410 * {@link StandardDeviationBodyKinematics} measurements,
411 * if gyroscope calibrator does not use
412 * {@link BodyKinematicsSequence} sequences of
413 * {@link StandardDeviationTimedBodyKinematics} or
414 * if magnetometer calibrator does not use
415 * {@link StandardDeviationBodyMagneticFluxDensity} measurements.
416 */
417 protected AccelerometerGyroscopeAndMagnetometerIntervalDetectorThresholdFactorOptimizer(
418 final AccelerometerGyroscopeAndMagnetometerIntervalDetectorThresholdFactorOptimizerDataSource dataSource,
419 final AccelerometerNonLinearCalibrator accelerometerCalibrator,
420 final GyroscopeNonLinearCalibrator gyroscopeCalibrator,
421 final MagnetometerNonLinearCalibrator magnetometerCalibrator) {
422 super(dataSource);
423 try {
424 setAccelerometerCalibrator(accelerometerCalibrator);
425 setGyroscopeCalibrator(gyroscopeCalibrator);
426 setMagnetometerCalibrator(magnetometerCalibrator);
427 } catch (final LockedException ignore) {
428 // never happens
429 }
430 initialize();
431 }
432
433 /**
434 * Gets provided accelerometer calibrator to be used to optimize its Mean Square Error (MSE).
435 *
436 * @return accelerometer calibrator to be used to optimize its MSE.
437 */
438 public AccelerometerNonLinearCalibrator getAccelerometerCalibrator() {
439 return accelerometerCalibrator;
440 }
441
442 /**
443 * Sets accelerometer calibrator to be used to optimize its Mean Square Error (MSE).
444 *
445 * @param accelerometerCalibrator accelerometer calibrator to be used to optimize its MSE.
446 * @throws LockedException if optimizer is already running.
447 * @throws IllegalArgumentException if accelerometer calibrator does not use
448 * {@link StandardDeviationBodyKinematics} measurements.
449 */
450 public void setAccelerometerCalibrator(
451 final AccelerometerNonLinearCalibrator accelerometerCalibrator) throws LockedException {
452 if (running) {
453 throw new LockedException();
454 }
455
456 if (accelerometerCalibrator != null && accelerometerCalibrator.getMeasurementType()
457 != AccelerometerCalibratorMeasurementType.STANDARD_DEVIATION_BODY_KINEMATICS) {
458 throw new IllegalArgumentException();
459 }
460
461 this.accelerometerCalibrator = accelerometerCalibrator;
462 }
463
464 /**
465 * Gets provided gyroscope calibrator to be used to optimize its Mean Square Error (MSE).
466 *
467 * @return gyroscope calibrator to be used to optimize its MSE.
468 */
469 public GyroscopeNonLinearCalibrator getGyroscopeCalibrator() {
470 return gyroscopeCalibrator;
471 }
472
473 /**
474 * Sets gyroscope calibrator to be used to optimize its Mean Square Error (MSE).
475 *
476 * @param gyroscopeCalibrator gyroscope calibrator to be use dto optimize its MSE.
477 * @throws LockedException if optimizer is already running.
478 * @throws IllegalArgumentException if gyroscope calibrator does not use
479 * {@link BodyKinematicsSequence} sequences of
480 * {@link StandardDeviationTimedBodyKinematics}.
481 */
482 public void setGyroscopeCalibrator(final GyroscopeNonLinearCalibrator gyroscopeCalibrator) throws LockedException {
483 if (running) {
484 throw new LockedException();
485 }
486
487 if (gyroscopeCalibrator != null && gyroscopeCalibrator.getMeasurementOrSequenceType()
488 != GyroscopeCalibratorMeasurementOrSequenceType.BODY_KINEMATICS_SEQUENCE) {
489 throw new IllegalArgumentException();
490 }
491
492 this.gyroscopeCalibrator = gyroscopeCalibrator;
493 }
494
495 /**
496 * Gets provided magnetometer calibrator to be used to optimize its Mean Square Error (MSE).
497 *
498 * @return magnetometer calibrator to be used to optimize its MSE.
499 */
500 public MagnetometerNonLinearCalibrator getMagnetometerCalibrator() {
501 return magnetometerCalibrator;
502 }
503
504 /**
505 * Sets a magnetometer calibrator to be used to optimize its Mean Square Error.
506 *
507 * @param magnetometerCalibrator magnetometer calibrator to be used to optimize its MSE.
508 * @throws LockedException if optimizer is already running.
509 * @throws IllegalArgumentException if magnetometer calibrator does not use
510 * {@link StandardDeviationBodyMagneticFluxDensity} measurements.
511 */
512 public void setMagnetometerCalibrator(final MagnetometerNonLinearCalibrator magnetometerCalibrator)
513 throws LockedException {
514 if (running) {
515 throw new LockedException();
516 }
517
518 if (magnetometerCalibrator != null && magnetometerCalibrator.getMeasurementType()
519 != MagnetometerCalibratorMeasurementType.STANDARD_DEVIATION_BODY_MAGNETIC_FLUX_DENSITY) {
520 throw new IllegalArgumentException();
521 }
522
523 this.magnetometerCalibrator = magnetometerCalibrator;
524 }
525
526 /**
527 * Gets mapper to convert {@link StandardDeviationBodyKinematics} accelerometer measurements
528 * into quality scores.
529 *
530 * @return mapper to convert accelerometer measurements into quality scores.
531 */
532 public QualityScoreMapper<StandardDeviationBodyKinematics> getAccelerometerQualityScoreMapper() {
533 return accelerometerQualityScoreMapper;
534 }
535
536 /**
537 * Sets mapper to convert {@link StandardDeviationBodyKinematics} accelerometer measurements
538 * into quality scores.
539 *
540 * @param accelerometerQualityScoreMapper mapper to convert accelerometer measurements into
541 * quality scores.
542 * @throws LockedException if optimizer is already running.
543 */
544 public void setAccelerometerQualityScoreMapper(
545 final QualityScoreMapper<StandardDeviationBodyKinematics> accelerometerQualityScoreMapper)
546 throws LockedException {
547 if (running) {
548 throw new LockedException();
549 }
550
551 this.accelerometerQualityScoreMapper = accelerometerQualityScoreMapper;
552 }
553
554 /**
555 * Gets mapper to convert {@link BodyKinematicsSequence} gyroscope sequences of
556 * {@link StandardDeviationTimedBodyKinematics} into quality scores.
557 *
558 * @return mapper to convert gyroscope sequences into quality scores.
559 */
560 public QualityScoreMapper<BodyKinematicsSequence<StandardDeviationTimedBodyKinematics>>
561 getGyroscopeQualityScoreMapper() {
562 return gyroscopeQualityScoreMapper;
563 }
564
565 /**
566 * Sets mapper to convert {@link BodyKinematicsSequence} gyroscope sequences of
567 * {@link StandardDeviationTimedBodyKinematics} into quality scores.
568 *
569 * @param gyroscopeQualityScoreMapper mapper to convert gyroscope sequences
570 * into quality scores.
571 * @throws LockedException if optimizer is already running.
572 */
573 public void setGyroscopeQualityScoreMapper(
574 final QualityScoreMapper<BodyKinematicsSequence<StandardDeviationTimedBodyKinematics>>
575 gyroscopeQualityScoreMapper) throws LockedException {
576 if (running) {
577 throw new LockedException();
578 }
579
580 this.gyroscopeQualityScoreMapper = gyroscopeQualityScoreMapper;
581 }
582
583 /**
584 * Gets mapper to convert {@link StandardDeviationBodyMagneticFluxDensity} magnetometer
585 * measurements into quality scores.
586 *
587 * @return mapper to convert magnetometer measurements into quality scores.
588 */
589 public QualityScoreMapper<StandardDeviationBodyMagneticFluxDensity> getMagnetometerQualityScoreMapper() {
590 return magnetometerQualityScoreMapper;
591 }
592
593 /**
594 * Sets mapper to convert {@link StandardDeviationBodyMagneticFluxDensity} magnetometer
595 * measurements into quality scores.
596 *
597 * @param magnetometerQualityScoreMapper mapper to convert magnetometer measurements into
598 * quality scores.
599 * @throws LockedException if optimizer is already running.
600 */
601 public void setMagnetometerQualityScoreMapper(
602 final QualityScoreMapper<StandardDeviationBodyMagneticFluxDensity> magnetometerQualityScoreMapper)
603 throws LockedException {
604 if (running) {
605 throw new LockedException();
606 }
607
608 this.magnetometerQualityScoreMapper = magnetometerQualityScoreMapper;
609 }
610
611 /**
612 * Gets rule to convert accelerometer, gyroscope and magnetometer
613 * MSE values into a single global MSE value.
614 *
615 * @return rule to convert accelerometer, gyroscope and
616 * magnetometer MSE values into a single global MSE value.
617 */
618 public AccelerometerGyroscopeAndMagnetometerMseRule getMseRule() {
619 return mseRule;
620 }
621
622 /**
623 * Sets rule to convert accelerometer, gyroscope and magnetometer
624 * MSE values into a single global MSE value.
625 *
626 * @param mseRule rule to convert accelerometer, gyroscope and
627 * magnetometer MSE values into a single global
628 * MSE value.
629 * @throws LockedException if optimizer is already running.
630 */
631 public void setMseRule(final AccelerometerGyroscopeAndMagnetometerMseRule mseRule) throws LockedException {
632 if (running) {
633 throw new LockedException();
634 }
635
636 this.mseRule = mseRule;
637 }
638
639 /**
640 * Gets the minimum threshold factor.
641 *
642 * @return minimum threshold factor.
643 */
644 public double getMinThresholdFactor() {
645 return minThresholdFactor;
646 }
647
648 /**
649 * Gets the maximum threshold factor.
650 *
651 * @return maximum threshold factor.
652 */
653 public double getMaxThresholdFactor() {
654 return maxThresholdFactor;
655 }
656
657 /**
658 * Sets a range of threshold factor values to get an optimized
659 * threshold factor value.
660 *
661 * @param minThresholdFactor minimum threshold.
662 * @param maxThresholdFactor maximum threshold.
663 * @throws LockedException if optimizer is already running.
664 * @throws IllegalArgumentException if either minimum or maximum values are
665 * negative, or if the minimum value is larger
666 * than the maximum one.
667 */
668 public void setThresholdFactorRange(final double minThresholdFactor, final double maxThresholdFactor)
669 throws LockedException {
670 if (running) {
671 throw new LockedException();
672 }
673 if (minThresholdFactor < 0.0 || maxThresholdFactor < 0.0 || minThresholdFactor >= maxThresholdFactor) {
674 throw new IllegalArgumentException();
675 }
676
677 this.minThresholdFactor = minThresholdFactor;
678 this.maxThresholdFactor = maxThresholdFactor;
679 }
680
681 /**
682 * Indicates whether this optimizer is ready to start optimization.
683 *
684 * @return true if this optimizer is ready, false otherwise.
685 */
686 @Override
687 public boolean isReady() {
688 return super.isReady() && accelerometerCalibrator != null
689 && gyroscopeCalibrator != null
690 && magnetometerCalibrator != null
691 && accelerometerQualityScoreMapper != null
692 && gyroscopeQualityScoreMapper != null
693 && magnetometerQualityScoreMapper != null
694 && mseRule != null;
695 }
696
697 /**
698 * Gets the time interval between input measurements provided to the
699 * {@link #getDataSource()} expressed in seconds (s).
700 *
701 * @return time interval between input measurements.
702 */
703 public double getTimeInterval() {
704 return generator.getTimeInterval();
705 }
706
707 /**
708 * Sets time interval between input measurements provided to the
709 * {@link #getDataSource()} expressed in seconds (s).
710 *
711 * @param timeInterval time interval between input measurements.
712 * @throws LockedException if optimizer is already running.
713 * @throws IllegalArgumentException if provided value is negative.
714 */
715 public void setTimeInterval(final double timeInterval) throws LockedException {
716 if (running) {
717 throw new LockedException();
718 }
719
720 generator.setTimeInterval(timeInterval);
721 }
722
723 /**
724 * Gets the time interval between input measurements provided to the
725 * {@link #getDataSource()}.
726 *
727 * @return time interval between input measurements.
728 */
729 public Time getTimeIntervalAsTime() {
730 return generator.getTimeIntervalAsTime();
731 }
732
733 /**
734 * Gets the time interval between input measurements provided to the
735 * {@link #getDataSource()}.
736 *
737 * @param result instance where the time interval will be stored.
738 */
739 public void getTimeIntervalAsTime(final Time result) {
740 generator.getTimeIntervalAsTime(result);
741 }
742
743 /**
744 * Sets time interval between input measurements provided to the
745 * {@link #getDataSource()}.
746 *
747 * @param timeInterval time interval between input measurements.
748 * @throws LockedException if optimizer is already running.
749 * @throws IllegalArgumentException if provided value is negative.
750 */
751 public void setTimeInterval(final Time timeInterval) throws LockedException {
752 if (running) {
753 throw new LockedException();
754 }
755
756 generator.setTimeInterval(timeInterval);
757 }
758
759 /**
760 * Gets minimum number of input measurements provided to the
761 * {@link #getDataSource()} required in a static interval to be taken
762 * into account.
763 * Smaller static intervals will be discarded.
764 *
765 * @return a minimum number of input measurements required in a static interval
766 * to be taken into account.
767 */
768 public int getMinStaticSamples() {
769 return generator.getMinStaticSamples();
770 }
771
772 /**
773 * Sets minimum number of input measurements provided to the
774 * {@link #getDataSource()} required in a static interval to be taken
775 * into account.
776 * Smaller static intervals will be discarded.
777 *
778 * @param minStaticSamples a minimum number of input measurements required in
779 * a static interval to be taken into account.
780 * @throws LockedException if optimizer is already running.
781 * @throws IllegalArgumentException if provided value is less than 2.
782 */
783 public void setMinStaticSamples(final int minStaticSamples) throws LockedException {
784 if (running) {
785 throw new LockedException();
786 }
787
788 generator.setMinStaticSamples(minStaticSamples);
789 }
790
791 /**
792 * Gets maximum number of input measurements provided to the
793 * {@link #getDataSource()} allowed in dynamic intervals.
794 *
795 * @return maximum number of input measurements allowed in dynamic intervals.
796 */
797 public int getMaxDynamicSamples() {
798 return generator.getMaxDynamicSamples();
799 }
800
801 /**
802 * Sets maximum number of input measurements provided to the
803 * {@link #getDataSource()} allowed in dynamic intervals.
804 *
805 * @param maxDynamicSamples maximum number of input measurements allowed in
806 * dynamic intervals.
807 * @throws LockedException if optimizer is already running.
808 * @throws IllegalArgumentException if provided value is less than 2.
809 */
810 public void setMaxDynamicSamples(final int maxDynamicSamples) throws LockedException {
811 if (running) {
812 throw new LockedException();
813 }
814
815 generator.setMaxDynamicSamples(maxDynamicSamples);
816 }
817
818 /**
819 * Gets length of number of input measurements provided to the
820 * {@link #getDataSource()} to keep within the window being processed
821 * to determine instantaneous accelerometer noise level.
822 *
823 * @return length of input measurements to keep within the window.
824 */
825 public int getWindowSize() {
826 return generator.getWindowSize();
827 }
828
829 /**
830 * Sets length of number of input measurements provided to the
831 * {@link #getDataSource()} to keep within the window being processed
832 * to determine instantaneous noise level.
833 * Window size must always be larger than the allowed minimum value, which is 2
834 * and must have an odd value.
835 *
836 * @param windowSize length of number of samples to keep within the window.
837 * @throws LockedException if optimizer is already running.
838 * @throws IllegalArgumentException if provided value is not valid.
839 */
840 public void setWindowSize(final int windowSize) throws LockedException {
841 if (running) {
842 throw new LockedException();
843 }
844
845 generator.setWindowSize(windowSize);
846 }
847
848 /**
849 * Gets number of input measurements provided to the
850 * {@link #getDataSource()} to be processed initially while keeping the sensor
851 * static to find the base noise level when the device is static.
852 *
853 * @return number of samples to be processed initially.
854 */
855 public int getInitialStaticSamples() {
856 return generator.getInitialStaticSamples();
857 }
858
859 /**
860 * Sets number of input parameters provided to the {@link #getDataSource()}
861 * to be processed initially while keeping the sensor static to
862 * find the base noise level when the device is static.
863 *
864 * @param initialStaticSamples number of samples ot be processed initially.
865 * @throws LockedException if optimizer is already running.
866 * @throws IllegalArgumentException if provided value is less than
867 * {@link TriadStaticIntervalDetector#MINIMUM_INITIAL_STATIC_SAMPLES}.
868 */
869 public void setInitialStaticSamples(final int initialStaticSamples) throws LockedException {
870 if (running) {
871 throw new LockedException();
872 }
873
874 generator.setInitialStaticSamples(initialStaticSamples);
875 }
876
877 /**
878 * Gets factor to determine that a sudden movement has occurred during
879 * initialization if instantaneous noise level exceeds accumulated noise
880 * level by this factor amount.
881 * This factor is unit-less.
882 *
883 * @return factor to determine that a sudden movement has occurred.
884 */
885 public double getInstantaneousNoiseLevelFactor() {
886 return generator.getInstantaneousNoiseLevelFactor();
887 }
888
889 /**
890 * Sets factor to determine that a sudden movement has occurred during
891 * initialization if instantaneous noise level exceeds accumulated noise
892 * level by this factor amount.
893 * This factor is unit-less.
894 *
895 * @param instantaneousNoiseLevelFactor factor to determine that a sudden
896 * movement has occurred during
897 * initialization.
898 * @throws LockedException if optimizer is already running.
899 * @throws IllegalArgumentException if provided value is zero or negative.
900 */
901 public void setInstantaneousNoiseLevelFactor(final double instantaneousNoiseLevelFactor) throws LockedException {
902 if (running) {
903 throw new LockedException();
904 }
905
906 generator.setInstantaneousNoiseLevelFactor(instantaneousNoiseLevelFactor);
907 }
908
909 /**
910 * Gets the overall absolute threshold to determine whether there has been
911 * excessive motion during the whole initialization phase.
912 * This threshold is expressed in meters per squared second (m/s^2).
913 *
914 * @return overall absolute threshold to determine whether there has been
915 * excessive motion.
916 */
917 public double getBaseNoiseLevelAbsoluteThreshold() {
918 return generator.getBaseNoiseLevelAbsoluteThreshold();
919 }
920
921 /**
922 * Sets the overall absolute threshold to determine whether there has been
923 * excessive motion during the whole initialization phase.
924 * This threshold is expressed in meters per squared second (m/s^2).
925 *
926 * @param baseNoiseLevelAbsoluteThreshold overall absolute threshold to
927 * determine whether there has been
928 * excessive motion.
929 * @throws LockedException if optimizer is already running.
930 * @throws IllegalArgumentException if provided value is zero or negative.
931 */
932 public void setBaseNoiseLevelAbsoluteThreshold(final double baseNoiseLevelAbsoluteThreshold)
933 throws LockedException {
934 if (running) {
935 throw new LockedException();
936 }
937
938 generator.setBaseNoiseLevelAbsoluteThreshold(baseNoiseLevelAbsoluteThreshold);
939 }
940
941 /**
942 * Gets the overall absolute threshold to determine whether there has been
943 * excessive motion during the whole initialization phase.
944 *
945 * @return overall absolute threshold to determine whether there has been
946 * excessive motion.
947 */
948 public Acceleration getBaseNoiseLevelAbsoluteThresholdAsMeasurement() {
949 return generator.getBaseNoiseLevelAbsoluteThresholdAsMeasurement();
950 }
951
952 /**
953 * Gets the overall absolute threshold to determine whether there has been
954 * excessive motion during the whole initialization phase.
955 *
956 * @param result instance where the result will be stored.
957 */
958 public void getBaseNoiseLevelAbsoluteThresholdAsMeasurement(final Acceleration result) {
959 generator.getBaseNoiseLevelAbsoluteThresholdAsMeasurement(result);
960 }
961
962 /**
963 * Sets the overall absolute threshold to determine whether there has been
964 * excessive motion during the whole initialization phase.
965 *
966 * @param baseNoiseLevelAbsoluteThreshold overall absolute threshold to
967 * determine whether there has been
968 * excessive motion.
969 * @throws LockedException if optimizer is already running.
970 * @throws IllegalArgumentException if provided value is zero or negative.
971 */
972 public void setBaseNoiseLevelAbsoluteThreshold(final Acceleration baseNoiseLevelAbsoluteThreshold)
973 throws LockedException {
974 if (running) {
975 throw new LockedException();
976 }
977
978 generator.setBaseNoiseLevelAbsoluteThreshold(baseNoiseLevelAbsoluteThreshold);
979 }
980
981 /**
982 * Gets accelerometer base noise level that has been detected during
983 * initialization of the best solution that has been found expressed in
984 * meters per squared second (m/s^2).
985 * This is equal to the standard deviation of the accelerometer measurements
986 * during the initialization phase.
987 *
988 * @return accelerometer base noise level of the best solution that has been
989 * found.
990 */
991 public double getAccelerometerBaseNoiseLevel() {
992 return baseNoiseLevel;
993 }
994
995 /**
996 * Gets accelerometer base noise level that has been detected during
997 * initialization of the best solution that has been found.
998 * This is equal to the standard deviation of the accelerometer measurements
999 * during the initialization phase.
1000 *
1001 * @return accelerometer base noise level of the best solution that has been
1002 * found.
1003 */
1004 public Acceleration getAccelerometerBaseNoiseLevelAsMeasurement() {
1005 return createMeasurement(baseNoiseLevel, getDefaultUnit());
1006 }
1007
1008 /**
1009 * Gets accelerometer base noise level that has been detected during
1010 * initialization of the best solution that has been found.
1011 * This is equal to the standard deviation of the accelerometer measurements
1012 * during the initialization phase.
1013 *
1014 * @param result instance where the result will be stored.
1015 */
1016 public void getAccelerometerBaseNoiseLevelAsMeasurement(final Acceleration result) {
1017 result.setValue(baseNoiseLevel);
1018 result.setUnit(getDefaultUnit());
1019 }
1020
1021 /**
1022 * Gets gyroscope base noise level PSD (Power Spectral Density)
1023 * expressed in (rad^2/s).
1024 *
1025 * @return gyroscope base noise level PSD.
1026 */
1027 public double getGyroscopeBaseNoiseLevelPsd() {
1028 return angularSpeedNoiseRootPsd * angularSpeedNoiseRootPsd;
1029 }
1030
1031 /**
1032 * Gets gyroscope base noise level root PSD (Power Spectral Density)
1033 * expressed in (rad * s^-0.5)
1034 *
1035 * @return gyroscope base noise level root PSD.
1036 */
1037 @Override
1038 public double getGyroscopeBaseNoiseLevelRootPsd() {
1039 return angularSpeedNoiseRootPsd;
1040 }
1041
1042 /**
1043 * Gets accelerometer base noise level PSD (Power Spectral Density)
1044 * expressed in (m^2 * s^-3).
1045 *
1046 * @return accelerometer base noise level PSD.
1047 */
1048 public double getAccelerometerBaseNoiseLevelPsd() {
1049 return baseNoiseLevel * baseNoiseLevel * getTimeInterval();
1050 }
1051
1052 /**
1053 * Gets accelerometer base noise level root PSD (Power Spectral Density)
1054 * expressed in (m * s^-1.5).
1055 *
1056 * @return accelerometer base noise level root PSD.
1057 */
1058 @Override
1059 public double getAccelerometerBaseNoiseLevelRootPsd() {
1060 return baseNoiseLevel * Math.sqrt(getTimeInterval());
1061 }
1062
1063 /**
1064 * Gets the threshold to determine static/dynamic period changes expressed in
1065 * meters per squared second (m/s^2) for the best calibration solution that
1066 * has been found.
1067 *
1068 * @return threshold to determine static/dynamic period changes for the best
1069 * solution.
1070 */
1071 public double getThreshold() {
1072 return threshold;
1073 }
1074
1075 /**
1076 * Gets the threshold to determine static/dynamic period changes for the best
1077 * calibration solution that has been found.
1078 *
1079 * @return threshold to determine static/dynamic period changes for the best
1080 * solution.
1081 */
1082 public Acceleration getThresholdAsMeasurement() {
1083 return createMeasurement(threshold, getDefaultUnit());
1084 }
1085
1086 /**
1087 * Get the threshold to determine static/dynamic period changes for the best
1088 * calibration solution that has been found.
1089 *
1090 * @param result instance where the result will be stored.
1091 */
1092 public void getThresholdAsMeasurement(final Acceleration result) {
1093 result.setValue(threshold);
1094 result.setUnit(getDefaultUnit());
1095 }
1096
1097 /**
1098 * Gets estimated standard deviation norm of accelerometer bias expressed in
1099 * meters per squared second (m/s^2).
1100 * This can be used as the initial accelerometer bias uncertainty for
1101 * {@link INSLooselyCoupledKalmanInitializerConfig} or {@link INSTightlyCoupledKalmanInitializerConfig}.
1102 *
1103 * @return estimated standard deviation norm of accelerometer bias or null
1104 * if not available.
1105 */
1106 public Double getEstimatedAccelerometerBiasStandardDeviationNorm() {
1107 return estimatedAccelerometerCovariance != null
1108 ? Math.sqrt(getEstimatedAccelerometerBiasFxVariance()
1109 + getEstimatedAccelerometerBiasFyVariance()
1110 + getEstimatedAccelerometerBiasFzVariance())
1111 : null;
1112 }
1113
1114 /**
1115 * Gets estimated x coordinate variance of accelerometer bias expressed in (m^2/s^4).
1116 *
1117 * @return estimated x coordinate variance of accelerometer bias or null if not available.
1118 */
1119 public Double getEstimatedAccelerometerBiasFxVariance() {
1120 return estimatedAccelerometerCovariance != null
1121 ? estimatedAccelerometerCovariance.getElementAt(0, 0) : null;
1122 }
1123
1124 /**
1125 * Gets estimated y coordinate variance of accelerometer bias expressed in (m^2/s^4).
1126 *
1127 * @return estimated y coordinate variance of accelerometer bias or null if not available.
1128 */
1129 public Double getEstimatedAccelerometerBiasFyVariance() {
1130 return estimatedAccelerometerCovariance != null
1131 ? estimatedAccelerometerCovariance.getElementAt(1, 1) : null;
1132 }
1133
1134 /**
1135 * Gets estimated z coordinate variance of accelerometer bias expressed in (m^2/s^4).
1136 *
1137 * @return estimated z coordinate variance of accelerometer bias or null if not available.
1138 */
1139 public Double getEstimatedAccelerometerBiasFzVariance() {
1140 return estimatedAccelerometerCovariance != null
1141 ? estimatedAccelerometerCovariance.getElementAt(2, 2) : null;
1142 }
1143
1144 /**
1145 * Gets the array containing x,y,z components of estimated accelerometer biases
1146 * expressed in meters per squared second (m/s^2).
1147 *
1148 * @return array containing x,y,z components of estimated accelerometer biases.
1149 */
1150 public double[] getEstimatedAccelerometerBiases() {
1151 return estimatedAccelerometerBiases;
1152 }
1153
1154 /**
1155 * Gets estimated accelerometer scale factors and cross-coupling errors.
1156 * This is the product of matrix Ta containing cross-coupling errors and Ka
1157 * containing scaling factors.
1158 * So that:
1159 * <pre>
1160 * Ma = [sx mxy mxz] = Ta*Ka
1161 * [myx sy myz]
1162 * [mzx mzy sz ]
1163 * </pre>
1164 * Where:
1165 * <pre>
1166 * Ka = [sx 0 0 ]
1167 * [0 sy 0 ]
1168 * [0 0 sz]
1169 * </pre>
1170 * and
1171 * <pre>
1172 * Ta = [1 -alphaXy alphaXz ]
1173 * [alphaYx 1 -alphaYz]
1174 * [-alphaZx alphaZy 1 ]
1175 * </pre>
1176 * Hence:
1177 * <pre>
1178 * Ma = [sx mxy mxz] = Ta*Ka = [sx -sy * alphaXy sz * alphaXz ]
1179 * [myx sy myz] [sx * alphaYx sy -sz * alphaYz]
1180 * [mzx mzy sz ] [-sx * alphaZx sy * alphaZy sz ]
1181 * </pre>
1182 * This instance allows any 3x3 matrix. However, typically alphaYx, alphaZx and alphaZy
1183 * are considered to be zero if the accelerometer z-axis is assumed to be the same
1184 * as the body z-axis. When this is assumed, myx = mzx = mzy = 0 and the Ma matrix
1185 * becomes upper diagonal:
1186 * <pre>
1187 * Ma = [sx mxy mxz]
1188 * [0 sy myz]
1189 * [0 0 sz ]
1190 * </pre>
1191 * Values of this matrix are unit-less.
1192 *
1193 * @return estimated accelerometer scale factors and cross-coupling errors, or null
1194 * if not available.
1195 */
1196 public Matrix getEstimatedAccelerometerMa() {
1197 return estimatedAccelerometerMa;
1198 }
1199
1200 /**
1201 * Gets estimated standard deviation norm of gyroscope bias expressed in
1202 * radians per second (rad/s).
1203 * This can be used as the initial gyroscope bias uncertainty for
1204 * {@link INSLooselyCoupledKalmanInitializerConfig} or {@link INSTightlyCoupledKalmanInitializerConfig}.
1205 *
1206 * @return estimated standard deviation norm of gyroscope bias or null
1207 * if not available.
1208 */
1209 public Double getEstimatedGyroscopeBiasStandardDeviationNorm() {
1210 return estimatedGyroscopeCovariance != null
1211 ? Math.sqrt(getEstimatedGyroscopeBiasXVariance()
1212 + getEstimatedGyroscopeBiasYVariance()
1213 + getEstimatedGyroscopeBiasZVariance())
1214 : null;
1215 }
1216
1217 /**
1218 * Gets estimated x coordinate variance of gyroscope bias expressed in (rad^2/s^2).
1219 *
1220 * @return estimated x coordinate variance of gyroscope bias or null if not available.
1221 */
1222 public Double getEstimatedGyroscopeBiasXVariance() {
1223 return estimatedGyroscopeCovariance != null
1224 ? estimatedGyroscopeCovariance.getElementAt(0, 0) : null;
1225 }
1226
1227 /**
1228 * Gets estimated y coordinate variance of gyroscope bias expressed in (rad^2/s^2).
1229 *
1230 * @return estimated y coordinate variance of gyroscope bias or null if not available.
1231 */
1232 public Double getEstimatedGyroscopeBiasYVariance() {
1233 return estimatedGyroscopeCovariance != null
1234 ? estimatedGyroscopeCovariance.getElementAt(1, 1) : null;
1235 }
1236
1237 /**
1238 * Gets estimated z coordinate variance of gyroscope bias expressed in (rad^2/s^2).
1239 *
1240 * @return estimated z coordinate variance of gyroscope bias or null if not available.
1241 */
1242 public Double getEstimatedGyroscopeBiasZVariance() {
1243 return estimatedGyroscopeCovariance != null
1244 ? estimatedGyroscopeCovariance.getElementAt(2, 2) : null;
1245 }
1246
1247 /**
1248 * Gets the array containing x,y,z components of estimated gyroscope biases
1249 * expressed in radians per second (rad/s).
1250 *
1251 * @return array containing x,y,z components of estimated gyroscope biases.
1252 */
1253 public double[] getEstimatedGyroscopeBiases() {
1254 return estimatedGyroscopeBiases;
1255 }
1256
1257 /**
1258 * Gets estimated gyroscope scale factors and cross-coupling errors.
1259 * This is the product of matrix Tg containing cross-coupling errors and Kg
1260 * containing scaling factors.
1261 * So that:
1262 * <pre>
1263 * Mg = [sx mxy mxz] = Tg*Kg
1264 * [myx sy myz]
1265 * [mzx mzy sz ]
1266 * </pre>
1267 * Where:
1268 * <pre>
1269 * Kg = [sx 0 0 ]
1270 * [0 sy 0 ]
1271 * [0 0 sz]
1272 * </pre>
1273 * and
1274 * <pre>
1275 * Tg = [1 -alphaXy alphaXz ]
1276 * [alphaYx 1 -alphaYz]
1277 * [-alphaZx alphaZy 1 ]
1278 * </pre>
1279 * Hence:
1280 * <pre>
1281 * Mg = [sx mxy mxz] = Tg*Kg = [sx -sy * alphaXy sz * alphaXz ]
1282 * [myx sy myz] [sx * alphaYx sy -sz * alphaYz]
1283 * [mzx mzy sz ] [-sx * alphaZx sy * alphaZy sz ]
1284 * </pre>
1285 * This instance allows any 3x3 matrix. However, typically alphaYx, alphaZx and alphaZy
1286 * are considered to be zero if the gyroscope z-axis is assumed to be the same
1287 * as the body z-axis. When this is assumed, myx = mzx = mzy = 0 and the Mg matrix
1288 * becomes upper diagonal:
1289 * <pre>
1290 * Mg = [sx mxy mxz]
1291 * [0 sy myz]
1292 * [0 0 sz ]
1293 * </pre>
1294 * Values of this matrix are unit-less.
1295 *
1296 * @return estimated gyroscope scale factors and cross-coupling errors.
1297 */
1298 public Matrix getEstimatedGyroscopeMg() {
1299 return estimatedGyroscopeMg;
1300 }
1301
1302 /**
1303 * Gets estimated G-dependent cross-biases introduced on the gyroscope by the
1304 * specific forces sensed by the accelerometer.
1305 *
1306 * @return a 3x3 matrix containing g-dependent cross biases.
1307 */
1308 public Matrix getEstimatedGyroscopeGg() {
1309 return estimatedGyroscopeGg;
1310 }
1311
1312 /**
1313 * Gets the array containing x,y,z components of estimated magnetometer
1314 * hard-iron biases expressed in Teslas (T).
1315 *
1316 * @return array containing x,y,z components of estimated magnetometer
1317 * hard-iron biases.
1318 */
1319 public double[] getEstimatedMagnetometerHardIron() {
1320 return estimatedMagnetometerHardIron;
1321 }
1322
1323 /**
1324 * Gets estimated magnetometer soft-iron matrix containing scale factors
1325 * and cross-coupling errors.
1326 * This is the product of matrix Tm containing cross-coupling errors and Km
1327 * containing scaling factors.
1328 * So that:
1329 * <pre>
1330 * Mm = [sx mxy mxz] = Tm*Km
1331 * [myx sy myz]
1332 * [mzx mzy sz ]
1333 * </pre>
1334 * Where:
1335 * <pre>
1336 * Km = [sx 0 0 ]
1337 * [0 sy 0 ]
1338 * [0 0 sz]
1339 * </pre>
1340 * and
1341 * <pre>
1342 * Tm = [1 -alphaXy alphaXz ]
1343 * [alphaYx 1 -alphaYz]
1344 * [-alphaZx alphaZy 1 ]
1345 * </pre>
1346 * Hence:
1347 * <pre>
1348 * Mm = [sx mxy mxz] = Tm*Km = [sx -sy * alphaXy sz * alphaXz ]
1349 * [myx sy myz] [sx * alphaYx sy -sz * alphaYz]
1350 * [mzx mzy sz ] [-sx * alphaZx sy * alphaZy sz ]
1351 * </pre>
1352 * This instance allows any 3x3 matrix. However, typically alphaYx, alphaZx and alphaZy
1353 * are considered to be zero if the accelerometer z-axis is assumed to be the same
1354 * as the body z-axis. When this is assumed, myx = mzx = mzy = 0 and the Mm matrix
1355 * becomes upper diagonal:
1356 * <pre>
1357 * Mm = [sx mxy mxz]
1358 * [0 sy myz]
1359 * [0 0 sz ]
1360 * </pre>
1361 * Values of this matrix are unit-less.
1362 *
1363 * @return estimated magnetometer soft-iron scale factors and cross-coupling errors,
1364 * or null if not available.
1365 */
1366 public Matrix getEstimatedMagnetometerMm() {
1367 return estimatedMagnetometerMm;
1368 }
1369
1370 /**
1371 * Evaluates calibration Mean Square Error (MSE) for the provided threshold factor.
1372 *
1373 * @param thresholdFactor threshold factor to be used for interval detection
1374 * and measurement generation to be used for
1375 * calibration.
1376 * @return calibration MSE.
1377 * @throws LockedException if the generator is busy.
1378 * @throws CalibrationException if calibration fails.
1379 * @throws NotReadyException if the calibrator is not ready.
1380 * @throws IntervalDetectorThresholdFactorOptimizerException interval detection failed.
1381 */
1382 protected double evaluateForThresholdFactor(final double thresholdFactor) throws LockedException,
1383 CalibrationException, NotReadyException, IntervalDetectorThresholdFactorOptimizerException {
1384 if (accelerometerMeasurements == null) {
1385 accelerometerMeasurements = new ArrayList<>();
1386 } else {
1387 accelerometerMeasurements.clear();
1388 }
1389
1390 if (gyroscopeSequences == null) {
1391 gyroscopeSequences = new ArrayList<>();
1392 } else {
1393 gyroscopeSequences.clear();
1394 }
1395
1396 if (magnetometerMeasurements == null) {
1397 magnetometerMeasurements = new ArrayList<>();
1398 } else {
1399 magnetometerMeasurements.clear();
1400 }
1401
1402 generator.reset();
1403 generator.setThresholdFactor(thresholdFactor);
1404
1405 var count = dataSource.count();
1406 var failed = false;
1407 for (var i = 0; i < count; i++) {
1408 final var timedBodyKinematics = dataSource.getAt(i);
1409 if (!generator.process(timedBodyKinematics)) {
1410 failed = true;
1411 break;
1412 }
1413 }
1414
1415 if (failed || generator.getStatus() == TriadStaticIntervalDetector.Status.FAILED) {
1416 // interval detection failed
1417 return Double.MAX_VALUE;
1418 }
1419
1420 // check that enough measurements have been obtained
1421 if (accelerometerMeasurements.size() < accelerometerCalibrator.getMinimumRequiredMeasurements()
1422 || gyroscopeSequences.size() < gyroscopeCalibrator.getMinimumRequiredMeasurementsOrSequences()
1423 || magnetometerMeasurements.size() < magnetometerCalibrator.getMinimumRequiredMeasurements()) {
1424 return Double.MAX_VALUE;
1425 }
1426
1427 // set calibrator measurements
1428 switch (accelerometerCalibrator.getMeasurementType()) {
1429 case STANDARD_DEVIATION_BODY_KINEMATICS:
1430 if (accelerometerCalibrator.isOrderedMeasurementsRequired()) {
1431 final var calibrator =
1432 (OrderedStandardDeviationBodyKinematicsAccelerometerCalibrator) accelerometerCalibrator;
1433
1434 calibrator.setMeasurements(accelerometerMeasurements);
1435
1436 } else {
1437 final var calibrator =
1438 (UnorderedStandardDeviationBodyKinematicsAccelerometerCalibrator) accelerometerCalibrator;
1439
1440 calibrator.setMeasurements(accelerometerMeasurements);
1441 }
1442
1443 if (accelerometerCalibrator.isQualityScoresRequired()) {
1444 final var calibrator = (QualityScoredAccelerometerCalibrator) accelerometerCalibrator;
1445
1446 final var size = accelerometerMeasurements.size();
1447 final var qualityScores = new double[size];
1448 for (var i = 0; i < size; i++) {
1449 qualityScores[i] = accelerometerQualityScoreMapper.map(accelerometerMeasurements.get(i));
1450 }
1451 calibrator.setQualityScores(qualityScores);
1452 }
1453 break;
1454 case FRAME_BODY_KINEMATICS, STANDARD_DEVIATION_FRAME_BODY_KINEMATICS:
1455 // Throw exception. Cannot use frames
1456 default:
1457 throw new IntervalDetectorThresholdFactorOptimizerException();
1458 }
1459
1460 if (gyroscopeCalibrator.getMeasurementOrSequenceType()
1461 == GyroscopeCalibratorMeasurementOrSequenceType.BODY_KINEMATICS_SEQUENCE) {
1462 final var calibrator = (OrderedBodyKinematicsSequenceGyroscopeCalibrator) gyroscopeCalibrator;
1463
1464 calibrator.setSequences(gyroscopeSequences);
1465 } else {
1466 throw new IntervalDetectorThresholdFactorOptimizerException();
1467 }
1468
1469 if (gyroscopeCalibrator.isQualityScoresRequired()) {
1470 final var calibrator = (QualityScoredGyroscopeCalibrator) gyroscopeCalibrator;
1471
1472 final var size = gyroscopeSequences.size();
1473 final var qualityScores = new double[size];
1474 for (var i = 0; i < size; i++) {
1475 qualityScores[i] = gyroscopeQualityScoreMapper.map(gyroscopeSequences.get(i));
1476 }
1477 calibrator.setQualityScores(qualityScores);
1478 }
1479
1480 switch (magnetometerCalibrator.getMeasurementType()) {
1481 case STANDARD_DEVIATION_BODY_MAGNETIC_FLUX_DENSITY:
1482 if (magnetometerCalibrator.isOrderedMeasurementsRequired()) {
1483 final var calibrator = (OrderedStandardDeviationBodyMagneticFluxDensityMagnetometerCalibrator)
1484 magnetometerCalibrator;
1485
1486 calibrator.setMeasurements(magnetometerMeasurements);
1487 } else {
1488 final var calibrator = (UnorderedStandardDeviationBodyMagneticFluxDensityMagnetometerCalibrator)
1489 magnetometerCalibrator;
1490
1491 calibrator.setMeasurements(magnetometerMeasurements);
1492 }
1493
1494 if (magnetometerCalibrator.isQualityScoresRequired()) {
1495 final var calibrator = (QualityScoredMagnetometerCalibrator) magnetometerCalibrator;
1496
1497 final var size = magnetometerMeasurements.size();
1498 final var qualityScores = new double[size];
1499 for (var i = 0; i < size; i++) {
1500 qualityScores[i] = magnetometerQualityScoreMapper.map(magnetometerMeasurements.get(i));
1501 }
1502 calibrator.setQualityScores(qualityScores);
1503 }
1504 break;
1505 case FRAME_BODY_MAGNETIC_FLUX_DENSITY, STANDARD_DEVIATION_FRAME_BODY_MAGNETIC_FLUX_DENSITY:
1506 // Throw exception. Cannot use frames
1507 default:
1508 throw new IntervalDetectorThresholdFactorOptimizerException();
1509 }
1510
1511 accelerometerCalibrator.calibrate();
1512
1513 // once we have accelerometer estimations, we can set known
1514 // accelerometer bias and cross-coupling errors to the gyroscope calibrator
1515 if (gyroscopeCalibrator instanceof AccelerometerDependentGyroscopeCalibrator accelGyroCalibrator) {
1516
1517 final double[] bias;
1518 if (accelerometerCalibrator instanceof UnknownBiasAccelerometerCalibrator unknownBiasAccelerometerCalibrator) {
1519 bias = unknownBiasAccelerometerCalibrator.getEstimatedBiases();
1520
1521 } else if (accelerometerCalibrator instanceof KnownBiasAccelerometerCalibrator knownBiasAccelerometerCalibrator) {
1522 bias = knownBiasAccelerometerCalibrator.getBias();
1523 } else {
1524 bias = null;
1525 }
1526
1527 if (bias != null) {
1528 accelGyroCalibrator.setAccelerometerBias(bias);
1529 accelGyroCalibrator.setAccelerometerMa(accelerometerCalibrator.getEstimatedMa());
1530 }
1531 }
1532
1533 gyroscopeCalibrator.calibrate();
1534 magnetometerCalibrator.calibrate();
1535
1536 final var accelerometerMse = accelerometerCalibrator.getEstimatedMse();
1537 final var gyroscopeMse = gyroscopeCalibrator.getEstimatedMse();
1538 final var magnetometerMse = magnetometerCalibrator.getEstimatedMse();
1539
1540 // convert accelerometer and gyroscope mse to global mse
1541 final var mse = mseRule.evaluate(accelerometerMse, gyroscopeMse, magnetometerMse);
1542 if (mse < minMse) {
1543 keepBestResult(mse, thresholdFactor);
1544 }
1545 return mse;
1546 }
1547
1548 /**
1549 * Initializes accelerometer, gyroscope and magnetometer measurement generator to
1550 * convert {@link TimedBodyKinematicsAndMagneticFluxDensity} measurements after
1551 * interval detection into measurements and sequences used for accelerometer,
1552 * gyroscope and magnetometer calibration.
1553 */
1554 private void initialize() {
1555 final var generatorListener = new AccelerometerGyroscopeAndMagnetometerMeasurementsGeneratorListener() {
1556 @Override
1557 public void onInitializationStarted(
1558 final AccelerometerGyroscopeAndMagnetometerMeasurementsGenerator generator) {
1559 // not needed
1560 }
1561
1562 @Override
1563 public void onInitializationCompleted(
1564 final AccelerometerGyroscopeAndMagnetometerMeasurementsGenerator generator,
1565 final double accelerometerBaseNoiseLevel) {
1566 // not needed
1567 }
1568
1569 @Override
1570 public void onError(
1571 final AccelerometerGyroscopeAndMagnetometerMeasurementsGenerator generator,
1572 final TriadStaticIntervalDetector.ErrorReason reason) {
1573 // not needed
1574 }
1575
1576 @Override
1577 public void onStaticIntervalDetected(
1578 final AccelerometerGyroscopeAndMagnetometerMeasurementsGenerator generator) {
1579 // not needed
1580 }
1581
1582 @Override
1583 public void onDynamicIntervalDetected(
1584 final AccelerometerGyroscopeAndMagnetometerMeasurementsGenerator generator) {
1585 // not needed
1586 }
1587
1588 @Override
1589 public void onStaticIntervalSkipped(
1590 final AccelerometerGyroscopeAndMagnetometerMeasurementsGenerator generator) {
1591 // not needed
1592 }
1593
1594 @Override
1595 public void onDynamicIntervalSkipped(
1596 final AccelerometerGyroscopeAndMagnetometerMeasurementsGenerator generator) {
1597 // not needed
1598 }
1599
1600 @Override
1601 public void onGeneratedAccelerometerMeasurement(
1602 final AccelerometerGyroscopeAndMagnetometerMeasurementsGenerator generator,
1603 final StandardDeviationBodyKinematics measurement) {
1604 accelerometerMeasurements.add(measurement);
1605 }
1606
1607 @Override
1608 public void onGeneratedGyroscopeMeasurement(
1609 final AccelerometerGyroscopeAndMagnetometerMeasurementsGenerator generator,
1610 final BodyKinematicsSequence<StandardDeviationTimedBodyKinematics> sequence) {
1611 gyroscopeSequences.add(sequence);
1612 }
1613
1614 @Override
1615 public void onGeneratedMagnetometerMeasurement(
1616 final AccelerometerGyroscopeAndMagnetometerMeasurementsGenerator generator,
1617 final StandardDeviationBodyMagneticFluxDensity measurement) {
1618 magnetometerMeasurements.add(measurement);
1619 }
1620
1621 @Override
1622 public void onReset(final AccelerometerGyroscopeAndMagnetometerMeasurementsGenerator generator) {
1623 // not needed
1624 }
1625 };
1626
1627 generator = new AccelerometerGyroscopeAndMagnetometerMeasurementsGenerator(generatorListener);
1628 }
1629
1630 /**
1631 * Keeps the best calibration solution found so far.
1632 *
1633 * @param mse Estimated Mean Square Error during calibration.
1634 * @param thresholdFactor threshold factor to be kept.
1635 */
1636 private void keepBestResult(final double mse, final double thresholdFactor) {
1637 minMse = mse;
1638 optimalThresholdFactor = thresholdFactor;
1639
1640 angularSpeedNoiseRootPsd = generator.getGyroscopeBaseNoiseLevelRootPsd();
1641 baseNoiseLevel = generator.getAccelerometerBaseNoiseLevel();
1642 threshold = generator.getThreshold();
1643
1644 if (estimatedAccelerometerCovariance == null) {
1645 estimatedAccelerometerCovariance = new Matrix(accelerometerCalibrator.getEstimatedCovariance());
1646 } else {
1647 estimatedAccelerometerCovariance.copyFrom(accelerometerCalibrator.getEstimatedCovariance());
1648 }
1649 if (estimatedAccelerometerMa == null) {
1650 estimatedAccelerometerMa = new Matrix(accelerometerCalibrator.getEstimatedMa());
1651 } else {
1652 estimatedAccelerometerMa.copyFrom(accelerometerCalibrator.getEstimatedMa());
1653 }
1654 if (accelerometerCalibrator instanceof UnknownBiasAccelerometerCalibrator unknownBiasAccelerometerCalibrator) {
1655 estimatedAccelerometerBiases = unknownBiasAccelerometerCalibrator.getEstimatedBiases();
1656 } else if (accelerometerCalibrator instanceof KnownBiasAccelerometerCalibrator knownBiasAccelerometerCalibrator) {
1657 estimatedAccelerometerBiases = knownBiasAccelerometerCalibrator.getBias();
1658 }
1659
1660 if (estimatedGyroscopeCovariance == null) {
1661 estimatedGyroscopeCovariance = new Matrix(gyroscopeCalibrator.getEstimatedCovariance());
1662 } else {
1663 estimatedGyroscopeCovariance.copyFrom(gyroscopeCalibrator.getEstimatedCovariance());
1664 }
1665 if (estimatedGyroscopeMg == null) {
1666 estimatedGyroscopeMg = new Matrix(gyroscopeCalibrator.getEstimatedMg());
1667 } else {
1668 estimatedGyroscopeMg.copyFrom(gyroscopeCalibrator.getEstimatedMg());
1669 }
1670 if (estimatedGyroscopeGg == null) {
1671 estimatedGyroscopeGg = new Matrix(gyroscopeCalibrator.getEstimatedGg());
1672 } else {
1673 estimatedGyroscopeGg.copyFrom(gyroscopeCalibrator.getEstimatedGg());
1674 }
1675 if (gyroscopeCalibrator instanceof UnknownBiasGyroscopeCalibrator unknownBiasGyroscopeCalibrator) {
1676 estimatedGyroscopeBiases = unknownBiasGyroscopeCalibrator.getEstimatedBiases();
1677 } else if (gyroscopeCalibrator instanceof KnownBiasAccelerometerCalibrator knownBiasAccelerometerCalibrator) {
1678 estimatedGyroscopeBiases = knownBiasAccelerometerCalibrator.getBias();
1679 }
1680
1681 if (estimatedMagnetometerCovariance == null) {
1682 estimatedMagnetometerCovariance = new Matrix(magnetometerCalibrator.getEstimatedCovariance());
1683 } else {
1684 estimatedMagnetometerCovariance.copyFrom(magnetometerCalibrator.getEstimatedCovariance());
1685 }
1686 if (estimatedMagnetometerMm == null) {
1687 estimatedMagnetometerMm = new Matrix(magnetometerCalibrator.getEstimatedMm());
1688 } else {
1689 estimatedMagnetometerMm.copyFrom(magnetometerCalibrator.getEstimatedMm());
1690 }
1691 if (magnetometerCalibrator instanceof UnknownHardIronMagnetometerCalibrator unknownHardIronMagnetometerCalibrator) {
1692 estimatedMagnetometerHardIron = unknownHardIronMagnetometerCalibrator.getEstimatedHardIron();
1693 } else if (magnetometerCalibrator instanceof KnownHardIronMagnetometerCalibrator knownHardIronMagnetometerCalibrator) {
1694 estimatedMagnetometerHardIron = knownHardIronMagnetometerCalibrator.getHardIron();
1695 }
1696 }
1697
1698 /**
1699 * Creates an acceleration instance using the provided value and unit.
1700 *
1701 * @param value value of measurement.
1702 * @param unit unit of value.
1703 * @return created acceleration.
1704 */
1705 private Acceleration createMeasurement(final double value, final AccelerationUnit unit) {
1706 return new Acceleration(value, unit);
1707 }
1708
1709 /**
1710 * Gets the default unit for acceleration, which is meters per
1711 * squared second (m/s^2).
1712 *
1713 * @return default unit for acceleration.
1714 */
1715 private AccelerationUnit getDefaultUnit() {
1716 return AccelerationUnit.METERS_PER_SQUARED_SECOND;
1717 }
1718 }