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.accelerometer;
17
18 import com.irurueta.navigation.LockedException;
19 import com.irurueta.navigation.NotReadyException;
20 import com.irurueta.navigation.inertial.calibration.CalibrationException;
21 import com.irurueta.navigation.inertial.calibration.StandardDeviationFrameBodyKinematics;
22 import com.irurueta.numerical.robust.PROSACRobustEstimator;
23 import com.irurueta.numerical.robust.PROSACRobustEstimatorListener;
24 import com.irurueta.numerical.robust.RobustEstimator;
25 import com.irurueta.numerical.robust.RobustEstimatorException;
26 import com.irurueta.numerical.robust.RobustEstimatorMethod;
27
28 import java.util.List;
29
30 /**
31 * Robustly estimates accelerometer biases, cross couplings and scaling factors
32 * using a PROSAC algorithm to discard outliers.
33 * <p>
34 * To use this calibrator at least 4 measurements at different known frames must
35 * be provided. In other words, accelerometer samples must be obtained at 4
36 * different positions, orientations and velocities (although typically velocities are
37 * always zero).
38 * <p>
39 * Measured specific force is assumed to follow the model shown below:
40 * <pre>
41 * fmeas = ba + (I + Ma) * ftrue + w
42 * </pre>
43 * Where:
44 * - fmeas is the measured specific force. This is a 3x1 vector.
45 * - ba is accelerometer bias. Ideally, on a perfect accelerometer, this should be a
46 * 3x1 zero vector.
47 * - I is the 3x3 identity matrix.
48 * - Ma is the 3x3 matrix containing cross-couplings and scaling factors. Ideally, on
49 * a perfect accelerometer, this should be a 3x3 zero matrix.
50 * - ftrue is ground-truth specific force.
51 * - w is measurement noise.
52 */
53 public class PROSACRobustKnownFrameAccelerometerCalibrator extends RobustKnownFrameAccelerometerCalibrator {
54
55 /**
56 * Constant defining default threshold to determine whether samples are inliers or not.
57 */
58 public static final double DEFAULT_THRESHOLD = 1e-2;
59
60 /**
61 * Minimum value that can be set as threshold.
62 * Threshold must be strictly greater than 0.0.
63 */
64 public static final double MIN_THRESHOLD = 0.0;
65
66 /**
67 * Indicates that by default inliers will only be computed but not kept.
68 */
69 public static final boolean DEFAULT_COMPUTE_AND_KEEP_INLIERS = false;
70
71 /**
72 * Indicates that by default residuals will only be computed but not kept.
73 */
74 public static final boolean DEFAULT_COMPUTE_AND_KEEP_RESIDUALS = false;
75
76 /**
77 * Threshold to determine whether samples are inliers or not when testing possible solutions.
78 * The threshold refers to the amount of error on distance between estimated position and
79 * distances provided for each sample.
80 */
81 private double threshold = DEFAULT_THRESHOLD;
82
83 /**
84 * Indicates whether inliers must be computed and kept.
85 */
86 private boolean computeAndKeepInliers = DEFAULT_COMPUTE_AND_KEEP_INLIERS;
87
88 /**
89 * Indicates whether residuals must be computed and kept.
90 */
91 private boolean computeAndKeepResiduals = DEFAULT_COMPUTE_AND_KEEP_RESIDUALS;
92
93 /**
94 * Quality scores corresponding to each provided sample.
95 * The larger the score value the better the quality of the sample.
96 */
97 private double[] qualityScores;
98
99 /**
100 * Constructor.
101 */
102 public PROSACRobustKnownFrameAccelerometerCalibrator() {
103 }
104
105 /**
106 * Constructor.
107 *
108 * @param listener listener to be notified of events such as when estimation
109 * starts, ends or its progress significantly changes.
110 */
111 public PROSACRobustKnownFrameAccelerometerCalibrator(
112 final RobustKnownFrameAccelerometerCalibratorListener listener) {
113 super(listener);
114 }
115
116 /**
117 * Constructor.
118 *
119 * @param measurements list of body kinematics measurements with standard
120 * deviations taken at different frames (positions, orientations
121 * and velocities).
122 */
123 public PROSACRobustKnownFrameAccelerometerCalibrator(
124 final List<StandardDeviationFrameBodyKinematics> measurements) {
125 super(measurements);
126 }
127
128 /**
129 * Constructor.
130 *
131 * @param measurements list of body kinematics measurements with standard
132 * deviations taken at different frames (positions, orientations
133 * and velocities).
134 * @param listener listener to handle events raised by this calibrator.
135 */
136 public PROSACRobustKnownFrameAccelerometerCalibrator(
137 final List<StandardDeviationFrameBodyKinematics> measurements,
138 final RobustKnownFrameAccelerometerCalibratorListener listener) {
139 super(measurements, listener);
140 }
141
142 /**
143 * Constructor.
144 *
145 * @param commonAxisUsed indicates whether z-axis is assumed to be common for
146 * accelerometer and gyroscope.
147 */
148 public PROSACRobustKnownFrameAccelerometerCalibrator(final boolean commonAxisUsed) {
149 super(commonAxisUsed);
150 }
151
152 /**
153 * Constructor.
154 *
155 * @param commonAxisUsed indicates whether z-axis is assumed to be common for
156 * accelerometer and gyroscope.
157 * @param listener listener to handle events raised by this calibrator.
158 */
159 public PROSACRobustKnownFrameAccelerometerCalibrator(
160 final boolean commonAxisUsed, final RobustKnownFrameAccelerometerCalibratorListener listener) {
161 super(commonAxisUsed, listener);
162 }
163
164 /**
165 * Constructor.
166 *
167 * @param measurements list of body kinematics measurements with standard
168 * deviations taken at different frames (positions, orientations
169 * and velocities).
170 * @param commonAxisUsed indicates whether z-axis is assumed to be common for
171 * accelerometer and gyroscope.
172 */
173 public PROSACRobustKnownFrameAccelerometerCalibrator(
174 final List<StandardDeviationFrameBodyKinematics> measurements, final boolean commonAxisUsed) {
175 super(measurements, commonAxisUsed);
176 }
177
178 /**
179 * Constructor.
180 *
181 * @param measurements list of body kinematics measurements with standard
182 * deviations taken at different frames (positions, orientations
183 * and velocities).
184 * @param commonAxisUsed indicates whether z-axis is assumed to be common for
185 * accelerometer and gyroscope.
186 * @param listener listener to handle events raised by this calibrator.
187 */
188 public PROSACRobustKnownFrameAccelerometerCalibrator(
189 final List<StandardDeviationFrameBodyKinematics> measurements, final boolean commonAxisUsed,
190 final RobustKnownFrameAccelerometerCalibratorListener listener) {
191 super(measurements, commonAxisUsed, listener);
192 }
193
194 /**
195 * Constructor.
196 *
197 * @param qualityScores quality scores corresponding to each provided
198 * measurement. The larger the score value the better
199 * the quality of the sample.
200 * @throws IllegalArgumentException if provided quality scores length
201 * is smaller than 4 samples.
202 */
203 public PROSACRobustKnownFrameAccelerometerCalibrator(final double[] qualityScores) {
204 internalSetQualityScores(qualityScores);
205 }
206
207 /**
208 * Constructor.
209 *
210 * @param qualityScores quality scores corresponding to each provided
211 * measurement. The larger the score value the better
212 * the quality of the sample.
213 * @param listener listener to be notified of events such as when estimation
214 * starts, ends or its progress significantly changes.
215 * @throws IllegalArgumentException if provided quality scores length
216 * is smaller than 4 samples.
217 */
218 public PROSACRobustKnownFrameAccelerometerCalibrator(
219 final double[] qualityScores, final RobustKnownFrameAccelerometerCalibratorListener listener) {
220 super(listener);
221 internalSetQualityScores(qualityScores);
222 }
223
224 /**
225 * Constructor.
226 *
227 * @param qualityScores quality scores corresponding to each provided
228 * measurement. The larger the score value the better
229 * the quality of the sample.
230 * @param measurements list of body kinematics measurements with standard
231 * deviations taken at different frames (positions, orientations
232 * and velocities).
233 * @throws IllegalArgumentException if provided quality scores length
234 * is smaller than 4 samples.
235 */
236 public PROSACRobustKnownFrameAccelerometerCalibrator(
237 final double[] qualityScores, final List<StandardDeviationFrameBodyKinematics> measurements) {
238 super(measurements);
239 internalSetQualityScores(qualityScores);
240 }
241
242 /**
243 * Constructor.
244 *
245 * @param qualityScores quality scores corresponding to each provided
246 * measurement. The larger the score value the better
247 * the quality of the sample.
248 * @param measurements list of body kinematics measurements with standard
249 * deviations taken at different frames (positions, orientations
250 * and velocities).
251 * @param listener listener to handle events raised by this calibrator.
252 * @throws IllegalArgumentException if provided quality scores length
253 * is smaller than 4 samples.
254 */
255 public PROSACRobustKnownFrameAccelerometerCalibrator(
256 final double[] qualityScores, final List<StandardDeviationFrameBodyKinematics> measurements,
257 final RobustKnownFrameAccelerometerCalibratorListener listener) {
258 super(measurements, listener);
259 internalSetQualityScores(qualityScores);
260 }
261
262 /**
263 * Constructor.
264 *
265 * @param qualityScores quality scores corresponding to each provided
266 * measurement. The larger the score value the better
267 * the quality of the sample.
268 * @param commonAxisUsed indicates whether z-axis is assumed to be common for
269 * accelerometer and gyroscope.
270 * @throws IllegalArgumentException if provided quality scores length
271 * is smaller than 4 samples.
272 */
273 public PROSACRobustKnownFrameAccelerometerCalibrator(final double[] qualityScores, final boolean commonAxisUsed) {
274 super(commonAxisUsed);
275 internalSetQualityScores(qualityScores);
276 }
277
278 /**
279 * Constructor.
280 *
281 * @param qualityScores quality scores corresponding to each provided
282 * measurement. The larger the score value the better
283 * the quality of the sample.
284 * @param commonAxisUsed indicates whether z-axis is assumed to be common for
285 * accelerometer and gyroscope.
286 * @param listener listener to handle events raised by this calibrator.
287 * @throws IllegalArgumentException if provided quality scores length
288 * is smaller than 4 samples.
289 */
290 public PROSACRobustKnownFrameAccelerometerCalibrator(
291 final double[] qualityScores, final boolean commonAxisUsed,
292 final RobustKnownFrameAccelerometerCalibratorListener listener) {
293 super(commonAxisUsed, listener);
294 internalSetQualityScores(qualityScores);
295 }
296
297 /**
298 * Constructor.
299 *
300 * @param qualityScores quality scores corresponding to each provided
301 * measurement. The larger the score value the better
302 * the quality of the sample.
303 * @param measurements list of body kinematics measurements with standard
304 * deviations taken at different frames (positions, orientations
305 * and velocities).
306 * @param commonAxisUsed indicates whether z-axis is assumed to be common for
307 * accelerometer and gyroscope.
308 * @throws IllegalArgumentException if provided quality scores length
309 * is smaller than 4 samples.
310 */
311 public PROSACRobustKnownFrameAccelerometerCalibrator(
312 final double[] qualityScores, final List<StandardDeviationFrameBodyKinematics> measurements,
313 final boolean commonAxisUsed) {
314 super(measurements, commonAxisUsed);
315 internalSetQualityScores(qualityScores);
316 }
317
318 /**
319 * Constructor.
320 *
321 * @param qualityScores quality scores corresponding to each provided
322 * measurement. The larger the score value the better
323 * the quality of the sample.
324 * @param measurements list of body kinematics measurements with standard
325 * deviations taken at different frames (positions, orientations
326 * and velocities).
327 * @param commonAxisUsed indicates whether z-axis is assumed to be common for
328 * accelerometer and gyroscope.
329 * @param listener listener to handle events raised by this calibrator.
330 * @throws IllegalArgumentException if provided quality scores length
331 * is smaller than 4 samples.
332 */
333 public PROSACRobustKnownFrameAccelerometerCalibrator(
334 final double[] qualityScores, final List<StandardDeviationFrameBodyKinematics> measurements,
335 final boolean commonAxisUsed, final RobustKnownFrameAccelerometerCalibratorListener listener) {
336 super(measurements, commonAxisUsed, listener);
337 internalSetQualityScores(qualityScores);
338 }
339
340 /**
341 * Gets threshold to determine whether samples are inliers or not when testing possible solutions.
342 * The threshold refers to the amount of error on norm between measured specific forces and the
343 * ones generated with estimated calibration parameters provided for each sample.
344 *
345 * @return threshold to determine whether samples are inliers or not.
346 */
347 public double getThreshold() {
348 return threshold;
349 }
350
351 /**
352 * Sets threshold to determine whether samples are inliers or not when testing possible solutions.
353 * The threshold refers to the amount of error on norm between measured specific forces and the
354 * ones generated with estimated calibration parameters provided for each sample.
355 *
356 * @param threshold threshold to determine whether samples are inliers or not.
357 * @throws IllegalArgumentException if provided value is equal or less than zero.
358 * @throws LockedException if calibrator is currently running.
359 */
360 public void setThreshold(final double threshold) throws LockedException {
361 if (running) {
362 throw new LockedException();
363 }
364 if (threshold <= MIN_THRESHOLD) {
365 throw new IllegalArgumentException();
366 }
367 this.threshold = threshold;
368 }
369
370 /**
371 * Returns quality scores corresponding to each provided sample.
372 * The larger the score value the better the quality of the sample.
373 *
374 * @return quality scores corresponding to each sample.
375 */
376 @Override
377 public double[] getQualityScores() {
378 return qualityScores;
379 }
380
381 /**
382 * Sets quality scores corresponding to each provided sample.
383 * The larger the score value the better the quality of the sample.
384 *
385 * @param qualityScores quality scores corresponding to each sample.
386 * @throws IllegalArgumentException if provided quality scores length
387 * is smaller than minimum required samples.
388 * @throws LockedException if calibrator is currently running.
389 */
390 @Override
391 public void setQualityScores(final double[] qualityScores) throws LockedException {
392 if (running) {
393 throw new LockedException();
394 }
395 internalSetQualityScores(qualityScores);
396 }
397
398 /**
399 * Indicates whether calibrator is ready to find a solution.
400 *
401 * @return true if calibrator is ready, false otherwise.
402 */
403 @Override
404 public boolean isReady() {
405 return super.isReady() && qualityScores != null && qualityScores.length == measurements.size();
406 }
407
408 /**
409 * Indicates whether inliers must be computed and kept.
410 *
411 * @return true if inliers must be computed and kept, false if inliers
412 * only need to be computed but not kept.
413 */
414 public boolean isComputeAndKeepInliersEnabled() {
415 return computeAndKeepInliers;
416 }
417
418 /**
419 * Specifies whether inliers must be computed and kept.
420 *
421 * @param computeAndKeepInliers true if inliers must be computed and kept,
422 * false if inliers only need to be computed but not kept.
423 * @throws LockedException if calibrator is currently running.
424 */
425 public void setComputeAndKeepInliersEnabled(final boolean computeAndKeepInliers) throws LockedException {
426 if (running) {
427 throw new LockedException();
428 }
429 this.computeAndKeepInliers = computeAndKeepInliers;
430 }
431
432 /**
433 * Indicates whether residuals must be computed and kept.
434 *
435 * @return true if residuals must be computed and kept, false if residuals
436 * only need to be computed but not kept.
437 */
438 public boolean isComputeAndKeepResiduals() {
439 return computeAndKeepResiduals;
440 }
441
442 /**
443 * Specifies whether residuals must be computed and kept.
444 *
445 * @param computeAndKeepResiduals true if residuals must be computed and kept,
446 * false if residuals only need to be computed but not kept.
447 * @throws LockedException if calibrator is currently running.
448 */
449 public void setComputeAndKeepResidualsEnabled(final boolean computeAndKeepResiduals) throws LockedException {
450 if (running) {
451 throw new LockedException();
452 }
453 this.computeAndKeepResiduals = computeAndKeepResiduals;
454 }
455
456 /**
457 * Estimates accelerometer calibration parameters containing bias, scale factors
458 * and cross-coupling errors.
459 *
460 * @throws LockedException if calibrator is currently running.
461 * @throws NotReadyException if calibrator is not ready.
462 * @throws CalibrationException if estimation fails for numerical reasons.
463 */
464 @SuppressWarnings("DuplicatedCode")
465 @Override
466 public void calibrate() throws LockedException, NotReadyException, CalibrationException {
467 if (running) {
468 throw new LockedException();
469 }
470 if (!isReady()) {
471 throw new NotReadyException();
472 }
473
474 final var innerEstimator = new PROSACRobustEstimator<>(new PROSACRobustEstimatorListener<PreliminaryResult>() {
475 @Override
476 public double[] getQualityScores() {
477 return qualityScores;
478 }
479
480 @Override
481 public double getThreshold() {
482 return threshold;
483 }
484
485 @Override
486 public int getTotalSamples() {
487 return measurements.size();
488 }
489
490 @Override
491 public int getSubsetSize() {
492 return preliminarySubsetSize;
493 }
494
495 @Override
496 public void estimatePreliminarSolutions(
497 final int[] samplesIndices, final List<PreliminaryResult> solutions) {
498 computePreliminarySolutions(samplesIndices, solutions);
499 }
500
501 @Override
502 public double computeResidual(final PreliminaryResult currentEstimation, final int i) {
503 return computeError(measurements.get(i), currentEstimation);
504 }
505
506 @Override
507 public boolean isReady() {
508 return PROSACRobustKnownFrameAccelerometerCalibrator.this.isReady();
509 }
510
511 @Override
512 public void onEstimateStart(final RobustEstimator<PreliminaryResult> estimator) {
513 // no action needed
514 }
515
516 @Override
517 public void onEstimateEnd(final RobustEstimator<PreliminaryResult> estimator) {
518 // no action needed
519 }
520
521 @Override
522 public void onEstimateNextIteration(
523 final RobustEstimator<PreliminaryResult> estimator, final int iteration) {
524 if (listener != null) {
525 listener.onCalibrateNextIteration(
526 PROSACRobustKnownFrameAccelerometerCalibrator.this, iteration);
527 }
528 }
529
530 @Override
531 public void onEstimateProgressChange(
532 final RobustEstimator<PreliminaryResult> estimator, final float progress) {
533 if (listener != null) {
534 listener.onCalibrateProgressChange(
535 PROSACRobustKnownFrameAccelerometerCalibrator.this, progress);
536 }
537 }
538 });
539
540 try {
541 running = true;
542
543 if (listener != null) {
544 listener.onCalibrateStart(this);
545 }
546
547 inliersData = null;
548 innerEstimator.setComputeAndKeepInliersEnabled(computeAndKeepInliers || refineResult);
549 innerEstimator.setComputeAndKeepResidualsEnabled(computeAndKeepResiduals || refineResult);
550 innerEstimator.setConfidence(confidence);
551 innerEstimator.setMaxIterations(maxIterations);
552 innerEstimator.setProgressDelta(progressDelta);
553 final var preliminaryResult = innerEstimator.estimate();
554 inliersData = innerEstimator.getInliersData();
555
556 attemptRefine(preliminaryResult);
557
558 if (listener != null) {
559 listener.onCalibrateEnd(this);
560 }
561
562 } catch (final com.irurueta.numerical.LockedException e) {
563 throw new LockedException(e);
564 } catch (final com.irurueta.numerical.NotReadyException e) {
565 throw new NotReadyException(e);
566 } catch (final RobustEstimatorException e) {
567 throw new CalibrationException(e);
568 } finally {
569 running = false;
570 }
571 }
572
573 /**
574 * Returns method being used for robust estimation.
575 *
576 * @return method being used for robust estimation.
577 */
578 @Override
579 public RobustEstimatorMethod getMethod() {
580 return RobustEstimatorMethod.PROSAC;
581 }
582
583 /**
584 * Indicates whether this calibrator requires quality scores for each
585 * measurement or not.
586 *
587 * @return true if quality scores are required, false otherwise.
588 */
589 @Override
590 public boolean isQualityScoresRequired() {
591 return true;
592 }
593
594 /**
595 * Sets quality scores corresponding to each provided sample.
596 * This method is used internally and does not check whether instance is
597 * locked or not.
598 *
599 * @param qualityScores quality scores to be set.
600 * @throws IllegalArgumentException if provided quality scores length
601 * is smaller than 4 samples.
602 */
603 private void internalSetQualityScores(final double[] qualityScores) {
604 if (qualityScores == null || qualityScores.length < MINIMUM_MEASUREMENTS) {
605 throw new IllegalArgumentException();
606 }
607
608 this.qualityScores = qualityScores;
609 }
610 }