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