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.BodyKinematics;
22  import com.irurueta.navigation.inertial.INSLooselyCoupledKalmanInitializerConfig;
23  import com.irurueta.navigation.inertial.INSTightlyCoupledKalmanInitializerConfig;
24  import com.irurueta.navigation.inertial.calibration.AccelerometerBiasUncertaintySource;
25  import com.irurueta.navigation.inertial.calibration.AccelerometerCalibrationSource;
26  import com.irurueta.navigation.inertial.calibration.AccelerometerNoiseRootPsdSource;
27  import com.irurueta.navigation.inertial.calibration.CalibrationException;
28  import com.irurueta.navigation.inertial.calibration.StandardDeviationBodyKinematics;
29  import com.irurueta.navigation.inertial.calibration.accelerometer.AccelerometerCalibratorMeasurementType;
30  import com.irurueta.navigation.inertial.calibration.accelerometer.AccelerometerNonLinearCalibrator;
31  import com.irurueta.navigation.inertial.calibration.accelerometer.KnownBiasAccelerometerCalibrator;
32  import com.irurueta.navigation.inertial.calibration.accelerometer.OrderedStandardDeviationBodyKinematicsAccelerometerCalibrator;
33  import com.irurueta.navigation.inertial.calibration.accelerometer.QualityScoredAccelerometerCalibrator;
34  import com.irurueta.navigation.inertial.calibration.accelerometer.UnknownBiasAccelerometerCalibrator;
35  import com.irurueta.navigation.inertial.calibration.accelerometer.UnorderedStandardDeviationBodyKinematicsAccelerometerCalibrator;
36  import com.irurueta.navigation.inertial.calibration.generators.AccelerometerMeasurementsGenerator;
37  import com.irurueta.navigation.inertial.calibration.generators.AccelerometerMeasurementsGeneratorListener;
38  import com.irurueta.navigation.inertial.calibration.intervals.TriadStaticIntervalDetector;
39  import com.irurueta.units.Acceleration;
40  import com.irurueta.units.AccelerationUnit;
41  import com.irurueta.units.Time;
42  
43  import java.util.ArrayList;
44  import java.util.List;
45  
46  /**
47   * Optimizes the threshold factor for interval detection of accelerometer data based
48   * on results of calibration.
49   * Implementations of this class will attempt to find the best threshold factor
50   * between the provided range of values.
51   * Only accelerometer calibrators based on unknown orientation are supported (in other terms,
52   * calibrators must be {@link AccelerometerNonLinearCalibrator} and must support
53   * {@link AccelerometerCalibratorMeasurementType#STANDARD_DEVIATION_BODY_KINEMATICS}).
54   */
55  public abstract class AccelerometerIntervalDetectorThresholdFactorOptimizer extends
56          IntervalDetectorThresholdFactorOptimizer<BodyKinematics,
57                  AccelerometerIntervalDetectorThresholdFactorOptimizerDataSource>
58          implements AccelerometerNoiseRootPsdSource, AccelerometerBiasUncertaintySource,
59          AccelerometerCalibrationSource {
60  
61      /**
62       * Default minimum threshold factor.
63       */
64      public static final double DEFAULT_MIN_THRESHOLD_FACTOR = 2.0;
65  
66      /**
67       * Default maximum threshold factor.
68       */
69      public static final double DEFAULT_MAX_THRESHOLD_FACTOR = 10.0;
70  
71      /**
72       * Minimum threshold factor.
73       */
74      protected double minThresholdFactor = DEFAULT_MIN_THRESHOLD_FACTOR;
75  
76      /**
77       * Maximum threshold factor.
78       */
79      protected double maxThresholdFactor = DEFAULT_MAX_THRESHOLD_FACTOR;
80  
81      /**
82       * Accelerometer calibrator.
83       */
84      private AccelerometerNonLinearCalibrator calibrator;
85  
86      /**
87       * A measurement generator for accelerometer calibrators.
88       */
89      private AccelerometerMeasurementsGenerator generator;
90  
91      /**
92       * Generated measurements to be used for accelerometer calibration.
93       */
94      private List<StandardDeviationBodyKinematics> measurements;
95  
96      /**
97       * Mapper to convert {@link StandardDeviationBodyKinematics} measurements into
98       * quality scores.
99       */
100     private QualityScoreMapper<StandardDeviationBodyKinematics> qualityScoreMapper =
101             new DefaultAccelerometerQualityScoreMapper();
102 
103     /**
104      * Accelerometer base noise level that has been detected during
105      * initialization of the best solution that has been found expressed in
106      * meters per squared second (m/s^2).
107      * This is equal to the standard deviation of the accelerometer measurements
108      * during the initialization phase.
109      */
110     private double baseNoiseLevel;
111 
112     /**
113      * Threshold to determine static/dynamic period changes expressed in
114      * meters per squared second (m/s^2) for the best calibration solution that
115      * has been found.
116      */
117     private double threshold;
118 
119     /**
120      * Estimated covariance matrix for estimated parameters.
121      */
122     private Matrix estimatedCovariance;
123 
124     /**
125      * Estimated accelerometer scale factors and cross-coupling errors.
126      * This is the product of matrix Ta containing cross-coupling errors and Ka
127      * containing scaling factors.
128      * So that:
129      * <pre>
130      *     Ma = [sx    mxy  mxz] = Ta*Ka
131      *          [myx   sy   myz]
132      *          [mzx   mzy  sz ]
133      * </pre>
134      * Where:
135      * <pre>
136      *     Ka = [sx 0   0 ]
137      *          [0  sy  0 ]
138      *          [0  0   sz]
139      * </pre>
140      * and
141      * <pre>
142      *     Ta = [1          -alphaXy    alphaXz ]
143      *          [alphaYx    1           -alphaYz]
144      *          [-alphaZx   alphaZy     1       ]
145      * </pre>
146      * Hence:
147      * <pre>
148      *     Ma = [sx    mxy  mxz] = Ta*Ka =  [sx             -sy * alphaXy   sz * alphaXz ]
149      *          [myx   sy   myz]            [sx * alphaYx   sy              -sz * alphaYz]
150      *          [mzx   mzy  sz ]            [-sx * alphaZx  sy * alphaZy    sz           ]
151      * </pre>
152      * This instance allows any 3x3 matrix. However, typically alphaYx, alphaZx and alphaZy
153      * are considered to be zero if the accelerometer z-axis is assumed to be the same
154      * as the body z-axis. When this is assumed, myx = mzx = mzy = 0 and the Ma matrix
155      * becomes upper diagonal:
156      * <pre>
157      *     Ma = [sx    mxy  mxz]
158      *          [0     sy   myz]
159      *          [0     0    sz ]
160      * </pre>
161      * Values of this matrix are unit-less.
162      */
163     private Matrix estimatedMa;
164 
165     /**
166      * Estimated accelerometer biases for each IMU axis expressed in meter per squared
167      * second (m/s^2).
168      */
169     private double[] estimatedBiases;
170 
171     /**
172      * Constructor.
173      */
174     protected AccelerometerIntervalDetectorThresholdFactorOptimizer() {
175         initialize();
176     }
177 
178     /**
179      * Constructor.
180      *
181      * @param dataSource instance in charge of retrieving data for this optimizer.
182      */
183     protected AccelerometerIntervalDetectorThresholdFactorOptimizer(
184             final AccelerometerIntervalDetectorThresholdFactorOptimizerDataSource dataSource) {
185         super(dataSource);
186         initialize();
187     }
188 
189     /**
190      * Constructor.
191      *
192      * @param calibrator an accelerometer calibrator to be used to optimize its
193      *                   Mean Square Error (MSE).
194      * @throws IllegalArgumentException if accelerometer calibrator does not use
195      *                                  {@link StandardDeviationBodyKinematics} measurements.
196      */
197     protected AccelerometerIntervalDetectorThresholdFactorOptimizer(final AccelerometerNonLinearCalibrator calibrator) {
198         try {
199             setCalibrator(calibrator);
200         } catch (final LockedException ignore) {
201             // never happens
202         }
203         initialize();
204     }
205 
206     /**
207      * Constructor.
208      *
209      * @param dataSource instance in charge of retrieving data for this optimizer.
210      * @param calibrator an accelerometer calibrator to be used to optimize its
211      *                   Mean Square Error (MSE).
212      * @throws IllegalArgumentException if accelerometer calibrator does not use
213      *                                  {@link StandardDeviationBodyKinematics} measurements.
214      */
215     protected AccelerometerIntervalDetectorThresholdFactorOptimizer(
216             final AccelerometerIntervalDetectorThresholdFactorOptimizerDataSource dataSource,
217             final AccelerometerNonLinearCalibrator calibrator) {
218         super(dataSource);
219         try {
220             setCalibrator(calibrator);
221         } catch (final LockedException ignore) {
222             // never happens
223         }
224         initialize();
225     }
226 
227     /**
228      * Gets provided accelerometer calibrator to be used to optimize its Mean Square Error (MSE).
229      *
230      * @return accelerometer calibrator to be used to optimize its MSE.
231      */
232     public AccelerometerNonLinearCalibrator getCalibrator() {
233         return calibrator;
234     }
235 
236     /**
237      * Sets accelerometer calibrator to be used to optimize its Mean Square Error (MSE).
238      *
239      * @param calibrator accelerometer calibrator to be used to optimize its MSE.
240      * @throws LockedException          if optimizer is already running.
241      * @throws IllegalArgumentException if accelerometer calibrator does not use
242      *                                  {@link StandardDeviationBodyKinematics} measurements.
243      */
244     public void setCalibrator(final AccelerometerNonLinearCalibrator calibrator) throws LockedException {
245         if (running) {
246             throw new LockedException();
247         }
248 
249         if (calibrator != null && calibrator.getMeasurementType()
250                 != AccelerometerCalibratorMeasurementType.STANDARD_DEVIATION_BODY_KINEMATICS) {
251             throw new IllegalArgumentException();
252         }
253 
254         this.calibrator = calibrator;
255     }
256 
257     /**
258      * Gets mapper to convert {@link StandardDeviationBodyKinematics} measurements into
259      * quality scores.
260      *
261      * @return mapper to convert measurements into quality scores.
262      */
263     public QualityScoreMapper<StandardDeviationBodyKinematics> getQualityScoreMapper() {
264         return qualityScoreMapper;
265     }
266 
267     /**
268      * Sets mapper to convert {@link StandardDeviationBodyKinematics} measurements into
269      * quality scores.
270      *
271      * @param qualityScoreMapper mapper to convert measurements into quality scores.
272      * @throws LockedException if optimizer is already running.
273      */
274     public void setQualityScoreMapper(
275             final QualityScoreMapper<StandardDeviationBodyKinematics> qualityScoreMapper) throws LockedException {
276         if (running) {
277             throw new LockedException();
278         }
279 
280         this.qualityScoreMapper = qualityScoreMapper;
281     }
282 
283     /**
284      * Gets the minimum threshold factor.
285      *
286      * @return minimum threshold factor.
287      */
288     public double getMinThresholdFactor() {
289         return minThresholdFactor;
290     }
291 
292     /**
293      * Gets the maximum threshold factor.
294      *
295      * @return maximum threshold factor.
296      */
297     public double getMaxThresholdFactor() {
298         return maxThresholdFactor;
299     }
300 
301     /**
302      * Sets a range of threshold factor values to get an optimized
303      * threshold factor value.
304      *
305      * @param minThresholdFactor minimum threshold.
306      * @param maxThresholdFactor maximum threshold.
307      * @throws LockedException          if optimizer is already running.
308      * @throws IllegalArgumentException if either minimum or maximum values are
309      *                                  negative, or if the minimum value is larger
310      *                                  than the maximum one.
311      */
312     public void setThresholdFactorRange(final double minThresholdFactor, final double maxThresholdFactor)
313             throws LockedException {
314         if (running) {
315             throw new LockedException();
316         }
317         if (minThresholdFactor < 0.0 || maxThresholdFactor < 0.0 || minThresholdFactor >= maxThresholdFactor) {
318             throw new IllegalArgumentException();
319         }
320 
321         this.minThresholdFactor = minThresholdFactor;
322         this.maxThresholdFactor = maxThresholdFactor;
323     }
324 
325     /**
326      * Indicates whether this optimizer is ready to start optimization.
327      *
328      * @return true if this optimizer is ready, false otherwise.
329      */
330     @Override
331     public boolean isReady() {
332         return super.isReady() && calibrator != null && qualityScoreMapper != null;
333     }
334 
335     /**
336      * Gets the time interval between input measurements provided to the
337      * {@link #getDataSource()} expressed in seconds (s).
338      *
339      * @return time interval between input measurements.
340      */
341     public double getTimeInterval() {
342         return generator.getTimeInterval();
343     }
344 
345     /**
346      * Sets time interval between input measurements provided to the
347      * {@link #getDataSource()} expressed in seconds (s).
348      *
349      * @param timeInterval time interval between input measurements.
350      * @throws LockedException          if optimizer is already running.
351      * @throws IllegalArgumentException if provided value is negative.
352      */
353     public void setTimeInterval(final double timeInterval) throws LockedException {
354         if (running) {
355             throw new LockedException();
356         }
357 
358         generator.setTimeInterval(timeInterval);
359     }
360 
361     /**
362      * Gets the time interval between input measurements provided to the
363      * {@link #getDataSource()}.
364      *
365      * @return time interval between input measurements.
366      */
367     public Time getTimeIntervalAsTime() {
368         return generator.getTimeIntervalAsTime();
369     }
370 
371     /**
372      * Gets the time interval between input measurements provided to the
373      * {@link #getDataSource()}.
374      *
375      * @param result instance where the time interval will be stored.
376      */
377     public void getTimeIntervalAsTime(final Time result) {
378         generator.getTimeIntervalAsTime(result);
379     }
380 
381     /**
382      * Sets time interval between input measurements provided to the
383      * {@link #getDataSource()}.
384      *
385      * @param timeInterval time interval between input measurements.
386      * @throws LockedException          if optimizer is already running.
387      * @throws IllegalArgumentException if provided value is negative.
388      */
389     public void setTimeInterval(final Time timeInterval) throws LockedException {
390         if (running) {
391             throw new LockedException();
392         }
393 
394         generator.setTimeInterval(timeInterval);
395     }
396 
397     /**
398      * Gets minimum number of input measurements provided to the
399      * {@link #getDataSource()} required in a static interval to be taken
400      * into account.
401      * Smaller static intervals will be discarded.
402      *
403      * @return a minimum number of input measurements required in a static interval
404      * to be taken into account.
405      */
406     public int getMinStaticSamples() {
407         return generator.getMinStaticSamples();
408     }
409 
410     /**
411      * Sets minimum number of input measurements provided to the
412      * {@link #getDataSource()} required in a static interval to be taken
413      * into account.
414      * Smaller static intervals will be discarded.
415      *
416      * @param minStaticSamples a minimum number of input measurements required in
417      *                         a static interval to be taken into account.
418      * @throws LockedException          if optimizer is already running.
419      * @throws IllegalArgumentException if provided value is less than 2.
420      */
421     public void setMinStaticSamples(final int minStaticSamples) throws LockedException {
422         if (running) {
423             throw new LockedException();
424         }
425 
426         generator.setMinStaticSamples(minStaticSamples);
427     }
428 
429     /**
430      * Gets maximum number of input measurements provided to the
431      * {@link #getDataSource()} allowed in dynamic intervals.
432      *
433      * @return maximum number of input measurements allowed in dynamic intervals.
434      */
435     public int getMaxDynamicSamples() {
436         return generator.getMaxDynamicSamples();
437     }
438 
439     /**
440      * Sets maximum number of input measurements provided to the
441      * {@link #getDataSource()} allowed in dynamic intervals.
442      *
443      * @param maxDynamicSamples maximum number of input measurements allowed in
444      *                          dynamic intervals.
445      * @throws LockedException          if optimizer is already running.
446      * @throws IllegalArgumentException if provided value is less than 2.
447      */
448     public void setMaxDynamicSamples(final int maxDynamicSamples) throws LockedException {
449         if (running) {
450             throw new LockedException();
451         }
452 
453         generator.setMaxDynamicSamples(maxDynamicSamples);
454     }
455 
456     /**
457      * Gets length of number of input measurements provided to the
458      * {@link #getDataSource()} to keep within the window being processed
459      * to determine instantaneous accelerometer noise level.
460      *
461      * @return length of input measurements to keep within the window.
462      */
463     public int getWindowSize() {
464         return generator.getWindowSize();
465     }
466 
467     /**
468      * Sets length of number of input measurements provided to the
469      * {@link #getDataSource()} to keep within the window being processed
470      * to determine instantaneous noise level.
471      * Window size must always be larger than the allowed minimum value, which is 2
472      * and must have an odd value.
473      *
474      * @param windowSize length of number of samples to keep within the window.
475      * @throws LockedException          if optimizer is already running.
476      * @throws IllegalArgumentException if provided value is not valid.
477      */
478     public void setWindowSize(final int windowSize) throws LockedException {
479         if (running) {
480             throw new LockedException();
481         }
482 
483         generator.setWindowSize(windowSize);
484     }
485 
486     /**
487      * Gets number of input measurements provided to the
488      * {@link #getDataSource()} to be processed initially while keeping the sensor
489      * static to find the base noise level when the device is static.
490      *
491      * @return number of samples to be processed initially.
492      */
493     public int getInitialStaticSamples() {
494         return generator.getInitialStaticSamples();
495     }
496 
497     /**
498      * Sets number of input parameters provided to the {@link #getDataSource()}
499      * to be processed initially while keeping the sensor static to
500      * find the base noise level when the device is static.
501      *
502      * @param initialStaticSamples number of samples ot be processed initially.
503      * @throws LockedException          if optimizer is already running.
504      * @throws IllegalArgumentException if provided value is less than
505      *                                  {@link TriadStaticIntervalDetector#MINIMUM_INITIAL_STATIC_SAMPLES}.
506      */
507     public void setInitialStaticSamples(final int initialStaticSamples) throws LockedException {
508         if (running) {
509             throw new LockedException();
510         }
511 
512         generator.setInitialStaticSamples(initialStaticSamples);
513     }
514 
515     /**
516      * Gets factor to determine that a sudden movement has occurred during
517      * initialization if instantaneous noise level exceeds accumulated noise
518      * level by this factor amount.
519      * This factor is unit-less.
520      *
521      * @return factor to determine that a sudden movement has occurred.
522      */
523     public double getInstantaneousNoiseLevelFactor() {
524         return generator.getInstantaneousNoiseLevelFactor();
525     }
526 
527     /**
528      * Sets factor to determine that a sudden movement has occurred during
529      * initialization if instantaneous noise level exceeds accumulated noise
530      * level by this factor amount.
531      * This factor is unit-less.
532      *
533      * @param instantaneousNoiseLevelFactor factor to determine that a sudden
534      *                                      movement has occurred during
535      *                                      initialization.
536      * @throws LockedException          if optimizer is already running.
537      * @throws IllegalArgumentException if provided value is zero or negative.
538      */
539     public void setInstantaneousNoiseLevelFactor(final double instantaneousNoiseLevelFactor) throws LockedException {
540         if (running) {
541             throw new LockedException();
542         }
543 
544         generator.setInstantaneousNoiseLevelFactor(instantaneousNoiseLevelFactor);
545     }
546 
547     /**
548      * Gets the overall absolute threshold to determine whether there has been
549      * excessive motion during the whole initialization phase.
550      * This threshold is expressed in meters per squared second (m/s^2).
551      *
552      * @return overall absolute threshold to determine whether there has been
553      * excessive motion.
554      */
555     public double getBaseNoiseLevelAbsoluteThreshold() {
556         return generator.getBaseNoiseLevelAbsoluteThreshold();
557     }
558 
559     /**
560      * Sets the overall absolute threshold to determine whether there has been
561      * excessive motion during the whole initialization phase.
562      * This threshold is expressed in meters per squared second (m/s^2).
563      *
564      * @param baseNoiseLevelAbsoluteThreshold overall absolute threshold to
565      *                                        determine whether there has been
566      *                                        excessive motion.
567      * @throws LockedException          if optimizer is already running.
568      * @throws IllegalArgumentException if provided value is zero or negative.
569      */
570     public void setBaseNoiseLevelAbsoluteThreshold(final double baseNoiseLevelAbsoluteThreshold)
571             throws LockedException {
572         if (running) {
573             throw new LockedException();
574         }
575 
576         generator.setBaseNoiseLevelAbsoluteThreshold(baseNoiseLevelAbsoluteThreshold);
577     }
578 
579     /**
580      * Gets the overall absolute threshold to determine whether there has been
581      * excessive motion during the whole initialization phase.
582      *
583      * @return overall absolute threshold to determine whether there has been
584      * excessive motion.
585      */
586     public Acceleration getBaseNoiseLevelAbsoluteThresholdAsMeasurement() {
587         return generator.getBaseNoiseLevelAbsoluteThresholdAsMeasurement();
588     }
589 
590     /**
591      * Gets the overall absolute threshold to determine whether there has been
592      * excessive motion during the whole initialization phase.
593      *
594      * @param result instance where the result will be stored.
595      */
596     public void getBaseNoiseLevelAbsoluteThresholdAsMeasurement(final Acceleration result) {
597         generator.getBaseNoiseLevelAbsoluteThresholdAsMeasurement(result);
598     }
599 
600     /**
601      * Sets the overall absolute threshold to determine whether there has been
602      * excessive motion during the whole initialization phase.
603      *
604      * @param baseNoiseLevelAbsoluteThreshold overall absolute threshold to
605      *                                        determine whether there has been
606      *                                        excessive motion.
607      * @throws LockedException          if optimizer is already running.
608      * @throws IllegalArgumentException if provided value is zero or negative.
609      */
610     public void setBaseNoiseLevelAbsoluteThreshold(final Acceleration baseNoiseLevelAbsoluteThreshold)
611             throws LockedException {
612         if (running) {
613             throw new LockedException();
614         }
615 
616         generator.setBaseNoiseLevelAbsoluteThreshold(baseNoiseLevelAbsoluteThreshold);
617     }
618 
619     /**
620      * Gets accelerometer base noise level that has been detected during
621      * initialization of the best solution that has been found expressed in
622      * meters per squared second (m/s^2).
623      * This is equal to the standard deviation of the accelerometer measurements
624      * during the initialization phase.
625      *
626      * @return accelerometer base noise level of the best solution that has been
627      * found.
628      */
629     public double getAccelerometerBaseNoiseLevel() {
630         return baseNoiseLevel;
631     }
632 
633     /**
634      * Gets accelerometer base noise level that has been detected during
635      * initialization of the best solution that has been found.
636      * This is equal to the standard deviation of the accelerometer measurements
637      * during the initialization phase.
638      *
639      * @return accelerometer base noise level of the best solution that has been
640      * found.
641      */
642     public Acceleration getAccelerometerBaseNoiseLevelAsMeasurement() {
643         return createMeasurement(baseNoiseLevel, getDefaultUnit());
644     }
645 
646     /**
647      * Gets accelerometer base noise level that has been detected during
648      * initialization of the best solution that has been found.
649      * This is equal to the standard deviation of the accelerometer measurements
650      * during the initialization phase.
651      *
652      * @param result instance where the result will be stored.
653      */
654     public void getAccelerometerBaseNoiseLevelAsMeasurement(final Acceleration result) {
655         result.setValue(baseNoiseLevel);
656         result.setUnit(getDefaultUnit());
657     }
658 
659     /**
660      * Gets accelerometer base noise level PSD (Power Spectral Density)
661      * expressed in (m^2 * s^-3) of the best solution that has been found.
662      *
663      * @return accelerometer base noise level PSD of the best solution that has
664      * been found.
665      */
666     public double getAccelerometerBaseNoiseLevelPsd() {
667         return baseNoiseLevel * baseNoiseLevel * getTimeInterval();
668     }
669 
670     /**
671      * Gets accelerometer base noise level root PSD (Power Spectral Density)
672      * expressed in (m * s^-1.5) of the best solution that has been found.
673      *
674      * @return accelerometer base noise level root PSD of the best solution that has
675      * been found.
676      */
677     @Override
678     public double getAccelerometerBaseNoiseLevelRootPsd() {
679         return baseNoiseLevel * Math.sqrt(getTimeInterval());
680     }
681 
682     /**
683      * Gets the threshold to determine static/dynamic period changes expressed in
684      * meters per squared second (m/s^2) for the best calibration solution that
685      * has been found.
686      *
687      * @return threshold to determine static/dynamic period changes for the best
688      * solution.
689      */
690     public double getThreshold() {
691         return threshold;
692     }
693 
694     /**
695      * Gets the threshold to determine static/dynamic period changes for the best
696      * calibration solution that has been found.
697      *
698      * @return threshold to determine static/dynamic period changes for the best
699      * solution.
700      */
701     public Acceleration getThresholdAsMeasurement() {
702         return createMeasurement(threshold, getDefaultUnit());
703     }
704 
705     /**
706      * Get the threshold to determine static/dynamic period changes for the best
707      * calibration solution that has been found.
708      *
709      * @param result instance where the result will be stored.
710      */
711     public void getThresholdAsMeasurement(final Acceleration result) {
712         result.setValue(threshold);
713         result.setUnit(getDefaultUnit());
714     }
715 
716     /**
717      * Gets estimated standard deviation norm of accelerometer bias expressed in
718      * meters per squared second (m/s^2).
719      * This can be used as the initial accelerometer bias uncertainty for
720      * {@link INSLooselyCoupledKalmanInitializerConfig} or {@link INSTightlyCoupledKalmanInitializerConfig}.
721      *
722      * @return estimated standard deviation norm of accelerometer bias or null
723      * if not available.
724      */
725     @Override
726     public Double getEstimatedBiasStandardDeviationNorm() {
727         return estimatedCovariance != null
728                 ? Math.sqrt(getEstimatedBiasFxVariance() + getEstimatedBiasFyVariance() + getEstimatedBiasFzVariance())
729                 : null;
730     }
731 
732     /**
733      * Gets estimated x coordinate variance of accelerometer bias expressed in (m^2/s^4).
734      *
735      * @return estimated x coordinate variance of accelerometer bias or null if not available.
736      */
737     public Double getEstimatedBiasFxVariance() {
738         return estimatedCovariance != null ? estimatedCovariance.getElementAt(0, 0) : null;
739     }
740 
741     /**
742      * Gets estimated y coordinate variance of accelerometer bias expressed in (m^2/s^4).
743      *
744      * @return estimated y coordinate variance of accelerometer bias or null if not available.
745      */
746     public Double getEstimatedBiasFyVariance() {
747         return estimatedCovariance != null ? estimatedCovariance.getElementAt(1, 1) : null;
748     }
749 
750     /**
751      * Gets estimated z coordinate variance of accelerometer bias expressed in (m^2/s^4).
752      *
753      * @return estimated z coordinate variance of accelerometer bias or null if not available.
754      */
755     public Double getEstimatedBiasFzVariance() {
756         return estimatedCovariance != null ? estimatedCovariance.getElementAt(2, 2) : null;
757     }
758 
759     /**
760      * Gets the array containing x,y,z components of estimated accelerometer biases
761      * expressed in meters per squared second (m/s^2).
762      *
763      * @return array containing x,y,z components of estimated accelerometer biases.
764      */
765     @Override
766     public double[] getEstimatedBiases() {
767         return estimatedBiases;
768     }
769 
770     /**
771      * Gets estimated accelerometer scale factors and cross-coupling errors.
772      * This is the product of matrix Ta containing cross-coupling errors and Ka
773      * containing scaling factors.
774      * So that:
775      * <pre>
776      *     Ma = [sx    mxy  mxz] = Ta*Ka
777      *          [myx   sy   myz]
778      *          [mzx   mzy  sz ]
779      * </pre>
780      * Where:
781      * <pre>
782      *     Ka = [sx 0   0 ]
783      *          [0  sy  0 ]
784      *          [0  0   sz]
785      * </pre>
786      * and
787      * <pre>
788      *     Ta = [1          -alphaXy    alphaXz ]
789      *          [alphaYx    1           -alphaYz]
790      *          [-alphaZx   alphaZy     1       ]
791      * </pre>
792      * Hence:
793      * <pre>
794      *     Ma = [sx    mxy  mxz] = Ta*Ka =  [sx             -sy * alphaXy   sz * alphaXz ]
795      *          [myx   sy   myz]            [sx * alphaYx   sy              -sz * alphaYz]
796      *          [mzx   mzy  sz ]            [-sx * alphaZx  sy * alphaZy    sz           ]
797      * </pre>
798      * This instance allows any 3x3 matrix. However, typically alphaYx, alphaZx and alphaZy
799      * are considered to be zero if the accelerometer z-axis is assumed to be the same
800      * as the body z-axis. When this is assumed, myx = mzx = mzy = 0 and the Ma matrix
801      * becomes upper diagonal:
802      * <pre>
803      *     Ma = [sx    mxy  mxz]
804      *          [0     sy   myz]
805      *          [0     0    sz ]
806      * </pre>
807      * Values of this matrix are unit-less.
808      *
809      * @return estimated accelerometer scale factors and cross-coupling errors, or null
810      * if not available.
811      */
812     @Override
813     public Matrix getEstimatedMa() {
814         return estimatedMa;
815     }
816 
817     /**
818      * Evaluates calibration Mean Square Error (MSE) for the provided threshold factor.
819      *
820      * @param thresholdFactor threshold factor to be used for interval detection
821      *                        and measurement generation to be used for
822      *                        calibration.
823      * @return calibration MSE.
824      * @throws LockedException                                   if the generator is busy.
825      * @throws CalibrationException                              if calibration fails.
826      * @throws NotReadyException                                 if the calibrator is not ready.
827      * @throws IntervalDetectorThresholdFactorOptimizerException interval detection failed.
828      */
829     protected double evaluateForThresholdFactor(final double thresholdFactor) throws LockedException,
830             CalibrationException, NotReadyException, IntervalDetectorThresholdFactorOptimizerException {
831         if (measurements == null) {
832             measurements = new ArrayList<>();
833         } else {
834             measurements.clear();
835         }
836 
837         generator.reset();
838         generator.setThresholdFactor(thresholdFactor);
839 
840         var count = dataSource.count();
841         var failed = false;
842         for (var i = 0; i < count; i++) {
843             final var bodyKinematics = dataSource.getAt(i);
844             if (!generator.process(bodyKinematics)) {
845                 failed = true;
846                 break;
847             }
848         }
849 
850         if (failed || generator.getStatus() == TriadStaticIntervalDetector.Status.FAILED) {
851             // interval detection failed
852             return Double.MAX_VALUE;
853         }
854 
855         // check that enough measurements have been obtained
856         if (measurements.size() < calibrator.getMinimumRequiredMeasurements()) {
857             return Double.MAX_VALUE;
858         }
859 
860         // set calibrator measurements
861         switch (calibrator.getMeasurementType()) {
862             case STANDARD_DEVIATION_BODY_KINEMATICS:
863                 if (calibrator.isOrderedMeasurementsRequired()) {
864                     final var calibrator = (OrderedStandardDeviationBodyKinematicsAccelerometerCalibrator)
865                             this.calibrator;
866 
867                     calibrator.setMeasurements(measurements);
868 
869                 } else {
870                     final var calibrator = (UnorderedStandardDeviationBodyKinematicsAccelerometerCalibrator)
871                             this.calibrator;
872 
873                     calibrator.setMeasurements(measurements);
874                 }
875 
876                 if (calibrator.isQualityScoresRequired()) {
877                     final var calibrator = (QualityScoredAccelerometerCalibrator) this.calibrator;
878 
879                     final var size = measurements.size();
880                     final var qualityScores = new double[size];
881                     for (var i = 0; i < size; i++) {
882                         qualityScores[i] = qualityScoreMapper.map(measurements.get(i));
883                     }
884                     calibrator.setQualityScores(qualityScores);
885                 }
886                 break;
887             case FRAME_BODY_KINEMATICS, STANDARD_DEVIATION_FRAME_BODY_KINEMATICS:
888                 // Throw exception. Cannot use frames
889             default:
890                 throw new IntervalDetectorThresholdFactorOptimizerException();
891         }
892 
893         calibrator.calibrate();
894 
895         final var mse = calibrator.getEstimatedMse();
896         if (mse < minMse) {
897             keepBestResult(mse, thresholdFactor);
898         }
899         return mse;
900     }
901 
902     /**
903      * Initializes accelerometer measurement generator to convert body kinematics
904      * measurements after interval detection into measurements used for
905      * accelerometer calibration.
906      */
907     private void initialize() {
908         final var generatorListener = new AccelerometerMeasurementsGeneratorListener() {
909             @Override
910             public void onInitializationStarted(final AccelerometerMeasurementsGenerator generator) {
911                 // not needed
912             }
913 
914             @Override
915             public void onInitializationCompleted(
916                     final AccelerometerMeasurementsGenerator generator,
917                     final double baseNoiseLevel) {
918                 // not needed
919             }
920 
921             @Override
922             public void onError(
923                     final AccelerometerMeasurementsGenerator generator,
924                     final TriadStaticIntervalDetector.ErrorReason reason) {
925                 // not needed
926             }
927 
928             @Override
929             public void onStaticIntervalDetected(final AccelerometerMeasurementsGenerator generator) {
930                 // not needed
931             }
932 
933             @Override
934             public void onDynamicIntervalDetected(final AccelerometerMeasurementsGenerator generator) {
935                 // not needed
936             }
937 
938             @Override
939             public void onStaticIntervalSkipped(final AccelerometerMeasurementsGenerator generator) {
940                 // not needed
941             }
942 
943             @Override
944             public void onDynamicIntervalSkipped(final AccelerometerMeasurementsGenerator generator) {
945                 // not needed
946             }
947 
948             @Override
949             public void onGeneratedMeasurement(
950                     final AccelerometerMeasurementsGenerator generator,
951                     final StandardDeviationBodyKinematics measurement) {
952                 measurements.add(measurement);
953             }
954 
955             @Override
956             public void onReset(final AccelerometerMeasurementsGenerator generator) {
957                 // not needed
958             }
959         };
960 
961         generator = new AccelerometerMeasurementsGenerator(generatorListener);
962     }
963 
964     /**
965      * Keeps the best calibration solution found so far.
966      *
967      * @param mse             Estimated Mean Square Error during calibration.
968      * @param thresholdFactor threshold factor to be kept.
969      */
970     private void keepBestResult(final double mse, final double thresholdFactor) {
971         minMse = mse;
972         optimalThresholdFactor = thresholdFactor;
973 
974         baseNoiseLevel = generator.getAccelerometerBaseNoiseLevel();
975         threshold = generator.getThreshold();
976 
977         if (estimatedCovariance == null) {
978             estimatedCovariance = new Matrix(calibrator.getEstimatedCovariance());
979         } else {
980             estimatedCovariance.copyFrom(calibrator.getEstimatedCovariance());
981         }
982         if (estimatedMa == null) {
983             estimatedMa = new Matrix(calibrator.getEstimatedMa());
984         } else {
985             estimatedMa.copyFrom(calibrator.getEstimatedMa());
986         }
987         if (calibrator instanceof UnknownBiasAccelerometerCalibrator unknownBiasAccelerometerCalibrator) {
988             estimatedBiases = unknownBiasAccelerometerCalibrator.getEstimatedBiases();
989         } else if (calibrator instanceof KnownBiasAccelerometerCalibrator knownBiasAccelerometerCalibrator) {
990             estimatedBiases = knownBiasAccelerometerCalibrator.getBias();
991         }
992     }
993 
994     /**
995      * Creates an acceleration instance using the provided value and unit.
996      *
997      * @param value value of measurement.
998      * @param unit  unit of value.
999      * @return created acceleration.
1000      */
1001     private Acceleration createMeasurement(final double value, final AccelerationUnit unit) {
1002         return new Acceleration(value, unit);
1003     }
1004 
1005     /**
1006      * Gets the default unit for acceleration, which is meters per
1007      * squared second (m/s^2).
1008      *
1009      * @return default unit for acceleration.
1010      */
1011     private AccelerationUnit getDefaultUnit() {
1012         return AccelerationUnit.METERS_PER_SQUARED_SECOND;
1013     }
1014 }