View Javadoc
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 }