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 }