All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.irurueta.ar.slam.SlamEstimator Maven / Gradle / Ivy

There is a newer version: 1.3.0
Show newest version
/*
 * Copyright (C) 2016 Alberto Irurueta Carro ([email protected])
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.irurueta.ar.slam;

import com.irurueta.algebra.ArrayUtils;
import com.irurueta.algebra.Matrix;
import com.irurueta.algebra.WrongSizeException;
import com.irurueta.numerical.signal.processing.KalmanFilter;
import com.irurueta.numerical.signal.processing.SignalProcessingException;
import com.irurueta.statistics.InvalidCovarianceMatrixException;
import com.irurueta.statistics.MultivariateNormalDist;

import java.io.Serializable;

/**
 * Estimates position, velocity, acceleration, orientation and angular speed
 * using data from accelerometer and gyroscope.
 *
 * @author Alberto Irurueta Carro ([email protected])
 */
@SuppressWarnings("DuplicatedCode")
public class SlamEstimator extends BaseSlamEstimator
        implements Serializable {

    /**
     * Internal state array length.
     */
    protected static final int STATE_LENGTH = 16;

    /**
     * Length of control array (changes in acceleration and angular speed).
     */
    protected static final int CONTROL_LENGTH = 9;

    /**
     * Length of position measurement, to correct any possible deviations of the
     * system after doing multiple predictions.
     */
    private static final int MEASUREMENT_LENGTH = 3;

    /**
     * Contains device status containing the following values: position-x,
     * position-y, position-z, quaternion-a, quaternion-b, quaternion-c,
     * quaternion-d, linear-velocity-x, linear-velocity-y, linear-velocity-z,
     * linear-acceleration-x, linear-acceleration-y, linear-acceleration-z,
     * angular-velocity-x, angular-velocity-y, angular-velocity-z.
     */
    private final double[] mX;

    /**
     * Control signals containing the following values:
     * linear-velocity-change-x, linear-velocity-change-y,
     * linear-velocity-change-z, linear-acceleration-change-x,
     * linear-acceleration-change-y, linear-acceleration-change-z,
     * angular-velocity-change-x, angular-velocity-change-y,
     * angular-velocity-change-z.
     */
    private final double[] mU;

    /**
     * Jacobian respect x state during prediction (16x16).
     */
    private Matrix mJacobianPredictionX;

    /**
     * Jacobian respect u control during state prediction (16x9).
     */
    private Matrix mJacobianPredictionU;

    /**
     * Column matrix containing mU values to be passed as control values during
     * Kalman filter prediction.
     */
    private Matrix mControl;

    /**
     * Kalman's filter to remove effects of noise.
     */
    private KalmanFilter mKalmanFilter;

    /**
     * Matrix of size 3x16 relating system status with obtained measures.
     * [1   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0]
     * [0   1   0   0   0   0   0   0   0   0   0   0   0   0   0   0]
     * [0   0   1   0   0   0   0   0   0   0   0   0   0   0   0   0]
     */
    private Matrix mMeasurementMatrix;

    /**
     * Measurement data for the Kalman filter in a column matrix.
     * Contains data in the following order:
     * [accelerationX]
     * [accelerationY]
     * [accelerationZ]
     * [angularSpeedX]
     * [angularSpeedY]
     * [angularSpeedZ]
     */
    private Matrix mMeasurement;

    /**
     * Last sample of linear acceleration along x-axis.
     */
    private double mLastAccelerationX;

    /**
     * Last sample of linear acceleration along y-axis.
     */
    private double mLastAccelerationY;

    /**
     * Last sample of linear acceleration along z-axis.
     */
    private double mLastAccelerationZ;

    /**
     * Last sample of angular speed along x-axis.
     */
    private double mLastAngularSpeedX;

    /**
     * Last sample of angular speed along y-axis.
     */
    private double mLastAngularSpeedY;

    /**
     * Last sample of angular speed along z-axis.
     */
    private double mLastAngularSpeedZ;

    /**
     * Last timestamp of a full sample expressed in nanoseconds since the epoch
     * time..
     */
    private long mLastTimestampNanos = -1;

    /**
     * Indicates whether a prediction has been made to initialize the internal
     * Kalman filter. Corrections can only be requested to Kalman filter once
     * a prediction has been made. Attempts to request corrections before having
     * a prediction will be ignored.
     */
    private boolean mPredictionAvailable;

    /**
     * Constructor.
     */
    public SlamEstimator() {
        super();
        mX = new double[STATE_LENGTH];
        // initial value of quaternion.
        mX[3] = 1.0;
        mU = new double[CONTROL_LENGTH];
        try {
            mJacobianPredictionX = new Matrix(STATE_LENGTH, STATE_LENGTH);
            mJacobianPredictionU = new Matrix(STATE_LENGTH, CONTROL_LENGTH);
            mControl = new Matrix(CONTROL_LENGTH, 1);
            mMeasurement = new Matrix(MEASUREMENT_LENGTH, 1);
            mMeasurementMatrix = Matrix.identity(MEASUREMENT_LENGTH,
                    STATE_LENGTH);

        } catch (final WrongSizeException ignore) {
            // never thrown
        }

        try {
            mKalmanFilter = new KalmanFilter(STATE_LENGTH, MEASUREMENT_LENGTH,
                    CONTROL_LENGTH);
            // setup matrix relating position measures with internal status.
            mKalmanFilter.setMeasurementMatrix(mMeasurementMatrix);
        } catch (final SignalProcessingException ignore) {
            // never thrown
        }

        try {
            // set initial Kalman filter state (state pre and pro must be two
            // different instances!)
            mKalmanFilter.getStatePre().fromArray(mX);
            mKalmanFilter.getStatePost().fromArray(mX);

        } catch (final WrongSizeException ignore) {
            // never thrown
        }
    }

    /**
     * Gets covariance matrix of state variables (position, velocity, acceleration, orientation and angular speed).
     * Diagonal elements of matrix returned by this method are in the following order:
     * position-x, position-y, position-z, quaternion-a, quaternion-b, quaternion-c,
     * quaternion-d, linear-velocity-x, linear-velocity-y, linear-velocity-z,
     * linear-acceleration-x, linear-acceleration-y, linear-acceleration-z,
     * angular-velocity-x, angular-velocity-y, angular-velocity-z.
     * Off-diagonal elements correspond to cross-correlation values of diagonal ones.
     *
     * @return covariance matrix of state variables.
     */
    @Override
    public Matrix getStateCovariance() {
        return mKalmanFilter.getStatePre();
    }

    /**
     * Updates covariance matrix of position measures.
     * If null is provided, covariance matrix is not updated.
     *
     * @param positionCovariance new position covariance determining position
     *                           accuracy or null if last available covariance does not need to be
     *                           updated.
     * @throws IllegalArgumentException if provided covariance matrix is not
     *                                  3x3.
     */
    @Override
    public void setPositionCovarianceMatrix(final Matrix positionCovariance) {
        if (positionCovariance != null) {
            mKalmanFilter.setMeasurementNoiseCov(positionCovariance);
        }
    }

    /**
     * Gets current covariance matrix of position measures determining current
     * accuracy of provided position measures.
     *
     * @return covariance matrix of position measures.
     */
    @Override
    public Matrix getPositionCovarianceMatrix() {
        return mKalmanFilter.getMeasurementNoiseCov();
    }

    /**
     * Corrects system state with provided position measure using current
     * position accuracy.
     *
     * @param positionX new position along x axis expressed in meters (m).
     * @param positionY new position along y axis expressed in meters (m).
     * @param positionZ new position along z axis expressed in meters (m).
     */
    @Override
    public void correctWithPositionMeasure(
            final double positionX, final double positionY, final double positionZ) {
        if (!mPredictionAvailable) {
            return;
        }

        if (mListener != null) {
            mListener.onCorrectWithPositionMeasure(this);
        }

        try {
            mMeasurement.setElementAtIndex(0, positionX);
            mMeasurement.setElementAtIndex(1, positionY);
            mMeasurement.setElementAtIndex(2, positionZ);

            updateCorrectedState(mKalmanFilter.correct(mMeasurement));

            // copy corrected state to predicted state
            mKalmanFilter.getStatePost().copyTo(mKalmanFilter.getStatePre());
            mKalmanFilter.getErrorCovPost().copyTo(
                    mKalmanFilter.getErrorCovPre());

        } catch (final SignalProcessingException e) {
            mError = true;
        }

        if (mListener != null) {
            mListener.onCorrectedWithPositionMeasure(this);
        }
    }

    /**
     * Creates an instance of a calibrator to be used with this SLAM estimator.
     *
     * @return a calibrator.
     */
    public static SlamCalibrator createCalibrator() {
        return new SlamCalibrator();
    }

    /**
     * Processes a full sample (accelerometer + gyroscope) to update system
     * state.
     */
    @Override
    protected void processFullSample() {
        if (mListener != null) {
            mListener.onFullSampleReceived(this);
        }

        final long timestamp = getMostRecentTimestampNanos();
        if (mLastTimestampNanos < 0) {
            // first time receiving control data, we just set linear acceleration
            // and angular speed into system state
            mLastAccelerationX = mStateAccelerationX = mX[10] =
                    mAccumulatedAccelerationSampleX;
            mLastAccelerationY = mStateAccelerationY = mX[11] =
                    mAccumulatedAccelerationSampleY;
            mLastAccelerationZ = mStateAccelerationZ = mX[12] =
                    mAccumulatedAccelerationSampleZ;
            mLastAngularSpeedX = mStateAngularSpeedX = mX[13] =
                    mAccumulatedAngularSpeedSampleX;
            mLastAngularSpeedY = mStateAngularSpeedY = mX[14] =
                    mAccumulatedAngularSpeedSampleY;
            mLastAngularSpeedZ = mStateAngularSpeedZ = mX[15] =
                    mAccumulatedAngularSpeedSampleZ;

            try {
                mKalmanFilter.getStatePre().fromArray(mX);
                mKalmanFilter.getStatePost().fromArray(mX);
            } catch (final WrongSizeException ignore) {
                // never thrown
            }

            mLastTimestampNanos = timestamp;

            if (mListener != null) {
                mListener.onFullSampleProcessed(this);
            }

            return;
        }

        final double deltaAccelerationX = mAccumulatedAccelerationSampleX -
                mLastAccelerationX;
        final double deltaAccelerationY = mAccumulatedAccelerationSampleY -
                mLastAccelerationY;
        final double deltaAccelerationZ = mAccumulatedAccelerationSampleZ -
                mLastAccelerationZ;
        final double deltaAngularSpeedX = mAccumulatedAngularSpeedSampleX -
                mLastAngularSpeedX;
        final double deltaAngularSpeedY = mAccumulatedAngularSpeedSampleY -
                mLastAngularSpeedY;
        final double deltaAngularSpeedZ = mAccumulatedAngularSpeedSampleZ -
                mLastAngularSpeedZ;
        final double deltaTimestamp = (timestamp - mLastTimestampNanos) *
                NANOS_TO_SECONDS;

        // when a full sample is received, we update the data model

        // change in linear speed
        mU[0] = mU[1] = mU[2] = 0.0;
        mU[3] = deltaAccelerationX;
        mU[4] = deltaAccelerationY;
        mU[5] = deltaAccelerationZ;
        mU[6] = deltaAngularSpeedX;
        mU[7] = deltaAngularSpeedY;
        mU[8] = deltaAngularSpeedZ;

        if (mCalibrationData != null &&
                mCalibrationData.getControlMean() != null) {
            // if calibrator is available, remove mean to correct possible biases
            ArrayUtils.subtract(mU, mCalibrationData.getControlMean(), mU);
        }

        StatePredictor.predict(mX, mU, deltaTimestamp, mX, mJacobianPredictionX,
                mJacobianPredictionU);

        // update Kalman filter transition matrix taking into account current
        // state
        mKalmanFilter.setTransitionMatrix(mJacobianPredictionX);

        // update control matrix from control vector jacobian
        mKalmanFilter.setControlMatrix(mJacobianPredictionU);

        if (mCalibrationData != null &&
                mCalibrationData.getControlMean() != null &&
                mCalibrationData.getControlCovariance() != null) {
            // if calibrator is available, propagate covariance to set process
            // covariance matrix
            if (mNormalDist == null) {
                mNormalDist = new MultivariateNormalDist(STATE_LENGTH);
            }

            try {
                mCalibrationData.propagateWithControlJacobian(
                        mJacobianPredictionU, mNormalDist);
                // update kalman filter process noise
                final Matrix processNoise = mKalmanFilter.getProcessNoiseCov();

                // copy normal dist covariance into processNoise
                mNormalDist.getCovariance(processNoise);
            } catch (final InvalidCovarianceMatrixException e) {
                // ignore
            }
        }

        try {
            // also predict the state using Kalman filter with current control
            // data
            mControl.fromArray(mU, true);
            updatePredictedState(mKalmanFilter.predict(mControl));

            // copy predicted state to corrected state
            mKalmanFilter.getStatePre().copyTo(mKalmanFilter.getStatePost());
            mKalmanFilter.getErrorCovPre().copyTo(
                    mKalmanFilter.getErrorCovPost());

            mPredictionAvailable = true;
        } catch (final Exception e) {
            mError = true;
        }

        mLastAccelerationX = mStateAccelerationX;
        mLastAccelerationY = mStateAccelerationY;
        mLastAccelerationZ = mStateAccelerationZ;
        mLastAngularSpeedX = mStateAngularSpeedX;
        mLastAngularSpeedY = mStateAngularSpeedY;
        mLastAngularSpeedZ = mStateAngularSpeedZ;
        mLastTimestampNanos = timestamp;

        if (mListener != null) {
            mListener.onFullSampleProcessed(this);
        }
    }

    /**
     * Resets position, linear velocity, linear acceleration, orientation and
     * angular speed to provided values.
     * This method implementation also resets Kalman filter state.
     *
     * @param statePositionX     position along x-axis expressed in meters (m).
     * @param statePositionY     position along y-axis expressed in meters (m).
     * @param statePositionZ     position along z-axis expressed in meters (m).
     * @param stateVelocityX     linear velocity along x-axis expressed in meters
     *                           per second (m/s).
     * @param stateVelocityY     linear velocity along y-axis expressed in meters
     *                           per second (m/s).
     * @param stateVelocityZ     linear velocity along z-axis expressed in meters
     *                           per second (m/s).
     * @param stateAccelerationX linear acceleration along x-axis expressed in
     *                           meters per squared second (m/s^2).
     * @param stateAccelerationY linear acceleration along y-axis expressed in
     *                           meters per squared second (m/s^2).
     * @param stateAccelerationZ linear acceleration along z-axis expressed in
     *                           meters per squared second (m/s^2).
     * @param stateQuaternionA   A value of orientation quaternion.
     * @param stateQuaternionB   B value of orientation quaternion.
     * @param stateQuaternionC   C value of orientation quaternion.
     * @param stateQuaternionD   D value of orientation quaternion.
     * @param stateAngularSpeedX angular speed along x-axis expressed in radians
     *                           per second (rad/s).
     * @param stateAngularSpeedY angular speed along y-axis expressed in radians
     *                           per second (rad/s).
     * @param stateAngularSpeedZ angular speed along z-axis expressed in radians
     *                           per second (rad/s).
     */
    @Override
    protected void reset(
            final double statePositionX, final double statePositionY, final double statePositionZ,
            final double stateVelocityX, final double stateVelocityY, final double stateVelocityZ,
            final double stateAccelerationX, final double stateAccelerationY, final double stateAccelerationZ,
            final double stateQuaternionA, final double stateQuaternionB,
            final double stateQuaternionC, final double stateQuaternionD,
            final double stateAngularSpeedX, final double stateAngularSpeedY, final double stateAngularSpeedZ) {
        super.reset(statePositionX, statePositionY, statePositionZ,
                stateVelocityX, stateVelocityY, stateVelocityZ,
                stateAccelerationX, stateAccelerationY, stateAccelerationZ,
                stateQuaternionA, stateQuaternionB, stateQuaternionC, stateQuaternionD,
                stateAngularSpeedX, stateAngularSpeedY, stateAngularSpeedZ);
        if (mX != null) {
            // position
            mX[0] = statePositionX;
            mX[1] = statePositionY;
            mX[2] = statePositionZ;

            // quaternion
            mX[3] = stateQuaternionA;
            mX[4] = stateQuaternionB;
            mX[5] = stateQuaternionC;
            mX[6] = stateQuaternionD;

            // velocity
            mX[7] = stateVelocityX;
            mX[8] = stateVelocityY;
            mX[9] = stateVelocityZ;

            // linear acceleration
            mX[10] = stateAccelerationX;
            mX[11] = stateAccelerationY;
            mX[12] = stateAccelerationZ;

            // angular speed
            mX[13] = stateAngularSpeedX;
            mX[14] = stateAngularSpeedY;
            mX[15] = stateAngularSpeedZ;

            try {
                // set initial Kalman filter state (state pre and pro must be two
                // different instances!)
                mKalmanFilter.getStatePre().fromArray(mX);
                mKalmanFilter.getStatePost().fromArray(mX);
            } catch (final WrongSizeException ignore) {
                // never thrown
            }
        }
        mError = false;
        mLastTimestampNanos = -1;
        mPredictionAvailable = false;
    }

    /**
     * Updates state data of the device by using state matrix obtained after
     * prediction from Kalman filter.
     * to ensure that state follows proper values (specially on quaternions),
     * we keep x values, which have been predicted using the state predictor,
     * which uses analytical values.
     * We then updated x using latest Kalman filter state for next iteration
     * on state predictor.
     *
     * @param state state matrix obtained from Kalman filter.
     */
    private void updatePredictedState(final Matrix state) {
        // position
        mStatePositionX = mX[0];
        mX[0] = state.getElementAtIndex(0);
        mStatePositionY = mX[1];
        mX[1] = state.getElementAtIndex(1);
        mStatePositionZ = mX[2];
        mX[2] = state.getElementAtIndex(2);

        // quaternion (state predictor is more reliable than Kalman filter, for
        // that reason we ignore predicted quaternion values on Kalman filter and
        // simply keep predicted ones. Besides, typically gyroscope samples are
        // much more reliable than accelerometer ones. For that reason state
        // elements corresponding to quaternion (3 to 6) are not copied into mX
        // array.
        mStateQuaternionA = mX[3];
        mStateQuaternionB = mX[4];
        mStateQuaternionC = mX[5];
        mStateQuaternionD = mX[6];

        // velocity
        mStateVelocityX = mX[7];
        mX[7] = state.getElementAtIndex(7);
        mStateVelocityY = mX[8];
        mX[8] = state.getElementAtIndex(8);
        mStateVelocityZ = mX[9];
        mX[9] = state.getElementAtIndex(9);

        // linear acceleration
        mStateAccelerationX = mX[10];
        mX[10] = state.getElementAtIndex(10);
        mStateAccelerationY = mX[11];
        mX[11] = state.getElementAtIndex(11);
        mStateAccelerationZ = mX[12];
        mX[12] = state.getElementAtIndex(12);

        // angular velocity
        mStateAngularSpeedX = mX[13];
        mX[13] = state.getElementAtIndex(13);
        mStateAngularSpeedY = mX[14];
        mX[14] = state.getElementAtIndex(14);
        mStateAngularSpeedZ = mX[15];
        mX[15] = state.getElementAtIndex(15);
    }

    /**
     * Updates state data of the device by using state matrix obtained from
     * Kalman filter after correction..
     *
     * @param state state matrix obtained from Kalman filter.
     */
    private void updateCorrectedState(final Matrix state) {
        // position
        mStatePositionX = mX[0] =
                state.getElementAtIndex(0);
        mStatePositionY = mX[1] =
                state.getElementAtIndex(1);
        mStatePositionZ = mX[2] =
                state.getElementAtIndex(2);

        // quaternion
        mStateQuaternionA = mX[3] =
                state.getElementAtIndex(3);
        mStateQuaternionB = mX[4] =
                state.getElementAtIndex(4);
        mStateQuaternionC = mX[5] =
                state.getElementAtIndex(5);
        mStateQuaternionD = mX[6] =
                state.getElementAtIndex(6);

        // velocity
        mStateVelocityX = mX[7] =
                state.getElementAtIndex(7);
        mStateVelocityY = mX[8] =
                state.getElementAtIndex(8);
        mStateVelocityZ = mX[9] =
                state.getElementAtIndex(9);

        // linear acceleration
        mStateAccelerationX = mX[10] =
                state.getElementAtIndex(10);
        mStateAccelerationY = mX[11] =
                state.getElementAtIndex(11);
        mStateAccelerationZ = mX[12] =
                state.getElementAtIndex(12);

        // angular velocity
        mStateAngularSpeedX = mX[13] =
                state.getElementAtIndex(13);
        mStateAngularSpeedY = mX[14] =
                state.getElementAtIndex(14);
        mStateAngularSpeedZ = mX[15] =
                state.getElementAtIndex(15);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy