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.MSACRobustEstimator;
23 import com.irurueta.numerical.robust.MSACRobustEstimatorListener;
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 MSAC 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 MSACRobustKnownFrameGyroscopeCalibrator extends RobustKnownFrameGyroscopeCalibrator {
60
61 /**
62 * Constant defining default threshold to determine whether samples are
63 * inliers or not.
64 */
65 public static final double DEFAULT_THRESHOLD = 1e-2;
66
67 /**
68 * Minimum value that can be set as threshold.
69 * Threshold must be strictly greater than 0.0.
70 */
71 public static final double MIN_THRESHOLD = 0.0;
72
73 /**
74 * Threshold to determine whether samples are inliers or not when
75 * testing possible estimation solutions.
76 */
77 private double threshold = DEFAULT_THRESHOLD;
78
79 /**
80 * Constructor.
81 */
82 public MSACRobustKnownFrameGyroscopeCalibrator() {
83 }
84
85 /**
86 * Constructor.
87 *
88 * @param listener listener to be notified of events such as when estimation
89 * starts, ends or its progress significantly changes.
90 */
91 public MSACRobustKnownFrameGyroscopeCalibrator(final RobustKnownFrameGyroscopeCalibratorListener listener) {
92 super(listener);
93 }
94
95 /**
96 * Constructor.
97 *
98 * @param measurements list of body kinematics measurements with standard
99 * deviations taken at different frames (positions, orientations
100 * and velocities).
101 */
102 public MSACRobustKnownFrameGyroscopeCalibrator(final List<StandardDeviationFrameBodyKinematics> measurements) {
103 super(measurements);
104 }
105
106 /**
107 * Constructor.
108 *
109 * @param measurements list of body kinematics measurements with standard
110 * deviations taken at different frames (positions, orientations
111 * and velocities).
112 * @param listener listener to be notified of events such as when estimation
113 * starts, ends or its progress significantly changes.
114 */
115 public MSACRobustKnownFrameGyroscopeCalibrator(
116 final List<StandardDeviationFrameBodyKinematics> measurements,
117 final RobustKnownFrameGyroscopeCalibratorListener listener) {
118 super(measurements, listener);
119 }
120
121 /**
122 * Constructor.
123 *
124 * @param commonAxisUsed indicates whether z-axis is assumed to be common for
125 * accelerometer and gyroscope.
126 */
127 public MSACRobustKnownFrameGyroscopeCalibrator(final boolean commonAxisUsed) {
128 super(commonAxisUsed);
129 }
130
131 /**
132 * Constructor.
133 *
134 * @param commonAxisUsed indicates whether z-axis is assumed to be common for
135 * accelerometer and gyroscope.
136 * @param listener listener to handle events raised by this calibrator.
137 */
138 public MSACRobustKnownFrameGyroscopeCalibrator(
139 final boolean commonAxisUsed, final RobustKnownFrameGyroscopeCalibratorListener listener) {
140 super(commonAxisUsed, listener);
141 }
142
143 /**
144 * Constructor.
145 *
146 * @param measurements list of body kinematics measurements with standard
147 * deviations taken at different frames (positions, orientations
148 * and velocities).
149 * @param commonAxisUsed indicates whether z-axis is assumed to be common for
150 * accelerometer and gyroscope.
151 */
152 public MSACRobustKnownFrameGyroscopeCalibrator(
153 final List<StandardDeviationFrameBodyKinematics> measurements, final boolean commonAxisUsed) {
154 super(measurements, commonAxisUsed);
155 }
156
157 /**
158 * Constructor.
159 *
160 * @param measurements list of body kinematics measurements with standard
161 * deviations taken at different frames (positions, orientations
162 * and velocities).
163 * @param commonAxisUsed indicates whether z-axis is assumed to be common for
164 * accelerometer and gyroscope.
165 * @param listener listener to handle events raised by this calibrator.
166 */
167 public MSACRobustKnownFrameGyroscopeCalibrator(
168 final List<StandardDeviationFrameBodyKinematics> measurements, final boolean commonAxisUsed,
169 final RobustKnownFrameGyroscopeCalibratorListener listener) {
170 super(measurements, commonAxisUsed, listener);
171 }
172
173 /**
174 * Returns threshold to determine whether samples are inliers or not.
175 *
176 * @return threshold to determine whether samples are inliers or not.
177 */
178 public double getThreshold() {
179 return threshold;
180 }
181
182 /**
183 * Sets threshold to determine whether samples are inliers or not.
184 *
185 * @param threshold threshold to be set.
186 * @throws IllegalArgumentException if provided value is equal or less than
187 * zero.
188 * @throws LockedException if calibrator is currently running.
189 */
190 public void setThreshold(final double threshold) throws LockedException {
191 if (running) {
192 throw new LockedException();
193 }
194 if (threshold <= MIN_THRESHOLD) {
195 throw new IllegalArgumentException();
196 }
197 this.threshold = threshold;
198 }
199
200 /**
201 * Estimates gyroscope calibration parameters containing bias, scale factors
202 * cross-coupling errors and g-dependant cross biases.
203 *
204 * @throws LockedException if calibrator is currently running.
205 * @throws NotReadyException if calibrator is not ready.
206 * @throws CalibrationException if estimation fails for numerical reasons.
207 */
208 @SuppressWarnings("DuplicatedCode")
209 @Override
210 public void calibrate() throws LockedException, NotReadyException, CalibrationException {
211 if (running) {
212 throw new LockedException();
213 }
214 if (!isReady()) {
215 throw new NotReadyException();
216 }
217
218 final var innerEstimator = new MSACRobustEstimator<>(new MSACRobustEstimatorListener<PreliminaryResult>() {
219 @Override
220 public double getThreshold() {
221 return threshold;
222 }
223
224 @Override
225 public int getTotalSamples() {
226 return measurements.size();
227 }
228
229 @Override
230 public int getSubsetSize() {
231 return preliminarySubsetSize;
232 }
233
234 @Override
235 public void estimatePreliminarSolutions(
236 final int[] samplesIndices, final List<PreliminaryResult> solutions) {
237 computePreliminarySolutions(samplesIndices, solutions);
238 }
239
240 @Override
241 public double computeResidual(final PreliminaryResult currentEstimation, final int i) {
242 return computeError(measurements.get(i), currentEstimation);
243 }
244
245 @Override
246 public boolean isReady() {
247 return MSACRobustKnownFrameGyroscopeCalibrator.super.isReady();
248 }
249
250 @Override
251 public void onEstimateStart(final RobustEstimator<PreliminaryResult> estimator) {
252 // no action needed
253 }
254
255 @Override
256 public void onEstimateEnd(final RobustEstimator<PreliminaryResult> estimator) {
257 // no action needed
258 }
259
260 @Override
261 public void onEstimateNextIteration(
262 final RobustEstimator<PreliminaryResult> estimator, final int iteration) {
263 if (listener != null) {
264 listener.onCalibrateNextIteration(
265 MSACRobustKnownFrameGyroscopeCalibrator.this, iteration);
266 }
267 }
268
269 @Override
270 public void onEstimateProgressChange(
271 final RobustEstimator<PreliminaryResult> estimator, final float progress) {
272 if (listener != null) {
273 listener.onCalibrateProgressChange(
274 MSACRobustKnownFrameGyroscopeCalibrator.this, progress);
275 }
276 }
277 });
278
279 try {
280 running = true;
281
282 if (listener != null) {
283 listener.onCalibrateStart(this);
284 }
285
286 inliersData = null;
287 innerEstimator.setConfidence(confidence);
288 innerEstimator.setMaxIterations(maxIterations);
289 innerEstimator.setProgressDelta(progressDelta);
290 final var preliminaryResult = innerEstimator.estimate();
291 inliersData = innerEstimator.getInliersData();
292
293 attemptRefine(preliminaryResult);
294
295 if (listener != null) {
296 listener.onCalibrateEnd(this);
297 }
298
299 } catch (final com.irurueta.numerical.LockedException e) {
300 throw new LockedException(e);
301 } catch (final com.irurueta.numerical.NotReadyException e) {
302 throw new NotReadyException(e);
303 } catch (final RobustEstimatorException e) {
304 throw new CalibrationException(e);
305 } finally {
306 running = false;
307 }
308 }
309
310 /**
311 * Returns method being used for robust estimation.
312 *
313 * @return method being used for robust estimation.
314 */
315 @Override
316 public RobustEstimatorMethod getMethod() {
317 return RobustEstimatorMethod.MSAC;
318 }
319
320 /**
321 * Indicates whether this calibrator requires quality scores for each
322 * measurement/sequence or not.
323 *
324 * @return true if quality scores are required, false otherwise.
325 */
326 @Override
327 public boolean isQualityScoresRequired() {
328 return false;
329 }
330 }