Scientific/Signal API
Class: LinearFilter
New in SDK 3.0
A LinearFilter represents any Finite Impulse Response (FIR) or Infinite
Impulse Response (IIR) linear filter. It provides a convenient mechanism
for keeping track of the entire configuration and state for such a
filter, but is just a convenience: It contains no hidden state, and could
be reconstructed every update
from its constituent fields, if desired.
The filter is represented in transfer function form, as arrays of
numerator and denominator coefficients. The filter is realized in direct
form II, requiring only a single array of delay elements with length
max(b.length, a.length) - 1
.
For the purposes of local design and debugging, the following should be equivalent:
JavaScript
import { LinearFilter } from "scientific/signal";
var filt = new LinearFilter(b, a, zi);
var y = filt.update(x);
var zf = filt.z;
Python
y, zf = scipy.signal.lfilter(b, a, x, zi)
Example
As a simple example, a butterworth bandpass filter can be constructed as follows:
import { LinearFilter } from "scientific/signal";
Generate some sample data
Typically this would come from an accelerometer, gyro, or other sensor.
const numSamples = 4096;
const sampleFreq = 44100;
const X = new Float32Array(numSamples);
for (let i = 0; i < X.length; i++) {
const t = i / sampleFreq;
// Form a signal containing a 50 Hz sinusoid of amplitude 0.7
var S = 0.7 * Math.sin(2 * Math.PI * 50 * t);
// ...and a 120 Hz sinusoid of amplitude 1.
S += Math.sin(2 * Math.PI * 120 * t);
// Corrupt the signal with zero-mean white noise with a variance of 4.
X[i] = S + 2 * Math.random()
}
Construct a filter
Typically this would be done at application startup, and the same filter(s) reused for the duration of application execution.
const filter50hz = new LinearFilter(
new Float32Array([2.88394643e-09, 0.0, -8.65183928e-09, 0.0, 8.65183928e-09, 0.0, -2.88394643e-09]),
new Float32Array([1., -5.99415495, 14.97093756, -19.94220015, 14.94252458, -5.97142422, 0.99431717]));
b
and a
filter coefficients were calculated using scipy.signal
Python
nyquist = 0.5 * sampleFreq
b, a = scipy.signal.butter(3, [40 / nyquist, 60 / nyquist], btype='band')
Update the filter
Typically this would be done each time new sensor samples come in:
const y = filter50hz.update(X);
y
contains the frequency components of X
between 40hz and 60hz, with
the 120Hz signal and noise filtered out.
See the example Scientific app on Github
Properties
readonly a
Float32Array
a
represents a copy of the coefficients of the polynomial
describing the denominator in the form:
a[0] + a[1]z^-1 + ... + a[N]z^-N
If a[0]
is not 1
, both a
and b
are normalized by a[0]
.
readonly b
Float32Array
b
represents a copy of the coefficients of the polynomial
describing the numerator in the form:
b[0] + b[1]z^-1 + ... + b[M]z^-M
readonly z
Float32Array
z
represents a copy of the internal state of the filter delay
elements. If not provided, defaults to an array of zeros.
Methods
update()
update(x: Float32Array)
Returns: Float32Array
update
runs each of x
's data points through the filter, updating
the internal state z
at each iteration, and returning the filter
output as a Float32Array
.