1
// -*- mode: C++; tab-width: 2; -*-
4
// --------------------------------------------------------------------------
5
// OpenMS Mass Spectrometry Framework
6
// --------------------------------------------------------------------------
7
// Copyright (C) 2003-2011 -- Oliver Kohlbacher, Knut Reinert
9
// This library is free software; you can redistribute it and/or
10
// modify it under the terms of the GNU Lesser General Public
11
// License as published by the Free Software Foundation; either
12
// version 2.1 of the License, or (at your option) any later version.
14
// This library is distributed in the hope that it will be useful,
15
// but WITHOUT ANY WARRANTY; without even the implied warranty of
16
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17
// Lesser General Public License for more details.
19
// You should have received a copy of the GNU Lesser General Public
20
// License along with this library; if not, write to the Free Software
21
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23
// --------------------------------------------------------------------------
24
// $Maintainer: Alexandra Zerck $
25
// $Authors: Eva Lange $
26
// --------------------------------------------------------------------------
28
#ifndef OPENMS_FILTERING_SMOOTHING_SAVITZKYGOLAYFILTER_H
29
#define OPENMS_FILTERING_SMOOTHING_SAVITZKYGOLAYFILTER_H
31
#include <OpenMS/DATASTRUCTURES/DefaultParamHandler.h>
32
#include <OpenMS/CONCEPT/ProgressLogger.h>
33
#include <OpenMS/KERNEL/MSExperiment.h>
35
#include <gsl/gsl_vector.h>
36
#include <gsl/gsl_matrix.h>
37
#include <gsl/gsl_linalg.h>
38
#include <gsl/gsl_permutation.h>
39
#include <gsl/gsl_pow_int.h>
44
@brief Computes the Savitzky-Golay filter coefficients using QR decomposition.
46
This class represents a Savitzky-Golay lowpass-filter. The idea of the Savitzky-Golay filter
47
is to find filtercoefficients that preserve higher moments, which means to approximate the underlying
48
function within the moving window by a polynomial of higher order (typically quadratic or quartic).
49
Therefore we least-squares fit for each data point a polynomial to all points \f$ f_i \f$ in the window
50
and set \f$g_i\f$ to be the value of that polynomial at position \f$ i \f$. This method is superior
51
to adjacent averaging because it tends to preserve features of the data such as peak height and width, which
52
are usually 'washed out' by adjacent averaging.
54
Because of the linearity of the problem, we can reduce the work computing by fitting in advance, for fictious data
55
consisiting of all zeros except for a singe 1 and then do the fits on the real data just by taking linear
56
combinations. There are a particular sets of filter coefficients \f$ c_n \f$ which accomplish the process of
57
polynomial least-squares fit inside a moving window. To get the symmetric coefficient-matrix
58
\f$C \in R^{frameSize \times frameSize}\f$ with
59
\f[ C= \left[ \begin{array}{cccc} c_{0,0} & c_{0,1} & \cdots & c_{0,frameSize-1} \\
60
\vdots & & & \vdots \\
61
c_{frameSize-1,0} & c_{frameSize-1,2} & \ldots & c_{frameSize-1,frameSize-1} \end{array} \right]\f]
62
The first (last) \f$ \frac{frameSize}{2} \f$ rows of \f$ C \f$ we need to smooth the first (last)
63
\f$ frameSize \f$ data points of the signal. So we use for the smoothing of the first data point the data
64
point itself and the next \f$ frameSize-1 \f$ future points. For the second point we take the first datapoint,
65
the data point itself and \f$ frameSize-2 \f$ of rightward data points... .
66
We compute the Matrix \f$ C \f$ by solving the underlying least-squares problems with the singular value decomposition.
67
Here we demonstrate the computation of the first row of a coefficient-matrix \f$ C \f$ for a Savitzky-Golay Filter
68
of order=3 and frameSize=5:
69
The design-matrix for the least-squares fit of a linear combination of 3 basis functions to 5 data points is:
70
\f[ A=\left[ \begin{array}{ccc} x_0^0 & x_0^1 & x_0^2 \\ \\
71
x_1^0 & x_1^1 & x_1^2 \\ \\
72
x_2^0 & x_2^1 & x_2^2 \\ \\
73
x_3^0 & x_3^1 & x_3^2 \\ \\
74
x_4^0 & x_4^1 & x_4^2 \end{array} \right]. \f]
75
To smooth the first data point we have to create a design-matrix with \f$ x=[0,\ldots, frameSize-1] \f$.
76
Now we have to solve the over-determined set of \f$ frameSize \f$ linear equations
78
where \f$ b=[1,0,\ldots,0] \f$ represents the fictious data.
79
Therefore we solve the normal equations of the least-squares problem
81
Now, it is possible to get
82
\f[ c_n=\sum_{m=0}^8 \{(A^TA)^{-1}\}_{0,m} n^m, \f]
83
with \f$ 0\le n \le 8\f$. Because we only need one row of the inverse matrix, it is possible to use LU decomposition with
84
only a single backsubstitution.
85
The vector \f$c=[c_0,\ldots,c_8] \f$ represents the wanted coefficients.
86
Note that the solution of a least-squares problem directly from the normal equations is faster than the singular value
87
decomposition but rather susceptible to roundoff error!
89
@note This filter works only for uniform profile data!
90
A polynom order of 4 is recommended.
91
The bigger the frame size the smoother the signal (the more detail information get lost!). The frame size corresponds to the number
92
of filter coefficients, so the width of the smoothing intervall is given by frame_size*spacing of the profile data.
94
@note The data must be sorted according to ascending m/z!
96
@htmlinclude OpenMS_SavitzkyGolayFilter.parameters
98
@ingroup SignalProcessing
100
class OPENMS_DLLAPI SavitzkyGolayFilter
101
: public ProgressLogger,
102
public DefaultParamHandler
106
SavitzkyGolayFilter();
109
virtual ~SavitzkyGolayFilter();
112
@brief Removed the noise from an MSSpectrum containing profile data.
114
template <typename PeakType>
115
void filter(MSSpectrum<PeakType>& spectrum)
117
UInt n = (UInt)spectrum.size();
119
typename MSSpectrum<PeakType>::iterator first = spectrum.begin();
120
typename MSSpectrum<PeakType>::iterator last = spectrum.end();
122
//copy the data AND META DATA to the output container
123
MSSpectrum<PeakType> output = spectrum;
132
int mid=(frame_size_/2);
135
typename MSSpectrum<PeakType>::iterator it_forward;
136
typename MSSpectrum<PeakType>::iterator it_help;
137
typename MSSpectrum<PeakType>::iterator out_it = output.begin();
139
// compute the transient on
140
for (i=0; i <= mid; ++i)
142
it_forward=(first-i);
145
for (j=0; j < frame_size_; ++j)
147
help+=it_forward->getIntensity()*coeffs_[(i+1)*frame_size_-1-j];
152
out_it->setPosition(first->getPosition());
153
out_it->setIntensity(std::max(0.0,help));
158
// compute the steady state output
160
while (first!=it_help)
162
it_forward=(first-mid);
165
for (j=0; j < frame_size_; ++j)
167
help+=it_forward->getIntensity()*coeffs_[mid*frame_size_+j];
172
out_it->setPosition(first->getPosition());
173
out_it->setIntensity(std::max(0.0,help));
178
// compute the transient off
179
for (i=(mid-1); i >= 0; --i)
181
it_forward=(first-(frame_size_-i-1));
184
for (j=0; j < frame_size_; ++j)
186
help+=it_forward->getIntensity()*coeffs_[i*frame_size_+j];
190
out_it->setPosition(first->getPosition());
191
out_it->setIntensity(std::max(0.0,help));
200
@brief Removed the noise from an MSExperiment containing profile data.
202
template <typename PeakType>
203
void filterExperiment(MSExperiment<PeakType>& map)
205
startProgress(0,map.size(),"smoothing data");
206
for (Size i = 0; i < map.size(); ++i)
216
std::vector<DoubleReal> coeffs_;
217
/// UInt of the filter kernel (number of pre-tabulated coefficients)
219
/// The order of the smoothing polynomial.
221
// Docu in base class
222
virtual void updateMembers_();
226
} // namespace OpenMS
227
#endif // OPENMS_FILTERING_SMOOTHING_SAVITZKYGOLAYFILTER_H