public class

PointerKalmanFilter

extends java.lang.Object

 java.lang.Object

↳androidx.input.motionprediction.kalman.PointerKalmanFilter

Gradle dependencies

compile group: 'androidx.input', name: 'input-motionprediction', version: '1.0.0-beta04'

  • groupId: androidx.input
  • artifactId: input-motionprediction
  • version: 1.0.0-beta04

Artifact androidx.input:input-motionprediction:1.0.0-beta04 it located at Google repository (https://maven.google.com/)

Overview

Class that independently applies the Kalman Filter to each axis of the pen.

Summary

Constructors
publicPointerKalmanFilter(double sigmaProcess, double sigmaMeasurement)

Methods
public DVector2getAcceleration()

public DVector2getJank()

public intgetNumIterations()

public DVector2getPosition()

public doublegetPressure()

public doublegetPressureChange()

public DVector2getVelocity()

public voidreset()

Reset filter into a neutral state.

public voidupdate(float x, float y, float pressure)

Update internal model of pen with new measurement.

from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

Constructors

public PointerKalmanFilter(double sigmaProcess, double sigmaMeasurement)

Parameters:

sigmaProcess: lower value = more filtering
sigmaMeasurement: higher value = more filtering

Methods

public void reset()

Reset filter into a neutral state.

public void update(float x, float y, float pressure)

Update internal model of pen with new measurement. The state of the model can be obtained by the getPosition, getVelocity, etc methods.

public DVector2 getPosition()

public DVector2 getVelocity()

public DVector2 getAcceleration()

public DVector2 getJank()

public double getPressure()

public double getPressureChange()

public int getNumIterations()

Source

/*
 * Copyright 2022 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package androidx.input.motionprediction.kalman;

import static androidx.annotation.RestrictTo.Scope.LIBRARY;

import androidx.annotation.NonNull;
import androidx.annotation.RestrictTo;
import androidx.input.motionprediction.kalman.matrix.DVector2;
import androidx.input.motionprediction.kalman.matrix.Matrix;

/**
 * Class that independently applies the Kalman Filter to each axis of the pen.
 *
 */
@RestrictTo(LIBRARY)
public class PointerKalmanFilter {
    private final KalmanFilter mXKalman;
    private final KalmanFilter mYKalman;
    private final KalmanFilter mPKalman;

    private final DVector2 mPosition = new DVector2();
    private final DVector2 mVelocity = new DVector2();
    private final DVector2 mAcceleration = new DVector2();
    private final DVector2 mJank = new DVector2();
    private double mPressure = 0;
    private double mPressureChange = 0;

    private double mSigmaProcess;
    private double mSigmaMeasurement;

    private int mNumIterations = 0;

    private final Matrix mNewX = new Matrix(1, 1);
    private final Matrix mNewY = new Matrix(1, 1);
    private final Matrix mNewP = new Matrix(1, 1);

    /**
     * @param sigmaProcess lower value = more filtering
     * @param sigmaMeasurement higher value = more filtering
     */
    public PointerKalmanFilter(double sigmaProcess, double sigmaMeasurement) {
        mSigmaProcess = sigmaProcess;
        mSigmaMeasurement = sigmaMeasurement;
        mXKalman = createAxisKalmanFilter();
        mYKalman = createAxisKalmanFilter();
        mPKalman = createAxisKalmanFilter();
    }

    /** Reset filter into a neutral state. */
    public void reset() {
        mXKalman.reset();
        mYKalman.reset();
        mPKalman.reset();
        mNumIterations = 0;
    }

    /**
     * Update internal model of pen with new measurement. The state of the model can be obtained by
     * the getPosition, getVelocity, etc methods.
     */
    public void update(float x, float y, float pressure) {
        if (mNumIterations == 0) {
            mXKalman.x.put(0, 0, x);
            mYKalman.x.put(0, 0, y);
            mPKalman.x.put(0, 0, pressure);
        } else {
            mNewX.put(0, 0, x);
            mXKalman.predict();
            mXKalman.update(mNewX);

            mNewY.put(0, 0, y);
            mYKalman.predict();
            mYKalman.update(mNewY);

            mNewP.put(0, 0, pressure);
            mPKalman.predict();
            mPKalman.update(mNewP);
        }
        mNumIterations += 1;

        mPosition.a1 = mXKalman.x.get(0, 0);
        mPosition.a2 = mYKalman.x.get(0, 0);
        mVelocity.a1 = mXKalman.x.get(1, 0);
        mVelocity.a2 = mYKalman.x.get(1, 0);
        mAcceleration.a1 = mXKalman.x.get(2, 0);
        mAcceleration.a2 = mYKalman.x.get(2, 0);
        mJank.a1 = mXKalman.x.get(3, 0);
        mJank.a2 = mYKalman.x.get(3, 0);
        mPressure = mPKalman.x.get(0, 0);
        mPressureChange = mPKalman.x.get(1, 0);
    }

    public @NonNull DVector2 getPosition() {
        return mPosition;
    }

    public @NonNull DVector2 getVelocity() {
        return mVelocity;
    }

    public @NonNull DVector2 getAcceleration() {
        return mAcceleration;
    }

    public @NonNull DVector2 getJank() {
        return mJank;
    }

    public double getPressure() {
        return mPressure;
    }

    public double getPressureChange() {
        return mPressureChange;
    }

    public int getNumIterations() {
        return mNumIterations;
    }

    private KalmanFilter createAxisKalmanFilter() {
        // We tune the filter with a normalized dt=1, then apply the actual report rate during
        // prediction.
        final double dt = 1.0;

        final KalmanFilter kalman = new KalmanFilter(4, 1);

        // State transition matrix is derived from basic physics:
        // new_x = x + v * dt + 1/2 * a * dt^2 + 1/6 * jank * dt^3
        // new_v = v + a * dt + 1/2 * jank * dt^2
        // ...
        kalman.F = new Matrix(4,
                new double[]{
                        1.0, dt, 0.5 * dt * dt, 0.16 * dt * dt * dt,
                        0.0, 1.0, dt, 0.5 * dt * dt,
                        0.0, 0.0, 1.0, dt,
                        0, 0, 0, 1.0
                });

        // We model the system noise as a noisy force on the pen.
        // The matrix G describes the impact of that noise on each state.
        final Matrix g = new Matrix(1, new double[] {0.16 * dt * dt * dt, 0.5 * dt * dt, dt, 1});
        g.dotTranspose(g, kalman.Q);
        kalman.Q.scale(mSigmaProcess);

        // Measurements only impact the location
        kalman.H = new Matrix(4, new double[] {1.0, 0.0, 0.0, 0.0});

        // Measurement noise is a 1-D normal distribution
        kalman.R.put(0, 0, mSigmaMeasurement);

        return kalman;
    }
}