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.RANSACRobustEstimator;
23 import com.irurueta.numerical.robust.RANSACRobustEstimatorListener;
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 RANSAC 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 RANSACRobustKnownFrameAccelerometerCalibrator 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 * Constructor.
95 */
96 public RANSACRobustKnownFrameAccelerometerCalibrator() {
97 }
98
99 /**
100 * Constructor.
101 *
102 * @param listener listener to be notified of events such as when estimation
103 * starts, ends or its progress significantly changes.
104 */
105 public RANSACRobustKnownFrameAccelerometerCalibrator(
106 final RobustKnownFrameAccelerometerCalibratorListener listener) {
107 super(listener);
108 }
109
110 /**
111 * Constructor.
112 *
113 * @param measurements list of body kinematics measurements with standard
114 * deviations taken at different frames (positions, orientations
115 * and velocities).
116 */
117 public RANSACRobustKnownFrameAccelerometerCalibrator(
118 final List<StandardDeviationFrameBodyKinematics> measurements) {
119 super(measurements);
120 }
121
122 /**
123 * Constructor.
124 *
125 * @param measurements list of body kinematics measurements with standard
126 * deviations taken at different frames (positions, orientations
127 * and velocities).
128 * @param listener listener to handle events raised by this calibrator.
129 */
130 public RANSACRobustKnownFrameAccelerometerCalibrator(
131 final List<StandardDeviationFrameBodyKinematics> measurements,
132 final RobustKnownFrameAccelerometerCalibratorListener listener) {
133 super(measurements, listener);
134 }
135
136 /**
137 * Constructor.
138 *
139 * @param commonAxisUsed indicates whether z-axis is assumed to be common for
140 * accelerometer and gyroscope.
141 */
142 public RANSACRobustKnownFrameAccelerometerCalibrator(final boolean commonAxisUsed) {
143 super(commonAxisUsed);
144 }
145
146 /**
147 * Constructor.
148 *
149 * @param commonAxisUsed indicates whether z-axis is assumed to be common for
150 * accelerometer and gyroscope.
151 * @param listener listener to handle events raised by this calibrator.
152 */
153 public RANSACRobustKnownFrameAccelerometerCalibrator(
154 final boolean commonAxisUsed, final RobustKnownFrameAccelerometerCalibratorListener listener) {
155 super(commonAxisUsed, listener);
156 }
157
158 /**
159 * Constructor.
160 *
161 * @param measurements list of body kinematics measurements with standard
162 * deviations taken at different frames (positions, orientations
163 * and velocities).
164 * @param commonAxisUsed indicates whether z-axis is assumed to be common for
165 * accelerometer and gyroscope.
166 */
167 public RANSACRobustKnownFrameAccelerometerCalibrator(
168 final List<StandardDeviationFrameBodyKinematics> measurements, final boolean commonAxisUsed) {
169 super(measurements, commonAxisUsed);
170 }
171
172 /**
173 * Constructor.
174 *
175 * @param measurements list of body kinematics measurements with standard
176 * deviations taken at different frames (positions, orientations
177 * and velocities).
178 * @param commonAxisUsed indicates whether z-axis is assumed to be common for
179 * accelerometer and gyroscope.
180 * @param listener listener to handle events raised by this calibrator.
181 */
182 public RANSACRobustKnownFrameAccelerometerCalibrator(
183 final List<StandardDeviationFrameBodyKinematics> measurements,
184 final boolean commonAxisUsed, final RobustKnownFrameAccelerometerCalibratorListener listener) {
185 super(measurements, commonAxisUsed, listener);
186 }
187
188 /**
189 * Gets threshold to determine whether samples are inliers or not when testing possible solutions.
190 * The threshold refers to the amount of error on norm between measured specific forces and the
191 * ones generated with estimated calibration parameters provided for each sample.
192 *
193 * @return threshold to determine whether samples are inliers or not.
194 */
195 public double getThreshold() {
196 return threshold;
197 }
198
199 /**
200 * Sets threshold to determine whether samples are inliers or not when testing possible solutions.
201 * The threshold refers to the amount of error on norm between measured specific forces and the
202 * ones generated with estimated calibration parameters provided for each sample.
203 *
204 * @param threshold threshold to determine whether samples are inliers or not.
205 * @throws IllegalArgumentException if provided value is equal or less than zero.
206 * @throws LockedException if calibrator is currently running.
207 */
208 public void setThreshold(final double threshold) throws LockedException {
209 if (running) {
210 throw new LockedException();
211 }
212 if (threshold <= MIN_THRESHOLD) {
213 throw new IllegalArgumentException();
214 }
215 this.threshold = threshold;
216 }
217
218 /**
219 * Indicates whether inliers must be computed and kept.
220 *
221 * @return true if inliers must be computed and kept, false if inliers
222 * only need to be computed but not kept.
223 */
224 public boolean isComputeAndKeepInliersEnabled() {
225 return computeAndKeepInliers;
226 }
227
228 /**
229 * Specifies whether inliers must be computed and kept.
230 *
231 * @param computeAndKeepInliers true if inliers must be computed and kept,
232 * false if inliers only need to be computed but not kept.
233 * @throws LockedException if calibrator is currently running.
234 */
235 public void setComputeAndKeepInliersEnabled(final boolean computeAndKeepInliers) throws LockedException {
236 if (running) {
237 throw new LockedException();
238 }
239 this.computeAndKeepInliers = computeAndKeepInliers;
240 }
241
242 /**
243 * Indicates whether residuals must be computed and kept.
244 *
245 * @return true if residuals must be computed and kept, false if residuals
246 * only need to be computed but not kept.
247 */
248 public boolean isComputeAndKeepResiduals() {
249 return computeAndKeepResiduals;
250 }
251
252 /**
253 * Specifies whether residuals must be computed and kept.
254 *
255 * @param computeAndKeepResiduals true if residuals must be computed and kept,
256 * false if residuals only need to be computed but not kept.
257 * @throws LockedException if calibrator is currently running.
258 */
259 public void setComputeAndKeepResidualsEnabled(final boolean computeAndKeepResiduals) throws LockedException {
260 if (running) {
261 throw new LockedException();
262 }
263 this.computeAndKeepResiduals = computeAndKeepResiduals;
264 }
265
266 /**
267 * Estimates accelerometer calibration parameters containing bias, scale factors
268 * and cross-coupling errors.
269 *
270 * @throws LockedException if calibrator is currently running.
271 * @throws NotReadyException if calibrator is not ready.
272 * @throws CalibrationException if estimation fails for numerical reasons.
273 */
274 @SuppressWarnings("DuplicatedCode")
275 @Override
276 public void calibrate() throws LockedException, NotReadyException, CalibrationException {
277 if (running) {
278 throw new LockedException();
279 }
280 if (!isReady()) {
281 throw new NotReadyException();
282 }
283
284 final var innerEstimator = new RANSACRobustEstimator<>(new RANSACRobustEstimatorListener<PreliminaryResult>() {
285 @Override
286 public double getThreshold() {
287 return threshold;
288 }
289
290 @Override
291 public int getTotalSamples() {
292 return measurements.size();
293 }
294
295 @Override
296 public int getSubsetSize() {
297 return preliminarySubsetSize;
298 }
299
300 @Override
301 public void estimatePreliminarSolutions(
302 final int[] samplesIndices, final List<PreliminaryResult> solutions) {
303 computePreliminarySolutions(samplesIndices, solutions);
304 }
305
306 @Override
307 public double computeResidual(final PreliminaryResult currentEstimation, final int i) {
308 return computeError(measurements.get(i), currentEstimation);
309 }
310
311 @Override
312 public boolean isReady() {
313 return RANSACRobustKnownFrameAccelerometerCalibrator.super.isReady();
314 }
315
316 @Override
317 public void onEstimateStart(final RobustEstimator<PreliminaryResult> estimator) {
318 // no action needed
319 }
320
321 @Override
322 public void onEstimateEnd(final RobustEstimator<PreliminaryResult> estimator) {
323 // no action needed
324 }
325
326 @Override
327 public void onEstimateNextIteration(
328 final RobustEstimator<PreliminaryResult> estimator, final int iteration) {
329 if (listener != null) {
330 listener.onCalibrateNextIteration(
331 RANSACRobustKnownFrameAccelerometerCalibrator.this, iteration);
332 }
333 }
334
335 @Override
336 public void onEstimateProgressChange(
337 final RobustEstimator<PreliminaryResult> estimator, final float progress) {
338 if (listener != null) {
339 listener.onCalibrateProgressChange(
340 RANSACRobustKnownFrameAccelerometerCalibrator.this, progress);
341 }
342 }
343 });
344
345 try {
346 running = true;
347
348 if (listener != null) {
349 listener.onCalibrateStart(this);
350 }
351
352 inliersData = null;
353 innerEstimator.setComputeAndKeepInliersEnabled(computeAndKeepInliers || refineResult);
354 innerEstimator.setComputeAndKeepResidualsEnabled(computeAndKeepResiduals || refineResult);
355 innerEstimator.setConfidence(confidence);
356 innerEstimator.setMaxIterations(maxIterations);
357 innerEstimator.setProgressDelta(progressDelta);
358 final var preliminaryResult = innerEstimator.estimate();
359 inliersData = innerEstimator.getInliersData();
360
361 attemptRefine(preliminaryResult);
362
363 if (listener != null) {
364 listener.onCalibrateEnd(this);
365 }
366
367 } catch (final com.irurueta.numerical.LockedException e) {
368 throw new LockedException(e);
369 } catch (final com.irurueta.numerical.NotReadyException e) {
370 throw new NotReadyException(e);
371 } catch (final RobustEstimatorException e) {
372 throw new CalibrationException(e);
373 } finally {
374 running = false;
375 }
376 }
377
378 /**
379 * Returns method being used for robust estimation.
380 *
381 * @return method being used for robust estimation.
382 */
383 @Override
384 public RobustEstimatorMethod getMethod() {
385 return RobustEstimatorMethod.RANSAC;
386 }
387
388 /**
389 * Indicates whether this calibrator requires quality scores for each
390 * measurement or not.
391 *
392 * @return true if quality scores are required, false otherwise.
393 */
394 @Override
395 public boolean isQualityScoresRequired() {
396 return false;
397 }
398 }