SDDSlib
Loading...
Searching...
No Matches
savitzkyGolay.c
Go to the documentation of this file.
1/**
2 * @file savitzkyGolay.c
3 * @brief Implementation of Savitzky-Golay smoothing functions.
4 *
5 * This file contains functions for performing Savitzky-Golay smoothing and differentiation on data arrays.
6 * It provides tools to smooth noisy data or compute derivatives using polynomial fitting over a moving window.
7 */
8
9#include "matlib.h"
10#include "mdb.h"
11#include "fftpackC.h"
12
13/**
14 * @brief Applies Savitzky-Golay smoothing or differentiation to a data array.
15 *
16 * This function smooths the provided data array using the Savitzky-Golay method.
17 * It can also compute derivatives of the data up to a specified order.
18 * The smoothing is performed in-place on the input data array.
19 *
20 * @param data Pointer to the data array to be smoothed or differentiated.
21 * @param rows Number of data points in the data array.
22 * @param order Polynomial order of the smoothing filter.
23 * @param nLeft Number of data points to include to the left of the central point in the smoothing window.
24 * @param nRight Number of data points to include to the right of the central point in the smoothing window.
25 * @param derivativeOrder Order of the derivative to compute (0 for smoothing only).
26 * @return Returns 1 on success, 0 on failure due to invalid parameters or memory allocation error.
27 *
28 * @note The function modifies the input data array in place.
29 * @warning The input data array must have at least (nLeft + nRight + 1) elements.
30 * @warning This function is not thread-safe due to the use of static internal buffers.
31 */
32long SavitzkyGolaySmooth(double *data, long rows, long order, long nLeft, long nRight, long derivativeOrder) {
33 static double *FFTdata = NULL, *FFTfilter = NULL, *TMPdata = NULL;
34 static long arraySize = 0, TMParraySize = 0;
35 long i, nfreq, sizeNeeded;
36
37 if (order < 0) {
38 fprintf(stderr, "order<0 (SavitzkyGolaySmooth)\n");
39 return (0);
40 }
41 if (nLeft < 0) {
42 fprintf(stderr, "nLeft<0 (SavitzkyGolaySmooth)\n");
43 return (0);
44 }
45 if (nRight < 0) {
46 fprintf(stderr, "nRight<0 (SavitzkyGolaySmooth)\n");
47 return (0);
48 }
49 if (derivativeOrder < 0) {
50 fprintf(stderr, "derivativeOrder<0 (SavitzkyGolaySmooth)\n");
51 return (0);
52 }
53 if (derivativeOrder > order) {
54 fprintf(stderr, "derivativeOrder>order (SavitzkyGolaySmooth)\n");
55 return (0);
56 }
57 if ((nLeft + nRight) < order) {
58 fprintf(stderr, "(nLeft+nRight)<order (SavitzkyGolaySmooth)\n");
59 return (0);
60 }
61 if ((nLeft + nRight) == 0) {
62 fprintf(stderr, "(nLeft+nRight)==0 (SavitzkyGolaySmooth)\n");
63 return (0);
64 }
65 if (rows < (nLeft + nRight + 1)) {
66 fprintf(stderr, "rows<(nLeft+nRight+1) (SavitzkyGolaySmooth)\n");
67 return (0);
68 }
69
70 if ((order == 1) && (nLeft == nRight) && (derivativeOrder == 0)) {
71 /* This is a special case of the filter. Sometimes called moving window averaging. */
72 /* It requires that nLeft=nRight and this is the only option for elegant now */
73 long np = nLeft + nRight + 1;
74 double scale = 1.0 / np;
75
76 sizeNeeded = rows;
77 if (TMParraySize < sizeNeeded && (!(TMPdata = realloc(TMPdata, sizeof(*TMPdata) * (TMParraySize = sizeNeeded))))) {
78 fprintf(stderr, "Error: memory allocation failure (SavitzkyGolaySmooth)\n");
79 exit(1);
80 }
81
82 for (i = 0; i < rows; i++) {
83 data[i] = scale * data[i];
84 TMPdata[i] = data[i];
85 }
86
87 /* Smooth the left side data with padding to eliminate end effects */
88 for (i = 1; i <= nRight; i++)
89 data[0] += data[i];
90 data[0] += nLeft * TMPdata[0];
91
92 for (i = 1; i <= nLeft; i++) {
93 data[i] = data[i - 1] + data[i + nRight] - TMPdata[0];
94 }
95
96 /* Smooth the middle part of the array */
97 for (i = nLeft + 1; i < rows - nRight; i++) {
98 data[i] = data[i - 1] + data[i + nRight] - TMPdata[i - nLeft - 1];
99 }
100
101 /* Smooth the right side data with padding to eliminate end effects */
102 for (i = rows - nRight; i < rows; i++) {
103 data[i] = data[i - 1] + data[rows - 1] - TMPdata[i - nLeft - 1];
104 }
105
106 return (1);
107 } else { /* Smooth data in the time domain */
108 long np = nLeft + nRight + 1, j;
109 static long coeffArraySize = 0;
110 static double *filterCoeff = NULL;
111
112 sizeNeeded = rows + nLeft + nRight;
113 if (TMParraySize < sizeNeeded && (!(TMPdata = realloc(TMPdata, sizeof(*TMPdata) * (TMParraySize = sizeNeeded))))) {
114 fprintf(stderr, "Error: memory allocation failure (SavitzkyGolaySmooth)\n");
115 exit(1);
116 }
117 if (coeffArraySize < np && (!(filterCoeff = realloc(filterCoeff, sizeof(*filterCoeff) * (coeffArraySize = np))))) {
118 fprintf(stderr, "Error: memory allocation failure (SavitzkyGolaySmooth)\n");
119 exit(1);
120 }
121
122 /* copy the data to a temporary array, with padding to eliminate end effects */
123 for (i = 0; i < rows; i++)
124 TMPdata[i + nLeft] = data[i];
125 for (i = 0; i < nLeft; i++)
126 TMPdata[i] = data[0];
127 for (i = 0; i < nRight; i++)
128 TMPdata[rows + nLeft + i] = data[rows - 1];
129
130 /* store SG coefficients in wrap-around order */
131 SavitzkyGolayCoefficients(filterCoeff, np, order, nLeft, nRight, derivativeOrder, 1);
132
133 for (i = 0; i < rows; i++) {
134 data[i] = data[i] * filterCoeff[0];
135 for (j = 1; j <= nLeft; j++)
136 data[i] += TMPdata[i + nLeft - j] * filterCoeff[j];
137 for (j = 1; j <= nRight; j++)
138 data[i] += TMPdata[i + nLeft + j] * filterCoeff[np - j];
139 }
140 return (1);
141 }
142
143 /* Below are the old implementation in the frequency domain, which could be very time consuming */
144 sizeNeeded = 2 * (rows + 1 + nLeft + nRight);
145 if (arraySize < sizeNeeded && (!(FFTdata = realloc(FFTdata, sizeof(*FFTdata) * (arraySize = sizeNeeded))) || !(FFTfilter = realloc(FFTfilter, sizeof(*FFTfilter) * (arraySize = sizeNeeded))))) {
146 fprintf(stderr, "Error: memory allocation failure (SavitzkyGolaySmooth)\n");
147 exit(1);
148 }
149 for (i = 0; i < arraySize; i++)
150 FFTdata[i] = FFTfilter[i] = 0;
151
152 /* store SG coefficients in wrap-around order */
153 SavitzkyGolayCoefficients(FFTfilter, 2 * (rows + nLeft + nRight), order, nLeft, nRight, derivativeOrder, 1);
154
155 /* copy the data to work array, with padding to eliminate end effects */
156 for (i = 0; i < rows; i++)
157 FFTdata[i + nLeft] = data[i];
158 for (i = 0; i < nLeft; i++)
159 FFTdata[i] = data[0];
160 for (i = 0; i < nRight; i++)
161 FFTdata[rows + nLeft + i] = data[rows - 1];
162
163 /* Take FFTs of signal and filter */
164 realFFT2(FFTdata, FFTdata, 2 * (rows + nLeft + nRight), 0);
165 realFFT2(FFTfilter, FFTfilter, 2 * (rows + nLeft + nRight), 0);
166
167 /* Multiply FFTs */
168 nfreq = rows + nLeft + nRight + 1;
169 for (i = 0; i < nfreq; i++)
170 complex_multiply(FFTdata + 2 * i, FFTdata + 2 * i + 1, FFTdata[2 * i], FFTdata[2 * i + 1], FFTfilter[2 * i], FFTfilter[2 * i + 1]);
171
172 /* Do inverse FFT */
173 realFFT2(FFTdata, FFTdata, 2 * (rows + nLeft + nRight), INVERSE_FFT);
174 for (i = 0; i < rows; i++)
175 data[i] = FFTdata[i + nLeft] * 2 * (rows + nLeft + nRight);
176
177 return (1);
178}
179
180typedef struct
181{
182 double *coef;
183 long order, right, left, derivOrder;
185
186static SAVITZKYGOLAY_COEF *svCoef = NULL;
187static long nSVCoef = 0;
188
189void SavitzkyGolayCoefficients(double *coef, long maxCoefs, long order, long nLeft, long nRight, long derivativeOrder, long wrapAround) {
190 MATRIX *A, *At, *AtA;
191 long i, j, m, iStore;
192 double factor;
193 long iSave;
194
195 if (!coef || order < 0 || derivativeOrder < 0 || derivativeOrder > order || (nLeft + nRight) < order || nLeft < 0 || nRight < 0 || maxCoefs < (nLeft + nRight + 1)) {
196 fprintf(stderr, "Error: invalid arguments (savitzkyGolayCoefficients)\n");
197 exit(1);
198 }
199
200 for (i = 0; i < maxCoefs; i++)
201 coef[i] = 0;
202
203 /* see if these coefs are already stored. if so, just copy them and return. */
204 for (iSave = 0; iSave < nSVCoef; iSave++) {
205 if (order == svCoef[iSave].order && nLeft == svCoef[iSave].left && nRight == svCoef[iSave].right && derivativeOrder == svCoef[iSave].derivOrder) {
206 for (i = -nLeft; i <= nRight; i++) {
207 if (wrapAround) {
208 if (i <= 0)
209 iStore = -i;
210 else
211 iStore = maxCoefs - i;
212 } else
213 iStore = i + nLeft;
214 coef[iStore] = svCoef[iSave].coef[i + nLeft];
215 }
216 return;
217 }
218 }
219
220 m_alloc(&A, nLeft + nRight + 1, order + 1);
221 m_alloc(&At, order + 1, nLeft + nRight + 1);
222 m_alloc(&AtA, order + 1, order + 1);
223
224 for (i = -nLeft; i <= nRight; i++) {
225 factor = 1;
226 for (j = 0; j <= order; j++) {
227 A->a[i + nLeft][j] = factor;
228 factor *= i;
229 }
230 }
231
232 if (!m_trans(At, A) || !m_mult(AtA, At, A) || !m_invert(AtA, AtA)) {
233 fprintf(stderr, "Error: matrix manipulation problem (savitzkyGolayCoefficients)\n");
234 exit(1);
235 }
236
237 if (!(svCoef = realloc(svCoef, sizeof(*svCoef) * (nSVCoef + 1))) || !(svCoef[nSVCoef].coef = malloc(sizeof(*svCoef[nSVCoef].coef) * (nRight + nLeft + 1)))) {
238 fprintf(stderr, "Error: memory allocation failure (savitzkyGolayCoefficients)\n");
239 exit(1);
240 }
241 svCoef[nSVCoef].left = nLeft;
242 svCoef[nSVCoef].right = nRight;
243 svCoef[nSVCoef].derivOrder = derivativeOrder;
244 svCoef[nSVCoef].order = order;
245
246 for (i = -nLeft; i <= nRight; i++) {
247 if (wrapAround) {
248 if (i <= 0)
249 iStore = -i;
250 else
251 iStore = maxCoefs - i;
252 } else
253 iStore = i + nLeft;
254 coef[iStore] = 0;
255 factor = 1;
256 for (m = 0; m <= order; m++) {
257 coef[iStore] += AtA->a[derivativeOrder][m] * factor;
258 factor *= i;
259 }
260 svCoef[nSVCoef].coef[i + nLeft] = coef[iStore];
261 }
262 nSVCoef++;
263 m_free(&A);
264 m_free(&At);
265 m_free(&AtA);
266}
long SavitzkyGolaySmooth(double *data, long rows, long order, long nLeft, long nRight, long derivativeOrder)
Applies Savitzky-Golay smoothing or differentiation to a data array.
void complex_multiply(double *r0, double *i0, double r1, double i1, double r2, double i2)
Multiplies two complex numbers.
Definition complex.cc:81