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.generators;
17  
18  import com.irurueta.navigation.LockedException;
19  import com.irurueta.navigation.inertial.BodyKinematics;
20  import com.irurueta.navigation.inertial.calibration.AngularSpeedTriad;
21  import com.irurueta.navigation.inertial.calibration.BodyKinematicsSequence;
22  import com.irurueta.navigation.inertial.calibration.GyroscopeNoiseRootPsdSource;
23  import com.irurueta.navigation.inertial.calibration.StandardDeviationTimedBodyKinematics;
24  import com.irurueta.navigation.inertial.calibration.TimedBodyKinematics;
25  import com.irurueta.navigation.inertial.calibration.intervals.TriadStaticIntervalDetector;
26  import com.irurueta.navigation.inertial.calibration.noise.AccumulatedAngularSpeedTriadNoiseEstimator;
27  import com.irurueta.units.AngularSpeed;
28  import com.irurueta.units.AngularSpeedUnit;
29  
30  import java.util.ArrayList;
31  import java.util.List;
32  
33  /**
34   * Generates measurements for the calibration of gyroscopes by alternating
35   * static and dynamic intervals where device is kept static or moved.
36   * Generated measurements must be used with easy gyroscope calibrators.
37   * Notice that accuracy of the gyroscope calibration is very sensitive to the
38   * accuracy of detected dynamic intervals respect the average specific forces
39   * during static intervals.
40   * In order to increase the accuracy, calibration should be repeated trying different
41   * threshold factors {@link #getThresholdFactor()}.
42   * Such calibrators are the following ones:
43   * - {@link com.irurueta.navigation.inertial.calibration.gyroscope.EasyGyroscopeCalibrator}
44   * - {@link com.irurueta.navigation.inertial.calibration.gyroscope.KnownBiasEasyGyroscopeCalibrator}
45   * - {@link com.irurueta.navigation.inertial.calibration.gyroscope.RobustEasyGyroscopeCalibrator} and all its
46   * implementations.
47   * - {@link com.irurueta.navigation.inertial.calibration.gyroscope.RobustKnownBiasEasyGyroscopeCalibrator} and all its
48   * implementations.
49   */
50  public class GyroscopeMeasurementsGenerator extends
51          MeasurementsGenerator<BodyKinematicsSequence<StandardDeviationTimedBodyKinematics>,
52                  GyroscopeMeasurementsGenerator, GyroscopeMeasurementsGeneratorListener, TimedBodyKinematics>
53          implements GyroscopeNoiseRootPsdSource {
54  
55      /**
56       * An angular speed triad.
57       * This is reused for memory efficiency.
58       */
59      protected final AngularSpeedTriad angularSpeedTriad = new AngularSpeedTriad();
60  
61      /**
62       * Items to be added to a generated sequence when next static period occurs.
63       */
64      private List<StandardDeviationTimedBodyKinematics> currentSequenceItems;
65  
66      /**
67       * Accumulated noise estimator for angular speed measurements.
68       */
69      private final AccumulatedAngularSpeedTriadNoiseEstimator accumulatedEstimator =
70              new AccumulatedAngularSpeedTriadNoiseEstimator();
71  
72      /**
73       * Estimated acceleration standard deviation during initialization expressed
74       * in meters per squared second (m/s^2).
75       */
76      private double accelerationStandardDeviation;
77  
78      /**
79       * Estimated angular speed standard deviation during initialization expressed
80       * in radians per second (rad/s).
81       */
82      private double angularSpeedStandardDeviation;
83  
84      /**
85       * Estimated norm of gyroscope noise root PSD (Power Spectral Density)
86       * expressed as (rad * s^-0.5).
87       */
88      private double angularSpeedNoiseRootPsd;
89  
90      /**
91       * Previous average x-coordinate of measurements expressed in meters
92       * per squared second (m/s^2).
93       */
94      private Double previousAvgX;
95  
96      /**
97       * Previous average y-coordinate of measurements expressed in meters
98       * per squared second (m/s^2).
99       */
100     private Double previousAvgY;
101 
102     /**
103      * Previous average z-coordinate of measurements expressed in meters
104      * per squared second (m/s^2).
105      */
106     private Double previousAvgZ;
107 
108     /**
109      * Current average x-coordinate of measurements expressed in meters
110      * per squared second (m/s^2).
111      */
112     private Double currentAvgX;
113 
114     /**
115      * Current average y-coordinate of measurements expressed in meters
116      * per squared second (m/s^2).
117      */
118     private Double currentAvgY;
119 
120     /**
121      * Current average z-coordinate of measurements expressed in meters
122      * per squared second (m/s^2).
123      */
124     private Double currentAvgZ;
125 
126     /**
127      * Contains previous status while processing samples.
128      */
129     private TriadStaticIntervalDetector.Status previousStatus;
130 
131     /**
132      * Constructor.
133      */
134     public GyroscopeMeasurementsGenerator() {
135         super();
136     }
137 
138     /**
139      * Constructor.
140      *
141      * @param listener listener to handle events raised by this generator.
142      */
143     public GyroscopeMeasurementsGenerator(final GyroscopeMeasurementsGeneratorListener listener) {
144         super(listener);
145     }
146 
147     /**
148      * Sets time interval between input samples expressed in seconds (s).
149      *
150      * @param timeInterval time interval between input samples.
151      * @throws IllegalArgumentException if provided value is negative.
152      * @throws LockedException          if generator is currently running.
153      */
154     @Override
155     public void setTimeInterval(final double timeInterval) throws LockedException {
156         super.setTimeInterval(timeInterval);
157         accumulatedEstimator.setTimeInterval(timeInterval);
158     }
159 
160     /**
161      * Resets this generator.
162      *
163      * @throws LockedException if generator is busy.
164      */
165     @Override
166     public void reset() throws LockedException {
167         super.reset();
168 
169         currentSequenceItems = null;
170 
171         accelerationStandardDeviation = 0.0;
172         angularSpeedStandardDeviation = 0.0;
173 
174         previousAvgX = null;
175         previousAvgY = null;
176         previousAvgZ = null;
177 
178         currentAvgX = null;
179         currentAvgY = null;
180         currentAvgZ = null;
181 
182         accumulatedEstimator.reset();
183 
184         previousStatus = null;
185     }
186 
187     /**
188      * Gets estimated average angular rate during initialization phase.
189      *
190      * @return estimated average angular rate during initialization phase.
191      */
192     public AngularSpeedTriad getInitialAvgAngularSpeedTriad() {
193         return accumulatedEstimator.getAvgTriad();
194     }
195 
196     /**
197      * Gets estimated average angular rate during initialization phase.
198      *
199      * @param result instance where result will be stored.
200      */
201     public void getInitialAvgAngularSpeedTriad(final AngularSpeedTriad result) {
202         accumulatedEstimator.getAvgTriad(result);
203     }
204 
205     /**
206      * Gets estimated standard deviation of angular rate during initialization phase.
207      *
208      * @return estimated standard deviation of angular rate during initialization phase.
209      */
210     public AngularSpeedTriad getInitialAngularSpeedTriadStandardDeviation() {
211         return accumulatedEstimator.getStandardDeviationTriad();
212     }
213 
214     /**
215      * Gets estimated standard deviation of angular rate during initialization phase.
216      *
217      * @param result instance where result will be stored.
218      */
219     public void getInitialAngularSpeedTriadStandardDeviation(final AngularSpeedTriad result) {
220         accumulatedEstimator.getStandardDeviationTriad(result);
221     }
222 
223     /**
224      * Gets gyroscope base noise level that has been detected during
225      * initialization expressed in radians per second (rad/s).
226      * This is equal to the standard deviation of the gyroscope measurements
227      * during initialization phase.
228      *
229      * @return gyroscope base noise level.
230      */
231     public double getGyroscopeBaseNoiseLevel() {
232         return angularSpeedStandardDeviation;
233     }
234 
235     /**
236      * Gets gyroscope base noise level that has been detected during
237      * initialization.
238      * This is equal to the standard deviation of the gyroscope measurements
239      * during initialization phase.
240      *
241      * @return gyroscope base noise level.
242      */
243     public AngularSpeed getGyroscopeBaseNoiseLevelAsMeasurement() {
244         return new AngularSpeed(angularSpeedStandardDeviation, AngularSpeedUnit.RADIANS_PER_SECOND);
245     }
246 
247     /**
248      * Gets gyroscope base noise level that has been detected during
249      * initialization.
250      * This is equal to the standard deviation of the gyroscope measurements
251      * during initialization phase.
252      *
253      * @param result instance where result will be stored.
254      */
255     public void getGyroscopeBaseNoiseLevelAsMeasurement(final AngularSpeed result) {
256         result.setValue(angularSpeedStandardDeviation);
257         result.setUnit(AngularSpeedUnit.RADIANS_PER_SECOND);
258     }
259 
260     /**
261      * Gets gyroscope base noise level PSD (Power Spectral Density)
262      * expressed in (rad^2/s).
263      *
264      * @return gyroscope base noise level PSD.
265      */
266     public double getGyroscopeBaseNoiseLevelPsd() {
267         return angularSpeedNoiseRootPsd * angularSpeedNoiseRootPsd;
268     }
269 
270     /**
271      * Gets gyroscope base noise level root PSD (Power Spectral Density)
272      * expressed in (rad * s^-0.5)
273      *
274      * @return gyroscope base noise level root PSD.
275      */
276     @Override
277     public double getGyroscopeBaseNoiseLevelRootPsd() {
278         return angularSpeedNoiseRootPsd;
279     }
280 
281     /**
282      * Post process provided input sample.
283      *
284      * @param sample an input sample.
285      * @throws LockedException if generator is busy.
286      */
287     @Override
288     protected void postProcess(final TimedBodyKinematics sample) throws LockedException {
289         final var status = staticIntervalDetector.getStatus();
290 
291         if (status == TriadStaticIntervalDetector.Status.INITIALIZING) {
292             sample.getKinematics().getAngularRateTriad(angularSpeedTriad);
293             accumulatedEstimator.addTriad(angularSpeedTriad);
294         }
295 
296         // while we are in a dynamic interval, we must record all timed kinematics
297         // along with accelerometer and gyroscope standard deviations
298         if (status == TriadStaticIntervalDetector.Status.DYNAMIC_INTERVAL) {
299             if (isDynamicIntervalSkipped()) {
300                 // dynamic interval has been skipped because there were too many
301                 // items in the sequence.
302                 currentSequenceItems = null;
303             } else {
304                 if (previousStatus == TriadStaticIntervalDetector.Status.STATIC_INTERVAL) {
305                     previousAvgX = staticIntervalDetector.getAccumulatedAvgX();
306                     previousAvgY = staticIntervalDetector.getAccumulatedAvgY();
307                     previousAvgZ = staticIntervalDetector.getAccumulatedAvgZ();
308                 }
309 
310                 addSequenceItem(sample);
311             }
312         } else if (status == TriadStaticIntervalDetector.Status.STATIC_INTERVAL
313                 && previousStatus == TriadStaticIntervalDetector.Status.DYNAMIC_INTERVAL
314                 && currentSequenceItems != null && !currentSequenceItems.isEmpty()) {
315 
316             currentAvgX = staticIntervalDetector.getInstantaneousAvgX();
317             currentAvgY = staticIntervalDetector.getInstantaneousAvgY();
318             currentAvgZ = staticIntervalDetector.getInstantaneousAvgZ();
319 
320             // we have all required data to generate a sequence
321             BodyKinematicsSequence<StandardDeviationTimedBodyKinematics> sequence = null;
322             if (listener != null) {
323                 sequence = new BodyKinematicsSequence<>();
324                 sequence.setBeforeMeanSpecificForceCoordinates(previousAvgX, previousAvgY, previousAvgZ);
325                 sequence.setItems(currentSequenceItems);
326                 sequence.setAfterMeanSpecificForceCoordinates(currentAvgX, currentAvgY, currentAvgZ);
327             }
328 
329             currentSequenceItems = null;
330 
331             if (listener != null) {
332                 listener.onGeneratedMeasurement(this, sequence);
333             }
334         }
335     }
336 
337     /**
338      * Gets corresponding acceleration triad from provided input sample.
339      * This method must store the result into {@link #triad}.
340      *
341      * @param sample input sample.
342      */
343     @Override
344     protected void getAccelerationTriadFromInputSample(final TimedBodyKinematics sample) {
345         sample.getKinematics().getSpecificForceTriad(triad);
346     }
347 
348     /**
349      * Handles a static-to-dynamic interval change.
350      *
351      * @param accumulatedAvgX average x-coordinate of measurements during last
352      *                        static period expressed in meters per squared
353      *                        second (m/s^2).
354      * @param accumulatedAvgY average y-coordinate of specific force during last
355      *                        static period expressed in meters per squared
356      *                        second (m/s^2).
357      * @param accumulatedAvgZ average z-coordinate of specific force during last
358      *                        static period expressed in meters per squared
359      *                        second (m/s^2).
360      * @param accumulatedStdX standard deviation of x-coordinate of measurements
361      *                        during last static period expressed in meters per
362      *                        squared second (m/s^2).
363      * @param accumulatedStdY standard deviation of y-coordinate of measurements
364      *                        during last static period expressed in meters per
365      *                        squared second (m/s^2).
366      * @param accumulatedStdZ standard deviation of z-coordinate of measurements
367      *                        during last static period expressed in meters per
368      *                        squared second (m/s^2).
369      */
370     @Override
371     protected void handleStaticToDynamicChange(
372             final double accumulatedAvgX, final double accumulatedAvgY, final double accumulatedAvgZ,
373             final double accumulatedStdX, final double accumulatedStdY, final double accumulatedStdZ) {
374         previousStatus = TriadStaticIntervalDetector.Status.STATIC_INTERVAL;
375     }
376 
377     /**
378      * Handles a dynamic-to-static interval change.
379      */
380     @Override
381     protected void handleDynamicToStaticChange() {
382         previousStatus = TriadStaticIntervalDetector.Status.DYNAMIC_INTERVAL;
383     }
384 
385     /**
386      * Handles an initialization completion.
387      */
388     @Override
389     protected void handleInitializationCompleted() {
390         accelerationStandardDeviation = staticIntervalDetector.getBaseNoiseLevel();
391         angularSpeedStandardDeviation = accumulatedEstimator.getStandardDeviationNorm();
392         angularSpeedNoiseRootPsd = accumulatedEstimator.getNoiseRootPsdNorm();
393 
394         previousStatus = staticIntervalDetector.getStatus();
395     }
396 
397     /**
398      * Handles an error during initialization.
399      */
400     @Override
401     protected void handleInitializationFailed() {
402         previousStatus = null;
403 
404         try {
405             accumulatedEstimator.reset();
406         } catch (final LockedException ignore) {
407             // no action needed
408         }
409     }
410 
411     /**
412      * Adds an item to current sequence items.
413      *
414      * @param sample sample to generate a sequence item from.
415      */
416     private void addSequenceItem(final TimedBodyKinematics sample) {
417         if (currentSequenceItems == null) {
418             currentSequenceItems = new ArrayList<>();
419         }
420 
421         final var kinematics = new BodyKinematics(sample.getKinematics());
422         final var timestampSeconds = sample.getTimestampSeconds();
423         final var stdTimedKinematics = new StandardDeviationTimedBodyKinematics(kinematics, timestampSeconds,
424                 accelerationStandardDeviation, angularSpeedStandardDeviation);
425         currentSequenceItems.add(stdTimedKinematics);
426     }
427 }