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.gyroscope;
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.RANSACRobustEstimator;
23  import com.irurueta.numerical.robust.RANSACRobustEstimatorListener;
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 gyroscope biases, cross couplings and scaling factors
32   * along with G-dependent cross biases introduced on the gyroscope by the
33   * specific forces sensed by the accelerometer using a RANSAC algorithm to discard
34   * outliers.
35   * <p>
36   * To use this calibrator at least 7 measurements at different known frames must
37   * be provided. In other words, accelerometer and gyroscope (i.e. body kinematics)
38   * samples must be obtained at 7 different positions, orientations and velocities
39   * (although typically velocities are always zero).
40   * <p>
41   * Measured gyroscope angular rates is assumed to follow the model shown below:
42   * <pre>
43   *     Ωmeas = bg + (I + Mg) * Ωtrue + Gg * ftrue + w
44   * </pre>
45   * Where:
46   * - Ωmeas is the measured gyroscope angular rates. This is a 3x1 vector.
47   * - bg is the gyroscope bias. Ideally, on a perfect gyroscope, this should be a
48   * 3x1 zero vector.
49   * - I is the 3x3 identity matrix.
50   * - Mg is the 3x3 matrix containing cross-couplings and scaling factors. Ideally, on
51   * a perfect gyroscope, this should be a 3x3 zero matrix.
52   * - Ωtrue is ground-truth gyroscope angular rates.
53   * - Gg is the G-dependent cross biases introduced by the specific forces sensed
54   * by the accelerometer. Ideally, on a perfect gyroscope, this should be a 3x3
55   * zero matrix.
56   * - ftrue is ground-truth specific force. This is a 3x1 vector.
57   * - w is measurement noise. This is a 3x1 vector.
58   */
59  public class RANSACRobustKnownFrameGyroscopeCalibrator extends RobustKnownFrameGyroscopeCalibrator {
60  
61      /**
62       * Constant defining default threshold to determine whether samples are inliers or not.
63       */
64      public static final double DEFAULT_THRESHOLD = 5e-4;
65  
66      /**
67       * Minimum value that can be set as threshold.
68       * Threshold must be strictly greater than 0.0.
69       */
70      public static final double MIN_THRESHOLD = 0.0;
71  
72      /**
73       * Indicates that by default inliers will only be computed but not kept.
74       */
75      public static final boolean DEFAULT_COMPUTE_AND_KEEP_INLIERS = false;
76  
77      /**
78       * Indicates that by default residuals will only be computed but not kept.
79       */
80      public static final boolean DEFAULT_COMPUTE_AND_KEEP_RESIDUALS = false;
81  
82      /**
83       * Threshold to determine whether samples are inliers or not when testing possible solutions.
84       * The threshold refers to the amount of error on distance between estimated position and
85       * distances provided for each sample.
86       */
87      private double threshold = DEFAULT_THRESHOLD;
88  
89      /**
90       * Indicates whether inliers must be computed and kept.
91       */
92      private boolean computeAndKeepInliers = DEFAULT_COMPUTE_AND_KEEP_INLIERS;
93  
94      /**
95       * Indicates whether residuals must be computed and kept.
96       */
97      private boolean computeAndKeepResiduals = DEFAULT_COMPUTE_AND_KEEP_RESIDUALS;
98  
99      /**
100      * Constructor.
101      */
102     public RANSACRobustKnownFrameGyroscopeCalibrator() {
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 RANSACRobustKnownFrameGyroscopeCalibrator(final RobustKnownFrameGyroscopeCalibratorListener listener) {
112         super(listener);
113     }
114 
115     /**
116      * Constructor.
117      *
118      * @param measurements list of body kinematics measurements with standard
119      *                     deviations taken at different frames (positions, orientations
120      *                     and velocities).
121      */
122     public RANSACRobustKnownFrameGyroscopeCalibrator(final List<StandardDeviationFrameBodyKinematics> measurements) {
123         super(measurements);
124     }
125 
126     /**
127      * Constructor.
128      *
129      * @param measurements list of body kinematics measurements with standard
130      *                     deviations taken at different frames (positions, orientations
131      *                     and velocities).
132      * @param listener     listener to be notified of events such as when estimation
133      *                     starts, ends or its progress significantly changes.
134      */
135     public RANSACRobustKnownFrameGyroscopeCalibrator(
136             final List<StandardDeviationFrameBodyKinematics> measurements,
137             final RobustKnownFrameGyroscopeCalibratorListener listener) {
138         super(measurements, listener);
139     }
140 
141     /**
142      * Constructor.
143      *
144      * @param commonAxisUsed indicates whether z-axis is assumed to be common for
145      *                       accelerometer and gyroscope.
146      */
147     public RANSACRobustKnownFrameGyroscopeCalibrator(final boolean commonAxisUsed) {
148         super(commonAxisUsed);
149     }
150 
151     /**
152      * Constructor.
153      *
154      * @param commonAxisUsed indicates whether z-axis is assumed to be common for
155      *                       accelerometer and gyroscope.
156      * @param listener       listener to handle events raised by this calibrator.
157      */
158     public RANSACRobustKnownFrameGyroscopeCalibrator(
159             final boolean commonAxisUsed, final RobustKnownFrameGyroscopeCalibratorListener listener) {
160         super(commonAxisUsed, listener);
161     }
162 
163     /**
164      * Constructor.
165      *
166      * @param measurements   list of body kinematics measurements with standard
167      *                       deviations taken at different frames (positions, orientations
168      *                       and velocities).
169      * @param commonAxisUsed indicates whether z-axis is assumed to be common for
170      *                       accelerometer and gyroscope.
171      */
172     public RANSACRobustKnownFrameGyroscopeCalibrator(
173             final List<StandardDeviationFrameBodyKinematics> measurements, final boolean commonAxisUsed) {
174         super(measurements, commonAxisUsed);
175     }
176 
177     /**
178      * Constructor.
179      *
180      * @param measurements   list of body kinematics measurements with standard
181      *                       deviations taken at different frames (positions, orientations
182      *                       and velocities).
183      * @param commonAxisUsed indicates whether z-axis is assumed to be common for
184      *                       accelerometer and gyroscope.
185      * @param listener       listener to handle events raised by this calibrator.
186      */
187     public RANSACRobustKnownFrameGyroscopeCalibrator(
188             final List<StandardDeviationFrameBodyKinematics> measurements, final boolean commonAxisUsed,
189             final RobustKnownFrameGyroscopeCalibratorListener listener) {
190         super(measurements, commonAxisUsed, listener);
191     }
192 
193     /**
194      * Gets threshold to determine whether samples are inliers or not when testing possible solutions.
195      * The threshold refers to the amount of error on norm between measured angular rates and the
196      * ones generated with estimated calibration parameters provided for each sample.
197      *
198      * @return threshold to determine whether samples are inliers or not.
199      */
200     public double getThreshold() {
201         return threshold;
202     }
203 
204     /**
205      * Sets threshold to determine whether samples are inliers or not when testing possible solutions.
206      * The threshold refers to the amount of error on norm between measured angular rates and the
207      * ones generated with estimated calibration parameters provided for each sample.
208      *
209      * @param threshold threshold to determine whether samples are inliers or not.
210      * @throws IllegalArgumentException if provided value is equal or less than zero.
211      * @throws LockedException          if calibrator is currently running.
212      */
213     public void setThreshold(final double threshold) throws LockedException {
214         if (running) {
215             throw new LockedException();
216         }
217         if (threshold <= MIN_THRESHOLD) {
218             throw new IllegalArgumentException();
219         }
220         this.threshold = threshold;
221     }
222 
223     /**
224      * Indicates whether inliers must be computed and kept.
225      *
226      * @return true if inliers must be computed and kept, false if inliers
227      * only need to be computed but not kept.
228      */
229     public boolean isComputeAndKeepInliersEnabled() {
230         return computeAndKeepInliers;
231     }
232 
233     /**
234      * Specifies whether inliers must be computed and kept.
235      *
236      * @param computeAndKeepInliers true if inliers must be computed and kept,
237      *                              false if inliers only need to be computed but not kept.
238      * @throws LockedException if calibrator is currently running.
239      */
240     public void setComputeAndKeepInliersEnabled(final boolean computeAndKeepInliers) throws LockedException {
241         if (running) {
242             throw new LockedException();
243         }
244         this.computeAndKeepInliers = computeAndKeepInliers;
245     }
246 
247     /**
248      * Indicates whether residuals must be computed and kept.
249      *
250      * @return true if residuals must be computed and kept, false if residuals
251      * only need to be computed but not kept.
252      */
253     public boolean isComputeAndKeepResiduals() {
254         return computeAndKeepResiduals;
255     }
256 
257     /**
258      * Specifies whether residuals must be computed and kept.
259      *
260      * @param computeAndKeepResiduals true if residuals must be computed and kept,
261      *                                false if residuals only need to be computed but not kept.
262      * @throws LockedException if calibrator is currently running.
263      */
264     public void setComputeAndKeepResidualsEnabled(final boolean computeAndKeepResiduals) throws LockedException {
265         if (running) {
266             throw new LockedException();
267         }
268         this.computeAndKeepResiduals = computeAndKeepResiduals;
269     }
270 
271     /**
272      * Estimates gyroscope calibration parameters containing bias, scale factors
273      * cross-coupling errors and g-dependant cross biases.
274      *
275      * @throws LockedException      if calibrator is currently running.
276      * @throws NotReadyException    if calibrator is not ready.
277      * @throws CalibrationException if estimation fails for numerical reasons.
278      */
279     @SuppressWarnings("DuplicatedCode")
280     @Override
281     public void calibrate() throws LockedException, NotReadyException, CalibrationException {
282         if (running) {
283             throw new LockedException();
284         }
285         if (!isReady()) {
286             throw new NotReadyException();
287         }
288 
289         final var innerEstimator = new RANSACRobustEstimator<>(new RANSACRobustEstimatorListener<PreliminaryResult>() {
290             @Override
291             public double getThreshold() {
292                 return threshold;
293             }
294 
295             @Override
296             public int getTotalSamples() {
297                 return measurements.size();
298             }
299 
300             @Override
301             public int getSubsetSize() {
302                 return preliminarySubsetSize;
303             }
304 
305             @Override
306             public void estimatePreliminarSolutions(
307                     final int[] samplesIndices, final List<PreliminaryResult> solutions) {
308                 computePreliminarySolutions(samplesIndices, solutions);
309             }
310 
311             @Override
312             public double computeResidual(final PreliminaryResult currentEstimation, final int i) {
313                 return computeError(measurements.get(i), currentEstimation);
314             }
315 
316             @Override
317             public boolean isReady() {
318                 return RANSACRobustKnownFrameGyroscopeCalibrator.super.isReady();
319             }
320 
321             @Override
322             public void onEstimateStart(final RobustEstimator<PreliminaryResult> estimator) {
323                 // no action needed
324             }
325 
326             @Override
327             public void onEstimateEnd(final RobustEstimator<PreliminaryResult> estimator) {
328                 // no action needed
329             }
330 
331             @Override
332             public void onEstimateNextIteration(
333                     final RobustEstimator<PreliminaryResult> estimator, final int iteration) {
334                 if (listener != null) {
335                     listener.onCalibrateNextIteration(
336                             RANSACRobustKnownFrameGyroscopeCalibrator.this, iteration);
337                 }
338             }
339 
340             @Override
341             public void onEstimateProgressChange(
342                     final RobustEstimator<PreliminaryResult> estimator, final float progress) {
343                 if (listener != null) {
344                     listener.onCalibrateProgressChange(
345                             RANSACRobustKnownFrameGyroscopeCalibrator.this, progress);
346                 }
347             }
348         });
349 
350         try {
351             running = true;
352 
353             if (listener != null) {
354                 listener.onCalibrateStart(this);
355             }
356 
357             inliersData = null;
358             innerEstimator.setComputeAndKeepInliersEnabled(computeAndKeepInliers || refineResult);
359             innerEstimator.setComputeAndKeepResidualsEnabled(computeAndKeepResiduals || refineResult);
360             innerEstimator.setConfidence(confidence);
361             innerEstimator.setMaxIterations(maxIterations);
362             innerEstimator.setProgressDelta(progressDelta);
363             final var preliminaryResult = innerEstimator.estimate();
364             inliersData = innerEstimator.getInliersData();
365 
366             attemptRefine(preliminaryResult);
367 
368             if (listener != null) {
369                 listener.onCalibrateEnd(this);
370             }
371 
372         } catch (final com.irurueta.numerical.LockedException e) {
373             throw new LockedException(e);
374         } catch (final com.irurueta.numerical.NotReadyException e) {
375             throw new NotReadyException(e);
376         } catch (final RobustEstimatorException e) {
377             throw new CalibrationException(e);
378         } finally {
379             running = false;
380         }
381     }
382 
383     /**
384      * Returns method being used for robust estimation.
385      *
386      * @return method being used for robust estimation.
387      */
388     @Override
389     public RobustEstimatorMethod getMethod() {
390         return RobustEstimatorMethod.RANSAC;
391     }
392 
393     /**
394      * Indicates whether this calibrator requires quality scores for each
395      * measurement/sequence or not.
396      *
397      * @return true if quality scores are required, false otherwise.
398      */
399     @Override
400     public boolean isQualityScoresRequired() {
401         return false;
402     }
403 }