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 }