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.frames.ECEFPosition;
22  import com.irurueta.navigation.frames.NEDPosition;
23  import com.irurueta.navigation.inertial.calibration.CalibrationException;
24  import com.irurueta.navigation.inertial.calibration.StandardDeviationBodyMagneticFluxDensity;
25  import com.irurueta.navigation.inertial.wmm.WorldMagneticModel;
26  import com.irurueta.numerical.robust.LMedSRobustEstimator;
27  import com.irurueta.numerical.robust.LMedSRobustEstimatorListener;
28  import com.irurueta.numerical.robust.RobustEstimator;
29  import com.irurueta.numerical.robust.RobustEstimatorException;
30  import com.irurueta.numerical.robust.RobustEstimatorMethod;
31  
32  import java.io.IOException;
33  import java.util.List;
34  
35  /**
36   * Robustly estimates magnetometer soft-iron cross couplings and
37   * scaling factors using LMedS algorithm.
38   * <p>
39   * To use this calibrator at least 7 measurements taken at a single known
40   * position and instant must be taken at 7 different unknown orientations and
41   * zero velocity when common z-axis is assumed, otherwise at least 10
42   * measurements are required.
43   * <p>
44   * Measured magnetic flux density is assumed to follow the model shown below:
45   * <pre>
46   *     mBmeas = bm + (I + Mm) * mBtrue + w
47   * </pre>
48   * Where:
49   * - mBmeas is the measured magnetic flux density. This is a 3x1 vector.
50   * - bm is magnetometer hard-iron bias. Ideally, on a perfect magnetometer,
51   * this should be a 3x1 zero vector.
52   * - I is the 3x3 identity matrix.
53   * - Mm is the 3x3 soft-iron matrix containing cross-couplings and scaling
54   * factors. Ideally, on a perfect magnetometer, this should be a 3x3 zero
55   * matrix.
56   * - mBtrue is ground-truth magnetic flux density. This is a 3x1 vector.
57   * - w is measurement noise. This is a 3x1 vector.
58   * Notice that this calibrator assumes that all measurements are taken in
59   * a short span of time, where Earth magnetic field can be assumed to be
60   * constant at provided location and instant.
61   */
62  public class LMedSRobustKnownHardIronPositionAndInstantMagnetometerCalibrator extends
63          RobustKnownHardIronPositionAndInstantMagnetometerCalibrator {
64  
65      /**
66       * Default value to be used for stop threshold. Stop threshold can be used to
67       * avoid keeping the algorithm unnecessarily iterating in case that best
68       * estimated threshold using median of residuals is not small enough. Once a
69       * solution is found that generates a threshold below this value, the
70       * algorithm will stop.
71       * The stop threshold can be used to prevent the LMedS algorithm iterating
72       * too many times in cases where samples have a very similar accuracy.
73       * For instance, in cases where proportion of outliers is very small (close
74       * to 0%), and samples are very accurate (i.e. 1e-6), the algorithm would
75       * iterate for a long time trying to find the best solution when indeed
76       * there is no need to do that if a reasonable threshold has already been
77       * reached.
78       * Because of this behaviour the stop threshold can be set to a value much
79       * lower than the one typically used in LMedS, and yet the algorithm could
80       * still produce even smaller thresholds in estimated results.
81       */
82      public static final double DEFAULT_STOP_THRESHOLD = 1e-9;
83  
84      /**
85       * Minimum allowed stop threshold value.
86       */
87      public static final double MIN_STOP_THRESHOLD = 0.0;
88  
89      /**
90       * Threshold to be used to keep the algorithm iterating in case that best
91       * estimated threshold using median of residuals is not small enough. Once
92       * a solution is found that generates a threshold below this value, the
93       * algorithm will stop.
94       * The stop threshold can be used to prevent the LMedS algorithm iterating
95       * too many times in cases where samples have a very similar accuracy.
96       * For instance, in cases where proportion of outliers is very small (close
97       * to 0%), and samples are very accurate (i.e. 1e-6), the algorithm would
98       * iterate for a long time trying to find the best solution when indeed
99       * there is no need to do that if a reasonable threshold has already been
100      * reached.
101      * Because of this behaviour the stop threshold can be set to a value much
102      * lower than the one typically used in LMedS, and yet the algorithm could
103      * still produce even smaller thresholds in estimated results.
104      */
105     private double stopThreshold = DEFAULT_STOP_THRESHOLD;
106 
107     /**
108      * Constructor.
109      */
110     public LMedSRobustKnownHardIronPositionAndInstantMagnetometerCalibrator() {
111         super();
112     }
113 
114     /**
115      * Constructor.
116      *
117      * @param listener listener to handle events raised by this calibrator.
118      */
119     public LMedSRobustKnownHardIronPositionAndInstantMagnetometerCalibrator(
120             final RobustKnownHardIronPositionAndInstantMagnetometerCalibratorListener listener) {
121         super(listener);
122     }
123 
124     /**
125      * Constructor.
126      *
127      * @param measurements list of body magnetic flux density
128      *                     measurements with standard deviation of
129      *                     magnetometer measurements taken at the same
130      *                     position with zero velocity and unknown different
131      *                     orientations.
132      */
133     public LMedSRobustKnownHardIronPositionAndInstantMagnetometerCalibrator(
134             final List<StandardDeviationBodyMagneticFluxDensity> measurements) {
135         super(measurements);
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 LMedSRobustKnownHardIronPositionAndInstantMagnetometerCalibrator(final boolean commonAxisUsed) {
145         super(commonAxisUsed);
146     }
147 
148     /**
149      * Constructor.
150      *
151      * @param magneticModel Earth's magnetic model. If null, a default model
152      *                      will be used instead.
153      */
154     public LMedSRobustKnownHardIronPositionAndInstantMagnetometerCalibrator(final WorldMagneticModel magneticModel) {
155         super(magneticModel);
156     }
157 
158     /**
159      * Constructor.
160      *
161      * @param hardIron known hard-iron.
162      * @throws IllegalArgumentException if provided hard-iron array does
163      *                                  not have length 3.
164      */
165     public LMedSRobustKnownHardIronPositionAndInstantMagnetometerCalibrator(final double[] hardIron) {
166         super(hardIron);
167     }
168 
169     /**
170      * Constructor.
171      *
172      * @param hardIron known hard-iron.
173      * @throws IllegalArgumentException if provided hard-iron matrix is not
174      *                                  3x1.
175      */
176     public LMedSRobustKnownHardIronPositionAndInstantMagnetometerCalibrator(final Matrix hardIron) {
177         super(hardIron);
178     }
179 
180     /**
181      * Constructor.
182      *
183      * @param hardIron  known hard-iron.
184      * @param initialMm initial soft-iron matrix containing scale factors
185      *                  and cross coupling errors.
186      * @throws IllegalArgumentException if provided hard-iron matrix is not
187      *                                  3x1 or if soft-iron matrix is not
188      *                                  3x3.
189      */
190     public LMedSRobustKnownHardIronPositionAndInstantMagnetometerCalibrator(
191             final Matrix hardIron, final Matrix initialMm) {
192         super(hardIron, initialMm);
193     }
194 
195     /**
196      * Constructor.
197      *
198      * @param position position where body magnetic flux density measurements
199      *                 have been taken.
200      */
201     public LMedSRobustKnownHardIronPositionAndInstantMagnetometerCalibrator(final NEDPosition position) {
202         super(position);
203     }
204 
205     /**
206      * Constructor.
207      *
208      * @param position     position where body magnetic flux density measurements
209      *                     have been taken.
210      * @param measurements collection of body magnetic flux density
211      *                     measurements with standard deviation of
212      *                     magnetometer measurements taken at the same
213      *                     position with zero velocity and unknown different
214      *                     orientations.
215      */
216     public LMedSRobustKnownHardIronPositionAndInstantMagnetometerCalibrator(
217             final NEDPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements) {
218         super(position, measurements);
219     }
220 
221     /**
222      * Constructor.
223      *
224      * @param position     position where body magnetic flux density measurements
225      *                     have been taken.
226      * @param measurements collection of body magnetic flux density
227      *                     measurements with standard deviation of
228      *                     magnetometer measurements taken at the same
229      *                     position with zero velocity and unknown different
230      *                     orientations.
231      * @param listener     listener to handle events raised by this calibrator.
232      */
233     public LMedSRobustKnownHardIronPositionAndInstantMagnetometerCalibrator(
234             final NEDPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
235             final RobustKnownHardIronPositionAndInstantMagnetometerCalibratorListener listener) {
236         super(position, measurements, listener);
237     }
238 
239     /**
240      * Constructor.
241      *
242      * @param position       position where body magnetic flux density measurements
243      *                       have been taken.
244      * @param measurements   collection of body magnetic flux density
245      *                       measurements with standard deviation of
246      *                       magnetometer measurements taken at the same
247      *                       position with zero velocity and unknown different
248      *                       orientations.
249      * @param commonAxisUsed indicates whether z-axis is assumed to be common
250      *                       for the accelerometer, gyroscope and magnetometer.
251      */
252     public LMedSRobustKnownHardIronPositionAndInstantMagnetometerCalibrator(
253             final NEDPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
254             final boolean commonAxisUsed) {
255         super(position, measurements, commonAxisUsed);
256     }
257 
258     /**
259      * Constructor.
260      *
261      * @param position       position where body magnetic flux density measurements
262      *                       have been taken.
263      * @param measurements   collection of body magnetic flux density
264      *                       measurements with standard deviation of
265      *                       magnetometer measurements taken at the same
266      *                       position with zero velocity and unknown different
267      *                       orientations.
268      * @param commonAxisUsed indicates whether z-axis is assumed to be common
269      *                       for the accelerometer, gyroscope and magnetometer.
270      * @param listener       listener to handle events raised by this calibrator.
271      */
272     public LMedSRobustKnownHardIronPositionAndInstantMagnetometerCalibrator(
273             final NEDPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
274             final boolean commonAxisUsed,
275             final RobustKnownHardIronPositionAndInstantMagnetometerCalibratorListener listener) {
276         super(position, measurements, commonAxisUsed, listener);
277     }
278 
279     /**
280      * Constructor.
281      *
282      * @param position     position where body magnetic flux density measurements
283      *                     have been taken.
284      * @param measurements collection of body magnetic flux density
285      *                     measurements with standard deviation of
286      *                     magnetometer measurements taken at the same
287      *                     position with zero velocity and unknown different
288      *                     orientations.
289      * @param hardIron     known hard-iron.
290      * @throws IllegalArgumentException if provided hard-iron array does
291      *                                  not have length 3.
292      */
293     public LMedSRobustKnownHardIronPositionAndInstantMagnetometerCalibrator(
294             final NEDPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
295             final double[] hardIron) {
296         super(position, measurements, hardIron);
297     }
298 
299     /**
300      * Constructor.
301      *
302      * @param position     position where body magnetic flux density measurements
303      *                     have been taken.
304      * @param measurements collection of body magnetic flux density
305      *                     measurements with standard deviation of
306      *                     magnetometer measurements taken at the same
307      *                     position with zero velocity and unknown different
308      *                     orientations.
309      * @param hardIron     known hard-iron.
310      * @param listener     listener to handle events raised by this calibrator.
311      * @throws IllegalArgumentException if provided hard-iron array does
312      *                                  not have length 3.
313      */
314     public LMedSRobustKnownHardIronPositionAndInstantMagnetometerCalibrator(
315             final NEDPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
316             final double[] hardIron,
317             final RobustKnownHardIronPositionAndInstantMagnetometerCalibratorListener listener) {
318         super(position, measurements, hardIron, listener);
319     }
320 
321     /**
322      * Constructor.
323      *
324      * @param position       position where body magnetic flux density measurements
325      *                       have been taken.
326      * @param measurements   collection of body magnetic flux density
327      *                       measurements with standard deviation of
328      *                       magnetometer measurements taken at the same
329      *                       position with zero velocity and unknown different
330      *                       orientations.
331      * @param commonAxisUsed indicates whether z-axis is assumed to be common
332      *                       for the accelerometer, gyroscope and magnetometer.
333      * @param hardIron       known hard-iron.
334      * @throws IllegalArgumentException if provided hard-iron array does
335      *                                  not have length 3.
336      */
337     public LMedSRobustKnownHardIronPositionAndInstantMagnetometerCalibrator(
338             final NEDPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
339             final boolean commonAxisUsed, final double[] hardIron) {
340         super(position, measurements, commonAxisUsed, hardIron);
341     }
342 
343     /**
344      * Constructor.
345      *
346      * @param position       position where body magnetic flux density measurements
347      *                       have been taken.
348      * @param measurements   collection of body magnetic flux density
349      *                       measurements with standard deviation of
350      *                       magnetometer measurements taken at the same
351      *                       position with zero velocity and unknown different
352      *                       orientations.
353      * @param commonAxisUsed indicates whether z-axis is assumed to be common
354      *                       for the accelerometer, gyroscope and magnetometer.
355      * @param hardIron       known hard-iron.
356      * @param listener       listener to handle events raised by this calibrator.
357      * @throws IllegalArgumentException if provided hard-iron array does
358      *                                  not have length 3.
359      */
360     public LMedSRobustKnownHardIronPositionAndInstantMagnetometerCalibrator(
361             final NEDPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
362             final boolean commonAxisUsed, final double[] hardIron,
363             final RobustKnownHardIronPositionAndInstantMagnetometerCalibratorListener listener) {
364         super(position, measurements, commonAxisUsed, hardIron, listener);
365     }
366 
367     /**
368      * Constructor.
369      *
370      * @param position     position where body magnetic flux density measurements
371      *                     have been taken.
372      * @param measurements collection of body magnetic flux density
373      *                     measurements with standard deviation of
374      *                     magnetometer measurements taken at the same
375      *                     position with zero velocity and unknown different
376      *                     orientations.
377      * @param hardIron     known hard-iron.
378      * @throws IllegalArgumentException if provided hard-iron matrix is not
379      *                                  3x1.
380      */
381     public LMedSRobustKnownHardIronPositionAndInstantMagnetometerCalibrator(
382             final NEDPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
383             final Matrix hardIron) {
384         super(position, measurements, hardIron);
385     }
386 
387     /**
388      * Constructor.
389      *
390      * @param position     position where body magnetic flux density measurements
391      *                     have been taken.
392      * @param measurements collection of body magnetic flux density
393      *                     measurements with standard deviation of
394      *                     magnetometer measurements taken at the same
395      *                     position with zero velocity and unknown different
396      *                     orientations.
397      * @param hardIron     known hard-iron.
398      * @param listener     listener to handle events raised by this calibrator.
399      * @throws IllegalArgumentException if provided hard-iron matrix is not
400      *                                  3x1.
401      */
402     public LMedSRobustKnownHardIronPositionAndInstantMagnetometerCalibrator(
403             final NEDPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
404             final Matrix hardIron, final RobustKnownHardIronPositionAndInstantMagnetometerCalibratorListener listener) {
405         super(position, measurements, hardIron, listener);
406     }
407 
408     /**
409      * Constructor.
410      *
411      * @param position       position where body magnetic flux density measurements
412      *                       have been taken.
413      * @param measurements   collection of body magnetic flux density
414      *                       measurements with standard deviation of
415      *                       magnetometer measurements taken at the same
416      *                       position with zero velocity and unknown different
417      *                       orientations.
418      * @param commonAxisUsed indicates whether z-axis is assumed to be common
419      *                       for the accelerometer, gyroscope and magnetometer.
420      * @param hardIron       known hard-iron.
421      * @throws IllegalArgumentException if provided hard-iron matrix is not
422      *                                  3x1.
423      */
424     public LMedSRobustKnownHardIronPositionAndInstantMagnetometerCalibrator(
425             final NEDPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
426             final boolean commonAxisUsed, final Matrix hardIron) {
427         super(position, measurements, commonAxisUsed, hardIron);
428     }
429 
430     /**
431      * Constructor.
432      *
433      * @param position       position where body magnetic flux density measurements
434      *                       have been taken.
435      * @param measurements   collection of body magnetic flux density
436      *                       measurements with standard deviation of
437      *                       magnetometer measurements taken at the same
438      *                       position with zero velocity and unknown different
439      *                       orientations.
440      * @param commonAxisUsed indicates whether z-axis is assumed to be common
441      *                       for the accelerometer, gyroscope and magnetometer.
442      * @param hardIron       known hard-iron.
443      * @param listener       listener to handle events raised by this calibrator.
444      * @throws IllegalArgumentException if provided hard-iron matrix is not
445      *                                  3x1.
446      */
447     public LMedSRobustKnownHardIronPositionAndInstantMagnetometerCalibrator(
448             final NEDPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
449             final boolean commonAxisUsed, final Matrix hardIron,
450             final RobustKnownHardIronPositionAndInstantMagnetometerCalibratorListener listener) {
451         super(position, measurements, commonAxisUsed, hardIron, listener);
452     }
453 
454     /**
455      * Constructor.
456      *
457      * @param position     position where body magnetic flux density measurements
458      *                     have been taken.
459      * @param measurements collection of body magnetic flux density
460      *                     measurements with standard deviation of
461      *                     magnetometer measurements taken at the same
462      *                     position with zero velocity and unknown different
463      *                     orientations.
464      * @param hardIron     known hard-iron.
465      * @param initialMm    initial soft-iron matrix containing scale factors
466      *                     and cross coupling errors.
467      * @throws IllegalArgumentException if provided hard-iron matrix is not
468      *                                  3x1 or if soft-iron matrix is not
469      *                                  3x3.
470      */
471     public LMedSRobustKnownHardIronPositionAndInstantMagnetometerCalibrator(
472             final NEDPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
473             final Matrix hardIron, final Matrix initialMm) {
474         super(position, measurements, hardIron, initialMm);
475     }
476 
477     /**
478      * Constructor.
479      *
480      * @param position     position where body magnetic flux density measurements
481      *                     have been taken.
482      * @param measurements collection of body magnetic flux density
483      *                     measurements with standard deviation of
484      *                     magnetometer measurements taken at the same
485      *                     position with zero velocity and unknown different
486      *                     orientations.
487      * @param hardIron     known hard-iron.
488      * @param initialMm    initial soft-iron matrix containing scale factors
489      *                     and cross coupling errors.
490      * @param listener     listener to handle events raised by this calibrator.
491      * @throws IllegalArgumentException if provided hard-iron matrix is not
492      *                                  3x1 or if soft-iron matrix is not
493      *                                  3x3.
494      */
495     public LMedSRobustKnownHardIronPositionAndInstantMagnetometerCalibrator(
496             final NEDPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
497             final Matrix hardIron, final Matrix initialMm,
498             final RobustKnownHardIronPositionAndInstantMagnetometerCalibratorListener listener) {
499         super(position, measurements, hardIron, initialMm, listener);
500     }
501 
502     /**
503      * Constructor.
504      *
505      * @param position       position where body magnetic flux density measurements
506      *                       have been taken.
507      * @param measurements   collection of body magnetic flux density
508      *                       measurements with standard deviation of
509      *                       magnetometer measurements taken at the same
510      *                       position with zero velocity and unknown different
511      *                       orientations.
512      * @param commonAxisUsed indicates whether z-axis is assumed to be common
513      *                       for the accelerometer, gyroscope and magnetometer.
514      * @param hardIron       known hard-iron.
515      * @param initialMm      initial soft-iron matrix containing scale factors
516      *                       and cross coupling errors.
517      * @throws IllegalArgumentException if provided hard-iron matrix is not
518      *                                  3x1 or if soft-iron matrix is not
519      *                                  3x3.
520      */
521     public LMedSRobustKnownHardIronPositionAndInstantMagnetometerCalibrator(
522             final NEDPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
523             final boolean commonAxisUsed, final Matrix hardIron, final Matrix initialMm) {
524         super(position, measurements, commonAxisUsed, hardIron, initialMm);
525     }
526 
527     /**
528      * Constructor.
529      *
530      * @param position       position where body magnetic flux density measurements
531      *                       have been taken.
532      * @param measurements   collection of body magnetic flux density
533      *                       measurements with standard deviation of
534      *                       magnetometer measurements taken at the same
535      *                       position with zero velocity and unknown different
536      *                       orientations.
537      * @param commonAxisUsed indicates whether z-axis is assumed to be common
538      *                       for the accelerometer, gyroscope and magnetometer.
539      * @param hardIron       known hard-iron.
540      * @param initialMm      initial soft-iron matrix containing scale factors
541      *                       and cross coupling errors.
542      * @param listener       listener to handle events raised by this calibrator.
543      * @throws IllegalArgumentException if provided hard-iron matrix is not
544      *                                  3x1 or if soft-iron matrix is not
545      *                                  3x3.
546      */
547     public LMedSRobustKnownHardIronPositionAndInstantMagnetometerCalibrator(
548             final NEDPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
549             final boolean commonAxisUsed, final Matrix hardIron, final Matrix initialMm,
550             final RobustKnownHardIronPositionAndInstantMagnetometerCalibratorListener listener) {
551         super(position, measurements, commonAxisUsed, hardIron, initialMm, listener);
552     }
553 
554     /**
555      * Constructor.
556      *
557      * @param position position where body magnetic flux density measurements
558      *                 have been taken.
559      */
560     public LMedSRobustKnownHardIronPositionAndInstantMagnetometerCalibrator(final ECEFPosition position) {
561         super(position);
562     }
563 
564     /**
565      * Constructor.
566      *
567      * @param position     position where body magnetic flux density measurements
568      *                     have been taken.
569      * @param measurements collection of body magnetic flux density
570      *                     measurements with standard deviation of
571      *                     magnetometer measurements taken at the same
572      *                     position with zero velocity and unknown different
573      *                     orientations.
574      */
575     public LMedSRobustKnownHardIronPositionAndInstantMagnetometerCalibrator(
576             final ECEFPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements) {
577         super(position, measurements);
578     }
579 
580     /**
581      * Constructor.
582      *
583      * @param position     position where body magnetic flux density measurements
584      *                     have been taken.
585      * @param measurements collection of body magnetic flux density
586      *                     measurements with standard deviation of
587      *                     magnetometer measurements taken at the same
588      *                     position with zero velocity and unknown different
589      *                     orientations.
590      * @param listener     listener to handle events raised by this calibrator.
591      */
592     public LMedSRobustKnownHardIronPositionAndInstantMagnetometerCalibrator(
593             final ECEFPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
594             final RobustKnownHardIronPositionAndInstantMagnetometerCalibratorListener listener) {
595         super(position, measurements, listener);
596     }
597 
598     /**
599      * Constructor.
600      *
601      * @param position       position where body magnetic flux density measurements
602      *                       have been taken.
603      * @param measurements   collection of body magnetic flux density
604      *                       measurements with standard deviation of
605      *                       magnetometer measurements taken at the same
606      *                       position with zero velocity and unknown different
607      *                       orientations.
608      * @param commonAxisUsed indicates whether z-axis is assumed to be common
609      *                       for the accelerometer, gyroscope and magnetometer.
610      */
611     public LMedSRobustKnownHardIronPositionAndInstantMagnetometerCalibrator(
612             final ECEFPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
613             final boolean commonAxisUsed) {
614         super(position, measurements, commonAxisUsed);
615     }
616 
617     /**
618      * Constructor.
619      *
620      * @param position       position where body magnetic flux density measurements
621      *                       have been taken.
622      * @param measurements   collection of body magnetic flux density
623      *                       measurements with standard deviation of
624      *                       magnetometer measurements taken at the same
625      *                       position with zero velocity and unknown different
626      *                       orientations.
627      * @param commonAxisUsed indicates whether z-axis is assumed to be common
628      *                       for the accelerometer, gyroscope and magnetometer.
629      * @param listener       listener to handle events raised by this calibrator.
630      */
631     public LMedSRobustKnownHardIronPositionAndInstantMagnetometerCalibrator(
632             final ECEFPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
633             final boolean commonAxisUsed,
634             final RobustKnownHardIronPositionAndInstantMagnetometerCalibratorListener listener) {
635         super(position, measurements, commonAxisUsed, listener);
636     }
637 
638     /**
639      * Constructor.
640      *
641      * @param position     position where body magnetic flux density measurements
642      *                     have been taken.
643      * @param measurements collection of body magnetic flux density
644      *                     measurements with standard deviation of
645      *                     magnetometer measurements taken at the same
646      *                     position with zero velocity and unknown different
647      *                     orientations.
648      * @param hardIron     known hard-iron.
649      * @throws IllegalArgumentException if provided hard-iron array does
650      *                                  not have length 3.
651      */
652     public LMedSRobustKnownHardIronPositionAndInstantMagnetometerCalibrator(
653             final ECEFPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
654             final double[] hardIron) {
655         super(position, measurements, hardIron);
656     }
657 
658     /**
659      * Constructor.
660      *
661      * @param position     position where body magnetic flux density measurements
662      *                     have been taken.
663      * @param measurements collection of body magnetic flux density
664      *                     measurements with standard deviation of
665      *                     magnetometer measurements taken at the same
666      *                     position with zero velocity and unknown different
667      *                     orientations.
668      * @param hardIron     known hard-iron.
669      * @param listener     listener to handle events raised by this calibrator.
670      * @throws IllegalArgumentException if provided hard-iron array does
671      *                                  not have length 3.
672      */
673     public LMedSRobustKnownHardIronPositionAndInstantMagnetometerCalibrator(
674             final ECEFPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
675             final double[] hardIron,
676             final RobustKnownHardIronPositionAndInstantMagnetometerCalibratorListener listener) {
677         super(position, measurements, hardIron, listener);
678     }
679 
680     /**
681      * Constructor.
682      *
683      * @param position       position where body magnetic flux density measurements
684      *                       have been taken.
685      * @param measurements   collection of body magnetic flux density
686      *                       measurements with standard deviation of
687      *                       magnetometer measurements taken at the same
688      *                       position with zero velocity and unknown different
689      *                       orientations.
690      * @param commonAxisUsed indicates whether z-axis is assumed to be common
691      *                       for the accelerometer, gyroscope and magnetometer.
692      * @param hardIron       known hard-iron.
693      * @throws IllegalArgumentException if provided hard-iron array does
694      *                                  not have length 3.
695      */
696     public LMedSRobustKnownHardIronPositionAndInstantMagnetometerCalibrator(
697             final ECEFPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
698             final boolean commonAxisUsed, final double[] hardIron) {
699         super(position, measurements, commonAxisUsed, hardIron);
700     }
701 
702     /**
703      * Constructor.
704      *
705      * @param position       position where body magnetic flux density measurements
706      *                       have been taken.
707      * @param measurements   collection of body magnetic flux density
708      *                       measurements with standard deviation of
709      *                       magnetometer measurements taken at the same
710      *                       position with zero velocity and unknown different
711      *                       orientations.
712      * @param commonAxisUsed indicates whether z-axis is assumed to be common
713      *                       for the accelerometer, gyroscope and magnetometer.
714      * @param hardIron       known hard-iron.
715      * @param listener       listener to handle events raised by this calibrator.
716      * @throws IllegalArgumentException if provided hard-iron array does
717      *                                  not have length 3.
718      */
719     public LMedSRobustKnownHardIronPositionAndInstantMagnetometerCalibrator(
720             final ECEFPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
721             final boolean commonAxisUsed, final double[] hardIron,
722             final RobustKnownHardIronPositionAndInstantMagnetometerCalibratorListener listener) {
723         super(position, measurements, commonAxisUsed, hardIron, listener);
724     }
725 
726     /**
727      * Constructor.
728      *
729      * @param position     position where body magnetic flux density measurements
730      *                     have been taken.
731      * @param measurements collection of body magnetic flux density
732      *                     measurements with standard deviation of
733      *                     magnetometer measurements taken at the same
734      *                     position with zero velocity and unknown different
735      *                     orientations.
736      * @param hardIron     known hard-iron.
737      * @throws IllegalArgumentException if provided hard-iron matrix is not
738      *                                  3x1.
739      */
740     public LMedSRobustKnownHardIronPositionAndInstantMagnetometerCalibrator(
741             final ECEFPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
742             final Matrix hardIron) {
743         super(position, measurements, hardIron);
744     }
745 
746     /**
747      * Constructor.
748      *
749      * @param position     position where body magnetic flux density measurements
750      *                     have been taken.
751      * @param measurements collection of body magnetic flux density
752      *                     measurements with standard deviation of
753      *                     magnetometer measurements taken at the same
754      *                     position with zero velocity and unknown different
755      *                     orientations.
756      * @param hardIron     known hard-iron.
757      * @param listener     listener to handle events raised by this calibrator.
758      * @throws IllegalArgumentException if provided hard-iron matrix is not
759      *                                  3x1.
760      */
761     public LMedSRobustKnownHardIronPositionAndInstantMagnetometerCalibrator(
762             final ECEFPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
763             final Matrix hardIron, final RobustKnownHardIronPositionAndInstantMagnetometerCalibratorListener listener) {
764         super(position, measurements, hardIron, listener);
765     }
766 
767     /**
768      * Constructor.
769      *
770      * @param position       position where body magnetic flux density measurements
771      *                       have been taken.
772      * @param measurements   collection of body magnetic flux density
773      *                       measurements with standard deviation of
774      *                       magnetometer measurements taken at the same
775      *                       position with zero velocity and unknown different
776      *                       orientations.
777      * @param commonAxisUsed indicates whether z-axis is assumed to be common
778      *                       for the accelerometer, gyroscope and magnetometer.
779      * @param hardIron       known hard-iron.
780      * @throws IllegalArgumentException if provided hard-iron matrix is not
781      *                                  3x1.
782      */
783     public LMedSRobustKnownHardIronPositionAndInstantMagnetometerCalibrator(
784             final ECEFPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
785             final boolean commonAxisUsed, final Matrix hardIron) {
786         super(position, measurements, commonAxisUsed, hardIron);
787     }
788 
789     /**
790      * Constructor.
791      *
792      * @param position       position where body magnetic flux density measurements
793      *                       have been taken.
794      * @param measurements   collection of body magnetic flux density
795      *                       measurements with standard deviation of
796      *                       magnetometer measurements taken at the same
797      *                       position with zero velocity and unknown different
798      *                       orientations.
799      * @param commonAxisUsed indicates whether z-axis is assumed to be common
800      *                       for the accelerometer, gyroscope and magnetometer.
801      * @param hardIron       known hard-iron.
802      * @param listener       listener to handle events raised by this calibrator.
803      * @throws IllegalArgumentException if provided hard-iron matrix is not
804      *                                  3x1.
805      */
806     public LMedSRobustKnownHardIronPositionAndInstantMagnetometerCalibrator(
807             final ECEFPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
808             final boolean commonAxisUsed, final Matrix hardIron,
809             final RobustKnownHardIronPositionAndInstantMagnetometerCalibratorListener listener) {
810         super(position, measurements, commonAxisUsed, hardIron, listener);
811     }
812 
813     /**
814      * Constructor.
815      *
816      * @param position     position where body magnetic flux density measurements
817      *                     have been taken.
818      * @param measurements collection of body magnetic flux density
819      *                     measurements with standard deviation of
820      *                     magnetometer measurements taken at the same
821      *                     position with zero velocity and unknown different
822      *                     orientations.
823      * @param hardIron     known hard-iron.
824      * @param initialMm    initial soft-iron matrix containing scale factors
825      *                     and cross coupling errors.
826      * @throws IllegalArgumentException if provided hard-iron matrix is not
827      *                                  3x1 or if soft-iron matrix is not
828      *                                  3x3.
829      */
830     public LMedSRobustKnownHardIronPositionAndInstantMagnetometerCalibrator(
831             final ECEFPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
832             final Matrix hardIron, final Matrix initialMm) {
833         super(position, measurements, hardIron, initialMm);
834     }
835 
836     /**
837      * Constructor.
838      *
839      * @param position     position where body magnetic flux density measurements
840      *                     have been taken.
841      * @param measurements collection of body magnetic flux density
842      *                     measurements with standard deviation of
843      *                     magnetometer measurements taken at the same
844      *                     position with zero velocity and unknown different
845      *                     orientations.
846      * @param hardIron     known hard-iron.
847      * @param initialMm    initial soft-iron matrix containing scale factors
848      *                     and cross coupling errors.
849      * @param listener     listener to handle events raised by this calibrator.
850      * @throws IllegalArgumentException if provided hard-iron matrix is not
851      *                                  3x1 or if soft-iron matrix is not
852      *                                  3x3.
853      */
854     public LMedSRobustKnownHardIronPositionAndInstantMagnetometerCalibrator(
855             final ECEFPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
856             final Matrix hardIron, final Matrix initialMm,
857             final RobustKnownHardIronPositionAndInstantMagnetometerCalibratorListener listener) {
858         super(position, measurements, hardIron, initialMm, listener);
859     }
860 
861     /**
862      * Constructor.
863      *
864      * @param position       position where body magnetic flux density measurements
865      *                       have been taken.
866      * @param measurements   collection of body magnetic flux density
867      *                       measurements with standard deviation of
868      *                       magnetometer measurements taken at the same
869      *                       position with zero velocity and unknown different
870      *                       orientations.
871      * @param commonAxisUsed indicates whether z-axis is assumed to be common
872      *                       for the accelerometer, gyroscope and magnetometer.
873      * @param hardIron       known hard-iron.
874      * @param initialMm      initial soft-iron matrix containing scale factors
875      *                       and cross coupling errors.
876      * @throws IllegalArgumentException if provided hard-iron matrix is not
877      *                                  3x1 or if soft-iron matrix is not
878      *                                  3x3.
879      */
880     public LMedSRobustKnownHardIronPositionAndInstantMagnetometerCalibrator(
881             final ECEFPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
882             final boolean commonAxisUsed, final Matrix hardIron, final Matrix initialMm) {
883         super(position, measurements, commonAxisUsed, hardIron, initialMm);
884     }
885 
886     /**
887      * Constructor.
888      *
889      * @param position       position where body magnetic flux density measurements
890      *                       have been taken.
891      * @param measurements   collection of body magnetic flux density
892      *                       measurements with standard deviation of
893      *                       magnetometer measurements taken at the same
894      *                       position with zero velocity and unknown different
895      *                       orientations.
896      * @param commonAxisUsed indicates whether z-axis is assumed to be common
897      *                       for the accelerometer, gyroscope and magnetometer.
898      * @param hardIron       known hard-iron.
899      * @param initialMm      initial soft-iron matrix containing scale factors
900      *                       and cross coupling errors.
901      * @param listener       listener to handle events raised by this calibrator.
902      * @throws IllegalArgumentException if provided hard-iron matrix is not
903      *                                  3x1 or if soft-iron matrix is not
904      *                                  3x3.
905      */
906     public LMedSRobustKnownHardIronPositionAndInstantMagnetometerCalibrator(
907             final ECEFPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
908             final boolean commonAxisUsed, final Matrix hardIron, final Matrix initialMm,
909             final RobustKnownHardIronPositionAndInstantMagnetometerCalibratorListener listener) {
910         super(position, measurements, commonAxisUsed, hardIron, initialMm, listener);
911     }
912 
913     /**
914      * Returns threshold to be used to keep the algorithm iterating in case that
915      * best estimated threshold using median of residuals is not small enough.
916      * Once a solution is found that generates a threshold below this value, the
917      * algorithm will stop.
918      * The stop threshold can be used to prevent the LMedS algorithm to iterate
919      * too many times in cases where samples have a very similar accuracy.
920      * For instance, in cases where proportion of outliers is very small (close
921      * to 0%), and samples are very accurate (i.e. 1e-6), the algorithm would
922      * iterate for a long time trying to find the best solution when indeed
923      * there is no need to do that if a reasonable threshold has already been
924      * reached.
925      * Because of this behaviour the stop threshold can be set to a value much
926      * lower than the one typically used in RANSAC, and yet the algorithm could
927      * still produce even smaller thresholds in estimated results.
928      *
929      * @return stop threshold to stop the algorithm prematurely when a certain
930      * accuracy has been reached.
931      */
932     public double getStopThreshold() {
933         return stopThreshold;
934     }
935 
936     /**
937      * Sets threshold to be used to keep the algorithm iterating in case that
938      * best estimated threshold using median of residuals is not small enough.
939      * Once a solution is found that generates a threshold below this value,
940      * the algorithm will stop.
941      * The stop threshold can be used to prevent the LMedS algorithm to iterate
942      * too many times in cases where samples have a very similar accuracy.
943      * For instance, in cases where proportion of outliers is very small (close
944      * to 0%), and samples are very accurate (i.e. 1e-6), the algorithm would
945      * iterate for a long time trying to find the best solution when indeed
946      * there is no need to do that if a reasonable threshold has already been
947      * reached.
948      * Because of this behaviour the stop threshold can be set to a value much
949      * lower than the one typically used in RANSAC, and yet the algorithm could
950      * still produce even smaller thresholds in estimated results.
951      *
952      * @param stopThreshold stop threshold to stop the algorithm prematurely
953      *                      when a certain accuracy has been reached.
954      * @throws IllegalArgumentException if provided value is zero or negative.
955      * @throws LockedException          if calibrator is currently running.
956      */
957     public void setStopThreshold(final double stopThreshold) throws LockedException {
958         if (running) {
959             throw new LockedException();
960         }
961         if (stopThreshold <= MIN_STOP_THRESHOLD) {
962             throw new IllegalArgumentException();
963         }
964 
965         this.stopThreshold = stopThreshold;
966     }
967 
968     /**
969      * Estimates magnetometer calibration parameters containing soft-iron
970      * scale factors and cross-coupling errors.
971      *
972      * @throws LockedException      if calibrator is currently running.
973      * @throws NotReadyException    if calibrator is not ready.
974      * @throws CalibrationException if estimation fails for numerical reasons.
975      */
976     @SuppressWarnings("DuplicatedCode")
977     @Override
978     public void calibrate() throws LockedException, NotReadyException, CalibrationException {
979         if (running) {
980             throw new LockedException();
981         }
982         if (!isReady()) {
983             throw new NotReadyException();
984         }
985 
986         final var innerEstimator = new LMedSRobustEstimator<>(new LMedSRobustEstimatorListener<Matrix>() {
987             @Override
988             public int getTotalSamples() {
989                 return measurements.size();
990             }
991 
992             @Override
993             public int getSubsetSize() {
994                 return preliminarySubsetSize;
995             }
996 
997             @Override
998             public void estimatePreliminarSolutions(final int[] samplesIndices, final List<Matrix> solutions) {
999                 computePreliminarySolutions(samplesIndices, solutions);
1000             }
1001 
1002             @Override
1003             public double computeResidual(final Matrix currentEstimation, final int i) {
1004                 return computeError(measurements.get(i), currentEstimation);
1005             }
1006 
1007             @Override
1008             public boolean isReady() {
1009                 return LMedSRobustKnownHardIronPositionAndInstantMagnetometerCalibrator.this.isReady();
1010             }
1011 
1012             @Override
1013             public void onEstimateStart(final RobustEstimator<Matrix> estimator) {
1014                 // no action needed
1015             }
1016 
1017             @Override
1018             public void onEstimateEnd(final RobustEstimator<Matrix> estimator) {
1019                 // no action needed
1020             }
1021 
1022             @Override
1023             public void onEstimateNextIteration(final RobustEstimator<Matrix> estimator, final int iteration) {
1024                 if (listener != null) {
1025                     listener.onCalibrateNextIteration(
1026                             LMedSRobustKnownHardIronPositionAndInstantMagnetometerCalibrator.this,
1027                             iteration);
1028                 }
1029             }
1030 
1031             @Override
1032             public void onEstimateProgressChange(final RobustEstimator<Matrix> estimator, final float progress) {
1033                 if (listener != null) {
1034                     listener.onCalibrateProgressChange(
1035                             LMedSRobustKnownHardIronPositionAndInstantMagnetometerCalibrator.this,
1036                             progress);
1037                 }
1038             }
1039         });
1040 
1041         try {
1042             running = true;
1043 
1044             if (listener != null) {
1045                 listener.onCalibrateStart(this);
1046             }
1047 
1048             inliersData = null;
1049 
1050             initialize();
1051 
1052             innerEstimator.setConfidence(confidence);
1053             innerEstimator.setMaxIterations(maxIterations);
1054             innerEstimator.setProgressDelta(progressDelta);
1055             innerEstimator.setStopThreshold(stopThreshold);
1056             final var preliminaryResult = innerEstimator.estimate();
1057             inliersData = innerEstimator.getInliersData();
1058 
1059             attemptRefine(preliminaryResult);
1060 
1061             if (listener != null) {
1062                 listener.onCalibrateEnd(this);
1063             }
1064 
1065         } catch (final com.irurueta.numerical.LockedException e) {
1066             throw new LockedException(e);
1067         } catch (final com.irurueta.numerical.NotReadyException e) {
1068             throw new NotReadyException(e);
1069         } catch (final RobustEstimatorException | IOException e) {
1070             throw new CalibrationException(e);
1071         } finally {
1072             running = false;
1073         }
1074     }
1075 
1076     /**
1077      * Returns method being used for robust estimation.
1078      *
1079      * @return method being used for robust estimation.
1080      */
1081     @Override
1082     public RobustEstimatorMethod getMethod() {
1083         return RobustEstimatorMethod.LMEDS;
1084     }
1085 
1086     /**
1087      * Indicates whether this calibrator requires quality scores for each
1088      * measurement or not.
1089      *
1090      * @return true if quality scores are required, false otherwise.
1091      */
1092     @Override
1093     public boolean isQualityScoresRequired() {
1094         return false;
1095     }
1096 }