dspx 1.3.0 → 1.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bindings.d.ts +115 -1
- package/dist/bindings.d.ts.map +1 -1
- package/dist/bindings.js +149 -0
- package/dist/bindings.js.map +1 -1
- package/dist/types.d.ts +47 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/prebuilds/win32-x64/dspx.node +0 -0
- package/src/native/DspPipeline.cc +116 -61
- package/src/native/adapters/AmplifyStage.h +148 -0
- package/src/native/adapters/FilterBankStage.cc +61 -84
- package/src/native/adapters/FilterBankStage.h +4 -7
- package/src/native/adapters/KalmanFilterStage.h +366 -0
- package/src/native/adapters/SquareStage.h +78 -0
- package/src/native/core/IirFilter.cc +10 -8
- package/src/native/utils/SimdOps.h +67 -0
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
#include "FilterBankStage.h"
|
|
2
|
-
#include <iostream>
|
|
3
2
|
|
|
4
3
|
namespace dsp::adapters
|
|
5
4
|
{
|
|
@@ -18,31 +17,6 @@ namespace dsp::adapters
|
|
|
18
17
|
|
|
19
18
|
initializeFilters();
|
|
20
19
|
}
|
|
21
|
-
FilterBankStage::~FilterBankStage()
|
|
22
|
-
{
|
|
23
|
-
try
|
|
24
|
-
{
|
|
25
|
-
// Explicitly clear filters first to ensure IirFilter destructors run
|
|
26
|
-
// before scratch buffers are destroyed
|
|
27
|
-
for (size_t ch = 0; ch < m_filters.size(); ++ch)
|
|
28
|
-
{
|
|
29
|
-
m_filters[ch].clear(); // Destroys all unique_ptrs in this channel
|
|
30
|
-
}
|
|
31
|
-
m_filters.clear(); // Clear outer vector
|
|
32
|
-
|
|
33
|
-
// Clear scratch buffers
|
|
34
|
-
m_planarInput.clear();
|
|
35
|
-
m_planarOutput.clear();
|
|
36
|
-
}
|
|
37
|
-
catch (const std::exception &e)
|
|
38
|
-
{
|
|
39
|
-
std::cerr << "[FilterBankStage] ERROR in destructor: " << e.what() << std::endl;
|
|
40
|
-
}
|
|
41
|
-
catch (...)
|
|
42
|
-
{
|
|
43
|
-
std::cerr << "[FilterBankStage] UNKNOWN ERROR in destructor" << std::endl;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
20
|
|
|
47
21
|
void FilterBankStage::processResizing(const float *inputBuffer, size_t inputSize,
|
|
48
22
|
float *outputBuffer, size_t &outputSize,
|
|
@@ -81,8 +55,9 @@ namespace dsp::adapters
|
|
|
81
55
|
for (size_t band = 0; band < numBands; ++band)
|
|
82
56
|
{
|
|
83
57
|
int outChIndex = (ch * static_cast<int>(numBands)) + static_cast<int>(band);
|
|
58
|
+
int filterIndex = outChIndex; // Same indexing for flat array
|
|
84
59
|
|
|
85
|
-
m_filters[
|
|
60
|
+
m_filters[filterIndex]->process(
|
|
86
61
|
m_planarInput[ch].data(),
|
|
87
62
|
m_planarOutput[outChIndex].data(),
|
|
88
63
|
samplesPerChannel);
|
|
@@ -100,18 +75,24 @@ namespace dsp::adapters
|
|
|
100
75
|
|
|
101
76
|
void FilterBankStage::initializeFilters()
|
|
102
77
|
{
|
|
103
|
-
|
|
78
|
+
size_t numBands = m_definitions.size();
|
|
79
|
+
size_t totalFilters = m_numInputChannels * numBands;
|
|
80
|
+
|
|
81
|
+
m_filters.clear();
|
|
82
|
+
m_filters.reserve(totalFilters);
|
|
83
|
+
|
|
104
84
|
for (int ch = 0; ch < m_numInputChannels; ++ch)
|
|
105
85
|
{
|
|
106
|
-
|
|
107
|
-
for (const auto &def : m_definitions)
|
|
86
|
+
for (size_t band = 0; band < numBands; ++band)
|
|
108
87
|
{
|
|
88
|
+
const auto &def = m_definitions[band];
|
|
89
|
+
|
|
109
90
|
// Convert double coefficients to float for IirFilter
|
|
110
91
|
std::vector<float> b_float(def.b.begin(), def.b.end());
|
|
111
92
|
std::vector<float> a_float(def.a.begin(), def.a.end());
|
|
112
93
|
|
|
113
94
|
// Create stateful IIR filter for each band
|
|
114
|
-
m_filters
|
|
95
|
+
m_filters.push_back(
|
|
115
96
|
std::make_unique<dsp::core::IirFilter<float>>(b_float, a_float, true));
|
|
116
97
|
}
|
|
117
98
|
}
|
|
@@ -148,12 +129,9 @@ namespace dsp::adapters
|
|
|
148
129
|
|
|
149
130
|
void FilterBankStage::reset()
|
|
150
131
|
{
|
|
151
|
-
for (auto &
|
|
132
|
+
for (auto &filter : m_filters)
|
|
152
133
|
{
|
|
153
|
-
|
|
154
|
-
{
|
|
155
|
-
filter->reset();
|
|
156
|
-
}
|
|
134
|
+
filter->reset();
|
|
157
135
|
}
|
|
158
136
|
}
|
|
159
137
|
|
|
@@ -165,13 +143,16 @@ namespace dsp::adapters
|
|
|
165
143
|
state.Set("numBands", m_definitions.size());
|
|
166
144
|
|
|
167
145
|
// Serialize filter states as nested arrays: channels[bands[state]]
|
|
168
|
-
|
|
169
|
-
|
|
146
|
+
size_t numBands = m_definitions.size();
|
|
147
|
+
Napi::Array channels = Napi::Array::New(env, m_numInputChannels);
|
|
148
|
+
|
|
149
|
+
for (int ch = 0; ch < m_numInputChannels; ++ch)
|
|
170
150
|
{
|
|
171
|
-
Napi::Array bands = Napi::Array::New(env,
|
|
172
|
-
for (size_t band = 0; band <
|
|
151
|
+
Napi::Array bands = Napi::Array::New(env, numBands);
|
|
152
|
+
for (size_t band = 0; band < numBands; ++band)
|
|
173
153
|
{
|
|
174
|
-
|
|
154
|
+
int filterIndex = ch * numBands + band;
|
|
155
|
+
const auto &filter = m_filters[filterIndex];
|
|
175
156
|
|
|
176
157
|
Napi::Object filterState = Napi::Object::New(env);
|
|
177
158
|
|
|
@@ -210,22 +191,24 @@ namespace dsp::adapters
|
|
|
210
191
|
throw std::runtime_error("FilterBank: Missing filterStates in serialized data");
|
|
211
192
|
}
|
|
212
193
|
|
|
194
|
+
size_t numBands = m_definitions.size();
|
|
213
195
|
Napi::Array channels = state.Get("filterStates").As<Napi::Array>();
|
|
214
|
-
if (channels.Length() !=
|
|
196
|
+
if (channels.Length() != static_cast<uint32_t>(m_numInputChannels))
|
|
215
197
|
{
|
|
216
198
|
throw std::runtime_error("FilterBank: Channel count mismatch in deserialization");
|
|
217
199
|
}
|
|
218
200
|
|
|
219
|
-
for (
|
|
201
|
+
for (int ch = 0; ch < m_numInputChannels; ++ch)
|
|
220
202
|
{
|
|
221
203
|
Napi::Array bands = channels.Get(ch).As<Napi::Array>();
|
|
222
|
-
if (bands.Length() !=
|
|
204
|
+
if (bands.Length() != static_cast<uint32_t>(numBands))
|
|
223
205
|
{
|
|
224
206
|
throw std::runtime_error("FilterBank: Band count mismatch in deserialization");
|
|
225
207
|
}
|
|
226
208
|
|
|
227
|
-
for (size_t band = 0; band <
|
|
209
|
+
for (size_t band = 0; band < numBands; ++band)
|
|
228
210
|
{
|
|
211
|
+
int filterIndex = ch * numBands + band;
|
|
229
212
|
Napi::Object filterState = bands.Get(band).As<Napi::Object>();
|
|
230
213
|
|
|
231
214
|
// Deserialize input history
|
|
@@ -253,7 +236,7 @@ namespace dsp::adapters
|
|
|
253
236
|
}
|
|
254
237
|
|
|
255
238
|
// Restore filter state
|
|
256
|
-
m_filters[
|
|
239
|
+
m_filters[filterIndex]->setState(inputHistory, outputHistory);
|
|
257
240
|
}
|
|
258
241
|
}
|
|
259
242
|
}
|
|
@@ -265,26 +248,23 @@ namespace dsp::adapters
|
|
|
265
248
|
serializer.writeInt32(static_cast<int32_t>(m_definitions.size()));
|
|
266
249
|
|
|
267
250
|
// Serialize each filter's state
|
|
268
|
-
for (const auto &
|
|
251
|
+
for (const auto &filter : m_filters)
|
|
269
252
|
{
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
// Get filter state (input and output history)
|
|
273
|
-
auto [inputHistory, outputHistory] = filter->getState();
|
|
253
|
+
// Get filter state (input and output history)
|
|
254
|
+
auto [inputHistory, outputHistory] = filter->getState();
|
|
274
255
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
256
|
+
// Write input history
|
|
257
|
+
serializer.writeInt32(static_cast<int32_t>(inputHistory.size()));
|
|
258
|
+
for (float val : inputHistory)
|
|
259
|
+
{
|
|
260
|
+
serializer.writeFloat(val);
|
|
261
|
+
}
|
|
281
262
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
}
|
|
263
|
+
// Write output history
|
|
264
|
+
serializer.writeInt32(static_cast<int32_t>(outputHistory.size()));
|
|
265
|
+
for (float val : outputHistory)
|
|
266
|
+
{
|
|
267
|
+
serializer.writeFloat(val);
|
|
288
268
|
}
|
|
289
269
|
}
|
|
290
270
|
}
|
|
@@ -305,31 +285,28 @@ namespace dsp::adapters
|
|
|
305
285
|
}
|
|
306
286
|
|
|
307
287
|
// Restore each filter's state
|
|
308
|
-
for (auto &
|
|
288
|
+
for (auto &filter : m_filters)
|
|
309
289
|
{
|
|
310
|
-
|
|
290
|
+
// Read input history
|
|
291
|
+
int32_t inputSize = deserializer.readInt32();
|
|
292
|
+
std::vector<float> inputHistory;
|
|
293
|
+
inputHistory.reserve(inputSize);
|
|
294
|
+
for (int32_t i = 0; i < inputSize; ++i)
|
|
311
295
|
{
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
std::vector<float> inputHistory;
|
|
315
|
-
inputHistory.reserve(inputSize);
|
|
316
|
-
for (int32_t i = 0; i < inputSize; ++i)
|
|
317
|
-
{
|
|
318
|
-
inputHistory.push_back(deserializer.readFloat());
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
// Read output history
|
|
322
|
-
int32_t outputSize = deserializer.readInt32();
|
|
323
|
-
std::vector<float> outputHistory;
|
|
324
|
-
outputHistory.reserve(outputSize);
|
|
325
|
-
for (int32_t i = 0; i < outputSize; ++i)
|
|
326
|
-
{
|
|
327
|
-
outputHistory.push_back(deserializer.readFloat());
|
|
328
|
-
}
|
|
296
|
+
inputHistory.push_back(deserializer.readFloat());
|
|
297
|
+
}
|
|
329
298
|
|
|
330
|
-
|
|
331
|
-
|
|
299
|
+
// Read output history
|
|
300
|
+
int32_t outputSize = deserializer.readInt32();
|
|
301
|
+
std::vector<float> outputHistory;
|
|
302
|
+
outputHistory.reserve(outputSize);
|
|
303
|
+
for (int32_t i = 0; i < outputSize; ++i)
|
|
304
|
+
{
|
|
305
|
+
outputHistory.push_back(deserializer.readFloat());
|
|
332
306
|
}
|
|
307
|
+
|
|
308
|
+
// Restore filter state
|
|
309
|
+
filter->setState(inputHistory, outputHistory);
|
|
333
310
|
}
|
|
334
311
|
}
|
|
335
312
|
|
|
@@ -50,11 +50,7 @@ namespace dsp::adapters
|
|
|
50
50
|
* @param numInputChannels Number of input channels
|
|
51
51
|
*/
|
|
52
52
|
FilterBankStage(const std::vector<FilterDefinition> &bandDefinitions, int numInputChannels);
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* @brief Destructor - ensures proper cleanup of filters and scratch buffers
|
|
56
|
-
*/
|
|
57
|
-
virtual ~FilterBankStage();
|
|
53
|
+
~FilterBankStage() = default;
|
|
58
54
|
|
|
59
55
|
const char *getType() const override { return "filterBank"; }
|
|
60
56
|
|
|
@@ -156,8 +152,9 @@ namespace dsp::adapters
|
|
|
156
152
|
int m_numInputChannels;
|
|
157
153
|
std::vector<FilterDefinition> m_definitions;
|
|
158
154
|
|
|
159
|
-
//
|
|
160
|
-
|
|
155
|
+
// Flattened filter array: filters[channelIndex * numBands + bandIndex]
|
|
156
|
+
// Using flat array instead of 2D vector to avoid nested vector cleanup issues
|
|
157
|
+
std::vector<std::unique_ptr<dsp::core::IirFilter<float>>> m_filters;
|
|
161
158
|
|
|
162
159
|
// Optimization: Persistent scratch buffers in planar layout
|
|
163
160
|
// m_planarInput[channel][sample] - de-interleaved input
|
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include "../IDspStage.h"
|
|
4
|
+
#include "../utils/SimdOps.h"
|
|
5
|
+
#include <Eigen/Dense>
|
|
6
|
+
#include <vector>
|
|
7
|
+
#include <stdexcept>
|
|
8
|
+
#include <cmath>
|
|
9
|
+
#include <string>
|
|
10
|
+
#include <algorithm>
|
|
11
|
+
|
|
12
|
+
namespace dsp::adapters
|
|
13
|
+
{
|
|
14
|
+
/**
|
|
15
|
+
* @brief Kalman Filter Stage for tracking interleaved multi-dimensional data
|
|
16
|
+
*
|
|
17
|
+
* Implements a discrete Kalman filter for position/velocity tracking with support
|
|
18
|
+
* for interleaved data streams (e.g., [x, y, x, y] or [lat, lon, alt, lat, lon, alt]).
|
|
19
|
+
*
|
|
20
|
+
* Perfect for:
|
|
21
|
+
* - GPS tracking (lat/lon pairs)
|
|
22
|
+
* - 3D position tracking (x/y/z coordinates)
|
|
23
|
+
* - Sensor fusion (accelerometer, gyroscope)
|
|
24
|
+
* - Any multi-dimensional time series with noise
|
|
25
|
+
*
|
|
26
|
+
* State vector: [position, velocity] for each dimension
|
|
27
|
+
* Measurement: position only
|
|
28
|
+
*
|
|
29
|
+
* Uses existing SIMD operations for efficient vector math on interleaved data.
|
|
30
|
+
*/
|
|
31
|
+
class KalmanFilterStage : public IDspStage
|
|
32
|
+
{
|
|
33
|
+
public:
|
|
34
|
+
/**
|
|
35
|
+
* @brief Constructs a Kalman Filter Stage
|
|
36
|
+
* @param dimensions Number of dimensions (2 for lat/lon, 3 for x/y/z, etc.)
|
|
37
|
+
* @param process_noise Process noise covariance (Q) - models uncertainty in motion
|
|
38
|
+
* @param measurement_noise Measurement noise covariance (R) - models sensor noise
|
|
39
|
+
* @param initial_error Initial state estimation error covariance (P)
|
|
40
|
+
*/
|
|
41
|
+
explicit KalmanFilterStage(
|
|
42
|
+
int dimensions = 2,
|
|
43
|
+
float process_noise = 1e-5f,
|
|
44
|
+
float measurement_noise = 1e-2f,
|
|
45
|
+
float initial_error = 1.0f)
|
|
46
|
+
: m_dimensions(dimensions),
|
|
47
|
+
m_process_noise(process_noise),
|
|
48
|
+
m_measurement_noise(measurement_noise),
|
|
49
|
+
m_initial_error(initial_error),
|
|
50
|
+
m_initialized(false),
|
|
51
|
+
m_first_measurement_received(false)
|
|
52
|
+
{
|
|
53
|
+
if (dimensions < 1 || dimensions > 10)
|
|
54
|
+
{
|
|
55
|
+
throw std::invalid_argument("KalmanFilter: dimensions must be between 1 and 10");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// State size: 2 * dimensions (position + velocity for each)
|
|
59
|
+
m_state_size = 2 * dimensions;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const char *getType() const override
|
|
63
|
+
{
|
|
64
|
+
return "kalmanFilter";
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
void process(float *buffer, size_t numSamples, int numChannels, const float *timestamps = nullptr) override
|
|
68
|
+
{
|
|
69
|
+
if (numChannels != m_dimensions)
|
|
70
|
+
{
|
|
71
|
+
throw std::invalid_argument("KalmanFilter: number of channels must match dimensions");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Initialize filter on first call
|
|
75
|
+
if (!m_initialized)
|
|
76
|
+
{
|
|
77
|
+
initialize();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Process interleaved samples: [x0, y0, x1, y1, x2, y2, ...]
|
|
81
|
+
// Each group of m_dimensions values is one measurement vector
|
|
82
|
+
const size_t samplesPerChannel = numSamples / numChannels;
|
|
83
|
+
|
|
84
|
+
for (size_t i = 0; i < samplesPerChannel; i++)
|
|
85
|
+
{
|
|
86
|
+
// Extract measurement vector from interleaved buffer
|
|
87
|
+
Eigen::VectorXf measurement(m_dimensions);
|
|
88
|
+
for (int d = 0; d < m_dimensions; d++)
|
|
89
|
+
{
|
|
90
|
+
measurement(d) = buffer[i * m_dimensions + d];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Initialize state from first measurement (cold start) - only once ever
|
|
94
|
+
if (!m_first_measurement_received)
|
|
95
|
+
{
|
|
96
|
+
for (int d = 0; d < m_dimensions; d++)
|
|
97
|
+
{
|
|
98
|
+
m_state(d) = measurement(d); // Initialize position from first measurement
|
|
99
|
+
}
|
|
100
|
+
m_first_measurement_received = true;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Predict step
|
|
104
|
+
predict(timestamps ? timestamps[i] : 1.0f / 360.0f); // Default 360 Hz
|
|
105
|
+
|
|
106
|
+
// Update step with measurement
|
|
107
|
+
update(measurement);
|
|
108
|
+
|
|
109
|
+
// Write filtered position back to buffer
|
|
110
|
+
for (int d = 0; d < m_dimensions; d++)
|
|
111
|
+
{
|
|
112
|
+
buffer[i * m_dimensions + d] = m_state(d); // Position component
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
Napi::Object serializeState(Napi::Env env) const override
|
|
118
|
+
{
|
|
119
|
+
Napi::Object state = Napi::Object::New(env);
|
|
120
|
+
|
|
121
|
+
state.Set("dimensions", Napi::Number::New(env, m_dimensions));
|
|
122
|
+
state.Set("processNoise", Napi::Number::New(env, m_process_noise));
|
|
123
|
+
state.Set("measurementNoise", Napi::Number::New(env, m_measurement_noise));
|
|
124
|
+
state.Set("initialized", Napi::Boolean::New(env, m_initialized));
|
|
125
|
+
state.Set("firstMeasurementReceived", Napi::Boolean::New(env, m_first_measurement_received));
|
|
126
|
+
|
|
127
|
+
if (m_initialized)
|
|
128
|
+
{
|
|
129
|
+
// Serialize state vector [pos0, pos1, ..., vel0, vel1, ...]
|
|
130
|
+
Napi::Array stateArray = Napi::Array::New(env, m_state.size());
|
|
131
|
+
for (int i = 0; i < m_state.size(); i++)
|
|
132
|
+
{
|
|
133
|
+
stateArray.Set(static_cast<uint32_t>(i), Napi::Number::New(env, m_state(i)));
|
|
134
|
+
}
|
|
135
|
+
state.Set("state", stateArray);
|
|
136
|
+
|
|
137
|
+
// Serialize covariance matrix P (flattened row-major)
|
|
138
|
+
Napi::Array covArray = Napi::Array::New(env, m_P.size());
|
|
139
|
+
int idx = 0;
|
|
140
|
+
for (int i = 0; i < m_P.rows(); i++)
|
|
141
|
+
{
|
|
142
|
+
for (int j = 0; j < m_P.cols(); j++)
|
|
143
|
+
{
|
|
144
|
+
covArray.Set(static_cast<uint32_t>(idx++), Napi::Number::New(env, m_P(i, j)));
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
state.Set("covariance", covArray);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return state;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
void deserializeState(const Napi::Object &state) override
|
|
154
|
+
{
|
|
155
|
+
if (state.Has("dimensions"))
|
|
156
|
+
{
|
|
157
|
+
m_dimensions = state.Get("dimensions").As<Napi::Number>().Int32Value();
|
|
158
|
+
m_state_size = 2 * m_dimensions;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (state.Has("processNoise"))
|
|
162
|
+
{
|
|
163
|
+
m_process_noise = state.Get("processNoise").As<Napi::Number>().FloatValue();
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (state.Has("measurementNoise"))
|
|
167
|
+
{
|
|
168
|
+
m_measurement_noise = state.Get("measurementNoise").As<Napi::Number>().FloatValue();
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (state.Has("initialized"))
|
|
172
|
+
{
|
|
173
|
+
m_initialized = state.Get("initialized").As<Napi::Boolean>().Value();
|
|
174
|
+
}
|
|
175
|
+
if (state.Has("firstMeasurementReceived"))
|
|
176
|
+
{
|
|
177
|
+
m_first_measurement_received = state.Get("firstMeasurementReceived").As<Napi::Boolean>().Value();
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (m_initialized && state.Has("state"))
|
|
181
|
+
{
|
|
182
|
+
Napi::Array stateArray = state.Get("state").As<Napi::Array>();
|
|
183
|
+
m_state.resize(stateArray.Length());
|
|
184
|
+
for (uint32_t i = 0; i < stateArray.Length(); i++)
|
|
185
|
+
{
|
|
186
|
+
m_state(i) = stateArray.Get(i).As<Napi::Number>().FloatValue();
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (state.Has("covariance"))
|
|
190
|
+
{
|
|
191
|
+
Napi::Array covArray = state.Get("covariance").As<Napi::Array>();
|
|
192
|
+
m_P.resize(m_state_size, m_state_size);
|
|
193
|
+
int idx = 0;
|
|
194
|
+
for (int i = 0; i < m_state_size; i++)
|
|
195
|
+
{
|
|
196
|
+
for (int j = 0; j < m_state_size; j++)
|
|
197
|
+
{
|
|
198
|
+
m_P(i, j) = covArray.Get(idx++).As<Napi::Number>().FloatValue();
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Reinitialize Q and R matrices (not serialized, derived from parameters)
|
|
204
|
+
m_Q = Eigen::MatrixXf::Identity(m_state_size, m_state_size) * m_process_noise;
|
|
205
|
+
m_R = Eigen::MatrixXf::Identity(m_dimensions, m_dimensions) * m_measurement_noise;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
void reset() override
|
|
210
|
+
{
|
|
211
|
+
// Reset to uninitialized state - will reinitialize on next process() call
|
|
212
|
+
m_initialized = false;
|
|
213
|
+
m_first_measurement_received = false;
|
|
214
|
+
m_state = Eigen::VectorXf();
|
|
215
|
+
m_P = Eigen::MatrixXf();
|
|
216
|
+
m_Q = Eigen::MatrixXf();
|
|
217
|
+
m_R = Eigen::MatrixXf();
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
void serializeToon(toon::Serializer &serializer) const override
|
|
221
|
+
{
|
|
222
|
+
// Serialize configuration
|
|
223
|
+
serializer.writeInt32(m_dimensions);
|
|
224
|
+
serializer.writeFloat(m_process_noise);
|
|
225
|
+
serializer.writeFloat(m_measurement_noise);
|
|
226
|
+
serializer.writeFloat(m_initial_error);
|
|
227
|
+
serializer.writeBool(m_initialized);
|
|
228
|
+
serializer.writeBool(m_first_measurement_received);
|
|
229
|
+
|
|
230
|
+
if (m_initialized)
|
|
231
|
+
{
|
|
232
|
+
// Serialize state vector
|
|
233
|
+
serializer.writeInt32(m_state.size());
|
|
234
|
+
for (int i = 0; i < m_state.size(); i++)
|
|
235
|
+
{
|
|
236
|
+
serializer.writeFloat(m_state(i));
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Serialize covariance matrix P
|
|
240
|
+
serializer.writeInt32(m_P.rows());
|
|
241
|
+
serializer.writeInt32(m_P.cols());
|
|
242
|
+
for (int i = 0; i < m_P.rows(); i++)
|
|
243
|
+
{
|
|
244
|
+
for (int j = 0; j < m_P.cols(); j++)
|
|
245
|
+
{
|
|
246
|
+
serializer.writeFloat(m_P(i, j));
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
void deserializeToon(toon::Deserializer &deserializer) override
|
|
253
|
+
{
|
|
254
|
+
// Deserialize configuration
|
|
255
|
+
m_dimensions = deserializer.readInt32();
|
|
256
|
+
m_process_noise = deserializer.readFloat();
|
|
257
|
+
m_measurement_noise = deserializer.readFloat();
|
|
258
|
+
m_initial_error = deserializer.readFloat();
|
|
259
|
+
m_initialized = deserializer.readBool();
|
|
260
|
+
m_first_measurement_received = deserializer.readBool();
|
|
261
|
+
|
|
262
|
+
m_state_size = 2 * m_dimensions;
|
|
263
|
+
|
|
264
|
+
if (m_initialized)
|
|
265
|
+
{
|
|
266
|
+
// Deserialize state vector
|
|
267
|
+
int stateSize = deserializer.readInt32();
|
|
268
|
+
m_state.resize(stateSize);
|
|
269
|
+
for (int i = 0; i < stateSize; i++)
|
|
270
|
+
{
|
|
271
|
+
m_state(i) = deserializer.readFloat();
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Deserialize covariance matrix P
|
|
275
|
+
int rows = deserializer.readInt32();
|
|
276
|
+
int cols = deserializer.readInt32();
|
|
277
|
+
m_P.resize(rows, cols);
|
|
278
|
+
for (int i = 0; i < rows; i++)
|
|
279
|
+
{
|
|
280
|
+
for (int j = 0; j < cols; j++)
|
|
281
|
+
{
|
|
282
|
+
m_P(i, j) = deserializer.readFloat();
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Reinitialize Q and R matrices based on configuration
|
|
287
|
+
m_Q = Eigen::MatrixXf::Identity(m_state_size, m_state_size) * m_process_noise;
|
|
288
|
+
m_R = Eigen::MatrixXf::Identity(m_dimensions, m_dimensions) * m_measurement_noise;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
private:
|
|
293
|
+
void initialize()
|
|
294
|
+
{
|
|
295
|
+
// State: [pos_1, pos_2, ..., pos_n, vel_1, vel_2, ..., vel_n]
|
|
296
|
+
m_state = Eigen::VectorXf::Zero(m_state_size);
|
|
297
|
+
|
|
298
|
+
// Covariance matrix P (state_size x state_size)
|
|
299
|
+
m_P = Eigen::MatrixXf::Identity(m_state_size, m_state_size) * m_initial_error;
|
|
300
|
+
|
|
301
|
+
// Process noise covariance Q (state_size x state_size)
|
|
302
|
+
m_Q = Eigen::MatrixXf::Identity(m_state_size, m_state_size) * m_process_noise;
|
|
303
|
+
|
|
304
|
+
// Measurement noise covariance R (dimensions x dimensions)
|
|
305
|
+
m_R = Eigen::MatrixXf::Identity(m_dimensions, m_dimensions) * m_measurement_noise;
|
|
306
|
+
|
|
307
|
+
m_initialized = true;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
void predict(float dt)
|
|
311
|
+
{
|
|
312
|
+
// State transition matrix F for constant velocity model
|
|
313
|
+
// F = [I dt*I]
|
|
314
|
+
// [0 I ]
|
|
315
|
+
Eigen::MatrixXf F = Eigen::MatrixXf::Identity(m_state_size, m_state_size);
|
|
316
|
+
for (int i = 0; i < m_dimensions; i++)
|
|
317
|
+
{
|
|
318
|
+
F(i, m_dimensions + i) = dt;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Predict state: x_pred = F * x
|
|
322
|
+
m_state = F * m_state;
|
|
323
|
+
|
|
324
|
+
// Predict covariance: P_pred = F * P * F^T + Q
|
|
325
|
+
m_P = F * m_P * F.transpose() + m_Q;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
void update(const Eigen::VectorXf &measurement)
|
|
329
|
+
{
|
|
330
|
+
// Measurement matrix H: [I 0] (only observe position, not velocity)
|
|
331
|
+
Eigen::MatrixXf H = Eigen::MatrixXf::Zero(m_dimensions, m_state_size);
|
|
332
|
+
H.block(0, 0, m_dimensions, m_dimensions) = Eigen::MatrixXf::Identity(m_dimensions, m_dimensions);
|
|
333
|
+
|
|
334
|
+
// Innovation: y = z - H * x_pred
|
|
335
|
+
Eigen::VectorXf innovation = measurement - H * m_state;
|
|
336
|
+
|
|
337
|
+
// Innovation covariance: S = H * P * H^T + R
|
|
338
|
+
Eigen::MatrixXf S = H * m_P * H.transpose() + m_R;
|
|
339
|
+
|
|
340
|
+
// Kalman gain: K = P * H^T * S^(-1)
|
|
341
|
+
// Use Eigen's inverse() which handles small matrices efficiently
|
|
342
|
+
Eigen::MatrixXf K = m_P * H.transpose() * S.inverse();
|
|
343
|
+
|
|
344
|
+
// Update state: x = x_pred + K * innovation
|
|
345
|
+
m_state = m_state + K * innovation;
|
|
346
|
+
|
|
347
|
+
// Update covariance: P = (I - K * H) * P
|
|
348
|
+
Eigen::MatrixXf I = Eigen::MatrixXf::Identity(m_state_size, m_state_size);
|
|
349
|
+
m_P = (I - K * H) * m_P;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
int m_dimensions; // Number of dimensions (2 for lat/lon, 3 for xyz, etc.)
|
|
353
|
+
int m_state_size; // 2 * dimensions (position + velocity)
|
|
354
|
+
float m_process_noise; // Q - process noise covariance
|
|
355
|
+
float m_measurement_noise; // R - measurement noise covariance
|
|
356
|
+
float m_initial_error; // P0 - initial error covariance
|
|
357
|
+
bool m_initialized;
|
|
358
|
+
bool m_first_measurement_received; // Track if we've received first measurement for cold start
|
|
359
|
+
|
|
360
|
+
Eigen::VectorXf m_state; // State vector [pos, vel]
|
|
361
|
+
Eigen::MatrixXf m_P; // Error covariance matrix
|
|
362
|
+
Eigen::MatrixXf m_Q; // Process noise covariance matrix
|
|
363
|
+
Eigen::MatrixXf m_R; // Measurement noise covariance matrix
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
} // namespace dsp::adapters
|