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 }