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.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 accelerometer biases, cross couplings and scaling factors
32   * using a RANSAC 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 RANSACRobustKnownFrameAccelerometerCalibrator 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       * Constructor.
95       */
96      public RANSACRobustKnownFrameAccelerometerCalibrator() {
97      }
98  
99      /**
100      * Constructor.
101      *
102      * @param listener listener to be notified of events such as when estimation
103      *                 starts, ends or its progress significantly changes.
104      */
105     public RANSACRobustKnownFrameAccelerometerCalibrator(
106             final RobustKnownFrameAccelerometerCalibratorListener listener) {
107         super(listener);
108     }
109 
110     /**
111      * Constructor.
112      *
113      * @param measurements list of body kinematics measurements with standard
114      *                     deviations taken at different frames (positions, orientations
115      *                     and velocities).
116      */
117     public RANSACRobustKnownFrameAccelerometerCalibrator(
118             final List<StandardDeviationFrameBodyKinematics> measurements) {
119         super(measurements);
120     }
121 
122     /**
123      * Constructor.
124      *
125      * @param measurements list of body kinematics measurements with standard
126      *                     deviations taken at different frames (positions, orientations
127      *                     and velocities).
128      * @param listener     listener to handle events raised by this calibrator.
129      */
130     public RANSACRobustKnownFrameAccelerometerCalibrator(
131             final List<StandardDeviationFrameBodyKinematics> measurements,
132             final RobustKnownFrameAccelerometerCalibratorListener listener) {
133         super(measurements, listener);
134     }
135 
136     /**
137      * Constructor.
138      *
139      * @param commonAxisUsed indicates whether z-axis is assumed to be common for
140      *                       accelerometer and gyroscope.
141      */
142     public RANSACRobustKnownFrameAccelerometerCalibrator(final boolean commonAxisUsed) {
143         super(commonAxisUsed);
144     }
145 
146     /**
147      * Constructor.
148      *
149      * @param commonAxisUsed indicates whether z-axis is assumed to be common for
150      *                       accelerometer and gyroscope.
151      * @param listener       listener to handle events raised by this calibrator.
152      */
153     public RANSACRobustKnownFrameAccelerometerCalibrator(
154             final boolean commonAxisUsed, final RobustKnownFrameAccelerometerCalibratorListener listener) {
155         super(commonAxisUsed, listener);
156     }
157 
158     /**
159      * Constructor.
160      *
161      * @param measurements   list of body kinematics measurements with standard
162      *                       deviations taken at different frames (positions, orientations
163      *                       and velocities).
164      * @param commonAxisUsed indicates whether z-axis is assumed to be common for
165      *                       accelerometer and gyroscope.
166      */
167     public RANSACRobustKnownFrameAccelerometerCalibrator(
168             final List<StandardDeviationFrameBodyKinematics> measurements, final boolean commonAxisUsed) {
169         super(measurements, commonAxisUsed);
170     }
171 
172     /**
173      * Constructor.
174      *
175      * @param measurements   list of body kinematics measurements with standard
176      *                       deviations taken at different frames (positions, orientations
177      *                       and velocities).
178      * @param commonAxisUsed indicates whether z-axis is assumed to be common for
179      *                       accelerometer and gyroscope.
180      * @param listener       listener to handle events raised by this calibrator.
181      */
182     public RANSACRobustKnownFrameAccelerometerCalibrator(
183             final List<StandardDeviationFrameBodyKinematics> measurements,
184             final boolean commonAxisUsed, final RobustKnownFrameAccelerometerCalibratorListener listener) {
185         super(measurements, commonAxisUsed, listener);
186     }
187 
188     /**
189      * Gets threshold to determine whether samples are inliers or not when testing possible solutions.
190      * The threshold refers to the amount of error on norm between measured specific forces and the
191      * ones generated with estimated calibration parameters provided for each sample.
192      *
193      * @return threshold to determine whether samples are inliers or not.
194      */
195     public double getThreshold() {
196         return threshold;
197     }
198 
199     /**
200      * Sets threshold to determine whether samples are inliers or not when testing possible solutions.
201      * The threshold refers to the amount of error on norm between measured specific forces and the
202      * ones generated with estimated calibration parameters provided for each sample.
203      *
204      * @param threshold threshold to determine whether samples are inliers or not.
205      * @throws IllegalArgumentException if provided value is equal or less than zero.
206      * @throws LockedException          if calibrator is currently running.
207      */
208     public void setThreshold(final double threshold) throws LockedException {
209         if (running) {
210             throw new LockedException();
211         }
212         if (threshold <= MIN_THRESHOLD) {
213             throw new IllegalArgumentException();
214         }
215         this.threshold = threshold;
216     }
217 
218     /**
219      * Indicates whether inliers must be computed and kept.
220      *
221      * @return true if inliers must be computed and kept, false if inliers
222      * only need to be computed but not kept.
223      */
224     public boolean isComputeAndKeepInliersEnabled() {
225         return computeAndKeepInliers;
226     }
227 
228     /**
229      * Specifies whether inliers must be computed and kept.
230      *
231      * @param computeAndKeepInliers true if inliers must be computed and kept,
232      *                              false if inliers only need to be computed but not kept.
233      * @throws LockedException if calibrator is currently running.
234      */
235     public void setComputeAndKeepInliersEnabled(final boolean computeAndKeepInliers) throws LockedException {
236         if (running) {
237             throw new LockedException();
238         }
239         this.computeAndKeepInliers = computeAndKeepInliers;
240     }
241 
242     /**
243      * Indicates whether residuals must be computed and kept.
244      *
245      * @return true if residuals must be computed and kept, false if residuals
246      * only need to be computed but not kept.
247      */
248     public boolean isComputeAndKeepResiduals() {
249         return computeAndKeepResiduals;
250     }
251 
252     /**
253      * Specifies whether residuals must be computed and kept.
254      *
255      * @param computeAndKeepResiduals true if residuals must be computed and kept,
256      *                                false if residuals only need to be computed but not kept.
257      * @throws LockedException if calibrator is currently running.
258      */
259     public void setComputeAndKeepResidualsEnabled(final boolean computeAndKeepResiduals) throws LockedException {
260         if (running) {
261             throw new LockedException();
262         }
263         this.computeAndKeepResiduals = computeAndKeepResiduals;
264     }
265 
266     /**
267      * Estimates accelerometer calibration parameters containing bias, scale factors
268      * and cross-coupling errors.
269      *
270      * @throws LockedException      if calibrator is currently running.
271      * @throws NotReadyException    if calibrator is not ready.
272      * @throws CalibrationException if estimation fails for numerical reasons.
273      */
274     @SuppressWarnings("DuplicatedCode")
275     @Override
276     public void calibrate() throws LockedException, NotReadyException, CalibrationException {
277         if (running) {
278             throw new LockedException();
279         }
280         if (!isReady()) {
281             throw new NotReadyException();
282         }
283 
284         final var innerEstimator = new RANSACRobustEstimator<>(new RANSACRobustEstimatorListener<PreliminaryResult>() {
285             @Override
286             public double getThreshold() {
287                 return threshold;
288             }
289 
290             @Override
291             public int getTotalSamples() {
292                 return measurements.size();
293             }
294 
295             @Override
296             public int getSubsetSize() {
297                 return preliminarySubsetSize;
298             }
299 
300             @Override
301             public void estimatePreliminarSolutions(
302                     final int[] samplesIndices, final List<PreliminaryResult> solutions) {
303                 computePreliminarySolutions(samplesIndices, solutions);
304             }
305 
306             @Override
307             public double computeResidual(final PreliminaryResult currentEstimation, final int i) {
308                 return computeError(measurements.get(i), currentEstimation);
309             }
310 
311             @Override
312             public boolean isReady() {
313                 return RANSACRobustKnownFrameAccelerometerCalibrator.super.isReady();
314             }
315 
316             @Override
317             public void onEstimateStart(final RobustEstimator<PreliminaryResult> estimator) {
318                 // no action needed
319             }
320 
321             @Override
322             public void onEstimateEnd(final RobustEstimator<PreliminaryResult> estimator) {
323                 // no action needed
324             }
325 
326             @Override
327             public void onEstimateNextIteration(
328                     final RobustEstimator<PreliminaryResult> estimator, final int iteration) {
329                 if (listener != null) {
330                     listener.onCalibrateNextIteration(
331                             RANSACRobustKnownFrameAccelerometerCalibrator.this, iteration);
332                 }
333             }
334 
335             @Override
336             public void onEstimateProgressChange(
337                     final RobustEstimator<PreliminaryResult> estimator, final float progress) {
338                 if (listener != null) {
339                     listener.onCalibrateProgressChange(
340                             RANSACRobustKnownFrameAccelerometerCalibrator.this, progress);
341                 }
342             }
343         });
344 
345         try {
346             running = true;
347 
348             if (listener != null) {
349                 listener.onCalibrateStart(this);
350             }
351 
352             inliersData = null;
353             innerEstimator.setComputeAndKeepInliersEnabled(computeAndKeepInliers || refineResult);
354             innerEstimator.setComputeAndKeepResidualsEnabled(computeAndKeepResiduals || refineResult);
355             innerEstimator.setConfidence(confidence);
356             innerEstimator.setMaxIterations(maxIterations);
357             innerEstimator.setProgressDelta(progressDelta);
358             final var preliminaryResult = innerEstimator.estimate();
359             inliersData = innerEstimator.getInliersData();
360 
361             attemptRefine(preliminaryResult);
362 
363             if (listener != null) {
364                 listener.onCalibrateEnd(this);
365             }
366 
367         } catch (final com.irurueta.numerical.LockedException e) {
368             throw new LockedException(e);
369         } catch (final com.irurueta.numerical.NotReadyException e) {
370             throw new NotReadyException(e);
371         } catch (final RobustEstimatorException e) {
372             throw new CalibrationException(e);
373         } finally {
374             running = false;
375         }
376     }
377 
378     /**
379      * Returns method being used for robust estimation.
380      *
381      * @return method being used for robust estimation.
382      */
383     @Override
384     public RobustEstimatorMethod getMethod() {
385         return RobustEstimatorMethod.RANSAC;
386     }
387 
388     /**
389      * Indicates whether this calibrator requires quality scores for each
390      * measurement or not.
391      *
392      * @return true if quality scores are required, false otherwise.
393      */
394     @Override
395     public boolean isQualityScoresRequired() {
396         return false;
397     }
398 }