View Javadoc
1   /*
2    * Copyright (C) 2020 Alberto Irurueta Carro (alberto@irurueta.com)
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *         http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package com.irurueta.navigation.inertial.calibration.accelerometer;
17  
18  import com.irurueta.navigation.LockedException;
19  import com.irurueta.navigation.NotReadyException;
20  import com.irurueta.navigation.inertial.calibration.CalibrationException;
21  import com.irurueta.navigation.inertial.calibration.StandardDeviationFrameBodyKinematics;
22  import com.irurueta.numerical.robust.PROSACRobustEstimator;
23  import com.irurueta.numerical.robust.PROSACRobustEstimatorListener;
24  import com.irurueta.numerical.robust.RobustEstimator;
25  import com.irurueta.numerical.robust.RobustEstimatorException;
26  import com.irurueta.numerical.robust.RobustEstimatorMethod;
27  
28  import java.util.List;
29  
30  /**
31   * Robustly estimates accelerometer biases, cross couplings and scaling factors
32   * using a PROSAC algorithm to discard outliers.
33   * <p>
34   * To use this calibrator at least 4 measurements at different known frames must
35   * be provided. In other words, accelerometer samples must be obtained at 4
36   * different positions, orientations and velocities (although typically velocities are
37   * always zero).
38   * <p>
39   * Measured specific force is assumed to follow the model shown below:
40   * <pre>
41   *     fmeas = ba + (I + Ma) * ftrue + w
42   * </pre>
43   * Where:
44   * - fmeas is the measured specific force. This is a 3x1 vector.
45   * - ba is accelerometer bias. Ideally, on a perfect accelerometer, this should be a
46   * 3x1 zero vector.
47   * - I is the 3x3 identity matrix.
48   * - Ma is the 3x3 matrix containing cross-couplings and scaling factors. Ideally, on
49   * a perfect accelerometer, this should be a 3x3 zero matrix.
50   * - ftrue is ground-truth specific force.
51   * - w is measurement noise.
52   */
53  public class PROSACRobustKnownFrameAccelerometerCalibrator extends RobustKnownFrameAccelerometerCalibrator {
54  
55      /**
56       * Constant defining default threshold to determine whether samples are inliers or not.
57       */
58      public static final double DEFAULT_THRESHOLD = 1e-2;
59  
60      /**
61       * Minimum value that can be set as threshold.
62       * Threshold must be strictly greater than 0.0.
63       */
64      public static final double MIN_THRESHOLD = 0.0;
65  
66      /**
67       * Indicates that by default inliers will only be computed but not kept.
68       */
69      public static final boolean DEFAULT_COMPUTE_AND_KEEP_INLIERS = false;
70  
71      /**
72       * Indicates that by default residuals will only be computed but not kept.
73       */
74      public static final boolean DEFAULT_COMPUTE_AND_KEEP_RESIDUALS = false;
75  
76      /**
77       * Threshold to determine whether samples are inliers or not when testing possible solutions.
78       * The threshold refers to the amount of error on distance between estimated position and
79       * distances provided for each sample.
80       */
81      private double threshold = DEFAULT_THRESHOLD;
82  
83      /**
84       * Indicates whether inliers must be computed and kept.
85       */
86      private boolean computeAndKeepInliers = DEFAULT_COMPUTE_AND_KEEP_INLIERS;
87  
88      /**
89       * Indicates whether residuals must be computed and kept.
90       */
91      private boolean computeAndKeepResiduals = DEFAULT_COMPUTE_AND_KEEP_RESIDUALS;
92  
93      /**
94       * Quality scores corresponding to each provided sample.
95       * The larger the score value the better the quality of the sample.
96       */
97      private double[] qualityScores;
98  
99      /**
100      * Constructor.
101      */
102     public PROSACRobustKnownFrameAccelerometerCalibrator() {
103     }
104 
105     /**
106      * Constructor.
107      *
108      * @param listener listener to be notified of events such as when estimation
109      *                 starts, ends or its progress significantly changes.
110      */
111     public PROSACRobustKnownFrameAccelerometerCalibrator(
112             final RobustKnownFrameAccelerometerCalibratorListener listener) {
113         super(listener);
114     }
115 
116     /**
117      * Constructor.
118      *
119      * @param measurements list of body kinematics measurements with standard
120      *                     deviations taken at different frames (positions, orientations
121      *                     and velocities).
122      */
123     public PROSACRobustKnownFrameAccelerometerCalibrator(
124             final List<StandardDeviationFrameBodyKinematics> measurements) {
125         super(measurements);
126     }
127 
128     /**
129      * Constructor.
130      *
131      * @param measurements list of body kinematics measurements with standard
132      *                     deviations taken at different frames (positions, orientations
133      *                     and velocities).
134      * @param listener     listener to handle events raised by this calibrator.
135      */
136     public PROSACRobustKnownFrameAccelerometerCalibrator(
137             final List<StandardDeviationFrameBodyKinematics> measurements,
138             final RobustKnownFrameAccelerometerCalibratorListener listener) {
139         super(measurements, listener);
140     }
141 
142     /**
143      * Constructor.
144      *
145      * @param commonAxisUsed indicates whether z-axis is assumed to be common for
146      *                       accelerometer and gyroscope.
147      */
148     public PROSACRobustKnownFrameAccelerometerCalibrator(final boolean commonAxisUsed) {
149         super(commonAxisUsed);
150     }
151 
152     /**
153      * Constructor.
154      *
155      * @param commonAxisUsed indicates whether z-axis is assumed to be common for
156      *                       accelerometer and gyroscope.
157      * @param listener       listener to handle events raised by this calibrator.
158      */
159     public PROSACRobustKnownFrameAccelerometerCalibrator(
160             final boolean commonAxisUsed, final RobustKnownFrameAccelerometerCalibratorListener listener) {
161         super(commonAxisUsed, listener);
162     }
163 
164     /**
165      * Constructor.
166      *
167      * @param measurements   list of body kinematics measurements with standard
168      *                       deviations taken at different frames (positions, orientations
169      *                       and velocities).
170      * @param commonAxisUsed indicates whether z-axis is assumed to be common for
171      *                       accelerometer and gyroscope.
172      */
173     public PROSACRobustKnownFrameAccelerometerCalibrator(
174             final List<StandardDeviationFrameBodyKinematics> measurements, final boolean commonAxisUsed) {
175         super(measurements, commonAxisUsed);
176     }
177 
178     /**
179      * Constructor.
180      *
181      * @param measurements   list of body kinematics measurements with standard
182      *                       deviations taken at different frames (positions, orientations
183      *                       and velocities).
184      * @param commonAxisUsed indicates whether z-axis is assumed to be common for
185      *                       accelerometer and gyroscope.
186      * @param listener       listener to handle events raised by this calibrator.
187      */
188     public PROSACRobustKnownFrameAccelerometerCalibrator(
189             final List<StandardDeviationFrameBodyKinematics> measurements, final boolean commonAxisUsed,
190             final RobustKnownFrameAccelerometerCalibratorListener listener) {
191         super(measurements, commonAxisUsed, listener);
192     }
193 
194     /**
195      * Constructor.
196      *
197      * @param qualityScores quality scores corresponding to each provided
198      *                      measurement. The larger the score value the better
199      *                      the quality of the sample.
200      * @throws IllegalArgumentException if provided quality scores length
201      *                                  is smaller than 4 samples.
202      */
203     public PROSACRobustKnownFrameAccelerometerCalibrator(final double[] qualityScores) {
204         internalSetQualityScores(qualityScores);
205     }
206 
207     /**
208      * Constructor.
209      *
210      * @param qualityScores quality scores corresponding to each provided
211      *                      measurement. The larger the score value the better
212      *                      the quality of the sample.
213      * @param listener      listener to be notified of events such as when estimation
214      *                      starts, ends or its progress significantly changes.
215      * @throws IllegalArgumentException if provided quality scores length
216      *                                  is smaller than 4 samples.
217      */
218     public PROSACRobustKnownFrameAccelerometerCalibrator(
219             final double[] qualityScores, final RobustKnownFrameAccelerometerCalibratorListener listener) {
220         super(listener);
221         internalSetQualityScores(qualityScores);
222     }
223 
224     /**
225      * Constructor.
226      *
227      * @param qualityScores quality scores corresponding to each provided
228      *                      measurement. The larger the score value the better
229      *                      the quality of the sample.
230      * @param measurements  list of body kinematics measurements with standard
231      *                      deviations taken at different frames (positions, orientations
232      *                      and velocities).
233      * @throws IllegalArgumentException if provided quality scores length
234      *                                  is smaller than 4 samples.
235      */
236     public PROSACRobustKnownFrameAccelerometerCalibrator(
237             final double[] qualityScores, final List<StandardDeviationFrameBodyKinematics> measurements) {
238         super(measurements);
239         internalSetQualityScores(qualityScores);
240     }
241 
242     /**
243      * Constructor.
244      *
245      * @param qualityScores quality scores corresponding to each provided
246      *                      measurement. The larger the score value the better
247      *                      the quality of the sample.
248      * @param measurements  list of body kinematics measurements with standard
249      *                      deviations taken at different frames (positions, orientations
250      *                      and velocities).
251      * @param listener      listener to handle events raised by this calibrator.
252      * @throws IllegalArgumentException if provided quality scores length
253      *                                  is smaller than 4 samples.
254      */
255     public PROSACRobustKnownFrameAccelerometerCalibrator(
256             final double[] qualityScores, final List<StandardDeviationFrameBodyKinematics> measurements,
257             final RobustKnownFrameAccelerometerCalibratorListener listener) {
258         super(measurements, listener);
259         internalSetQualityScores(qualityScores);
260     }
261 
262     /**
263      * Constructor.
264      *
265      * @param qualityScores  quality scores corresponding to each provided
266      *                       measurement. The larger the score value the better
267      *                       the quality of the sample.
268      * @param commonAxisUsed indicates whether z-axis is assumed to be common for
269      *                       accelerometer and gyroscope.
270      * @throws IllegalArgumentException if provided quality scores length
271      *                                  is smaller than 4 samples.
272      */
273     public PROSACRobustKnownFrameAccelerometerCalibrator(final double[] qualityScores, final boolean commonAxisUsed) {
274         super(commonAxisUsed);
275         internalSetQualityScores(qualityScores);
276     }
277 
278     /**
279      * Constructor.
280      *
281      * @param qualityScores  quality scores corresponding to each provided
282      *                       measurement. The larger the score value the better
283      *                       the quality of the sample.
284      * @param commonAxisUsed indicates whether z-axis is assumed to be common for
285      *                       accelerometer and gyroscope.
286      * @param listener       listener to handle events raised by this calibrator.
287      * @throws IllegalArgumentException if provided quality scores length
288      *                                  is smaller than 4 samples.
289      */
290     public PROSACRobustKnownFrameAccelerometerCalibrator(
291             final double[] qualityScores, final boolean commonAxisUsed,
292             final RobustKnownFrameAccelerometerCalibratorListener listener) {
293         super(commonAxisUsed, listener);
294         internalSetQualityScores(qualityScores);
295     }
296 
297     /**
298      * Constructor.
299      *
300      * @param qualityScores  quality scores corresponding to each provided
301      *                       measurement. The larger the score value the better
302      *                       the quality of the sample.
303      * @param measurements   list of body kinematics measurements with standard
304      *                       deviations taken at different frames (positions, orientations
305      *                       and velocities).
306      * @param commonAxisUsed indicates whether z-axis is assumed to be common for
307      *                       accelerometer and gyroscope.
308      * @throws IllegalArgumentException if provided quality scores length
309      *                                  is smaller than 4 samples.
310      */
311     public PROSACRobustKnownFrameAccelerometerCalibrator(
312             final double[] qualityScores, final List<StandardDeviationFrameBodyKinematics> measurements,
313             final boolean commonAxisUsed) {
314         super(measurements, commonAxisUsed);
315         internalSetQualityScores(qualityScores);
316     }
317 
318     /**
319      * Constructor.
320      *
321      * @param qualityScores  quality scores corresponding to each provided
322      *                       measurement. The larger the score value the better
323      *                       the quality of the sample.
324      * @param measurements   list of body kinematics measurements with standard
325      *                       deviations taken at different frames (positions, orientations
326      *                       and velocities).
327      * @param commonAxisUsed indicates whether z-axis is assumed to be common for
328      *                       accelerometer and gyroscope.
329      * @param listener       listener to handle events raised by this calibrator.
330      * @throws IllegalArgumentException if provided quality scores length
331      *                                  is smaller than 4 samples.
332      */
333     public PROSACRobustKnownFrameAccelerometerCalibrator(
334             final double[] qualityScores, final List<StandardDeviationFrameBodyKinematics> measurements,
335             final boolean commonAxisUsed, final RobustKnownFrameAccelerometerCalibratorListener listener) {
336         super(measurements, commonAxisUsed, listener);
337         internalSetQualityScores(qualityScores);
338     }
339 
340     /**
341      * Gets threshold to determine whether samples are inliers or not when testing possible solutions.
342      * The threshold refers to the amount of error on norm between measured specific forces and the
343      * ones generated with estimated calibration parameters provided for each sample.
344      *
345      * @return threshold to determine whether samples are inliers or not.
346      */
347     public double getThreshold() {
348         return threshold;
349     }
350 
351     /**
352      * Sets threshold to determine whether samples are inliers or not when testing possible solutions.
353      * The threshold refers to the amount of error on norm between measured specific forces and the
354      * ones generated with estimated calibration parameters provided for each sample.
355      *
356      * @param threshold threshold to determine whether samples are inliers or not.
357      * @throws IllegalArgumentException if provided value is equal or less than zero.
358      * @throws LockedException          if calibrator is currently running.
359      */
360     public void setThreshold(final double threshold) throws LockedException {
361         if (running) {
362             throw new LockedException();
363         }
364         if (threshold <= MIN_THRESHOLD) {
365             throw new IllegalArgumentException();
366         }
367         this.threshold = threshold;
368     }
369 
370     /**
371      * Returns quality scores corresponding to each provided sample.
372      * The larger the score value the better the quality of the sample.
373      *
374      * @return quality scores corresponding to each sample.
375      */
376     @Override
377     public double[] getQualityScores() {
378         return qualityScores;
379     }
380 
381     /**
382      * Sets quality scores corresponding to each provided sample.
383      * The larger the score value the better the quality of the sample.
384      *
385      * @param qualityScores quality scores corresponding to each sample.
386      * @throws IllegalArgumentException if provided quality scores length
387      *                                  is smaller than minimum required samples.
388      * @throws LockedException          if calibrator is currently running.
389      */
390     @Override
391     public void setQualityScores(final double[] qualityScores) throws LockedException {
392         if (running) {
393             throw new LockedException();
394         }
395         internalSetQualityScores(qualityScores);
396     }
397 
398     /**
399      * Indicates whether calibrator is ready to find a solution.
400      *
401      * @return true if calibrator is ready, false otherwise.
402      */
403     @Override
404     public boolean isReady() {
405         return super.isReady() && qualityScores != null && qualityScores.length == measurements.size();
406     }
407 
408     /**
409      * Indicates whether inliers must be computed and kept.
410      *
411      * @return true if inliers must be computed and kept, false if inliers
412      * only need to be computed but not kept.
413      */
414     public boolean isComputeAndKeepInliersEnabled() {
415         return computeAndKeepInliers;
416     }
417 
418     /**
419      * Specifies whether inliers must be computed and kept.
420      *
421      * @param computeAndKeepInliers true if inliers must be computed and kept,
422      *                              false if inliers only need to be computed but not kept.
423      * @throws LockedException if calibrator is currently running.
424      */
425     public void setComputeAndKeepInliersEnabled(final boolean computeAndKeepInliers) throws LockedException {
426         if (running) {
427             throw new LockedException();
428         }
429         this.computeAndKeepInliers = computeAndKeepInliers;
430     }
431 
432     /**
433      * Indicates whether residuals must be computed and kept.
434      *
435      * @return true if residuals must be computed and kept, false if residuals
436      * only need to be computed but not kept.
437      */
438     public boolean isComputeAndKeepResiduals() {
439         return computeAndKeepResiduals;
440     }
441 
442     /**
443      * Specifies whether residuals must be computed and kept.
444      *
445      * @param computeAndKeepResiduals true if residuals must be computed and kept,
446      *                                false if residuals only need to be computed but not kept.
447      * @throws LockedException if calibrator is currently running.
448      */
449     public void setComputeAndKeepResidualsEnabled(final boolean computeAndKeepResiduals) throws LockedException {
450         if (running) {
451             throw new LockedException();
452         }
453         this.computeAndKeepResiduals = computeAndKeepResiduals;
454     }
455 
456     /**
457      * Estimates accelerometer calibration parameters containing bias, scale factors
458      * and cross-coupling errors.
459      *
460      * @throws LockedException      if calibrator is currently running.
461      * @throws NotReadyException    if calibrator is not ready.
462      * @throws CalibrationException if estimation fails for numerical reasons.
463      */
464     @SuppressWarnings("DuplicatedCode")
465     @Override
466     public void calibrate() throws LockedException, NotReadyException, CalibrationException {
467         if (running) {
468             throw new LockedException();
469         }
470         if (!isReady()) {
471             throw new NotReadyException();
472         }
473 
474         final var innerEstimator = new PROSACRobustEstimator<>(new PROSACRobustEstimatorListener<PreliminaryResult>() {
475             @Override
476             public double[] getQualityScores() {
477                 return qualityScores;
478             }
479 
480             @Override
481             public double getThreshold() {
482                 return threshold;
483             }
484 
485             @Override
486             public int getTotalSamples() {
487                 return measurements.size();
488             }
489 
490             @Override
491             public int getSubsetSize() {
492                 return preliminarySubsetSize;
493             }
494 
495             @Override
496             public void estimatePreliminarSolutions(
497                     final int[] samplesIndices, final List<PreliminaryResult> solutions) {
498                 computePreliminarySolutions(samplesIndices, solutions);
499             }
500 
501             @Override
502             public double computeResidual(final PreliminaryResult currentEstimation, final int i) {
503                 return computeError(measurements.get(i), currentEstimation);
504             }
505 
506             @Override
507             public boolean isReady() {
508                 return PROSACRobustKnownFrameAccelerometerCalibrator.this.isReady();
509             }
510 
511             @Override
512             public void onEstimateStart(final RobustEstimator<PreliminaryResult> estimator) {
513                 // no action needed
514             }
515 
516             @Override
517             public void onEstimateEnd(final RobustEstimator<PreliminaryResult> estimator) {
518                 // no action needed
519             }
520 
521             @Override
522             public void onEstimateNextIteration(
523                     final RobustEstimator<PreliminaryResult> estimator, final int iteration) {
524                 if (listener != null) {
525                     listener.onCalibrateNextIteration(
526                             PROSACRobustKnownFrameAccelerometerCalibrator.this, iteration);
527                 }
528             }
529 
530             @Override
531             public void onEstimateProgressChange(
532                     final RobustEstimator<PreliminaryResult> estimator, final float progress) {
533                 if (listener != null) {
534                     listener.onCalibrateProgressChange(
535                             PROSACRobustKnownFrameAccelerometerCalibrator.this, progress);
536                 }
537             }
538         });
539 
540         try {
541             running = true;
542 
543             if (listener != null) {
544                 listener.onCalibrateStart(this);
545             }
546 
547             inliersData = null;
548             innerEstimator.setComputeAndKeepInliersEnabled(computeAndKeepInliers || refineResult);
549             innerEstimator.setComputeAndKeepResidualsEnabled(computeAndKeepResiduals || refineResult);
550             innerEstimator.setConfidence(confidence);
551             innerEstimator.setMaxIterations(maxIterations);
552             innerEstimator.setProgressDelta(progressDelta);
553             final var preliminaryResult = innerEstimator.estimate();
554             inliersData = innerEstimator.getInliersData();
555 
556             attemptRefine(preliminaryResult);
557 
558             if (listener != null) {
559                 listener.onCalibrateEnd(this);
560             }
561 
562         } catch (final com.irurueta.numerical.LockedException e) {
563             throw new LockedException(e);
564         } catch (final com.irurueta.numerical.NotReadyException e) {
565             throw new NotReadyException(e);
566         } catch (final RobustEstimatorException e) {
567             throw new CalibrationException(e);
568         } finally {
569             running = false;
570         }
571     }
572 
573     /**
574      * Returns method being used for robust estimation.
575      *
576      * @return method being used for robust estimation.
577      */
578     @Override
579     public RobustEstimatorMethod getMethod() {
580         return RobustEstimatorMethod.PROSAC;
581     }
582 
583     /**
584      * Indicates whether this calibrator requires quality scores for each
585      * measurement or not.
586      *
587      * @return true if quality scores are required, false otherwise.
588      */
589     @Override
590     public boolean isQualityScoresRequired() {
591         return true;
592     }
593 
594     /**
595      * Sets quality scores corresponding to each provided sample.
596      * This method is used internally and does not check whether instance is
597      * locked or not.
598      *
599      * @param qualityScores quality scores to be set.
600      * @throws IllegalArgumentException if provided quality scores length
601      *                                  is smaller than 4 samples.
602      */
603     private void internalSetQualityScores(final double[] qualityScores) {
604         if (qualityScores == null || qualityScores.length < MINIMUM_MEASUREMENTS) {
605             throw new IllegalArgumentException();
606         }
607 
608         this.qualityScores = qualityScores;
609     }
610 }