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 }