View Javadoc
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 }