Img2Num C++ (Internal Developer Docs) dev
API Documentation
Loading...
Searching...
No Matches
SavitskyGolay.cpp
1#include "internal/SavitskyGolay.h"
2
3#include <cassert>
4#include <cmath>
5#include <numeric>
6#include <stdexcept>
7
8SavitzkyGolay::SavitzkyGolay(int radius, int poly_order)
9 : window_radius_(radius)
10 , window_size_(2 * radius + 1)
11 , poly_order_(poly_order) {
12 assert(radius >= 0);
13 assert(window_size_ > poly_order_);
14
15 compute_coefficients();
16}
17
18std::vector<Point> SavitzkyGolay::filter(const std::vector<Point>& data) {
19 if (data.size() < window_size_) {
20 return data; // Data too short to filter
21 }
22
23 std::vector<Point> result(data.size());
24
25 // 1. Convolution for the valid range
26 for (size_t i = window_radius_; i < data.size() - window_radius_; ++i) {
27 Point val {0.0, 0.0};
28 for (int j = -window_radius_; j <= window_radius_; ++j) {
29 val += data[i + j] * coeffs_[j + window_radius_];
30 }
31 result[i] = val;
32 }
33
34 // 2. Handle Edges (Simple Repeat/Nearest padding strategy)
35 // For a robust production app, you might calculate asymmetric kernels here.
36 for (int i = 0; i < window_radius_; ++i)
37 result[i] = data[i];
38 for (size_t i = data.size() - window_radius_; i < data.size(); ++i)
39 result[i] = data[i];
40
41 return result;
42}
43
44std::vector<Point> SavitzkyGolay::filter_wrap(const std::vector<Point>& data) {
45 // wrap around
46 if (data.size() < window_size_) {
47 return data; // Data too short to filter
48 }
49
50 std::vector<Point> result(data.size());
51 std::copy(data.begin(), data.end(), result.begin());
52
53 for (size_t i = 0; i < data.size(); ++i) {
54 Point val {0.0, 0.0};
55 for (int j = -window_radius_; j <= window_radius_; ++j) {
56 int k = i + j;
57 if (k < 0) {
58 k = data.size() + k;
59 } else if (k >= data.size()) {
60 k = k - data.size();
61 }
62 val = val + data[k] * coeffs_[j + window_radius_];
63 }
64 result[i] = val;
65 }
66
67 return result;
68}
69
70// Helper: Invert a matrix using Gauss-Jordan Elimination
71// A is (N x N), returns A_inv
72std::vector<std::vector<float>> SavitzkyGolay::invert_matrix(std::vector<std::vector<float>> A) {
73 int n = A.size();
74 std::vector<std::vector<float>> inv(n, std::vector<float>(n, 0.0));
75
76 // Initialize inverse as identity
77 for (int i = 0; i < n; ++i)
78 inv[i][i] = 1.0;
79
80 for (int i = 0; i < n; ++i) {
81 // Find pivot
82 float pivot = A[i][i];
83 // (Simple pivot check, typically you'd swap rows for stability)
84 if (std::abs(pivot) < 1e-10)
85 throw std::runtime_error("Matrix singular, cannot invert.");
86
87 // Normalize row
88 for (int j = 0; j < n; ++j) {
89 A[i][j] /= pivot;
90 inv[i][j] /= pivot;
91 }
92
93 // Eliminate other rows
94 for (int k = 0; k < n; ++k) {
95 if (k != i) {
96 float factor = A[k][i];
97 for (int j = 0; j < n; ++j) {
98 A[k][j] -= factor * A[i][j];
99 inv[k][j] -= factor * inv[i][j];
100 }
101 }
102 }
103 }
104 return inv;
105}
106
107void SavitzkyGolay::compute_coefficients() {
108 // 1. Create the matrix J = (A^T * A)
109 // Size is (poly_order + 1) x (poly_order + 1)
110 // Element J[i][j] is the sum of k^(i+j) for k in -m..m
111
112 int rows = poly_order_ + 1;
113 std::vector<std::vector<float>> J(rows, std::vector<float>(rows));
114
115 for (int i = 0; i < rows; ++i) {
116 for (int j = 0; j < rows; ++j) {
117 float sum = 0;
118 for (int k = -window_radius_; k <= window_radius_; ++k) {
119 sum += std::pow(k, i + j);
120 }
121 J[i][j] = sum;
122 }
123 }
124
125 // 2. Invert J to solve the normal equations
126 auto J_inv = invert_matrix(J);
127
128 // 3. Compute the weights
129 // The smoothed value is the coefficient c_0 of the polynomial.
130 // c_0 = sum( weight_k * y_k )
131 // weight_k = sum( J_inv[0][j] * k^j ) for j=0..order
132
133 coeffs_.resize(window_size_);
134 for (int k = -window_radius_; k <= window_radius_; ++k) {
135 float weight = 0.0;
136 for (int j = 0; j < rows; ++j) {
137 weight += J_inv[0][j] * std::pow(k, j);
138 }
139 coeffs_[k + window_radius_] = weight;
140 }
141}
Definition Point.h:5