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