
kalman-filter
Kalman Filter in JavaScript (for both node.js and the browser)
This library implements following features:
- N-dimensional Kalman Filter (for multivariate Gaussian)
- Forward Kalman Filter (Online)
- Forward-Backward Smoothing Kalman Filter
- Split Prediction/Correction steps
- Extended Kalman Filter
- Correlation Matrix
Demos/Examples
Open an issue to add more examples in this section explaining how you use this library !
Installation
Npm
npm install kalman-filter
const {KalmanFilter} = require('kalman-filter');
Browser usage
Download the file kalman-filter.min.js
from Releases page
Add it to your project like :
<script src="dist/kalman-filter.min.js"></script>
<script>
var {KalmanFilter} = kalmanFilter;
</script>
Simple Example
1D Smoothing Usage
const {KalmanFilter} = require('kalman-filter');
const observations = [0, 0.1, 0.5, 0.2, 3, 4, 2, 1, 2, 3, 5, 6];
const kFilter = new KalmanFilter();
const res = kFilter.filterAll(observations)
Result is :

2D Smoothing Usage
const {KalmanFilter} = require('kalman-filter');
const observations = [[0, 1], [0.1, 0.5], [0.2, 3], [4, 2], [1, 2]];
const kFilter = new KalmanFilter({observation: 2});
const res = kFilter.filterAll(observations)
2D Smoothing with constant-speed model
const {KalmanFilter} = require('kalman-filter');
const observations = [[0, 1], [0.1, 0.5], [0.2, 3], [4, 2], [1, 2]];
const kFilter = new KalmanFilter({
observation: 2,
dynamic: 'constant-speed'
});
const res = kFilter.filterAll(observations)
How to instantiate your kalman filter
Advanced usage
This library gives you the ability to fully configure your kalman-filter.
For advanced usage, here is the correspondance table with the matrix name of the wikipedia article
$F_k$, the state-transition model | dynamic.transition |
$H_k$, the observation model | observation.stateProjection |
$Q_k$, the covariance of the process noise | dynamic.covariance |
$R_k$, the covariance of the observation noise | observation.covariance |
$B_k u_k$, the control-input model multiplied by the control vector | dynamic.constant |
$\mathbf{P}_{0\mid 0}$ | dynamic.init.covariance |
$\mathbf{x}_{0\mid 0}$ | dynamic.init.mean |
Configure the dynamic with dynamic.name
dynamic.name
is a shortcut to give you access to preconfigured dynamic models, you can also register your own shortcust see Register models shortcuts
Available default models as :
- constant-position
- constant-speed
- constant-acceleration
This will automatically configure the dynamic.transition
matrix.
constant-position
\begin{align}
State :& \begin{bmatrix} x_t \end{bmatrix}\\
Transition Equation :& x_t \sim x_{t-1} \\
dynamic.transition :& \begin{bmatrix} 1 \end{bmatrix}
\end{align}
constant-speed
\begin{align}
State :& \begin{bmatrix} x_t \\ speed_t \end{bmatrix} \\
Transition Equation :&
\begin{split}
x_t &\sim x_{t-1} + speed_{t-1},\\
speed_t &\sim speed_{t-1}
\end{split} \\
dynamic.transition :& \begin{bmatrix} 1 & 1 \\ 0 & 1 \end{bmatrix}
\end{align}
constant-acceleration
\begin{align}
State :& \begin{bmatrix} x_t \\ speed_t \\ acc_t \end{bmatrix} \\
Transition Equation :&
\begin{split}
x_t &\sim x_{t-1} + speed_{t-1} \\
speed_t &\sim speed_{t-1} + acc_{t-1} \\
acc_t &\sim acc_{t-1}
\end{split} \\
dynamic.transition :& \begin{bmatrix} 1 & 1 & 0 \\ 0 & 1 & 1 \\ 0 & 0 & 1\end{bmatrix}
\end{align}
'constant-position' on 2D data
This is the default behavior
const {KalmanFilter} = require('kalman-filter');
const kFilter = new KalmanFilter({
observation: {
sensorDimension: 2,
name: 'sensor'
},
dynamic: {
name: 'constant-position',
covariance: [3, 4]
}
});
'constant-speed' on 3D data
const {KalmanFilter} = require('kalman-filter');
const kFilter = new KalmanFilter({
observation: {
sensorDimension: 3,
name: 'sensor'
},
dynamic: {
name: 'constant-speed',
timeStep: 0.1,
covariance: [3, 3, 3, 4, 4, 4]
}
});
'constant-acceleration' on 2D data
const {KalmanFilter} = require('kalman-filter');
const kFilter = new KalmanFilter({
observation: {
sensorDimension: 2,
name: 'sensor'
},
dynamic: {
name: 'constant-acceleration',
timeStep: 0.1,
covariance: [3, 3, 4, 4, 5, 5]
}
});
Instanciation of a generic linear model
This is an example of how to build a constant speed model, in 3D without dynamic.name
, using detailed api.
dynamic.dimension
is the size of the state
dynamic.transition
is the state transition model that defines the dynamic of the system
dynamic.covariance
is the covariance matrix of the transition model
dynamic.init
is used for initial state (we generally set a big covariance on it)
const {KalmanFilter} = require('kalman-filter');
const timeStep = 0.1;
const huge = 1e8;
const kFilter = new KalmanFilter({
observation: {
dimension: 3
},
dynamic: {
init: {
mean: [[500], [500], [500], [0], [0], [0]],
covariance: [
[huge, 0, 0, 0, 0, 0],
[0, huge, 0, 0, 0, 0],
[0, 0, huge, 0, 0, 0],
[0, 0, 0, huge, 0, 0],
[0, 0, 0, 0, huge, 0],
[0, 0, 0, 0, 0, huge],
],
},
dimension: 6,
transition: [
[1, 0, 0, timeStep, 0, 0],
[0, 1, 0, 0, timeStep, 0],
[0, 0, 1, 0, 0, timeStep],
[0, 0, 0, 1, 0, 0],
[0, 0, 0, 0, 1, 0],
[0, 0, 0, 0, 0, 1]
],
covariance: [1, 1, 1, 0.01, 0.01, 0.01]
}
});
Configure the observation
Using sensor
observation
The observation is made from 2 different sensors with identical properties (i.e. same covariances) , the input measure will be [<sensor0-dim0>, <sensor0-dim1>, <sensor1-dim0>, <sensor1-dim1>]
.
const {KalmanFilter} = require('kalman-filter');
const timeStep = 0.1;
const kFilter = new KalmanFilter({
observation: {
sensorDimension: 2,
nSensors: 2,
sensorCovariance: [3, 4],
name: 'sensor'
},
dynamic: {
name: 'constant-speed',
covariance: [3, 3, 4, 4]
}
});
Custom Observation matrix
The observation is made from 2 different sensors with different properties (i.e. different covariances), the input measure will be [<sensor0-dim0>, <sensor0-dim1>, <sensor1-dim0>, <sensor1-dim1>]
.
This can be achived manually by using the detailed API :
observation.dimension
is the size of the observation
observation.stateProjection
is the matrix that transforms state into observation, also called observation model
observation.covariance
is the covariance matrix of the observation model
const {KalmanFilter} = require('kalman-filter');
const timeStep = 0.1;
const kFilter = new KalmanFilter({
observation: {
dimension: 4,
stateProjection: [
[1, 0, 0, 0],
[0, 1, 0, 0],
[1, 0, 0, 0],
[0, 1, 0, 0]
],
covariance: [3, 4, 0.3, 0.4]
},
dynamic: {
name: 'constant-speed',
covariance: [3, 3, 4, 4]
}
});
Play with Kalman Filter
In order to use the Kalman-Filter with a dynamic or observation model which is not strictly a General linear model, it is possible to use function
in following parameters :
observation.stateProjection
observation.covariance
dynamic.transition
dynamic.covariance
dynamic.constant
In this situation this function
will return the value of the matrix at each step of the kalman-filter.
In this example, we create a constant-speed filter with non-uniform intervals;
const {KalmanFilter} = require('kalman-filter');
const intervals = [1,1,1,1,2,1,1,1];
const kFilter = new KalmanFilter({
observation: {
dimension: 2,
stateProjection: function(opts){
return [
[1, 0, 0, 0],
[0, 1, 0, 0]
]
},
covariance: function(opts){
return [
[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1]
]
}
},
dynamic: {
dimension: 4,
transition: function(opts){
const dT = intervals[opts.index];
if(typeof(dT) !== 'number' || isNaN(dT) || dT <= 0){
throw(new Error('dT should be positive number'))
}
return [
[1, 0, dT, 0],
[0, 1, 0, dT]
[0, 0, 1, 0]
[0, 0, 0, 1]
]
},
covariance: function(opts){
const dT = intervals[opts.index];
if(typeof(dT) !== 'number' || isNaN(dT) || dT <= 0){
throw(new Error('dT should be positive number'))
}
return [
[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 1*dT, 0],
[0, 0, 0, 1*dT]
]
}
}
});
Extended
If you want to implement an extended kalman filter
You will need to put your non-linear functions in the following parameters
observation.fn
dynamic.fn
See an example in Sinusoidale Extended Kalman-Filter
Using Control model
If you want to add a constant parameter in the dynamic model (also called control input
), you can use dynamic.constant
function
See an example code in demo/bouncing-ball
or the result in Bouncing Ball example
Use your kalman filter
Simple Batch usage (run it once for the whole dataset)
const observations = [[0, 2], [0.1, 4], [0.5, 9], [0.2, 12]];
const results = kFilter.filterAll(observations);
Online filter
When using online usage (only the forward step), the output of the filter
method is an instance of the "State" class.
let previousCorrected = null;
const results = [];
observations.forEach(observation => {
previousCorrected = kFilter.filter({previousCorrected, observation});
results.push(previousCorrected.mean);
});
Predict/Correct detailed usage (run it online)
If you want to use KalmanFilter in more advanced usage, you might want to dissociate the predict
and the correct
functions
let previousCorrected = null;
const results = [];
observations.forEach(observation => {
const predicted = kFilter.predict({
previousCorrected
});
const correctedState = kFilter.correct({
predicted,
observation
});
results.push(correctedState.mean);
previousCorrected = correctedState
});
console.log(results);
Batch Forward - Backward smoothing usage
The Forward - Backward process
const results = kFilter.filterAll({observations, passMode: 'forward-backward'});
Register models shortcuts
To get more information on how to build a dynamic model, check in the code lib/dynamic/
(or lib/observation
for observation models).
If you feel your model can be used by other, do not hesitate to create a Pull Request.
const {registerDynamic, KalmanFilter, registerObservation} = require('kalman-filter');
registerObservation('custom-sensor', function(opts1){
return {
dimension,
stateProjection,
covariance
}
})
registerDynamic('custom-dynamic', function(opts2, observation){
return {
dimension,
transition,
covariance
}
})
const kFilter = new KalmanFilter({
observation: {
name: 'custom-sensor',
},
dynamic: {
name: 'custom-dynamic',
}
});
Set your model parameters from the ground truths state values
In order to find the proper values for covariance matrix, we use following approach :
const {getCovariance, KalmanFilter} = require('kalman-filter');
const groundTruthStates = [
[[0, 1.1], [1.1, 1], [2.1, 0.9], [3, 1], [4, 1.2]],
[[8, 1.1], [9.1, 1], [10.1, 0.9], [11, 1], [12, 1.2]]
]
const measures = [
[[0.1], [1.3], [2.4], [2.6], [3.8]],
[[8.1], [9.3], [10.4], [10.6], [11.8]]
];
const kFilter = new KalmanFilter({
observation: {
name: 'sensor',
sensorDimension: 1
},
dynamic: {
name: 'constant-speed'
}
})
const dynamicCovariance = getCovariance({
measures: groundTruthStates.map(ex =>
return ex.slice(1)
).reduce((a,b) => a.concat(b)),
averages: groundTruthStates.map(ex =>
return ex.slice(1).map((_, index) => {
return kFilter.predict({previousCorrected: ex[index - 1]}).mean;
})
).reduce((a,b) => a.concat(b))
});
const observationCovariance = getCovariance({
measures: measures.reduce((a,b) => a.concat(b)),
averages: groundTruthStates.map((a) => a[0]).reduce((a,b) => a.concat(b))
});
How to measure how good does a specific model fits with data
There are different ways to measure the performance of a model against some measures :
Model fits with a specific measurements
We use Mahalanobis distance
const observations = [[0, 2], [0.1, 4], [0.5, 9], [0.2, 12]];
let previousCorrected = null;
const results = [];
observations.forEach(observation => {
const predicted = kFilter.predict({
previousCorrected
});
const dist = predicted.mahalanobis(observation)
previousCorrected = kFilter.correct({
predicted,
observation
});
distances.push(dist);
});
const distance = distances.reduce((d1, d2) => d1 + d2, 0);
How precise is this Model
We compare the model with random generated numbers sequence.
const h = require('hasard')
const observationHasard = h.array({value: h.number({type: 'normal'}), size: 2})
const observations = observationHasard.run(200);
let previousCorrected = null;
const results = [];
observations.forEach(observation => {
const predicted = kFilter.predict({
previousCorrected
});
const dist = predicted.mahalanobis(measure)
previousCorrected = kFilter.correct({
predicted,
observation
});
distances.push(dist);
});
const distance = distances.reduce((d1, d2) => d1 + d2, 0);
Credits
Thanks to Adrien Pellissier for his hard work on this library.
Similar Project
For a simple 1D Kalman filter in javascript see https://github.com/wouterbulten/kalmanjs