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.gyroscope;
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 gyroscope biases, cross couplings and scaling factors
32 * along with G-dependent cross biases introduced on the gyroscope by the
33 * specific forces sensed by the accelerometer using a RANSAC algorithm to discard
34 * outliers.
35 * <p>
36 * To use this calibrator at least 7 measurements at different known frames must
37 * be provided. In other words, accelerometer and gyroscope (i.e. body kinematics)
38 * samples must be obtained at 7 different positions, orientations and velocities
39 * (although typically velocities are always zero).
40 * <p>
41 * Measured gyroscope angular rates is assumed to follow the model shown below:
42 * <pre>
43 * Ωmeas = bg + (I + Mg) * Ωtrue + Gg * ftrue + w
44 * </pre>
45 * Where:
46 * - Ωmeas is the measured gyroscope angular rates. This is a 3x1 vector.
47 * - bg is the gyroscope bias. Ideally, on a perfect gyroscope, this should be a
48 * 3x1 zero vector.
49 * - I is the 3x3 identity matrix.
50 * - Mg is the 3x3 matrix containing cross-couplings and scaling factors. Ideally, on
51 * a perfect gyroscope, this should be a 3x3 zero matrix.
52 * - Ωtrue is ground-truth gyroscope angular rates.
53 * - Gg is the G-dependent cross biases introduced by the specific forces sensed
54 * by the accelerometer. Ideally, on a perfect gyroscope, this should be a 3x3
55 * zero matrix.
56 * - ftrue is ground-truth specific force. This is a 3x1 vector.
57 * - w is measurement noise. This is a 3x1 vector.
58 */
59 public class RANSACRobustKnownFrameGyroscopeCalibrator extends RobustKnownFrameGyroscopeCalibrator {
60
61 /**
62 * Constant defining default threshold to determine whether samples are inliers or not.
63 */
64 public static final double DEFAULT_THRESHOLD = 5e-4;
65
66 /**
67 * Minimum value that can be set as threshold.
68 * Threshold must be strictly greater than 0.0.
69 */
70 public static final double MIN_THRESHOLD = 0.0;
71
72 /**
73 * Indicates that by default inliers will only be computed but not kept.
74 */
75 public static final boolean DEFAULT_COMPUTE_AND_KEEP_INLIERS = false;
76
77 /**
78 * Indicates that by default residuals will only be computed but not kept.
79 */
80 public static final boolean DEFAULT_COMPUTE_AND_KEEP_RESIDUALS = false;
81
82 /**
83 * Threshold to determine whether samples are inliers or not when testing possible solutions.
84 * The threshold refers to the amount of error on distance between estimated position and
85 * distances provided for each sample.
86 */
87 private double threshold = DEFAULT_THRESHOLD;
88
89 /**
90 * Indicates whether inliers must be computed and kept.
91 */
92 private boolean computeAndKeepInliers = DEFAULT_COMPUTE_AND_KEEP_INLIERS;
93
94 /**
95 * Indicates whether residuals must be computed and kept.
96 */
97 private boolean computeAndKeepResiduals = DEFAULT_COMPUTE_AND_KEEP_RESIDUALS;
98
99 /**
100 * Constructor.
101 */
102 public RANSACRobustKnownFrameGyroscopeCalibrator() {
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 RANSACRobustKnownFrameGyroscopeCalibrator(final RobustKnownFrameGyroscopeCalibratorListener listener) {
112 super(listener);
113 }
114
115 /**
116 * Constructor.
117 *
118 * @param measurements list of body kinematics measurements with standard
119 * deviations taken at different frames (positions, orientations
120 * and velocities).
121 */
122 public RANSACRobustKnownFrameGyroscopeCalibrator(final List<StandardDeviationFrameBodyKinematics> measurements) {
123 super(measurements);
124 }
125
126 /**
127 * Constructor.
128 *
129 * @param measurements list of body kinematics measurements with standard
130 * deviations taken at different frames (positions, orientations
131 * and velocities).
132 * @param listener listener to be notified of events such as when estimation
133 * starts, ends or its progress significantly changes.
134 */
135 public RANSACRobustKnownFrameGyroscopeCalibrator(
136 final List<StandardDeviationFrameBodyKinematics> measurements,
137 final RobustKnownFrameGyroscopeCalibratorListener listener) {
138 super(measurements, listener);
139 }
140
141 /**
142 * Constructor.
143 *
144 * @param commonAxisUsed indicates whether z-axis is assumed to be common for
145 * accelerometer and gyroscope.
146 */
147 public RANSACRobustKnownFrameGyroscopeCalibrator(final boolean commonAxisUsed) {
148 super(commonAxisUsed);
149 }
150
151 /**
152 * Constructor.
153 *
154 * @param commonAxisUsed indicates whether z-axis is assumed to be common for
155 * accelerometer and gyroscope.
156 * @param listener listener to handle events raised by this calibrator.
157 */
158 public RANSACRobustKnownFrameGyroscopeCalibrator(
159 final boolean commonAxisUsed, final RobustKnownFrameGyroscopeCalibratorListener listener) {
160 super(commonAxisUsed, listener);
161 }
162
163 /**
164 * Constructor.
165 *
166 * @param measurements list of body kinematics measurements with standard
167 * deviations taken at different frames (positions, orientations
168 * and velocities).
169 * @param commonAxisUsed indicates whether z-axis is assumed to be common for
170 * accelerometer and gyroscope.
171 */
172 public RANSACRobustKnownFrameGyroscopeCalibrator(
173 final List<StandardDeviationFrameBodyKinematics> measurements, final boolean commonAxisUsed) {
174 super(measurements, commonAxisUsed);
175 }
176
177 /**
178 * Constructor.
179 *
180 * @param measurements list of body kinematics measurements with standard
181 * deviations taken at different frames (positions, orientations
182 * and velocities).
183 * @param commonAxisUsed indicates whether z-axis is assumed to be common for
184 * accelerometer and gyroscope.
185 * @param listener listener to handle events raised by this calibrator.
186 */
187 public RANSACRobustKnownFrameGyroscopeCalibrator(
188 final List<StandardDeviationFrameBodyKinematics> measurements, final boolean commonAxisUsed,
189 final RobustKnownFrameGyroscopeCalibratorListener listener) {
190 super(measurements, commonAxisUsed, listener);
191 }
192
193 /**
194 * Gets threshold to determine whether samples are inliers or not when testing possible solutions.
195 * The threshold refers to the amount of error on norm between measured angular rates and the
196 * ones generated with estimated calibration parameters provided for each sample.
197 *
198 * @return threshold to determine whether samples are inliers or not.
199 */
200 public double getThreshold() {
201 return threshold;
202 }
203
204 /**
205 * Sets threshold to determine whether samples are inliers or not when testing possible solutions.
206 * The threshold refers to the amount of error on norm between measured angular rates and the
207 * ones generated with estimated calibration parameters provided for each sample.
208 *
209 * @param threshold threshold to determine whether samples are inliers or not.
210 * @throws IllegalArgumentException if provided value is equal or less than zero.
211 * @throws LockedException if calibrator is currently running.
212 */
213 public void setThreshold(final double threshold) throws LockedException {
214 if (running) {
215 throw new LockedException();
216 }
217 if (threshold <= MIN_THRESHOLD) {
218 throw new IllegalArgumentException();
219 }
220 this.threshold = threshold;
221 }
222
223 /**
224 * Indicates whether inliers must be computed and kept.
225 *
226 * @return true if inliers must be computed and kept, false if inliers
227 * only need to be computed but not kept.
228 */
229 public boolean isComputeAndKeepInliersEnabled() {
230 return computeAndKeepInliers;
231 }
232
233 /**
234 * Specifies whether inliers must be computed and kept.
235 *
236 * @param computeAndKeepInliers true if inliers must be computed and kept,
237 * false if inliers only need to be computed but not kept.
238 * @throws LockedException if calibrator is currently running.
239 */
240 public void setComputeAndKeepInliersEnabled(final boolean computeAndKeepInliers) throws LockedException {
241 if (running) {
242 throw new LockedException();
243 }
244 this.computeAndKeepInliers = computeAndKeepInliers;
245 }
246
247 /**
248 * Indicates whether residuals must be computed and kept.
249 *
250 * @return true if residuals must be computed and kept, false if residuals
251 * only need to be computed but not kept.
252 */
253 public boolean isComputeAndKeepResiduals() {
254 return computeAndKeepResiduals;
255 }
256
257 /**
258 * Specifies whether residuals must be computed and kept.
259 *
260 * @param computeAndKeepResiduals true if residuals must be computed and kept,
261 * false if residuals only need to be computed but not kept.
262 * @throws LockedException if calibrator is currently running.
263 */
264 public void setComputeAndKeepResidualsEnabled(final boolean computeAndKeepResiduals) throws LockedException {
265 if (running) {
266 throw new LockedException();
267 }
268 this.computeAndKeepResiduals = computeAndKeepResiduals;
269 }
270
271 /**
272 * Estimates gyroscope calibration parameters containing bias, scale factors
273 * cross-coupling errors and g-dependant cross biases.
274 *
275 * @throws LockedException if calibrator is currently running.
276 * @throws NotReadyException if calibrator is not ready.
277 * @throws CalibrationException if estimation fails for numerical reasons.
278 */
279 @SuppressWarnings("DuplicatedCode")
280 @Override
281 public void calibrate() throws LockedException, NotReadyException, CalibrationException {
282 if (running) {
283 throw new LockedException();
284 }
285 if (!isReady()) {
286 throw new NotReadyException();
287 }
288
289 final var innerEstimator = new RANSACRobustEstimator<>(new RANSACRobustEstimatorListener<PreliminaryResult>() {
290 @Override
291 public double getThreshold() {
292 return threshold;
293 }
294
295 @Override
296 public int getTotalSamples() {
297 return measurements.size();
298 }
299
300 @Override
301 public int getSubsetSize() {
302 return preliminarySubsetSize;
303 }
304
305 @Override
306 public void estimatePreliminarSolutions(
307 final int[] samplesIndices, final List<PreliminaryResult> solutions) {
308 computePreliminarySolutions(samplesIndices, solutions);
309 }
310
311 @Override
312 public double computeResidual(final PreliminaryResult currentEstimation, final int i) {
313 return computeError(measurements.get(i), currentEstimation);
314 }
315
316 @Override
317 public boolean isReady() {
318 return RANSACRobustKnownFrameGyroscopeCalibrator.super.isReady();
319 }
320
321 @Override
322 public void onEstimateStart(final RobustEstimator<PreliminaryResult> estimator) {
323 // no action needed
324 }
325
326 @Override
327 public void onEstimateEnd(final RobustEstimator<PreliminaryResult> estimator) {
328 // no action needed
329 }
330
331 @Override
332 public void onEstimateNextIteration(
333 final RobustEstimator<PreliminaryResult> estimator, final int iteration) {
334 if (listener != null) {
335 listener.onCalibrateNextIteration(
336 RANSACRobustKnownFrameGyroscopeCalibrator.this, iteration);
337 }
338 }
339
340 @Override
341 public void onEstimateProgressChange(
342 final RobustEstimator<PreliminaryResult> estimator, final float progress) {
343 if (listener != null) {
344 listener.onCalibrateProgressChange(
345 RANSACRobustKnownFrameGyroscopeCalibrator.this, progress);
346 }
347 }
348 });
349
350 try {
351 running = true;
352
353 if (listener != null) {
354 listener.onCalibrateStart(this);
355 }
356
357 inliersData = null;
358 innerEstimator.setComputeAndKeepInliersEnabled(computeAndKeepInliers || refineResult);
359 innerEstimator.setComputeAndKeepResidualsEnabled(computeAndKeepResiduals || refineResult);
360 innerEstimator.setConfidence(confidence);
361 innerEstimator.setMaxIterations(maxIterations);
362 innerEstimator.setProgressDelta(progressDelta);
363 final var preliminaryResult = innerEstimator.estimate();
364 inliersData = innerEstimator.getInliersData();
365
366 attemptRefine(preliminaryResult);
367
368 if (listener != null) {
369 listener.onCalibrateEnd(this);
370 }
371
372 } catch (final com.irurueta.numerical.LockedException e) {
373 throw new LockedException(e);
374 } catch (final com.irurueta.numerical.NotReadyException e) {
375 throw new NotReadyException(e);
376 } catch (final RobustEstimatorException e) {
377 throw new CalibrationException(e);
378 } finally {
379 running = false;
380 }
381 }
382
383 /**
384 * Returns method being used for robust estimation.
385 *
386 * @return method being used for robust estimation.
387 */
388 @Override
389 public RobustEstimatorMethod getMethod() {
390 return RobustEstimatorMethod.RANSAC;
391 }
392
393 /**
394 * Indicates whether this calibrator requires quality scores for each
395 * measurement/sequence or not.
396 *
397 * @return true if quality scores are required, false otherwise.
398 */
399 @Override
400 public boolean isQualityScoresRequired() {
401 return false;
402 }
403 }