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.navigation.inertial.calibration.Triad;
21  import com.irurueta.units.Measurement;
22  import com.irurueta.units.Time;
23  import com.irurueta.units.TimeConverter;
24  import com.irurueta.units.TimeUnit;
25  
26  import java.util.LinkedList;
27  
28  /**
29   * Base class to estimate measurement noise variances and PSD's (Power Spectral Densities)
30   * along with average values for a windowed amount of samples.
31   * Implementations of this estimator must be used when the body where the measurement device
32   * is attached to remains static on the same position with zero velocity while capturing data.
33   * To compute PSD's, this estimator assumes that measurement samples are obtained
34   * at a constant provided rate equal to {@link #getTimeInterval()} seconds.
35   * If not available, accelerometer sampling rate average can be estimated using
36   * {@link TimeIntervalEstimator}.
37   * This estimator does NOT require the knowledge of current location and body
38   * orientation.
39   * Because body location and orientation is not known, estimated average values
40   * cannot be used to determine biases. Only norm of noise estimations
41   * (variance or standard deviation) can be safely used.
42   * Notice that if there are less than {@link #getWindowSize()} processed
43   * samples in the window, this estimator will assume that the remaining ones
44   * until the window is completed have zero values.
45   *
46   * @param <U> a measurement unit type.
47   * @param <M> a measurement type.
48   * @param <T> a triad type.
49   * @param <E> an estimator type.
50   * @param <L> a listener type.
51   */
52  @SuppressWarnings("DuplicatedCode")
53  public abstract class WindowedTriadNoiseEstimator<U extends Enum<?>,
54          M extends Measurement<U>, T extends Triad<U, M, T>,
55          E extends WindowedTriadNoiseEstimator<U, M, T, E, L>,
56          L extends WindowedTriadNoiseEstimatorListener<U, M, T, E>> {
57  
58      /**
59       * Number of samples to keep within the window by default.
60       * For an accelerometer generating 100 samples/second, this is equivalent to
61       * 1 second.
62       * For an accelerometer generating 50 samples/second, this is equivalent to
63       * 2 seconds.
64       */
65      public static final int DEFAULT_WINDOW_SIZE = 101;
66  
67      /**
68       * Minimum allowed window size.
69       */
70      public static final int MIN_WINDOW_SIZE = 3;
71  
72      /**
73       * Default time interval between accelerometer samples expressed in seconds
74       * (s).
75       */
76      public static final double DEFAULT_TIME_INTERVAL_SECONDS = 0.02;
77  
78      /**
79       * Length of number of samples to keep within the window being processed.
80       * Window size must always be larger than allowed minimum value and must have
81       * an odd value.
82       */
83      private int windowSize = DEFAULT_WINDOW_SIZE;
84  
85      /**
86       * Time interval expressed in seconds (s) between consecutive triad
87       * samples.
88       */
89      private double timeInterval = DEFAULT_TIME_INTERVAL_SECONDS;
90  
91      /**
92       * Keeps the list of triad samples that remain within the window.
93       */
94      private final LinkedList<T> windowedSamples = new LinkedList<>();
95  
96      /**
97       * Listener to handle events raised by this estimator.
98       */
99      private L listener;
100 
101     /**
102      * Contains estimated average of x coordinate of measurement expressed in its default
103      * unit (m/s^2 for acceleration, rad/s for angular speed or T for magnetic flux density).
104      */
105     private double avgX;
106 
107     /**
108      * Contains estimated average of y coordinate of measurement expressed in its default
109      * unit (m/s^2 for acceleration, rad/s for angular speed or T for magnetic flux density).
110      */
111     private double avgY;
112 
113     /**
114      * Contains estimated average of z coordinate of measurement expressed in its default
115      * unit (m/s^2 for acceleration, rad/s for angular speed or T for magnetic flux density).
116      */
117     private double avgZ;
118 
119     /**
120      * Contains estimated variance of x coordinate of measurement expressed in its default
121      * squared unit (m^2/s^4 for acceleration, rad^2/s^2 for angular speed or T^2 for magnetic
122      * flux density).
123      */
124     private double varianceX;
125 
126     /**
127      * Contains estimated variance of y coordinate of measurement expressed in its default
128      * squared unit (m^2/s^4 for acceleration, rad^2/s^2 for angular speed or T^2 for magnetic
129      * flux density).
130      */
131     private double varianceY;
132 
133     /**
134      * Contains estimated variance of x coordinate of measurement expressed in its default
135      * squared unit (m^2/s^4 for acceleration, rad^2/s^2 for angular speed or T^2 for magnetic
136      * flux density).
137      */
138     private double varianceZ;
139 
140     /**
141      * Number of processed acceleration triad samples.
142      */
143     private int numberOfProcessedSamples;
144 
145     /**
146      * Indicates whether estimator is running or not.
147      */
148     private boolean running;
149 
150     /**
151      * Constructor.
152      */
153     protected WindowedTriadNoiseEstimator() {
154     }
155 
156     /**
157      * Constructor.
158      *
159      * @param listener listener to handle events raised by this estimator.
160      */
161     protected WindowedTriadNoiseEstimator(final L listener) {
162         this.listener = listener;
163     }
164 
165     /**
166      * Gets length of number of samples to keep within the window being processed.
167      * Window size must always be larger than allowed minimum value and must
168      * have an odd value.
169      *
170      * @return length of number of samples to keep within the window.
171      */
172     public int getWindowSize() {
173         return windowSize;
174     }
175 
176     /**
177      * Sets length of number of samples to keep within the window being processed.
178      * Window size must always be larger than allowed minimum value and must have
179      * an odd value.
180      * When window size is modified, instance state is reset.
181      *
182      * @param windowSize length of number of samples to keep within the window.
183      * @throws IllegalArgumentException if provided value is not valid.
184      * @throws LockedException          if estimator is currently running.
185      */
186     public void setWindowSize(final int windowSize) throws LockedException {
187         if (running) {
188             throw new LockedException();
189         }
190 
191         // check that window is larger than minimum allowed value
192         if (windowSize < MIN_WINDOW_SIZE) {
193             throw new IllegalArgumentException();
194         }
195 
196         // check that window size is not even
197         if (windowSize % 2 == 0) {
198             throw new IllegalArgumentException();
199         }
200 
201         this.windowSize = windowSize;
202         reset();
203     }
204 
205     /**
206      * Gets time interval between triad samples expressed in
207      * seconds (s).
208      *
209      * @return time interval between triad samples.
210      */
211     public double getTimeInterval() {
212         return timeInterval;
213     }
214 
215     /**
216      * Sets time interval between triad samples expressed in
217      * seconds (s).
218      *
219      * @param timeInterval time interval between triad samples.
220      * @throws IllegalArgumentException if provided value is negative.
221      * @throws LockedException          if estimator is currently running.
222      */
223     public void setTimeInterval(final double timeInterval) throws LockedException {
224         if (running) {
225             throw new LockedException();
226         }
227 
228         if (timeInterval < 0.0) {
229             throw new IllegalArgumentException();
230         }
231 
232         this.timeInterval = timeInterval;
233     }
234 
235     /**
236      * Gets time interval between triad samples.
237      *
238      * @return time interval between triad samples.
239      */
240     public Time getTimeIntervalAsTime() {
241         return new Time(timeInterval, TimeUnit.SECOND);
242     }
243 
244     /**
245      * Gets time interval between triad samples.
246      *
247      * @param result instance where time interval will be stored.
248      */
249     public void getTimeIntervalAsTime(final Time result) {
250         result.setValue(timeInterval);
251         result.setUnit(TimeUnit.SECOND);
252     }
253 
254     /**
255      * Sets time interval between triad samples.
256      *
257      * @param timeInterval time interval between triad samples.
258      * @throws LockedException if estimator is currently running.
259      */
260     public void setTimeInterval(final Time timeInterval) throws LockedException {
261         setTimeInterval(TimeConverter.convert(timeInterval.getValue().doubleValue(), timeInterval.getUnit(),
262                 TimeUnit.SECOND));
263     }
264 
265     /**
266      * Gets listener to handle events raised by this estimator.
267      *
268      * @return listener to handle events raised by this estimator.
269      */
270     public L getListener() {
271         return listener;
272     }
273 
274     /**
275      * Sets listener to handle events raised by this estimator.
276      *
277      * @param listener listener to handle events raised by this estimator.
278      * @throws LockedException if this estimator is running.
279      */
280     public void setListener(final L listener) throws LockedException {
281         if (running) {
282             throw new LockedException();
283         }
284 
285         this.listener = listener;
286     }
287 
288     /**
289      * Gets first provided measurement triad within the window.
290      *
291      * @return first provided measurement triad within the window or null if not
292      * available.
293      */
294     public T getFirstWindowedTriad() {
295         return windowedSamples.isEmpty() ? null : windowedSamples.getFirst();
296     }
297 
298     /**
299      * Gets first provided measurement triad within the window.
300      *
301      * @param result instance where first provided measurement triad will be stored.
302      * @return true if result instance was updated, false otherwise.
303      */
304     public boolean getFirstWindowedTriad(final T result) {
305         if (windowedSamples.isEmpty()) {
306             return false;
307         } else {
308             result.copyFrom(windowedSamples.getFirst());
309             return true;
310         }
311     }
312 
313     /**
314      * Gets last provided measurement triad within the window.
315      *
316      * @return last provided measurement triad within the window or null if not
317      * available.
318      */
319     public T getLastWindowedTriad() {
320         return windowedSamples.isEmpty() ? null : windowedSamples.getLast();
321     }
322 
323     /**
324      * Gets last provided measurement triad within the window.
325      *
326      * @param result instance where last provided measurement triad will be stored.
327      * @return true if result instance was updated, false otherwise.
328      */
329     public boolean getLastWindowedTriad(final T result) {
330         if (windowedSamples.isEmpty()) {
331             return false;
332         } else {
333             result.copyFrom(windowedSamples.getLast());
334             return true;
335         }
336     }
337 
338     /**
339      * Gets estimated average of x coordinate of measurement expressed in its default
340      * unit (m/s^2 for acceleration, rad/s for angular speed or T for magnetic flux density).
341      * This value will depend of body location and orientation, hence it should never
342      * be used as a calibration bias.
343      *
344      * @return average of x coordinate of measurement in current window.
345      */
346     public double getAvgX() {
347         return avgX;
348     }
349 
350     /**
351      * Gets estimated average of x coordinate of measurement within current window.
352      * This value will depend of body location and orientation, hence it should never
353      * be used as a calibration bias.
354      *
355      * @return average of x coordinate of measurement in current window.
356      */
357     public M getAvgXAsMeasurement() {
358         return createMeasurement(avgX, getDefaultUnit());
359     }
360 
361     /**
362      * Gets estimated average of x coordinate of measurement within current window.
363      * This value will depend of body location and orientation, hence it should never
364      * be used as a calibration bias.
365      *
366      * @param result instance where average of x coordinate of measurement will be stored.
367      */
368     public void getAvgXAsMeasurement(final M result) {
369         result.setValue(avgX);
370         result.setUnit(getDefaultUnit());
371     }
372 
373     /**
374      * Gets estimated average of y coordinate of measurement expressed in its default
375      * unit (m/s^2 for acceleration, rad/s for angular speed or T for magnetic flux density).
376      * This value will depend of body location and orientation, hence it should never
377      * be used as a calibration bias.
378      *
379      * @return average of y coordinate of measurement in current window.
380      */
381     public double getAvgY() {
382         return avgY;
383     }
384 
385     /**
386      * Gets estimated average of y coordinate of measurement within current window.
387      * This value will depend of body location and orientation, hence it should never
388      * be used as a calibration bias.
389      *
390      * @return average of y coordinate of measurement in current window.
391      */
392     public M getAvgYAsMeasurement() {
393         return createMeasurement(avgY, getDefaultUnit());
394     }
395 
396     /**
397      * Gets estimated average of y coordinate of measurement within current window.
398      * This value will depend of body location and orientation, hence it should never
399      * be used as a calibration bias.
400      *
401      * @param result instance where average of y coordinate of measurement will be stored.
402      */
403     public void getAvgYAsMeasurement(final M result) {
404         result.setValue(avgY);
405         result.setUnit(getDefaultUnit());
406     }
407 
408     /**
409      * Gets estimated average of z coordinate of measurement expressed in its default
410      * unit (m/s^2 for acceleration, rad/s for angular speed or T for magnetic flux density).
411      * This value will depend of body location and orientation, hence it should never
412      * be used as a calibration bias.
413      *
414      * @return average of z coordinate of measurement in current window.
415      */
416     public double getAvgZ() {
417         return avgZ;
418     }
419 
420     /**
421      * Gets estimated average of z coordinate of measurement within current window.
422      * This value will depend of body location and orientation, hence it should never
423      * be used as a calibration bias.
424      *
425      * @return average of z coordinate of measurement in current window.
426      */
427     public M getAvgZAsMeasurement() {
428         return createMeasurement(avgZ, getDefaultUnit());
429     }
430 
431     /**
432      * Gets estimated average of z coordinate of measurement within current window.
433      * This value will depend of body location and orientation, hence it should never
434      * be used as a calibration bias.
435      *
436      * @param result instance where average of z coordinate of measurement will be stored.
437      */
438     public void getAvgZAsMeasurement(final M result) {
439         result.setValue(avgZ);
440         result.setUnit(getDefaultUnit());
441     }
442 
443     /**
444      * Gets estimated average as a measurement triad.
445      *
446      * @return average measurement triad.
447      */
448     public T getAvgTriad() {
449         return createTriad(avgX, avgY, avgZ, getDefaultUnit());
450     }
451 
452     /**
453      * Gets estimated average as a measurement triad.
454      *
455      * @param result instance where average values and unit will be stored.
456      */
457     public void getAvgTriad(final T result) {
458         result.setValueCoordinatesAndUnit(avgX, avgY, avgZ, getDefaultUnit());
459     }
460 
461     /**
462      * Gets norm of estimated average measurement within current window expressed in its default
463      * unit (m/s^2 for acceleration, rad/s for angular speed or T for magnetic flux density).
464      * This value is independent of body orientation.
465      *
466      * @return norm of estimated average specific force.
467      */
468     public double getAvgNorm() {
469         return Math.sqrt(avgX * avgX + avgY * avgY + avgZ * avgZ);
470     }
471 
472     /**
473      * Gets norm of estimated average measurement within current window.
474      *
475      * @return norm of estimated average measurement.
476      */
477     public M getAvgNormAsMeasurement() {
478         return createMeasurement(getAvgNorm(), getDefaultUnit());
479     }
480 
481     /**
482      * Gets norm of estimated average measurement within current window.
483      *
484      * @param result instance where norm of estimated average measurement will be stored.
485      */
486     public void getAvgNormAsMeasurement(final M result) {
487         result.setValue(getAvgNorm());
488         result.setUnit(getDefaultUnit());
489     }
490 
491     /**
492      * Gets estimated variance of x coordinate of measurement within current window
493      * expressed in its default squared unit (m^2/s^4 for acceleration,
494      * rad^2/s^2 for angular speed or T^2 for magnetic flux density).
495      *
496      * @return estimated variance of x coordinate of measurement within current
497      * window.
498      */
499     public double getVarianceX() {
500         return varianceX;
501     }
502 
503     /**
504      * Gets estimated variance of y coordinate of measurement within current window
505      * expressed in its default squared unit (m^2/s^4 for acceleration,
506      * rad^2/s^2 for angular speed or T^2 for magnetic flux density).
507      *
508      * @return estimated variance of y coordinate of measurement within current
509      * window.
510      */
511     public double getVarianceY() {
512         return varianceY;
513     }
514 
515     /**
516      * Gets estimated variance of z coordinate of measurement within current window
517      * expressed in its default squared unit (m^2/s^4 for acceleration,
518      * rad^2/s^2 for angular speed or T^2 for magnetic flux density).
519      *
520      * @return estimated variance of z coordinate of measurement within current
521      * window.
522      */
523     public double getVarianceZ() {
524         return varianceZ;
525     }
526 
527     /**
528      * Gets estimated standard deviation of x coordinate of measurement within current
529      * window and expressed in its default unit (m/s^2 for acceleration, rad/s for
530      * angular speed or T for magnetic flux density).
531      *
532      * @return estimated standard deviation of x coordinate of measurement within
533      * current window.
534      */
535     public double getStandardDeviationX() {
536         return Math.sqrt(varianceX);
537     }
538 
539     /**
540      * Gets estimated standard deviation of x coordinate of measurement within current
541      * window.
542      *
543      * @return estimated standard deviation of x coordinate of measurement.
544      */
545     public M getStandardDeviationXAsMeasurement() {
546         return createMeasurement(getStandardDeviationX(), getDefaultUnit());
547     }
548 
549     /**
550      * Gets estimated standard deviation of x coordinate of measurement within current
551      * window.
552      *
553      * @param result instance where estimated standard deviation of x coordinate of
554      *               measurement will be stored.
555      */
556     public void getStandardDeviationXAsMeasurement(final M result) {
557         result.setValue(getStandardDeviationX());
558         result.setUnit(getDefaultUnit());
559     }
560 
561     /**
562      * Gets estimated standard deviation of y coordinate of measurement within current
563      * window and expressed in its default unit (m/s^2 for acceleration, rad/s for
564      * angular speed or T for magnetic flux density).
565      *
566      * @return estimated standard deviation of y coordinate of measurement within
567      * current window.
568      */
569     public double getStandardDeviationY() {
570         return Math.sqrt(varianceY);
571     }
572 
573     /**
574      * Gets estimated standard deviation of y coordinate of measurement within current
575      * window.
576      *
577      * @return estimated standard deviation of y coordinate of measurement.
578      */
579     public M getStandardDeviationYAsMeasurement() {
580         return createMeasurement(getStandardDeviationY(), getDefaultUnit());
581     }
582 
583     /**
584      * Gets estimated standard deviation of y coordinate of measurement within current
585      * window.
586      *
587      * @param result instance where estimated standard deviation of y coordinate of
588      *               measurement will be stored.
589      */
590     public void getStandardDeviationYAsMeasurement(final M result) {
591         result.setValue(getStandardDeviationY());
592         result.setUnit(getDefaultUnit());
593     }
594 
595     /**
596      * Gets estimated standard deviation of z coordinate of measurement within current
597      * window and expressed in its default unit (m/s^2 for acceleration, rad/s for
598      * angular speed or T for magnetic flux density).
599      *
600      * @return estimated standard deviation of z coordinate of measurement within
601      * current window.
602      */
603     public double getStandardDeviationZ() {
604         return Math.sqrt(varianceZ);
605     }
606 
607     /**
608      * Gets estimated standard deviation of z coordinate of measurement within current
609      * window.
610      *
611      * @return estimated standard deviation of z coordinate of measurement.
612      */
613     public M getStandardDeviationZAsMeasurement() {
614         return createMeasurement(getStandardDeviationZ(), getDefaultUnit());
615     }
616 
617     /**
618      * Gets estimated standard deviation of z coordinate of measurement within current
619      * window.
620      *
621      * @param result instance where estimated standard deviation of z coordinate of
622      *               measurement will be stored.
623      */
624     public void getStandardDeviationZAsMeasurement(final M result) {
625         result.setValue(getStandardDeviationZ());
626         result.setUnit(getDefaultUnit());
627     }
628 
629     /**
630      * Gets estimated standard deviation of measurement within current window.
631      *
632      * @return estimated standard deviation triad of measurement.
633      */
634     public T getStandardDeviationTriad() {
635         return createTriad(getStandardDeviationX(), getStandardDeviationY(), getStandardDeviationZ(), getDefaultUnit());
636     }
637 
638     /**
639      * Gets estimated standard deviation of measurement within current window.
640      *
641      * @param result instance where estimated standard deviation triad of
642      *               measurement will be stored.
643      */
644     public void getStandardDeviationTriad(final T result) {
645         result.setValueCoordinatesAndUnit(getStandardDeviationX(), getStandardDeviationY(), getStandardDeviationZ(),
646                 getDefaultUnit());
647     }
648 
649     /**
650      * Gets norm of estimated standard deviation of measurement within current
651      * window expressed in its default unit (m/s^2 for acceleration, rad/s for
652      * angular speed or T for magnetic flux density).
653      *
654      * @return norm of estimated standard deviation of measurement.
655      */
656     public double getStandardDeviationNorm() {
657         final var fx = getStandardDeviationX();
658         final var fy = getStandardDeviationY();
659         final var fz = getStandardDeviationZ();
660         return Math.sqrt(fx * fx + fy * fy + fz * fz);
661     }
662 
663     /**
664      * Gets norm of estimated standard deviation of measurement within current window.
665      *
666      * @return norm of estimated standard deviation of measurement.
667      */
668     public M getStandardDeviationNormAsMeasurement() {
669         return createMeasurement(getStandardDeviationNorm(), getDefaultUnit());
670     }
671 
672     /**
673      * Gets norm of estimated standard deviation of measurement within current window.
674      *
675      * @param result instance where norm of estimated standard deviation will be stored.
676      */
677     public void getStandardDeviationNormAsMeasurement(final M result) {
678         result.setValue(getStandardDeviationNorm());
679         result.setUnit(getDefaultUnit());
680     }
681 
682     /**
683      * Gets average of estimated standard deviation coordinates of measurement within
684      * current window expressed in its default unit (m/s^2 for acceleration, rad/s for
685      * angular speed or T for magnetic flux density).
686      *
687      * @return average of estimated standard deviation coordinates.
688      */
689     public double getAverageStandardDeviation() {
690         final var fx = getStandardDeviationX();
691         final var fy = getStandardDeviationY();
692         final var fz = getStandardDeviationZ();
693         return (fx + fy + fz) / 3.0;
694     }
695 
696     /**
697      * Gets average of estimated standard deviation coordinates of measurement within
698      * current window.
699      *
700      * @return average of estimated standard deviation coordinates.
701      */
702     public M getAverageStandardDeviationAsMeasurement() {
703         return createMeasurement(getAverageStandardDeviation(), getDefaultUnit());
704     }
705 
706     /**
707      * Gets average of estimated standard deviation coordinates of measurement within
708      * current window.
709      *
710      * @param result instance where average of estimated standard deviation coordinates
711      *               will be stored.
712      */
713     public void getAverageStandardDeviationAsMeasurement(final M result) {
714         result.setValue(getAverageStandardDeviation());
715         result.setUnit(getDefaultUnit());
716     }
717 
718     /**
719      * Gets measurement noise PSD (Power Spectral Density) on x axis expressed
720      * in (m^2 * s^-3) for accelerometer, (rad^2/s) for gyroscope or (T^2 * s) for
721      * magnetometer.
722      *
723      * @return measurement noise PSD on x axis.
724      */
725     public double getPsdX() {
726         return varianceX * timeInterval;
727     }
728 
729     /**
730      * Gets measurement noise PSD (Power Spectral Density) on y axis expressed
731      * in (m^2 * s^-3) for accelerometer, (rad^2/s) for gyroscope or (T^2 * s) for
732      * magnetometer.
733      *
734      * @return measurement noise PSD on y axis.
735      */
736     public double getPsdY() {
737         return varianceY * timeInterval;
738     }
739 
740     /**
741      * Gets measurement noise PSD (Power Spectral Density) on z axis expressed
742      * in (m^2 * s^-3) for accelerometer, (rad^2/s) for gyroscope or (T^2 * s) for
743      * magnetometer.
744      *
745      * @return measurement noise PSD on z axis.
746      */
747     public double getPsdZ() {
748         return varianceZ * timeInterval;
749     }
750 
751     /**
752      * Gets measurement noise root PSD (Power Spectral Density) on x axis expressed in
753      * (m * s^-1.5) for accelerometer, (rad * s^-0.5) for gyroscope or (T * s^0.5) for
754      * magnetometer.
755      *
756      * @return measurement noise root PSD on x axis.
757      */
758     public double getRootPsdX() {
759         return Math.sqrt(getPsdX());
760     }
761 
762     /**
763      * Gets measurement noise root PSD (Power Spectral Density) on y axis expressed in
764      * (m * s^-1.5) for accelerometer, (rad * s^-0.5) for gyroscope or (T * s^0.5) for
765      * magnetometer.
766      *
767      * @return measurement noise root PSD on y axis.
768      */
769     public double getRootPsdY() {
770         return Math.sqrt(getPsdY());
771     }
772 
773     /**
774      * Gets measurement noise root PSD (Power Spectral Density) on z axis expressed in
775      * (m * s^-1.5) for accelerometer, (rad * s^-0.5) for gyroscope or (T * s^0.5) for
776      * magnetometer.
777      *
778      * @return measurement noise root PSD on z axis.
779      */
780     public double getRootPsdZ() {
781         return Math.sqrt(getPsdZ());
782     }
783 
784     /**
785      * Gets average measurement noise PSD (Power Spectral Density) among
786      * x,y,z components expressed as (m^2 * s^-3) for accelerometer,
787      * (rad^2/s) for gyroscope or (T^2 * s) for magnetometer.
788      *
789      * @return average measurement noise PSD.
790      */
791     public double getAvgNoisePsd() {
792         return (getPsdX() + getPsdY() + getPsdZ()) / 3.0;
793     }
794 
795     /**
796      * Gets norm of noise root PSD (Power Spectral Density) among x,y,z
797      * components expressed as (m * s^-1.5) for accelerometer,
798      * (rad * s^-0.5) for gyroscope or (T * s^0.5) for magnetometer.
799      *
800      * @return norm of measurement noise root PSD.
801      */
802     public double getNoiseRootPsdNorm() {
803         return Math.sqrt(getPsdX() + getPsdY() + getPsdZ());
804     }
805 
806     /**
807      * Gets number of samples that have been processed so far.
808      *
809      * @return number of samples that have been processed so far.
810      */
811     public int getNumberOfProcessedSamples() {
812         return numberOfProcessedSamples;
813     }
814 
815     /**
816      * Gets number of currently windowed samples.
817      *
818      * @return number of samples within the window.
819      */
820     public int getNumberOfSamplesInWindow() {
821         return windowedSamples.size();
822     }
823 
824     /**
825      * Indicates whether estimator is currently running or not.
826      *
827      * @return true if estimator is running, false otherwise.
828      */
829     public boolean isRunning() {
830         return running;
831     }
832 
833     /**
834      * Indicates whether window of samples is filled or not.
835      *
836      * @return true if window is filled, false otherwise.
837      */
838     public boolean isWindowFilled() {
839         return getNumberOfSamplesInWindow() == windowSize;
840     }
841 
842     /**
843      * Adds a triad of measurement samples and processes current window.
844      * Notice that if there are less than {@link #getWindowSize()} processed
845      * samples in the window, the remaining ones are considered to be zero
846      * when average values and standard deviation is estimated.
847      *
848      * @param triad measurement triad to be added and processed.
849      * @throws LockedException if estimator is currently running.
850      */
851     public void addTriadAndProcess(final T triad) throws LockedException {
852         internalAdd(triad, true);
853     }
854 
855     /**
856      * Adds a triad of measurement samples and processes current window.
857      * Values are expressed in measurement default unit (m/s^2 for acceleration, rad/s for
858      * angular speed or T for magnetic flux density).
859      * Notice that if there are less than {@link #getWindowSize()} processed
860      * samples in the window, the remaining ones are considered to be zero
861      * when average values and standard deviation is estimated.
862      *
863      * @param valueX x coordinate of measurement to be added and processed.
864      * @param valueY y coordinate of measurement to be added and processed.
865      * @param valueZ z coordinate of measurement to be added and processed.
866      * @throws LockedException if estimator is currently running.
867      */
868     public void addTriadAndProcess(final double valueX, final double valueY, final double valueZ)
869             throws LockedException {
870         addTriadAndProcess(createTriad(valueX, valueY, valueZ, getDefaultUnit()));
871     }
872 
873     /**
874      * Adds a triad of measurement samples and processes current window.
875      * Notice that if there are less than {@link #getWindowSize()} processed
876      * samples in the window, the remaining ones are considered to be zero
877      * when average values and standard deviation is estimated.
878      *
879      * @param valueX x coordinate of measurement to be added and processed.
880      * @param valueY y coordinate of measurement to be added and processed.
881      * @param valueZ z coordinate of measurement to be added and processed.
882      * @throws LockedException if estimator is currently running.
883      */
884     public void addTriadAndProcess(final M valueX, final M valueY, final M valueZ) throws LockedException {
885         addTriadAndProcess(createTriad(valueX, valueY, valueZ));
886     }
887 
888     /**
889      * Adds a triad of measurement samples without processing current window or updating
890      * result values.
891      * Notice that if there are less than {@link #getWindowSize()} processed
892      * samples in the window, the remaining ones are considered to be zero
893      * when average values and standard deviation is estimated.
894      *
895      * @param triad measurement triad to be added.
896      * @throws LockedException if estimator is currently running.
897      */
898     public void addTriad(final T triad) throws LockedException {
899         internalAdd(triad, false);
900     }
901 
902     /**
903      * Adds a triad of measurement samples without processing current window or updating
904      * result values.
905      * Values are expressed in measurement default unit (m/s^2 for acceleration, rad/s for
906      * angular speed or T for magnetic flux density).
907      * Notice that if there are less than {@link #getWindowSize()} processed
908      * samples in the window, the remaining ones are considered to be zero
909      * when average values and standard deviation is estimated.
910      *
911      * @param valueX x coordinate of measurement to be added.
912      * @param valueY y coordinate of measurement to be added.
913      * @param valueZ z coordinate of measurement to be added.
914      * @throws LockedException if estimator is currently running.
915      */
916     public void addTriad(final double valueX, final double valueY, final double valueZ) throws LockedException {
917         addTriad(createTriad(valueX, valueY, valueZ, getDefaultUnit()));
918     }
919 
920     /**
921      * Adds a triad of measurement samples without processing current window or updating
922      * result values.
923      * Notice that if there are less than {@link #getWindowSize()} processed
924      * samples in the window, the remaining ones are considered to be zero
925      * when average values and standard deviation is estimated.
926      *
927      * @param valueX x coordinate of measurement to be added.
928      * @param valueY y coordinate of measurement to be added.
929      * @param valueZ z coordinate of measurement to be added.
930      * @throws LockedException if estimator is currently running.
931      */
932     public void addTriad(final M valueX, final M valueY, final M valueZ) throws LockedException {
933         addTriad(createTriad(valueX, valueY, valueZ));
934     }
935 
936     /**
937      * Resets current estimator.
938      *
939      * @return true if estimator was successfully reset, false if no reset was needed.
940      * @throws LockedException if estimator is currently running.
941      */
942     public boolean reset() throws LockedException {
943         if (running) {
944             throw new LockedException();
945         }
946 
947         if (numberOfProcessedSamples == 0) {
948             return false;
949         }
950 
951         windowedSamples.clear();
952         avgX = 0.0;
953         avgY = 0.0;
954         avgZ = 0.0;
955         varianceX = 0.0;
956         varianceY = 0.0;
957         varianceZ = 0.0;
958         numberOfProcessedSamples = 0;
959 
960         if (listener != null) {
961             //noinspection unchecked
962             listener.onReset((E) this);
963         }
964 
965         return true;
966     }
967 
968     /**
969      * Creates a copy of a triad.
970      *
971      * @param input triad to be copied.
972      * @return copy of a triad.
973      */
974     protected abstract T copyTriad(final T input);
975 
976     /**
977      * Creates a triad with provided values and unit.
978      *
979      * @param valueX x coordinate value.
980      * @param valueY y coordinate value.
981      * @param valueZ z coordinate value.
982      * @param unit   unit.
983      * @return created triad.
984      */
985     protected abstract T createTriad(final double valueX, final double valueY, final double valueZ, final U unit);
986 
987     /**
988      * Creates a triad with provided values.
989      *
990      * @param valueX x coordinate value.
991      * @param valueY y coordinate value.
992      * @param valueZ z coordinate value.
993      * @return created triad.
994      */
995     protected abstract T createTriad(final M valueX, final M valueY, final M valueZ);
996 
997     /**
998      * Gets default unit for a measurement.
999      *
1000      * @return default unit for a measurement.
1001      */
1002     protected abstract U getDefaultUnit();
1003 
1004     /**
1005      * Creates a measurement with provided value and unit.
1006      *
1007      * @param value value to be set.
1008      * @param unit  unit to be set.
1009      * @return created measurement.
1010      */
1011     protected abstract M createMeasurement(final double value, final U unit);
1012 
1013     /**
1014      * Internally adds a triad of measurement samples and processes current window if indicated.
1015      *
1016      * @param triad   measurement triad to be added.
1017      * @param process true if window of samples must also be processed, false otherwise.
1018      * @throws LockedException if estimator is currently running.
1019      */
1020     private void internalAdd(final T triad, final boolean process) throws LockedException {
1021         if (running) {
1022             throw new LockedException();
1023         }
1024 
1025         running = true;
1026 
1027         if (windowedSamples.isEmpty() && listener != null) {
1028             //noinspection unchecked
1029             listener.onStart((E) this);
1030         }
1031 
1032         final var wasFilled = isWindowFilled();
1033         if (wasFilled) {
1034             // remove first sample
1035             windowedSamples.removeFirst();
1036         }
1037 
1038         windowedSamples.addLast(copyTriad(triad));
1039 
1040         // process window
1041         if (process) {
1042             processWindow();
1043         }
1044 
1045         running = false;
1046 
1047         if (listener != null) {
1048             //noinspection unchecked
1049             listener.onTriadAdded((E) this);
1050 
1051             if (!wasFilled && isWindowFilled()) {
1052                 //noinspection unchecked
1053                 listener.onWindowFilled((E) this);
1054             }
1055         }
1056     }
1057 
1058     /**
1059      * Processes current windowed samples.
1060      */
1061     private void processWindow() {
1062 
1063         numberOfProcessedSamples++;
1064 
1065         // compute averages
1066         var averageX = 0.0;
1067         var averageY = 0.0;
1068         var averageZ = 0.0;
1069         for (final var triad : windowedSamples) {
1070             final var valueX = triad.getValueX();
1071             final var valueY = triad.getValueY();
1072             final var valueZ = triad.getValueZ();
1073 
1074             averageX += valueX;
1075             averageY += valueY;
1076             averageZ += valueZ;
1077         }
1078 
1079         averageX /= windowSize;
1080         averageY /= windowSize;
1081         averageZ /= windowSize;
1082 
1083         // compute variances
1084         var varX = 0.0;
1085         var varY = 0.0;
1086         var varZ = 0.0;
1087         for (final var triad : windowedSamples) {
1088             final var fx = triad.getValueX();
1089             final var fy = triad.getValueY();
1090             final var fz = triad.getValueZ();
1091 
1092             final var diffX = fx - averageX;
1093             final var diffY = fy - averageY;
1094             final var diffZ = fz - averageZ;
1095 
1096             final var diffX2 = diffX * diffX;
1097             final var diffY2 = diffY * diffY;
1098             final var diffZ2 = diffZ * diffZ;
1099 
1100             varX += diffX2;
1101             varY += diffY2;
1102             varZ += diffZ2;
1103         }
1104 
1105         final var m = windowSize - 1;
1106 
1107         varX /= m;
1108         varY /= m;
1109         varZ /= m;
1110 
1111         this.avgX = averageX;
1112         this.avgY = averageY;
1113         this.avgZ = averageZ;
1114 
1115         varianceX = varX;
1116         varianceY = varY;
1117         varianceZ = varZ;
1118     }
1119 }