dspx 0.1.1-alpha.0
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/.github/workflows/ci.yml +185 -0
- package/.vscode/c_cpp_properties.json +17 -0
- package/.vscode/settings.json +68 -0
- package/.vscode/tasks.json +28 -0
- package/DISCLAIMER.md +32 -0
- package/LICENSE +21 -0
- package/README.md +1803 -0
- package/ROADMAP.md +192 -0
- package/TECHNICAL_DEBT.md +165 -0
- package/binding.gyp +65 -0
- package/docs/ADVANCED_LOGGER_FEATURES.md +598 -0
- package/docs/AUTHENTICATION_SECURITY.md +396 -0
- package/docs/BACKEND_IMPROVEMENTS.md +399 -0
- package/docs/CHEBYSHEV_BIQUAD_EQ_IMPLEMENTATION.md +405 -0
- package/docs/FFT_IMPLEMENTATION.md +490 -0
- package/docs/FFT_IMPROVEMENTS_SUMMARY.md +387 -0
- package/docs/FFT_USER_GUIDE.md +494 -0
- package/docs/FILTERS_IMPLEMENTATION.md +260 -0
- package/docs/FILTER_API_GUIDE.md +418 -0
- package/docs/FIR_SIMD_OPTIMIZATION.md +175 -0
- package/docs/LOGGER_API_REFERENCE.md +350 -0
- package/docs/NOTCH_FILTER_QUICK_REF.md +121 -0
- package/docs/PHASE2_TESTS_AND_NOTCH_FILTER.md +341 -0
- package/docs/PHASES_5_7_SUMMARY.md +403 -0
- package/docs/PIPELINE_FILTER_INTEGRATION.md +446 -0
- package/docs/SIMD_OPTIMIZATIONS.md +211 -0
- package/docs/TEST_MIGRATION_SUMMARY.md +173 -0
- package/docs/TIMESERIES_IMPLEMENTATION_SUMMARY.md +322 -0
- package/docs/TIMESERIES_QUICK_REF.md +85 -0
- package/docs/advanced.md +559 -0
- package/docs/time-series-guide.md +617 -0
- package/docs/time-series-migration.md +376 -0
- package/jest.config.js +37 -0
- package/package.json +42 -0
- package/prebuilds/linux-x64/dsp-ts-redis.node +0 -0
- package/prebuilds/win32-x64/dsp-ts-redis.node +0 -0
- package/scripts/test.js +24 -0
- package/src/build/dsp-ts-redis.node +0 -0
- package/src/native/DspPipeline.cc +675 -0
- package/src/native/DspPipeline.h +44 -0
- package/src/native/FftBindings.cc +817 -0
- package/src/native/FilterBindings.cc +1001 -0
- package/src/native/IDspStage.h +53 -0
- package/src/native/adapters/InterpolatorStage.h +201 -0
- package/src/native/adapters/MeanAbsoluteValueStage.h +289 -0
- package/src/native/adapters/MovingAverageStage.h +306 -0
- package/src/native/adapters/RectifyStage.h +88 -0
- package/src/native/adapters/ResamplerStage.h +238 -0
- package/src/native/adapters/RmsStage.h +299 -0
- package/src/native/adapters/SscStage.h +121 -0
- package/src/native/adapters/VarianceStage.h +307 -0
- package/src/native/adapters/WampStage.h +114 -0
- package/src/native/adapters/WaveformLengthStage.h +115 -0
- package/src/native/adapters/ZScoreNormalizeStage.h +326 -0
- package/src/native/core/FftEngine.cc +441 -0
- package/src/native/core/FftEngine.h +224 -0
- package/src/native/core/FirFilter.cc +324 -0
- package/src/native/core/FirFilter.h +149 -0
- package/src/native/core/IirFilter.cc +576 -0
- package/src/native/core/IirFilter.h +210 -0
- package/src/native/core/MovingAbsoluteValueFilter.cc +17 -0
- package/src/native/core/MovingAbsoluteValueFilter.h +135 -0
- package/src/native/core/MovingAverageFilter.cc +18 -0
- package/src/native/core/MovingAverageFilter.h +135 -0
- package/src/native/core/MovingFftFilter.cc +291 -0
- package/src/native/core/MovingFftFilter.h +203 -0
- package/src/native/core/MovingVarianceFilter.cc +194 -0
- package/src/native/core/MovingVarianceFilter.h +114 -0
- package/src/native/core/MovingZScoreFilter.cc +215 -0
- package/src/native/core/MovingZScoreFilter.h +113 -0
- package/src/native/core/Policies.h +352 -0
- package/src/native/core/RmsFilter.cc +18 -0
- package/src/native/core/RmsFilter.h +131 -0
- package/src/native/core/SscFilter.cc +16 -0
- package/src/native/core/SscFilter.h +137 -0
- package/src/native/core/WampFilter.cc +16 -0
- package/src/native/core/WampFilter.h +101 -0
- package/src/native/core/WaveformLengthFilter.cc +17 -0
- package/src/native/core/WaveformLengthFilter.h +98 -0
- package/src/native/utils/CircularBufferArray.cc +336 -0
- package/src/native/utils/CircularBufferArray.h +62 -0
- package/src/native/utils/CircularBufferVector.cc +145 -0
- package/src/native/utils/CircularBufferVector.h +45 -0
- package/src/native/utils/NapiUtils.cc +53 -0
- package/src/native/utils/NapiUtils.h +21 -0
- package/src/native/utils/SimdOps.h +870 -0
- package/src/native/utils/SlidingWindowFilter.cc +239 -0
- package/src/native/utils/SlidingWindowFilter.h +159 -0
- package/src/native/utils/TimeSeriesBuffer.cc +205 -0
- package/src/native/utils/TimeSeriesBuffer.h +140 -0
- package/src/ts/CircularLogBuffer.ts +87 -0
- package/src/ts/DriftDetector.ts +331 -0
- package/src/ts/TopicRouter.ts +428 -0
- package/src/ts/__tests__/AdvancedDsp.test.ts +585 -0
- package/src/ts/__tests__/AuthAndEdgeCases.test.ts +241 -0
- package/src/ts/__tests__/Chaining.test.ts +387 -0
- package/src/ts/__tests__/ChebyshevBiquad.test.ts +229 -0
- package/src/ts/__tests__/CircularLogBuffer.test.ts +158 -0
- package/src/ts/__tests__/DriftDetector.test.ts +389 -0
- package/src/ts/__tests__/Fft.test.ts +484 -0
- package/src/ts/__tests__/ListState.test.ts +153 -0
- package/src/ts/__tests__/Logger.test.ts +208 -0
- package/src/ts/__tests__/LoggerAdvanced.test.ts +319 -0
- package/src/ts/__tests__/LoggerMinor.test.ts +247 -0
- package/src/ts/__tests__/MeanAbsoluteValue.test.ts +398 -0
- package/src/ts/__tests__/MovingAverage.test.ts +322 -0
- package/src/ts/__tests__/RMS.test.ts +315 -0
- package/src/ts/__tests__/Rectify.test.ts +272 -0
- package/src/ts/__tests__/Redis.test.ts +456 -0
- package/src/ts/__tests__/SlopeSignChange.test.ts +166 -0
- package/src/ts/__tests__/Tap.test.ts +164 -0
- package/src/ts/__tests__/TimeBasedExpiration.test.ts +124 -0
- package/src/ts/__tests__/TimeBasedRmsAndMav.test.ts +231 -0
- package/src/ts/__tests__/TimeBasedVarianceAndZScore.test.ts +284 -0
- package/src/ts/__tests__/TimeSeries.test.ts +254 -0
- package/src/ts/__tests__/TopicRouter.test.ts +332 -0
- package/src/ts/__tests__/TopicRouterAdvanced.test.ts +483 -0
- package/src/ts/__tests__/TopicRouterPriority.test.ts +487 -0
- package/src/ts/__tests__/Variance.test.ts +509 -0
- package/src/ts/__tests__/WaveformLength.test.ts +147 -0
- package/src/ts/__tests__/WillisonAmplitude.test.ts +197 -0
- package/src/ts/__tests__/ZScoreNormalize.test.ts +459 -0
- package/src/ts/advanced-dsp.ts +566 -0
- package/src/ts/backends.ts +1137 -0
- package/src/ts/bindings.ts +1225 -0
- package/src/ts/easter-egg.ts +42 -0
- package/src/ts/examples/MeanAbsoluteValue/test-state.ts +99 -0
- package/src/ts/examples/MeanAbsoluteValue/test-streaming.ts +269 -0
- package/src/ts/examples/MovingAverage/test-state.ts +85 -0
- package/src/ts/examples/MovingAverage/test-streaming.ts +188 -0
- package/src/ts/examples/RMS/test-state.ts +97 -0
- package/src/ts/examples/RMS/test-streaming.ts +253 -0
- package/src/ts/examples/Rectify/test-state.ts +107 -0
- package/src/ts/examples/Rectify/test-streaming.ts +242 -0
- package/src/ts/examples/Variance/test-state.ts +195 -0
- package/src/ts/examples/Variance/test-streaming.ts +260 -0
- package/src/ts/examples/ZScoreNormalize/test-state.ts +277 -0
- package/src/ts/examples/ZScoreNormalize/test-streaming.ts +306 -0
- package/src/ts/examples/advanced-dsp-examples.ts +397 -0
- package/src/ts/examples/callbacks/advanced-router-features.ts +326 -0
- package/src/ts/examples/callbacks/benchmark-circular-buffer.ts +109 -0
- package/src/ts/examples/callbacks/monitoring-example.ts +265 -0
- package/src/ts/examples/callbacks/pipeline-callbacks-example.ts +137 -0
- package/src/ts/examples/callbacks/pooled-callbacks-example.ts +274 -0
- package/src/ts/examples/callbacks/priority-routing-example.ts +277 -0
- package/src/ts/examples/callbacks/production-topic-router.ts +214 -0
- package/src/ts/examples/callbacks/topic-based-logging.ts +161 -0
- package/src/ts/examples/chaining/test-chaining-redis.ts +113 -0
- package/src/ts/examples/chaining/test-chaining.ts +52 -0
- package/src/ts/examples/emg-features-example.ts +284 -0
- package/src/ts/examples/fft-example.ts +309 -0
- package/src/ts/examples/fft-examples.ts +349 -0
- package/src/ts/examples/filter-examples.ts +320 -0
- package/src/ts/examples/list-state-example.ts +131 -0
- package/src/ts/examples/logger-example.ts +91 -0
- package/src/ts/examples/notch-filter-examples.ts +243 -0
- package/src/ts/examples/phase5/drift-detection-example.ts +290 -0
- package/src/ts/examples/phase6-7/production-observability.ts +476 -0
- package/src/ts/examples/phase6-7/redis-timeseries-integration.ts +446 -0
- package/src/ts/examples/redis/redis-example.ts +202 -0
- package/src/ts/examples/redis-example.ts +202 -0
- package/src/ts/examples/simd-benchmark.ts +126 -0
- package/src/ts/examples/tap-debugging.ts +230 -0
- package/src/ts/examples/timeseries/comparison-example.ts +290 -0
- package/src/ts/examples/timeseries/iot-sensor-example.ts +143 -0
- package/src/ts/examples/timeseries/redis-streaming-example.ts +233 -0
- package/src/ts/examples/waveform-length-example.ts +139 -0
- package/src/ts/fft.ts +722 -0
- package/src/ts/filters.ts +1078 -0
- package/src/ts/index.ts +120 -0
- package/src/ts/types.ts +589 -0
- package/tsconfig.json +15 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include "../utils/CircularBufferArray.h"
|
|
4
|
+
#include <utility>
|
|
5
|
+
#include <vector>
|
|
6
|
+
#include <cmath>
|
|
7
|
+
#include <stdexcept> // for noexcept
|
|
8
|
+
#include <algorithm> // for std::max
|
|
9
|
+
|
|
10
|
+
namespace dsp::core
|
|
11
|
+
{
|
|
12
|
+
/**
|
|
13
|
+
* @brief Implements an efficient Moving Variance filter.
|
|
14
|
+
*
|
|
15
|
+
* This class uses a circular buffer to store the last 'N' samples
|
|
16
|
+
* and maintains a running sum AND a running sum-of-squares
|
|
17
|
+
* for O(1) variance calculation using the formula:
|
|
18
|
+
* Variance = (Mean of Squares) - (Mean)^2
|
|
19
|
+
*
|
|
20
|
+
* @tparam T The numeric type of the samples (e.g., float, double).
|
|
21
|
+
*/
|
|
22
|
+
template <typename T>
|
|
23
|
+
class MovingVarianceFilter
|
|
24
|
+
{
|
|
25
|
+
public:
|
|
26
|
+
/**
|
|
27
|
+
* @brief Constructs a new Moving Variance Filter.
|
|
28
|
+
* @param window_size The number of samples to average over (N).
|
|
29
|
+
*/
|
|
30
|
+
explicit MovingVarianceFilter(size_t window_size);
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @brief Constructs a new time-aware Moving Variance Filter.
|
|
34
|
+
* @param window_size The buffer capacity in samples.
|
|
35
|
+
* @param window_duration_ms The time window duration in milliseconds.
|
|
36
|
+
*/
|
|
37
|
+
explicit MovingVarianceFilter(size_t window_size, double window_duration_ms);
|
|
38
|
+
|
|
39
|
+
// Delete copy constructor and copy assignment
|
|
40
|
+
MovingVarianceFilter(const MovingVarianceFilter &) = delete;
|
|
41
|
+
MovingVarianceFilter &operator=(const MovingVarianceFilter &) = delete;
|
|
42
|
+
|
|
43
|
+
// Enable move semantics
|
|
44
|
+
MovingVarianceFilter(MovingVarianceFilter &&) noexcept = default;
|
|
45
|
+
MovingVarianceFilter &operator=(MovingVarianceFilter &&) noexcept = default;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @brief Adds a new sample to the filter.
|
|
49
|
+
*
|
|
50
|
+
* This updates the running sums and the internal circular buffer.
|
|
51
|
+
* The new variance value is calculated and returned.
|
|
52
|
+
*
|
|
53
|
+
* @param newValue The new sample value to add.
|
|
54
|
+
* @return T The new moving variance.
|
|
55
|
+
*/
|
|
56
|
+
T addSample(T newValue);
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* @brief Adds a new sample with timestamp (time-aware mode).
|
|
60
|
+
*
|
|
61
|
+
* Expires old samples, rebuilds statistics, then adds new sample.
|
|
62
|
+
*
|
|
63
|
+
* @param newValue The new sample value to add.
|
|
64
|
+
* @param timestamp The timestamp in milliseconds.
|
|
65
|
+
* @return T The new moving variance.
|
|
66
|
+
*/
|
|
67
|
+
T addSampleWithTimestamp(T newValue, double timestamp);
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* @brief Gets the current moving variance.
|
|
71
|
+
* @return T The variance of the samples currently in the buffer.
|
|
72
|
+
*/
|
|
73
|
+
T getVariance() const;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* @brief Clears all samples from the filter and resets the sums.
|
|
77
|
+
*/
|
|
78
|
+
void clear();
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* @brief Checks if the filter's buffer is full (i.e., has N samples).
|
|
82
|
+
* @return true if the buffer is full, false otherwise.
|
|
83
|
+
*/
|
|
84
|
+
bool isFull() const noexcept;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* @brief Checks if the filter is in time-aware mode.
|
|
88
|
+
* @return true if time-aware, false otherwise.
|
|
89
|
+
*/
|
|
90
|
+
bool isTimeAware() const noexcept;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* @brief Exports the filter's internal state.
|
|
94
|
+
* @return A pair containing:
|
|
95
|
+
* 1. The buffer contents (std::vector<T>)
|
|
96
|
+
* 2. A pair of sums (running_sum, running_sum_of_squares)
|
|
97
|
+
*/
|
|
98
|
+
std::pair<std::vector<T>, std::pair<T, T>> getState() const;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* @brief Restores the filter's internal state.
|
|
102
|
+
* @param bufferData The buffer contents to restore.
|
|
103
|
+
* @param sum The running sum to restore.
|
|
104
|
+
* @param sumOfSquares The running sum of squares to restore.
|
|
105
|
+
*/
|
|
106
|
+
void setState(const std::vector<T> &bufferData, T sum, T sumOfSquares);
|
|
107
|
+
|
|
108
|
+
private:
|
|
109
|
+
dsp::utils::CircularBufferArray<T> buffer;
|
|
110
|
+
T running_sum;
|
|
111
|
+
T running_sum_of_squares;
|
|
112
|
+
size_t window_size;
|
|
113
|
+
};
|
|
114
|
+
} // namespace dsp::core
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
#include "MovingZScoreFilter.h"
|
|
2
|
+
|
|
3
|
+
using namespace dsp::core;
|
|
4
|
+
|
|
5
|
+
// -----------------------------------------------------------------------------
|
|
6
|
+
// Constructor
|
|
7
|
+
// -----------------------------------------------------------------------------
|
|
8
|
+
template <typename T>
|
|
9
|
+
MovingZScoreFilter<T>::MovingZScoreFilter(size_t window_size, T epsilon)
|
|
10
|
+
: buffer(window_size), // Initialize the circular buffer
|
|
11
|
+
running_sum(0),
|
|
12
|
+
running_sum_of_squares(0),
|
|
13
|
+
window_size(window_size),
|
|
14
|
+
m_epsilon(epsilon)
|
|
15
|
+
{
|
|
16
|
+
if (window_size == 0)
|
|
17
|
+
{
|
|
18
|
+
throw std::invalid_argument("Window size must be greater than 0");
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// -----------------------------------------------------------------------------
|
|
23
|
+
// Time-aware Constructor
|
|
24
|
+
// -----------------------------------------------------------------------------
|
|
25
|
+
template <typename T>
|
|
26
|
+
MovingZScoreFilter<T>::MovingZScoreFilter(size_t window_size, double window_duration_ms, T epsilon)
|
|
27
|
+
: buffer(window_size, window_duration_ms), // Initialize time-aware circular buffer
|
|
28
|
+
running_sum(0),
|
|
29
|
+
running_sum_of_squares(0),
|
|
30
|
+
window_size(window_size),
|
|
31
|
+
m_epsilon(epsilon)
|
|
32
|
+
{
|
|
33
|
+
if (window_size == 0)
|
|
34
|
+
{
|
|
35
|
+
throw std::invalid_argument("Window size must be greater than 0");
|
|
36
|
+
}
|
|
37
|
+
if (window_duration_ms <= 0.0)
|
|
38
|
+
{
|
|
39
|
+
throw std::invalid_argument("Window duration must be greater than 0");
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// -----------------------------------------------------------------------------
|
|
44
|
+
// Method: addSample
|
|
45
|
+
// -----------------------------------------------------------------------------
|
|
46
|
+
template <typename T>
|
|
47
|
+
T MovingZScoreFilter<T>::addSample(T newValue)
|
|
48
|
+
{
|
|
49
|
+
T oldestValue = 0;
|
|
50
|
+
|
|
51
|
+
// If the buffer is full, get the oldest value
|
|
52
|
+
if (buffer.isFull())
|
|
53
|
+
{
|
|
54
|
+
oldestValue = buffer.peek();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Add the new value to the buffer
|
|
58
|
+
buffer.pushOverwrite(newValue);
|
|
59
|
+
|
|
60
|
+
// Update running sums
|
|
61
|
+
T oldestValueSquared = oldestValue * oldestValue;
|
|
62
|
+
T newValueSquared = newValue * newValue;
|
|
63
|
+
|
|
64
|
+
running_sum = running_sum - oldestValue + newValue;
|
|
65
|
+
running_sum_of_squares = running_sum_of_squares - oldestValueSquared + newValueSquared;
|
|
66
|
+
|
|
67
|
+
// --- Calculate new stats ---
|
|
68
|
+
size_t count = buffer.getCount();
|
|
69
|
+
T countT = static_cast<T>(count);
|
|
70
|
+
|
|
71
|
+
// Calculate mean
|
|
72
|
+
T mean = running_sum / countT;
|
|
73
|
+
|
|
74
|
+
// Calculate mean of squares
|
|
75
|
+
T mean_of_squares = running_sum_of_squares / countT;
|
|
76
|
+
|
|
77
|
+
// Variance = E[X^2] - (E[X])^2
|
|
78
|
+
T variance = std::max(static_cast<T>(0), mean_of_squares - (mean * mean));
|
|
79
|
+
|
|
80
|
+
// Standard Deviation
|
|
81
|
+
T stddev = std::sqrt(variance);
|
|
82
|
+
|
|
83
|
+
// --- Calculate Z-Score ---
|
|
84
|
+
// Use epsilon to prevent division by zero
|
|
85
|
+
if (stddev < m_epsilon)
|
|
86
|
+
{
|
|
87
|
+
return 0; // If stddev is ~0, all values are the mean, so z-score is 0
|
|
88
|
+
}
|
|
89
|
+
else
|
|
90
|
+
{
|
|
91
|
+
return (newValue - mean) / stddev;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// -----------------------------------------------------------------------------
|
|
96
|
+
// Method: addSampleWithTimestamp
|
|
97
|
+
// -----------------------------------------------------------------------------
|
|
98
|
+
template <typename T>
|
|
99
|
+
T MovingZScoreFilter<T>::addSampleWithTimestamp(T newValue, double timestamp)
|
|
100
|
+
{
|
|
101
|
+
// Expire old samples
|
|
102
|
+
size_t expired_count = buffer.expireOld(timestamp);
|
|
103
|
+
|
|
104
|
+
// Rebuild running statistics if samples were expired
|
|
105
|
+
if (expired_count > 0)
|
|
106
|
+
{
|
|
107
|
+
running_sum = 0;
|
|
108
|
+
running_sum_of_squares = 0;
|
|
109
|
+
|
|
110
|
+
auto remaining = buffer.toVector();
|
|
111
|
+
for (const auto &value : remaining)
|
|
112
|
+
{
|
|
113
|
+
running_sum += value;
|
|
114
|
+
running_sum_of_squares += value * value;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Add new sample
|
|
119
|
+
T oldestValue = 0;
|
|
120
|
+
if (buffer.isFull())
|
|
121
|
+
{
|
|
122
|
+
oldestValue = buffer.peek();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
buffer.pushOverwriteWithTimestamp(newValue, timestamp);
|
|
126
|
+
|
|
127
|
+
// Update running sums
|
|
128
|
+
T oldestValueSquared = oldestValue * oldestValue;
|
|
129
|
+
T newValueSquared = newValue * newValue;
|
|
130
|
+
|
|
131
|
+
running_sum = running_sum - oldestValue + newValue;
|
|
132
|
+
running_sum_of_squares = running_sum_of_squares - oldestValueSquared + newValueSquared;
|
|
133
|
+
|
|
134
|
+
// --- Calculate new stats ---
|
|
135
|
+
size_t count = buffer.getCount();
|
|
136
|
+
T countT = static_cast<T>(count);
|
|
137
|
+
|
|
138
|
+
// Calculate mean
|
|
139
|
+
T mean = running_sum / countT;
|
|
140
|
+
|
|
141
|
+
// Calculate mean of squares
|
|
142
|
+
T mean_of_squares = running_sum_of_squares / countT;
|
|
143
|
+
|
|
144
|
+
// Variance = E[X^2] - (E[X])^2
|
|
145
|
+
T variance = std::max(static_cast<T>(0), mean_of_squares - (mean * mean));
|
|
146
|
+
|
|
147
|
+
// Standard Deviation
|
|
148
|
+
T stddev = std::sqrt(variance);
|
|
149
|
+
|
|
150
|
+
// --- Calculate Z-Score ---
|
|
151
|
+
if (stddev < m_epsilon)
|
|
152
|
+
{
|
|
153
|
+
return 0; // If stddev is ~0, all values are the mean, so z-score is 0
|
|
154
|
+
}
|
|
155
|
+
else
|
|
156
|
+
{
|
|
157
|
+
return (newValue - mean) / stddev;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// -----------------------------------------------------------------------------
|
|
162
|
+
// Method: clear
|
|
163
|
+
// -----------------------------------------------------------------------------
|
|
164
|
+
template <typename T>
|
|
165
|
+
void MovingZScoreFilter<T>::clear()
|
|
166
|
+
{
|
|
167
|
+
buffer.clear();
|
|
168
|
+
running_sum = 0;
|
|
169
|
+
running_sum_of_squares = 0;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// -----------------------------------------------------------------------------
|
|
173
|
+
// Method: isFull
|
|
174
|
+
// -----------------------------------------------------------------------------
|
|
175
|
+
template <typename T>
|
|
176
|
+
bool MovingZScoreFilter<T>::isFull() const noexcept
|
|
177
|
+
{
|
|
178
|
+
return buffer.isFull();
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// -----------------------------------------------------------------------------
|
|
182
|
+
// Method: isTimeAware
|
|
183
|
+
// -----------------------------------------------------------------------------
|
|
184
|
+
template <typename T>
|
|
185
|
+
bool MovingZScoreFilter<T>::isTimeAware() const noexcept
|
|
186
|
+
{
|
|
187
|
+
return buffer.isTimeAware();
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// -----------------------------------------------------------------------------
|
|
191
|
+
// Method: getState
|
|
192
|
+
// -----------------------------------------------------------------------------
|
|
193
|
+
template <typename T>
|
|
194
|
+
std::pair<std::vector<T>, std::pair<T, T>> MovingZScoreFilter<T>::getState() const
|
|
195
|
+
{
|
|
196
|
+
return {buffer.toVector(), {running_sum, running_sum_of_squares}};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// -----------------------------------------------------------------------------
|
|
200
|
+
// Method: setState
|
|
201
|
+
// -----------------------------------------------------------------------------
|
|
202
|
+
template <typename T>
|
|
203
|
+
void MovingZScoreFilter<T>::setState(const std::vector<T> &bufferData, T sum, T sumOfSquares)
|
|
204
|
+
{
|
|
205
|
+
buffer.fromVector(bufferData);
|
|
206
|
+
running_sum = sum;
|
|
207
|
+
running_sum_of_squares = sumOfSquares;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Explicit template instantiation for common types
|
|
211
|
+
namespace dsp::core
|
|
212
|
+
{
|
|
213
|
+
template class MovingZScoreFilter<float>;
|
|
214
|
+
template class MovingZScoreFilter<double>;
|
|
215
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include "../utils/CircularBufferArray.h"
|
|
4
|
+
#include <utility>
|
|
5
|
+
#include <vector>
|
|
6
|
+
#include <cmath>
|
|
7
|
+
#include <stdexcept>
|
|
8
|
+
#include <algorithm> // for std::max
|
|
9
|
+
|
|
10
|
+
namespace dsp::core
|
|
11
|
+
{
|
|
12
|
+
/**
|
|
13
|
+
* @brief Implements an efficient Moving Z-Score normalization filter.
|
|
14
|
+
*
|
|
15
|
+
* This class uses a circular buffer to store the last 'N' samples
|
|
16
|
+
* and maintains a running sum AND a running sum-of-squares
|
|
17
|
+
* for O(1) mean and standard deviation calculation.
|
|
18
|
+
*
|
|
19
|
+
* Z-Score = (Value - Mean) / StandardDeviation
|
|
20
|
+
*
|
|
21
|
+
* @tparam T The numeric type of the samples (e.g., float, double).
|
|
22
|
+
*/
|
|
23
|
+
template <typename T>
|
|
24
|
+
class MovingZScoreFilter
|
|
25
|
+
{
|
|
26
|
+
public:
|
|
27
|
+
/**
|
|
28
|
+
* @brief Constructs a new Moving Z-Score Filter.
|
|
29
|
+
* @param window_size The number of samples to average over (N).
|
|
30
|
+
* @param epsilon A small value to prevent division by zero (default 1e-6).
|
|
31
|
+
*/
|
|
32
|
+
explicit MovingZScoreFilter(size_t window_size, T epsilon = 1e-6);
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @brief Constructs a new time-aware Moving Z-Score Filter.
|
|
36
|
+
* @param window_size The buffer capacity in samples.
|
|
37
|
+
* @param window_duration_ms The time window duration in milliseconds.
|
|
38
|
+
* @param epsilon A small value to prevent division by zero (default 1e-6).
|
|
39
|
+
*/
|
|
40
|
+
explicit MovingZScoreFilter(size_t window_size, double window_duration_ms, T epsilon = 1e-6);
|
|
41
|
+
|
|
42
|
+
// Delete copy constructor and copy assignment
|
|
43
|
+
MovingZScoreFilter(const MovingZScoreFilter &) = delete;
|
|
44
|
+
MovingZScoreFilter &operator=(const MovingZScoreFilter &) = delete;
|
|
45
|
+
|
|
46
|
+
// Enable move semantics
|
|
47
|
+
MovingZScoreFilter(MovingZScoreFilter &&) noexcept = default;
|
|
48
|
+
MovingZScoreFilter &operator=(MovingZScoreFilter &&) noexcept = default;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* @brief Adds a new sample to the filter and returns its Z-Score.
|
|
52
|
+
*
|
|
53
|
+
* This updates the running sums and the internal circular buffer.
|
|
54
|
+
* The Z-Score of the new sample is calculated based on the
|
|
55
|
+
* *new* window statistics (including the sample just added).
|
|
56
|
+
*
|
|
57
|
+
* @param newValue The new sample value to add.
|
|
58
|
+
* @return T The Z-Score of the new sample.
|
|
59
|
+
*/
|
|
60
|
+
T addSample(T newValue);
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* @brief Adds a new sample with timestamp and returns its Z-Score.
|
|
64
|
+
*
|
|
65
|
+
* Expires old samples, rebuilds statistics, then adds new sample.
|
|
66
|
+
*
|
|
67
|
+
* @param newValue The new sample value to add.
|
|
68
|
+
* @param timestamp The timestamp in milliseconds.
|
|
69
|
+
* @return T The Z-Score of the new sample.
|
|
70
|
+
*/
|
|
71
|
+
T addSampleWithTimestamp(T newValue, double timestamp);
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* @brief Clears all samples from the filter and resets the sums.
|
|
75
|
+
*/
|
|
76
|
+
void clear();
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* @brief Checks if the filter's buffer is full (i.e., has N samples).
|
|
80
|
+
* @return true if the buffer is full, false otherwise.
|
|
81
|
+
*/
|
|
82
|
+
bool isFull() const noexcept;
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* @brief Checks if the filter is in time-aware mode.
|
|
86
|
+
* @return true if time-aware, false otherwise.
|
|
87
|
+
*/
|
|
88
|
+
bool isTimeAware() const noexcept;
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* @brief Exports the filter's internal state.
|
|
92
|
+
* @return A pair containing:
|
|
93
|
+
* 1. The buffer contents (std::vector<T>)
|
|
94
|
+
* 2. A pair of sums (running_sum, running_sum_of_squares)
|
|
95
|
+
*/
|
|
96
|
+
std::pair<std::vector<T>, std::pair<T, T>> getState() const;
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* @brief Restores the filter's internal state.
|
|
100
|
+
* @param bufferData The buffer contents to restore.
|
|
101
|
+
* @param sum The running sum to restore.
|
|
102
|
+
* @param sumOfSquares The running sum of squares to restore.
|
|
103
|
+
*/
|
|
104
|
+
void setState(const std::vector<T> &bufferData, T sum, T sumOfSquares);
|
|
105
|
+
|
|
106
|
+
private:
|
|
107
|
+
dsp::utils::CircularBufferArray<T> buffer;
|
|
108
|
+
T running_sum;
|
|
109
|
+
T running_sum_of_squares;
|
|
110
|
+
size_t window_size;
|
|
111
|
+
T m_epsilon;
|
|
112
|
+
};
|
|
113
|
+
} // namespace dsp::core
|