libpappsomspp
Library for mass spectrometry
Loading...
Searching...
No Matches
savgolfilter.cpp
Go to the documentation of this file.
1/* BEGIN software license
2 *
3 * msXpertSuite - mass spectrometry software suite
4 * -----------------------------------------------
5 * Copyright(C) 2009,...,2018 Filippo Rusconi
6 *
7 * http://www.msxpertsuite.org
8 *
9 * This file is part of the msXpertSuite project.
10 *
11 * The msXpertSuite project is the successor of the massXpert project. This
12 * project now includes various independent modules:
13 *
14 * - massXpert, model polymer chemistries and simulate mass spectrometric data;
15 * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner;
16 *
17 * This program is free software: you can redistribute it and/or modify
18 * it under the terms of the GNU General Public License as published by
19 * the Free Software Foundation, either version 3 of the License, or
20 * (at your option) any later version.
21 *
22 * This program is distributed in the hope that it will be useful,
23 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 * GNU General Public License for more details.
26 *
27 * You should have received a copy of the GNU General Public License
28 * along with this program. If not, see <http://www.gnu.org/licenses/>.
29 *
30 * END software license
31 */
32
33
34#include <qmath.h>
35
36
37#include <QVector>
38#include <QDebug>
39
41
42#include "savgolfilter.h"
43
44
45namespace pappso
46{
47
48
49#define SWAP(a, b) \
50 tempr = (a); \
51 (a) = (b); \
52 (b) = tempr
53
55{
56 // m_params will have defaults that work well with mass spectra.
57}
58
59FilterSavitzkyGolay::FilterSavitzkyGolay(int nL, int nR, int m, int lD, bool convolveWithNr)
60{
61 m_params.nL = nL;
62 m_params.nR = nR;
63 m_params.m = m;
64 m_params.lD = lD;
65 m_params.convolveWithNr = convolveWithNr;
66}
67
68
70{
71 m_params.nL = sav_gol_params.nL;
72 m_params.nR = sav_gol_params.nR;
73 m_params.m = sav_gol_params.m;
74 m_params.lD = sav_gol_params.lD;
75 m_params.convolveWithNr = sav_gol_params.convolveWithNr;
76}
77
78
80{
81 buildFilterFromString(parameters);
82}
83
84
86{
87 // This function only copies the parameters, not the data.
88
89 m_params.nL = other.m_params.nL;
90 m_params.nR = other.m_params.nR;
91 m_params.m = other.m_params.m;
92 m_params.lD = other.m_params.lD;
93 m_params.convolveWithNr = other.m_params.convolveWithNr;
94}
95
96
100
103{
104 if(&other == this)
105 return *this;
106
107 // This function only copies the parameters, not the data.
108
109 m_params.nL = other.m_params.nL;
110 m_params.nR = other.m_params.nR;
111 m_params.m = other.m_params.m;
112 m_params.lD = other.m_params.lD;
113 m_params.convolveWithNr = other.m_params.convolveWithNr;
114
115 return *this;
116}
117
118
119void
121{
122 // Typical string: "Savitzky-Golay|15;15;4;0;false"
123 if(parameters.startsWith(QString("%1|").arg(name())))
124 {
125 QStringList params = parameters.split("|").back().split(";");
126
127 m_params.nL = params.at(0).toInt();
128 m_params.nR = params.at(1).toInt();
129 m_params.m = params.at(2).toInt();
130 m_params.lD = params.at(3).toInt();
131 m_params.convolveWithNr = (params.at(4) == "true" ? true : false);
132 }
133 else
134 {
136 QString("Building of FilterSavitzkyGolay from string '%1' failed").arg(parameters));
137 }
138}
139
140
141Trace &
143{
144 // Initialize data:
145
146 // We want the filter to stay constant so we create a local copy of the data.
147
148 int data_point_count = data_points.size();
149
150 pappso_double *x_data_p = dvector(1, data_point_count);
151 pappso_double *y_initial_data_p = dvector(1, data_point_count);
152 pappso_double *y_filtered_data_p = nullptr;
153
154 if(m_params.convolveWithNr)
155 y_filtered_data_p = dvector(1, 2 * data_point_count);
156 else
157 y_filtered_data_p = dvector(1, data_point_count);
158
159 for(int iter = 0; iter < data_point_count; ++iter)
160 {
161 x_data_p[iter] = data_points.at(iter).x;
162 y_initial_data_p[iter] = data_points.at(iter).y;
163 }
164
165 // Now run the filter.
166
167 runFilter(y_initial_data_p, y_filtered_data_p, data_point_count);
168
169 // Put back the modified y values into the trace.
170 auto iter_yf = y_filtered_data_p;
171 for(auto &data_point : data_points)
172 {
173 data_point.y = *iter_yf;
174 iter_yf++;
175 }
176
177 return data_points;
178}
179
180
183{
184 return SavGolParams(m_params.nL, m_params.nR, m_params.m, m_params.lD, m_params.convolveWithNr);
185}
186
187
188//! Return a string with the textual representation of the configuration data.
189QString
191{
192 return QString("%1|%2").arg(name()).arg(m_params.toString());
193}
194
195
196QString
198{
199 return "Savitzky-Golay";
200}
201
202
203int *
204FilterSavitzkyGolay::ivector(long nl, long nh) const
205{
206 int *v;
207 v = (int *)malloc((size_t)((nh - nl + 2) * sizeof(int)));
208 if(!v)
209 {
210 qFatal("Error: Allocation failure.");
211 }
212 return v - nl + 1;
213}
214
215
217FilterSavitzkyGolay::dvector(long nl, long nh) const
218{
219 pappso_double *v;
220 long k;
221 v = (pappso_double *)malloc((size_t)((nh - nl + 2) * sizeof(pappso_double)));
222 if(!v)
223 {
224 qFatal("Error: Allocation failure.");
225 }
226 for(k = nl; k <= nh; k++)
227 v[k] = 0.0;
228 return v - nl + 1;
229}
230
231
233FilterSavitzkyGolay::dmatrix(long nrl, long nrh, long ncl, long nch) const
234{
235 long i, nrow = nrh - nrl + 1, ncol = nch - ncl + 1;
236 pappso_double **m;
237 m = (pappso_double **)malloc((size_t)((nrow + 1) * sizeof(pappso_double *)));
238 if(!m)
239 {
240 qFatal("Error: Allocation failure.");
241 }
242 m += 1;
243 m -= nrl;
244 m[nrl] = (pappso_double *)malloc((size_t)((nrow * ncol + 1) * sizeof(pappso_double)));
245 if(!m[nrl])
246 {
247 qFatal("Error: Allocation failure.");
248 }
249 m[nrl] += 1;
250 m[nrl] -= ncl;
251 for(i = nrl + 1; i <= nrh; i++)
252 m[i] = m[i - 1] + ncol;
253 return m;
254}
255
256
257void
258FilterSavitzkyGolay::free_ivector(int *v, long nl, [[maybe_unused]] long nh) const
259{
260 free((char *)(v + nl - 1));
261}
262
263
264void
265FilterSavitzkyGolay::free_dvector(pappso_double *v, long nl, [[maybe_unused]] long nh) const
266{
267 free((char *)(v + nl - 1));
268}
269
270
271void
273 pappso_double **m, long nrl, [[maybe_unused]] long nrh, long ncl, [[maybe_unused]] long nch) const
274{
275 free((char *)(m[nrl] + ncl - 1));
276 free((char *)(m + nrl - 1));
277}
278
279
280void
282{
283 int i, ii = 0, ip, j;
284 pappso_double sum;
285
286 for(i = 1; i <= n; i++)
287 {
288 ip = indx[i];
289 sum = b[ip];
290 b[ip] = b[i];
291 if(ii)
292 for(j = ii; j <= i - 1; j++)
293 sum -= a[i][j] * b[j];
294 else if(sum)
295 ii = i;
296 b[i] = sum;
297 }
298 for(i = n; i >= 1; i--)
299 {
300 sum = b[i];
301 for(j = i + 1; j <= n; j++)
302 sum -= a[i][j] * b[j];
303 b[i] = sum / a[i][i];
304 }
305}
306
307
308void
310{
311 int i, imax = 0, j, k;
312 pappso_double big, dum, sum, temp;
313 pappso_double *vv;
314
315 vv = dvector(1, n);
316 *d = 1.0;
317 for(i = 1; i <= n; i++)
318 {
319 big = 0.0;
320 for(j = 1; j <= n; j++)
321 if((temp = fabs(a[i][j])) > big)
322 big = temp;
323 if(big == 0.0)
324 {
325 qFatal("Error: Singular matrix found in routine ludcmp().");
326 }
327 vv[i] = 1.0 / big;
328 }
329 for(j = 1; j <= n; j++)
330 {
331 for(i = 1; i < j; i++)
332 {
333 sum = a[i][j];
334 for(k = 1; k < i; k++)
335 sum -= a[i][k] * a[k][j];
336 a[i][j] = sum;
337 }
338 big = 0.0;
339 for(i = j; i <= n; i++)
340 {
341 sum = a[i][j];
342 for(k = 1; k < j; k++)
343 sum -= a[i][k] * a[k][j];
344 a[i][j] = sum;
345 if((dum = vv[i] * fabs(sum)) >= big)
346 {
347 big = dum;
348 imax = i;
349 }
350 }
351 if(j != imax)
352 {
353 for(k = 1; k <= n; k++)
354 {
355 dum = a[imax][k];
356 a[imax][k] = a[j][k];
357 a[j][k] = dum;
358 }
359 *d = -(*d);
360 vv[imax] = vv[j];
361 }
362 indx[j] = imax;
363 if(a[j][j] == 0.0)
364 a[j][j] = std::numeric_limits<pappso_double>::epsilon();
365 if(j != n)
366 {
367 dum = 1.0 / (a[j][j]);
368 for(i = j + 1; i <= n; i++)
369 a[i][j] *= dum;
370 }
371 }
372 free_dvector(vv, 1, n);
373}
374
375
376void
377FilterSavitzkyGolay::four1(pappso_double data[], unsigned long nn, int isign)
378{
379 unsigned long n, mmax, m, j, istep, i;
380 pappso_double wtemp, wr, wpr, wpi, wi, theta;
381 pappso_double tempr, tempi;
382
383 n = nn << 1;
384 j = 1;
385 for(i = 1; i < n; i += 2)
386 {
387 if(j > i)
388 {
389 SWAP(data[j], data[i]);
390 SWAP(data[j + 1], data[i + 1]);
391 }
392 m = n >> 1;
393 while(m >= 2 && j > m)
394 {
395 j -= m;
396 m >>= 1;
397 }
398 j += m;
399 }
400 mmax = 2;
401 while(n > mmax)
402 {
403 istep = mmax << 1;
404 theta = isign * (6.28318530717959 / mmax);
405 wtemp = sin(0.5 * theta);
406 wpr = -2.0 * wtemp * wtemp;
407 wpi = sin(theta);
408 wr = 1.0;
409 wi = 0.0;
410 for(m = 1; m < mmax; m += 2)
411 {
412 for(i = m; i <= n; i += istep)
413 {
414 j = i + mmax;
415 tempr = wr * data[j] - wi * data[j + 1];
416 tempi = wr * data[j + 1] + wi * data[j];
417 data[j] = data[i] - tempr;
418 data[j + 1] = data[i + 1] - tempi;
419 data[i] += tempr;
420 data[i + 1] += tempi;
421 }
422 wr = (wtemp = wr) * wpr - wi * wpi + wr;
423 wi = wi * wpr + wtemp * wpi + wi;
424 }
425 mmax = istep;
426 }
427}
428
429
430void
432 pappso_double data2[],
433 pappso_double fft1[],
434 pappso_double fft2[],
435 unsigned long n)
436{
437 unsigned long nn3, nn2, jj, j;
438 pappso_double rep, rem, aip, aim;
439
440 nn3 = 1 + (nn2 = 2 + n + n);
441 for(j = 1, jj = 2; j <= n; j++, jj += 2)
442 {
443 fft1[jj - 1] = data1[j];
444 fft1[jj] = data2[j];
445 }
446 four1(fft1, n, 1);
447 fft2[1] = fft1[2];
448 fft1[2] = fft2[2] = 0.0;
449 for(j = 3; j <= n + 1; j += 2)
450 {
451 rep = 0.5 * (fft1[j] + fft1[nn2 - j]);
452 rem = 0.5 * (fft1[j] - fft1[nn2 - j]);
453 aip = 0.5 * (fft1[j + 1] + fft1[nn3 - j]);
454 aim = 0.5 * (fft1[j + 1] - fft1[nn3 - j]);
455 fft1[j] = rep;
456 fft1[j + 1] = aim;
457 fft1[nn2 - j] = rep;
458 fft1[nn3 - j] = -aim;
459 fft2[j] = aip;
460 fft2[j + 1] = -rem;
461 fft2[nn2 - j] = aip;
462 fft2[nn3 - j] = rem;
463 }
464}
465
466
467void
468FilterSavitzkyGolay::realft(pappso_double data[], unsigned long n, int isign)
469{
470 unsigned long i, i1, i2, i3, i4, np3;
471 pappso_double c1 = 0.5, c2, h1r, h1i, h2r, h2i;
472 pappso_double wr, wi, wpr, wpi, wtemp, theta;
473
474 theta = 3.141592653589793 / (pappso_double)(n >> 1);
475 if(isign == 1)
476 {
477 c2 = -0.5;
478 four1(data, n >> 1, 1);
479 }
480 else
481 {
482 c2 = 0.5;
483 theta = -theta;
484 }
485 wtemp = sin(0.5 * theta);
486 wpr = -2.0 * wtemp * wtemp;
487 wpi = sin(theta);
488 wr = 1.0 + wpr;
489 wi = wpi;
490 np3 = n + 3;
491 for(i = 2; i <= (n >> 2); i++)
492 {
493 i4 = 1 + (i3 = np3 - (i2 = 1 + (i1 = i + i - 1)));
494 h1r = c1 * (data[i1] + data[i3]);
495 h1i = c1 * (data[i2] - data[i4]);
496 h2r = -c2 * (data[i2] + data[i4]);
497 h2i = c2 * (data[i1] - data[i3]);
498 data[i1] = h1r + wr * h2r - wi * h2i;
499 data[i2] = h1i + wr * h2i + wi * h2r;
500 data[i3] = h1r - wr * h2r + wi * h2i;
501 data[i4] = -h1i + wr * h2i + wi * h2r;
502 wr = (wtemp = wr) * wpr - wi * wpi + wr;
503 wi = wi * wpr + wtemp * wpi + wi;
504 }
505 if(isign == 1)
506 {
507 data[1] = (h1r = data[1]) + data[2];
508 data[2] = h1r - data[2];
509 }
510 else
511 {
512 data[1] = c1 * ((h1r = data[1]) + data[2]);
513 data[2] = c1 * (h1r - data[2]);
514 four1(data, n >> 1, -1);
515 }
516}
517
518
519char
521 unsigned long n,
522 pappso_double respns[],
523 unsigned long m,
524 int isign,
525 pappso_double ans[])
526{
527 unsigned long i, no2;
528 pappso_double dum, mag2, *fft;
529
530 fft = dvector(1, n << 1);
531 for(i = 1; i <= (m - 1) / 2; i++)
532 respns[n + 1 - i] = respns[m + 1 - i];
533 for(i = (m + 3) / 2; i <= n - (m - 1) / 2; i++)
534 respns[i] = 0.0;
535 twofft(data, respns, fft, ans, n);
536 no2 = n >> 1;
537 for(i = 2; i <= n + 2; i += 2)
538 {
539 if(isign == 1)
540 {
541 ans[i - 1] = (fft[i - 1] * (dum = ans[i - 1]) - fft[i] * ans[i]) / no2;
542 ans[i] = (fft[i] * dum + fft[i - 1] * ans[i]) / no2;
543 }
544 else if(isign == -1)
545 {
546 if((mag2 = ans[i - 1] * ans[i - 1] + ans[i] * ans[i]) == 0.0)
547 {
548 qDebug("Attempt of deconvolving at zero response in convlv().");
549 return (1);
550 }
551 ans[i - 1] = (fft[i - 1] * (dum = ans[i - 1]) + fft[i] * ans[i]) / mag2 / no2;
552 ans[i] = (fft[i] * dum - fft[i - 1] * ans[i]) / mag2 / no2;
553 }
554 else
555 {
556 qDebug("No meaning for isign in convlv().");
557 return (1);
558 }
559 }
560 ans[2] = ans[n + 1];
561 realft(ans, n, -1);
562 free_dvector(fft, 1, n << 1);
563 return (0);
564}
565
566
567char
568FilterSavitzkyGolay::sgcoeff(pappso_double c[], int np, int nl, int nr, int ld, int m) const
569{
570 int imj, ipj, j, k, kk, mm, *indx;
571 pappso_double d, fac, sum, **a, *b;
572
573 if(np < nl + nr + 1 || nl < 0 || nr < 0 || ld > m || nl + nr < m)
574 {
575 qDebug("Inconsistent arguments detected in routine sgcoeff.");
576 return (1);
577 }
578 indx = ivector(1, m + 1);
579 a = dmatrix(1, m + 1, 1, m + 1);
580 b = dvector(1, m + 1);
581 for(ipj = 0; ipj <= (m << 1); ipj++)
582 {
583 sum = (ipj ? 0.0 : 1.0);
584 for(k = 1; k <= nr; k++)
585 sum += pow((pappso_double)k, (pappso_double)ipj);
586 for(k = 1; k <= nl; k++)
587 sum += pow((pappso_double)-k, (pappso_double)ipj);
588 mm = (ipj < 2 * m - ipj ? ipj : 2 * m - ipj);
589 for(imj = -mm; imj <= mm; imj += 2)
590 a[1 + (ipj + imj) / 2][1 + (ipj - imj) / 2] = sum;
591 }
592 ludcmp(a, m + 1, indx, &d);
593 for(j = 1; j <= m + 1; j++)
594 b[j] = 0.0;
595 b[ld + 1] = 1.0;
596 lubksb(a, m + 1, indx, b);
597 for(kk = 1; kk <= np; kk++)
598 c[kk] = 0.0;
599 for(k = -nl; k <= nr; k++)
600 {
601 sum = b[1];
602 fac = 1.0;
603 for(mm = 1; mm <= m; mm++)
604 sum += b[mm + 1] * (fac *= k);
605 kk = ((np - k) % np) + 1;
606 c[kk] = sum;
607 }
608 free_dvector(b, 1, m + 1);
609 free_dmatrix(a, 1, m + 1, 1, m + 1);
610 free_ivector(indx, 1, m + 1);
611 return (0);
612}
613
614
615//! Perform the Savitzky-Golay filtering process.
616/*
617 The results are in the \c y_filtered_data_p C array of pappso_double
618 values.
619 */
620char
622 double *y_filtered_data_p,
623 int data_point_count) const
624{
625 int np = m_params.nL + 1 + m_params.nR;
627 char retval;
628
629#if CONVOLVE_WITH_NR_CONVLV
630 c = dvector(1, data_point_count);
631 retval = sgcoeff(c, np, m_nL, m_nR, m_lD, m_m);
632 if(retval == 0)
633 convlv(y_data_p, data_point_count, c, np, 1, y_filtered_data_p);
634 free_dvector(c, 1, data_point_count);
635#else
636 int j;
637 long int k;
638 c = dvector(1, m_params.nL + m_params.nR + 1);
639 retval = sgcoeff(c, np, m_params.nL, m_params.nR, m_params.lD, m_params.m);
640 if(retval == 0)
641 {
642 qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
643 << "retval is 0";
644
645 for(k = 1; k <= m_params.nL; k++)
646 {
647 for(y_filtered_data_p[k] = 0.0, j = -m_params.nL; j <= m_params.nR; j++)
648 {
649 if(k + j >= 1)
650 {
651 y_filtered_data_p[k] +=
652 c[(j >= 0 ? j + 1 : m_params.nR + m_params.nL + 2 + j)] * y_data_p[k + j];
653 }
654 }
655 }
656 for(k = m_params.nL + 1; k <= data_point_count - m_params.nR; k++)
657 {
658 for(y_filtered_data_p[k] = 0.0, j = -m_params.nL; j <= m_params.nR; j++)
659 {
660 y_filtered_data_p[k] +=
661 c[(j >= 0 ? j + 1 : m_params.nR + m_params.nL + 2 + j)] * y_data_p[k + j];
662 }
663 }
664 for(k = data_point_count - m_params.nR + 1; k <= data_point_count; k++)
665 {
666 for(y_filtered_data_p[k] = 0.0, j = -m_params.nL; j <= m_params.nR; j++)
667 {
668 if(k + j <= data_point_count)
669 {
670 y_filtered_data_p[k] +=
671 c[(j >= 0 ? j + 1 : m_params.nR + m_params.nL + 2 + j)] * y_data_p[k + j];
672 }
673 }
674 }
675 }
676
677 free_dvector(c, 1, m_params.nR + m_params.nL + 1);
678#endif
679
680 return (retval);
681}
682
683
684} // namespace pappso
excetion to use when an item type is not recognized
uses Savitsky-Golay filter on trace
void free_dmatrix(pappso_double **m, long nrl, long nrh, long ncl, long nch) const
pappso_double * dvector(long nl, long nh) const
void buildFilterFromString(const QString &strBuildParams) override
build this filter using a string
void free_dvector(pappso_double *v, long nl, long nh) const
QString name() const override
pappso_double ** dmatrix(long nrl, long nrh, long ncl, long nch) const
QString toString() const override
Return a string with the textual representation of the configuration data.
void twofft(pappso_double data1[], pappso_double data2[], pappso_double fft1[], pappso_double fft2[], unsigned long n)
void realft(pappso_double data[], unsigned long n, int isign)
void free_ivector(int *v, long nl, long nh) const
char convlv(pappso_double data[], unsigned long n, pappso_double respns[], unsigned long m, int isign, pappso_double ans[])
void lubksb(pappso_double **a, int n, int *indx, pappso_double b[]) const
int * ivector(long nl, long nh) const
void ludcmp(pappso_double **a, int n, int *indx, pappso_double *d) const
void four1(pappso_double data[], unsigned long nn, int isign)
FilterSavitzkyGolay & operator=(const FilterSavitzkyGolay &other)
Trace & filter(Trace &data_points) const override
char sgcoeff(pappso_double c[], int np, int nl, int nr, int ld, int m) const
char runFilter(double *y_data_p, double *y_filtered_data_p, int data_point_count) const
Perform the Savitzky-Golay filtering process.
SavGolParams getParameters() const
A simple container of DataPoint instances.
Definition trace.h:152
tries to keep as much as possible monoisotopes, removing any possible C13 peaks and changes multichar...
Definition aa.cpp:39
double pappso_double
A type definition for doubles.
Definition types.h:60
#define SWAP(a, b)
Parameters for the Savitzky-Golay filter.
int lD
specifies the order of the derivative to extract from the Savitzky-Golay smoothing algorithm (for reg...
int m
order of the polynomial to use in the regression analysis leading to the Savitzky-Golay coefficients ...
int nR
number of data points on the right of the filtered point
int nL
number of data points on the left of the filtered point
bool convolveWithNr
set to false for best results