v2.0.0
Loading...
Searching...
No Matches
firfilter.cpp
Go to the documentation of this file.
1//=============================================================================================================
12
13//=============================================================================================================
14// INCLUDES
15//=============================================================================================================
16
17#include "firfilter.h"
18
19#include <algorithm>
20#include <cmath>
21#include <complex>
22#include <vector>
23
24//=============================================================================================================
25// EIGEN INCLUDES
26//=============================================================================================================
27
28#include <Eigen/Core>
29#include <unsupported/Eigen/FFT>
30
31//=============================================================================================================
32// QT INCLUDES
33//=============================================================================================================
34
35#include <QString>
36
37//=============================================================================================================
38// USED NAMESPACES
39//=============================================================================================================
40
41using namespace UTILSLIB;
42using namespace Eigen;
43
44//=============================================================================================================
45// STATIC DEFINITIONS
46//=============================================================================================================
47
49 FilterType type,
50 double dCutoffLow,
51 double dCutoffHigh,
52 double dSFreq,
53 double dTransition,
54 DesignMethod method)
55{
56 // FilterKernel frequency encoding (all values normalised to Nyquist = sFreq/2):
57 //
58 // LPF : dCenterfreq = dCutoffLow / nyquist, dBandwidth = 0
59 // HPF : dCenterfreq = dCutoffLow / nyquist, dBandwidth = 0
60 // BPF : dCenterfreq = (lo+hi) / sFreq, dBandwidth = (hi-lo) / nyquist
61 // NOTCH: dCenterfreq = (lo+hi) / sFreq, dBandwidth = (hi-lo) / nyquist
62 //
63 // dParkswidth = dTransition / nyquist
64
65 const double nyquist = dSFreq / 2.0;
66
67 double dCenterfreq = 0.0;
68 double dBandwidth = 0.0;
69 const double dParkswidth = dTransition / nyquist;
70
71 int iFilterType = static_cast<int>(type); // LPF=0, HPF=1, BPF=2, NOTCH=3
72
73 switch (type) {
74 case LowPass:
75 case HighPass:
76 dCenterfreq = dCutoffLow / nyquist;
77 dBandwidth = 0.0;
78 break;
79 case BandPass:
80 case BandStop:
81 dCenterfreq = (dCutoffLow + dCutoffHigh) / dSFreq; // = centre / nyquist normalised to [0,1]
82 dBandwidth = (dCutoffHigh - dCutoffLow) / nyquist;
83 break;
84 }
85
86 QString sName;
87 switch (type) {
88 case LowPass:
89 sName = QStringLiteral("LP_%1Hz").arg(dCutoffLow);
90 break;
91 case HighPass:
92 sName = QStringLiteral("HP_%1Hz").arg(dCutoffLow);
93 break;
94 case BandPass:
95 sName = QStringLiteral("BP_%1-%2Hz").arg(dCutoffLow).arg(dCutoffHigh);
96 break;
97 case BandStop:
98 sName = QStringLiteral("BS_%1-%2Hz").arg(dCutoffLow).arg(dCutoffHigh);
99 break;
100 }
101
102 return FilterKernel(sName,
103 iFilterType,
104 iOrder,
105 dCenterfreq,
106 dBandwidth,
107 dParkswidth,
108 dSFreq,
109 static_cast<int>(method));
110}
111
112//=============================================================================================================
113
114RowVectorXd FirFilter::apply(const RowVectorXd& vecData,
115 FilterKernel& kernel)
116{
117 RowVectorXd work = vecData;
118 kernel.applyFftFilter(work, /*bKeepOverhead=*/false);
119 return work;
120}
121
122//=============================================================================================================
123
124RowVectorXd FirFilter::applyZeroPhase(const RowVectorXd& vecData,
125 FilterKernel& kernel)
126{
127 // Forward pass
128 RowVectorXd work = vecData;
129 kernel.applyFftFilter(work, false);
130
131 // Reverse pass
132 RowVectorXd rev = work.reverse();
133 kernel.applyFftFilter(rev, false);
134
135 return rev.reverse();
136}
137
138//=============================================================================================================
139
140MatrixXd FirFilter::applyZeroPhaseMatrix(const MatrixXd& matData,
141 FilterKernel& kernel,
142 const RowVectorXi& vecPicks)
143{
144 MatrixXd result = matData;
145
146 if (vecPicks.size() == 0) {
147 // All rows
148 for (int i = 0; i < result.rows(); ++i) {
149 RowVectorXd row = result.row(i);
150 result.row(i) = applyZeroPhase(row, kernel);
151 }
152 } else {
153 for (int k = 0; k < vecPicks.size(); ++k) {
154 int i = vecPicks(k);
155 if (i < 0 || i >= result.rows())
156 continue;
157 RowVectorXd row = result.row(i);
158 result.row(i) = applyZeroPhase(row, kernel);
159 }
160 }
161
162 return result;
163}
164
165//=============================================================================================================
166
167RowVectorXd FirFilter::designMne(double dSFreq, double dLFreq, double dHFreq)
168{
169 // Adapted from mne.filter create_filter / _triage_filter_params / _firwin_design
170 // (MNE-Python, BSD-3-Clause), fir_window="hamming", fir_design="firwin", phase="zero".
171 const double nyquist = dSFreq / 2.0;
172 const bool highPass = dLFreq > 0.0;
173 const bool lowPass = dHFreq > 0.0;
174 const double lTrans = highPass ? std::min(std::max(0.25 * dLFreq, 2.0), dLFreq) : 0.0;
175 const double hTrans = lowPass ? std::min(std::max(0.25 * dHFreq, 2.0), nyquist - dHFreq) : 0.0;
176 const double narrowest = std::min(highPass ? lTrans : INFINITY, lowPass ? hTrans : INFINITY);
177
178 int nTaps = std::max(static_cast<int>(std::ceil(3.3 / narrowest * dSFreq)), 1);
179 nTaps += (nTaps - 1) % 2;
180
181 // Piecewise-constant gain as (frequency, gain) points from 0 to Nyquist
182 std::vector<double> freq{0.0};
183 std::vector<double> gain{highPass ? 0.0 : 1.0};
184 if (highPass) {
185 freq.insert(freq.end(), {dLFreq - lTrans, dLFreq});
186 gain.insert(gain.end(), {0.0, 1.0});
187 }
188 if (lowPass) {
189 freq.insert(freq.end(), {dHFreq, dHFreq + hTrans});
190 gain.insert(gain.end(), {1.0, 0.0});
191 }
192 freq.push_back(nyquist);
193 gain.push_back(gain.back());
194
195 // Each gain step is one windowed-sinc low-pass of the length its transition needs, centred.
196 RowVectorXd h = RowVectorXd::Zero(nTaps);
197 if (gain.back() == 1.0) {
198 h(nTaps / 2) = 1.0;
199 }
200 for (int k = static_cast<int>(freq.size()) - 2; k >= 0; --k) {
201 if (gain[k] == gain[k + 1]) {
202 continue;
203 }
204 const double transition = (freq[k + 1] - freq[k]) / nyquist / 2.0;
205 int nStep = static_cast<int>(std::nearbyint(3.3 / transition)); // Python round(): half to even
206 nStep += 1 - nStep % 2;
207 const double cutoff = (freq[k + 1] + freq[k]) / 2.0 / nyquist;
208 RowVectorXd step(nStep);
209 for (int n = 0; n < nStep; ++n) {
210 const double m = n - (nStep - 1) / 2.0;
211 const double sinc = m == 0.0 ? 1.0 : std::sin(M_PI * cutoff * m) / (M_PI * cutoff * m);
212 const double window = nStep > 1 ? 0.54 - 0.46 * std::cos(2.0 * M_PI * n / (nStep - 1)) : 1.0;
213 step(n) = cutoff * sinc * window;
214 }
215 step /= step.sum();
216 const int offset = (nTaps - nStep) / 2;
217 h.segment(offset, nStep) += (gain[k] == 0.0 ? -1.0 : 1.0) * step;
218 }
219 return h;
220}
221
222//=============================================================================================================
223
224MatrixXd FirFilter::filterData(const MatrixXd& matData, double dSFreq, double dLFreq, double dHFreq)
225{
226 // Adapted from mne.filter._overlap_add_filter / mne.cuda._smart_pad (MNE-Python, BSD-3-Clause).
227 const RowVectorXd h = designMne(dSFreq, dLFreq, dHFreq);
228 const Index nTimes = matData.cols();
229 const Index nH = h.size();
230 if (nTimes == 0 || nH == 1) {
231 return matData * (nH == 1 ? h(0) : 1.0);
232 }
233 const Index nEdge = std::min(nH, nTimes) - 1;
234 const Index nExt = nTimes + 2 * nEdge;
235 const Index nFft = nExt + nH - 1;
236
237 FFT<double> fft;
238 RowVectorXd hPadded = RowVectorXd::Zero(nFft);
239 hPadded.head(nH) = h;
240 RowVectorXcd hSpec;
241 fft.fwd(hSpec, hPadded);
242
243 MatrixXd out(matData.rows(), nTimes);
244 RowVectorXd ext = RowVectorXd::Zero(nFft);
245 RowVectorXcd spec;
246 RowVectorXd conv;
247 for (Index r = 0; r < matData.rows(); ++r) {
248 const RowVectorXd x = matData.row(r);
249 // "reflect_limited" = odd reflection about the end samples (nEdge < nTimes, so no zero part)
250 ext.setZero();
251 for (Index i = 0; i < nEdge; ++i) {
252 ext(i) = 2.0 * x(0) - x(nEdge - i);
253 ext(nEdge + nTimes + i) = 2.0 * x(nTimes - 1) - x(nTimes - 2 - i);
254 }
255 ext.segment(nEdge, nTimes) = x;
256 fft.fwd(spec, ext);
257 spec = spec.cwiseProduct(hSpec);
258 fft.inv(conv, spec);
259 out.row(r) = conv.segment(nEdge + (nH - 1) / 2, nTimes);
260 }
261 return out;
262}
#define M_PI
Discoverable design / apply façade over the UTILSLIB::FilterKernel FIR engine.
Shared utilities (I/O helpers, spectral analysis, layout management, warp algorithms).
The FilterKernel class provides methods to create/design a FIR filter kernel.
void applyFftFilter(Eigen::RowVectorXd &vecData, bool bKeepOverhead=false)
FilterType
FIR filter type — mirrors IirFilter::FilterType for a uniform API.
Definition firfilter.h:70
static Eigen::MatrixXd filterData(const Eigen::MatrixXd &matData, double dSFreq, double dLFreq, double dHFreq)
static Eigen::MatrixXd applyZeroPhaseMatrix(const Eigen::MatrixXd &matData, FilterKernel &kernel, const Eigen::RowVectorXi &vecPicks=Eigen::RowVectorXi())
static Eigen::RowVectorXd designMne(double dSFreq, double dLFreq, double dHFreq)
DesignMethod
FIR design method — wraps FilterKernel's iDesignMethod integer codes.
Definition firfilter.h:82
static Eigen::RowVectorXd applyZeroPhase(const Eigen::RowVectorXd &vecData, FilterKernel &kernel)
static FilterKernel design(int iOrder, FilterType type, double dCutoffLow, double dCutoffHigh, double dSFreq, double dTransition=5.0, DesignMethod method=Cosine)
Definition firfilter.cpp:48
static Eigen::RowVectorXd apply(const Eigen::RowVectorXd &vecData, FilterKernel &kernel)