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.noise;
17  
18  import com.irurueta.navigation.LockedException;
19  import com.irurueta.navigation.inertial.calibration.TimeIntervalEstimator;
20  import com.irurueta.units.Measurement;
21  import com.irurueta.units.Time;
22  import com.irurueta.units.TimeConverter;
23  import com.irurueta.units.TimeUnit;
24  
25  /**
26   * Base class to estimate measurement noise variances and PSD's (Power Spectral Densities)
27   * along with their average values.
28   * Implementations of this estimator may use norms of measurement triads to estimate noise
29   * levels.
30   * To compute PSD's, this estimator assumes that measurement samples are obtained
31   * at a constant provided rate equal to {@link #getTimeInterval()} seconds.
32   * If not available, accelerometer sampling rate average can be estimated using
33   * {@link TimeIntervalEstimator}.
34   *
35   * @param <U> a measurement unit type.
36   * @param <M> a measurement type.
37   * @param <E> an estimator type.
38   * @param <L> a listener type.
39   */
40  @SuppressWarnings("DuplicatedCode")
41  public abstract class AccumulatedMeasurementNoiseEstimator<U extends Enum<?>,
42          M extends Measurement<U>,
43          E extends AccumulatedMeasurementNoiseEstimator<U, M, E, L>,
44          L extends AccumulatedMeasurementNoiseEstimatorListener<U, M, E>> {
45  
46      /**
47       * Default time interval between accelerometer samples expressed in seconds
48       * (s).
49       */
50      public static final double DEFAULT_TIME_INTERVAL_SECONDS = 0.02;
51  
52      /**
53       * Time interval expressed in seconds (s) between consecutive accelerometer
54       * samples.
55       */
56      private double timeInterval = DEFAULT_TIME_INTERVAL_SECONDS;
57  
58      /**
59       * Listener to handle events raised by this estimator.
60       */
61      private L listener;
62  
63      /**
64       * Last provided measurement.
65       */
66      private M lastMeasurement;
67  
68      /**
69       * Contains estimated average of measurement expressed in its default unit
70       * (m/s^2 for acceleration, rad/s for angular speed or T for magnetic flux density).
71       */
72      private double avg;
73  
74      /**
75       * Contains estimated variance of measurement expressed in its default squared unit
76       * (m^2/s^4 for acceleration, rad^2/s^2 for angular speed or T^2 for magnetic
77       * flux density).
78       */
79      private double variance;
80  
81      /**
82       * Number of processed body kinematics samples.
83       */
84      private int numberOfProcessedSamples;
85  
86      /**
87       * Number of processed timestamp samples plus one.
88       */
89      private int numberOfProcessedSamplesPlusOne = 1;
90  
91      /**
92       * Indicates that estimator is running.
93       */
94      private boolean running;
95  
96      /**
97       * Constructor.
98       */
99      protected AccumulatedMeasurementNoiseEstimator() {
100     }
101 
102     /**
103      * Constructor.
104      *
105      * @param listener listener to handle events raised by this estimator.
106      */
107     protected AccumulatedMeasurementNoiseEstimator(final L listener) {
108         this.listener = listener;
109     }
110 
111     /**
112      * Gets time interval between accelerometer triad samples expressed in
113      * seconds (s).
114      *
115      * @return time interval between accelerometer triad samples.
116      */
117     public double getTimeInterval() {
118         return timeInterval;
119     }
120 
121     /**
122      * Sets time interval between accelerometer triad samples expressed in
123      * seconds (s).
124      *
125      * @param timeInterval time interval between accelerometer triad samples.
126      * @throws IllegalArgumentException if provided value is negative.
127      * @throws LockedException          if estimator is currently running.
128      */
129     public void setTimeInterval(final double timeInterval) throws LockedException {
130         if (running) {
131             throw new LockedException();
132         }
133 
134         if (timeInterval < 0.0) {
135             throw new IllegalArgumentException();
136         }
137 
138         this.timeInterval = timeInterval;
139     }
140 
141     /**
142      * Gets time interval between accelerometer triad samples.
143      *
144      * @return time interval between accelerometer triad samples.
145      */
146     public Time getTimeIntervalAsTime() {
147         return new Time(timeInterval, TimeUnit.SECOND);
148     }
149 
150     /**
151      * Gets time interval between accelerometer triad samples.
152      *
153      * @param result instance where time interval will be stored.
154      */
155     public void getTimeIntervalAsTime(final Time result) {
156         result.setValue(timeInterval);
157         result.setUnit(TimeUnit.SECOND);
158     }
159 
160     /**
161      * Sets time interval between accelerometer triad samples.
162      *
163      * @param timeInterval time interval between accelerometer triad samples.
164      * @throws LockedException if estimator is currently running.
165      */
166     public void setTimeInterval(final Time timeInterval) throws LockedException {
167         setTimeInterval(TimeConverter.convert(timeInterval.getValue().doubleValue(),
168                 timeInterval.getUnit(), TimeUnit.SECOND));
169     }
170 
171     /**
172      * Gets listener to handle events raised by this estimator.
173      *
174      * @return listener to handle events raised by this estimator.
175      */
176     public L getListener() {
177         return listener;
178     }
179 
180     /**
181      * Sets listener to handle events raised by this estimator.
182      *
183      * @param listener listener to handle events raised by this estimator.
184      * @throws LockedException if this estimator is running.
185      */
186     public void setListener(final L listener) throws LockedException {
187         if (running) {
188             throw new LockedException();
189         }
190 
191         this.listener = listener;
192     }
193 
194     /**
195      * Gets last provided measurement or null if not available.
196      *
197      * @return last provided measurement or null.
198      */
199     public M getLastMeasurement() {
200         return lastMeasurement;
201     }
202 
203     /**
204      * Gets last provided measurement.
205      *
206      * @param result instance where last provided measurement will be stored.
207      * @return true if result instance was updated, false otherwise.
208      */
209     public boolean getLastMeasurement(final M result) {
210         if (lastMeasurement != null) {
211             result.setValue(lastMeasurement.getValue());
212             result.setUnit(lastMeasurement.getUnit());
213             return true;
214         } else {
215             return false;
216         }
217     }
218 
219     /**
220      * Gets estimated average of measurement expressed in its default unit
221      * (m/s^2 for acceleration, rad/s for angular speed or T for magnetic flux density).
222      *
223      * @return average of measurement in current window.
224      */
225     public double getAvg() {
226         return avg;
227     }
228 
229     /**
230      * Gets estimated average of measurement within current window.
231      *
232      * @return average of measurement in current window
233      */
234     public M getAvgAsMeasurement() {
235         return createMeasurement(avg, getDefaultUnit());
236     }
237 
238     /**
239      * Gets estimated average of measurement within current window.
240      *
241      * @param result instance where average of measurement will be stored.
242      */
243     public void getAvgAsMeasurement(final M result) {
244         result.setValue(avg);
245         result.setUnit(getDefaultUnit());
246     }
247 
248     /**
249      * Gets estimated variance of measurement within current window
250      * expressed in its default squared unit (m^2/s^4 for acceleration,
251      * rad^2/s^2 for angular speed or T^2 for magnetic flux density).
252      *
253      * @return estimated variance of measurement within current window.
254      */
255     public double getVariance() {
256         return variance;
257     }
258 
259     /**
260      * Gets estimated standard deviation of measurement within current window
261      * and expressed in its default unit (m/s^2 for acceleration, rad/s for
262      * angular speed or T for magnetic flux density).
263      *
264      * @return estimated standard of measurement.
265      */
266     public double getStandardDeviation() {
267         return Math.sqrt(variance);
268     }
269 
270     /**
271      * Gets estimated standard deviation of measurement within current window.
272      *
273      * @return estimated standard deviation of measurement.
274      */
275     public M getStandardDeviationAsMeasurement() {
276         return createMeasurement(getStandardDeviation(), getDefaultUnit());
277     }
278 
279     /**
280      * Gets estimated standard deviation of measurement within current window.
281      *
282      * @param result instance where estimated standard deviation of measurement
283      *               will be stored.
284      */
285     public void getStandardDeviationAsMeasurement(final M result) {
286         result.setValue(getStandardDeviation());
287         result.setUnit(getDefaultUnit());
288     }
289 
290     /**
291      * Gets measurement noise PSD (Power Spectral Density) expressed
292      * in (m^2 * s^-3) for accelerometer, (rad^2/s) for gyroscope or (T^2 * s) for
293      * magnetometer.
294      *
295      * @return measurement noise PSD.
296      */
297     public double getPsd() {
298         return variance * timeInterval;
299     }
300 
301     /**
302      * Gets measurement noise root PSD (Power Spectral Density) expressed in
303      * (m * s^-1.5) for accelerometer, (rad * s^-0.5) for gyroscope or (T * s^0.5) for
304      * magnetometer.
305      *
306      * @return measurement noise root PSD.
307      */
308     public double getRootPsd() {
309         return Math.sqrt(getPsd());
310     }
311 
312     /**
313      * Gets number of samples that have been processed so far.
314      *
315      * @return number of samples that have been processed so far.
316      */
317     public int getNumberOfProcessedSamples() {
318         return numberOfProcessedSamples;
319     }
320 
321     /**
322      * Indicates whether estimator is currently running or not.
323      *
324      * @return true if estimator is running, false otherwise.
325      */
326     public boolean isRunning() {
327         return running;
328     }
329 
330     /**
331      * Adds a measurement value expressed in its default unit (m/s^2 for acceleration, rad/s for
332      * angular speed or T for magnetic flux density).
333      *
334      * @param value value to be added.
335      * @throws LockedException if estimator is currently running.
336      */
337     public void addMeasurement(final double value) throws LockedException {
338 
339         if (running) {
340             throw new LockedException();
341         }
342 
343         running = true;
344 
345         if (lastMeasurement == null && listener != null) {
346             //noinspection unchecked
347             listener.onStart((E) this);
348         }
349 
350         // compute average
351         final var tmp = (double) numberOfProcessedSamples / (double) numberOfProcessedSamplesPlusOne;
352         avg = avg * tmp + value / numberOfProcessedSamplesPlusOne;
353 
354         // compute variance
355         final var diff = value - avg;
356         final var diff2 = diff * diff;
357 
358         variance = variance * tmp + diff2 / numberOfProcessedSamplesPlusOne;
359 
360         if (lastMeasurement == null) {
361             lastMeasurement = createMeasurement(value, getDefaultUnit());
362         } else {
363             lastMeasurement.setValue(value);
364             lastMeasurement.setUnit(getDefaultUnit());
365         }
366 
367         numberOfProcessedSamples++;
368         numberOfProcessedSamplesPlusOne++;
369 
370         if (listener != null) {
371             //noinspection unchecked
372             listener.onMeasurementAdded((E) this);
373         }
374 
375         running = false;
376     }
377 
378     /**
379      * Adds a measurement value.
380      *
381      * @param measurement measurement to be added.
382      * @throws LockedException if estimator is currently running.
383      */
384     public void addMeasurement(final M measurement) throws LockedException {
385         addMeasurement(convertToDefaultUnit(measurement));
386     }
387 
388     /**
389      * Resets current estimator.
390      *
391      * @return true if estimator was successfully reset, false if no reset was needed.
392      * @throws LockedException if estimator is currently running.
393      */
394     public boolean reset() throws LockedException {
395         if (running) {
396             throw new LockedException();
397         }
398 
399         if (numberOfProcessedSamples == 0) {
400             return false;
401         }
402 
403         running = true;
404         lastMeasurement = null;
405         avg = 0.0;
406         variance = 0.0;
407         numberOfProcessedSamples = 0;
408         numberOfProcessedSamplesPlusOne = 1;
409 
410         if (listener != null) {
411             //noinspection unchecked
412             listener.onReset((E) this);
413         }
414 
415         running = false;
416 
417         return true;
418     }
419 
420     /**
421      * Gets default unit for a measurement.
422      *
423      * @return default unit for a measurement.
424      */
425     protected abstract U getDefaultUnit();
426 
427     /**
428      * Creates a measurement with provided value and unit.
429      *
430      * @param value value to be set.
431      * @param unit  unit to be set.
432      * @return created measurement.
433      */
434     protected abstract M createMeasurement(final double value, final U unit);
435 
436     /**
437      * Converts provided measurement into default unit.
438      *
439      * @param value measurement to be converted.
440      * @return converted value.
441      */
442     protected abstract double convertToDefaultUnit(M value);
443 }