1 /*
2 * Copyright (C) 2016 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.ar.slam;
17
18 import com.irurueta.algebra.ArrayUtils;
19 import com.irurueta.algebra.Matrix;
20 import com.irurueta.algebra.WrongSizeException;
21 import com.irurueta.geometry.Quaternion;
22 import com.irurueta.numerical.signal.processing.KalmanFilter;
23 import com.irurueta.numerical.signal.processing.SignalProcessingException;
24 import com.irurueta.statistics.InvalidCovarianceMatrixException;
25 import com.irurueta.statistics.MultivariateNormalDist;
26
27 import java.io.Serializable;
28
29 /**
30 * Estimates position, velocity, acceleration, orientation and angular speed
31 * using data from accelerometer, gyroscope and magnetic field, assuming a
32 * constant velocity model.
33 * This implementation of BaseSlamEstimator is an improvement respect
34 * SlamEstimator to be able to take into account absolute orientation respect
35 * Earth frame instead of just having a relative orientation respect to the
36 * start time of the estimator.
37 */
38 @SuppressWarnings("DuplicatedCode")
39 public class AbsoluteOrientationConstantVelocityModelSlamEstimator extends
40 AbsoluteOrientationBaseSlamEstimator<AbsoluteOrientationConstantVelocityModelSlamCalibrationData>
41 implements Serializable {
42
43 /**
44 * Internal state array length.
45 */
46 protected static final int STATE_LENGTH = 13;
47
48 /**
49 * Length of control array (changes in acceleration and angular speed).
50 */
51 protected static final int CONTROL_LENGTH = 10;
52
53 /**
54 * Length of position measurement, to correct any possible deviations of the
55 * system after doing multiple predictions.
56 */
57 private static final int MEASUREMENT_LENGTH = 3;
58
59 /**
60 * Contains device status containing the following values: position-x,
61 * position-y, position-z, quaternion-a, quaternion-b, quaternion-c,
62 * quaternion-d, linear-velocity-x, linear-velocity-y, linear-velocity-z,
63 * angular-velocity-x, angular-velocity-y, angular-velocity-z.
64 */
65 private final double[] x;
66
67 /**
68 * Control signals containing the following values:
69 * quaternion-change-a, quaternion-change-b, quaternion-change-c,
70 * quaternion-change-d, linear-velocity-change-x, linear-velocity-change-y,
71 * linear-velocity-change-z, angular-velocity-change-x,
72 * angular-velocity-change-y, angular-velocity-change-z.
73 */
74 private final double[] u;
75
76 /**
77 * Jacobian respect x state during prediction (16x16).
78 */
79 private Matrix jacobianPredictionX;
80
81 /**
82 * Jacobian respect u control during state prediction (16x13).
83 */
84 private Matrix jacobianPredictionU;
85
86 /**
87 * Column matrix containing mU values to be passed as control values during
88 * Kalman filter prediction.
89 */
90 private Matrix control;
91
92 /**
93 * Kalman's filter to remove effects of noise.
94 */
95 private KalmanFilter kalmanFilter;
96
97 /**
98 * Matrix of size 3x13 relating system status with obtained measures.
99 * [1 0 0 0 0 0 0 0 0 0 0 0 0]
100 * [0 1 0 0 0 0 0 0 0 0 0 0 0]
101 * [0 0 1 0 0 0 0 0 0 0 0 0 0]
102 */
103 private Matrix measurementMatrix;
104
105 /**
106 * Measurement data for the Kalman filter in a column matrix.
107 * Contains data in the following order:
108 * [accelerationX]
109 * [accelerationY]
110 * [accelerationZ]
111 * [angularSpeedX]
112 * [angularSpeedY]
113 * [angularSpeedZ]
114 */
115 private Matrix measurement;
116
117 /**
118 * Last sample of absolute orientation.
119 */
120 private final Quaternion lastOrientation = new Quaternion();
121
122 /**
123 * Variation of orientation respect to last sample.
124 */
125 private final Quaternion deltaOrientation = new Quaternion();
126
127 /**
128 * Current state orientation.
129 */
130 private final Quaternion stateOrientation = new Quaternion();
131
132 /**
133 * Last sample of angular speed along x-axis.
134 */
135 private double lastAngularSpeedX;
136
137 /**
138 * Last sample of angular speed along y-axis.
139 */
140 private double lastAngularSpeedY;
141
142 /**
143 * Last sample of angular speed along z-axis.
144 */
145 private double lastAngularSpeedZ;
146
147 /**
148 * Last timestamp of a full sample expressed in nanoseconds since the epoch
149 * time.
150 */
151 private long lastTimestampNanos = -1;
152
153 /**
154 * Indicates whether a prediction has been made to initialize the internal
155 * Kalman filter. Corrections can only be requested to Kalman filter once
156 * a prediction has been made. Attempts to request corrections before having
157 * a prediction will be ignored.
158 */
159 private boolean predictionAvailable;
160
161 /**
162 * Constructor.
163 */
164 public AbsoluteOrientationConstantVelocityModelSlamEstimator() {
165 super();
166 x = new double[STATE_LENGTH];
167 // initial value of quaternion.
168 x[3] = 1.0;
169 u = new double[CONTROL_LENGTH];
170 try {
171 jacobianPredictionX = new Matrix(STATE_LENGTH, STATE_LENGTH);
172 jacobianPredictionU = new Matrix(STATE_LENGTH, CONTROL_LENGTH);
173 control = new Matrix(CONTROL_LENGTH, 1);
174 measurement = new Matrix(MEASUREMENT_LENGTH, 1);
175 measurementMatrix = Matrix.identity(MEASUREMENT_LENGTH, STATE_LENGTH);
176
177 } catch (final WrongSizeException ignore) {
178 // never thrown
179 }
180
181 try {
182 kalmanFilter = new KalmanFilter(STATE_LENGTH, MEASUREMENT_LENGTH, CONTROL_LENGTH);
183 // setup matrix relating position measures with internal status.
184 kalmanFilter.setMeasurementMatrix(measurementMatrix);
185 } catch (final SignalProcessingException ignore) {
186 // never thrown
187 }
188
189 try {
190 // set initial Kalman filter state (state pre and pro must be two
191 // different instances!)
192 kalmanFilter.getStatePre().fromArray(x);
193 kalmanFilter.getStatePost().fromArray(x);
194
195 } catch (final WrongSizeException ignore) {
196 // never thrown
197 }
198 }
199
200 /**
201 * Gets covariance matrix of state variables (position, velocity, acceleration, orientation and
202 * angular speed).
203 * Diagonal elements of matrix returned by this method are in the following order:
204 * position-x, position-y, position-z, quaternion-a, quaternion-b, quaternion-c,
205 * quaternion-d, linear-velocity-x, linear-velocity-y, linear-velocity-z,
206 * angular-velocity-x, angular-velocity-y, angular-velocity-z.
207 * Off-diagonal elements correspond to cross-correlation values of diagonal ones.
208 *
209 * @return covariance matrix of state variables.
210 */
211 @Override
212 public Matrix getStateCovariance() {
213 return kalmanFilter.getStatePre();
214 }
215
216 /**
217 * Updates covariance matrix of position measures.
218 * If null is provided, covariance matrix is not updated.
219 *
220 * @param positionCovariance new position covariance determining position
221 * accuracy or null if last available covariance does not need to be
222 * updated.
223 * @throws IllegalArgumentException if provided covariance matrix is not
224 * 3x3.
225 */
226 @Override
227 public void setPositionCovarianceMatrix(final Matrix positionCovariance) {
228 if (positionCovariance != null) {
229 kalmanFilter.setMeasurementNoiseCov(positionCovariance);
230 }
231 }
232
233 /**
234 * Gets current covariance matrix of position measures determining current
235 * accuracy of provided position measures.
236 *
237 * @return covariance matrix of position measures.
238 */
239 @Override
240 public Matrix getPositionCovarianceMatrix() {
241 return kalmanFilter.getMeasurementNoiseCov();
242 }
243
244 /**
245 * Corrects system state with provided position measure using current
246 * position accuracy.
247 *
248 * @param positionX new position along x-axis expressed in meters (m).
249 * @param positionY new position along y-axis expressed in meters (m).
250 * @param positionZ new position along z-axis expressed in meters (m).
251 */
252 @Override
253 public void correctWithPositionMeasure(final double positionX, final double positionY, final double positionZ) {
254 if (!predictionAvailable) {
255 return;
256 }
257
258 if (listener != null) {
259 listener.onCorrectWithPositionMeasure(this);
260 }
261
262 try {
263 measurement.setElementAtIndex(0, positionX);
264 measurement.setElementAtIndex(1, positionY);
265 measurement.setElementAtIndex(2, positionZ);
266
267 updateCorrectedState(kalmanFilter.correct(measurement));
268
269 // copy corrected state to predicted state
270 kalmanFilter.getStatePost().copyTo(kalmanFilter.getStatePre());
271 kalmanFilter.getErrorCovPost().copyTo(kalmanFilter.getErrorCovPre());
272
273 } catch (final SignalProcessingException e) {
274 error = true;
275 }
276
277 if (listener != null) {
278 listener.onCorrectedWithPositionMeasure(this);
279 }
280 }
281
282 /**
283 * Creates an instance of a calibrator to be used with this SLAM estimator.
284 *
285 * @return a calibrator.
286 */
287 public static AbsoluteOrientationConstantVelocityModelSlamCalibrator createCalibrator() {
288 return new AbsoluteOrientationConstantVelocityModelSlamCalibrator();
289 }
290
291 /**
292 * Processes a full sample (accelerometer + gyroscope) to update system
293 * state.
294 */
295 @Override
296 protected void processFullSample() {
297 if (listener != null) {
298 listener.onFullSampleReceived(this);
299 }
300
301 final long timestamp = getMostRecentTimestampNanos();
302 if (lastTimestampNanos < 0) {
303 // first time receiving control data, we just set linear acceleration
304 // and angular speed into system state
305 stateAccelerationX = accumulatedAccelerationSampleX;
306 stateAccelerationY = accumulatedAccelerationSampleY;
307 stateAccelerationZ = accumulatedAccelerationSampleZ;
308 lastAngularSpeedX = stateAngularSpeedX = x[10] = accumulatedAngularSpeedSampleX;
309 lastAngularSpeedY = stateAngularSpeedY = x[11] = accumulatedAngularSpeedSampleY;
310 lastAngularSpeedZ = stateAngularSpeedZ = x[12] = accumulatedAngularSpeedSampleZ;
311
312 try {
313 kalmanFilter.getStatePre().fromArray(x);
314 kalmanFilter.getStatePost().fromArray(x);
315 } catch (final WrongSizeException ignore) {
316 // never thrown
317 }
318
319 lastTimestampNanos = timestamp;
320
321 if (listener != null) {
322 listener.onFullSampleProcessed(this);
323 }
324
325 return;
326 }
327
328 accumulatedOrientation.normalize();
329
330 lastOrientation.inverse(deltaOrientation);
331 deltaOrientation.combine(accumulatedOrientation);
332 deltaOrientation.normalize();
333
334 final var deltaAngularSpeedX = accumulatedAngularSpeedSampleX - lastAngularSpeedX;
335 final var deltaAngularSpeedY = accumulatedAngularSpeedSampleY - lastAngularSpeedY;
336 final var deltaAngularSpeedZ = accumulatedAngularSpeedSampleZ - lastAngularSpeedZ;
337 final var deltaTimestamp = (timestamp - lastTimestampNanos) * NANOS_TO_SECONDS;
338
339 // when a full sample is received, we update the data model
340 u[0] = deltaOrientation.getA();
341 u[1] = deltaOrientation.getB();
342 u[2] = deltaOrientation.getC();
343 u[3] = deltaOrientation.getD();
344 // change in linear speed
345 u[4] = u[5] = u[6] = 0.0;
346 u[7] = deltaAngularSpeedX;
347 u[8] = deltaAngularSpeedY;
348 u[9] = deltaAngularSpeedZ;
349
350 if (calibrationData != null && calibrationData.getControlMean() != null) {
351 // if calibrator is available, remove mean to correct possible biases
352 ArrayUtils.subtract(u, calibrationData.getControlMean(), u);
353 }
354
355 ConstantVelocityModelStatePredictor.predictWithRotationAdjustment(x, u, deltaTimestamp, x, jacobianPredictionX,
356 jacobianPredictionU);
357
358 // update Kalman filter transition matrix taking into account current
359 // state
360 kalmanFilter.setTransitionMatrix(jacobianPredictionX);
361
362 // update control matrix from control vector jacobian
363 kalmanFilter.setControlMatrix(jacobianPredictionU);
364
365 if (calibrationData != null && calibrationData.getControlMean() != null
366 && calibrationData.getControlCovariance() != null) {
367 // if calibrator is available, propagate covariance to set process
368 // covariance matrix
369 if (normalDist == null) {
370 normalDist = new MultivariateNormalDist(STATE_LENGTH);
371 }
372
373 try {
374 calibrationData.propagateWithControlJacobian(jacobianPredictionU, normalDist);
375 // update kalman filter process noise
376 final var processNoise = kalmanFilter.getProcessNoiseCov();
377
378 // copy normal dist covariance into processNoise
379 normalDist.getCovariance(processNoise);
380 } catch (final InvalidCovarianceMatrixException e) {
381 // ignore
382 }
383 }
384
385 try {
386 // also predict the state using Kalman filter with current control
387 // data
388 control.fromArray(u, true);
389 updatePredictedState(kalmanFilter.predict(control));
390
391 // copy predicted state to corrected state
392 kalmanFilter.getStatePre().copyTo(kalmanFilter.getStatePost());
393 kalmanFilter.getErrorCovPre().copyTo(kalmanFilter.getErrorCovPost());
394
395 predictionAvailable = true;
396 } catch (final Exception e) {
397 error = true;
398 }
399
400 lastOrientation.fromRotation(stateOrientation);
401 lastAngularSpeedX = stateAngularSpeedX;
402 lastAngularSpeedY = stateAngularSpeedY;
403 lastAngularSpeedZ = stateAngularSpeedZ;
404 lastTimestampNanos = timestamp;
405
406 if (listener != null) {
407 listener.onFullSampleProcessed(this);
408 }
409 }
410
411 /**
412 * Resets position, linear velocity, linear acceleration, orientation and
413 * angular speed to provided values.
414 * This method implementation also resets Kalman filter state.
415 *
416 * @param statePositionX position along x-axis expressed in meters (m).
417 * @param statePositionY position along y-axis expressed in meters (m).
418 * @param statePositionZ position along z-axis expressed in meters (m).
419 * @param stateVelocityX linear velocity along x-axis expressed in meters
420 * per second (m/s).
421 * @param stateVelocityY linear velocity along y-axis expressed in meters
422 * per second (m/s).
423 * @param stateVelocityZ linear velocity along z-axis expressed in meters
424 * per second (m/s).
425 * @param stateAccelerationX linear acceleration along x-axis expressed in
426 * meters per squared second (m/s^2).
427 * @param stateAccelerationY linear acceleration along y-axis expressed in
428 * meters per squared second (m/s^2).
429 * @param stateAccelerationZ linear acceleration along z-axis expressed in
430 * meters per squared second (m/s^2).
431 * @param stateQuaternionA A value of orientation quaternion.
432 * @param stateQuaternionB B value of orientation quaternion.
433 * @param stateQuaternionC C value of orientation quaternion.
434 * @param stateQuaternionD D value of orientation quaternion.
435 * @param stateAngularSpeedX angular speed along x-axis expressed in radians
436 * per second (rad/s).
437 * @param stateAngularSpeedY angular speed along y-axis expressed in radians
438 * per second (rad/s).
439 * @param stateAngularSpeedZ angular speed along z-axis expressed in radians
440 * per second (rad/s).
441 */
442 @Override
443 protected void reset(
444 final double statePositionX, final double statePositionY, final double statePositionZ,
445 final double stateVelocityX, final double stateVelocityY, final double stateVelocityZ,
446 final double stateAccelerationX, final double stateAccelerationY, final double stateAccelerationZ,
447 final double stateQuaternionA, final double stateQuaternionB,
448 final double stateQuaternionC, final double stateQuaternionD,
449 final double stateAngularSpeedX, final double stateAngularSpeedY, final double stateAngularSpeedZ) {
450 super.reset(statePositionX, statePositionY, statePositionZ, stateVelocityX, stateVelocityY, stateVelocityZ,
451 stateAccelerationX, stateAccelerationY, stateAccelerationZ,
452 stateQuaternionA, stateQuaternionB, stateQuaternionC, stateQuaternionD,
453 stateAngularSpeedX, stateAngularSpeedY, stateAngularSpeedZ);
454 //noinspection ConstantValue
455 if (stateOrientation != null) {
456 stateOrientation.setA(stateQuaternionA);
457 stateOrientation.setB(stateQuaternionB);
458 stateOrientation.setC(stateQuaternionC);
459 stateOrientation.setD(stateQuaternionD);
460 }
461
462 //noinspection ConstantValue
463 if (lastOrientation != null) {
464 lastOrientation.setA(1.0);
465 lastOrientation.setB(0.0);
466 lastOrientation.setC(0.0);
467 lastOrientation.setD(0.0);
468 }
469
470 if (x != null) {
471 // position
472 x[0] = statePositionX;
473 x[1] = statePositionY;
474 x[2] = statePositionZ;
475
476 // quaternion
477 x[3] = stateQuaternionA;
478 x[4] = stateQuaternionB;
479 x[5] = stateQuaternionC;
480 x[6] = stateQuaternionD;
481
482 // velocity
483 x[7] = stateVelocityX;
484 x[8] = stateVelocityY;
485 x[9] = stateVelocityZ;
486
487 // angular speed
488 x[10] = stateAngularSpeedX;
489 x[11] = stateAngularSpeedY;
490 x[12] = stateAngularSpeedZ;
491
492 try {
493 // set initial Kalman filter state (state pre and pro must be two
494 // different instances!)
495 kalmanFilter.getStatePre().fromArray(x);
496 kalmanFilter.getStatePost().fromArray(x);
497 } catch (final WrongSizeException ignore) {
498 // never thrown
499 }
500 }
501
502 error = false;
503 lastTimestampNanos = -1;
504 predictionAvailable = false;
505 }
506
507 /**
508 * Updates state data of the device by using state matrix obtained after
509 * prediction from Kalman filter.
510 * to ensure that state follows proper values (specially on quaternions),
511 * we keep x values, which have been predicted using the state predictor,
512 * which uses analytical values.
513 * We then updated x using latest Kalman filter state for next iteration
514 * on state predictor.
515 *
516 * @param state state matrix obtained from Kalman filter.
517 */
518 private void updatePredictedState(Matrix state) {
519 // position
520 statePositionX = x[0];
521 x[0] = state.getElementAtIndex(0);
522 statePositionY = x[1];
523 x[1] = state.getElementAtIndex(1);
524 statePositionZ = x[2];
525 x[2] = state.getElementAtIndex(2);
526
527 // quaternion state predictor is more reliable than Kalman filter, for
528 // that reason we ignore predicted quaternion values on Kalman filter and
529 // simply keep predicted ones. Besides, typically gyroscope samples are
530 // much more reliable than accelerometer ones. For that reason state
531 // elements corresponding to quaternion (3 to 6) are not copied into mX
532 // array.
533 stateQuaternionA = x[3];
534 stateQuaternionB = x[4];
535 stateQuaternionC = x[5];
536 stateQuaternionD = x[6];
537
538 stateOrientation.setA(stateQuaternionA);
539 stateOrientation.setB(stateQuaternionB);
540 stateOrientation.setC(stateQuaternionC);
541 stateOrientation.setD(stateQuaternionD);
542
543 // velocity
544 stateVelocityX = x[7];
545 x[7] = state.getElementAtIndex(7);
546 stateVelocityY = x[8];
547 x[8] = state.getElementAtIndex(8);
548 stateVelocityZ = x[9];
549 x[9] = state.getElementAtIndex(9);
550
551 // linear acceleration
552 stateAccelerationX = accumulatedAccelerationSampleX;
553 stateAccelerationY = accumulatedAccelerationSampleY;
554 stateAccelerationZ = accumulatedAccelerationSampleZ;
555
556 // angular velocity
557 stateAngularSpeedX = x[10];
558 x[10] = state.getElementAtIndex(10);
559 stateAngularSpeedY = x[11];
560 x[11] = state.getElementAtIndex(11);
561 stateAngularSpeedZ = x[12];
562 x[12] = state.getElementAtIndex(12);
563 }
564
565 /**
566 * Updates state data of the device by using state matrix obtained from
567 * Kalman filter after correction.
568 *
569 * @param state state matrix obtained from Kalman filter.
570 */
571 private void updateCorrectedState(final Matrix state) {
572 // position
573 statePositionX = x[0] = state.getElementAtIndex(0);
574 statePositionY = x[1] = state.getElementAtIndex(1);
575 statePositionZ = x[2] = state.getElementAtIndex(2);
576
577 // quaternion
578 stateQuaternionA = x[3] = state.getElementAtIndex(3);
579 stateQuaternionB = x[4] = state.getElementAtIndex(4);
580 stateQuaternionC = x[5] = state.getElementAtIndex(5);
581 stateQuaternionD = x[6] = state.getElementAtIndex(6);
582
583 stateOrientation.setA(stateQuaternionA);
584 stateOrientation.setB(stateQuaternionB);
585 stateOrientation.setC(stateQuaternionC);
586 stateOrientation.setD(stateQuaternionD);
587 stateOrientation.normalize();
588
589 // velocity
590 stateVelocityX = x[7] = state.getElementAtIndex(7);
591 stateVelocityY = x[8] = state.getElementAtIndex(8);
592 stateVelocityZ = x[9] = state.getElementAtIndex(9);
593
594 // linear acceleration (is not contained in state, thus it is not
595 // corrected)
596
597 // angular velocity
598 stateAngularSpeedX = x[10] = state.getElementAtIndex(10);
599 stateAngularSpeedY = x[10] = state.getElementAtIndex(10);
600 stateAngularSpeedZ = x[10] = state.getElementAtIndex(10);
601 }
602 }