diff --git a/ihmc-parameter-estimation/src/main/java/us/ihmc/parameterEstimation/ExtendedKalmanFilter.java b/ihmc-parameter-estimation/src/main/java/us/ihmc/parameterEstimation/ExtendedKalmanFilter.java
index 35ce3cc8dee1..9a9a1cbe01d7 100644
--- a/ihmc-parameter-estimation/src/main/java/us/ihmc/parameterEstimation/ExtendedKalmanFilter.java
+++ b/ihmc-parameter-estimation/src/main/java/us/ihmc/parameterEstimation/ExtendedKalmanFilter.java
@@ -6,6 +6,7 @@
import org.ejml.dense.row.CommonOps_DDRM;
import org.ejml.dense.row.MatrixFeatures_DDRM;
import org.ejml.dense.row.factory.LinearSolverFactory_DDRM;
+import us.ihmc.matrixlib.MatrixTools;
/**
* This class provides an implementation of an Extended Kalman Filter (EKF). The EKF is a recursive filter that uses a linearization of the process and
@@ -13,6 +14,22 @@
*
* This implementation is for state-only systems, no input -- or if you prefer: x_dot = f(x), not x_dot = f(x, u).
*
+ *
+ * To utilize this filter, the user must extend this class and provide implementations of the process and measurement models by overriding the abstract methods:
+ *
+ * - {@link #processModel(DMatrixRMaj)}
+ * - {@link #measurementModel(DMatrixRMaj)}
+ *
+ * Then, to get updates, the user calls {@link #calculateEstimate(DMatrix)} with the observation at the current time step. This will return the estimated
+ * state based on the observations.
+ *
+ *
+ * For example implementations, see:
+ *
+ * - {@link us.ihmc.parameterEstimation.examples.ExamplePendulumExtendedKalmanFilter}
+ * - {@link us.ihmc.parameterEstimation.examples.ExampleConstantVelocity2DKalmanFilter}
+ *
+ *
*
* @author James Foster
*/
@@ -66,7 +83,7 @@ public abstract class ExtendedKalmanFilter
/**
* The residual 'y' between the actual observation (i.e. direct from the sensors), and the predicted measurement from the prediction step of the filter.
*/
- private final DMatrixRMaj measurementResidual;
+ protected final DMatrixRMaj measurementResidual;
/**
* Container variable used for garbage free operations.
*/
@@ -74,7 +91,23 @@ public abstract class ExtendedKalmanFilter
/**
* Covariance matrix 'S' of the linearized measurement model, used in the update step of the filter.
*/
- private final DMatrixRMaj residualCovariance;
+ protected final DMatrixRMaj residualCovariance;
+ /**
+ * Masked version of the residual, mask^T * y
+ */
+ protected final DMatrixRMaj maskedMeasurementResidual;
+ /**
+ * Masked version of the measurement covariance, mask^T * R * mask, used to reject outlier measurements from the filter
+ */
+ protected final DMatrixRMaj maskedMeasurementCovariance;
+ /**
+ * Masked version of the covariance, mask^T * S * mask, used to reject outlier measurements from the filter
+ */
+ protected final DMatrixRMaj maskedResidualCovariance;
+ /**
+ * Masked measurement jacobian, mask^T * H
+ */
+ protected final DMatrixRMaj maskedMeasurementJacobian;
/**
* Container variable used to hold the inverse of the residual covariance matrix.
*/
@@ -92,7 +125,10 @@ public abstract class ExtendedKalmanFilter
* Container variable used for garbage free operations.
*/
private final DMatrixRMaj kalmanGainContainer;
-
+ /**
+ * This is the update to the predicted state, based on the Kalman feedback.
+ */
+ protected final DMatrixRMaj stateCorrection;
/**
* The predicted estimate of the state according to the measurement model of the filter.
*/
@@ -123,32 +159,47 @@ public abstract class ExtendedKalmanFilter
/** Terms used for calculating the Joseph form of the covariance update. */
private final DMatrixRMaj josephTransposeTerm;
- private final DMatrixRMaj josephTransposeTermContainer;
- private final DMatrixRMaj josephIdentity;
- private final DMatrixRMaj josephGainTerm;
private final DMatrixRMaj josephGainTermContainer;
+ private final int stateSize;
+ private final int errorSize;
+ private final int measurementSize;
+
public ExtendedKalmanFilter(int stateSize, int measurementSize)
{
- processCovariance = new DMatrixRMaj(stateSize, stateSize);
+ this(stateSize, stateSize, measurementSize);
+ }
+
+ public ExtendedKalmanFilter(int stateSize, int errorSize, int measurementSize)
+ {
+ this.stateSize = stateSize;
+ this.errorSize = errorSize;
+ this.measurementSize = measurementSize;
+
+ processCovariance = new DMatrixRMaj(errorSize, errorSize);
measurementCovariance = new DMatrixRMaj(measurementSize, measurementSize);
+ maskedMeasurementCovariance = new DMatrixRMaj(measurementSize, measurementSize);
- processJacobian = new DMatrixRMaj(stateSize, stateSize);
- measurementJacobian = new DMatrixRMaj(measurementSize, stateSize);
+ processJacobian = new DMatrixRMaj(stateSize, errorSize);
+ measurementJacobian = new DMatrixRMaj(measurementSize, errorSize);
+ maskedMeasurementJacobian = new DMatrixRMaj(measurementSize, errorSize);
state = new DMatrixRMaj(stateSize, 1);
predictedState = new DMatrixRMaj(stateSize, 1);
- covariance = new DMatrixRMaj(stateSize, stateSize);
- predictedCovariance = new DMatrixRMaj(stateSize, stateSize);
- predictedCovarianceContainer = new DMatrixRMaj(stateSize, stateSize);
+ covariance = new DMatrixRMaj(errorSize, errorSize);
+ predictedCovariance = new DMatrixRMaj(errorSize, errorSize);
+ predictedCovarianceContainer = new DMatrixRMaj(errorSize, errorSize);
measurementResidual = new DMatrixRMaj(measurementSize, 1);
- residualCovarianceContainer = new DMatrixRMaj(measurementSize, measurementSize);
+ maskedMeasurementResidual = new DMatrixRMaj(measurementSize, 1);
+ residualCovarianceContainer = new DMatrixRMaj(errorSize, errorSize);
residualCovariance = new DMatrixRMaj(measurementSize, measurementSize);
+ maskedResidualCovariance = new DMatrixRMaj(measurementSize, measurementSize);
inverseResidualCovariance = new DMatrixRMaj(measurementSize, measurementSize);
solver = new LinearSolverSafe<>(LinearSolverFactory_DDRM.symmPosDef(measurementSize));
kalmanGain = new DMatrixRMaj(stateSize, measurementSize);
kalmanGainContainer = new DMatrixRMaj(stateSize, measurementSize);
+ stateCorrection = new DMatrixRMaj(errorSize, 1);
updatedState = new DMatrixRMaj(stateSize, 1);
updatedCovariance = new DMatrixRMaj(stateSize, stateSize);
updatedCovarianceContainer = new DMatrixRMaj(stateSize, stateSize);
@@ -156,11 +207,8 @@ public ExtendedKalmanFilter(int stateSize, int measurementSize)
normalizedInnovation = new DMatrixRMaj(measurementSize, 1);
normalizedInnovationContainer = new DMatrixRMaj(measurementSize, 1);
- josephTransposeTerm = new DMatrixRMaj(stateSize, stateSize);
- josephTransposeTermContainer = new DMatrixRMaj(stateSize, stateSize);
- josephIdentity = CommonOps_DDRM.identity(stateSize);
- josephGainTerm = new DMatrixRMaj(stateSize, stateSize);
- josephGainTermContainer = new DMatrixRMaj(stateSize, stateSize);
+ josephTransposeTerm = new DMatrixRMaj(errorSize, errorSize);
+ josephGainTermContainer = new DMatrixRMaj(errorSize, errorSize);
}
public ExtendedKalmanFilter(DMatrixRMaj initialState, DMatrixRMaj initialCovariance, DMatrixRMaj processCovariance, DMatrixRMaj measurementCovariance)
@@ -188,6 +236,26 @@ public ExtendedKalmanFilter(DMatrixRMaj initialState, DMatrixRMaj initialCovaria
covariance.set(initialCovariance);
}
+ public void initializeState(DMatrixRMaj state)
+ {
+ this.state.set(state);
+ }
+
+ public int getStateSize()
+ {
+ return stateSize;
+ }
+
+ public int getErrorSize()
+ {
+ return errorSize;
+ }
+
+ public int getMeasurementSize()
+ {
+ return measurementSize;
+ }
+
/**
* Runs the filter for a single time step.
*
@@ -280,6 +348,15 @@ private void applyInnovationGate()
covariance.set(predictedCovariance);
}
+ /**
+ * This is broken into its own function, in case the state exists on some manifold. This is the case for, for example, quaternions. In that event, this
+ * function should overriden.
+ */
+ protected void applyStateCorrection(DMatrixRMaj state, DMatrixRMaj modification, DMatrixRMaj modifiedState)
+ {
+ CommonOps_DDRM.add(state, modification, modifiedState);
+ }
+
/**
* If the observation is valid, the actual observation, as well as the measurement model and its noise profile, are used to correct the
* prediction made in the {@link #predictionStep()}.
@@ -289,8 +366,9 @@ protected void updateStep()
calculateKalmanGain();
// x_(k|k) = x_(k|k-1) + K_k * y_k
- updatedState.set(predictedState);
- CommonOps_DDRM.multAdd(kalmanGain, measurementResidual, updatedState);
+ CommonOps_DDRM.mult(kalmanGain, maskedMeasurementResidual, stateCorrection);
+ // We want to do this, in case the state exists on a manifold (e.g. quaternion)
+ applyStateCorrection(predictedState, stateCorrection, updatedState);
calculateUpdatedCovariance();
state.set(updatedState);
@@ -300,20 +378,33 @@ protected void updateStep()
private void calculateResidualCovarianceAndInverse()
{
// S_k = H_k * P_(k|k-1) * H_k^T + R_k
- residualCovariance.zero();
CommonOps_DDRM.mult(measurementJacobian, predictedCovariance, residualCovarianceContainer);
CommonOps_DDRM.multTransB(residualCovarianceContainer, measurementJacobian, residualCovariance);
CommonOps_DDRM.addEquals(residualCovariance, measurementCovariance);
- solver.setA(residualCovariance);
- solver.invert(inverseResidualCovariance);
+ maskResidualCovarianceForOutlierRejection();
+
+ solver.setA(maskedResidualCovariance);
+ // fixme this doesn't work.
+ CommonOps_DDRM.invert(maskedResidualCovariance, inverseResidualCovariance);
+// solver.invert(inverseResidualCovariance);
+ }
+
+ /**
+ * If the user wants to reject outlier measurements, they can override this method to modify the masked variables. By default, no masking is done.
+ */
+ protected void maskResidualCovarianceForOutlierRejection()
+ {
+ maskedResidualCovariance.set(residualCovariance);
+ maskedMeasurementCovariance.set(measurementCovariance);
+ maskedMeasurementResidual.set(measurementResidual);
+ maskedMeasurementJacobian.set(measurementJacobian);
}
protected void calculateKalmanGain()
{
// K_k = P_(k|k-1) * H_k^T * S_k^-1
- kalmanGain.zero();
- CommonOps_DDRM.multTransB(predictedCovariance, measurementJacobian, kalmanGainContainer);
+ CommonOps_DDRM.multTransB(predictedCovariance, maskedMeasurementJacobian, kalmanGainContainer);
CommonOps_DDRM.mult(kalmanGainContainer, inverseResidualCovariance, kalmanGain);
}
@@ -324,28 +415,22 @@ protected void calculateUpdatedCovariance()
{
// Joseph form of covariance update is apparently more numerically stable
// P_(k|k) = (I - K_k * H_k) * P_(k|k-1) * (I - K_k * H_k)^T + K_k * R_k * K_k^T
- josephGainTerm.set(kalmanGain);
- CommonOps_DDRM.mult(josephGainTerm, measurementCovariance, josephGainTermContainer);
- CommonOps_DDRM.multTransB(josephGainTermContainer, kalmanGain, josephGainTerm);
+ CommonOps_DDRM.mult(kalmanGain, maskedMeasurementCovariance, josephGainTermContainer);
+ CommonOps_DDRM.multTransB(josephGainTermContainer, kalmanGain, updatedCovariance);
- josephTransposeTermContainer.set(kalmanGain);
- CommonOps_DDRM.mult(josephTransposeTermContainer, measurementJacobian, josephTransposeTerm);
- CommonOps_DDRM.scale(-1.0, josephTransposeTerm);
- CommonOps_DDRM.addEquals(josephTransposeTerm, josephIdentity);
+ CommonOps_DDRM.mult(-1.0, kalmanGain, maskedMeasurementJacobian, josephTransposeTerm);
+ MatrixTools.addDiagonal(josephTransposeTerm, 1.0);
// Now put these terms into the covariance update variables
CommonOps_DDRM.multTransB(predictedCovariance, josephTransposeTerm, updatedCovarianceContainer);
- CommonOps_DDRM.mult(josephTransposeTerm, updatedCovarianceContainer, updatedCovariance);
- CommonOps_DDRM.addEquals(updatedCovariance, josephGainTerm);
+ CommonOps_DDRM.multAdd(josephTransposeTerm, updatedCovarianceContainer, updatedCovariance);
}
else // DEFAULT
{
// P_(k|k) = (I - K_k * H_k) * P_(k|k-1)
- updatedCovariance.set(kalmanGain);
- CommonOps_DDRM.mult(updatedCovariance, measurementJacobian, updatedCovarianceContainer);
+ CommonOps_DDRM.mult(-1.0, kalmanGain, measurementJacobian, updatedCovarianceContainer);
+ MatrixTools.addDiagonal(updatedCovarianceContainer, 1.0);
CommonOps_DDRM.mult(updatedCovarianceContainer, predictedCovariance, updatedCovariance);
- CommonOps_DDRM.scale(-1.0, updatedCovariance);
- CommonOps_DDRM.addEquals(updatedCovariance, predictedCovariance);
}
}
@@ -361,8 +446,8 @@ protected void calculateUpdatedCovariance()
*/
private double calculateNormalizedInnovation()
{
- CommonOps_DDRM.mult(inverseResidualCovariance, measurementResidual, normalizedInnovationContainer);
- CommonOps_DDRM.multTransA(measurementResidual, normalizedInnovationContainer, normalizedInnovation);
+ CommonOps_DDRM.mult(inverseResidualCovariance, maskedMeasurementResidual, normalizedInnovationContainer);
+ CommonOps_DDRM.multTransA(maskedMeasurementResidual, normalizedInnovationContainer, normalizedInnovation);
return normalizedInnovation.getData()[0];
}
@@ -424,6 +509,11 @@ public double getNormalizedInnovation()
return normalizedInnovation.getData()[0];
}
+ public DMatrixRMaj getCovariance()
+ {
+ return covariance;
+ }
+
public void setNormalizedInnovationThreshold(double normalizedInnovationThreshold)
{
this.normalizedInnovationThreshold = normalizedInnovationThreshold;
diff --git a/ihmc-parameter-estimation/src/main/java/us/ihmc/parameterEstimation/examples/ExampleConstantVelocity2DKalmanFilter.java b/ihmc-parameter-estimation/src/main/java/us/ihmc/parameterEstimation/examples/ExampleConstantVelocity2DKalmanFilter.java
new file mode 100644
index 000000000000..264adf477c08
--- /dev/null
+++ b/ihmc-parameter-estimation/src/main/java/us/ihmc/parameterEstimation/examples/ExampleConstantVelocity2DKalmanFilter.java
@@ -0,0 +1,74 @@
+package us.ihmc.parameterEstimation.examples;
+
+import org.ejml.data.DMatrixRMaj;
+import org.ejml.dense.row.CommonOps_DDRM;
+import us.ihmc.parameterEstimation.ExtendedKalmanFilter;
+
+public class ExampleConstantVelocity2DKalmanFilter extends ExtendedKalmanFilter
+{
+ static final int stateSize = 4;
+ static final int measurementSize = 2;
+
+ // Start at (x,y) = (0,0) with no velocity
+ static final DMatrixRMaj x0 = new DMatrixRMaj(new double[] {0.0, 0.0, 0.0, 0.0});
+
+ // Position is barely affected by noise, velocity is affected by noise
+ static final DMatrixRMaj Q = new DMatrixRMaj(new double[][] {{1e-6, 0.0, 0.0, 0.0},
+ {0.0, 1e-6, 0.0, 0.0},
+ {0.0, 0.0, 1e-6, 0.0},
+ {0.0, 0.0, 0.0, 1e-6}});
+
+ // Both x and y position measurements are corrupted by noise
+ static final DMatrixRMaj R = new DMatrixRMaj(new double[][] {{1, 0.0}, {0.0, 1}});
+
+ // Ignorant initial guess on P0, we assume we're more certain about positions than velocities
+ static final DMatrixRMaj P0 = new DMatrixRMaj(new double[][] {{0.1, 0.0, 0.0, 0.0},
+ {0.0, 1.0, 0.0, 0.0},
+ {0.0, 0.0, 0.1, 0.0},
+ {0.0, 0.0, 0.0, 1.0}});
+
+ static final double dt = 0.01;
+
+ // Constant velocity model of a 2D planar system
+ static final DMatrixRMaj A = new DMatrixRMaj(new double[][] {{1.0, dt, 0.0, 0.0}, {0.0, 1.0, 0.0, 0.0}, {0.0, 0.0, 1.0, dt}, {0.0, 0.0, 0.0, 1.0}});
+
+ // We only measure positions, not velocities
+ static final DMatrixRMaj C = new DMatrixRMaj(new double[][] {{1.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 1.0, 0.0}});
+
+ public ExampleConstantVelocity2DKalmanFilter()
+ {
+ super(x0, P0, Q, R);
+ }
+
+ // In the case of a linear process model, the linearization is just the A matrix of the process model
+ @Override
+ public DMatrixRMaj linearizeProcessModel(DMatrixRMaj previousState)
+ {
+ return A;
+ }
+
+ // In the case of a linear measurement model, the linearization is just the C matrix of the measurement model
+ @Override
+ public DMatrixRMaj linearizeMeasurementModel(DMatrixRMaj predictedState)
+ {
+ return C;
+ }
+
+ // y = Ax
+ @Override
+ public DMatrixRMaj processModel(DMatrixRMaj state)
+ {
+ DMatrixRMaj nextState = new DMatrixRMaj(stateSize, 1);
+ CommonOps_DDRM.mult(A, state, nextState);
+ return nextState;
+ }
+
+ // y = Cx
+ @Override
+ public DMatrixRMaj measurementModel(DMatrixRMaj state)
+ {
+ DMatrixRMaj measurement = new DMatrixRMaj(measurementSize, 1);
+ CommonOps_DDRM.mult(C, state, measurement);
+ return measurement;
+ }
+}
diff --git a/ihmc-parameter-estimation/src/main/java/us/ihmc/parameterEstimation/examples/ExamplePendulumExtendedKalmanFilter.java b/ihmc-parameter-estimation/src/main/java/us/ihmc/parameterEstimation/examples/ExamplePendulumExtendedKalmanFilter.java
new file mode 100644
index 000000000000..7bc9cf752727
--- /dev/null
+++ b/ihmc-parameter-estimation/src/main/java/us/ihmc/parameterEstimation/examples/ExamplePendulumExtendedKalmanFilter.java
@@ -0,0 +1,136 @@
+package us.ihmc.parameterEstimation.examples;
+
+import org.ejml.data.DMatrixRMaj;
+import us.ihmc.parameterEstimation.ExtendedKalmanFilter;
+
+public class ExamplePendulumExtendedKalmanFilter extends ExtendedKalmanFilter
+{
+ static final int stateSize = 2;
+ static final int measurementSize = 1;
+
+ // Picking an arbitrary nonzero initial condition since 0 is a singularity for the measurement
+ private static final DMatrixRMaj x0 = new DMatrixRMaj(new double[] {0.1, 0.1});
+
+ // Process noise
+ private static final DMatrixRMaj Q = new DMatrixRMaj(new double[][] {{1e-6, 0.0}, {0.0, 1e-6}});
+
+ // Measurement noise
+ private static final DMatrixRMaj R = new DMatrixRMaj(new double[][] {{1e-3}});
+
+ // Ignorant initial guess on P0, we assume we're more certain about positions than velocities
+ private static final DMatrixRMaj P0 = new DMatrixRMaj(new double[][] {{0.1, 0.0}, {0.0, 1.0}});
+
+ // Linearized process model dynamics ( Jacobian of f(x) where x[k+1] = f(x[k]) )
+ final DMatrixRMaj F = new DMatrixRMaj(stateSize, stateSize);
+
+ // Linearized measurement model dynamics (Jacobian of h(x) where y = h(x))
+ final DMatrixRMaj H = new DMatrixRMaj(measurementSize, stateSize);
+
+ private double I = 0.006; // inertia [kg m^2]
+ private double m = 0.2; // mass [kg]
+ private double b = 0.02; // damping coefficient (not pendulum) [N/s]
+ private double g = 9.8; // acceleration due to gravity [m/s^2]
+ private double l = 0.3; // length to center of mass [m]
+ private double dt = 0.01; // integration dt [s]
+
+ public ExamplePendulumExtendedKalmanFilter()
+ {
+ super(x0, P0, Q, R);
+ }
+
+ public void setModelProperties(double m, double I, double l, double b, double g, double dt)
+ {
+ this.m = m;
+ this.I = I;
+ this.l = l;
+ this.b = b;
+ this.g = g;
+ this.dt = dt;
+ }
+
+ public DMatrixRMaj getQ()
+ {
+ return Q;
+ }
+
+ public DMatrixRMaj getR()
+ {
+ return R;
+ }
+
+ public DMatrixRMaj getF()
+ {
+ return F;
+ }
+
+ public DMatrixRMaj getH()
+ {
+ return H;
+ }
+
+ // Linearize f(x) to obtain it's Jacobian matrix, F(x)
+ @Override
+ public DMatrixRMaj linearizeProcessModel(DMatrixRMaj previousState)
+ {
+ double q = previousState.get(0, 0);
+
+ F.set(0, 0, 1);
+ F.set(0, 1, dt);
+ F.set(1, 0, -(dt * m * g * l * Math.cos(q)) / I);
+ F.set(1, 1, 1 - dt * b / I);
+
+ return F;
+ }
+
+ // Linearize h(x) to obtain it's Jacobian matrix, H(x)
+ @Override
+ public DMatrixRMaj linearizeMeasurementModel(DMatrixRMaj predictedState)
+ {
+ double q = predictedState.get(0, 0);
+ double qDot = predictedState.get(1, 0);
+
+ H.set(0, 0, m * g * l * Math.sin(q));
+ H.set(0, 1, I * qDot);
+
+ return H;
+ }
+
+ // x[k+1] = f(x[k])
+ @Override
+ public DMatrixRMaj processModel(DMatrixRMaj state)
+ {
+ return getNextState(state);
+ }
+
+ // y = h(x)
+ @Override
+ public DMatrixRMaj measurementModel(DMatrixRMaj state)
+ {
+ return getMeasurement(state);
+ }
+
+ DMatrixRMaj getNextState(DMatrixRMaj state)
+ {
+ DMatrixRMaj nextState = new DMatrixRMaj(state.numRows, 1);
+ double q = state.get(0, 0);
+ double qDot = state.get(1, 0);
+
+ // Discretized with Forward Euler
+ nextState.set(0, 0, q + dt * qDot);
+ nextState.set(1, 0, qDot - dt * (b * qDot + m * g * l * Math.sin(q)) / I);
+
+ return nextState;
+ }
+
+ DMatrixRMaj getMeasurement(DMatrixRMaj state)
+ {
+ DMatrixRMaj measurement = new DMatrixRMaj(ExamplePendulumExtendedKalmanFilter.measurementSize, 1);
+ double q = state.get(0, 0);
+ double qDot = state.get(1, 0);
+
+ // Measurement is the total energy in the system, which will be dissipated with nonzero damping.
+ measurement.set(0, 0, 0.5 * I * qDot * qDot + m * g * l * (1 - Math.cos(q)));
+
+ return measurement;
+ }
+}
diff --git a/ihmc-parameter-estimation/src/test/java/us/ihmc/parameterEstimation/examples/ConstantVelocity2DExtendedKalmanFilterDemo.java b/ihmc-parameter-estimation/src/test/java/us/ihmc/parameterEstimation/examples/ConstantVelocity2DExtendedKalmanFilterDemo.java
index d7d40f8317bd..cd199e0b5f28 100644
--- a/ihmc-parameter-estimation/src/test/java/us/ihmc/parameterEstimation/examples/ConstantVelocity2DExtendedKalmanFilterDemo.java
+++ b/ihmc-parameter-estimation/src/test/java/us/ihmc/parameterEstimation/examples/ConstantVelocity2DExtendedKalmanFilterDemo.java
@@ -5,7 +5,6 @@
import org.knowm.xchart.QuickChart;
import org.knowm.xchart.SwingWrapper;
import org.knowm.xchart.XYChart;
-import us.ihmc.parameterEstimation.ExtendedKalmanFilter;
import us.ihmc.parameterEstimation.ExtendedKalmanFilterTestTools.LinearSystem;
import javax.swing.*;
@@ -22,77 +21,6 @@
*/
public class ConstantVelocity2DExtendedKalmanFilterDemo
{
- public static class ConstantVelocity2DKalmanFilter extends ExtendedKalmanFilter
- {
- private static final int stateSize = 4;
- private static final int measurementSize = 2;
-
- // Start at (x,y) = (0,0) with no velocity
- private static final DMatrixRMaj x0 = new DMatrixRMaj(new double[] {0.0, 0.0, 0.0, 0.0});
-
- // Position is barely affected by noise, velocity is affected by noise
- private static final DMatrixRMaj Q = new DMatrixRMaj(new double[][] {{1e-6, 0.0, 0.0, 0.0},
- {0.0, 1e-6, 0.0, 0.0},
- {0.0, 0.0, 1e-6, 0.0},
- {0.0, 0.0, 0.0, 1e-6}});
-
- // Both x and y position measurements are corrupted by noise
- private static final DMatrixRMaj R = new DMatrixRMaj(new double[][] {{1, 0.0}, {0.0, 1}});
-
- // Ignorant initial guess on P0, we assume we're more certain about positions than velocities
- private static final DMatrixRMaj P0 = new DMatrixRMaj(new double[][] {{0.1, 0.0, 0.0, 0.0},
- {0.0, 1.0, 0.0, 0.0},
- {0.0, 0.0, 0.1, 0.0},
- {0.0, 0.0, 0.0, 1.0}});
-
- private static final double dt = 0.01;
-
- // Constant velocity model of a 2D planar system
- private static final DMatrixRMaj A = new DMatrixRMaj(new double[][] {{1.0, dt, 0.0, 0.0},
- {0.0, 1.0, 0.0, 0.0},
- {0.0, 0.0, 1.0, dt},
- {0.0, 0.0, 0.0, 1.0}});
-
- // We only measure positions, not velocities
- private static final DMatrixRMaj C = new DMatrixRMaj(new double[][] {{1.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 1.0, 0.0}});
-
- public ConstantVelocity2DKalmanFilter()
- {
- super(x0, P0, Q, R);
- }
-
- // In the case of a linear process model, the linearization is just the A matrix of the process model
- @Override
- public DMatrixRMaj linearizeProcessModel(DMatrixRMaj previousState)
- {
- return A;
- }
-
- // In the case of a linear measurement model, the linearization is just the C matrix of the measurement model
- @Override
- public DMatrixRMaj linearizeMeasurementModel(DMatrixRMaj predictedState)
- {
- return C;
- }
-
- // y = Ax
- @Override
- public DMatrixRMaj processModel(DMatrixRMaj state)
- {
- DMatrixRMaj nextState = new DMatrixRMaj(stateSize, 1);
- CommonOps_DDRM.mult(A, state, nextState);
- return nextState;
- }
-
- // y = Cx
- @Override
- public DMatrixRMaj measurementModel(DMatrixRMaj state)
- {
- DMatrixRMaj measurement = new DMatrixRMaj(measurementSize, 1);
- CommonOps_DDRM.mult(C, state, measurement);
- return measurement;
- }
- }
private static final int ITERATIONS = 1000;
@@ -102,11 +30,11 @@ public static void main(String[] args)
// We'll set things up such that the real system is always less noisy than the filter. Said the other way, our filter design
// will assume that the system is more noisy than it actually is -- a good thing.
- DMatrixRMaj Q = new DMatrixRMaj(ConstantVelocity2DKalmanFilter.Q);
+ DMatrixRMaj Q = new DMatrixRMaj(ExampleConstantVelocity2DKalmanFilter.Q);
CommonOps_DDRM.scale(0.1, Q);
// We'll set things up such that the real system is always less noisy than the filter. Said the other way, our filter design
// will assume that the system is more noisy than it actually is -- a good thing.
- DMatrixRMaj R = new DMatrixRMaj(ConstantVelocity2DKalmanFilter.R);
+ DMatrixRMaj R = new DMatrixRMaj(ExampleConstantVelocity2DKalmanFilter.R);
CommonOps_DDRM.scale(0.01, R);
// In general, initial state of system is different from that of filter.
@@ -116,14 +44,14 @@ public static void main(String[] args)
// LinearSystem system = new LinearSystem(initialSystemState,
// ConstantVelocity2DKalmanFilter.A,
// ConstantVelocity2DKalmanFilter.C);
- LinearSystem system = new LinearSystem(initialSystemState, ConstantVelocity2DKalmanFilter.A, Q, ConstantVelocity2DKalmanFilter.C, R, random);
+ LinearSystem system = new LinearSystem(initialSystemState, ExampleConstantVelocity2DKalmanFilter.A, Q, ExampleConstantVelocity2DKalmanFilter.C, R, random);
- DMatrixRMaj state = new DMatrixRMaj(ConstantVelocity2DKalmanFilter.stateSize, 1);
- DMatrixRMaj measurement = new DMatrixRMaj(ConstantVelocity2DKalmanFilter.measurementSize, 1);
+ DMatrixRMaj state = new DMatrixRMaj(ExampleConstantVelocity2DKalmanFilter.stateSize, 1);
+ DMatrixRMaj measurement = new DMatrixRMaj(ExampleConstantVelocity2DKalmanFilter.measurementSize, 1);
// Filter
- ConstantVelocity2DKalmanFilter kalmanFilter = new ConstantVelocity2DKalmanFilter();
- DMatrixRMaj estimatedState = new DMatrixRMaj(ConstantVelocity2DKalmanFilter.stateSize, 1);
+ ExampleConstantVelocity2DKalmanFilter kalmanFilter = new ExampleConstantVelocity2DKalmanFilter();
+ DMatrixRMaj estimatedState = new DMatrixRMaj(ExampleConstantVelocity2DKalmanFilter.stateSize, 1);
// Arrays for recording data that we'll plot
double[] timestamps = new double[ITERATIONS];
@@ -139,7 +67,7 @@ public static void main(String[] args)
estimatedState.set(kalmanFilter.calculateEstimate(measurement));
- timestamps[i] = i * ConstantVelocity2DKalmanFilter.dt;
+ timestamps[i] = i * ExampleConstantVelocity2DKalmanFilter.dt;
trueStates.add(new DMatrixRMaj(state));
measurements.add(new DMatrixRMaj(measurement));
estimatedStates.add(new DMatrixRMaj(estimatedState));
diff --git a/ihmc-parameter-estimation/src/test/java/us/ihmc/parameterEstimation/examples/PendulumExtendedKalmanFilterDemo.java b/ihmc-parameter-estimation/src/test/java/us/ihmc/parameterEstimation/examples/PendulumExtendedKalmanFilterDemo.java
index 56ec3bc0537a..4e66031d5ca9 100644
--- a/ihmc-parameter-estimation/src/test/java/us/ihmc/parameterEstimation/examples/PendulumExtendedKalmanFilterDemo.java
+++ b/ihmc-parameter-estimation/src/test/java/us/ihmc/parameterEstimation/examples/PendulumExtendedKalmanFilterDemo.java
@@ -5,13 +5,13 @@
import org.knowm.xchart.QuickChart;
import org.knowm.xchart.SwingWrapper;
import org.knowm.xchart.XYChart;
-import us.ihmc.parameterEstimation.ExtendedKalmanFilter;
import us.ihmc.parameterEstimation.ExtendedKalmanFilterTestTools.NonlinearSystem;
import javax.swing.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Random;
+import java.util.function.Function;
/**
* Demo of an EKF being used with a nonlinear process model and measurement model.
@@ -30,139 +30,65 @@ public class PendulumExtendedKalmanFilterDemo
private static final double dt = 0.01;
- public static class PendulumExtendedKalmanFilter extends ExtendedKalmanFilter
- {
- private static final int stateSize = 2;
- private static final int measurementSize = 1;
-
- // Picking an arbitrary nonzero initial condition since 0 is a singularity for the measurement
- private static final DMatrixRMaj x0 = new DMatrixRMaj(new double[] {0.1, 0.1});
-
- // Process noise
- private static final DMatrixRMaj Q = new DMatrixRMaj(new double[][] {{1e-6, 0.0}, {0.0, 1e-6}});
-
- // Measurement noise
- private static final DMatrixRMaj R = new DMatrixRMaj(new double[][] {{1e-3}});
-
- // Ignorant initial guess on P0, we assume we're more certain about positions than velocities
- private static final DMatrixRMaj P0 = new DMatrixRMaj(new double[][] {{0.1, 0.0}, {0.0, 1.0}});
-
- // Linearized process model dynamics ( Jacobian of f(x) where x[k+1] = f(x[k]) )
- private static final DMatrixRMaj F = new DMatrixRMaj(stateSize, stateSize);
-
- // Linearized measurement model dynamics (Jacobian of h(x) where y = h(x))
- private static final DMatrixRMaj H = new DMatrixRMaj(measurementSize, stateSize);
-
- public PendulumExtendedKalmanFilter()
- {
- super(x0, P0, Q, R);
- }
-
- // Linearize f(x) to obtain it's Jacobian matrix, F(x)
- @Override
- public DMatrixRMaj linearizeProcessModel(DMatrixRMaj previousState)
- {
- double q = previousState.get(0, 0);
-
- F.set(0, 0, 1);
- F.set(0, 1, dt);
- F.set(1, 0, -(dt * m * g * l * Math.cos(q)) / I);
- F.set(1, 1, 1 - dt * b / I);
-
- return F;
- }
-
- // Linearize h(x) to obtain it's Jacobian matrix, H(x)
- @Override
- public DMatrixRMaj linearizeMeasurementModel(DMatrixRMaj predictedState)
- {
- double q = predictedState.get(0, 0);
- double qDot = predictedState.get(1, 0);
-
- H.set(0, 0, m * g * l * Math.sin(q));
- H.set(0, 1, I * qDot);
-
- return H;
- }
-
- // x[k+1] = f(x[k])
- @Override
- public DMatrixRMaj processModel(DMatrixRMaj state)
- {
- return getNextState(state);
- }
-
- // y = h(x)
- @Override
- public DMatrixRMaj measurementModel(DMatrixRMaj state)
- {
- return getMeasurement(state);
- }
- }
-
private static class PendulumSystem extends NonlinearSystem
{
- public PendulumSystem(DMatrixRMaj x0, DMatrixRMaj F, DMatrixRMaj H)
+ private final Function systemDynamics;
+ private final Function measurementDynamics;
+
+ public PendulumSystem(DMatrixRMaj x0,
+ DMatrixRMaj F,
+ DMatrixRMaj H,
+ Function systemDynamics,
+ Function measurementDynamics)
{
super(x0, F, H);
+
+ this.systemDynamics = systemDynamics;
+ this.measurementDynamics = measurementDynamics;
}
- public PendulumSystem(DMatrixRMaj x0, DMatrixRMaj F, DMatrixRMaj Q, DMatrixRMaj H, DMatrixRMaj R, Random random)
+ public PendulumSystem(DMatrixRMaj x0, DMatrixRMaj F, DMatrixRMaj Q, DMatrixRMaj H, DMatrixRMaj R, Random random,
+ Function systemDynamics,
+ Function measurementDynamics)
{
super(x0, F, Q, H, R, random);
+
+ this.systemDynamics = systemDynamics;
+ this.measurementDynamics = measurementDynamics;
+
}
@Override
public DMatrixRMaj calculateSystemDynamics(DMatrixRMaj state)
{
- return getNextState(state);
+ return systemDynamics.apply(state);
}
@Override
public DMatrixRMaj calculateMeasurementDynamics(DMatrixRMaj state)
{
- return getMeasurement(state);
+ return measurementDynamics.apply(state);
}
}
- private static DMatrixRMaj getNextState(DMatrixRMaj state)
- {
- DMatrixRMaj nextState = new DMatrixRMaj(state.numRows, 1);
- double q = state.get(0, 0);
- double qDot = state.get(1, 0);
-
- // Discretized with Forward Euler
- nextState.set(0, 0, q + dt * qDot);
- nextState.set(1, 0, qDot - dt * (b * qDot + m * g * l * Math.sin(q)) / I);
-
- return nextState;
- }
-
- private static DMatrixRMaj getMeasurement(DMatrixRMaj state)
- {
- DMatrixRMaj measurement = new DMatrixRMaj(PendulumExtendedKalmanFilter.measurementSize, 1);
- double q = state.get(0, 0);
- double qDot = state.get(1, 0);
-
- // Measurement is the total energy in the system, which will be dissipated with nonzero damping.
- measurement.set(0, 0, 0.5 * I * qDot * qDot + m * g * l * (1 - Math.cos(q)));
-
- return measurement;
- }
-
private static final int ITERATIONS = 500;
public static void main(String[] args)
{
Random random = new Random(45);
+ // Filter
+ ExamplePendulumExtendedKalmanFilter kalmanFilter = new ExamplePendulumExtendedKalmanFilter();
+ kalmanFilter.setModelProperties(m, I, l, b, g, dt);
+ DMatrixRMaj estimatedState = new DMatrixRMaj(ExamplePendulumExtendedKalmanFilter.stateSize, 1);
+
// We'll set things up such that the real system is always less noisy than the filter. Said the other way, our filter design
// will assume that the system is more noisy than it actually is -- a good thing.
- DMatrixRMaj Q = new DMatrixRMaj(PendulumExtendedKalmanFilter.Q);
+ DMatrixRMaj Q = new DMatrixRMaj(kalmanFilter.getQ());
CommonOps_DDRM.scale(0.1, Q);
// We'll set things up such that the real system is always less noisy than the filter. Said the other way, our filter design
// will assume that the system is more noisy than it actually is -- a good thing.
- DMatrixRMaj R = new DMatrixRMaj(PendulumExtendedKalmanFilter.R);
+ DMatrixRMaj R = new DMatrixRMaj(kalmanFilter.getR());
CommonOps_DDRM.scale(0.01, R);
// In general, initial state of system is different from that of filter.
@@ -172,14 +98,11 @@ public static void main(String[] args)
// NonlinearSystem system = new ConstantVelocity2DNonlinearMeasurementSystem(initialSystemState,
// PendulumExtendedKalmanFilter.F,
// PendulumExtendedKalmanFilter.H);
- NonlinearSystem system = new PendulumSystem(initialSystemState, PendulumExtendedKalmanFilter.F, Q, PendulumExtendedKalmanFilter.H, R, random);
-
- DMatrixRMaj state = new DMatrixRMaj(PendulumExtendedKalmanFilter.stateSize, 1);
- DMatrixRMaj measurement = new DMatrixRMaj(PendulumExtendedKalmanFilter.measurementSize, 1);
+ NonlinearSystem system = new PendulumSystem(initialSystemState, kalmanFilter.getF(), Q, kalmanFilter.getH(), R, random,
+ kalmanFilter::getNextState, kalmanFilter::getMeasurement);
- // Filter
- PendulumExtendedKalmanFilter kalmanFilter = new PendulumExtendedKalmanFilter();
- DMatrixRMaj estimatedState = new DMatrixRMaj(PendulumExtendedKalmanFilter.stateSize, 1);
+ DMatrixRMaj state = new DMatrixRMaj(ExamplePendulumExtendedKalmanFilter.stateSize, 1);
+ DMatrixRMaj measurement = new DMatrixRMaj(ExamplePendulumExtendedKalmanFilter.measurementSize, 1);
// Arrays for recording data that we'll plot
double[] timestamps = new double[ITERATIONS];
diff --git a/ihmc-robotics-toolkit/src/main/java/us/ihmc/robotics/sensors/IMUDefinition.java b/ihmc-robotics-toolkit/src/main/java/us/ihmc/robotics/sensors/IMUDefinition.java
index df1b2287416d..2f87bee20642 100644
--- a/ihmc-robotics-toolkit/src/main/java/us/ihmc/robotics/sensors/IMUDefinition.java
+++ b/ihmc-robotics-toolkit/src/main/java/us/ihmc/robotics/sensors/IMUDefinition.java
@@ -4,6 +4,7 @@
import us.ihmc.euclid.referenceFrame.tools.ReferenceFrameTools;
import us.ihmc.euclid.transform.RigidBodyTransform;
import us.ihmc.euclid.transform.interfaces.RigidBodyTransformReadOnly;
+import us.ihmc.mecano.frames.MovingReferenceFrame;
import us.ihmc.mecano.multiBodySystem.interfaces.RigidBodyBasics;
public class IMUDefinition
@@ -11,7 +12,7 @@ public class IMUDefinition
private final String name;
private final RigidBodyBasics rigidBody;
private final RigidBodyTransform transformFromIMUToJoint;
- private final ReferenceFrame imuFrame;
+ private final MovingReferenceFrame imuFrame;
public IMUDefinition(String name, RigidBodyBasics rigidBody, RigidBodyTransformReadOnly transformFromIMUToJoint)
{
@@ -19,8 +20,8 @@ public IMUDefinition(String name, RigidBodyBasics rigidBody, RigidBodyTransformR
this.rigidBody = rigidBody;
this.transformFromIMUToJoint = new RigidBodyTransform(transformFromIMUToJoint);
- ReferenceFrame frameAfterJoint = rigidBody.getParentJoint().getFrameAfterJoint();
- imuFrame = ReferenceFrameTools.constructFrameWithUnchangingTransformToParent(name, frameAfterJoint, transformFromIMUToJoint);
+ MovingReferenceFrame frameAfterJoint = rigidBody.getParentJoint().getFrameAfterJoint();
+ imuFrame = MovingReferenceFrame.constructFrameFixedInParent(name, frameAfterJoint, transformFromIMUToJoint);
}
public String getName()
@@ -38,7 +39,7 @@ public RigidBodyTransformReadOnly getTransformFromIMUToJoint()
return transformFromIMUToJoint;
}
- public ReferenceFrame getIMUFrame()
+ public MovingReferenceFrame getIMUFrame()
{
return imuFrame;
}
diff --git a/ihmc-sensor-processing/src/main/java/us/ihmc/sensorProcessing/imu/IMUSensor.java b/ihmc-sensor-processing/src/main/java/us/ihmc/sensorProcessing/imu/IMUSensor.java
index d69575551967..5fffa5c85b42 100644
--- a/ihmc-sensor-processing/src/main/java/us/ihmc/sensorProcessing/imu/IMUSensor.java
+++ b/ihmc-sensor-processing/src/main/java/us/ihmc/sensorProcessing/imu/IMUSensor.java
@@ -10,6 +10,7 @@
import us.ihmc.euclid.tuple3D.interfaces.Vector3DReadOnly;
import us.ihmc.euclid.tuple4D.Quaternion;
import us.ihmc.euclid.tuple4D.interfaces.QuaternionReadOnly;
+import us.ihmc.mecano.frames.MovingReferenceFrame;
import us.ihmc.mecano.multiBodySystem.interfaces.RigidBodyBasics;
import us.ihmc.robotics.sensors.IMUDefinition;
import us.ihmc.sensorProcessing.simulatedSensors.SensorNoiseParameters;
@@ -19,7 +20,7 @@ public class IMUSensor implements IMUSensorReadOnly
{
private final String sensorName;
- private final ReferenceFrame measurementFrame;
+ private final MovingReferenceFrame measurementFrame;
private final RigidBodyBasics measurementLink;
private final Quaternion orientationMeasurement = new Quaternion();
@@ -68,7 +69,7 @@ public String getSensorName()
}
@Override
- public ReferenceFrame getMeasurementFrame()
+ public MovingReferenceFrame getMeasurementFrame()
{
return measurementFrame;
}
diff --git a/ihmc-sensor-processing/src/main/java/us/ihmc/sensorProcessing/stateEstimation/IMUSensorReadOnly.java b/ihmc-sensor-processing/src/main/java/us/ihmc/sensorProcessing/stateEstimation/IMUSensorReadOnly.java
index c1e048fde073..06056ed439ac 100644
--- a/ihmc-sensor-processing/src/main/java/us/ihmc/sensorProcessing/stateEstimation/IMUSensorReadOnly.java
+++ b/ihmc-sensor-processing/src/main/java/us/ihmc/sensorProcessing/stateEstimation/IMUSensorReadOnly.java
@@ -7,13 +7,14 @@
import us.ihmc.euclid.tuple3D.interfaces.Vector3DBasics;
import us.ihmc.euclid.tuple3D.interfaces.Vector3DReadOnly;
import us.ihmc.euclid.tuple4D.interfaces.QuaternionReadOnly;
+import us.ihmc.mecano.frames.MovingReferenceFrame;
import us.ihmc.mecano.multiBodySystem.interfaces.RigidBodyBasics;
public interface IMUSensorReadOnly
{
public abstract String getSensorName();
- public abstract ReferenceFrame getMeasurementFrame();
+ public abstract MovingReferenceFrame getMeasurementFrame();
public abstract RigidBodyBasics getMeasurementLink();
diff --git a/ihmc-state-estimation/src/main/java/us/ihmc/stateEstimation/humanoid/kinematicsBasedStateEstimation/DRCKinematicsBasedStateEstimator.java b/ihmc-state-estimation/src/main/java/us/ihmc/stateEstimation/humanoid/kinematicsBasedStateEstimation/DRCKinematicsBasedStateEstimator.java
index cc9fb8862086..1f0ed3240994 100644
--- a/ihmc-state-estimation/src/main/java/us/ihmc/stateEstimation/humanoid/kinematicsBasedStateEstimation/DRCKinematicsBasedStateEstimator.java
+++ b/ihmc-state-estimation/src/main/java/us/ihmc/stateEstimation/humanoid/kinematicsBasedStateEstimation/DRCKinematicsBasedStateEstimator.java
@@ -39,6 +39,7 @@
import us.ihmc.stateEstimation.humanoid.kinematicsBasedStateEstimation.centerOfMassEstimator.MomentumStateUpdater;
import us.ihmc.stateEstimation.humanoid.kinematicsBasedStateEstimation.centerOfMassEstimator.SimpleMomentumStateUpdater;
import us.ihmc.stateEstimation.humanoid.kinematicsBasedStateEstimation.centerOfMassEstimator.WrenchBasedMomentumStateUpdater;
+import us.ihmc.stateEstimation.humanoid.kinematicsBasedStateEstimation.odomEKF.OdometryKalmanFilter;
import us.ihmc.yoVariables.euclid.referenceFrame.YoFramePose3D;
import us.ihmc.yoVariables.parameters.BooleanParameter;
import us.ihmc.yoVariables.providers.BooleanProvider;
@@ -65,6 +66,7 @@ public class DRCKinematicsBasedStateEstimator implements StateEstimatorControlle
private final MomentumStateUpdater momentumStateUpdater;
private final IMUBiasStateEstimator imuBiasStateEstimator;
private final IMUYawDriftEstimator imuYawDriftEstimator;
+ private final OdometryKalmanFilter odometry;
private final PelvisPoseHistoryCorrectionInterface pelvisPoseHistoryCorrection;
@@ -107,6 +109,7 @@ public DRCKinematicsBasedStateEstimator(FullInverseDynamicsStructure inverseDyna
rootJoint = inverseDynamicsStructure.getRootJoint();
+
usePelvisCorrector = new YoBoolean("useExternalPelvisCorrector", registry);
usePelvisCorrector.set(true);
@@ -144,6 +147,7 @@ public DRCKinematicsBasedStateEstimator(FullInverseDynamicsStructure inverseDyna
List imusToUse = new ArrayList<>(imuProcessedOutputs);
+
BooleanProvider cancelGravityFromAccelerationMeasurement = new BooleanParameter("cancelGravityFromAccelerationMeasurement",
registry,
stateEstimatorParameters.cancelGravityFromAccelerationMeasurement());
@@ -162,6 +166,10 @@ public DRCKinematicsBasedStateEstimator(FullInverseDynamicsStructure inverseDyna
stateEstimatorParameters,
registry);
+
+ List feetIMUs = imusToUse.stream().filter(sensor -> feet.containsKey(sensor.getMeasurementLink())).toList();
+ odometry = new OdometryKalmanFilter(imusToUse.get(0), feetIMUs, imuBiasStateEstimator, cancelGravityFromAccelerationMeasurement, estimatorDT, gravitationalAcceleration, registry);
+
jointStateUpdater = new JointStateUpdater(inverseDynamicsStructure, sensorOutputMap, stateEstimatorParameters, registry);
if (forceSensorDataHolderToUpdate != null)
@@ -336,6 +344,8 @@ public void doControl()
for (int i = 0; i < footSwitchList.size(); i++)
footSwitchList.get(i).update();
+
+
switch (operatingMode.getEnumValue())
{
case FROZEN:
@@ -347,6 +357,9 @@ public void doControl()
break;
}
+ // TODO should this also consume some of the foot switch data?
+ odometry.compute();
+
if (momentumStateUpdater != null)
momentumStateUpdater.update();
diff --git a/ihmc-state-estimation/src/main/java/us/ihmc/stateEstimation/humanoid/kinematicsBasedStateEstimation/PelvisKinematicsBasedLinearStateCalculator.java b/ihmc-state-estimation/src/main/java/us/ihmc/stateEstimation/humanoid/kinematicsBasedStateEstimation/PelvisKinematicsBasedLinearStateCalculator.java
index 8641f12b2153..7b023c24ec7a 100644
--- a/ihmc-state-estimation/src/main/java/us/ihmc/stateEstimation/humanoid/kinematicsBasedStateEstimation/PelvisKinematicsBasedLinearStateCalculator.java
+++ b/ihmc-state-estimation/src/main/java/us/ihmc/stateEstimation/humanoid/kinematicsBasedStateEstimation/PelvisKinematicsBasedLinearStateCalculator.java
@@ -4,45 +4,22 @@
import java.util.List;
import java.util.Map;
-import us.ihmc.euclid.referenceFrame.FrameConvexPolygon2D;
-import us.ihmc.euclid.referenceFrame.FrameLineSegment2D;
-import us.ihmc.euclid.referenceFrame.FramePoint2D;
import us.ihmc.euclid.referenceFrame.FramePoint3D;
-import us.ihmc.euclid.referenceFrame.FrameVector3D;
import us.ihmc.euclid.referenceFrame.ReferenceFrame;
-import us.ihmc.euclid.referenceFrame.interfaces.FixedFramePoint3DBasics;
-import us.ihmc.euclid.referenceFrame.interfaces.FixedFrameVector3DBasics;
-import us.ihmc.euclid.referenceFrame.interfaces.FramePoint2DBasics;
import us.ihmc.euclid.referenceFrame.interfaces.FramePoint3DReadOnly;
import us.ihmc.euclid.referenceFrame.interfaces.FrameVector3DReadOnly;
-import us.ihmc.euclid.referenceFrame.interfaces.FrameVertex2DSupplier;
-import us.ihmc.graphicsDescription.appearance.YoAppearance;
-import us.ihmc.graphicsDescription.yoGraphics.YoGraphicPosition;
import us.ihmc.graphicsDescription.yoGraphics.YoGraphicsListRegistry;
-import us.ihmc.graphicsDescription.yoGraphics.plotting.YoArtifactPosition;
import us.ihmc.humanoidRobotics.model.CenterOfPressureDataHolder;
-import us.ihmc.log.LogTools;
-import us.ihmc.mecano.multiBodySystem.interfaces.FloatingJointBasics;
import us.ihmc.mecano.multiBodySystem.interfaces.RigidBodyBasics;
-import us.ihmc.mecano.spatial.Twist;
-import us.ihmc.mecano.spatial.interfaces.TwistReadOnly;
-import us.ihmc.robotics.SCS2YoGraphicHolder;
import us.ihmc.robotics.contactable.ContactablePlaneBody;
-import us.ihmc.yoVariables.euclid.filters.AlphaFilteredYoFramePoint2D;
-import us.ihmc.yoVariables.euclid.filters.AlphaFilteredYoFrameVector3D;
+import us.ihmc.sensorProcessing.stateEstimation.IMUSensorReadOnly;
import us.ihmc.yoVariables.euclid.filters.BacklashCompensatingVelocityYoFrameVector3D;
import us.ihmc.robotics.sensors.FootSwitchInterface;
-import us.ihmc.scs2.definition.visual.ColorDefinitions;
-import us.ihmc.scs2.definition.yoGraphic.YoGraphicDefinition;
-import us.ihmc.scs2.definition.yoGraphic.YoGraphicDefinitionFactory;
-import us.ihmc.scs2.definition.yoGraphic.YoGraphicDefinitionFactory.DefaultPoint2DGraphic;
-import us.ihmc.scs2.definition.yoGraphic.YoGraphicGroupDefinition;
-import us.ihmc.scs2.definition.yoGraphic.YoGraphicPoint2DDefinition;
import us.ihmc.sensorProcessing.stateEstimation.StateEstimatorParameters;
import us.ihmc.sensorProcessing.stateEstimation.evaluation.FullInverseDynamicsStructure;
-import us.ihmc.yoVariables.euclid.referenceFrame.YoFramePoint2D;
import us.ihmc.yoVariables.euclid.referenceFrame.YoFramePoint3D;
import us.ihmc.yoVariables.euclid.referenceFrame.YoFrameVector3D;
+import us.ihmc.yoVariables.filters.AlphaFilterTools;
import us.ihmc.yoVariables.filters.AlphaFilteredYoVariable;
import us.ihmc.yoVariables.parameters.BooleanParameter;
import us.ihmc.yoVariables.parameters.DoubleParameter;
@@ -58,87 +35,52 @@
*
* @author Sylvain
*/
-public class PelvisKinematicsBasedLinearStateCalculator implements SCS2YoGraphicHolder
+public class PelvisKinematicsBasedLinearStateCalculator
{
- private static final boolean VISUALIZE = true;
-
private final YoRegistry registry = new YoRegistry(getClass().getSimpleName());
- private final FloatingJointBasics rootJoint;
- private final RigidBodyBasics[] feetRigidBodies;
- private final SingleFootEstimator[] footEstimators;
- private final Map footEstimatorMap = new HashMap<>();
+ private final Map footEstimatorMap;
private final ReferenceFrame worldFrame = ReferenceFrame.getWorldFrame();
private final YoFramePoint3D rootJointPosition = new YoFramePoint3D("estimatedRootJointPositionKinematics", worldFrame, registry);
private final YoFrameVector3D rootJointLinearVelocity = new YoFrameVector3D("estimatedRootJointLinearVelocityKinematics", worldFrame, registry);
+ private final YoFrameVector3D averagedTrustedFootVelocity = new YoFrameVector3D("averagedTrustedFootVelocity", worldFrame, registry);
private final DoubleProvider alphaRootJointLinearVelocity;
- /** Debug variable */
- private final YoDouble alphaLinearVelocityDebug = new YoDouble("alphaRootJointLinearVelocityBacklashKinematics", registry);
- /** Debug variable */
- private final YoDouble slopTimeLinearVelocityDebug = new YoDouble("slopTimeRootJointLinearVelocityBacklashKinematics", registry);
/** Debug variable */
private final BacklashCompensatingVelocityYoFrameVector3D rootJointLinearVelocityFDDebug;
- private final DoubleProvider footToRootJointPositionBreakFrequency;
private final BooleanProvider correctTrustedFeetPositions;
- private final DoubleProvider copFilterBreakFrequency;
-
private final YoBoolean kinematicsIsUpToDate = new YoBoolean("kinematicsIsUpToDate", registry);
- private final BooleanProvider useControllerDesiredCoP;
private final BooleanProvider trustCoPAsNonSlippingContactPoint;
private final BooleanParameter assumeTrustedFootAtZeroHeight = new BooleanParameter("assumeTrustedFootAtZeroHeight", registry, false);
+ private final BooleanProvider useControllerDesiredCoP;
- public PelvisKinematicsBasedLinearStateCalculator(FullInverseDynamicsStructure inverseDynamicsStructure,
- Map feetContactablePlaneBodies,
- Map footSwitches,
- CenterOfPressureDataHolder centerOfPressureDataHolderFromController,
+ public PelvisKinematicsBasedLinearStateCalculator(Map footEstimatorMap,
double estimatorDT,
StateEstimatorParameters stateEstimatorParameters,
- YoGraphicsListRegistry yoGraphicsListRegistry,
YoRegistry parentRegistry)
{
- rootJoint = inverseDynamicsStructure.getRootJoint();
- feetRigidBodies = feetContactablePlaneBodies.keySet().toArray(new RigidBodyBasics[0]);
-
- footToRootJointPositionBreakFrequency = new DoubleParameter("FootToRootJointPositionBreakFrequency",
- registry,
- stateEstimatorParameters.getKinematicsPelvisPositionFilterFreqInHertz());
+ this.footEstimatorMap = footEstimatorMap;
alphaRootJointLinearVelocity = new DoubleParameter("alphaRootJointLinearVelocityKinematics",
registry,
stateEstimatorParameters.getPelvisLinearVelocityAlphaNewTwist());
trustCoPAsNonSlippingContactPoint = new BooleanParameter("trustCoPAsNonSlippingContactPoint",
registry,
stateEstimatorParameters.trustCoPAsNonSlippingContactPoint());
- useControllerDesiredCoP = new BooleanParameter("useControllerDesiredCoP", registry, stateEstimatorParameters.useControllerDesiredCenterOfPressure());
- copFilterBreakFrequency = new DoubleParameter("CopFilterBreakFrequency", registry, stateEstimatorParameters.getCoPFilterFreqInHertz());
- correctTrustedFeetPositions = new BooleanParameter("correctTrustedFeetPositions", registry, stateEstimatorParameters.correctTrustedFeetPositions());
- footEstimators = new SingleFootEstimator[feetRigidBodies.length];
- for (int i = 0; i < feetRigidBodies.length; i++)
- {
- RigidBodyBasics footRigidBody = feetRigidBodies[i];
- ContactablePlaneBody contactableFoot = feetContactablePlaneBodies.get(footRigidBody);
- FootSwitchInterface footSwitch = footSwitches.get(footRigidBody);
- footEstimators[i] = new SingleFootEstimator(rootJoint,
- contactableFoot,
- footSwitch,
- footToRootJointPositionBreakFrequency,
- copFilterBreakFrequency,
- centerOfPressureDataHolderFromController,
- estimatorDT,
- registry);
- footEstimatorMap.put(footRigidBody, footEstimators[i]);
- }
+ correctTrustedFeetPositions = new BooleanParameter("correctTrustedFeetPositions", registry, stateEstimatorParameters.correctTrustedFeetPositions());
+ useControllerDesiredCoP = new BooleanParameter("useControllerDesiredCoP", registry, stateEstimatorParameters.useControllerDesiredCenterOfPressure());
/*
* These are for debug purposes, not need to clutter the state estimator parameters class with them.
*/
- alphaLinearVelocityDebug.set(AlphaFilteredYoVariable.computeAlphaGivenBreakFrequencyProperly(16.0, estimatorDT));
+ YoDouble alphaLinearVelocityDebug = new YoDouble("alphaRootJointLinearVelocityBacklashKinematics", registry);
+ YoDouble slopTimeLinearVelocityDebug = new YoDouble("slopTimeRootJointLinearVelocityBacklashKinematics", registry);
+ alphaLinearVelocityDebug.set(AlphaFilterTools.computeAlphaGivenBreakFrequencyProperly(16.0, estimatorDT));
slopTimeLinearVelocityDebug.set(0.03);
rootJointLinearVelocityFDDebug = new BacklashCompensatingVelocityYoFrameVector3D("estimatedRootJointLinearVelocityBacklashKin",
"",
@@ -151,455 +93,125 @@ public PelvisKinematicsBasedLinearStateCalculator(FullInverseDynamicsStructure i
* -------------------------------------------------------------------------------------------------
*/
- if (VISUALIZE)
- {
- for (SingleFootEstimator footEstimator : footEstimators)
- {
- footEstimator.createVisualization(yoGraphicsListRegistry);
- }
- }
parentRegistry.addChild(registry);
}
/**
- * Estimates the foot positions corresponding to the given pelvisPosition
+ * Estimates the foot positions corresponding to the given rootJointPosition
*
- * @param pelvisPosition
+ * @param rootJointPosition
*/
- public void initialize(FramePoint3DReadOnly pelvisPosition)
+ public void initialize(FramePoint3DReadOnly rootJointPosition)
{
- for (SingleFootEstimator footEstimator : footEstimators)
- {
- footEstimator.initialize();
- }
- setPelvisLinearVelocityToZero();
+ rootJointLinearVelocityFDDebug.reset();
+ rootJointLinearVelocityFDDebug.setToZero();
+ rootJointLinearVelocity.setToZero();
- updateKinematics();
- setPelvisPosition(pelvisPosition);
+ this.rootJointPosition.set(rootJointPosition);
- for (SingleFootEstimator footEstimator : footEstimators)
- {
- footEstimator.updateUntrustedFootPosition(pelvisPosition);
- }
kinematicsIsUpToDate.set(false);
}
+
/**
* Updates the different kinematics related stuff that is used to estimate the pelvis state
*/
public void updateKinematics()
{
rootJointPosition.setToZero();
- updateKinematicsNewTwist();
-
- for (SingleFootEstimator footEstimator : footEstimators)
- footEstimator.updateKinematics();
-
kinematicsIsUpToDate.set(true);
}
- private final Twist tempRootBodyTwist = new Twist();
-
- private void updateKinematicsNewTwist()
- {
- tempRootBodyTwist.setIncludingFrame(rootJoint.getJointTwist());
- tempRootBodyTwist.getLinearPart().setMatchingFrame(rootJointLinearVelocity);
-
- for (SingleFootEstimator footEstimator : footEstimators)
- footEstimator.updateFootLinearVelocityInWorld(tempRootBodyTwist);
- }
-
/**
* Updates this module when no foot can be trusted.
*
- * @param pelvisPosition the current estimated pelvis position. This module does not compute
+ * @param rootJointPosition the current estimated pelvis position. This module does not compute
* an estimate.
- * @param pelvisLinearVelocity the current estimated pelvis linear velocity. This module does not
+ * @param rootJointLinearVelocity the current estimated pelvis linear velocity. This module does not
* compute an estimate. If {@code null}, a zero velocity is assumed.
*/
- public void updateNoTrustedFeet(FramePoint3DReadOnly pelvisPosition, FrameVector3DReadOnly pelvisLinearVelocity)
+ public void setRootJointState(FramePoint3DReadOnly rootJointPosition, FrameVector3DReadOnly rootJointLinearVelocity)
{
- for (SingleFootEstimator footEstimator : footEstimators)
- {
- footEstimator.updateUntrustedFootPosition(pelvisPosition);
- }
-
- rootJointPosition.set(pelvisPosition);
+ this.rootJointPosition.set(rootJointPosition);
- if (pelvisLinearVelocity != null)
- rootJointLinearVelocity.set(pelvisLinearVelocity);
+ if (rootJointLinearVelocity != null)
+ this.rootJointLinearVelocity.set(rootJointLinearVelocity);
else
- rootJointLinearVelocity.setToZero();
- }
-
- public void estimatePelvisLinearState(List trustedFeet, List unTrustedFeet, FramePoint3DReadOnly pelvisPosition)
- {
- if (!kinematicsIsUpToDate.getBooleanValue())
- throw new RuntimeException("Leg kinematics needs to be updated before trying to estimate the pelvis position/linear velocity.");
-
- if (assumeTrustedFootAtZeroHeight.getValue())
- {
- for (int i = 0; i < trustedFeet.size(); i++)
- {
- SingleFootEstimator footEstimator = footEstimatorMap.get(trustedFeet.get(i));
- if (trustCoPAsNonSlippingContactPoint.getValue())
- {
- footEstimator.updateCoPPosition(trustCoPAsNonSlippingContactPoint.getValue(), useControllerDesiredCoP.getValue());
- footEstimator.copPositionInWorld.setZ(0.0);
- footEstimator.correctFootPositionsUsingCoP(trustCoPAsNonSlippingContactPoint.getValue());
- }
- else
- {
- footEstimator.footPositionInWorld.setZ(0.0);
- }
- footEstimator.updatePelvisWithKinematics(trustedFeet.size(), alphaRootJointLinearVelocity.getValue(), rootJointPosition, rootJointLinearVelocity);
- }
- }
- else
- {
- for (int i = 0; i < trustedFeet.size(); i++)
- {
- SingleFootEstimator footEstimator = footEstimatorMap.get(trustedFeet.get(i));
- footEstimator.updateCoPPosition(trustCoPAsNonSlippingContactPoint.getValue(), useControllerDesiredCoP.getValue());
- footEstimator.correctFootPositionsUsingCoP(trustCoPAsNonSlippingContactPoint.getValue());
- footEstimator.updatePelvisWithKinematics(trustedFeet.size(), alphaRootJointLinearVelocity.getValue(), rootJointPosition, rootJointLinearVelocity);
- }
- }
-
- rootJointLinearVelocityFDDebug.update();
-
- if (correctTrustedFeetPositions.getValue())
- {
- for (int i = 0; i < trustedFeet.size(); i++)
- {
- SingleFootEstimator footEstimator = footEstimatorMap.get(trustedFeet.get(i));
- footEstimator.updateTrustedFootPosition(trustCoPAsNonSlippingContactPoint.getValue(), rootJointPosition);
- }
- }
-
- for (int i = 0; i < unTrustedFeet.size(); i++)
- {
- SingleFootEstimator footEstimator = footEstimatorMap.get(unTrustedFeet.get(i));
- footEstimator.updateUntrustedFootPosition(pelvisPosition);
- }
-
- kinematicsIsUpToDate.set(false);
- }
-
- public void setPelvisPosition(FramePoint3DReadOnly pelvisPosition)
- {
- rootJointPosition.set(pelvisPosition);
- }
-
- public void setPelvisLinearVelocity(FrameVector3DReadOnly pelvisLinearVelocity)
- {
- rootJointLinearVelocityFDDebug.reset();
- rootJointLinearVelocityFDDebug.set(pelvisLinearVelocity);
- rootJointLinearVelocity.set(pelvisLinearVelocity);
+ this.rootJointLinearVelocity.setToZero();
}
- public void setPelvisLinearVelocityToZero()
+ public boolean getKinematicsIsUpToDate()
{
- rootJointLinearVelocityFDDebug.reset();
- rootJointLinearVelocityFDDebug.setToZero();
- rootJointLinearVelocity.setToZero();
+ return kinematicsIsUpToDate.getBooleanValue();
}
- public FramePoint3DReadOnly getPelvisPosition()
+ public boolean getAssumeTrustedFootAtZeroHeight()
{
- return rootJointPosition;
+ return assumeTrustedFootAtZeroHeight.getValue();
}
- public FrameVector3DReadOnly getPelvisVelocity()
+ public boolean getTrustCoPAsNonSlippingContactPoint()
{
- return rootJointLinearVelocity;
+ return trustCoPAsNonSlippingContactPoint.getValue();
}
- public void getFootToRootJointPosition(FramePoint3D positionToPack, RigidBodyBasics foot)
+ public boolean getUseControllerDesiredCoP()
{
- positionToPack.setIncludingFrame(footEstimatorMap.get(foot).footToRootJointPosition);
+ return useControllerDesiredCoP.getValue();
}
- public FrameVector3DReadOnly getFootVelocityInWorld(RigidBodyBasics foot)
+ public boolean getCorrectTrustedFeetPositions()
{
- return footEstimatorMap.get(foot).footVelocityInWorld;
+ return correctTrustedFeetPositions.getValue();
}
- private static class SingleFootEstimator implements SCS2YoGraphicHolder
+ public void estimatePelvisLinearState(List trustedFeet, FramePoint3DReadOnly pelvisPosition, FrameVector3DReadOnly pelvisLinearVelocity)
{
- private final RigidBodyBasics foot;
-
- private final ReferenceFrame rootJointFrame;
- private final ReferenceFrame soleFrame;
- private final ReferenceFrame worldFrame = ReferenceFrame.getWorldFrame();
-
- private final YoFrameVector3D footVelocityInWorld;
- private final AlphaFilteredYoFrameVector3D footToRootJointPosition;
- private final YoFramePoint3D footPositionInWorld;
- /** Debug variable */
- private final YoFramePoint3D rootJointPositionPerFoot;
- private final YoFramePoint3D copPositionInWorld;
- private final AlphaFilteredYoFramePoint2D copFilteredInFootFrame;
- private final YoFramePoint2D copRawInFootFrame;
- private final FrameConvexPolygon2D footPolygon;
- private final FrameLineSegment2D footCenterCoPLineSegment;
- private final FootSwitchInterface footSwitch;
- private final CenterOfPressureDataHolder centerOfPressureDataHolderFromController;
-
- private final FramePoint2DBasics[] intersectionPoints = new FramePoint2DBasics[] {new FramePoint2D(), new FramePoint2D()};
-
- public SingleFootEstimator(FloatingJointBasics rootJoint,
- ContactablePlaneBody contactableFoot,
- FootSwitchInterface footSwitch,
- DoubleProvider footToRootJointPositionBreakFrequency,
- DoubleProvider copFilterBreakFrequency,
- CenterOfPressureDataHolder centerOfPressureDataHolderFromController,
- double estimatorDT,
- YoRegistry registry)
- {
- this.rootJointFrame = rootJoint.getFrameAfterJoint();
- this.footSwitch = footSwitch;
- this.centerOfPressureDataHolderFromController = centerOfPressureDataHolderFromController;
- foot = contactableFoot.getRigidBody();
- soleFrame = contactableFoot.getContactFrame();
-
- String namePrefix = foot.getName();
-
- DoubleProvider alphaFoot = () -> AlphaFilteredYoVariable.computeAlphaGivenBreakFrequencyProperly(footToRootJointPositionBreakFrequency.getValue(),
- estimatorDT);
- footToRootJointPosition = new AlphaFilteredYoFrameVector3D(namePrefix + "FootToRootJointPosition", "", registry, alphaFoot, worldFrame);
- rootJointPositionPerFoot = new YoFramePoint3D(namePrefix + "BasedRootJointPosition", worldFrame, registry);
- footPositionInWorld = new YoFramePoint3D(namePrefix + "FootPositionInWorld", worldFrame, registry);
- footPolygon = new FrameConvexPolygon2D(FrameVertex2DSupplier.asFrameVertex2DSupplier(contactableFoot.getContactPoints2D()));
- footCenterCoPLineSegment = new FrameLineSegment2D(soleFrame);
- copRawInFootFrame = new YoFramePoint2D(namePrefix + "CoPRawInFootFrame", soleFrame, registry);
-
- DoubleProvider alphaCop = () -> AlphaFilteredYoVariable.computeAlphaGivenBreakFrequencyProperly(copFilterBreakFrequency.getValue(), estimatorDT);
- copFilteredInFootFrame = new AlphaFilteredYoFramePoint2D(namePrefix + "CoPFilteredInFootFrame", "", registry, alphaCop, copRawInFootFrame);
- copFilteredInFootFrame.update(0.0, 0.0);
- copPositionInWorld = new YoFramePoint3D(namePrefix + "CoPPositionsInWorld", worldFrame, registry);
- footVelocityInWorld = new YoFrameVector3D(namePrefix + "VelocityInWorld", worldFrame, registry);
- }
-
- public void createVisualization(YoGraphicsListRegistry yoGraphicsListRegistry)
- {
- if (yoGraphicsListRegistry == null)
- return;
-
- String sidePrefix = foot.getName();
- YoGraphicPosition copInWorld = new YoGraphicPosition(sidePrefix + "StateEstimatorCoP", copPositionInWorld, 0.005, YoAppearance.DeepPink());
- YoArtifactPosition artifact = copInWorld.createArtifact();
- artifact.setVisible(false);
- yoGraphicsListRegistry.registerArtifact("StateEstimator", artifact);
- }
-
- @Override
- public YoGraphicDefinition getSCS2YoGraphics()
- {
- YoGraphicPoint2DDefinition copVisual = YoGraphicDefinitionFactory.newYoGraphicPoint2D(foot.getName() + "StateEstimatorCoP",
- copPositionInWorld,
- 0.01,
- ColorDefinitions.DeepPink(),
- DefaultPoint2DGraphic.CIRCLE);
- return copVisual;
- }
-
- public void initialize()
- {
- footToRootJointPosition.reset();
- copFilteredInFootFrame.reset();
- copFilteredInFootFrame.update(0.0, 0.0);
- footVelocityInWorld.setToZero();
- }
-
- private final FramePoint3D tempFramePoint = new FramePoint3D();
-
- /**
- * Estimates the pelvis position and linear velocity using the leg kinematics
- *
- * @param trustedFoot is the foot used to estimates the pelvis state
- * @param numberOfTrustedSides is only one or both legs used to estimate the pelvis state
- */
- private void updatePelvisWithKinematics(int numberOfTrustedFeet,
- double alphaVelocityUpdate,
- FixedFramePoint3DBasics rootJointPosition,
- FixedFrameVector3DBasics rootJointLinearVelocity)
- {
- double scaleFactor = 1.0 / numberOfTrustedFeet;
-
- rootJointPositionPerFoot.add(footPositionInWorld, footToRootJointPosition);
- rootJointPosition.scaleAdd(scaleFactor, rootJointPositionPerFoot, rootJointPosition);
-
- rootJointLinearVelocity.scaleAdd(-scaleFactor * alphaVelocityUpdate, footVelocityInWorld, rootJointLinearVelocity);
- }
+ if (!kinematicsIsUpToDate.getBooleanValue())
+ throw new RuntimeException("Leg kinematics needs to be updated before trying to estimate the pelvis position/linear velocity.");
- /**
- * updates the position of a swinging foot
- *
- * @param swingingFoot a foot in swing
- * @param pelvisPosition the current pelvis position
- */
- private void updateUntrustedFootPosition(FramePoint3DReadOnly pelvisPosition)
+ if (trustedFeet.isEmpty())
{
- footPositionInWorld.sub(pelvisPosition, footToRootJointPosition);
-
- copPositionInWorld.set(footPositionInWorld);
-
- copRawInFootFrame.setToZero();
- copFilteredInFootFrame.setToZero();
+ rootJointPosition.set(pelvisPosition);
+ if (pelvisLinearVelocity != null)
+ rootJointLinearVelocity.set(pelvisLinearVelocity);
+ else
+ rootJointLinearVelocity.setToZero();
}
-
- private final FrameVector3D tempFrameVector = new FrameVector3D();
-
- private void updateTrustedFootPosition(boolean trustCoPAsNonSlippingContactPoint, FramePoint3DReadOnly rootJointPosition)
+ else
{
- if (trustCoPAsNonSlippingContactPoint)
+ averagedTrustedFootVelocity.setToZero();
+ for (int i = 0; i < trustedFeet.size(); i++)
{
- tempFrameVector.setIncludingFrame(rootJointPosition);
- tempFrameVector.sub(footToRootJointPosition); // New foot position
- tempFrameVector.sub(footPositionInWorld); // Delta from previous to new foot position
- copPositionInWorld.add(tempFrameVector); // New CoP position
- }
+ SingleFootEstimator footEstimator = footEstimatorMap.get(trustedFeet.get(i));
- footPositionInWorld.set(rootJointPosition);
- footPositionInWorld.sub(footToRootJointPosition);
- }
+ double scaleFactor = 1.0 / trustedFeet.size();
- private final FramePoint2D tempCoP2d = new FramePoint2D();
- private final FrameVector3D tempCoPOffset = new FrameVector3D();
+ // The root joint position is zeroed when we call #updateKinematics
+ rootJointPosition.scaleAdd(scaleFactor, footEstimator.getRootJointPositionFromKinematics(), rootJointPosition);
+ averagedTrustedFootVelocity.scaleAdd(scaleFactor, footEstimator.getCopVelocityInWorld(), averagedTrustedFootVelocity);
- /**
- * Compute the foot CoP. The CoP is the point on the support foot trusted to be not slipping.
- *
- * @param trustedSide
- * @param footSwitch
- */
- private void updateCoPPosition(boolean trustCoPAsNonSlippingContactPoint, boolean useControllerDesiredCoP)
- {
- if (trustCoPAsNonSlippingContactPoint)
- {
- if (useControllerDesiredCoP)
- centerOfPressureDataHolderFromController.getCenterOfPressure(tempCoP2d, foot);
- else
- footSwitch.getCenterOfPressure(tempCoP2d);
-
- if (tempCoP2d.containsNaN())
- {
- tempCoP2d.setToZero(soleFrame);
- }
- else
- {
- boolean isCoPInsideFoot = footPolygon.isPointInside(tempCoP2d);
- if (!isCoPInsideFoot)
- {
- if (footSwitch.getFootLoadPercentage() > 0.2)
- {
- footCenterCoPLineSegment.set(soleFrame, 0.0, 0.0, tempCoP2d.getX(), tempCoP2d.getY());
- int intersections = footPolygon.intersectionWith(footCenterCoPLineSegment, intersectionPoints[0], intersectionPoints[1]);
-
- if (intersections == 0)
- {
- LogTools.info("Found no solution for the CoP projection.");
- tempCoP2d.setToZero(soleFrame);
- }
- else
- {
- tempCoP2d.set(intersectionPoints[0]);
-
- if (intersections == 2)
- LogTools.info("Found two solutions for the CoP projection.");
- }
- }
- else // If foot barely loaded and actual CoP outside, then don't update the raw CoP right below
- {
- tempCoP2d.setIncludingFrame(copRawInFootFrame);
- }
- }
- }
-
- copRawInFootFrame.set(tempCoP2d);
-
- tempCoPOffset.setIncludingFrame(soleFrame, copFilteredInFootFrame.getX(), copFilteredInFootFrame.getY(), 0.0);
- copFilteredInFootFrame.update();
- tempCoPOffset.setIncludingFrame(soleFrame,
- copFilteredInFootFrame.getX() - tempCoPOffset.getX(),
- copFilteredInFootFrame.getY() - tempCoPOffset.getY(),
- 0.0);
-
- tempCoPOffset.changeFrame(worldFrame);
- copPositionInWorld.add(tempCoPOffset);
- }
- else
- {
- tempCoP2d.setToZero(soleFrame);
}
- }
-
- /**
- * Assuming the CoP is not moving, the foot position can be updated. That way we can see if the foot
- * is on the edge.
- *
- * @param plantedFoot
- */
- private void correctFootPositionsUsingCoP(boolean trustCoPAsNonSlippingContactPoint)
- {
- if (!trustCoPAsNonSlippingContactPoint)
- return;
-
- tempCoPOffset.setIncludingFrame(copFilteredInFootFrame, 0.0);
- tempCoPOffset.changeFrame(worldFrame);
- footPositionInWorld.sub(copPositionInWorld, tempCoPOffset);
- }
-
- /**
- * Updates the different kinematics related stuff that is used to estimate the pelvis state
- */
- public void updateKinematics()
- {
- tempFramePoint.setToZero(rootJointFrame);
- tempFramePoint.changeFrame(soleFrame);
- tempFrameVector.setIncludingFrame(tempFramePoint);
- tempFrameVector.changeFrame(worldFrame);
-
- footToRootJointPosition.update(tempFrameVector);
- }
-
- private final Twist tempRootBodyTwist = new Twist();
- private final Twist footTwistInWorld = new Twist();
-
- private void updateFootLinearVelocityInWorld(TwistReadOnly rootBodyTwist)
- {
- computeFootLinearVelocityInWorld(rootBodyTwist, footVelocityInWorld);
+ // We want the average velocity of the feet to trend to zero, since the trusted feet are assumed to not be moving.
+ rootJointLinearVelocity.scaleAdd(-alphaRootJointLinearVelocity.getValue(),
+ averagedTrustedFootVelocity,
+ rootJointLinearVelocity);
}
- private void computeFootLinearVelocityInWorld(TwistReadOnly rootBodyTwist, FixedFrameVector3DBasics footLinearVelocityToPack)
- {
- tempRootBodyTwist.setIncludingFrame(rootBodyTwist);
- tempRootBodyTwist.setBaseFrame(worldFrame);
- tempRootBodyTwist.changeFrame(foot.getBodyFixedFrame());
-
- foot.getBodyFixedFrame().getTwistRelativeToOther(rootJointFrame, footTwistInWorld);
- footTwistInWorld.add(tempRootBodyTwist);
- footTwistInWorld.setBodyFrame(soleFrame);
- footTwistInWorld.changeFrame(worldFrame);
+ rootJointLinearVelocityFDDebug.update();
- footTwistInWorld.getLinearVelocityAt(copPositionInWorld, footLinearVelocityToPack);
- }
+ kinematicsIsUpToDate.set(false);
}
- @Override
- public YoGraphicDefinition getSCS2YoGraphics()
+ public FramePoint3DReadOnly getPelvisPosition()
{
- if (!VISUALIZE)
- return null;
+ return rootJointPosition;
+ }
- YoGraphicGroupDefinition group = new YoGraphicGroupDefinition(getClass().getSimpleName());
- for (SingleFootEstimator footEstimator : footEstimators)
- {
- group.addChild(footEstimator.getSCS2YoGraphics());
- }
- return group;
+ public FrameVector3DReadOnly getPelvisVelocity()
+ {
+ return rootJointLinearVelocity;
}
}
diff --git a/ihmc-state-estimation/src/main/java/us/ihmc/stateEstimation/humanoid/kinematicsBasedStateEstimation/PelvisLinearStateUpdater.java b/ihmc-state-estimation/src/main/java/us/ihmc/stateEstimation/humanoid/kinematicsBasedStateEstimation/PelvisLinearStateUpdater.java
index 1c3615be2e70..5c2c9821db96 100644
--- a/ihmc-state-estimation/src/main/java/us/ihmc/stateEstimation/humanoid/kinematicsBasedStateEstimation/PelvisLinearStateUpdater.java
+++ b/ihmc-state-estimation/src/main/java/us/ihmc/stateEstimation/humanoid/kinematicsBasedStateEstimation/PelvisLinearStateUpdater.java
@@ -1,6 +1,7 @@
package us.ihmc.stateEstimation.humanoid.kinematicsBasedStateEstimation;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -11,6 +12,9 @@
import us.ihmc.euclid.referenceFrame.ReferenceFrame;
import us.ihmc.euclid.referenceFrame.interfaces.FixedFramePoint3DBasics;
import us.ihmc.euclid.referenceFrame.interfaces.FixedFrameVector3DBasics;
+import us.ihmc.euclid.referenceFrame.interfaces.FrameVector3DReadOnly;
+import us.ihmc.euclid.referenceFrame.tools.EuclidFrameFactories;
+import us.ihmc.euclid.tuple3D.Vector3D;
import us.ihmc.euclid.tuple3D.interfaces.Tuple3DReadOnly;
import us.ihmc.graphicsDescription.yoGraphics.YoGraphicsListRegistry;
import us.ihmc.humanoidRobotics.model.CenterOfPressureDataHolder;
@@ -56,6 +60,7 @@
public class PelvisLinearStateUpdater implements SCS2YoGraphicHolder
{
private static final boolean MORE_YOVARIABLES = false;
+ private static final boolean VISUALIZE_FEET_ESTIMATORS = true;
private static final double minForceZInPercentThresholdToFilterFoot = 0.0;
private static final double maxForceZInPercentThresholdToFilterFoot = 0.45;
@@ -94,11 +99,15 @@ public class PelvisLinearStateUpdater implements SCS2YoGraphicHolder
private final DoubleProvider forceZInPercentThresholdToTrustFoot;
private final DoubleProvider forceZInPercentThresholdToNotTrustFoot;
+ private final SingleFootEstimator[] footEstimators;
+ private final Map footEstimatorMap = new HashMap<>();
+
private final Map footSwitches;
private final Map footForces = new LinkedHashMap<>();
private final Map footWrenches = new LinkedHashMap<>();
private final DoubleProvider delayTimeBeforeTrustingFoot;
private final Map haveFeetHitGroundFiltered = new LinkedHashMap<>();
+ private final Map areFeetNotMovingFiltered = new LinkedHashMap<>();
private final Map areFeetTrusted = new LinkedHashMap<>();
private final Map wereFeetTrustedLastTick = new LinkedHashMap<>();
private final List listOfTrustedFeet = new ArrayList<>();
@@ -166,13 +175,71 @@ public PelvisLinearStateUpdater(FullInverseDynamicsStructure inverseDynamicsStru
setupBunchOfVariables();
- kinematicsBasedLinearStateCalculator = new PelvisKinematicsBasedLinearStateCalculator(inverseDynamicsStructure,
- feetContactablePlaneBodies,
- footSwitches,
- centerOfPressureDataHolderFromController,
+ Map footIMUs = new HashMap<>();
+ for (IMUSensorReadOnly imu : imuProcessedOutputs)
+ {
+ if (feetContactablePlaneBodies.containsKey(imu.getMeasurementLink()))
+ footIMUs.put(imu.getMeasurementLink(), imu);
+ }
+
+ FrameVector3DReadOnly gravityVector = EuclidFrameFactories.newLinkedFrameVector3DReadOnly(() -> worldFrame, new Vector3D(0, 0, -Math.abs(gravitationalAcceleration)));
+
+ DoubleProvider footToRootJointPositionBreakFrequency = new DoubleParameter("FootToRootJointPositionBreakFrequency",
+ registry,
+ stateEstimatorParameters.getKinematicsPelvisPositionFilterFreqInHertz());
+ DoubleProvider copFilterBreakFrequency = new DoubleParameter("CopFilterBreakFrequency", registry, stateEstimatorParameters.getCoPFilterFreqInHertz());
+ YoBoolean useFootIMUData = new YoBoolean("useFootIMUData", registry);
+ YoDouble footLinearVelocityMovingThreshold = new YoDouble("footLinearVelocityMovingThreshold", registry);
+ YoDouble footAngularVelocityMovingThreshold = new YoDouble("footAngularVelocityMovingThreshold", registry);
+ YoDouble footAlphaLeakIMUOnly = new YoDouble("footIMUOnlyAlphaLeak", registry);
+ YoDouble footImuAgainstKinematicsForVelocityBreakFrequency = new YoDouble("footIMUAgainstKinematicsForVelocityBreakFrequency", registry);
+ YoDouble footImuAgainstKinematicsForPositionBreakFrequency = new YoDouble("footIMUAgainstKinematicsForPositionBreakFrequency", registry);
+ footImuAgainstKinematicsForPositionBreakFrequency.set(12.0);
+ footImuAgainstKinematicsForVelocityBreakFrequency.set(2.0);
+ footAlphaLeakIMUOnly.set(0.999);
+ footLinearVelocityMovingThreshold.set(0.3);
+ footAngularVelocityMovingThreshold.set(0.6);
+
+ RigidBodyBasics[] feetRigidBodies = feetContactablePlaneBodies.keySet().toArray(new RigidBodyBasics[0]);
+
+ footEstimators = new SingleFootEstimator[feetRigidBodies.length];
+ for (int i = 0; i < feetRigidBodies.length; i++)
+ {
+ RigidBodyBasics footRigidBody = feetRigidBodies[i];
+ ContactablePlaneBody contactableFoot = feetContactablePlaneBodies.get(footRigidBody);
+ FootSwitchInterface footSwitch = footSwitches.get(footRigidBody);
+ footEstimators[i] = new SingleFootEstimator(rootJoint,
+ contactableFoot,
+ footSwitch,
+ footIMUs.get(footRigidBody),
+ imuBiasProvider,
+ footToRootJointPositionBreakFrequency,
+ copFilterBreakFrequency,
+ centerOfPressureDataHolderFromController,
+ cancelGravityFromAccelerationMeasurement,
+ footLinearVelocityMovingThreshold,
+ footAngularVelocityMovingThreshold,
+ gravityVector,
+ useFootIMUData,
+ footImuAgainstKinematicsForPositionBreakFrequency,
+ footImuAgainstKinematicsForVelocityBreakFrequency,
+ footAlphaLeakIMUOnly,
+ estimatorDT,
+ registry);
+ footEstimatorMap.put(footRigidBody, footEstimators[i]);
+ }
+
+ if (VISUALIZE_FEET_ESTIMATORS)
+ {
+ for (SingleFootEstimator footEstimator : footEstimators)
+ {
+ footEstimator.createVisualization(yoGraphicsListRegistry);
+ }
+ }
+
+ kinematicsBasedLinearStateCalculator = new PelvisKinematicsBasedLinearStateCalculator(footEstimatorMap,
estimatorDT,
stateEstimatorParameters,
- yoGraphicsListRegistry,
registry);
imuBasedLinearStateCalculator = new PelvisIMUBasedLinearStateCalculator(rootJoint,
@@ -263,8 +330,11 @@ private void setupBunchOfVariables()
String footPrefix = foot.getName();
final GlitchFilteredYoBoolean hasFootHitTheGroundFiltered = new GlitchFilteredYoBoolean("has" + footPrefix + "FootHitGroundFiltered", registry, 0);
+ final GlitchFilteredYoBoolean isFootMovingFiltered = new GlitchFilteredYoBoolean("is" + footPrefix + "FootNotMovingFiltered", registry, 0);
hasFootHitTheGroundFiltered.set(true);
+ isFootMovingFiltered.set(false);
haveFeetHitGroundFiltered.put(foot, hasFootHitTheGroundFiltered);
+ areFeetNotMovingFiltered.put(foot, isFootMovingFiltered);
YoBoolean isFootTrusted = new YoBoolean("is" + footPrefix + "FootTrusted", registry);
YoBoolean wasFootTrusted = new YoBoolean("was" + footPrefix + "FootTrustedLastTick", registry);
@@ -322,6 +392,16 @@ private void initializeRobotState()
rootJointVelocity.setToZero();
kinematicsBasedLinearStateCalculator.initialize(rootJointPosition);
+ // Initialize the feet states
+ tempRootBodyTwist.setIncludingFrame(rootJoint.getJointTwist());
+
+ for (SingleFootEstimator footEstimator : footEstimators)
+ {
+ footEstimator.initialize();
+ footEstimator.updateStateFromKinematics(tempRootBodyTwist);
+ footEstimator.correctFootPositionFromRootJoint(false, false, rootJointPosition);
+ }
+
imuBasedLinearStateCalculator.initialize();
mainIMULinearVelocityEstimate.getPositionEstimation().setToZero();
mainIMULinearVelocityEstimate.getRateEstimation().setToZero();
@@ -340,7 +420,13 @@ public void updateForFrozenState()
// Keep setting the position so the localization updater works properly.
rootJoint.setJointPosition(rootJointPosition);
kinematicsBasedLinearStateCalculator.updateKinematics();
- kinematicsBasedLinearStateCalculator.updateNoTrustedFeet(rootJointPosition, null);
+ updateFeetFromKinematics();
+ for (SingleFootEstimator footEstimator : footEstimators)
+ {
+ footEstimator.correctFootPositionFromRootJoint(false, false, rootJointPosition);
+ }
+ kinematicsBasedLinearStateCalculator.setRootJointState(rootJointPosition, null);
+
// Reset the IMU updater
imuBasedLinearStateCalculator.initialize();
@@ -350,14 +436,51 @@ public void updateForFrozenState()
rootJointVelocity.setToZero();
}
+ private final Twist tempRootBodyTwist = new Twist();
+
+ private void updateFeetFromKinematics()
+ {
+ // FIXME do we want to use the fused pelvis velocity?
+ tempRootBodyTwist.setIncludingFrame(rootJoint.getJointTwist());
+ tempRootBodyTwist.getLinearPart().setMatchingFrame(kinematicsBasedLinearStateCalculator.getPelvisVelocity());
+
+ for (SingleFootEstimator footEstimator : footEstimators)
+ {
+ footEstimator.updateStateFromKinematics(tempRootBodyTwist);
+ }
+ }
+
+ private void correctFeetStateFromPelvis()
+ {
+ if (kinematicsBasedLinearStateCalculator.getCorrectTrustedFeetPositions())
+ {
+ for (int i = 0; i < listOfTrustedFeet.size(); i++)
+ {
+ SingleFootEstimator footEstimator = footEstimatorMap.get(listOfTrustedFeet.get(i));
+ footEstimator.correctFootPositionFromRootJoint(true,
+ kinematicsBasedLinearStateCalculator.getTrustCoPAsNonSlippingContactPoint(),
+ kinematicsBasedLinearStateCalculator.getPelvisPosition());
+ }
+ }
+
+ for (int i = 0; i < listOfUnTrustedFeet.size(); i++)
+ {
+ SingleFootEstimator footEstimator = footEstimatorMap.get(listOfUnTrustedFeet.get(i));
+ footEstimator.correctFootPositionFromRootJoint(false, false, kinematicsBasedLinearStateCalculator.getPelvisPosition());
+ }
+ }
+
+
+
public void updateRootJointPositionAndLinearVelocity()
{
+ kinematicsBasedLinearStateCalculator.updateKinematics();
+ updateFeetFromKinematics();
+
if (requestStopEstimationOfPelvisLinearState.getBooleanValue())
return;
- kinematicsBasedLinearStateCalculator.updateKinematics();
-
- numberOfEndEffectorsTrusted.set(setTrustedFeetUsingFootSwitches());
+ numberOfEndEffectorsTrusted.set(setTrustedFeetUsingFootSwitchesAndKinematics());
numberOfEndEffectorsFilteredByLoad.set(0);
if (numberOfEndEffectorsTrusted.getIntegerValue() >= optimalNumberOfTrustedFeet.getValue())
@@ -393,15 +516,21 @@ public void updateRootJointPositionAndLinearVelocity()
tempTwist.changeFrame(rootJointFrame);
rootJointNewLinearVelocityEstimate.setMatchingFrame(tempTwist.getLinearPart());
rootJointPositionEstimate.update(null, rootJointNewLinearVelocityEstimate);
- kinematicsBasedLinearStateCalculator.updateNoTrustedFeet(rootJointPosition, rootJointVelocity);
+
+ for (SingleFootEstimator footEstimator : footEstimators)
+ {
+ footEstimator.correctFootPositionFromRootJoint(false, false, rootJointPosition);
+ }
+
+ kinematicsBasedLinearStateCalculator.setRootJointState(rootJointPosition, rootJointVelocity);
}
else
throw new RuntimeException("No foot trusted!");
}
else if (numberOfEndEffectorsTrusted.getIntegerValue() > 0)
{
- updateTrustedFeetLists();
- kinematicsBasedLinearStateCalculator.estimatePelvisLinearState(listOfTrustedFeet, listOfUnTrustedFeet, rootJointPosition);
+ updateTrustedFeet();
+ kinematicsBasedLinearStateCalculator.estimatePelvisLinearState(listOfTrustedFeet, rootJointPosition, rootJointVelocity);
if (imuBasedLinearStateCalculator.isEstimationEnabled())
{
@@ -412,6 +541,9 @@ else if (numberOfEndEffectorsTrusted.getIntegerValue() > 0)
rootJointPosition.set(kinematicsBasedLinearStateCalculator.getPelvisPosition());
rootJointVelocity.set(kinematicsBasedLinearStateCalculator.getPelvisVelocity());
}
+
+ // FIXME should this used the fused measurement, rather than the kinematic only measurement?
+ correctFeetStateFromPelvis();
}
else
{
@@ -430,8 +562,9 @@ private void updateRootJoint()
imuBasedLinearStateCalculator.saveMeasurementFrameTwist(rootJoint.getJointTwist());
}
- private int setTrustedFeetUsingFootSwitches()
+ private int setTrustedFeetUsingFootSwitchesAndKinematics()
{
+ int numberOfEndEffectorsHitGround = 0;
int numberOfEndEffectorsTrusted = 0;
int windowSize = (int) (delayTimeBeforeTrustingFoot.getValue() / estimatorDT);
@@ -440,19 +573,49 @@ private int setTrustedFeetUsingFootSwitches()
{
RigidBodyBasics foot = feet.get(i);
wereFeetTrustedLastTick.get(foot).set(areFeetTrusted.get(foot).getValue());
- haveFeetHitGroundFiltered.get(foot).setWindowSize(windowSize);
+
+ GlitchFilteredYoBoolean hasFootHitGround = haveFeetHitGroundFiltered.get(foot);
+ GlitchFilteredYoBoolean isFootNotMoving = areFeetNotMovingFiltered.get(foot);
+ hasFootHitGround.setWindowSize(windowSize);
+ isFootNotMoving.setWindowSize(windowSize);
if (footSwitches.get(foot).hasFootHitGroundFiltered())
- haveFeetHitGroundFiltered.get(foot).update(true);
+ hasFootHitGround.update(true);
+ else
+ hasFootHitGround.set(false);
+ if (!footEstimatorMap.get(foot).isFootMoving())
+ isFootNotMoving.update(true);
else
- haveFeetHitGroundFiltered.get(foot).set(false);
+ isFootNotMoving.set(false);
- if (haveFeetHitGroundFiltered.get(foot).getBooleanValue())
+ if (hasFootHitGround.getBooleanValue())
+ numberOfEndEffectorsHitGround++;
+ if (hasFootHitGround.getBooleanValue() && isFootNotMoving.getBooleanValue())
numberOfEndEffectorsTrusted++;
}
- // Update only if at least one foot hit the ground
+ // TODO better incorporate the conditions of nothing moving
+
+ // Update only if at least one foot hit the ground and its not moving
if (numberOfEndEffectorsTrusted > 0)
+ {
+ if (trustOnlyLowestFoot.getValue())
+ {
+ numberOfEndEffectorsTrusted = filterAndTrustLowestFoot();
+ }
+ else
+ {
+ for (int i = 0; i < feet.size(); i++)
+ {
+ RigidBodyBasics foot = feet.get(i);
+ boolean isFootOnGround = haveFeetHitGroundFiltered.get(foot).getBooleanValue();
+ boolean isFootNotMoving = areFeetNotMovingFiltered.get(foot).getBooleanValue();
+ areFeetTrusted.get(foot).set(isFootOnGround && isFootNotMoving);
+ }
+ }
+ }
+ // Update if you have a foot on the ground, but it's moving
+ else if (numberOfEndEffectorsHitGround > 0)
{
if (trustOnlyLowestFoot.getValue())
{
@@ -466,6 +629,8 @@ private int setTrustedFeetUsingFootSwitches()
boolean isFootOnGround = haveFeetHitGroundFiltered.get(foot).getBooleanValue();
areFeetTrusted.get(foot).set(isFootOnGround);
}
+ // override the number of end effectors trusted with the number that hit the ground.
+ numberOfEndEffectorsTrusted = numberOfEndEffectorsHitGround;
}
}
// Else if there is a foot with a force past the threshold trust the force and not the CoP
@@ -494,6 +659,8 @@ private int setTrustedFeetUsingFootSwitches()
}
}
+
+
if (numberOfEndEffectorsTrusted == 0)
{
if (trustImuWhenNoFeetAreInContact.getValue())
@@ -678,7 +845,7 @@ private void computePositionFromMergingMeasurements()
}
}
- private void updateTrustedFeetLists()
+ private void updateTrustedFeet()
{
listOfTrustedFeet.clear();
listOfUnTrustedFeet.clear();
@@ -691,6 +858,32 @@ private void updateTrustedFeetLists()
else
listOfUnTrustedFeet.add(foot);
}
+
+ if (!kinematicsBasedLinearStateCalculator.getKinematicsIsUpToDate())
+ throw new RuntimeException("Leg kinematics needs to be updated before trying to estimate the pelvis position/linear velocity.");
+
+ for (int i = 0; i < listOfTrustedFeet.size(); i++)
+ {
+ SingleFootEstimator footEstimator = footEstimatorMap.get(listOfTrustedFeet.get(i));
+ if (kinematicsBasedLinearStateCalculator.getAssumeTrustedFootAtZeroHeight())
+ {
+ if (kinematicsBasedLinearStateCalculator.getTrustCoPAsNonSlippingContactPoint())
+ {
+ footEstimator.computeStateSettingCoPZ(kinematicsBasedLinearStateCalculator.getTrustCoPAsNonSlippingContactPoint(),
+ kinematicsBasedLinearStateCalculator.getUseControllerDesiredCoP(),
+ 0.0);
+ }
+ else
+ {
+ footEstimator.computeStateSettingFootZ(0.0);
+ }
+ }
+ else
+ {
+ footEstimator.computeState(kinematicsBasedLinearStateCalculator.getTrustCoPAsNonSlippingContactPoint(),
+ kinematicsBasedLinearStateCalculator.getUseControllerDesiredCoP());
+ }
+ }
}
public void getEstimatedPelvisPosition(FramePoint3D pelvisPositionToPack)
@@ -712,8 +905,17 @@ public List getCurrentListOfTrustedFeet()
public YoGraphicDefinition getSCS2YoGraphics()
{
YoGraphicGroupDefinition group = new YoGraphicGroupDefinition(getClass().getSimpleName());
- group.addChild(kinematicsBasedLinearStateCalculator.getSCS2YoGraphics());
group.addChild(imuBasedLinearStateCalculator.getSCS2YoGraphics());
+
+ if (!VISUALIZE_FEET_ESTIMATORS)
+ return group;
+
+ for (SingleFootEstimator footEstimator : footEstimators)
+ {
+ group.addChild(footEstimator.getSCS2YoGraphics());
+ }
+
+
return group;
}
}
diff --git a/ihmc-state-estimation/src/main/java/us/ihmc/stateEstimation/humanoid/kinematicsBasedStateEstimation/SingleFootEstimator.java b/ihmc-state-estimation/src/main/java/us/ihmc/stateEstimation/humanoid/kinematicsBasedStateEstimation/SingleFootEstimator.java
new file mode 100644
index 000000000000..5d149fcd4f6e
--- /dev/null
+++ b/ihmc-state-estimation/src/main/java/us/ihmc/stateEstimation/humanoid/kinematicsBasedStateEstimation/SingleFootEstimator.java
@@ -0,0 +1,517 @@
+package us.ihmc.stateEstimation.humanoid.kinematicsBasedStateEstimation;
+
+import us.ihmc.euclid.referenceFrame.FrameConvexPolygon2D;
+import us.ihmc.euclid.referenceFrame.FrameLineSegment2D;
+import us.ihmc.euclid.referenceFrame.FramePoint2D;
+import us.ihmc.euclid.referenceFrame.FramePoint3D;
+import us.ihmc.euclid.referenceFrame.FrameVector3D;
+import us.ihmc.euclid.referenceFrame.ReferenceFrame;
+import us.ihmc.euclid.referenceFrame.interfaces.FixedFrameVector3DBasics;
+import us.ihmc.euclid.referenceFrame.interfaces.FramePoint2DBasics;
+import us.ihmc.euclid.referenceFrame.interfaces.FramePoint3DReadOnly;
+import us.ihmc.euclid.referenceFrame.interfaces.FrameVector3DReadOnly;
+import us.ihmc.euclid.referenceFrame.interfaces.FrameVertex2DSupplier;
+import us.ihmc.graphicsDescription.appearance.YoAppearance;
+import us.ihmc.graphicsDescription.yoGraphics.YoGraphicPosition;
+import us.ihmc.graphicsDescription.yoGraphics.YoGraphicsListRegistry;
+import us.ihmc.graphicsDescription.yoGraphics.plotting.YoArtifactPosition;
+import us.ihmc.humanoidRobotics.model.CenterOfPressureDataHolder;
+import us.ihmc.log.LogTools;
+import us.ihmc.mecano.multiBodySystem.interfaces.FloatingJointBasics;
+import us.ihmc.mecano.multiBodySystem.interfaces.RigidBodyBasics;
+import us.ihmc.mecano.spatial.Twist;
+import us.ihmc.mecano.spatial.interfaces.TwistReadOnly;
+import us.ihmc.robotics.SCS2YoGraphicHolder;
+import us.ihmc.robotics.contactable.ContactablePlaneBody;
+import us.ihmc.robotics.sensors.FootSwitchInterface;
+import us.ihmc.scs2.definition.visual.ColorDefinitions;
+import us.ihmc.scs2.definition.yoGraphic.YoGraphicDefinition;
+import us.ihmc.scs2.definition.yoGraphic.YoGraphicDefinitionFactory;
+import us.ihmc.scs2.definition.yoGraphic.YoGraphicDefinitionFactory.DefaultPoint2DGraphic;
+import us.ihmc.sensorProcessing.stateEstimation.IMUSensorReadOnly;
+import us.ihmc.yoVariables.euclid.filters.AlphaFilteredYoFramePoint2D;
+import us.ihmc.yoVariables.euclid.filters.AlphaFilteredYoFrameVector3D;
+import us.ihmc.yoVariables.euclid.referenceFrame.YoFramePoint2D;
+import us.ihmc.yoVariables.euclid.referenceFrame.YoFramePoint3D;
+import us.ihmc.yoVariables.euclid.referenceFrame.YoFrameVector3D;
+import us.ihmc.yoVariables.filters.AlphaFilterTools;
+import us.ihmc.yoVariables.providers.BooleanProvider;
+import us.ihmc.yoVariables.providers.DoubleProvider;
+import us.ihmc.yoVariables.registry.YoRegistry;
+import us.ihmc.yoVariables.variable.YoBoolean;
+import us.ihmc.yoVariables.variable.YoDouble;
+
+public class SingleFootEstimator implements SCS2YoGraphicHolder
+{
+ private final RigidBodyBasics foot;
+
+ private final ReferenceFrame rootJointFrame;
+ private final ReferenceFrame soleFrame;
+ private final ReferenceFrame worldFrame = ReferenceFrame.getWorldFrame();
+
+ private final BooleanProvider useIMUData;
+ private final YoFrameVector3D footIKLinearVelocityInWorld;
+ private final YoFrameVector3D footIKAngularVelocityInWorld;
+ private final YoFrameVector3D footIMULinearVelocityInWorld;
+ private final YoFrameVector3D footIMUAngularVelocityInWorld;
+ private final YoFrameVector3D footFusedLinearVelocityInWorld;
+ private final YoFrameVector3D footFusedAngularVelocityInWorld;
+
+ private final YoDouble footLinearVelocity;
+ private final YoDouble footAngularVelocity;
+ private final YoBoolean footIsMoving;
+
+ private final YoFrameVector3D copVelocityInWorld;
+ private final AlphaFilteredYoFrameVector3D footToRootJointPosition;
+ private final AlphaFilteredYoFrameVector3D footToRootJointVelocity;
+ private final YoFramePoint3D footPositionInWorld;
+ /** Debug variable */
+ private final YoFramePoint3D rootJointPositionPerFoot;
+ private final YoFrameVector3D rootJointVelocityPerFoot;
+
+ private final YoFramePoint3D copIKPositionInWorld;
+ private final YoFramePoint3D copIMUPositionInWorld;
+ private final YoFramePoint3D copFusedPositionInWorld;
+ private final YoFramePoint2D previousCoPInFootFrame;
+
+ private final AlphaFilteredYoFramePoint2D copFilteredInFootFrame;
+ private final YoFramePoint2D copRawInFootFrame;
+ private final FrameConvexPolygon2D footPolygon;
+ private final FrameLineSegment2D footCenterCoPLineSegment;
+ private final FootSwitchInterface footSwitch;
+ private final IMUSensorReadOnly footIMU;
+ private final IMUBiasProvider imuBiasProvider;
+ private final CenterOfPressureDataHolder centerOfPressureDataHolderFromController;
+ private final DoubleProvider footAlphaLeakIMUOnly;
+ private final DoubleProvider imuAgainstKinematicsForVelocityBreakFrequency;
+ private final DoubleProvider imuAgainstKinematicsForPositionBreakFrequency;
+ private final BooleanProvider cancelGravityFromAccelerationMeasurement;
+ private final DoubleProvider footLinearVelocityMovingThreshold;
+ private final DoubleProvider footAngularVelocityMovingThreshold;
+ private final FrameVector3DReadOnly gravityVector;
+ private final double estimatorDT;
+
+ private final FramePoint2DBasics[] intersectionPoints = new FramePoint2DBasics[] {new FramePoint2D(), new FramePoint2D()};
+
+ public SingleFootEstimator(FloatingJointBasics rootJoint,
+ ContactablePlaneBody contactableFoot,
+ FootSwitchInterface footSwitch,
+ IMUSensorReadOnly footIMU,
+ IMUBiasProvider imuBiasProvider,
+ DoubleProvider footToRootJointPositionBreakFrequency,
+ DoubleProvider copFilterBreakFrequency,
+ CenterOfPressureDataHolder centerOfPressureDataHolderFromController,
+ BooleanProvider cancelGravityFromAccelerationMeasurement,
+ DoubleProvider footLinearVelocityMovingThreshold,
+ DoubleProvider footAngularVelocityMovingThreshold,
+ FrameVector3DReadOnly gravityVector,
+ BooleanProvider useIMUData,
+ DoubleProvider imuAgainstKinematicsForPositionBreakFrequency,
+ DoubleProvider imuAgainstKinematicsForVelocityBreakFrequency,
+ DoubleProvider footAlphaLeakIMUOnly,
+ double estimatorDT,
+ YoRegistry registry)
+ {
+ this.rootJointFrame = rootJoint.getFrameAfterJoint();
+ this.footSwitch = footSwitch;
+ this.footIMU = footIMU;
+ this.imuBiasProvider = imuBiasProvider;
+ this.centerOfPressureDataHolderFromController = centerOfPressureDataHolderFromController;
+ this.cancelGravityFromAccelerationMeasurement = cancelGravityFromAccelerationMeasurement;
+ this.footLinearVelocityMovingThreshold = footLinearVelocityMovingThreshold;
+ this.footAngularVelocityMovingThreshold = footAngularVelocityMovingThreshold;
+ this.gravityVector = gravityVector;
+ this.useIMUData = useIMUData;
+ this.imuAgainstKinematicsForPositionBreakFrequency = imuAgainstKinematicsForPositionBreakFrequency;
+ this.imuAgainstKinematicsForVelocityBreakFrequency = imuAgainstKinematicsForVelocityBreakFrequency;
+ this.footAlphaLeakIMUOnly = footAlphaLeakIMUOnly;
+ this.estimatorDT = estimatorDT;
+ foot = contactableFoot.getRigidBody();
+ soleFrame = contactableFoot.getContactFrame();
+
+ String namePrefix = foot.getName();
+
+ DoubleProvider alphaFoot = () -> AlphaFilterTools.computeAlphaGivenBreakFrequencyProperly(footToRootJointPositionBreakFrequency.getValue(), estimatorDT);
+ footToRootJointPosition = new AlphaFilteredYoFrameVector3D(namePrefix + "FootToRootJointPosition", "", registry, alphaFoot, worldFrame);
+ footToRootJointVelocity = new AlphaFilteredYoFrameVector3D(namePrefix + "FootToRootJointVelocity", "", registry, alphaFoot, worldFrame);
+ rootJointPositionPerFoot = new YoFramePoint3D(namePrefix + "BasedRootJointPosition", worldFrame, registry);
+ rootJointVelocityPerFoot = new YoFrameVector3D(namePrefix + "BasedRootJointVelocity", worldFrame, registry);
+ footPositionInWorld = new YoFramePoint3D(namePrefix + "FootPositionInWorld", worldFrame, registry);
+ footPolygon = new FrameConvexPolygon2D(FrameVertex2DSupplier.asFrameVertex2DSupplier(contactableFoot.getContactPoints2D()));
+ footCenterCoPLineSegment = new FrameLineSegment2D(soleFrame);
+ copRawInFootFrame = new YoFramePoint2D(namePrefix + "CoPRawInFootFrame", soleFrame, registry);
+ previousCoPInFootFrame = new YoFramePoint2D(namePrefix + "PreviousCoPInFootFrame", soleFrame, registry);
+
+ footLinearVelocity = new YoDouble(namePrefix + "LinearVelocity", registry);
+ footAngularVelocity = new YoDouble(namePrefix + "AngularVelocity", registry);
+ footIsMoving = new YoBoolean(namePrefix + "IsMoving", registry);
+
+ footIKLinearVelocityInWorld = new YoFrameVector3D(namePrefix + "IKLinearVelocityInWorld", worldFrame, registry);
+ footIKAngularVelocityInWorld = new YoFrameVector3D(namePrefix + "IKAngularVelocityInWorld", worldFrame, registry);
+ footIMULinearVelocityInWorld = new YoFrameVector3D(namePrefix + "IMULinearVelocityInWorld", worldFrame, registry);
+ footIMUAngularVelocityInWorld = new YoFrameVector3D(namePrefix + "IMUAngularVelocityInWorld", worldFrame, registry);
+ footFusedLinearVelocityInWorld = new YoFrameVector3D(namePrefix + "FusedLinearVelocityInWorld", worldFrame, registry);
+ footFusedAngularVelocityInWorld = new YoFrameVector3D(namePrefix + "FusedAngularVelocityInWorld", worldFrame, registry);
+
+ DoubleProvider alphaCop = () -> AlphaFilterTools.computeAlphaGivenBreakFrequencyProperly(copFilterBreakFrequency.getValue(), estimatorDT);
+ copFilteredInFootFrame = new AlphaFilteredYoFramePoint2D(namePrefix + "CoPFilteredInFootFrame", "", registry, alphaCop, copRawInFootFrame);
+ copFilteredInFootFrame.update(0.0, 0.0);
+ copIKPositionInWorld = new YoFramePoint3D(namePrefix + "CoPIKPositionInWorld", worldFrame, registry);
+ copIMUPositionInWorld = new YoFramePoint3D(namePrefix + "CoPIMUPositionInWorld", worldFrame, registry);
+ copFusedPositionInWorld = new YoFramePoint3D(namePrefix + "CoPFusedPositionInWorld", worldFrame, registry);
+ copVelocityInWorld = new YoFrameVector3D(namePrefix + "CoPVelocityInWorld", worldFrame, registry);
+ }
+
+ public void createVisualization(YoGraphicsListRegistry yoGraphicsListRegistry)
+ {
+ if (yoGraphicsListRegistry == null)
+ return;
+
+ String sidePrefix = foot.getName();
+ YoGraphicPosition copInWorld = new YoGraphicPosition(sidePrefix + "StateEstimatorCoP", copFusedPositionInWorld, 0.005, YoAppearance.DeepPink());
+ YoArtifactPosition artifact = copInWorld.createArtifact();
+ artifact.setVisible(false);
+ yoGraphicsListRegistry.registerArtifact("StateEstimator", artifact);
+ }
+
+ @Override
+ public YoGraphicDefinition getSCS2YoGraphics()
+ {
+ return YoGraphicDefinitionFactory.newYoGraphicPoint2D(foot.getName() + "StateEstimatorCoP", copFusedPositionInWorld,
+ 0.01,
+ ColorDefinitions.DeepPink(),
+ DefaultPoint2DGraphic.CIRCLE);
+ }
+
+ public void initialize()
+ {
+ footToRootJointPosition.reset();
+ footToRootJointVelocity.reset();
+ copFilteredInFootFrame.reset();
+ copFilteredInFootFrame.update(0.0, 0.0);
+ copFusedPositionInWorld.setMatchingFrame(copFilteredInFootFrame, 0.0);
+ copVelocityInWorld.setToZero();
+ }
+
+ private final FramePoint3D tempFramePoint = new FramePoint3D();
+
+ private final Twist tempRootBodyTwist = new Twist();
+ private final Twist footTwistInWorld = new Twist();
+ private final Twist imuTwist = new Twist();
+
+
+ private final FrameVector3D tempFrameVector = new FrameVector3D();
+ private final FramePoint3D tempPoint = new FramePoint3D();
+
+ /**
+ * Updates the different kinematics related stuff that is used to estimate the pelvis state. This should be called before calling the other objectives.
+ */
+ public void updateStateFromKinematics(TwistReadOnly rootBodyTwist)
+ {
+ computeFootTwistInWorldFusingBaseStateAndFootIMU(rootBodyTwist);
+
+ // Update the vector from the root joint to the foot, based on the kinematics
+ tempFramePoint.setToZero(rootJointFrame);
+ tempFramePoint.changeFrame(soleFrame);
+
+ tempFrameVector.setIncludingFrame(tempFramePoint);
+ tempFrameVector.changeFrame(worldFrame);
+
+ footToRootJointPosition.update(tempFrameVector);
+
+ footLinearVelocity.set(getFootLinearVelocityInWorld().norm());
+ footAngularVelocity.set(getFootAngularVelocityInWorld().norm());
+
+ if (useIMUData.getValue() && footIMU != null)
+ footIsMoving.set(footLinearVelocity.getDoubleValue() > footLinearVelocityMovingThreshold.getValue() || footAngularVelocity.getDoubleValue() > footAngularVelocityMovingThreshold.getValue());
+ else
+ footIsMoving.set(false);
+ }
+
+ /**
+ * One of the options for computing the
+ * @param useControllerDesiredCoP
+ * @param zPosition
+ */
+ public void computeStateSettingCoPZ(boolean trustCoPAsNonSlippingContactPoint, boolean useControllerDesiredCoP, double zPosition)
+ {
+ updateCoPState(trustCoPAsNonSlippingContactPoint, useControllerDesiredCoP);
+ copFusedPositionInWorld.setZ(zPosition);
+ if (trustCoPAsNonSlippingContactPoint)
+ correctFootPositionUsingCoP();
+
+ rootJointPositionPerFoot.add(footPositionInWorld, footToRootJointPosition);
+ rootJointVelocityPerFoot.sub(footFusedLinearVelocityInWorld, footToRootJointVelocity);
+ }
+
+ public void computeState(boolean trustCoPAsNonSlippingContactPoint, boolean useControllerDesiredCoP)
+ {
+ updateCoPState(trustCoPAsNonSlippingContactPoint, useControllerDesiredCoP);
+ if (trustCoPAsNonSlippingContactPoint)
+ correctFootPositionUsingCoP();
+
+ rootJointPositionPerFoot.add(footPositionInWorld, footToRootJointPosition);
+ rootJointVelocityPerFoot.sub(footFusedLinearVelocityInWorld, footToRootJointVelocity);
+ }
+
+ public void computeStateSettingFootZ(double zPosition)
+ {
+ footPositionInWorld.setZ(zPosition);
+ // Update the CoP position from this foot position
+ copIKPositionInWorld.set(footPositionInWorld);
+ copFusedPositionInWorld.set(footPositionInWorld);
+
+ rootJointPositionPerFoot.add(footPositionInWorld, footToRootJointPosition);
+ rootJointVelocityPerFoot.sub(footFusedLinearVelocityInWorld, footToRootJointVelocity);
+ }
+
+ /**
+ * Corrects the position of the foot and center of pressure in the world using the root joint position and the forward kinematics from the root joint to the
+ * foot. If the foot is not trusted, it sets the center of pressure from the foot. If it is trusted, but is non slipping, we shift the center of pressure
+ * based on this correction.
+ *
+ *
+ * This should be called once the fusion of the different feet to compute the root joint state has occurred.
+ *
+ *
+ * @param isTrusted whether the foot is trusted, and therefore the CoP is trusted.
+ * @param trustCoPAsNonSlippingContactPoint whether to shift the CoP of a trusted foot based on the correction
+ * @param rootJointPosition accurate root joint position to apply.
+ */
+ public void correctFootPositionFromRootJoint(boolean isTrusted, boolean trustCoPAsNonSlippingContactPoint, FramePoint3DReadOnly rootJointPosition)
+ {
+ // Store the old, uncorrected foot position.
+ tempPoint.set(footPositionInWorld);
+ // Update the foot position
+ footPositionInWorld.sub(rootJointPosition, footToRootJointPosition);
+
+ if (!isTrusted)
+ {
+ // If the foot isn't trusted, the CoP position isn't relevant, and we can set it to zero in the foot frame.
+ copFusedPositionInWorld.set(footPositionInWorld);
+ copRawInFootFrame.setToZero();
+ copFilteredInFootFrame.setToZero();
+ }
+ else if (trustCoPAsNonSlippingContactPoint)
+ {
+ // If the foot is trusted, but we allow the CoP to move, we need to shift the CoP position to account for this drift. That means computing the drift
+ // delta correction, and then adding it to the position.
+ tempFrameVector.sub(footPositionInWorld, tempPoint); // Delta from previous to new foot position
+ copFusedPositionInWorld.add(tempFrameVector); // New CoP position
+ }
+ }
+
+ public FramePoint3DReadOnly getRootJointPositionFromKinematics()
+ {
+ return rootJointPositionPerFoot;
+ }
+
+ public FrameVector3DReadOnly getRootJointVelocityFromKinematics()
+ {
+ return rootJointVelocityPerFoot;
+ }
+
+ public FrameVector3DReadOnly getCopVelocityInWorld()
+ {
+ return copVelocityInWorld;
+ }
+
+ public FrameVector3DReadOnly getFootLinearVelocityInWorld()
+ {
+ return footFusedLinearVelocityInWorld;
+ }
+
+ public FrameVector3DReadOnly getFootAngularVelocityInWorld()
+ {
+ return footFusedAngularVelocityInWorld;
+ }
+
+ public boolean isFootMoving()
+ {
+ return footIsMoving.getBooleanValue();
+ }
+
+ private final FramePoint2D tempCoP2d = new FramePoint2D();
+ private final FrameVector3D tempCoPOffset = new FrameVector3D();
+
+ private void updateCoPState(boolean trustCoPAsNonSlippingContactPoint, boolean useControllerDesiredCoP)
+ {
+ if (trustCoPAsNonSlippingContactPoint)
+ {
+ if (useControllerDesiredCoP)
+ centerOfPressureDataHolderFromController.getCenterOfPressure(tempCoP2d, foot);
+ else
+ footSwitch.getCenterOfPressure(tempCoP2d);
+
+ if (tempCoP2d.containsNaN())
+ {
+ tempCoP2d.setToZero(soleFrame);
+ }
+ else
+ {
+ boolean isCoPInsideFoot = footPolygon.isPointInside(tempCoP2d);
+ if (!isCoPInsideFoot)
+ {
+ if (footSwitch.getFootLoadPercentage() > 0.2)
+ {
+ footCenterCoPLineSegment.set(soleFrame, 0.0, 0.0, tempCoP2d.getX(), tempCoP2d.getY());
+ int intersections = footPolygon.intersectionWith(footCenterCoPLineSegment, intersectionPoints[0], intersectionPoints[1]);
+
+ if (intersections == 0)
+ {
+ LogTools.info("Found no solution for the CoP projection.");
+ tempCoP2d.setToZero(soleFrame);
+ }
+ else
+ {
+ tempCoP2d.set(intersectionPoints[0]);
+
+ if (intersections == 2)
+ LogTools.info("Found two solutions for the CoP projection.");
+ }
+ }
+ else // If foot barely loaded and actual CoP outside, then don't update the raw CoP right below
+ {
+ tempCoP2d.setIncludingFrame(copRawInFootFrame);
+ }
+ }
+ }
+
+
+ // Update the CoP values.
+ previousCoPInFootFrame.set(copFilteredInFootFrame);
+ copRawInFootFrame.set(tempCoP2d);
+ copFilteredInFootFrame.update();
+
+ // Update the CoP value from kinematics. This treats it as non-stationary.
+ copIKPositionInWorld.setMatchingFrame(soleFrame, copFilteredInFootFrame.getX(), copFilteredInFootFrame.getY(), 0.0);
+
+ // Update the change in the CoP from the previous tick.
+ tempCoPOffset.setIncludingFrame(soleFrame,
+ copFilteredInFootFrame.getX() - previousCoPInFootFrame.getX(),
+ copFilteredInFootFrame.getY() - previousCoPInFootFrame.getY(),
+ 0.0);
+ tempCoPOffset.changeFrame(worldFrame);
+ // Apply this same change to the CoP in world.
+ copFusedPositionInWorld.add(tempCoPOffset);
+ }
+ else
+ {
+ tempCoP2d.setToZero(soleFrame);
+ // Update the CoP value to match the center of the foot.
+ copIKPositionInWorld.setFromReferenceFrame(soleFrame);
+ copFusedPositionInWorld.setFromReferenceFrame(soleFrame);
+ }
+
+ // If we have an IMU and are using it, we should update the fused value.
+ if (useIMUData.getValue() && footIMU != null)
+ {
+ // Integrate the fused position using the velocity, which is from the kinematics and the IMU.
+ computeLinearVelocityAtPointInWorld(copFusedPositionInWorld, tempFrameVector);
+ copIMUPositionInWorld.scaleAdd(footAlphaLeakIMUOnly.getValue() * estimatorDT, tempFrameVector, copFusedPositionInWorld);
+
+ // Fuse the kinematic data with the IMU data. A higher break frequency equals a lower alpha value, trusting the kinematics more. If break frequency is
+ // zero, alpha equals one, and we trust the IMU completely.
+ double alpha = AlphaFilterTools.computeAlphaGivenBreakFrequencyProperly(imuAgainstKinematicsForPositionBreakFrequency.getValue(), estimatorDT);
+ copFusedPositionInWorld.interpolate(copIKPositionInWorld, copIMUPositionInWorld, alpha);
+ }
+
+ // Update the velocity of the foot at the CoP position
+ computeLinearVelocityAtPointInWorld(copFusedPositionInWorld, copVelocityInWorld);
+ }
+
+ private void correctFootPositionUsingCoP()
+ {
+ // Compute where the CoP position is in the world, according to the kinematics
+ tempCoPOffset.setIncludingFrame(copFilteredInFootFrame, 0.0);
+ tempCoPOffset.changeFrame(worldFrame);
+ // Update where the foot must be, according to this CoP location
+ footPositionInWorld.sub(copFusedPositionInWorld, tempCoPOffset);
+ }
+
+ private final FrameVector3D linearAcceleration = new FrameVector3D();
+ private final FrameVector3D angularVelocity = new FrameVector3D();
+
+ private void computeFootTwistInWorldFusingBaseStateAndFootIMU(TwistReadOnly rootBodyTwist)
+ {
+ tempRootBodyTwist.setIncludingFrame(rootBodyTwist);
+ tempRootBodyTwist.setBaseFrame(worldFrame);
+ tempRootBodyTwist.changeFrame(foot.getBodyFixedFrame());
+
+ foot.getBodyFixedFrame().getTwistRelativeToOther(rootJointFrame, footTwistInWorld);
+
+ tempFrameVector.setIncludingFrame(footTwistInWorld.getLinearPart());
+ tempFrameVector.changeFrame(worldFrame);
+ footToRootJointVelocity.update(tempFrameVector);
+
+ footTwistInWorld.add(tempRootBodyTwist);
+ footTwistInWorld.setBodyFrame(soleFrame);
+ footTwistInWorld.changeFrame(worldFrame);
+
+ if (useIMUData.getValue() && footIMU != null)
+ {
+ // Record the kinematic data
+ footIKLinearVelocityInWorld.set(footTwistInWorld.getLinearPart());
+ footIKAngularVelocityInWorld.set(footTwistInWorld.getAngularPart());
+
+ // Get the previous IMU twist estimate from the fused data set
+ imuTwist.setToZero(worldFrame);
+ imuTwist.getLinearPart().set(footFusedLinearVelocityInWorld);
+ imuTwist.getAngularPart().set(footFusedAngularVelocityInWorld);
+ imuTwist.changeFrame(footIMU.getMeasurementFrame());
+
+ // Compute the acceleration reading from the IMU
+ linearAcceleration.setIncludingFrame(footIMU.getMeasurementFrame(), footIMU.getLinearAccelerationMeasurement());
+ FrameVector3DReadOnly accelerationBias = imuBiasProvider.getLinearAccelerationBiasInIMUFrame(footIMU);
+ if (accelerationBias != null)
+ linearAcceleration.sub(accelerationBias);
+
+ // Update acceleration (minus gravity)
+ if (cancelGravityFromAccelerationMeasurement.getValue())
+ {
+ linearAcceleration.changeFrame(worldFrame);
+ linearAcceleration.add(gravityVector);
+ linearAcceleration.changeFrame(footIMU.getMeasurementFrame());
+ }
+
+ // Compute the gyro reading from the IMU
+ angularVelocity.setIncludingFrame(footIMU.getMeasurementFrame(), footIMU.getAngularVelocityMeasurement());
+ FrameVector3DReadOnly gyroBias = imuBiasProvider.getAngularVelocityBiasInIMUFrame(footIMU);
+ if (gyroBias != null)
+ angularVelocity.sub(gyroBias);
+
+ // Integrate the linear acceleration into the velocity measurement
+ imuTwist.getLinearPart().scaleAdd(estimatorDT * footAlphaLeakIMUOnly.getValue(), linearAcceleration, imuTwist.getLinearPart());
+ // Set the angular part from the gyro
+ imuTwist.getAngularPart().set(angularVelocity);
+
+ imuTwist.changeFrame(worldFrame);
+
+ footIMUAngularVelocityInWorld.set(imuTwist.getAngularPart());
+ footIMULinearVelocityInWorld.set(imuTwist.getLinearPart());
+
+ // Fuse the IMU and kinematics data for velocity
+ double alpha = AlphaFilterTools.computeAlphaGivenBreakFrequencyProperly(imuAgainstKinematicsForVelocityBreakFrequency.getValue(), estimatorDT);
+ footFusedAngularVelocityInWorld.interpolate(footIKAngularVelocityInWorld, footIMUAngularVelocityInWorld, alpha);
+ footFusedLinearVelocityInWorld.interpolate(footIKLinearVelocityInWorld, footIMULinearVelocityInWorld, alpha);
+ }
+ else
+ {
+ footIKLinearVelocityInWorld.setToNaN();
+ footIKAngularVelocityInWorld.setToNaN();
+ footIMULinearVelocityInWorld.setToNaN();
+ footIMUAngularVelocityInWorld.setToNaN();
+
+ footFusedLinearVelocityInWorld.set(footTwistInWorld.getLinearPart());
+ footFusedAngularVelocityInWorld.set(footTwistInWorld.getAngularPart());
+ }
+ }
+
+ private void computeLinearVelocityAtPointInWorld(FramePoint3DReadOnly point, FixedFrameVector3DBasics footLinearVelocityToPack)
+ {
+ footTwistInWorld.getLinearPart().set(footFusedLinearVelocityInWorld);
+ footTwistInWorld.getAngularPart().set(footFusedAngularVelocityInWorld);
+
+ footTwistInWorld.getLinearVelocityAt(point, footLinearVelocityToPack);
+ }
+}
diff --git a/ihmc-state-estimation/src/main/java/us/ihmc/stateEstimation/humanoid/kinematicsBasedStateEstimation/odomEKF/MeasurementModel.java b/ihmc-state-estimation/src/main/java/us/ihmc/stateEstimation/humanoid/kinematicsBasedStateEstimation/odomEKF/MeasurementModel.java
new file mode 100644
index 000000000000..bbbbdb11f499
--- /dev/null
+++ b/ihmc-state-estimation/src/main/java/us/ihmc/stateEstimation/humanoid/kinematicsBasedStateEstimation/odomEKF/MeasurementModel.java
@@ -0,0 +1,213 @@
+package us.ihmc.stateEstimation.humanoid.kinematicsBasedStateEstimation.odomEKF;
+
+import org.ejml.data.DMatrixRMaj;
+import org.ejml.dense.row.CommonOps_DDRM;
+import us.ihmc.euclid.referenceFrame.interfaces.FrameVector3DReadOnly;
+import us.ihmc.euclid.tools.QuaternionTools;
+import us.ihmc.euclid.tuple3D.Vector3D;
+import us.ihmc.euclid.tuple4D.Quaternion;
+import us.ihmc.matrixlib.MatrixTools;
+import us.ihmc.yoVariables.euclid.YoQuaternion;
+import us.ihmc.yoVariables.providers.DoubleProvider;
+import us.ihmc.yoVariables.registry.YoRegistry;
+import us.ihmc.yoVariables.variable.YoBoolean;
+import us.ihmc.yoVariables.variable.YoDouble;
+
+import static us.ihmc.stateEstimation.humanoid.kinematicsBasedStateEstimation.odomEKF.OdometryIndexHelper.*;
+
+class MeasurementModel
+{
+ private static final DMatrixRMaj eye3x3 = CommonOps_DDRM.identity(3);
+
+ private final StateVariables baseState;
+ private final StateVariables footState;
+ private final SensedVariables footSensing;
+ private final MeasurementVariables predictedMeasurements;
+ private final FrameVector3DReadOnly gravityVector;
+
+ // State variables
+ public final YoQuaternion footOrientationInBaseFrame;
+
+ public final YoDouble footVariance;
+ private final DoubleProvider contactThreshold;
+
+ // Temp variables
+ private final Quaternion footOrientationError = new Quaternion();
+ private final Vector3D positionError = new Vector3D();
+ private final Vector3D velocityError = new Vector3D();
+ private final Vector3D tempVector = new Vector3D();
+ private final DMatrixRMaj rotationMatrixTranspose = new DMatrixRMaj(3, 3);
+ private final DMatrixRMaj skewMatrix = new DMatrixRMaj(3, 3);
+ private final DMatrixRMaj left3x3 = new DMatrixRMaj(3, 3);
+ private final DMatrixRMaj left = new DMatrixRMaj(4, 4);
+ private final DMatrixRMaj right = new DMatrixRMaj(4, 4);
+ private final DMatrixRMaj lr = new DMatrixRMaj(4, 4);
+ private final Quaternion tempRotation = new Quaternion();
+
+ public MeasurementModel(String prefix,
+ StateVariables baseState,
+ StateVariables footState,
+ SensedVariables footSensing,
+ MeasurementVariables predictedMeasurement,
+ FrameVector3DReadOnly gravityVector,
+ DoubleProvider contactThreshold,
+ YoRegistry registry)
+ {
+ this.baseState = baseState;
+ this.footState = footState;
+ this.footSensing = footSensing;
+ this.predictedMeasurements = predictedMeasurement;
+ this.gravityVector = gravityVector;
+ this.contactThreshold = contactThreshold;
+
+ footOrientationInBaseFrame = new YoQuaternion(prefix + "OrientationInBaseFrame", registry);
+ footVariance = new YoDouble(prefix + "FootVariance", registry);
+ }
+
+ /**
+ * This is equation 35
+ */
+ public void update(DMatrixRMaj footCovariance)
+ {
+ footSensing.isInContact.set(computeIsFootInContact(footCovariance));
+
+ // First row. Update the predicted foot position of the foot relative to the base in the base frame
+ predictedMeasurements.relativePosition.sub(footState.translation, baseState.translation);
+ baseState.orientation.inverseTransform(predictedMeasurements.relativePosition);
+
+ // Update the predicted foot orientation of the foot relative to the base in the base frame
+ QuaternionTools.multiplyConjugateLeft(baseState.orientation, footState.orientation, footOrientationInBaseFrame);
+
+ // Compute the error between the predicted and measured foot orientation
+ QuaternionTools.multiplyConjugateLeft(footSensing.orientationMeasurement, footOrientationInBaseFrame, footOrientationError);
+ footOrientationError.getRotationVector(predictedMeasurements.relativeOrientationError);
+
+ // Third row. Compute the linear velocity error in the base frame
+ velocityError.sub(baseState.linearVelocity, footState.linearVelocity);
+ baseState.orientation.inverseTransform(velocityError);
+
+ predictedMeasurements.relativeLinearVelocityError.cross(baseState.unbiasedGyro, footSensing.positionMeasurement);
+ predictedMeasurements.relativeLinearVelocityError.scale(-1.0);
+ predictedMeasurements.relativeLinearVelocityError.sub(velocityError);
+ predictedMeasurements.relativeLinearVelocityError.add(footSensing.linearVelocity);
+
+ // Fourth row. Set the predicted linear velocity
+ predictedMeasurements.contactVelocity.set(footState.linearVelocity);
+
+ // Fifth row. Set the predicted gravity vector
+ footState.orientation.inverseTransform(gravityVector, predictedMeasurements.accelMeasure);
+ if (OdometryKalmanFilter.includeBias)
+ predictedMeasurements.accelMeasure.add(footState.accelBias);
+ }
+
+ private final DMatrixRMaj vector = new DMatrixRMaj(3, 1);
+ private final DMatrixRMaj tempDVector = new DMatrixRMaj(3, 1);
+ private final DMatrixRMaj tempScalar = new DMatrixRMaj(3, 1);
+
+ private boolean computeIsFootInContact(DMatrixRMaj footCovariance)
+ {
+ footState.linearVelocity.get(vector);
+
+ CommonOps_DDRM.mult(footCovariance, vector, tempDVector);
+ CommonOps_DDRM.multTransA(vector, tempDVector, tempScalar);
+
+ footVariance.set(Math.sqrt(tempScalar.get(0, 0)));
+
+ // tests the mahalonobis distance of the foot velocity being below a certain threshold.
+ return footVariance.getValue() < contactThreshold.getValue();
+ }
+
+ public boolean getIsFootInContact()
+ {
+ return footSensing.isInContact.getBooleanValue();
+ }
+
+ /**
+ * This is equation 41
+ */
+ public void computeBaseMeasurementJacobian(int rowOffset, DMatrixRMaj jacobianToPack)
+ {
+ OdometryTools.toRotationMatrix(baseState.orientation, rotationMatrixTranspose);
+ CommonOps_DDRM.transpose(rotationMatrixTranspose);
+
+ positionError.sub(footState.translation, baseState.translation);
+ velocityError.sub(footState.linearVelocity, baseState.linearVelocity);
+ baseState.orientation.inverseTransform(positionError);
+ baseState.orientation.inverseTransform(velocityError);
+
+ OdometryTools.toSkewSymmetricMatrix(positionError, skewMatrix);
+
+ // First row. Partial of relative foot position w.r.t. the base state
+ int row = rowOffset + measurementRelativeTranslationIndex;
+ MatrixTools.setMatrixBlock(jacobianToPack, row, errorTranslationIndex, rotationMatrixTranspose, 0, 0, 3, 3, -1.0);
+ CommonOps_DDRM.insert(skewMatrix, jacobianToPack, row, errorOrientationIndex);
+
+ // Second row. Partial of relative foot orientation error w.r.t. the base state
+ row = rowOffset + measurementRelativeOrientationErrorIndex;
+ QuaternionTools.multiplyConjugateLeft(footState.orientation, baseState.orientation, tempRotation);
+ OdometryTools.lOperator(tempRotation, left);
+ OdometryTools.rOperator(footSensing.orientationMeasurement, right);
+ CommonOps_DDRM.mult(left, right, lr);
+ MatrixTools.setMatrixBlock(jacobianToPack, row, errorOrientationIndex, lr, 1, 1, 3, 3, -1.0);
+
+ // Third row. Partial of relative foot velocity error w.r.t. the base state
+ row = rowOffset + measurementRelativeVelocityIndex;
+ OdometryTools.toSkewSymmetricMatrix(velocityError, skewMatrix);
+
+ MatrixTools.setMatrixBlock(jacobianToPack, row, errorLinearVelocityIndex, rotationMatrixTranspose, 0, 0, 3, 3, -1.0);
+ CommonOps_DDRM.insert(skewMatrix, jacobianToPack, row, errorOrientationIndex);
+
+ if (OdometryKalmanFilter.includeBias)
+ {
+ OdometryTools.toSkewSymmetricMatrix(footSensing.positionMeasurement, skewMatrix);
+ MatrixTools.setMatrixBlock(jacobianToPack, row, errorGyroBiasIndex, skewMatrix, 0, 0, 3, 3, -1.0);
+ }
+ }
+
+ /**
+ * This is equation 42
+ */
+ public void computeFootMeasurementJacobian(int rowOffset, int colOffset, DMatrixRMaj jacobianToPack)
+ {
+ OdometryTools.toRotationMatrix(baseState.orientation, rotationMatrixTranspose);
+ CommonOps_DDRM.transpose(rotationMatrixTranspose);
+
+ positionError.sub(footState.translation, baseState.translation);
+ velocityError.sub(footState.linearVelocity, baseState.linearVelocity);
+ baseState.orientation.inverseTransform(positionError);
+ baseState.orientation.inverseTransform(velocityError);
+
+ OdometryTools.toSkewSymmetricMatrix(positionError, skewMatrix);
+
+ // First row. Partial of relative contact position w.r.t. foot state
+ int row = rowOffset + measurementRelativeTranslationIndex;
+ CommonOps_DDRM.insert(rotationMatrixTranspose, jacobianToPack, row, colOffset + errorTranslationIndex);
+
+ // Second row. Partial of relative orientation error w.r.t. foot state
+ row = rowOffset + measurementRelativeOrientationErrorIndex;
+ tempRotation.set(footSensing.orientationMeasurement);
+ tempRotation.multiplyConjugateOther(baseState.orientation);
+ tempRotation.multiply(footState.orientation);
+ OdometryTools.l3Operator(tempRotation, left3x3);
+ CommonOps_DDRM.insert(left3x3, jacobianToPack, row, colOffset + errorOrientationIndex);
+
+ // Third row. Partial of relative velocity error w.r.t. foot state
+ row = rowOffset + measurementRelativeVelocityIndex;
+ CommonOps_DDRM.insert(rotationMatrixTranspose, jacobianToPack, row, colOffset + errorLinearVelocityIndex);
+
+ // Fourth row. Partial of contact velocity w.r.t. foot state
+ // The contact velocity is directly the foot velocity when it's in contact.
+ row = rowOffset + measurementContactVelocityIndex;
+ CommonOps_DDRM.insert(eye3x3, jacobianToPack, row, colOffset + errorLinearVelocityIndex);
+
+ // Fifth row. Partial of contact acceleration w.r.t. foot state
+ row = rowOffset + measurementAccelIndex;
+ tempVector.set(gravityVector);
+ footState.orientation.inverseTransform(tempVector);
+ OdometryTools.toSkewSymmetricMatrix(tempVector, skewMatrix);
+ CommonOps_DDRM.insert(skewMatrix, jacobianToPack, row, colOffset + errorOrientationIndex);
+
+ if (OdometryKalmanFilter.includeBias)
+ CommonOps_DDRM.insert(eye3x3, jacobianToPack, row, colOffset + errorAccelBiasIndex);
+ }
+}
diff --git a/ihmc-state-estimation/src/main/java/us/ihmc/stateEstimation/humanoid/kinematicsBasedStateEstimation/odomEKF/MeasurementResidualVariables.java b/ihmc-state-estimation/src/main/java/us/ihmc/stateEstimation/humanoid/kinematicsBasedStateEstimation/odomEKF/MeasurementResidualVariables.java
new file mode 100644
index 000000000000..debe6134a654
--- /dev/null
+++ b/ihmc-state-estimation/src/main/java/us/ihmc/stateEstimation/humanoid/kinematicsBasedStateEstimation/odomEKF/MeasurementResidualVariables.java
@@ -0,0 +1,35 @@
+package us.ihmc.stateEstimation.humanoid.kinematicsBasedStateEstimation.odomEKF;
+
+import org.ejml.data.DMatrixRMaj;
+import us.ihmc.yoVariables.euclid.YoVector3D;
+import us.ihmc.yoVariables.registry.YoRegistry;
+
+import static us.ihmc.stateEstimation.humanoid.kinematicsBasedStateEstimation.odomEKF.OdometryIndexHelper.*;
+
+class MeasurementResidualVariables
+{
+ // State variables
+ public final YoVector3D footPositionResidual;
+ public final YoVector3D footOrientationResidual;
+ public final YoVector3D linearVelocityResidual;
+ public final YoVector3D contactVelocityResidual;
+ public final YoVector3D contactAccelResidual;
+
+ public MeasurementResidualVariables(String prefix, YoRegistry registry)
+ {
+ footPositionResidual = new YoVector3D(prefix + "PositionResidual", registry);
+ footOrientationResidual = new YoVector3D(prefix + "OrientationResidual", registry);
+ linearVelocityResidual = new YoVector3D(prefix + "LinearVelocityResidual", registry);
+ contactVelocityResidual = new YoVector3D(prefix + "ContactVelocityResidual", registry);
+ contactAccelResidual = new YoVector3D(prefix + "ContactAccelResidual", registry);
+ }
+
+ public void set(int start, DMatrixRMaj residual)
+ {
+ footPositionResidual.set(start + measurementRelativeTranslationIndex, residual);
+ footOrientationResidual.set(start + measurementRelativeOrientationErrorIndex, residual);
+ linearVelocityResidual.set(start + measurementRelativeVelocityIndex, residual);
+ contactVelocityResidual.set(start + measurementContactVelocityIndex, residual);
+ contactAccelResidual.set(start + measurementAccelIndex, residual);
+ }
+}
diff --git a/ihmc-state-estimation/src/main/java/us/ihmc/stateEstimation/humanoid/kinematicsBasedStateEstimation/odomEKF/MeasurementVariables.java b/ihmc-state-estimation/src/main/java/us/ihmc/stateEstimation/humanoid/kinematicsBasedStateEstimation/odomEKF/MeasurementVariables.java
new file mode 100644
index 000000000000..d01c4de05a50
--- /dev/null
+++ b/ihmc-state-estimation/src/main/java/us/ihmc/stateEstimation/humanoid/kinematicsBasedStateEstimation/odomEKF/MeasurementVariables.java
@@ -0,0 +1,38 @@
+package us.ihmc.stateEstimation.humanoid.kinematicsBasedStateEstimation.odomEKF;
+
+import org.ejml.data.DMatrixRMaj;
+import us.ihmc.yoVariables.euclid.YoVector3D;
+import us.ihmc.yoVariables.registry.YoRegistry;
+import us.ihmc.yoVariables.variable.YoBoolean;
+
+import static us.ihmc.stateEstimation.humanoid.kinematicsBasedStateEstimation.odomEKF.OdometryIndexHelper.*;
+import static us.ihmc.stateEstimation.humanoid.kinematicsBasedStateEstimation.odomEKF.OdometryIndexHelper.measurementAccelIndex;
+import static us.ihmc.stateEstimation.humanoid.kinematicsBasedStateEstimation.odomEKF.OdometryIndexHelper.measurementContactVelocityIndex;
+
+class MeasurementVariables
+{
+ public final YoVector3D relativePosition;
+ public final YoVector3D relativeOrientationError;
+ public final YoVector3D relativeLinearVelocityError;
+ public final YoVector3D contactVelocity;
+ public final YoVector3D accelMeasure;
+
+
+ public MeasurementVariables(String prefix, YoRegistry registry)
+ {
+ relativePosition = new YoVector3D(prefix + "RelativePosition", registry);
+ relativeOrientationError = new YoVector3D(prefix + "RelativeOrientationError", registry);
+ relativeLinearVelocityError = new YoVector3D(prefix + "RelativeLinearVelocityError", registry);
+ contactVelocity = new YoVector3D(prefix + "ContactVelocity", registry);
+ accelMeasure = new YoVector3D(prefix + "AccelMeasure", registry);
+ }
+
+ public void get(int start, DMatrixRMaj measurementToPack)
+ {
+ relativePosition.get(start + measurementRelativeTranslationIndex, measurementToPack);
+ relativeOrientationError.get(start + measurementRelativeOrientationErrorIndex, measurementToPack);
+ relativeLinearVelocityError.get(start + measurementRelativeVelocityIndex, measurementToPack);
+ contactVelocity.get(start + measurementContactVelocityIndex, measurementToPack);
+ accelMeasure.get(start + measurementAccelIndex, measurementToPack);
+ }
+}
diff --git a/ihmc-state-estimation/src/main/java/us/ihmc/stateEstimation/humanoid/kinematicsBasedStateEstimation/odomEKF/ObservationModel.java b/ihmc-state-estimation/src/main/java/us/ihmc/stateEstimation/humanoid/kinematicsBasedStateEstimation/odomEKF/ObservationModel.java
new file mode 100644
index 000000000000..220f63eca37a
--- /dev/null
+++ b/ihmc-state-estimation/src/main/java/us/ihmc/stateEstimation/humanoid/kinematicsBasedStateEstimation/odomEKF/ObservationModel.java
@@ -0,0 +1,23 @@
+package us.ihmc.stateEstimation.humanoid.kinematicsBasedStateEstimation.odomEKF;
+
+import org.ejml.data.DMatrixRMaj;
+
+import static us.ihmc.stateEstimation.humanoid.kinematicsBasedStateEstimation.odomEKF.OdometryIndexHelper.measurementAccelIndex;
+import static us.ihmc.stateEstimation.humanoid.kinematicsBasedStateEstimation.odomEKF.OdometryIndexHelper.measurementRelativeTranslationIndex;
+
+public class ObservationModel
+{
+ // State providers
+ private final SensedVariables footMeasurements;
+
+ public ObservationModel(SensedVariables footMeasurements)
+ {
+ this.footMeasurements = footMeasurements;
+ }
+
+ public void get(int startRow, DMatrixRMaj observation)
+ {
+ footMeasurements.positionMeasurement.get(startRow + measurementRelativeTranslationIndex, observation);
+ footMeasurements.accelMeasurement.get(startRow + measurementAccelIndex, observation);
+ }
+}
diff --git a/ihmc-state-estimation/src/main/java/us/ihmc/stateEstimation/humanoid/kinematicsBasedStateEstimation/odomEKF/OdometryIndexHelper.java b/ihmc-state-estimation/src/main/java/us/ihmc/stateEstimation/humanoid/kinematicsBasedStateEstimation/odomEKF/OdometryIndexHelper.java
new file mode 100644
index 000000000000..ae1222f2e200
--- /dev/null
+++ b/ihmc-state-estimation/src/main/java/us/ihmc/stateEstimation/humanoid/kinematicsBasedStateEstimation/odomEKF/OdometryIndexHelper.java
@@ -0,0 +1,30 @@
+package us.ihmc.stateEstimation.humanoid.kinematicsBasedStateEstimation.odomEKF;
+
+public class OdometryIndexHelper
+{
+ static final int stateSizePerLink = 16;
+ static final int stateTranslationIndex = 0;
+ static final int stateLinearVelocityIndex = 3;
+ static final int stateOrientationIndex = 6;
+ static final int stateAccelBiasIndex = 10;
+ static final int stateGyroBiasIndex = 13;
+
+ static final int errorSizePerLink = 15;
+ static final int errorTranslationIndex = 0;
+ static final int errorLinearVelocityIndex = 3;
+ static final int errorOrientationIndex = 6;
+ static final int errorAccelBiasIndex = 9;
+ static final int errorGyroBiasIndex = 12;
+
+ static final int measurementSizePerLink = 15;
+ static final int measurementRelativeTranslationIndex = 0;
+ static final int measurementRelativeOrientationErrorIndex = 3;
+ static final int measurementRelativeVelocityIndex = 6;
+ static final int measurementContactVelocityIndex = 9;
+ static final int measurementAccelIndex = 12;
+
+ public static int getFootPositionIndex(int footNumber)
+ {
+ return stateSizePerLink * (footNumber + 1) + stateTranslationIndex;
+ }
+}
diff --git a/ihmc-state-estimation/src/main/java/us/ihmc/stateEstimation/humanoid/kinematicsBasedStateEstimation/odomEKF/OdometryKalmanFilter.java b/ihmc-state-estimation/src/main/java/us/ihmc/stateEstimation/humanoid/kinematicsBasedStateEstimation/odomEKF/OdometryKalmanFilter.java
new file mode 100644
index 000000000000..7f6c91825574
--- /dev/null
+++ b/ihmc-state-estimation/src/main/java/us/ihmc/stateEstimation/humanoid/kinematicsBasedStateEstimation/odomEKF/OdometryKalmanFilter.java
@@ -0,0 +1,498 @@
+package us.ihmc.stateEstimation.humanoid.kinematicsBasedStateEstimation.odomEKF;
+
+import org.ejml.data.DMatrixRMaj;
+import org.ejml.dense.row.CommonOps_DDRM;
+import us.ihmc.euclid.referenceFrame.FramePoint3D;
+import us.ihmc.euclid.referenceFrame.FramePose3D;
+import us.ihmc.euclid.referenceFrame.FrameVector3D;
+import us.ihmc.euclid.referenceFrame.ReferenceFrame;
+import us.ihmc.euclid.referenceFrame.interfaces.FixedFrameVector3DBasics;
+import us.ihmc.euclid.referenceFrame.interfaces.FrameVector3DReadOnly;
+import us.ihmc.euclid.referenceFrame.tools.EuclidFrameFactories;
+import us.ihmc.euclid.transform.interfaces.RigidBodyTransformReadOnly;
+import us.ihmc.euclid.tuple3D.Vector3D;
+import us.ihmc.euclid.tuple4D.Quaternion;
+import us.ihmc.matrixlib.MatrixTools;
+import us.ihmc.mecano.multiBodySystem.interfaces.FloatingJointBasics;
+import us.ihmc.mecano.multiBodySystem.interfaces.RigidBodyBasics;
+import us.ihmc.mecano.spatial.Twist;
+import us.ihmc.parameterEstimation.ExtendedKalmanFilter;
+import us.ihmc.sensorProcessing.stateEstimation.IMUSensorReadOnly;
+import us.ihmc.stateEstimation.humanoid.kinematicsBasedStateEstimation.IMUBiasProvider;
+import us.ihmc.yoVariables.euclid.referenceFrame.YoFramePoint3D;
+import us.ihmc.yoVariables.euclid.referenceFrame.YoFramePose3D;
+import us.ihmc.yoVariables.euclid.referenceFrame.YoFrameQuaternion;
+import us.ihmc.yoVariables.euclid.referenceFrame.YoFrameVector3D;
+import us.ihmc.yoVariables.providers.BooleanProvider;
+import us.ihmc.yoVariables.registry.YoRegistry;
+import us.ihmc.yoVariables.variable.YoDouble;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static us.ihmc.stateEstimation.humanoid.kinematicsBasedStateEstimation.odomEKF.OdometryIndexHelper.*;
+
+/**
+ * The process model in this class is defined as
+ */
+public class OdometryKalmanFilter extends ExtendedKalmanFilter
+{
+ static final boolean includeBias = true;
+ static final boolean usePredictedStateInJacobian = true;
+
+ // Constants and providers
+ private static final ReferenceFrame worldFrame = ReferenceFrame.getWorldFrame();
+
+ private final YoRegistry registry = new YoRegistry(getClass().getSimpleName());
+
+ private final IMUSensorReadOnly baseIMU;
+ private final List extends IMUSensorReadOnly> feetIMUs;
+ private final IMUBiasProvider imuBiasProvider;
+
+ private final FrameVector3DReadOnly gravityVector;
+
+ private final OdometryKalmanFilterParameters parameters = new OdometryKalmanFilterParameters(registry);
+
+ private final YoDouble contactThreshold = new YoDouble("contactVarianceThreshold", registry);
+
+ // Internal variables for state holding
+ private final YoFrameVector3D yoBaseAngularVelocityMeasurement;
+
+ private final YoFrameVector3D yoRootJointAngularVelocityMeasurementInWorld;
+ private final YoFrameVector3D yoRootJointAngularVelocityMeasurement;
+
+ // Outputs
+ private final DMatrixRMaj predictedState;
+ private final DMatrixRMaj predictedMeasurement;
+ private final DMatrixRMaj observationVector;
+
+ private final DMatrixRMaj AMatrix;
+ private final DMatrixRMaj CMatrix;
+
+ private final SensedVariables baseSensing;
+ private final List feetSensing = new ArrayList<>();
+
+ private final StateVariables baseProcessState;
+ private final StateVariables basePredictedState;
+ private final StateCorrectionVariables baseStateCorrection;
+ private final List feetProcessState = new ArrayList<>();
+ private final List feetPredictedState = new ArrayList<>();
+ private final List feetStateCorrections = new ArrayList<>();
+ private final List feetMeasurementResiduals = new ArrayList<>();
+ private final List feetMeasurements = new ArrayList<>();
+
+ private final List processModels = new ArrayList<>();
+ private final List measurementModels = new ArrayList<>();
+ private final List observationModels = new ArrayList<>();
+
+ private final YoFrameQuaternion filteredBaseOrientation;
+ private final YoFramePoint3D filteredBaseTranslation;
+ private final YoFrameVector3D filteredBaseLinearVelocity;
+
+ private final YoFramePose3D estimatedRootJointPose;
+ private final YoFrameVector3D estimatedRootJointLinearVelocity;
+ private final YoFrameVector3D estimatedRootJointAngularVelocity;
+
+ private final RigidBodyTransformReadOnly transformToRootJoint;
+
+ // Temporary variables
+ private final DMatrixRMaj footCovariance = new DMatrixRMaj(3, 3);
+ private final DMatrixRMaj processCovariance;
+ private final DMatrixRMaj measurementCovariance;
+
+ private static final DMatrixRMaj eye3x3 = CommonOps_DDRM.identity(3);
+
+ public OdometryKalmanFilter(IMUSensorReadOnly baseIMU,
+ List extends IMUSensorReadOnly> feetIMUs,
+ IMUBiasProvider baseImuBiasProvider,
+ BooleanProvider cancelGravityFromAccelerationMeasurement,
+ double estimatorDT,
+ double gravitationalAcceleration,
+ YoRegistry parentRegistry)
+ {
+ super(stateSizePerLink * (1 + feetIMUs.size()), errorSizePerLink * (1 + feetIMUs.size()) , measurementSizePerLink * feetIMUs.size());
+
+ // Initialize covariance
+ processCovariance = new DMatrixRMaj(getErrorSize(), getErrorSize());
+ measurementCovariance = new DMatrixRMaj(getMeasurementSize(), getMeasurementSize());
+ MatrixTools.setDiagonal(processCovariance, 1e-2);
+ MatrixTools.setDiagonal(measurementCovariance, 1e-3);
+
+ contactThreshold.set(0.5);
+
+ this.baseIMU = baseIMU;
+ this.feetIMUs = feetIMUs;
+ this.imuBiasProvider = baseImuBiasProvider;
+
+ predictedState = new DMatrixRMaj(getStateSize(), 1);
+ predictedMeasurement = new DMatrixRMaj(getMeasurementSize(), 1);
+ AMatrix = new DMatrixRMaj(getErrorSize(), getErrorSize());
+ CMatrix = new DMatrixRMaj(getMeasurementSize(), getErrorSize());
+ observationVector = new DMatrixRMaj(getMeasurementSize(), 1);
+
+ gravityVector = EuclidFrameFactories.newLinkedFrameVector3DReadOnly(() -> worldFrame, new Vector3D(0, 0, Math.abs(gravitationalAcceleration)));
+
+ baseSensing = new SensedVariables("base", baseIMU, baseIMU, imuBiasProvider, registry);
+ baseProcessState = new StateVariables("baseProcessState", baseIMU.getMeasurementFrame(), registry);
+ basePredictedState = new StateVariables("basePredicted", baseIMU.getMeasurementFrame(), registry);
+ baseStateCorrection = new StateCorrectionVariables("baseStateCorrection", baseIMU.getMeasurementFrame(), registry);
+ processModels.add(new ProcessModel(baseProcessState,
+ basePredictedState,
+ cancelGravityFromAccelerationMeasurement,
+ gravityVector,
+ estimatorDT));
+ for (int i = 0; i < feetIMUs.size(); i++)
+ {
+ SensedVariables footSensing = new SensedVariables("foot" + i, baseIMU, feetIMUs.get(i), imuBiasProvider, registry);
+ StateVariables footProcessState = new StateVariables("foot" + i + "ProcessState", feetIMUs.get(i).getMeasurementFrame(), registry);
+ StateVariables footPredictedState = new StateVariables("foot" + i + "PredictedState", feetIMUs.get(i).getMeasurementFrame(), registry);
+ StateCorrectionVariables footStateCorrection = new StateCorrectionVariables("foot" + i + "StateCorrection", feetIMUs.get(i).getMeasurementFrame(), registry);
+ MeasurementVariables footMeasurement = new MeasurementVariables("foot" + i + "Predicted", registry);
+ MeasurementResidualVariables footMeasurementResidual = new MeasurementResidualVariables("foot" + i + "Residual", registry);
+ feetSensing.add(footSensing);
+ feetMeasurements.add(footMeasurement);
+ feetProcessState.add(footProcessState);
+ feetPredictedState.add(footPredictedState);
+ feetStateCorrections.add(footStateCorrection);
+ feetMeasurementResiduals.add(footMeasurementResidual);
+
+ observationModels.add(new ObservationModel(footSensing));
+ processModels.add(new ProcessModel(footProcessState,
+ footPredictedState,
+ cancelGravityFromAccelerationMeasurement,
+ gravityVector,
+ estimatorDT));
+ measurementModels.add(new MeasurementModel("foot" + i,
+ basePredictedState,
+ footPredictedState,
+ footSensing,
+ footMeasurement,
+ gravityVector,
+ contactThreshold,
+ registry));
+ }
+
+ filteredBaseOrientation = new YoFrameQuaternion("filteredBaseOrientation", worldFrame, registry);
+ filteredBaseTranslation = new YoFramePoint3D("filteredBaseTranslation", worldFrame, registry);
+ filteredBaseLinearVelocity = new YoFrameVector3D("filteredBaseLinearVelocity", worldFrame, registry);
+
+ estimatedRootJointPose = new YoFramePose3D("estimatedRootJointPose", worldFrame, registry);
+ estimatedRootJointLinearVelocity = new YoFrameVector3D("estimatedRootJointLinearVelocity", worldFrame, registry);
+ estimatedRootJointAngularVelocity = new YoFrameVector3D("estimatedRootJointAngularVelocity", worldFrame, registry);
+
+ transformToRootJoint = baseIMU.getMeasurementFrame().getTransformToDesiredFrame(baseIMU.getMeasurementLink().getParentJoint().getFrameAfterJoint());
+
+ yoBaseAngularVelocityMeasurement = new YoFrameVector3D("baseImuLinearAccelerationInWorld", baseIMU.getMeasurementFrame(), registry);
+ yoRootJointAngularVelocityMeasurement = new YoFrameVector3D("rootJointImuLinearAcceleration", baseIMU.getMeasurementFrame(), registry);
+ yoRootJointAngularVelocityMeasurementInWorld = new YoFrameVector3D("rootJointImuLinearAccelerationInWorld", worldFrame, registry);
+
+ parentRegistry.addChild(registry);
+ }
+
+ private void initialize()
+ {
+ baseProcessState.initialize();
+ for (int i = 0; i < feetProcessState.size(); i++)
+ feetProcessState.get(i).initialize();
+
+ // update the predicted state from the yo variablized state
+ baseProcessState.get(stateTranslationIndex, state);
+ for (int i = 0; i < feetIMUs.size(); i++)
+ {
+ feetProcessState.get(i).get(OdometryIndexHelper.getFootPositionIndex(i), state);
+ }
+
+ initializeState(state);
+ }
+
+ private boolean initialized = false;
+
+ public void compute()
+ {
+ updateCovariance();
+
+ baseSensing.update();
+ for (int i = 0; i < feetSensing.size(); i++)
+ feetSensing.get(i).update();
+
+ for (int i = 0; i < observationModels.size(); i++)
+ {
+ observationModels.get(i).get(i * measurementSizePerLink, observationVector);
+ }
+
+ if (!initialized)
+ {
+ initialize();
+ initialized = true;
+ }
+
+ updateRootJointTwistAngularPart(); // TODO maybe do this after we update everything based on the estimate?
+
+ DMatrixRMaj stateEstimate = calculateEstimate(observationVector);
+
+ filteredBaseTranslation.set(stateEstimate);
+ filteredBaseLinearVelocity.set(3, stateEstimate);
+ filteredBaseOrientation.set(6, stateEstimate);
+
+ // Transform the pose of the IMU in the world frame to the pose of the root joint in the world frame
+ tempPose.set(filteredBaseOrientation, filteredBaseTranslation);
+ tempPose.appendTransform(transformToRootJoint);
+ estimatedRootJointPose.set(tempPose);
+ estimatedRootJointAngularVelocity.set(yoRootJointAngularVelocityMeasurementInWorld);
+
+ updateRootJointTwistLinearPart(filteredBaseLinearVelocity, estimatedRootJointAngularVelocity, estimatedRootJointLinearVelocity);
+ }
+
+ private void updateCovariance()
+ {
+ OdometryTools.setDiagonals(errorTranslationIndex, 3, parameters.baseProcessTranslationNoise.getValue(), processCovariance);
+ OdometryTools.setDiagonals(errorLinearVelocityIndex, 2, parameters.baseProcessVelocityXYNoise.getValue(), processCovariance);
+ OdometryTools.setDiagonals(errorLinearVelocityIndex + 2, 1, parameters.baseProcessVelocityZNoise.getValue(), processCovariance);
+ OdometryTools.setDiagonals(errorOrientationIndex, 3, parameters.baseProcessOrientationNoise.getValue(), processCovariance);
+ OdometryTools.setDiagonals(errorAccelBiasIndex, 3, parameters.baseProcessAccelBiasNoise.getValue(), processCovariance);
+ OdometryTools.setDiagonals(errorGyroBiasIndex, 3, parameters.baseProcessGyroBiasNoise.getValue(), processCovariance);
+
+ for (int i = 0; i < feetIMUs.size(); i++)
+ {
+ int index = (i + 1) * errorSizePerLink;
+ OdometryTools.setDiagonals(index + errorTranslationIndex, 3, parameters.footProcessTranslationNoise.getValue(), processCovariance);
+ OdometryTools.setDiagonals(index + errorLinearVelocityIndex, 3, parameters.footProcessVelocityNoise.getValue(), processCovariance);
+ OdometryTools.setDiagonals(index + errorOrientationIndex, 3, parameters.footProcessOrientationNoise.getValue(), processCovariance);
+ OdometryTools.setDiagonals(index + errorAccelBiasIndex, 3, parameters.footProcessAccelBiasNoise.getValue(), processCovariance);
+ OdometryTools.setDiagonals(index + errorGyroBiasIndex, 3, parameters.footProcessGyroBiasNoise.getValue(), processCovariance);
+ }
+
+ for (int i = 0; i < feetIMUs.size(); i++)
+ {
+ int index = i * measurementSizePerLink;
+ OdometryTools.setDiagonals(index + measurementRelativeTranslationIndex, 3, parameters.measurementIKPositionNoise.getValue(), measurementCovariance);
+ OdometryTools.setDiagonals(index + measurementRelativeOrientationErrorIndex, 3, parameters.measurementIKOrientationNoise.getValue(), measurementCovariance);
+ OdometryTools.setDiagonals(index + measurementRelativeVelocityIndex, 3, parameters.measurementIKVelocityNoise.getValue(), measurementCovariance);
+ OdometryTools.setDiagonals(index + measurementContactVelocityIndex, 3, parameters.measurementContactVelocityNoise.getValue(), measurementCovariance);
+ OdometryTools.setDiagonals(index + measurementAccelIndex, 3, parameters.measurementContactAccelNoise.getValue(), measurementCovariance);
+ }
+
+ setProcessCovariance(processCovariance);
+ setMeasurementCovariance(measurementCovariance);
+ }
+
+ private final FramePose3D tempPose = new FramePose3D();
+
+ private final Vector3D angularVelocityMeasurement = new Vector3D();
+
+ /** Angular velocity of the measurement link, with respect to world. */
+ private final FrameVector3D angularVelocityMeasurementLinkRelativeToWorld = new FrameVector3D();
+
+ /** Angular velocity of the estimation link, with respect to the measurement link. */
+ private final FrameVector3D angularVelocityRootJointFrameRelativeToMeasurementLink = new FrameVector3D();
+
+ /** Twist of the estimation link, with respect to the measurement link. */
+ private final Twist twistRootJointFrameRelativeToMeasurementLink = new Twist();
+
+ private void updateRootJointTwistAngularPart()
+ {
+ RigidBodyBasics measurementLink = baseIMU.getMeasurementLink();
+ FloatingJointBasics rootJoint = (FloatingJointBasics) baseIMU.getMeasurementLink().getParentJoint();
+ ReferenceFrame rootJointFrame = rootJoint.getFrameAfterJoint();
+ // T_{rootBody}^{rootBody, measurementLink}
+ rootJoint.getSuccessor().getBodyFixedFrame().getTwistRelativeToOther(measurementLink.getBodyFixedFrame(), twistRootJointFrameRelativeToMeasurementLink);
+ // T_{rootBody}^{rootJointFrame, measurementLink}
+ twistRootJointFrameRelativeToMeasurementLink.changeFrame(rootJointFrame);
+ // T_{rootJointFrame}^{rootJointFrame, measurementLink}
+ twistRootJointFrameRelativeToMeasurementLink.setBodyFrame(rootJointFrame);
+
+ // omega_{rootJointFrame}^{rootJointFrame, measurementLink}
+ angularVelocityRootJointFrameRelativeToMeasurementLink.setIncludingFrame(twistRootJointFrameRelativeToMeasurementLink.getAngularPart());
+
+ // omega_{measurementLink}^{measurementFrame, world}
+ angularVelocityMeasurement.set(baseIMU.getAngularVelocityMeasurement());
+ if (imuBiasProvider != null)
+ {
+ FrameVector3DReadOnly angularVelocityBiasInIMUFrame = imuBiasProvider.getAngularVelocityBiasInIMUFrame(baseIMU);
+ if (angularVelocityBiasInIMUFrame != null)
+ angularVelocityMeasurement.sub(angularVelocityBiasInIMUFrame);
+ }
+ angularVelocityMeasurementLinkRelativeToWorld.setIncludingFrame(baseIMU.getMeasurementFrame(), angularVelocityMeasurement);
+
+ // omega_{measurementLink}^{rootJointFrame, world}
+ angularVelocityMeasurementLinkRelativeToWorld.changeFrame(rootJointFrame);
+
+ // omega_{rootJointFrame}^{rootJointFrame, world} = omega_{rootJointFrame}^{rootJointFrame, measurementLink} + omega_{measurementLink}^{rootJointFrame, world}
+ rootJoint.getJointTwist().getAngularPart().add(angularVelocityRootJointFrameRelativeToMeasurementLink, angularVelocityMeasurementLinkRelativeToWorld);
+ rootJoint.updateFrame();
+
+ yoRootJointAngularVelocityMeasurement.setMatchingFrame(rootJoint.getJointTwist().getAngularPart());
+ yoRootJointAngularVelocityMeasurementInWorld.setMatchingFrame(rootJoint.getJointTwist().getAngularPart());
+ yoBaseAngularVelocityMeasurement.setMatchingFrame(angularVelocityMeasurementLinkRelativeToWorld);
+ }
+
+ private final FramePoint3D imuToRoot = new FramePoint3D();
+
+ private final FrameVector3D addedAngular = new FrameVector3D();
+ private final FrameVector3D momentArm = new FrameVector3D();
+ private void updateRootJointTwistLinearPart(FrameVector3DReadOnly imuLinearVelocity,
+ FrameVector3DReadOnly imuAngularVelocity,
+ FixedFrameVector3DBasics rootLinearVelocityToPack)
+ {
+ FloatingJointBasics rootJoint = (FloatingJointBasics) baseIMU.getMeasurementLink().getParentJoint();
+ ReferenceFrame rootJointFrame = rootJoint.getFrameAfterJoint();
+
+ imuToRoot.setToZero(baseIMU.getMeasurementFrame());
+ imuToRoot.changeFrame(rootJointFrame);
+
+ momentArm.setIncludingFrame(imuToRoot);
+ momentArm.changeFrame(worldFrame);
+
+ addedAngular.cross(momentArm, imuAngularVelocity);
+
+ rootLinearVelocityToPack.set(imuLinearVelocity);
+ rootLinearVelocityToPack.add(addedAngular);
+ }
+
+ @Override
+ protected void postSolveHook()
+ {
+ // Update some yo variables
+ baseStateCorrection.set(0, stateCorrection);
+ for (int i = 0; i < feetIMUs.size(); i++)
+ {
+ feetStateCorrections.get(i).set((i + 1) * errorSizePerLink, stateCorrection);
+ feetMeasurementResiduals.get(i).set(i * measurementSizePerLink, measurementResidual);
+ }
+ }
+
+ @Override
+ protected void applyStateCorrection(DMatrixRMaj state, DMatrixRMaj correction, DMatrixRMaj correctedState)
+ {
+ correctedState.set(state);
+ for (int i = 0; i < feetIMUs.size() + 1; i++)
+ {
+ int destStart = stateSizePerLink * i;
+ int sourceStart = errorSizePerLink * i;
+ // Update the translation
+ MatrixTools.addMatrixBlock(correctedState, destStart + stateTranslationIndex, 0, correction, sourceStart + errorTranslationIndex, 0, 3, 1, 1.0);
+ // Update the linear velocity
+ MatrixTools.addMatrixBlock(correctedState, destStart + stateLinearVelocityIndex, 0, correction, sourceStart + errorLinearVelocityIndex, 0, 3, 1, 1.0);
+ // Update the orientation
+ // FIXME garbage
+ Vector3D rotationVector = new Vector3D();
+ rotationVector.set(sourceStart + errorOrientationIndex, correction);
+ Quaternion correctionRotation = new Quaternion(rotationVector);
+ Quaternion rotation = new Quaternion();
+ rotation.set(destStart + stateOrientationIndex, state);
+ rotation.multiply(correctionRotation);
+ rotation.get(destStart + stateOrientationIndex, correctedState);
+ // Update the accel bias
+ MatrixTools.addMatrixBlock(correctedState, destStart + stateAccelBiasIndex, 0, correction, sourceStart + errorAccelBiasIndex, 0, 3, 1, 1.0);
+ // Update the gyro bias
+ MatrixTools.addMatrixBlock(correctedState, destStart + stateGyroBiasIndex, 0, correction, sourceStart + errorGyroBiasIndex, 0, 3, 1, 1.0);
+ }
+ }
+
+ @Override
+ public DMatrixRMaj processModel(DMatrixRMaj state)
+ {
+ predictedState.zero();
+
+ // update the yo variables representing the state
+ baseProcessState.set(stateTranslationIndex, state, baseSensing);
+ for (int i = 0; i < feetIMUs.size(); i++)
+ {
+ feetProcessState.get(i).set(OdometryIndexHelper.getFootPositionIndex(i), state, feetSensing.get(i));
+ }
+
+ // do the prediction
+ for (int i = 0; i < processModels.size(); i++)
+ processModels.get(i).update();
+
+ // update the predicted state from the yo variablized state
+ basePredictedState.get(stateTranslationIndex, predictedState);
+ for (int i = 0; i < feetIMUs.size(); i++)
+ {
+ feetPredictedState.get(i).get(OdometryIndexHelper.getFootPositionIndex(i), predictedState);
+ }
+
+ // return the predicted state
+ return predictedState;
+ }
+
+ @Override
+ public DMatrixRMaj measurementModel(DMatrixRMaj predictedState)
+ {
+ predictedMeasurement.zero();
+
+ // Note that this bypasses the use of predicted state vector, as the predicted state variables are computed in the {@link #processModel(DMatrixRMaj)} call
+ for (int i = 0; i < feetIMUs.size(); i++)
+ {
+ int row = errorSizePerLink * (i + 1) + measurementContactVelocityIndex;
+ MatrixTools.setMatrixBlock(footCovariance, 0, 0, getCovariance(), row, row, 3, 3, 1.0);
+
+ measurementModels.get(i).update(footCovariance);
+ feetMeasurements.get(i).get(i * measurementSizePerLink, predictedMeasurement);
+ }
+
+ // return the predicted measurement
+ return predictedMeasurement;
+ }
+
+ @Override
+ protected DMatrixRMaj linearizeProcessModel(DMatrixRMaj previousState)
+ {
+ // make sure to call zero, as the compute process model stuff has a bunch of add operators.
+ AMatrix.zero();
+ int offset = 0;
+
+ for (int i = 0; i < feetIMUs.size() + 1; i++)
+ {
+ processModels.get(i).computeProcessJacobian(offset, AMatrix);
+ offset += errorSizePerLink;
+ }
+
+ return AMatrix;
+ }
+
+ @Override
+ protected DMatrixRMaj linearizeMeasurementModel(DMatrixRMaj predictedState)
+ {
+ // make sure to call zero, as the compute process model stuff has a bunch of add operators.
+ CMatrix.zero();
+ int rowOffset = 0;
+ int colOffset = errorSizePerLink;
+ for (int i = 0; i < feetIMUs.size(); i++)
+ {
+ MeasurementModel measurementModel = measurementModels.get(i);
+ measurementModel.computeBaseMeasurementJacobian(rowOffset, CMatrix);
+ measurementModel.computeFootMeasurementJacobian(rowOffset, colOffset, CMatrix);
+ rowOffset += measurementSizePerLink;
+ colOffset += errorSizePerLink;
+ }
+
+ return CMatrix;
+ }
+
+ @Override
+ protected void maskResidualCovarianceForOutlierRejection()
+ {
+ // FIXME garbage
+ DMatrixRMaj mask = CommonOps_DDRM.identity(getMeasurementSize(), getMeasurementSize());
+ DMatrixRMaj maskTransposeS = new DMatrixRMaj(0, 0);
+
+ for (int i = feetIMUs.size() - 1; i >= 0; i--)
+ {
+ if (!measurementModels.get(i).getIsFootInContact())
+ {
+ int index = i * measurementSizePerLink;
+ // remove the rows of the measurement accel, since it's larger
+ CommonOps_DDRM.removeColumns(mask, index + measurementAccelIndex, index + measurementAccelIndex + 2);
+ CommonOps_DDRM.removeColumns(mask, index + measurementContactVelocityIndex, index + measurementContactVelocityIndex + 2);
+ }
+ }
+
+ CommonOps_DDRM.multTransA(mask, measurementResidual, maskedMeasurementResidual);
+ CommonOps_DDRM.multTransA(mask, measurementJacobian, maskedMeasurementJacobian);
+ CommonOps_DDRM.multTransA(mask, residualCovariance, maskTransposeS);
+ CommonOps_DDRM.mult(maskTransposeS, mask, maskedResidualCovariance);
+ CommonOps_DDRM.multTransA(mask, measurementCovariance, maskTransposeS);
+ CommonOps_DDRM.mult(maskTransposeS, mask, maskedMeasurementCovariance);
+ }
+}
diff --git a/ihmc-state-estimation/src/main/java/us/ihmc/stateEstimation/humanoid/kinematicsBasedStateEstimation/odomEKF/OdometryKalmanFilterParameters.java b/ihmc-state-estimation/src/main/java/us/ihmc/stateEstimation/humanoid/kinematicsBasedStateEstimation/odomEKF/OdometryKalmanFilterParameters.java
new file mode 100644
index 000000000000..2e959e3a35d0
--- /dev/null
+++ b/ihmc-state-estimation/src/main/java/us/ihmc/stateEstimation/humanoid/kinematicsBasedStateEstimation/odomEKF/OdometryKalmanFilterParameters.java
@@ -0,0 +1,53 @@
+package us.ihmc.stateEstimation.humanoid.kinematicsBasedStateEstimation.odomEKF;
+
+import us.ihmc.yoVariables.registry.YoRegistry;
+import us.ihmc.yoVariables.variable.YoDouble;
+
+public class OdometryKalmanFilterParameters
+{
+ private final YoRegistry registry = new YoRegistry(getClass().getSimpleName());
+
+ public final YoDouble baseProcessTranslationNoise = new YoDouble("baseProcessTranslationNoise", registry);
+ public final YoDouble baseProcessVelocityXYNoise = new YoDouble("baseProcessVelocityXYNoise", registry);
+ public final YoDouble baseProcessVelocityZNoise = new YoDouble("baseProcessVelocityZNoise", registry);
+ public final YoDouble baseProcessOrientationNoise = new YoDouble("baseProcessOrientationNoise", registry);
+ public final YoDouble baseProcessGyroBiasNoise = new YoDouble("baseProcessGyroBiasNoise", registry);
+ public final YoDouble baseProcessAccelBiasNoise = new YoDouble("baseProcessAccelBiasNoise", registry);
+
+ public final YoDouble footProcessTranslationNoise = new YoDouble("footProcessTranslationNoise", registry);
+ public final YoDouble footProcessVelocityNoise = new YoDouble("footProcessVelocityNoise", registry);
+ public final YoDouble footProcessOrientationNoise = new YoDouble("footProcessOrientationNoise", registry);
+ public final YoDouble footProcessAccelBiasNoise = new YoDouble("footProcessAccelBiasNoise", registry);
+ public final YoDouble footProcessGyroBiasNoise = new YoDouble("footProcessGyroBiasNoise", registry);
+
+
+ public final YoDouble measurementIKPositionNoise = new YoDouble("measurementIKPositionNoise", registry);
+ public final YoDouble measurementIKVelocityNoise = new YoDouble("measurementIKVelocityNoise", registry);
+ public final YoDouble measurementIKOrientationNoise = new YoDouble("measurementIKOrientationNoise", registry);
+ public final YoDouble measurementContactVelocityNoise = new YoDouble("measurementContactVelocityNoise", registry);
+ public final YoDouble measurementContactAccelNoise = new YoDouble("measurementContactAccelNoise", registry);
+
+ public OdometryKalmanFilterParameters(YoRegistry parentRegistry)
+ {
+ baseProcessTranslationNoise.set(0.0005);
+ baseProcessVelocityXYNoise.set(0.005);
+ baseProcessVelocityZNoise.set(0.005);
+ baseProcessOrientationNoise.set(1e-7);
+ baseProcessGyroBiasNoise.set(1e-5);
+ baseProcessAccelBiasNoise.set(1e-4);
+
+ footProcessTranslationNoise.set(1e-4);
+ footProcessVelocityNoise.set(5.0);
+ footProcessOrientationNoise.set(1e-5); // made up.
+ footProcessGyroBiasNoise.set(1e-5); // made up
+ footProcessAccelBiasNoise.set(1e-4);
+
+ measurementIKPositionNoise.set(0.001);
+ measurementIKOrientationNoise.set(1e-8);
+ measurementIKVelocityNoise.set(0.05);
+ measurementContactVelocityNoise.set(0.01);
+ measurementContactAccelNoise.set(1.0);
+
+ parentRegistry.addChild(registry);
+ }
+}
diff --git a/ihmc-state-estimation/src/main/java/us/ihmc/stateEstimation/humanoid/kinematicsBasedStateEstimation/odomEKF/OdometryTools.java b/ihmc-state-estimation/src/main/java/us/ihmc/stateEstimation/humanoid/kinematicsBasedStateEstimation/odomEKF/OdometryTools.java
new file mode 100644
index 000000000000..93c8f3fa3f14
--- /dev/null
+++ b/ihmc-state-estimation/src/main/java/us/ihmc/stateEstimation/humanoid/kinematicsBasedStateEstimation/odomEKF/OdometryTools.java
@@ -0,0 +1,278 @@
+package us.ihmc.stateEstimation.humanoid.kinematicsBasedStateEstimation.odomEKF;
+
+import org.ejml.data.DMatrixRMaj;
+import org.ejml.dense.row.CommonOps_DDRM;
+import us.ihmc.euclid.tools.EuclidCoreTools;
+import us.ihmc.euclid.tuple3D.interfaces.Tuple3DReadOnly;
+import us.ihmc.euclid.tuple3D.interfaces.Vector3DBasics;
+import us.ihmc.euclid.tuple3D.interfaces.Vector3DReadOnly;
+import us.ihmc.euclid.tuple4D.interfaces.QuaternionBasics;
+import us.ihmc.euclid.tuple4D.interfaces.QuaternionReadOnly;
+import us.ihmc.matrixlib.MatrixTools;
+
+public class OdometryTools
+{
+ private static final DMatrixRMaj eye3x3 = CommonOps_DDRM.identity(3);
+
+ static void toRotationMatrixInverse(double qx, double qy, double qz, double qs, DMatrixRMaj rotationMatrix)
+ {
+ double qx2 = qx * qx;
+ double qy2 = qy * qy;
+ double qz2 = qz * qz;
+ double qs2 = qs * qs;
+
+ rotationMatrix.set(0, 0, qx2 - qy2 - qz2 + qs2);
+ rotationMatrix.set(1, 1, -qx2 + qy2 - qz2 + qs2);
+ rotationMatrix.set(2, 2, -qx2 - qy2 + qz2 + qs2);
+
+ double xy = qx * qy;
+ double xz = qx * qz;
+ double yz = qy * qz;
+ double xw = qx * qs;
+ double yw = qy * qs;
+ double zw = qz * qs;
+
+ rotationMatrix.set(1, 0, 2.0 * (xy - zw));
+ rotationMatrix.set(0, 1, 2.0 * (xy + zw));
+
+ rotationMatrix.set(2, 0, 2.0 * (xz + yw));
+ rotationMatrix.set(0, 2, 2.0 * (xz - yw));
+
+ rotationMatrix.set(2, 1, 2.0 * (yz - xw));
+ rotationMatrix.set(1, 2, 2.0 * (yz + xw));
+ }
+
+ static void toRotationMatrixInverse(QuaternionReadOnly quaternion, DMatrixRMaj rotationMatrix)
+ {
+ toRotationMatrixInverse(quaternion.getX(), quaternion.getY(), quaternion.getZ(), quaternion.getS(), rotationMatrix);
+ }
+
+ static void toSkewSymmetricMatrix(double x, double y, double z, DMatrixRMaj matrixToPack)
+ {
+ matrixToPack.zero();
+ matrixToPack.set(0, 1, -z);
+ matrixToPack.set(0, 2, y);
+ matrixToPack.set(1, 0, z);
+ matrixToPack.set(1, 2, -x);
+ matrixToPack.set(2, 0, -y);
+ matrixToPack.set(2, 1, x);
+ }
+
+ static void toSkewSymmetricMatrix(double x, double y, double z, int row, int col, DMatrixRMaj matrixToPack)
+ {
+ matrixToPack.zero();
+ matrixToPack.set(row, col + 1, -z);
+ matrixToPack.set(row, col + 2, y);
+ matrixToPack.set(row + 1, col, z);
+ matrixToPack.set(row + 1, col + 2, -x);
+ matrixToPack.set(row + 2, col, -y);
+ matrixToPack.set(row + 2, col + 1, x);
+ }
+
+ static void toSkewSymmetricMatrix(Tuple3DReadOnly vector, DMatrixRMaj matrixToPack)
+ {
+ toSkewSymmetricMatrix(vector.getX(), vector.getY(), vector.getZ(), matrixToPack);
+ }
+
+ static void toRotationMatrix(double qx, double qy, double qz, double qs, DMatrixRMaj rotationMatrix)
+ {
+ double qx2 = qx * qx;
+ double qy2 = qy * qy;
+ double qz2 = qz * qz;
+ double qs2 = qs * qs;
+
+ rotationMatrix.set(0, 0, qx2 - qy2 - qz2 + qs2);
+ rotationMatrix.set(1, 1, -qx2 + qy2 - qz2 + qs2);
+ rotationMatrix.set(2, 2, -qx2 - qy2 + qz2 + qs2);
+
+ double xy = qx * qy;
+ double xz = qx * qz;
+ double yz = qy * qz;
+ double xw = qx * qs;
+ double yw = qy * qs;
+ double zw = qz * qs;
+
+ rotationMatrix.set(0, 1, 2.0 * (xy - zw));
+ rotationMatrix.set(1, 0, 2.0 * (xy + zw));
+
+ rotationMatrix.set(0, 2, 2.0 * (xz + yw));
+ rotationMatrix.set(2, 0, 2.0 * (xz - yw));
+
+ rotationMatrix.set(1, 2, 2.0 * (yz - xw));
+ rotationMatrix.set(2, 1, 2.0 * (yz + xw));
+ }
+
+ static void toRotationMatrix(QuaternionReadOnly quaternion, DMatrixRMaj rotationMatrix)
+ {
+ toRotationMatrix(quaternion.getX(), quaternion.getY(), quaternion.getZ(), quaternion.getS(), rotationMatrix);
+ }
+
+ static void setDiagonals(int start, int elements, double value, DMatrixRMaj matrix)
+ {
+ for (int i = start; i < start + elements; i++)
+ matrix.set(i, i, value);
+ }
+
+ public static void toQuaternionFromRotationVectorSmallAngle(Vector3DReadOnly rotation, QuaternionBasics quaternionToPack)
+ {
+ double magnitude = rotation.norm();
+ double s = magnitude * 0.5;
+ double qs = 1.0;
+ double qx = s * rotation.getX() / magnitude;
+ double qy = s * rotation.getY() / magnitude;
+ double qz = s * rotation.getZ() / magnitude;
+ quaternionToPack.set(qx, qy, qz, qs);
+ }
+
+ public static void logMap(QuaternionReadOnly quaternion, Vector3DBasics rotationToPack)
+ {
+ // TODO is this just a transform to the axis-angle representation?
+ double norm = EuclidCoreTools.norm(quaternion.getX(), quaternion.getY(), quaternion.getZ());
+ double scale = 2.0 * Math.atan2(norm, quaternion.getS()) / norm;
+ rotationToPack.set(quaternion.getX(), quaternion.getY(), quaternion.getZ());
+ rotationToPack.scale(scale);
+ }
+
+ public static void logMapSmallAngle(QuaternionReadOnly quaternion, Vector3DBasics rotationToPack)
+ {
+ rotationToPack.set(quaternion.getX(), quaternion.getY(), quaternion.getZ());
+ rotationToPack.scale(2.0);
+ }
+
+ static DMatrixRMaj lOperator(DMatrixRMaj quaternion)
+ {
+ return lOperator(quaternion.get(0), quaternion.get(1), quaternion.get(2), quaternion.get(3));
+ }
+
+ static DMatrixRMaj lOperator(QuaternionReadOnly quaternion)
+ {
+ return lOperator(quaternion.getS(), quaternion.getX(), quaternion.getY(), quaternion.getZ());
+ }
+
+ static DMatrixRMaj lOperator(double qs, double qx, double qy, double qz)
+ {
+ DMatrixRMaj lOperator = new DMatrixRMaj(4, 4);
+ lOperator(qs, qx, qy, qz, lOperator);
+ return lOperator;
+ }
+
+ static void lOperator(QuaternionReadOnly quaternion, DMatrixRMaj lOperatorToPack)
+ {
+ lOperator(quaternion.getS(), quaternion.getX(), quaternion.getY(), quaternion.getZ(), lOperatorToPack);
+ }
+
+ static void lOperator(double qs, double qx, double qy, double qz, DMatrixRMaj lOperatorToPack)
+ {
+ toSkewSymmetricMatrix(qx, qy, qz, 1, 1, lOperatorToPack);
+ for (int i = 1; i < 4; i++)
+ lOperatorToPack.add(i, i, qs);
+ lOperatorToPack.set(0, 0, qs);
+ lOperatorToPack.set(0, 1, -qx);
+ lOperatorToPack.set(0, 2, -qy);
+ lOperatorToPack.set(0, 3, -qz);
+ lOperatorToPack.set(1, 0, qx);
+ lOperatorToPack.set(2, 0, qy);
+ lOperatorToPack.set(3, 0, qz);
+ }
+
+ static DMatrixRMaj rOperator(DMatrixRMaj quaternion)
+ {
+ return rOperator(quaternion.get(0), quaternion.get(1), quaternion.get(2), quaternion.get(3));
+ }
+
+ static DMatrixRMaj rOperator(QuaternionReadOnly quaternion)
+ {
+ return rOperator(quaternion.getS(), quaternion.getX(), quaternion.getY(), quaternion.getZ());
+ }
+
+ static DMatrixRMaj rOperator(double qs, double qx, double qy, double qz)
+ {
+ DMatrixRMaj rOperator = new DMatrixRMaj(4, 4);
+ rOperator(qs, qx, qy, qz, rOperator);
+ return rOperator;
+ }
+
+ static void rOperator(QuaternionReadOnly quaternion, DMatrixRMaj lOperatorToPack)
+ {
+ rOperator(quaternion.getS(), quaternion.getX(), quaternion.getY(), quaternion.getZ(), lOperatorToPack);
+ }
+
+ static void rOperator(double qs, double qx, double qy, double qz, DMatrixRMaj rOperatorToPack)
+ {
+ toSkewSymmetricMatrix(-qx, -qy, -qz, 1, 1, rOperatorToPack);
+ for (int i = 1; i < 4; i++)
+ rOperatorToPack.add(i, i, qs);
+ rOperatorToPack.set(0, 0, qs);
+ rOperatorToPack.set(0, 1, -qx);
+ rOperatorToPack.set(0, 2, -qy);
+ rOperatorToPack.set(0, 3, -qz);
+ rOperatorToPack.set(1, 0, qx);
+ rOperatorToPack.set(2, 0, qy);
+ rOperatorToPack.set(3, 0, qz);
+ }
+
+ static void l3Operator(QuaternionReadOnly quaternion, DMatrixRMaj matrixToPack)
+ {
+ toSkewSymmetricMatrix(quaternion.getX(), quaternion.getY(), quaternion.getZ(), matrixToPack);
+ MatrixTools.addDiagonal(matrixToPack, quaternion.getS());
+ }
+
+ static void r3Operator(QuaternionReadOnly quaternion, DMatrixRMaj matrixToPack)
+ {
+ toSkewSymmetricMatrix(quaternion.getX(), quaternion.getY(), quaternion.getZ(), matrixToPack);
+ CommonOps_DDRM.scale(-1.0, matrixToPack);
+ MatrixTools.addDiagonal(matrixToPack, quaternion.getS());
+ }
+
+ static DMatrixRMaj exponentialMap(DMatrixRMaj vector)
+ {
+ return exponentialMap(vector.get(0), vector.get(1), vector.get(2));
+ }
+
+ static DMatrixRMaj exponentialMap(Vector3DReadOnly vector)
+ {
+ return exponentialMap(vector.getX(), vector.getY(), vector.getZ());
+ }
+
+ static DMatrixRMaj exponentialMap(double x, double y, double z)
+ {
+ DMatrixRMaj map = new DMatrixRMaj(4, 1);
+
+ double length = EuclidCoreTools.norm(x, y, z);
+
+ if (Math.abs(length) < 1e-5 )
+ {
+ map.set(0, 0, 1.0);
+ return map;
+ }
+ double s = Math.sin(length / 2.0);
+ map.set(0, 0, Math.cos(length / 2.0));
+ map.set(1, 0, s * x / length);
+ map.set(2, 0, s * y / length);
+ map.set(3, 0, s * z / length);
+
+ return map;
+ }
+
+ static DMatrixRMaj logMap(QuaternionReadOnly quaternion)
+ {
+ return logMap(quaternion.getS(), quaternion.getX(), quaternion.getY(), quaternion.getZ());
+ }
+
+ static DMatrixRMaj logMap(DMatrixRMaj quaternion)
+ {
+ return logMap(quaternion.get(0), quaternion.get(1), quaternion.get(2), quaternion.get(3));
+ }
+
+ static DMatrixRMaj logMap(double qs, double qx, double qy, double qz)
+ {
+ double length = EuclidCoreTools.norm(qx, qy, qz);
+ double multiplier = 2.0 * Math.atan2(length, qs) / length;
+ DMatrixRMaj ret = new DMatrixRMaj(3, 1);
+ ret.set(0, qx * multiplier);
+ ret.set(1, qy * multiplier);
+ ret.set(2, qz * multiplier);
+
+ return ret;
+ }
+}
diff --git a/ihmc-state-estimation/src/main/java/us/ihmc/stateEstimation/humanoid/kinematicsBasedStateEstimation/odomEKF/ProcessModel.java b/ihmc-state-estimation/src/main/java/us/ihmc/stateEstimation/humanoid/kinematicsBasedStateEstimation/odomEKF/ProcessModel.java
new file mode 100644
index 000000000000..b08887592814
--- /dev/null
+++ b/ihmc-state-estimation/src/main/java/us/ihmc/stateEstimation/humanoid/kinematicsBasedStateEstimation/odomEKF/ProcessModel.java
@@ -0,0 +1,120 @@
+package us.ihmc.stateEstimation.humanoid.kinematicsBasedStateEstimation.odomEKF;
+
+import org.ejml.data.DMatrixRMaj;
+import org.ejml.dense.row.CommonOps_DDRM;
+import us.ihmc.euclid.referenceFrame.interfaces.FrameVector3DReadOnly;
+import us.ihmc.euclid.tools.QuaternionTools;
+import us.ihmc.euclid.tuple3D.Vector3D;
+import us.ihmc.euclid.tuple4D.Quaternion;
+import us.ihmc.log.LogTools;
+import us.ihmc.matrixlib.MatrixTools;
+import us.ihmc.yoVariables.providers.BooleanProvider;
+
+import static us.ihmc.stateEstimation.humanoid.kinematicsBasedStateEstimation.odomEKF.OdometryIndexHelper.*;
+
+class ProcessModel
+{
+ private static final DMatrixRMaj eye3x3 = CommonOps_DDRM.identity(3);
+ private final DMatrixRMaj deltaT;
+
+ private static final boolean usePredictedInJacobian = true;
+
+ // State providers
+ private final StateVariables currentState;
+ private final StateVariables predictedState;
+ private final BooleanProvider cancelGravityFromAccelerationMeasurement;
+ private final FrameVector3DReadOnly gravityVector;
+ private final double estimatorDt;
+
+ // Temp variables
+ private final Vector3D integratedVelocity = new Vector3D();
+ private final Vector3D unbiasedAcceleration = new Vector3D();
+ private final Quaternion integratedRotation = new Quaternion();
+ private final DMatrixRMaj rotationMatrix = new DMatrixRMaj(3, 3);
+ private final DMatrixRMaj skewMatrix = new DMatrixRMaj(3, 3);
+
+
+ public ProcessModel(StateVariables processState,
+ StateVariables predictedState,
+ BooleanProvider cancelGravityFromAccelerationMeasurement,
+ FrameVector3DReadOnly gravityVector,
+ double estimatorDt)
+ {
+ this.currentState = processState;
+ this.predictedState = predictedState;
+ this.cancelGravityFromAccelerationMeasurement = cancelGravityFromAccelerationMeasurement;
+ this.gravityVector = gravityVector;
+ this.estimatorDt = estimatorDt;
+
+ deltaT = new DMatrixRMaj(eye3x3);
+ CommonOps_DDRM.scale(estimatorDt, deltaT);
+ }
+
+ public void update()
+ {
+ // transform the acceleration to world
+ unbiasedAcceleration.set(currentState.unbiasedAccel);
+ currentState.orientation.transform(unbiasedAcceleration);
+ if (cancelGravityFromAccelerationMeasurement.getValue())
+ {
+ // remove gravity from acceleration
+ unbiasedAcceleration.sub(gravityVector);
+ }
+
+ // First order integration of the position state
+ predictedState.translation.scaleAdd(estimatorDt, currentState.linearVelocity, currentState.translation);
+
+ // Integration of the velocity state
+ predictedState.linearVelocity.scaleAdd(estimatorDt, unbiasedAcceleration, currentState.linearVelocity);
+
+ // First order integration of the angular state along the SO(3) manifold
+ integratedVelocity.setAndScale(estimatorDt, currentState.unbiasedGyro);
+ integratedRotation.setRotationVector(integratedVelocity);
+ QuaternionTools.multiply(currentState.orientation, integratedRotation, predictedState.orientation);
+
+ // Propagate the bias
+ predictedState.accelBias.set(currentState.accelBias);
+ predictedState.gyroBias.set(currentState.gyroBias);
+ }
+
+ /**
+ * This is equation 39
+ */
+ public void computeProcessJacobian(int offset, DMatrixRMaj jacobianToPack)
+ {
+ StateVariables stateVariables = OdometryKalmanFilter.usePredictedStateInJacobian ? predictedState : currentState;
+ // First row. Partial derivative with respect to the base position state
+ // p_k+1 = p_k + v_k deltaT
+ int rowStart = offset + errorTranslationIndex;
+ CommonOps_DDRM.insert(eye3x3, jacobianToPack, rowStart, offset + errorTranslationIndex);
+ CommonOps_DDRM.insert(deltaT, jacobianToPack, rowStart, offset + errorLinearVelocityIndex);
+
+ // Second row. Partial derivative with respect to the base velocity state
+ // v_k+1 = v_k + deltaT * (R * a - g)
+ rowStart = offset + errorLinearVelocityIndex;
+ CommonOps_DDRM.insert(eye3x3, jacobianToPack, rowStart, offset + errorLinearVelocityIndex);
+
+
+ OdometryTools.toRotationMatrix(stateVariables.orientation, rotationMatrix);
+ OdometryTools.toSkewSymmetricMatrix(stateVariables.unbiasedAccel, skewMatrix);
+ MatrixTools.multAddBlock(-estimatorDt, rotationMatrix, skewMatrix, jacobianToPack, rowStart, offset + errorOrientationIndex);
+
+ if (OdometryKalmanFilter.includeBias)
+ MatrixTools.setMatrixBlock(jacobianToPack, rowStart, offset + errorAccelBiasIndex, rotationMatrix, 0, 0, 3, 3, -estimatorDt);
+
+ // Third row. Partial derivative with respect to the orientation state
+ rowStart = offset + errorOrientationIndex;
+ OdometryTools.toSkewSymmetricMatrix(stateVariables.unbiasedGyro, skewMatrix);
+ CommonOps_DDRM.insert(eye3x3, jacobianToPack, rowStart, offset + errorOrientationIndex);
+ MatrixTools.addMatrixBlock(jacobianToPack, rowStart, offset + errorOrientationIndex, skewMatrix, 0, 0, 3, 3, -estimatorDt);
+ if (OdometryKalmanFilter.includeBias)
+ {
+ MatrixTools.setMatrixBlock(jacobianToPack, rowStart, offset + errorGyroBiasIndex, deltaT, 0, 0, 3, 3, -1.0);
+ }
+
+ // Fourth row. Partial derivative with respect to the acceleration bias state
+ CommonOps_DDRM.insert(eye3x3, jacobianToPack, offset + errorAccelBiasIndex, offset + errorAccelBiasIndex);
+ // Fifth row. Partial derivative with respect to the gyro bias state
+ CommonOps_DDRM.insert(eye3x3, jacobianToPack, offset + errorGyroBiasIndex, offset + errorGyroBiasIndex);
+ }
+}
diff --git a/ihmc-state-estimation/src/main/java/us/ihmc/stateEstimation/humanoid/kinematicsBasedStateEstimation/odomEKF/SensedVariables.java b/ihmc-state-estimation/src/main/java/us/ihmc/stateEstimation/humanoid/kinematicsBasedStateEstimation/odomEKF/SensedVariables.java
new file mode 100644
index 000000000000..ce08c1996dae
--- /dev/null
+++ b/ihmc-state-estimation/src/main/java/us/ihmc/stateEstimation/humanoid/kinematicsBasedStateEstimation/odomEKF/SensedVariables.java
@@ -0,0 +1,89 @@
+package us.ihmc.stateEstimation.humanoid.kinematicsBasedStateEstimation.odomEKF;
+
+import us.ihmc.euclid.referenceFrame.FrameVector3D;
+import us.ihmc.euclid.referenceFrame.ReferenceFrame;
+import us.ihmc.euclid.referenceFrame.interfaces.FrameVector3DReadOnly;
+import us.ihmc.euclid.tuple3D.interfaces.Vector3DReadOnly;
+import us.ihmc.mecano.spatial.Twist;
+import us.ihmc.sensorProcessing.stateEstimation.IMUSensorReadOnly;
+import us.ihmc.stateEstimation.humanoid.kinematicsBasedStateEstimation.IMUBiasProvider;
+import us.ihmc.yoVariables.euclid.referenceFrame.YoFramePoint3D;
+import us.ihmc.yoVariables.euclid.referenceFrame.YoFrameQuaternion;
+import us.ihmc.yoVariables.euclid.referenceFrame.YoFrameVector3D;
+import us.ihmc.yoVariables.registry.YoRegistry;
+import us.ihmc.yoVariables.variable.YoBoolean;
+
+class SensedVariables
+{
+ private static final ReferenceFrame worldFrame = ReferenceFrame.getWorldFrame();
+
+ // State providers
+ private final IMUSensorReadOnly baseIMU;
+ private final IMUSensorReadOnly imu;
+ private final IMUBiasProvider imuBiasProvider;
+
+ // State recorders
+ public final YoFrameVector3D gyroMeasurementInWorld;
+ public final YoFrameVector3D gyroMeasurement;
+ public final YoFrameVector3D accelMeasurementInWorld;
+ public final YoFrameVector3D accelMeasurement;
+
+ public final YoFramePoint3D positionMeasurement;
+ public final YoFrameQuaternion orientationMeasurement;
+ public final YoFrameVector3D linearVelocity;
+
+ public final YoBoolean isInContact;
+
+ // Temp variables
+ private final FrameVector3D linearAcceleration = new FrameVector3D();
+ private final FrameVector3D angularVelocity = new FrameVector3D();
+ private final Twist twist = new Twist();
+
+ public SensedVariables(String prefix, IMUSensorReadOnly baseIMU, IMUSensorReadOnly imu, IMUBiasProvider imuBiasProvider, YoRegistry registry)
+ {
+ this.baseIMU = baseIMU;
+ this.imu = imu;
+ this.imuBiasProvider = imuBiasProvider;
+
+ gyroMeasurementInWorld = new YoFrameVector3D(prefix + "GyroMeasurementInWorld", worldFrame, registry);
+ gyroMeasurement = new YoFrameVector3D(prefix + "GyroMeasurement", imu.getMeasurementFrame(), registry);
+ accelMeasurementInWorld = new YoFrameVector3D(prefix + "AccelMeasurementInWorld", worldFrame, registry);
+ accelMeasurement = new YoFrameVector3D(prefix + "AccelMeasurement", imu.getMeasurementFrame(), registry);
+
+ positionMeasurement = new YoFramePoint3D(prefix + "PositionMeasurement", baseIMU.getMeasurementFrame(), registry);
+ orientationMeasurement = new YoFrameQuaternion(prefix + "OrientationMeasurement", baseIMU.getMeasurementFrame(), registry);
+
+ linearVelocity = new YoFrameVector3D(prefix + "LinearVelocity", worldFrame, registry);
+ isInContact = new YoBoolean(prefix + "IsInContact", registry);
+ }
+
+ public void update()
+ {
+ // Update gyro measure
+ FrameVector3DReadOnly gyroBiasInput = imuBiasProvider.getAngularVelocityBiasInIMUFrame(imu);
+ Vector3DReadOnly gyroRawInput = imu.getAngularVelocityMeasurement();
+
+ angularVelocity.setReferenceFrame(imu.getMeasurementFrame());
+ angularVelocity.sub(gyroRawInput, gyroBiasInput);
+
+ gyroMeasurementInWorld.setMatchingFrame(angularVelocity);
+ gyroMeasurement.setMatchingFrame(angularVelocity);
+
+ // Update the accelerometer measure
+ FrameVector3DReadOnly accelBiasInput = imuBiasProvider.getLinearAccelerationBiasInIMUFrame(imu);
+ Vector3DReadOnly accelRawInput = imu.getLinearAccelerationMeasurement();
+
+ linearAcceleration.setReferenceFrame(imu.getMeasurementFrame());
+ linearAcceleration.sub(accelRawInput, accelBiasInput);
+
+ accelMeasurementInWorld.setMatchingFrame(linearAcceleration);
+ accelMeasurement.setMatchingFrame(linearAcceleration);
+
+ positionMeasurement.setFromReferenceFrame(imu.getMeasurementFrame());
+ orientationMeasurement.setFromReferenceFrame(imu.getMeasurementFrame());
+
+ imu.getMeasurementFrame().getTwistRelativeToOther(baseIMU.getMeasurementFrame(), twist);
+ twist.changeFrame(worldFrame);
+ linearVelocity.set(twist.getLinearPart());
+ }
+}
diff --git a/ihmc-state-estimation/src/main/java/us/ihmc/stateEstimation/humanoid/kinematicsBasedStateEstimation/odomEKF/StateCorrectionVariables.java b/ihmc-state-estimation/src/main/java/us/ihmc/stateEstimation/humanoid/kinematicsBasedStateEstimation/odomEKF/StateCorrectionVariables.java
new file mode 100644
index 000000000000..77f95ed757ba
--- /dev/null
+++ b/ihmc-state-estimation/src/main/java/us/ihmc/stateEstimation/humanoid/kinematicsBasedStateEstimation/odomEKF/StateCorrectionVariables.java
@@ -0,0 +1,39 @@
+package us.ihmc.stateEstimation.humanoid.kinematicsBasedStateEstimation.odomEKF;
+
+import org.ejml.data.DMatrixRMaj;
+import us.ihmc.euclid.referenceFrame.ReferenceFrame;
+import us.ihmc.mecano.frames.MovingReferenceFrame;
+import us.ihmc.yoVariables.euclid.referenceFrame.YoFramePoint3D;
+import us.ihmc.yoVariables.euclid.referenceFrame.YoFrameVector3D;
+import us.ihmc.yoVariables.registry.YoRegistry;
+
+import static us.ihmc.stateEstimation.humanoid.kinematicsBasedStateEstimation.odomEKF.OdometryIndexHelper.*;
+
+class StateCorrectionVariables
+{
+ private static final ReferenceFrame worldFrame = ReferenceFrame.getWorldFrame();
+
+ public final YoFramePoint3D translation;
+ public final YoFrameVector3D linearVelocity;
+ public final YoFrameVector3D orientation;
+ public final YoFrameVector3D accelBias;
+ public final YoFrameVector3D gyroBias;
+
+ public StateCorrectionVariables(String prefix, MovingReferenceFrame sensorFrame, YoRegistry registry)
+ {
+ translation = new YoFramePoint3D(prefix + "TranslationCorrection", worldFrame, registry);
+ linearVelocity = new YoFrameVector3D(prefix + "LinearVelocityCorrection", worldFrame, registry);
+ orientation = new YoFrameVector3D(prefix + "OrientationCorrection", worldFrame, registry);
+ accelBias = new YoFrameVector3D(prefix + "AccelBiasCorrection", sensorFrame, registry);
+ gyroBias = new YoFrameVector3D(prefix + "GyroBiasCorrection", sensorFrame, registry);
+ }
+
+ public void set(int start, DMatrixRMaj state)
+ {
+ translation.set(start + errorTranslationIndex, state);
+ linearVelocity.set(start + errorLinearVelocityIndex, state);
+ orientation.set(start + errorOrientationIndex, state);
+ accelBias.set(start + errorAccelBiasIndex, state);
+ gyroBias.set(start + errorGyroBiasIndex, state);
+ }
+}
diff --git a/ihmc-state-estimation/src/main/java/us/ihmc/stateEstimation/humanoid/kinematicsBasedStateEstimation/odomEKF/StateVariables.java b/ihmc-state-estimation/src/main/java/us/ihmc/stateEstimation/humanoid/kinematicsBasedStateEstimation/odomEKF/StateVariables.java
new file mode 100644
index 000000000000..12e24dc5bd9e
--- /dev/null
+++ b/ihmc-state-estimation/src/main/java/us/ihmc/stateEstimation/humanoid/kinematicsBasedStateEstimation/odomEKF/StateVariables.java
@@ -0,0 +1,78 @@
+package us.ihmc.stateEstimation.humanoid.kinematicsBasedStateEstimation.odomEKF;
+
+import org.ejml.data.DMatrixRMaj;
+import us.ihmc.euclid.referenceFrame.ReferenceFrame;
+import us.ihmc.mecano.frames.MovingReferenceFrame;
+import us.ihmc.yoVariables.euclid.referenceFrame.YoFramePoint3D;
+import us.ihmc.yoVariables.euclid.referenceFrame.YoFrameQuaternion;
+import us.ihmc.yoVariables.euclid.referenceFrame.YoFrameVector3D;
+import us.ihmc.yoVariables.registry.YoRegistry;
+
+import static us.ihmc.stateEstimation.humanoid.kinematicsBasedStateEstimation.odomEKF.OdometryIndexHelper.*;
+
+class StateVariables
+{
+ private static final ReferenceFrame worldFrame = ReferenceFrame.getWorldFrame();
+
+ public final YoFramePoint3D translation;
+ public final YoFrameVector3D linearVelocity;
+ public final YoFrameQuaternion orientation;
+ public final YoFrameVector3D accelBias;
+ public final YoFrameVector3D gyroBias;
+
+ public final YoFrameVector3D unbiasedAccel;
+ public final YoFrameVector3D unbiasedGyro;
+
+ private final MovingReferenceFrame sensorFrame;
+
+ public StateVariables(String prefix, MovingReferenceFrame sensorFrame, YoRegistry registry)
+ {
+ this.sensorFrame = sensorFrame;
+
+ translation = new YoFramePoint3D(prefix + "Translation", worldFrame, registry);
+ linearVelocity = new YoFrameVector3D(prefix + "LinearVelocity", worldFrame, registry);
+ orientation = new YoFrameQuaternion(prefix + "Orientation", worldFrame, registry);
+ accelBias = new YoFrameVector3D(prefix + "AccelBias", sensorFrame, registry);
+ gyroBias = new YoFrameVector3D(prefix + "GyroBias", sensorFrame, registry);
+ unbiasedAccel = new YoFrameVector3D(prefix + "UnbiasedAccel", sensorFrame, registry);
+ unbiasedGyro = new YoFrameVector3D(prefix + "UnbiasedGyro", sensorFrame, registry);
+ }
+
+ public void initialize()
+ {
+ this.translation.setFromReferenceFrame(sensorFrame);
+ this.orientation.setFromReferenceFrame(sensorFrame);
+ linearVelocity.setMatchingFrame(sensorFrame.getTwistOfFrame().getLinearPart());
+ accelBias.setToZero();
+ gyroBias.setToZero();
+ }
+
+ public void set(int start, DMatrixRMaj state, SensedVariables sensedVariables)
+ {
+ translation.set(start + stateTranslationIndex, state);
+ linearVelocity.set(start + stateLinearVelocityIndex, state);
+ orientation.set(start + stateOrientationIndex, state);
+ accelBias.set(start + stateAccelBiasIndex, state);
+ gyroBias.set(start + stateGyroBiasIndex, state);
+
+ if (OdometryKalmanFilter.includeBias)
+ {
+ unbiasedAccel.sub(sensedVariables.accelMeasurement, accelBias);
+ unbiasedGyro.sub(sensedVariables.gyroMeasurement, gyroBias);
+ }
+ else
+ {
+ unbiasedAccel.set(sensedVariables.accelMeasurement);
+ unbiasedGyro.set(sensedVariables.gyroMeasurement);
+ }
+ }
+
+ public void get(int start, DMatrixRMaj stateToPack)
+ {
+ translation.get(start + stateTranslationIndex, stateToPack);
+ linearVelocity.get(start + stateLinearVelocityIndex, stateToPack);
+ orientation.get(start + stateOrientationIndex, stateToPack);
+ accelBias.get(start + stateAccelBiasIndex, stateToPack);
+ gyroBias.get(start + stateGyroBiasIndex, stateToPack);
+ }
+}
diff --git a/ihmc-state-estimation/src/test/java/us/ihmc/stateEstimation/humanoid/kinematicsBasedStateEstimation/odomEKF/MeasurementModelTest.java b/ihmc-state-estimation/src/test/java/us/ihmc/stateEstimation/humanoid/kinematicsBasedStateEstimation/odomEKF/MeasurementModelTest.java
new file mode 100644
index 000000000000..49bf96e8b6fc
--- /dev/null
+++ b/ihmc-state-estimation/src/test/java/us/ihmc/stateEstimation/humanoid/kinematicsBasedStateEstimation/odomEKF/MeasurementModelTest.java
@@ -0,0 +1,435 @@
+package us.ihmc.stateEstimation.humanoid.kinematicsBasedStateEstimation.odomEKF;
+
+import org.ejml.EjmlUnitTests;
+import org.ejml.data.DMatrixRMaj;
+import org.ejml.dense.row.CommonOps_DDRM;
+import org.junit.jupiter.api.Test;
+import us.ihmc.euclid.geometry.Pose3D;
+import us.ihmc.euclid.referenceFrame.FrameVector3D;
+import us.ihmc.euclid.referenceFrame.ReferenceFrame;
+import us.ihmc.euclid.referenceFrame.interfaces.FrameVector3DReadOnly;
+import us.ihmc.euclid.tools.EuclidCoreRandomTools;
+import us.ihmc.euclid.transform.RigidBodyTransform;
+import us.ihmc.euclid.tuple3D.Vector3D;
+import us.ihmc.euclid.tuple3D.interfaces.Vector3DReadOnly;
+import us.ihmc.euclid.tuple4D.interfaces.QuaternionReadOnly;
+import us.ihmc.matrixlib.MatrixTools;
+import us.ihmc.mecano.frames.MovingReferenceFrame;
+import us.ihmc.mecano.multiBodySystem.interfaces.RigidBodyBasics;
+import us.ihmc.mecano.spatial.Twist;
+import us.ihmc.sensorProcessing.stateEstimation.IMUSensorReadOnly;
+import us.ihmc.stateEstimation.humanoid.kinematicsBasedStateEstimation.IMUBiasProvider;
+import us.ihmc.stateEstimation.humanoid.kinematicsBasedStateEstimation.odomEKF.ProcessModelTest.TestBiasProvider;
+import us.ihmc.stateEstimation.humanoid.kinematicsBasedStateEstimation.odomEKF.ProcessModelTest.TestIMU;
+import us.ihmc.yoVariables.registry.YoRegistry;
+
+import java.util.Random;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class MeasurementModelTest
+{
+ private static final double gravityZ = 9.81;
+ private static final FrameVector3D gravity = new FrameVector3D(ReferenceFrame.getWorldFrame(), 0.0, 0.0, gravityZ);
+ private static final double estimatorDt = 0.01;
+
+ @Test
+ public void testUpdate()
+ {
+ int iters = 100;
+ Random random = new Random(1738L);
+
+ YoRegistry test = new YoRegistry(getClass().getSimpleName());
+ ProcessModelTest.TestIMU baseIMU = new ProcessModelTest.TestIMU("base");
+ ProcessModelTest.TestIMU footIMU = new ProcessModelTest.TestIMU("foot");
+
+ StateVariables baseState = new StateVariables("base", baseIMU.getMeasurementFrame(), test);
+ StateVariables footState = new StateVariables("foot", footIMU.getMeasurementFrame(), test);
+ SensedVariables baseSensing = new SensedVariables("baseSensed", baseIMU, baseIMU, new TestBiasProvider(), test);
+ SensedVariables footSensing = new SensedVariables("footSensed", baseIMU, footIMU, new TestBiasProvider(), test);
+ MeasurementVariables footMeasure = new MeasurementVariables("footMeasure", test);
+
+ MeasurementModel measurementModel = new MeasurementModel("foot0", baseState, footState, footSensing, footMeasure, gravity, () -> Double.MAX_VALUE, test);
+ DMatrixRMaj jacobian = new DMatrixRMaj(OdometryIndexHelper.errorSizePerLink, OdometryIndexHelper.errorSizePerLink * 2);
+
+ for (int iter = 0; iter < iters; iter++)
+ {
+ jacobian.zero();
+
+ OdomTestTools.setRandomSensed(random, footSensing);
+ footIMU.linearAcceleration.set(footSensing.accelMeasurement);
+ footIMU.angularVelocity.set(footSensing.gyroMeasurement);
+ footIMU.pose.set(EuclidCoreRandomTools.nextPoint3D(random, 10.0), EuclidCoreRandomTools.nextOrientation3D(random));
+ footIMU.linearVelocity.set(EuclidCoreRandomTools.nextVector3D(random, 10.0));
+ footIMU.sensorFrame.update();
+
+ OdomTestTools.setRandomSensed(random, baseSensing);
+ baseIMU.linearAcceleration.set(baseSensing.accelMeasurement);
+ baseIMU.angularVelocity.set(baseSensing.gyroMeasurement);
+ baseIMU.pose.set(EuclidCoreRandomTools.nextPoint3D(random, 10.0), EuclidCoreRandomTools.nextOrientation3D(random));
+ baseIMU.linearVelocity.set(EuclidCoreRandomTools.nextVector3D(random, 10.0));
+ baseIMU.sensorFrame.update();
+
+ OdomTestTools.setRandomState(random, baseSensing, baseState);
+ OdomTestTools.setRandomState(random, footSensing, footState);
+
+ measurementModel.update(new DMatrixRMaj(3, 3));
+
+ DMatrixRMaj baseTranslation = new DMatrixRMaj(3, 1);
+ DMatrixRMaj footTranslation = new DMatrixRMaj(3, 1);
+ DMatrixRMaj footRelativePosition = new DMatrixRMaj(3, 1);
+ DMatrixRMaj baseLinearVelocity = new DMatrixRMaj(3, 1);
+ DMatrixRMaj footLinearVelocity = new DMatrixRMaj(3, 1);
+ DMatrixRMaj gravityVector = new DMatrixRMaj(3, 1);
+ DMatrixRMaj baseRotationMatrix = new DMatrixRMaj(3, 3);
+ DMatrixRMaj footRotationMatrix = new DMatrixRMaj(3, 3);
+ DMatrixRMaj baseAcceleration = new DMatrixRMaj(3, 1);
+ DMatrixRMaj baseAccelerationBias = new DMatrixRMaj(3, 1);
+ DMatrixRMaj footAccelerationBias = new DMatrixRMaj(3, 1);
+ DMatrixRMaj baseGyro = new DMatrixRMaj(3, 1);
+ DMatrixRMaj baseGyroBias = new DMatrixRMaj(3, 1);
+ DMatrixRMaj baseGyroUnbiased = new DMatrixRMaj(3, 1);
+ DMatrixRMaj footVelocity = new DMatrixRMaj(3, 1);
+
+ DMatrixRMaj footOffset = new DMatrixRMaj(3, 1);
+ DMatrixRMaj skewMatrix = new DMatrixRMaj(3, 3);
+
+ baseState.translation.get(baseTranslation);
+ footState.translation.get(footTranslation);
+ footSensing.positionMeasurement.get(footRelativePosition);
+ baseState.linearVelocity.get(baseLinearVelocity);
+ footState.linearVelocity.get(footLinearVelocity);
+ OdometryTools.toRotationMatrix(baseState.orientation, baseRotationMatrix);
+ OdometryTools.toRotationMatrix(footState.orientation, footRotationMatrix);
+ gravity.get(gravityVector);
+ baseSensing.accelMeasurement.get(baseAcceleration);
+ baseState.accelBias.get(baseAccelerationBias);
+ footState.accelBias.get(footAccelerationBias);
+ baseSensing.gyroMeasurement.get(baseGyro);
+ baseState.gyroBias.get(baseGyroBias);
+
+ DMatrixRMaj expectedRelativeError = new DMatrixRMaj(3, 1);
+ DMatrixRMaj expectedPredictedLinearVelocityError = new DMatrixRMaj(3, 1);
+ DMatrixRMaj expectedPredictedRotationError = new DMatrixRMaj(4, 1);
+ DMatrixRMaj expectedPredictedContactVelocity = new DMatrixRMaj(3, 1);
+ DMatrixRMaj expectedPredictedContactAccel = new DMatrixRMaj(3, 1);
+
+ // First term
+ CommonOps_DDRM.subtract(footTranslation, baseTranslation, footOffset);
+ CommonOps_DDRM.multTransA(baseRotationMatrix, footOffset, expectedRelativeError);
+
+ // Second term
+ DMatrixRMaj rotation = multiplyLeftInverse(footSensing.orientationMeasurement, OdomTestTools.fromVector(multiplyLeftInverse(baseState.orientation, footState.orientation)));
+ expectedPredictedRotationError.set(OdometryTools.logMap(rotation));
+
+ // Third term
+ CommonOps_DDRM.subtract(baseLinearVelocity, footLinearVelocity, footVelocity);
+ if (OdometryKalmanFilter.includeBias)
+ CommonOps_DDRM.subtract(baseGyro, baseGyroBias, baseGyroUnbiased);
+ else
+ baseGyroUnbiased.set(baseGyro);
+ OdometryTools.toSkewSymmetricMatrix(baseGyroUnbiased.get(0), baseGyroUnbiased.get(1), baseGyroUnbiased.get(2), skewMatrix);
+ footSensing.linearVelocity.get(expectedPredictedLinearVelocityError);
+ CommonOps_DDRM.multAdd(-1.0, skewMatrix, footRelativePosition, expectedPredictedLinearVelocityError);
+ CommonOps_DDRM.multAddTransA(-1.0, baseRotationMatrix, footVelocity, expectedPredictedLinearVelocityError);
+
+ // Fourth term
+ expectedPredictedContactVelocity.set(footLinearVelocity);
+
+
+ // Fifth term
+ CommonOps_DDRM.multTransA(footRotationMatrix, gravityVector, expectedPredictedContactAccel);
+ if (OdometryKalmanFilter.includeBias)
+ CommonOps_DDRM.addEquals(expectedPredictedContactAccel, footAccelerationBias);
+
+ DMatrixRMaj predictedRelativeError = new DMatrixRMaj(3, 1);
+ DMatrixRMaj predictedRotationError = new DMatrixRMaj(3, 1);
+ DMatrixRMaj predictedLinearVelocityError = new DMatrixRMaj(3, 1);
+ DMatrixRMaj predictedContactVelocity = new DMatrixRMaj(3, 1);
+ DMatrixRMaj predictedContactAccel = new DMatrixRMaj(3, 1);
+
+ footMeasure.relativePosition.get(predictedRelativeError);
+ footMeasure.relativeOrientationError.get(predictedRotationError);
+ footMeasure.relativeLinearVelocityError.get(predictedLinearVelocityError);
+ footMeasure.contactVelocity.get(predictedContactVelocity);
+ footMeasure.accelMeasure.get(predictedContactAccel);
+
+ EjmlUnitTests.assertEquals(expectedRelativeError, predictedRelativeError, 1e-6);
+ EjmlUnitTests.assertEquals(expectedPredictedRotationError, predictedRotationError, 1e-6);
+ EjmlUnitTests.assertEquals(expectedPredictedLinearVelocityError, predictedLinearVelocityError, 1e-6);
+ EjmlUnitTests.assertEquals(expectedPredictedContactVelocity, predictedContactVelocity, 1e-6);
+ EjmlUnitTests.assertEquals(expectedPredictedContactAccel, predictedContactAccel, 1e-6);
+ }
+ }
+
+
+ @Test
+ public void testComputeMeasurementJacobian()
+ {
+ int iters = 100;
+ Random random = new Random(1738L);
+
+ YoRegistry test = new YoRegistry(getClass().getSimpleName());
+ TestIMU baseIMU = new TestIMU("base");
+ TestIMU footIMU = new TestIMU("foot");
+
+ StateVariables baseState = new StateVariables("base", baseIMU.getMeasurementFrame(), test);
+ StateVariables footState = new StateVariables("foot", footIMU.getMeasurementFrame(), test);
+ SensedVariables baseSensing = new SensedVariables("baseSensed", baseIMU, baseIMU, new TestBiasProvider(), test);
+ SensedVariables footSensing = new SensedVariables("footSensed", baseIMU, footIMU, new TestBiasProvider(), test);
+ MeasurementVariables measurement = new MeasurementVariables("footMeasure", test);
+
+ MeasurementModel measurementModel = new MeasurementModel("foot0", baseState, footState, footSensing, measurement, gravity, () -> Double.MAX_VALUE, test);
+ DMatrixRMaj jacobian = new DMatrixRMaj(OdometryIndexHelper.errorSizePerLink, OdometryIndexHelper.errorSizePerLink * 2);
+
+ for (int iter = 0; iter < iters; iter++)
+ {
+ jacobian.zero();
+
+ measurementModel.isInContact.set(true);
+
+ OdomTestTools.setRandomSensed(random, footSensing);
+ footIMU.linearAcceleration.set(footSensing.accelMeasurement);
+ footIMU.angularVelocity.set(footSensing.gyroMeasurement);
+ footIMU.pose.set(EuclidCoreRandomTools.nextPoint3D(random, 10.0), EuclidCoreRandomTools.nextOrientation3D(random));
+ footIMU.linearVelocity.set(EuclidCoreRandomTools.nextVector3D(random, 10.0));
+ footIMU.sensorFrame.update();
+
+ OdomTestTools.setRandomSensed(random, baseSensing);
+ baseIMU.linearAcceleration.set(baseSensing.accelMeasurement);
+ baseIMU.angularVelocity.set(baseSensing.gyroMeasurement);
+ baseIMU.pose.set(EuclidCoreRandomTools.nextPoint3D(random, 10.0), EuclidCoreRandomTools.nextOrientation3D(random));
+ baseIMU.linearVelocity.set(EuclidCoreRandomTools.nextVector3D(random, 10.0));
+ baseIMU.sensorFrame.update();
+
+ OdomTestTools.setRandomState(random, baseSensing, baseState);
+ OdomTestTools.setRandomState(random, footSensing, footState);
+
+ measurementModel.computeBaseMeasurementJacobian( 0, jacobian);
+ measurementModel.computeFootMeasurementJacobian( 0, OdometryIndexHelper.errorSizePerLink, jacobian);
+
+ DMatrixRMaj jacobianExpected = new DMatrixRMaj(OdometryIndexHelper.errorSizePerLink, 2 * OdometryIndexHelper.errorSizePerLink);
+ DMatrixRMaj skewMatrix = new DMatrixRMaj(3, 3);
+ DMatrixRMaj baseRotationMatrix = new DMatrixRMaj(3, 3);
+ DMatrixRMaj baseRotationMatrixTranspose = new DMatrixRMaj(3, 3);
+ DMatrixRMaj footRotationMatrix = new DMatrixRMaj(3, 3);
+ DMatrixRMaj footRotationMatrixTranspose = new DMatrixRMaj(3, 3);
+ OdometryTools.toRotationMatrix(baseState.orientation, baseRotationMatrix);
+ OdometryTools.toRotationMatrix(footState.orientation, footRotationMatrix);
+ CommonOps_DDRM.transpose(baseRotationMatrix, baseRotationMatrixTranspose);
+ CommonOps_DDRM.transpose(footRotationMatrix, footRotationMatrixTranspose);
+
+ // Row 1, base
+ MatrixTools.setMatrixBlock(jacobianExpected, 0, 0, baseRotationMatrixTranspose, 0, 0, 3, 3, -1.0);
+ DMatrixRMaj footPosition = new DMatrixRMaj(3, 1);
+ DMatrixRMaj basePosition = new DMatrixRMaj(3, 1);
+ DMatrixRMaj footOffset = new DMatrixRMaj(3, 1);
+ DMatrixRMaj footOffsetInBaseFrame = new DMatrixRMaj(3, 1);
+ footState.translation.get(footPosition);
+ baseState.translation.get(basePosition);
+ CommonOps_DDRM.subtract(footPosition, basePosition, footOffset);
+ CommonOps_DDRM.multTransA(baseRotationMatrix, footOffset, footOffsetInBaseFrame);
+ OdometryTools.toSkewSymmetricMatrix(footOffsetInBaseFrame.get(0), footOffsetInBaseFrame.get(1), footOffsetInBaseFrame.get(2), skewMatrix);
+ CommonOps_DDRM.insert(skewMatrix, jacobianExpected, 0, 6);
+
+ // Row 1, foot
+ CommonOps_DDRM.insert(baseRotationMatrixTranspose, jacobianExpected, 0, 15);
+
+ // Row 2, base
+ DMatrixRMaj left = OdometryTools.lOperator(multiplyLeftInverse(footState.orientation, baseState.orientation));
+ DMatrixRMaj right = OdometryTools.rOperator(footSensing.orientationMeasurement);
+ DMatrixRMaj block = new DMatrixRMaj(4, 4);
+ CommonOps_DDRM.mult(left, right, block);
+ MatrixTools.setMatrixBlock(jacobianExpected, 3, 6, block, 1, 1, 3, 3, -1.0);
+
+ // Row 2, foot
+ block = OdometryTools.lOperator(multiply(footSensing.orientationMeasurement, OdomTestTools.fromVector(multiplyLeftInverse(baseState.orientation, footState.orientation))));
+ MatrixTools.setMatrixBlock(jacobianExpected, 3, 21, block, 1, 1, 3, 3, 1.0);
+
+ // Row 3, base
+ MatrixTools.setMatrixBlock(jacobianExpected, 6, 3, baseRotationMatrixTranspose, 0, 0, 3, 3, -1.0);
+ DMatrixRMaj footVelocity = new DMatrixRMaj(3, 1);
+ DMatrixRMaj baseVelocity = new DMatrixRMaj(3, 1);
+ DMatrixRMaj velocityError = new DMatrixRMaj(3, 1);
+ DMatrixRMaj velocityErrorInBaseFrame = new DMatrixRMaj(3, 1);
+ footState.linearVelocity.get(footVelocity);
+ baseState.linearVelocity.get(baseVelocity);
+ CommonOps_DDRM.subtract(footVelocity, baseVelocity, velocityError);
+ CommonOps_DDRM.multTransA(baseRotationMatrix, velocityError, velocityErrorInBaseFrame);
+ OdometryTools.toSkewSymmetricMatrix(velocityErrorInBaseFrame.get(0), velocityErrorInBaseFrame.get(1), velocityErrorInBaseFrame.get(2), skewMatrix);
+ CommonOps_DDRM.insert(skewMatrix, jacobianExpected, 6, 6);
+ if (OdometryKalmanFilter.includeBias)
+ {
+ OdometryTools.toSkewSymmetricMatrix(footSensing.positionMeasurement, skewMatrix);
+ MatrixTools.setMatrixBlock(jacobianExpected, 6, 12, skewMatrix, 0, 0, 3, 3, -1.0);
+ }
+
+ // Row 3, foot
+ CommonOps_DDRM.insert(baseRotationMatrixTranspose, jacobianExpected, 6, 18);
+
+ // Row 4, foot
+ if (OdometryKalmanFilter.includeBias)
+ {
+ for (int i = 0; i < 3; i++)
+ jacobianExpected.set(9 + i, 18 + i, 1.0);
+ }
+
+ // Row 5, foot
+ if (OdometryKalmanFilter.includeBias)
+ {
+ DMatrixRMaj gravityVector = new DMatrixRMaj(3, 1);
+ DMatrixRMaj rotatedGravityVector = new DMatrixRMaj(3, 1);
+ gravity.get(gravityVector);
+ CommonOps_DDRM.multTransA(footRotationMatrix, gravityVector, rotatedGravityVector);
+ OdometryTools.toSkewSymmetricMatrix(rotatedGravityVector.get(0), rotatedGravityVector.get(1), rotatedGravityVector.get(2), skewMatrix);
+ CommonOps_DDRM.insert(skewMatrix, jacobianExpected, 12, 21);
+
+ for (int i = 0; i < 3; i++)
+ jacobianExpected.set(12 + i, 24 + i, 1.0);
+ }
+
+ EjmlUnitTests.assertEquals(jacobianExpected, jacobian, 1e-5);
+ }
+ }
+
+ static DMatrixRMaj multiply(QuaternionReadOnly a, QuaternionReadOnly b)
+ {
+ DMatrixRMaj vector = new DMatrixRMaj(4, 1);
+ CommonOps_DDRM.mult(OdometryTools.lOperator(a), OdomTestTools.toVector(b), vector);
+
+ return vector;
+ }
+
+ static DMatrixRMaj multiplyLeftInverse(QuaternionReadOnly a, QuaternionReadOnly b)
+ {
+ DMatrixRMaj vector = new DMatrixRMaj(4, 1);
+ CommonOps_DDRM.mult(OdometryTools.lOperator(a.getS(), -a.getX(), -a.getY(), -a.getZ()), OdomTestTools.toVector(b), vector);
+
+ return vector;
+ }
+
+ static class TestBiasProvider implements IMUBiasProvider
+ {
+
+ @Override
+ public FrameVector3DReadOnly getAngularVelocityBiasInIMUFrame(IMUSensorReadOnly imu)
+ {
+ return new FrameVector3D(imu.getMeasurementFrame());
+ }
+
+ @Override
+ public FrameVector3DReadOnly getAngularVelocityBiasInWorldFrame(IMUSensorReadOnly imu)
+ {
+ return new FrameVector3D();
+ }
+
+ @Override
+ public FrameVector3DReadOnly getLinearAccelerationBiasInIMUFrame(IMUSensorReadOnly imu)
+ {
+ return new FrameVector3D(imu.getMeasurementFrame());
+ }
+
+ @Override
+ public FrameVector3DReadOnly getLinearAccelerationBiasInWorldFrame(IMUSensorReadOnly imu)
+ {
+ return new FrameVector3D();
+ }
+ }
+
+ static class TestIMU implements IMUSensorReadOnly
+ {
+ private String name;
+ public final MovingReferenceFrame sensorFrame;
+ public final Pose3D pose = new Pose3D();
+ public final Vector3D angularVelocity = new Vector3D();
+ public final Vector3D linearVelocity = new Vector3D();
+ public final Vector3D linearAcceleration = new Vector3D();
+
+ public TestIMU(String name)
+ {
+ this.name = name;
+
+ sensorFrame = new MovingReferenceFrame(name, ReferenceFrame.getWorldFrame())
+ {
+ @Override
+ protected void updateTwistRelativeToParent(Twist twist)
+ {
+ twist.setToZero();
+ pose.transform(linearVelocity, twist.getLinearPart());
+ }
+
+ @Override
+ protected void updateTransformToParent(RigidBodyTransform rigidBodyTransform)
+ {
+ rigidBodyTransform.set(pose);
+ }
+ };
+ }
+
+ @Override
+ public String getSensorName()
+ {
+ return name;
+ }
+
+ @Override
+ public MovingReferenceFrame getMeasurementFrame()
+ {
+ return sensorFrame;
+ }
+
+ @Override
+ public RigidBodyBasics getMeasurementLink()
+ {
+ return null;
+ }
+
+ @Override
+ public QuaternionReadOnly getOrientationMeasurement()
+ {
+ return pose.getOrientation();
+ }
+
+ @Override
+ public Vector3DReadOnly getAngularVelocityMeasurement()
+ {
+ return angularVelocity;
+ }
+
+ @Override
+ public Vector3DReadOnly getLinearAccelerationMeasurement()
+ {
+ return linearAcceleration;
+ }
+
+ @Override
+ public void getOrientationNoiseCovariance(DMatrixRMaj noiseCovarianceToPack)
+ {
+
+ }
+
+ @Override
+ public void getAngularVelocityNoiseCovariance(DMatrixRMaj noiseCovarianceToPack)
+ {
+
+ }
+
+ @Override
+ public void getAngularVelocityBiasProcessNoiseCovariance(DMatrixRMaj biasProcessNoiseCovarianceToPack)
+ {
+
+ }
+
+ @Override
+ public void getLinearAccelerationNoiseCovariance(DMatrixRMaj noiseCovarianceToPack)
+ {
+
+ }
+
+ @Override
+ public void getLinearAccelerationBiasProcessNoiseCovariance(DMatrixRMaj biasProcessNoiseCovarianceToPack)
+ {
+
+ }
+ }
+}
diff --git a/ihmc-state-estimation/src/test/java/us/ihmc/stateEstimation/humanoid/kinematicsBasedStateEstimation/odomEKF/OdomTestTools.java b/ihmc-state-estimation/src/test/java/us/ihmc/stateEstimation/humanoid/kinematicsBasedStateEstimation/odomEKF/OdomTestTools.java
new file mode 100644
index 000000000000..41e7dadfa740
--- /dev/null
+++ b/ihmc-state-estimation/src/test/java/us/ihmc/stateEstimation/humanoid/kinematicsBasedStateEstimation/odomEKF/OdomTestTools.java
@@ -0,0 +1,67 @@
+package us.ihmc.stateEstimation.humanoid.kinematicsBasedStateEstimation.odomEKF;
+
+import org.ejml.data.DMatrixRMaj;
+import us.ihmc.euclid.tools.EuclidCoreRandomTools;
+import us.ihmc.euclid.tools.EuclidCoreTools;
+import us.ihmc.euclid.tuple4D.Quaternion;
+import us.ihmc.euclid.tuple4D.interfaces.QuaternionReadOnly;
+
+import java.util.Random;
+
+public class OdomTestTools
+{
+ public static void setRandomSensed(Random random, SensedVariables sensedVariablesToPack)
+ {
+ sensedVariablesToPack.gyroMeasurement.set(EuclidCoreRandomTools.nextVector3D(random, 5.0));
+ sensedVariablesToPack.accelMeasurement.set(EuclidCoreRandomTools.nextVector3D(random, 5.0));
+ sensedVariablesToPack.positionMeasurement.set(EuclidCoreRandomTools.nextVector3D(random, 5.0));
+ sensedVariablesToPack.orientationMeasurement.set(EuclidCoreRandomTools.nextQuaternion(random, 5.0));
+ sensedVariablesToPack.linearVelocity.set(EuclidCoreRandomTools.nextVector3D(random, 5.0));
+ }
+
+ public static DMatrixRMaj setRandomState(Random random, SensedVariables sensedVariables, StateVariables stateVariablesToPack)
+ {
+ stateVariablesToPack.translation.set(EuclidCoreRandomTools.nextPoint3D(random, 10.0));
+ stateVariablesToPack.linearVelocity.set(EuclidCoreRandomTools.nextVector3D(random, 10.0));
+ stateVariablesToPack.orientation.set(EuclidCoreRandomTools.nextQuaternion(random));
+ stateVariablesToPack.accelBias.set(EuclidCoreRandomTools.nextVector3D(random, 5.0));
+ stateVariablesToPack.gyroBias.set(EuclidCoreRandomTools.nextVector3D(random, 5.0));
+
+ if (OdometryKalmanFilter.includeBias)
+ {
+ stateVariablesToPack.unbiasedAccel.sub(sensedVariables.accelMeasurement, stateVariablesToPack.accelBias);
+ stateVariablesToPack.unbiasedGyro.sub(sensedVariables.gyroMeasurement, stateVariablesToPack.gyroBias);
+ }
+ else
+ {
+ stateVariablesToPack.unbiasedAccel.set(sensedVariables.accelMeasurement);
+ stateVariablesToPack.unbiasedGyro.set(sensedVariables.gyroMeasurement);
+ }
+
+ DMatrixRMaj stateVector = new DMatrixRMaj(OdometryIndexHelper.stateSizePerLink, 1);
+ stateVariablesToPack.translation.get(stateVector);
+ stateVariablesToPack.linearVelocity.get(3, stateVector);
+ stateVariablesToPack.orientation.get(6, stateVector);
+ stateVariablesToPack.accelBias.get(10, stateVector);
+ stateVariablesToPack.gyroBias.get(13, stateVector);
+
+ return stateVector;
+ }
+
+
+ static Quaternion fromVector(DMatrixRMaj quaternion)
+ {
+ return new Quaternion(quaternion.get(1), quaternion.get(2), quaternion.get(3), quaternion.get(0));
+ }
+
+ static DMatrixRMaj toVector(QuaternionReadOnly quaternionReadOnly)
+ {
+ DMatrixRMaj vector = new DMatrixRMaj(4, 1);
+ vector.set(0, quaternionReadOnly.getS());
+ vector.set(1, quaternionReadOnly.getX());
+ vector.set(2, quaternionReadOnly.getY());
+ vector.set(3, quaternionReadOnly.getZ());
+
+ return vector;
+ }
+}
diff --git a/ihmc-state-estimation/src/test/java/us/ihmc/stateEstimation/humanoid/kinematicsBasedStateEstimation/odomEKF/OdometryToolsTest.java b/ihmc-state-estimation/src/test/java/us/ihmc/stateEstimation/humanoid/kinematicsBasedStateEstimation/odomEKF/OdometryToolsTest.java
new file mode 100644
index 000000000000..ae6d107be4ee
--- /dev/null
+++ b/ihmc-state-estimation/src/test/java/us/ihmc/stateEstimation/humanoid/kinematicsBasedStateEstimation/odomEKF/OdometryToolsTest.java
@@ -0,0 +1,151 @@
+package us.ihmc.stateEstimation.humanoid.kinematicsBasedStateEstimation.odomEKF;
+
+
+import org.ejml.EjmlUnitTests;
+import org.ejml.data.DMatrixRMaj;
+import org.ejml.dense.row.CommonOps_DDRM;
+import org.junit.jupiter.api.Test;
+import us.ihmc.euclid.matrix.RotationMatrix;
+import us.ihmc.euclid.tools.EuclidCoreRandomTools;
+import us.ihmc.euclid.tools.EuclidCoreTestTools;
+import us.ihmc.euclid.tuple3D.Vector3D;
+import us.ihmc.euclid.tuple3D.interfaces.Vector3DReadOnly;
+import us.ihmc.euclid.tuple4D.Quaternion;
+import us.ihmc.euclid.tuple4D.interfaces.QuaternionReadOnly;
+import us.ihmc.matrixlib.MatrixTools;
+
+import java.util.Random;
+
+public class OdometryToolsTest
+{
+ @Test
+ public void testToSkewSymmetric()
+ {
+ Random random = new Random(1738L);
+
+ for (int i = 0; i < 1000; i++)
+ {
+ Vector3D a = EuclidCoreRandomTools.nextVector3D(random);
+ Vector3D b = EuclidCoreRandomTools.nextVector3D(random);
+ Vector3D cExpected = EuclidCoreRandomTools.nextVector3D(random);
+ Vector3D c = EuclidCoreRandomTools.nextVector3D(random);
+
+ cExpected.cross(a, b);
+
+ DMatrixRMaj skewMatrix = new DMatrixRMaj(3, 3);
+
+ DMatrixRMaj bVector = new DMatrixRMaj(3, 1);
+ b.get(bVector);
+ DMatrixRMaj cVector = new DMatrixRMaj(3, 1);
+ OdometryTools.toSkewSymmetricMatrix(a, skewMatrix);
+
+ CommonOps_DDRM.mult(skewMatrix, bVector, cVector);
+ c.set(cVector);
+
+ EuclidCoreTestTools.assertEquals(cExpected, c, 1e-8);
+ }
+ }
+
+ @Test
+ public void testTransform()
+ {
+ Random random = new Random(1738L);
+
+ for (int i = 0; i < 1000; i++)
+ {
+ Quaternion a = EuclidCoreRandomTools.nextQuaternion(random);
+ Vector3D b = EuclidCoreRandomTools.nextVector3D(random);
+ Vector3D cExpected = EuclidCoreRandomTools.nextVector3D(random);
+ Vector3D c = EuclidCoreRandomTools.nextVector3D(random);
+ Vector3D cInverseExpected = EuclidCoreRandomTools.nextVector3D(random);
+ Vector3D cInverse = EuclidCoreRandomTools.nextVector3D(random);
+
+ DMatrixRMaj rotationMatrix = new DMatrixRMaj(3, 3);
+ DMatrixRMaj rotationMatrixTranspose = new DMatrixRMaj(3, 3);
+ DMatrixRMaj bVector = new DMatrixRMaj(3, 1);
+ DMatrixRMaj cVector = new DMatrixRMaj(3, 1);
+ DMatrixRMaj cInverseVector = new DMatrixRMaj(3, 1);
+ OdometryTools.toRotationMatrix(a, rotationMatrix);
+ CommonOps_DDRM.transpose(rotationMatrix, rotationMatrixTranspose);
+ OdometryTools.toRotationMatrix(a, rotationMatrix);
+ b.get(bVector);
+
+ CommonOps_DDRM.mult(rotationMatrix, bVector, cVector);
+ CommonOps_DDRM.mult(rotationMatrixTranspose, bVector, cInverseVector);
+ c.set(cVector);
+ cInverse.set(cInverseVector);
+
+ a.transform(b, cExpected);
+ a.inverseTransform(b, cInverseExpected);
+
+ EuclidCoreTestTools.assertEquals(cExpected, c, 1e-5);
+ EuclidCoreTestTools.assertEquals(cInverseExpected, cInverse, 1e-5);
+ }
+ }
+
+ @Test
+ public void testToRotationMatrix()
+ {
+ Random random = new Random(1738L);
+
+ for (int i = 0; i < 1000; i++)
+ {
+ Quaternion quaternion = EuclidCoreRandomTools.nextQuaternion(random);
+ DMatrixRMaj rotationMatrix = new DMatrixRMaj(3, 3);
+
+ OdometryTools.toRotationMatrix(quaternion, rotationMatrix);
+
+ RotationMatrix mat = new RotationMatrix(quaternion);
+ DMatrixRMaj expected = new DMatrixRMaj(3, 3);
+ mat.get(expected);
+
+ EjmlUnitTests.assertEquals(expected, rotationMatrix, 1e-8);
+ }
+ }
+
+ @Test
+ public void testToRotationMatrixInverse()
+ {
+ Random random = new Random(1738L);
+
+ for (int i = 0; i < 1000; i++)
+ {
+ Quaternion quaternion = EuclidCoreRandomTools.nextQuaternion(random);
+ DMatrixRMaj rotationMatrixInverse = new DMatrixRMaj(3, 3);
+
+ OdometryTools.toRotationMatrixInverse(quaternion, rotationMatrixInverse);
+
+ RotationMatrix mat = new RotationMatrix(quaternion);
+ DMatrixRMaj rotationExpected = new DMatrixRMaj(3, 3);
+ mat.get(rotationExpected);
+
+ CommonOps_DDRM.invert(rotationExpected);
+
+ EjmlUnitTests.assertEquals(rotationExpected, rotationMatrixInverse, 1e-8);
+ }
+ }
+
+ @Test
+ public void testExponentialMap()
+ {
+ Random random = new Random(1738L);
+
+ for (int i = 0; i < 1000; i++)
+ {
+ Quaternion a = EuclidCoreRandomTools.nextQuaternion(random);
+ Vector3DReadOnly rotationVector = EuclidCoreRandomTools.nextVector3D(random, 0.02);
+ DMatrixRMaj rotatedAVector = new DMatrixRMaj(4, 1);
+ DMatrixRMaj rotationQuat = OdometryTools.exponentialMap(rotationVector);
+ CommonOps_DDRM.mult(OdometryTools.lOperator(a), OdometryTools.exponentialMap(rotationVector), rotatedAVector);
+ Quaternion rotatedA = OdomTestTools.fromVector(rotatedAVector);
+
+ Quaternion rotationQuaternion = new Quaternion(rotationVector);
+
+ EuclidCoreTestTools.assertEquals(rotationQuaternion, OdomTestTools.fromVector(rotationQuat), 1e-5);
+ Quaternion rotatedAExpected = new Quaternion();
+ rotatedAExpected.multiply(a,rotationQuaternion);
+
+ EuclidCoreTestTools.assertEquals(rotatedAExpected, rotatedA, 1e-6);
+ }
+ }
+}
diff --git a/ihmc-state-estimation/src/test/java/us/ihmc/stateEstimation/humanoid/kinematicsBasedStateEstimation/odomEKF/ProcessModelTest.java b/ihmc-state-estimation/src/test/java/us/ihmc/stateEstimation/humanoid/kinematicsBasedStateEstimation/odomEKF/ProcessModelTest.java
new file mode 100644
index 000000000000..2e38e7920f91
--- /dev/null
+++ b/ihmc-state-estimation/src/test/java/us/ihmc/stateEstimation/humanoid/kinematicsBasedStateEstimation/odomEKF/ProcessModelTest.java
@@ -0,0 +1,348 @@
+package us.ihmc.stateEstimation.humanoid.kinematicsBasedStateEstimation.odomEKF;
+
+import org.ejml.EjmlUnitTests;
+import org.ejml.data.DMatrixRMaj;
+import org.ejml.dense.row.CommonOps_DDRM;
+import org.junit.jupiter.api.Test;
+import us.ihmc.euclid.geometry.Pose3D;
+import us.ihmc.euclid.referenceFrame.FrameVector3D;
+import us.ihmc.euclid.referenceFrame.ReferenceFrame;
+import us.ihmc.euclid.referenceFrame.interfaces.FrameVector3DReadOnly;
+import us.ihmc.euclid.tools.EuclidCoreRandomTools;
+import us.ihmc.euclid.transform.RigidBodyTransform;
+import us.ihmc.euclid.tuple3D.Vector3D;
+import us.ihmc.euclid.tuple3D.interfaces.Vector3DReadOnly;
+import us.ihmc.euclid.tuple4D.interfaces.QuaternionReadOnly;
+import us.ihmc.log.LogTools;
+import us.ihmc.matrixlib.MatrixTools;
+import us.ihmc.mecano.frames.MovingReferenceFrame;
+import us.ihmc.mecano.multiBodySystem.interfaces.RigidBodyBasics;
+import us.ihmc.mecano.spatial.Twist;
+import us.ihmc.sensorProcessing.stateEstimation.IMUSensorReadOnly;
+import us.ihmc.stateEstimation.humanoid.kinematicsBasedStateEstimation.IMUBiasProvider;
+import us.ihmc.yoVariables.registry.YoRegistry;
+
+import java.util.Random;
+
+public class ProcessModelTest
+{
+ private static final double gravityZ = 9.81;
+ private static final FrameVector3D gravity = new FrameVector3D(ReferenceFrame.getWorldFrame(), 0.0, 0.0, gravityZ);
+ private static final double estimatorDt = 0.01;
+
+ @Test
+ public void testUpdate()
+ {
+ int iters = 100;
+ Random random = new Random(1738L);
+
+ YoRegistry test = new YoRegistry(getClass().getSimpleName());
+ TestIMU baseIMU = new TestIMU("base");
+ TestIMU footIMU = new TestIMU("foot");
+
+ StateVariables processState = new StateVariables("process", footIMU.getMeasurementFrame(), test);
+ StateVariables predictedState = new StateVariables("predicted", footIMU.getMeasurementFrame(), test);
+ SensedVariables sensedVariables = new SensedVariables("foot", baseIMU, footIMU, new TestBiasProvider(), test);
+
+ ProcessModel processModel = new ProcessModel(processState, predictedState, () -> true, gravity, estimatorDt);
+ DMatrixRMaj jacobian = new DMatrixRMaj(OdometryIndexHelper.errorSizePerLink, OdometryIndexHelper.errorSizePerLink);
+
+ for (int iter = 0; iter < iters; iter++)
+ {
+ OdomTestTools.setRandomSensed(random, sensedVariables);
+ footIMU.linearAcceleration.set(sensedVariables.accelMeasurement);
+ footIMU.angularVelocity.set(sensedVariables.gyroMeasurement);
+ footIMU.pose.set(EuclidCoreRandomTools.nextPoint3D(random, 10.0), EuclidCoreRandomTools.nextOrientation3D(random));
+ footIMU.linearVelocity.set(EuclidCoreRandomTools.nextVector3D(random, 10.0));
+ footIMU.sensorFrame.update();
+ baseIMU.pose.set(EuclidCoreRandomTools.nextPoint3D(random, 10.0), EuclidCoreRandomTools.nextOrientation3D(random));
+ baseIMU.linearVelocity.set(EuclidCoreRandomTools.nextVector3D(random, 10.0));
+ baseIMU.sensorFrame.update();
+
+ OdomTestTools.setRandomState(random, sensedVariables, processState);
+ OdomTestTools.setRandomState(random, sensedVariables, predictedState);
+
+ processModel.update();
+
+ DMatrixRMaj translation = new DMatrixRMaj(3, 1);
+ DMatrixRMaj linearVelocity = new DMatrixRMaj(3, 1);
+ DMatrixRMaj gravityVector = new DMatrixRMaj(3, 1);
+ DMatrixRMaj rotationMatrix = new DMatrixRMaj(3, 3);
+ DMatrixRMaj acceleration = new DMatrixRMaj(3, 1);
+ DMatrixRMaj accelerationBias = new DMatrixRMaj(3, 1);
+ DMatrixRMaj accelerationUnbiased = new DMatrixRMaj(3, 1);
+ DMatrixRMaj accelerationUnbiasedInWorld = new DMatrixRMaj(3, 1);
+ DMatrixRMaj gyro = new DMatrixRMaj(3, 1);
+ DMatrixRMaj gyroBias = new DMatrixRMaj(3, 1);
+ DMatrixRMaj gyroUnbiased = new DMatrixRMaj(3, 1);
+
+ processState.translation.get(translation);
+ processState.linearVelocity.get(linearVelocity);
+ OdometryTools.toRotationMatrix(processState.orientation, rotationMatrix);
+ gravity.get(gravityVector);
+ sensedVariables.accelMeasurement.get(acceleration);
+ processState.accelBias.get(accelerationBias);
+ sensedVariables.gyroMeasurement.get(gyro);
+ processState.gyroBias.get(gyroBias);
+
+ DMatrixRMaj expectedPredictedTranslation = new DMatrixRMaj(3, 1);
+ DMatrixRMaj expectedPredictedLinearVelocity = new DMatrixRMaj(3, 1);
+ DMatrixRMaj expectedPredictedRotation = new DMatrixRMaj(4, 1);
+ DMatrixRMaj expectedPredictedAccelBias = new DMatrixRMaj(3, 1);
+ DMatrixRMaj expectedPredictedGyroBias = new DMatrixRMaj(3, 1);
+
+ // First term
+ CommonOps_DDRM.add(estimatorDt, linearVelocity, translation, expectedPredictedTranslation);
+
+ // Second term
+ if (OdometryKalmanFilter.includeBias)
+ CommonOps_DDRM.subtract(acceleration, accelerationBias, accelerationUnbiased);
+ else
+ accelerationUnbiased.set(acceleration);
+ CommonOps_DDRM.mult(rotationMatrix, accelerationUnbiased, accelerationUnbiasedInWorld);
+ CommonOps_DDRM.subtractEquals(accelerationUnbiasedInWorld, gravityVector);
+ CommonOps_DDRM.add(estimatorDt, accelerationUnbiasedInWorld, linearVelocity, expectedPredictedLinearVelocity);
+
+ // Third term
+ if (OdometryKalmanFilter.includeBias)
+ CommonOps_DDRM.subtract(gyro, gyroBias, gyroUnbiased);
+ else
+ gyroUnbiased.set(gyro);
+ CommonOps_DDRM.scale(estimatorDt, gyroUnbiased);
+ CommonOps_DDRM.mult(OdometryTools.lOperator(processState.orientation), OdometryTools.exponentialMap(gyroUnbiased), expectedPredictedRotation);
+
+ // Fourth term
+ expectedPredictedAccelBias.set(accelerationBias);
+
+ // Fifth term
+ expectedPredictedGyroBias.set(gyroBias);
+
+ DMatrixRMaj predictedTranslation = new DMatrixRMaj(3, 1);
+ DMatrixRMaj predictedLinearVelocity = new DMatrixRMaj(3, 1);
+ DMatrixRMaj predictedRotation = new DMatrixRMaj(4, 1);
+ DMatrixRMaj predictedAccelBias = new DMatrixRMaj(3, 1);
+ DMatrixRMaj predictedGyroBias = new DMatrixRMaj(3, 1);
+
+ predictedState.translation.get(predictedTranslation);
+ predictedState.linearVelocity.get(predictedLinearVelocity);
+ predictedRotation.set(OdomTestTools.toVector(predictedState.orientation));
+ predictedState.accelBias.get(predictedAccelBias);
+ predictedState.gyroBias.get(predictedGyroBias);
+
+ EjmlUnitTests.assertEquals(expectedPredictedTranslation, predictedTranslation, 1e-6);
+ EjmlUnitTests.assertEquals(expectedPredictedLinearVelocity, predictedLinearVelocity, 1e-6);
+ EjmlUnitTests.assertEquals(expectedPredictedRotation, predictedRotation, 1e-6);
+ EjmlUnitTests.assertEquals(expectedPredictedAccelBias, predictedAccelBias, 1e-6);
+ EjmlUnitTests.assertEquals(expectedPredictedGyroBias, predictedGyroBias, 1e-6);
+ }
+ }
+
+ @Test
+ public void testComputeProcessJacobian()
+ {
+ int iters = 100;
+ Random random = new Random(1738L);
+
+ YoRegistry test = new YoRegistry(getClass().getSimpleName());
+ TestIMU baseIMU = new TestIMU("base");
+ TestIMU footIMU = new TestIMU("foot");
+
+ StateVariables processState = new StateVariables("process", footIMU.getMeasurementFrame(), test);
+ StateVariables predictedState = new StateVariables("predicted", footIMU.getMeasurementFrame(), test);
+ SensedVariables sensedVariables = new SensedVariables("foot", baseIMU, footIMU, new TestBiasProvider(), test);
+
+ ProcessModel processModel = new ProcessModel(processState, predictedState, () -> true, gravity, estimatorDt);
+ DMatrixRMaj jacobian = new DMatrixRMaj(OdometryIndexHelper.errorSizePerLink, OdometryIndexHelper.errorSizePerLink);
+
+ for (int iter = 0; iter < iters; iter++)
+ {
+ jacobian.zero();
+ OdomTestTools.setRandomSensed(random, sensedVariables);
+ footIMU.linearAcceleration.set(sensedVariables.accelMeasurement);
+ footIMU.angularVelocity.set(sensedVariables.gyroMeasurement);
+ footIMU.pose.set(EuclidCoreRandomTools.nextPoint3D(random, 10.0), EuclidCoreRandomTools.nextOrientation3D(random));
+ footIMU.linearVelocity.set(EuclidCoreRandomTools.nextVector3D(random, 10.0));
+ footIMU.sensorFrame.update();
+ baseIMU.pose.set(EuclidCoreRandomTools.nextPoint3D(random, 10.0), EuclidCoreRandomTools.nextOrientation3D(random));
+ baseIMU.linearVelocity.set(EuclidCoreRandomTools.nextVector3D(random, 10.0));
+ baseIMU.sensorFrame.update();
+
+ OdomTestTools.setRandomState(random, sensedVariables, processState);
+ OdomTestTools.setRandomState(random, sensedVariables, predictedState);
+
+ processModel.computeProcessJacobian( 0, jacobian);
+
+ DMatrixRMaj jacobianExpected = new DMatrixRMaj(OdometryIndexHelper.errorSizePerLink, OdometryIndexHelper.errorSizePerLink);
+ // Row 1
+ CommonOps_DDRM.setIdentity(jacobianExpected);
+ for (int i = 0; i < 3; i++)
+ jacobianExpected.set(i, i + 3, estimatorDt);
+
+ // Row 2
+ DMatrixRMaj rotationMatrix = new DMatrixRMaj(3, 3);
+ DMatrixRMaj skewMatrix = new DMatrixRMaj(3, 3);
+ OdometryTools.toRotationMatrix(predictedState.orientation, rotationMatrix);
+ DMatrixRMaj acceleration = new DMatrixRMaj(3, 1);
+ DMatrixRMaj accelerationBias = new DMatrixRMaj(3, 1);
+ DMatrixRMaj accelerationUnbiased = new DMatrixRMaj(3, 1);
+ sensedVariables.accelMeasurement.get(acceleration);
+ predictedState.accelBias.get(accelerationBias);
+ if (OdometryKalmanFilter.includeBias)
+ CommonOps_DDRM.subtract(acceleration, accelerationBias, accelerationUnbiased);
+ else
+ accelerationUnbiased.set(acceleration);
+ CommonOps_DDRM.scale(estimatorDt, accelerationUnbiased);
+ OdometryTools.toSkewSymmetricMatrix(accelerationUnbiased.get(0), accelerationUnbiased.get(1), accelerationUnbiased.get(2), skewMatrix);
+ MatrixTools.multAddBlock(-1.0, rotationMatrix, skewMatrix, jacobianExpected, 3, 6);
+ if (OdometryKalmanFilter.includeBias)
+ MatrixTools.setMatrixBlock(jacobianExpected, 3, 9, rotationMatrix, 0, 0, 3, 3, -estimatorDt);
+
+ // Row 3
+ DMatrixRMaj velocity = new DMatrixRMaj(3, 1);
+ DMatrixRMaj velocityBias = new DMatrixRMaj(3, 1);
+ DMatrixRMaj velocityUnbiased = new DMatrixRMaj(3, 1);
+ sensedVariables.gyroMeasurement.get(velocity);
+ predictedState.gyroBias.get(velocityBias);
+ if (OdometryKalmanFilter.includeBias)
+ CommonOps_DDRM.subtract(velocity, velocityBias, velocityUnbiased);
+ else
+ velocityUnbiased.set(velocity);
+ CommonOps_DDRM.scale(estimatorDt, velocityUnbiased);
+ OdometryTools.toSkewSymmetricMatrix(velocityUnbiased.get(0), velocityUnbiased.get(1), velocityUnbiased.get(2), skewMatrix);
+ MatrixTools.addMatrixBlock(jacobianExpected, 6, 6, skewMatrix, 0, 0, 3, 3, -1.0);
+ if (OdometryKalmanFilter.includeBias)
+ {
+ for (int i = 0; i < 3; i++)
+ jacobianExpected.set(i + 6, i + 12, -estimatorDt);
+ }
+
+ // Row 4 and 5 are already set
+ EjmlUnitTests.assertEquals(jacobianExpected, jacobian, 1e-5);
+ }
+ }
+
+ static class TestBiasProvider implements IMUBiasProvider
+ {
+
+ @Override
+ public FrameVector3DReadOnly getAngularVelocityBiasInIMUFrame(IMUSensorReadOnly imu)
+ {
+ return new FrameVector3D(imu.getMeasurementFrame());
+ }
+
+ @Override
+ public FrameVector3DReadOnly getAngularVelocityBiasInWorldFrame(IMUSensorReadOnly imu)
+ {
+ return new FrameVector3D();
+ }
+
+ @Override
+ public FrameVector3DReadOnly getLinearAccelerationBiasInIMUFrame(IMUSensorReadOnly imu)
+ {
+ return new FrameVector3D(imu.getMeasurementFrame());
+ }
+
+ @Override
+ public FrameVector3DReadOnly getLinearAccelerationBiasInWorldFrame(IMUSensorReadOnly imu)
+ {
+ return new FrameVector3D();
+ }
+ }
+
+ static class TestIMU implements IMUSensorReadOnly
+ {
+ private String name;
+ public final MovingReferenceFrame sensorFrame;
+ public final Pose3D pose = new Pose3D();
+ public final Vector3D angularVelocity = new Vector3D();
+ public final Vector3D linearVelocity = new Vector3D();
+ public final Vector3D linearAcceleration = new Vector3D();
+
+ public TestIMU(String name)
+ {
+ this.name = name;
+
+ sensorFrame = new MovingReferenceFrame(name, ReferenceFrame.getWorldFrame())
+ {
+ @Override
+ protected void updateTwistRelativeToParent(Twist twist)
+ {
+ twist.setToZero();
+ pose.transform(linearVelocity, twist.getLinearPart());
+ }
+
+ @Override
+ protected void updateTransformToParent(RigidBodyTransform rigidBodyTransform)
+ {
+ rigidBodyTransform.set(pose);
+ }
+ };
+ }
+
+ @Override
+ public String getSensorName()
+ {
+ return name;
+ }
+
+ @Override
+ public MovingReferenceFrame getMeasurementFrame()
+ {
+ return sensorFrame;
+ }
+
+ @Override
+ public RigidBodyBasics getMeasurementLink()
+ {
+ return null;
+ }
+
+ @Override
+ public QuaternionReadOnly getOrientationMeasurement()
+ {
+ return pose.getOrientation();
+ }
+
+ @Override
+ public Vector3DReadOnly getAngularVelocityMeasurement()
+ {
+ return angularVelocity;
+ }
+
+ @Override
+ public Vector3DReadOnly getLinearAccelerationMeasurement()
+ {
+ return linearAcceleration;
+ }
+
+ @Override
+ public void getOrientationNoiseCovariance(DMatrixRMaj noiseCovarianceToPack)
+ {
+
+ }
+
+ @Override
+ public void getAngularVelocityNoiseCovariance(DMatrixRMaj noiseCovarianceToPack)
+ {
+
+ }
+
+ @Override
+ public void getAngularVelocityBiasProcessNoiseCovariance(DMatrixRMaj biasProcessNoiseCovarianceToPack)
+ {
+
+ }
+
+ @Override
+ public void getLinearAccelerationNoiseCovariance(DMatrixRMaj noiseCovarianceToPack)
+ {
+
+ }
+
+ @Override
+ public void getLinearAccelerationBiasProcessNoiseCovariance(DMatrixRMaj biasProcessNoiseCovarianceToPack)
+ {
+
+ }
+ }
+}