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