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.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 }