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 hard-iron biases, cross couplings and
37   * scaling factors using LMedS algorithm.
38   * <p>
39   * To use this calibrator at least 10 measurements taken at a single known
40   * position and instant must be taken at 10 different unknown orientations and
41   * zero velocity when common z-axis is assumed, otherwise at least 13
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 LMedSRobustKnownPositionAndInstantMagnetometerCalibrator extends
63          RobustKnownPositionAndInstantMagnetometerCalibrator {
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 LMedSRobustKnownPositionAndInstantMagnetometerCalibrator() {
111         super();
112     }
113 
114     /**
115      * Constructor.
116      *
117      * @param listener listener to handle events raised by this calibrator.
118      */
119     public LMedSRobustKnownPositionAndInstantMagnetometerCalibrator(
120             final RobustKnownPositionAndInstantMagnetometerCalibratorListener 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 LMedSRobustKnownPositionAndInstantMagnetometerCalibrator(
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 LMedSRobustKnownPositionAndInstantMagnetometerCalibrator(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 LMedSRobustKnownPositionAndInstantMagnetometerCalibrator(final WorldMagneticModel magneticModel) {
155         super(magneticModel);
156     }
157 
158     /**
159      * Constructor.
160      *
161      * @param initialHardIron initial hard-iron to find a solution.
162      * @throws IllegalArgumentException if provided hard-iron array does
163      *                                  not have length 3.
164      */
165     public LMedSRobustKnownPositionAndInstantMagnetometerCalibrator(final double[] initialHardIron) {
166         super(initialHardIron);
167     }
168 
169     /**
170      * Constructor.
171      *
172      * @param initialHardIron initial hard-iron to find a solution.
173      * @throws IllegalArgumentException if provided hard-iron matrix is not
174      *                                  3x1.
175      */
176     public LMedSRobustKnownPositionAndInstantMagnetometerCalibrator(final Matrix initialHardIron) {
177         super(initialHardIron);
178     }
179 
180     /**
181      * Constructor.
182      *
183      * @param initialHardIron initial hard-iron to find a solution.
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 LMedSRobustKnownPositionAndInstantMagnetometerCalibrator(
191             final Matrix initialHardIron, final Matrix initialMm) {
192         super(initialHardIron, initialMm);
193     }
194 
195     /**
196      * Constructor.
197      *
198      * @param position position where body magnetic flux density measurements
199      *                 have been taken.
200      */
201     public LMedSRobustKnownPositionAndInstantMagnetometerCalibrator(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 LMedSRobustKnownPositionAndInstantMagnetometerCalibrator(
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 LMedSRobustKnownPositionAndInstantMagnetometerCalibrator(
234             final NEDPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
235             final RobustKnownPositionAndInstantMagnetometerCalibratorListener 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 LMedSRobustKnownPositionAndInstantMagnetometerCalibrator(
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 LMedSRobustKnownPositionAndInstantMagnetometerCalibrator(
273             final NEDPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
274             final boolean commonAxisUsed, final RobustKnownPositionAndInstantMagnetometerCalibratorListener listener) {
275         super(position, measurements, commonAxisUsed, listener);
276     }
277 
278     /**
279      * Constructor.
280      *
281      * @param position        position where body magnetic flux density measurements
282      *                        have been taken.
283      * @param measurements    collection of body magnetic flux density
284      *                        measurements with standard deviation of
285      *                        magnetometer measurements taken at the same
286      *                        position with zero velocity and unknown different
287      *                        orientations.
288      * @param initialHardIron initial hard-iron to find a solution.
289      * @throws IllegalArgumentException if provided hard-iron array does
290      *                                  not have length 3.
291      */
292     public LMedSRobustKnownPositionAndInstantMagnetometerCalibrator(
293             final NEDPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
294             final double[] initialHardIron) {
295         super(position, measurements, initialHardIron);
296     }
297 
298     /**
299      * Constructor.
300      *
301      * @param position        position where body magnetic flux density measurements
302      *                        have been taken.
303      * @param measurements    collection of body magnetic flux density
304      *                        measurements with standard deviation of
305      *                        magnetometer measurements taken at the same
306      *                        position with zero velocity and unknown different
307      *                        orientations.
308      * @param initialHardIron initial hard-iron to find a solution.
309      * @param listener        listener to handle events raised by this calibrator.
310      * @throws IllegalArgumentException if provided hard-iron array does
311      *                                  not have length 3.
312      */
313     public LMedSRobustKnownPositionAndInstantMagnetometerCalibrator(
314             final NEDPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
315             final double[] initialHardIron,
316             final RobustKnownPositionAndInstantMagnetometerCalibratorListener listener) {
317         super(position, measurements, initialHardIron, listener);
318     }
319 
320     /**
321      * Constructor.
322      *
323      * @param position        position where body magnetic flux density measurements
324      *                        have been taken.
325      * @param measurements    collection of body magnetic flux density
326      *                        measurements with standard deviation of
327      *                        magnetometer measurements taken at the same
328      *                        position with zero velocity and unknown different
329      *                        orientations.
330      * @param commonAxisUsed  indicates whether z-axis is assumed to be common
331      *                        for the accelerometer, gyroscope and magnetometer.
332      * @param initialHardIron initial hard-iron to find a solution.
333      * @throws IllegalArgumentException if provided hard-iron array does
334      *                                  not have length 3.
335      */
336     public LMedSRobustKnownPositionAndInstantMagnetometerCalibrator(
337             final NEDPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
338             final boolean commonAxisUsed, final double[] initialHardIron) {
339         super(position, measurements, commonAxisUsed, initialHardIron);
340     }
341 
342     /**
343      * Constructor.
344      *
345      * @param position        position where body magnetic flux density measurements
346      *                        have been taken.
347      * @param measurements    collection of body magnetic flux density
348      *                        measurements with standard deviation of
349      *                        magnetometer measurements taken at the same
350      *                        position with zero velocity and unknown different
351      *                        orientations.
352      * @param commonAxisUsed  indicates whether z-axis is assumed to be common
353      *                        for the accelerometer, gyroscope and magnetometer.
354      * @param initialHardIron initial hard-iron to find a solution.
355      * @param listener        listener to handle events raised by this calibrator.
356      * @throws IllegalArgumentException if provided hard-iron array does
357      *                                  not have length 3.
358      */
359     public LMedSRobustKnownPositionAndInstantMagnetometerCalibrator(
360             final NEDPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
361             final boolean commonAxisUsed, final double[] initialHardIron,
362             final RobustKnownPositionAndInstantMagnetometerCalibratorListener listener) {
363         super(position, measurements, commonAxisUsed, initialHardIron, listener);
364     }
365 
366     /**
367      * Constructor.
368      *
369      * @param position        position where body magnetic flux density measurements
370      *                        have been taken.
371      * @param measurements    collection of body magnetic flux density
372      *                        measurements with standard deviation of
373      *                        magnetometer measurements taken at the same
374      *                        position with zero velocity and unknown different
375      *                        orientations.
376      * @param initialHardIron initial hard-iron to find a solution.
377      * @throws IllegalArgumentException if provided hard-iron matrix is not
378      *                                  3x1.
379      */
380     public LMedSRobustKnownPositionAndInstantMagnetometerCalibrator(
381             final NEDPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
382             final Matrix initialHardIron) {
383         super(position, measurements, initialHardIron);
384     }
385 
386     /**
387      * Constructor.
388      *
389      * @param position        position where body magnetic flux density measurements
390      *                        have been taken.
391      * @param measurements    collection of body magnetic flux density
392      *                        measurements with standard deviation of
393      *                        magnetometer measurements taken at the same
394      *                        position with zero velocity and unknown different
395      *                        orientations.
396      * @param initialHardIron initial hard-iron to find a solution.
397      * @param listener        listener to handle events raised by this calibrator.
398      * @throws IllegalArgumentException if provided hard-iron matrix is not
399      *                                  3x1.
400      */
401     public LMedSRobustKnownPositionAndInstantMagnetometerCalibrator(
402             final NEDPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
403             final Matrix initialHardIron, final RobustKnownPositionAndInstantMagnetometerCalibratorListener listener) {
404         super(position, measurements, initialHardIron, listener);
405     }
406 
407     /**
408      * Constructor.
409      *
410      * @param position        position where body magnetic flux density measurements
411      *                        have been taken.
412      * @param measurements    collection of body magnetic flux density
413      *                        measurements with standard deviation of
414      *                        magnetometer measurements taken at the same
415      *                        position with zero velocity and unknown different
416      *                        orientations.
417      * @param commonAxisUsed  indicates whether z-axis is assumed to be common
418      *                        for the accelerometer, gyroscope and magnetometer.
419      * @param initialHardIron initial hard-iron to find a solution.
420      * @throws IllegalArgumentException if provided hard-iron matrix is not
421      *                                  3x1.
422      */
423     public LMedSRobustKnownPositionAndInstantMagnetometerCalibrator(
424             final NEDPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
425             final boolean commonAxisUsed, final Matrix initialHardIron) {
426         super(position, measurements, commonAxisUsed, initialHardIron);
427     }
428 
429     /**
430      * Constructor.
431      *
432      * @param position        position where body magnetic flux density measurements
433      *                        have been taken.
434      * @param measurements    collection of body magnetic flux density
435      *                        measurements with standard deviation of
436      *                        magnetometer measurements taken at the same
437      *                        position with zero velocity and unknown different
438      *                        orientations.
439      * @param commonAxisUsed  indicates whether z-axis is assumed to be common
440      *                        for the accelerometer, gyroscope and magnetometer.
441      * @param initialHardIron initial hard-iron to find a solution.
442      * @param listener        listener to handle events raised by this calibrator.
443      * @throws IllegalArgumentException if provided hard-iron matrix is not
444      *                                  3x1.
445      */
446     public LMedSRobustKnownPositionAndInstantMagnetometerCalibrator(
447             final NEDPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
448             final boolean commonAxisUsed, final Matrix initialHardIron,
449             final RobustKnownPositionAndInstantMagnetometerCalibratorListener listener) {
450         super(position, measurements, commonAxisUsed, initialHardIron, listener);
451     }
452 
453     /**
454      * Constructor.
455      *
456      * @param position        position where body magnetic flux density measurements
457      *                        have been taken.
458      * @param measurements    collection of body magnetic flux density
459      *                        measurements with standard deviation of
460      *                        magnetometer measurements taken at the same
461      *                        position with zero velocity and unknown different
462      *                        orientations.
463      * @param initialHardIron initial hard-iron to find a solution.
464      * @param initialMm       initial soft-iron matrix containing scale factors
465      *                        and cross coupling errors.
466      * @throws IllegalArgumentException if provided hard-iron matrix is not
467      *                                  3x1 or if soft-iron matrix is not
468      *                                  3x3.
469      */
470     public LMedSRobustKnownPositionAndInstantMagnetometerCalibrator(
471             final NEDPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
472             final Matrix initialHardIron, final Matrix initialMm) {
473         super(position, measurements, initialHardIron, initialMm);
474     }
475 
476     /**
477      * Constructor.
478      *
479      * @param position        position where body magnetic flux density measurements
480      *                        have been taken.
481      * @param measurements    collection of body magnetic flux density
482      *                        measurements with standard deviation of
483      *                        magnetometer measurements taken at the same
484      *                        position with zero velocity and unknown different
485      *                        orientations.
486      * @param initialHardIron initial hard-iron to find a solution.
487      * @param initialMm       initial soft-iron matrix containing scale factors
488      *                        and cross coupling errors.
489      * @param listener        listener to handle events raised by this calibrator.
490      * @throws IllegalArgumentException if provided hard-iron matrix is not
491      *                                  3x1 or if soft-iron matrix is not
492      *                                  3x3.
493      */
494     public LMedSRobustKnownPositionAndInstantMagnetometerCalibrator(
495             final NEDPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
496             final Matrix initialHardIron, final Matrix initialMm,
497             final RobustKnownPositionAndInstantMagnetometerCalibratorListener listener) {
498         super(position, measurements, initialHardIron, initialMm, listener);
499     }
500 
501     /**
502      * Constructor.
503      *
504      * @param position        position where body magnetic flux density measurements
505      *                        have been taken.
506      * @param measurements    collection of body magnetic flux density
507      *                        measurements with standard deviation of
508      *                        magnetometer measurements taken at the same
509      *                        position with zero velocity and unknown different
510      *                        orientations.
511      * @param commonAxisUsed  indicates whether z-axis is assumed to be common
512      *                        for the accelerometer, gyroscope and magnetometer.
513      * @param initialHardIron initial hard-iron to find a solution.
514      * @param initialMm       initial soft-iron matrix containing scale factors
515      *                        and cross coupling errors.
516      * @throws IllegalArgumentException if provided hard-iron matrix is not
517      *                                  3x1 or if soft-iron matrix is not
518      *                                  3x3.
519      */
520     public LMedSRobustKnownPositionAndInstantMagnetometerCalibrator(
521             final NEDPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
522             final boolean commonAxisUsed, final Matrix initialHardIron, final Matrix initialMm) {
523         super(position, measurements, commonAxisUsed, initialHardIron, initialMm);
524     }
525 
526     /**
527      * Constructor.
528      *
529      * @param position        position where body magnetic flux density measurements
530      *                        have been taken.
531      * @param measurements    collection of body magnetic flux density
532      *                        measurements with standard deviation of
533      *                        magnetometer measurements taken at the same
534      *                        position with zero velocity and unknown different
535      *                        orientations.
536      * @param commonAxisUsed  indicates whether z-axis is assumed to be common
537      *                        for the accelerometer, gyroscope and magnetometer.
538      * @param initialHardIron initial hard-iron to find a solution.
539      * @param initialMm       initial soft-iron matrix containing scale factors
540      *                        and cross coupling errors.
541      * @param listener        listener to handle events raised by this calibrator.
542      * @throws IllegalArgumentException if provided hard-iron matrix is not
543      *                                  3x1 or if soft-iron matrix is not
544      *                                  3x3.
545      */
546     public LMedSRobustKnownPositionAndInstantMagnetometerCalibrator(
547             final NEDPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
548             final boolean commonAxisUsed, final Matrix initialHardIron, final Matrix initialMm,
549             final RobustKnownPositionAndInstantMagnetometerCalibratorListener listener) {
550         super(position, measurements, commonAxisUsed, initialHardIron, initialMm, listener);
551     }
552 
553     /**
554      * Constructor.
555      *
556      * @param position position where body magnetic flux density measurements
557      *                 have been taken.
558      */
559     public LMedSRobustKnownPositionAndInstantMagnetometerCalibrator(final ECEFPosition position) {
560         super(position);
561     }
562 
563     /**
564      * Constructor.
565      *
566      * @param position     position where body magnetic flux density measurements
567      *                     have been taken.
568      * @param measurements collection of body magnetic flux density
569      *                     measurements with standard deviation of
570      *                     magnetometer measurements taken at the same
571      *                     position with zero velocity and unknown different
572      *                     orientations.
573      */
574     public LMedSRobustKnownPositionAndInstantMagnetometerCalibrator(
575             final ECEFPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements) {
576         super(position, measurements);
577     }
578 
579     /**
580      * Constructor.
581      *
582      * @param position     position where body magnetic flux density measurements
583      *                     have been taken.
584      * @param measurements collection of body magnetic flux density
585      *                     measurements with standard deviation of
586      *                     magnetometer measurements taken at the same
587      *                     position with zero velocity and unknown different
588      *                     orientations.
589      * @param listener     listener to handle events raised by this calibrator.
590      */
591     public LMedSRobustKnownPositionAndInstantMagnetometerCalibrator(
592             final ECEFPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
593             final RobustKnownPositionAndInstantMagnetometerCalibratorListener listener) {
594         super(position, measurements, listener);
595     }
596 
597     /**
598      * Constructor.
599      *
600      * @param position       position where body magnetic flux density measurements
601      *                       have been taken.
602      * @param measurements   collection of body magnetic flux density
603      *                       measurements with standard deviation of
604      *                       magnetometer measurements taken at the same
605      *                       position with zero velocity and unknown different
606      *                       orientations.
607      * @param commonAxisUsed indicates whether z-axis is assumed to be common
608      *                       for the accelerometer, gyroscope and magnetometer.
609      */
610     public LMedSRobustKnownPositionAndInstantMagnetometerCalibrator(
611             final ECEFPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
612             final boolean commonAxisUsed) {
613         super(position, measurements, commonAxisUsed);
614     }
615 
616     /**
617      * Constructor.
618      *
619      * @param position       position where body magnetic flux density measurements
620      *                       have been taken.
621      * @param measurements   collection of body magnetic flux density
622      *                       measurements with standard deviation of
623      *                       magnetometer measurements taken at the same
624      *                       position with zero velocity and unknown different
625      *                       orientations.
626      * @param commonAxisUsed indicates whether z-axis is assumed to be common
627      *                       for the accelerometer, gyroscope and magnetometer.
628      * @param listener       listener to handle events raised by this calibrator.
629      */
630     public LMedSRobustKnownPositionAndInstantMagnetometerCalibrator(
631             final ECEFPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
632             final boolean commonAxisUsed, final RobustKnownPositionAndInstantMagnetometerCalibratorListener listener) {
633         super(position, measurements, commonAxisUsed, listener);
634     }
635 
636     /**
637      * Constructor.
638      *
639      * @param position        position where body magnetic flux density measurements
640      *                        have been taken.
641      * @param measurements    collection of body magnetic flux density
642      *                        measurements with standard deviation of
643      *                        magnetometer measurements taken at the same
644      *                        position with zero velocity and unknown different
645      *                        orientations.
646      * @param initialHardIron initial hard-iron to find a solution.
647      * @throws IllegalArgumentException if provided hard-iron array does
648      *                                  not have length 3.
649      */
650     public LMedSRobustKnownPositionAndInstantMagnetometerCalibrator(
651             final ECEFPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
652             final double[] initialHardIron) {
653         super(position, measurements, initialHardIron);
654     }
655 
656     /**
657      * Constructor.
658      *
659      * @param position        position where body magnetic flux density measurements
660      *                        have been taken.
661      * @param measurements    collection of body magnetic flux density
662      *                        measurements with standard deviation of
663      *                        magnetometer measurements taken at the same
664      *                        position with zero velocity and unknown different
665      *                        orientations.
666      * @param initialHardIron initial hard-iron to find a solution.
667      * @param listener        listener to handle events raised by this calibrator.
668      * @throws IllegalArgumentException if provided hard-iron array does
669      *                                  not have length 3.
670      */
671     public LMedSRobustKnownPositionAndInstantMagnetometerCalibrator(
672             final ECEFPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
673             final double[] initialHardIron,
674             final RobustKnownPositionAndInstantMagnetometerCalibratorListener listener) {
675         super(position, measurements, initialHardIron, listener);
676     }
677 
678     /**
679      * Constructor.
680      *
681      * @param position        position where body magnetic flux density measurements
682      *                        have been taken.
683      * @param measurements    collection of body magnetic flux density
684      *                        measurements with standard deviation of
685      *                        magnetometer measurements taken at the same
686      *                        position with zero velocity and unknown different
687      *                        orientations.
688      * @param commonAxisUsed  indicates whether z-axis is assumed to be common
689      *                        for the accelerometer, gyroscope and magnetometer.
690      * @param initialHardIron initial hard-iron to find a solution.
691      * @throws IllegalArgumentException if provided hard-iron array does
692      *                                  not have length 3.
693      */
694     public LMedSRobustKnownPositionAndInstantMagnetometerCalibrator(
695             final ECEFPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
696             final boolean commonAxisUsed, final double[] initialHardIron) {
697         super(position, measurements, commonAxisUsed, initialHardIron);
698     }
699 
700     /**
701      * Constructor.
702      *
703      * @param position        position where body magnetic flux density measurements
704      *                        have been taken.
705      * @param measurements    collection of body magnetic flux density
706      *                        measurements with standard deviation of
707      *                        magnetometer measurements taken at the same
708      *                        position with zero velocity and unknown different
709      *                        orientations.
710      * @param commonAxisUsed  indicates whether z-axis is assumed to be common
711      *                        for the accelerometer, gyroscope and magnetometer.
712      * @param initialHardIron initial hard-iron to find a solution.
713      * @param listener        listener to handle events raised by this calibrator.
714      * @throws IllegalArgumentException if provided hard-iron array does
715      *                                  not have length 3.
716      */
717     public LMedSRobustKnownPositionAndInstantMagnetometerCalibrator(
718             final ECEFPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
719             final boolean commonAxisUsed, final double[] initialHardIron,
720             final RobustKnownPositionAndInstantMagnetometerCalibratorListener listener) {
721         super(position, measurements, commonAxisUsed, initialHardIron, listener);
722     }
723 
724     /**
725      * Constructor.
726      *
727      * @param position        position where body magnetic flux density measurements
728      *                        have been taken.
729      * @param measurements    collection of body magnetic flux density
730      *                        measurements with standard deviation of
731      *                        magnetometer measurements taken at the same
732      *                        position with zero velocity and unknown different
733      *                        orientations.
734      * @param initialHardIron initial hard-iron to find a solution.
735      * @throws IllegalArgumentException if provided hard-iron matrix is not
736      *                                  3x1.
737      */
738     public LMedSRobustKnownPositionAndInstantMagnetometerCalibrator(
739             final ECEFPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
740             final Matrix initialHardIron) {
741         super(position, measurements, initialHardIron);
742     }
743 
744     /**
745      * Constructor.
746      *
747      * @param position        position where body magnetic flux density measurements
748      *                        have been taken.
749      * @param measurements    collection of body magnetic flux density
750      *                        measurements with standard deviation of
751      *                        magnetometer measurements taken at the same
752      *                        position with zero velocity and unknown different
753      *                        orientations.
754      * @param initialHardIron initial hard-iron to find a solution.
755      * @param listener        listener to handle events raised by this calibrator.
756      * @throws IllegalArgumentException if provided hard-iron matrix is not
757      *                                  3x1.
758      */
759     public LMedSRobustKnownPositionAndInstantMagnetometerCalibrator(
760             final ECEFPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
761             final Matrix initialHardIron, final RobustKnownPositionAndInstantMagnetometerCalibratorListener listener) {
762         super(position, measurements, initialHardIron, listener);
763     }
764 
765     /**
766      * Constructor.
767      *
768      * @param position        position where body magnetic flux density measurements
769      *                        have been taken.
770      * @param measurements    collection of body magnetic flux density
771      *                        measurements with standard deviation of
772      *                        magnetometer measurements taken at the same
773      *                        position with zero velocity and unknown different
774      *                        orientations.
775      * @param commonAxisUsed  indicates whether z-axis is assumed to be common
776      *                        for the accelerometer, gyroscope and magnetometer.
777      * @param initialHardIron initial hard-iron to find a solution.
778      * @throws IllegalArgumentException if provided hard-iron matrix is not
779      *                                  3x1.
780      */
781     public LMedSRobustKnownPositionAndInstantMagnetometerCalibrator(
782             final ECEFPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
783             final boolean commonAxisUsed, final Matrix initialHardIron) {
784         super(position, measurements, commonAxisUsed, initialHardIron);
785     }
786 
787     /**
788      * Constructor.
789      *
790      * @param position        position where body magnetic flux density measurements
791      *                        have been taken.
792      * @param measurements    collection of body magnetic flux density
793      *                        measurements with standard deviation of
794      *                        magnetometer measurements taken at the same
795      *                        position with zero velocity and unknown different
796      *                        orientations.
797      * @param commonAxisUsed  indicates whether z-axis is assumed to be common
798      *                        for the accelerometer, gyroscope and magnetometer.
799      * @param initialHardIron initial hard-iron to find a solution.
800      * @param listener        listener to handle events raised by this calibrator.
801      * @throws IllegalArgumentException if provided hard-iron matrix is not
802      *                                  3x1.
803      */
804     public LMedSRobustKnownPositionAndInstantMagnetometerCalibrator(
805             final ECEFPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
806             final boolean commonAxisUsed, final Matrix initialHardIron,
807             final RobustKnownPositionAndInstantMagnetometerCalibratorListener listener) {
808         super(position, measurements, commonAxisUsed, initialHardIron, listener);
809     }
810 
811     /**
812      * Constructor.
813      *
814      * @param position        position where body magnetic flux density measurements
815      *                        have been taken.
816      * @param measurements    collection of body magnetic flux density
817      *                        measurements with standard deviation of
818      *                        magnetometer measurements taken at the same
819      *                        position with zero velocity and unknown different
820      *                        orientations.
821      * @param initialHardIron initial hard-iron to find a solution.
822      * @param initialMm       initial soft-iron matrix containing scale factors
823      *                        and cross coupling errors.
824      * @throws IllegalArgumentException if provided hard-iron matrix is not
825      *                                  3x1 or if soft-iron matrix is not
826      *                                  3x3.
827      */
828     public LMedSRobustKnownPositionAndInstantMagnetometerCalibrator(
829             final ECEFPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
830             final Matrix initialHardIron, final Matrix initialMm) {
831         super(position, measurements, initialHardIron, initialMm);
832     }
833 
834     /**
835      * Constructor.
836      *
837      * @param position        position where body magnetic flux density measurements
838      *                        have been taken.
839      * @param measurements    collection of body magnetic flux density
840      *                        measurements with standard deviation of
841      *                        magnetometer measurements taken at the same
842      *                        position with zero velocity and unknown different
843      *                        orientations.
844      * @param initialHardIron initial hard-iron to find a solution.
845      * @param initialMm       initial soft-iron matrix containing scale factors
846      *                        and cross coupling errors.
847      * @param listener        listener to handle events raised by this calibrator.
848      * @throws IllegalArgumentException if provided hard-iron matrix is not
849      *                                  3x1 or if soft-iron matrix is not
850      *                                  3x3.
851      */
852     public LMedSRobustKnownPositionAndInstantMagnetometerCalibrator(
853             final ECEFPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
854             final Matrix initialHardIron, final Matrix initialMm,
855             final RobustKnownPositionAndInstantMagnetometerCalibratorListener listener) {
856         super(position, measurements, initialHardIron, initialMm, listener);
857     }
858 
859     /**
860      * Constructor.
861      *
862      * @param position        position where body magnetic flux density measurements
863      *                        have been taken.
864      * @param measurements    collection of body magnetic flux density
865      *                        measurements with standard deviation of
866      *                        magnetometer measurements taken at the same
867      *                        position with zero velocity and unknown different
868      *                        orientations.
869      * @param commonAxisUsed  indicates whether z-axis is assumed to be common
870      *                        for the accelerometer, gyroscope and magnetometer.
871      * @param initialHardIron initial hard-iron to find a solution.
872      * @param initialMm       initial soft-iron matrix containing scale factors
873      *                        and cross coupling errors.
874      * @throws IllegalArgumentException if provided hard-iron matrix is not
875      *                                  3x1 or if soft-iron matrix is not
876      *                                  3x3.
877      */
878     public LMedSRobustKnownPositionAndInstantMagnetometerCalibrator(
879             final ECEFPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
880             final boolean commonAxisUsed, final Matrix initialHardIron, final Matrix initialMm) {
881         super(position, measurements, commonAxisUsed, initialHardIron, initialMm);
882     }
883 
884     /**
885      * Constructor.
886      *
887      * @param position        position where body magnetic flux density measurements
888      *                        have been taken.
889      * @param measurements    collection of body magnetic flux density
890      *                        measurements with standard deviation of
891      *                        magnetometer measurements taken at the same
892      *                        position with zero velocity and unknown different
893      *                        orientations.
894      * @param commonAxisUsed  indicates whether z-axis is assumed to be common
895      *                        for the accelerometer, gyroscope and magnetometer.
896      * @param initialHardIron initial hard-iron to find a solution.
897      * @param initialMm       initial soft-iron matrix containing scale factors
898      *                        and cross coupling errors.
899      * @param listener        listener to handle events raised by this calibrator.
900      * @throws IllegalArgumentException if provided hard-iron matrix is not
901      *                                  3x1 or if soft-iron matrix is not
902      *                                  3x3.
903      */
904     public LMedSRobustKnownPositionAndInstantMagnetometerCalibrator(
905             final ECEFPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
906             final boolean commonAxisUsed, final Matrix initialHardIron, final Matrix initialMm,
907             final RobustKnownPositionAndInstantMagnetometerCalibratorListener listener) {
908         super(position, measurements, commonAxisUsed, initialHardIron, initialMm, listener);
909     }
910 
911     /**
912      * Returns threshold to be used to keep the algorithm iterating in case that
913      * best estimated threshold using median of residuals is not small enough.
914      * Once a solution is found that generates a threshold below this value, the
915      * algorithm will stop.
916      * The stop threshold can be used to prevent the LMedS algorithm to iterate
917      * too many times in cases where samples have a very similar accuracy.
918      * For instance, in cases where proportion of outliers is very small (close
919      * to 0%), and samples are very accurate (i.e. 1e-6), the algorithm would
920      * iterate for a long time trying to find the best solution when indeed
921      * there is no need to do that if a reasonable threshold has already been
922      * reached.
923      * Because of this behaviour the stop threshold can be set to a value much
924      * lower than the one typically used in RANSAC, and yet the algorithm could
925      * still produce even smaller thresholds in estimated results.
926      *
927      * @return stop threshold to stop the algorithm prematurely when a certain
928      * accuracy has been reached.
929      */
930     public double getStopThreshold() {
931         return stopThreshold;
932     }
933 
934     /**
935      * Sets threshold to be used to keep the algorithm iterating in case that
936      * best estimated threshold using median of residuals is not small enough.
937      * Once a solution is found that generates a threshold below this value,
938      * the algorithm will stop.
939      * The stop threshold can be used to prevent the LMedS algorithm to iterate
940      * too many times in cases where samples have a very similar accuracy.
941      * For instance, in cases where proportion of outliers is very small (close
942      * to 0%), and samples are very accurate (i.e. 1e-6), the algorithm would
943      * iterate for a long time trying to find the best solution when indeed
944      * there is no need to do that if a reasonable threshold has already been
945      * reached.
946      * Because of this behaviour the stop threshold can be set to a value much
947      * lower than the one typically used in RANSAC, and yet the algorithm could
948      * still produce even smaller thresholds in estimated results.
949      *
950      * @param stopThreshold stop threshold to stop the algorithm prematurely
951      *                      when a certain accuracy has been reached.
952      * @throws IllegalArgumentException if provided value is zero or negative.
953      * @throws LockedException          if calibrator is currently running.
954      */
955     public void setStopThreshold(final double stopThreshold) throws LockedException {
956         if (running) {
957             throw new LockedException();
958         }
959         if (stopThreshold <= MIN_STOP_THRESHOLD) {
960             throw new IllegalArgumentException();
961         }
962 
963         this.stopThreshold = stopThreshold;
964     }
965 
966     /**
967      * Estimates magnetometer calibration parameters containing hard-iron
968      * bias and soft-iron scale factors and cross-coupling errors.
969      *
970      * @throws LockedException      if calibrator is currently running.
971      * @throws NotReadyException    if calibrator is not ready.
972      * @throws CalibrationException if estimation fails for numerical reasons.
973      */
974     @SuppressWarnings("DuplicatedCode")
975     @Override
976     public void calibrate() throws LockedException, NotReadyException, CalibrationException {
977         if (running) {
978             throw new LockedException();
979         }
980         if (!isReady()) {
981             throw new NotReadyException();
982         }
983 
984         final var innerEstimator = new LMedSRobustEstimator<>(new LMedSRobustEstimatorListener<PreliminaryResult>() {
985             @Override
986             public int getTotalSamples() {
987                 return measurements.size();
988             }
989 
990             @Override
991             public int getSubsetSize() {
992                 return preliminarySubsetSize;
993             }
994 
995             @Override
996             public void estimatePreliminarSolutions(
997                     final int[] samplesIndices, final List<PreliminaryResult> solutions) {
998                 computePreliminarySolutions(samplesIndices, solutions);
999             }
1000 
1001             @Override
1002             public double computeResidual(final PreliminaryResult currentEstimation, final int i) {
1003                 return computeError(measurements.get(i), currentEstimation);
1004             }
1005 
1006             @Override
1007             public boolean isReady() {
1008                 return LMedSRobustKnownPositionAndInstantMagnetometerCalibrator.this.isReady();
1009             }
1010 
1011             @Override
1012             public void onEstimateStart(final RobustEstimator<PreliminaryResult> estimator) {
1013                 // no action needed
1014             }
1015 
1016             @Override
1017             public void onEstimateEnd(final RobustEstimator<PreliminaryResult> estimator) {
1018                 // no action needed
1019             }
1020 
1021             @Override
1022             public void onEstimateNextIteration(
1023                     final RobustEstimator<PreliminaryResult> estimator, final int iteration) {
1024                 if (listener != null) {
1025                     listener.onCalibrateNextIteration(
1026                             LMedSRobustKnownPositionAndInstantMagnetometerCalibrator.this, iteration);
1027                 }
1028             }
1029 
1030             @Override
1031             public void onEstimateProgressChange(
1032                     final RobustEstimator<PreliminaryResult> estimator, final float progress) {
1033                 if (listener != null) {
1034                     listener.onCalibrateProgressChange(
1035                             LMedSRobustKnownPositionAndInstantMagnetometerCalibrator.this, progress);
1036                 }
1037             }
1038         });
1039 
1040         try {
1041             running = true;
1042 
1043             if (listener != null) {
1044                 listener.onCalibrateStart(this);
1045             }
1046 
1047             inliersData = null;
1048 
1049             initialize();
1050 
1051             innerEstimator.setConfidence(confidence);
1052             innerEstimator.setMaxIterations(maxIterations);
1053             innerEstimator.setProgressDelta(progressDelta);
1054             innerEstimator.setStopThreshold(stopThreshold);
1055             final var preliminaryResult = innerEstimator.estimate();
1056             inliersData = innerEstimator.getInliersData();
1057 
1058             attemptRefine(preliminaryResult);
1059 
1060             if (listener != null) {
1061                 listener.onCalibrateEnd(this);
1062             }
1063 
1064         } catch (final com.irurueta.numerical.LockedException e) {
1065             throw new LockedException(e);
1066         } catch (final com.irurueta.numerical.NotReadyException e) {
1067             throw new NotReadyException(e);
1068         } catch (final RobustEstimatorException | IOException e) {
1069             throw new CalibrationException(e);
1070         } finally {
1071             running = false;
1072         }
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 }