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