v2.0.0
Loading...
Searching...
No Matches
filterkernel.cpp
Go to the documentation of this file.
1//=============================================================================================================
12
13//=============================================================================================================
14// INCLUDES
15//=============================================================================================================
16
17#include "filterkernel.h"
18
19#include <math/numerics.h>
20
21#include "parksmcclellan.h"
22#include "cosinefilter.h"
23
24#include <algorithm>
25#include <iostream>
26
27//=============================================================================================================
28// QT INCLUDES
29//=============================================================================================================
30
31#include <QDebug>
32
33//=============================================================================================================
34// EIGEN INCLUDES
35//=============================================================================================================
36
37#include <Eigen/SparseCore>
38//#ifndef EIGEN_FFTW_DEFAULT
39//#define EIGEN_FFTW_DEFAULT
40//#endif
41#include <unsupported/Eigen/FFT>
42
43//=============================================================================================================
44// USED NAMESPACES
45//=============================================================================================================
46
47using namespace UTILSLIB;
48using namespace Eigen;
49
50//=============================================================================================================
51// INIT STATIC MEMBERS
52//=============================================================================================================
53
54QVector<UTILSLIB::FilterParameter> FilterKernel::m_designMethods ({
55 FilterParameter(QString("Cosine"), QString("A cosine filter")),
56 FilterParameter(QString("Tschebyscheff"), QString("A tschebyscheff filter"))
57// FilterParameter(QString("External"), QString("An external filter"))
58});
59QVector<UTILSLIB::FilterParameter> FilterKernel::m_filterTypes ({
60 FilterParameter(QString("LPF"), QString("An LPF filter")),
61 FilterParameter(QString("HPF"), QString("An HPF filter")),
62 FilterParameter(QString("BPF"), QString("A BPF filter")),
63 FilterParameter(QString("NOTCH"), QString("A NOTCH filter")),
64 FilterParameter(QString("UNKNOWN"), QString("An UNKNOWN filter"))
65});
66
67//=============================================================================================================
68// DEFINE MEMBER METHODS
69//=============================================================================================================
70
72: m_sFreq(1000)
73, m_dCenterFreq(0.5)
74, m_dBandwidth(0.1)
75, m_dParksWidth(0.1)
76, m_dLowpassFreq(40)
77, m_dHighpassFreq(4)
78, m_iFilterOrder(80)
79, m_iDesignMethod(m_designMethods.indexOf(FilterParameter("Cosine")))
80, m_iFilterType(m_filterTypes.indexOf(FilterParameter("BPF")))
81, m_sFilterName("Unknown")
82, m_sFilterShortDescription("")
83{
84 designFilter();
85}
86
87//=============================================================================================================
88
89FilterKernel::FilterKernel(const QString& sFilterName,
90 int iFilterType,
91 int iOrder,
92 double dCenterfreq,
93 double dBandwidth,
94 double dParkswidth,
95 double dSFreq,
96 int iDesignMethod)
97: m_sFreq(dSFreq)
98, m_dCenterFreq(dCenterfreq)
99, m_dBandwidth(dBandwidth)
100, m_dParksWidth(dParkswidth)
101, m_iFilterOrder(iOrder)
102, m_iDesignMethod(iDesignMethod)
103, m_iFilterType(iFilterType)
104, m_sFilterName(sFilterName)
105, m_sFilterShortDescription()
106{
107 if(iOrder < 9) {
108 qWarning() << "[FilterKernel::FilterKernel] Less than 9 taps were provided. Setting number of taps to 9.";
109 }
110
111 designFilter();
112}
113
114//=============================================================================================================
115
117{
118 int iFftLength, exp;
119
120 iFftLength = iDataSize + m_vecCoeff.cols();
121 exp = ceil(Numerics::log2(iFftLength));
122 iFftLength = pow(2, exp);
123
124 // Transform coefficients anew if needed
125 if(m_vecCoeff.cols() != (iFftLength/2+1)) {
126 fftTransformCoeffs(iFftLength);
127 }
128}
129
130//=============================================================================================================
131
132RowVectorXd FilterKernel::applyConvFilter(const RowVectorXd& vecData,
133 bool bKeepOverhead) const
134{
135 //Do zero padding or mirroring depending on user input
136 RowVectorXd vecDataZeroPad = RowVectorXd::Zero(2*m_vecCoeff.cols() + vecData.cols());
137 RowVectorXd vecFilteredTime = RowVectorXd::Zero(2*m_vecCoeff.cols() + vecData.cols());
138
139 vecDataZeroPad.segment(m_vecCoeff.cols(), vecData.cols()) = vecData;
140
141 //Do the convolution
142 for(int i = m_vecCoeff.cols(); i < vecFilteredTime.cols(); i++) {
143 vecFilteredTime(i-m_vecCoeff.cols()) = vecDataZeroPad.segment(i-m_vecCoeff.cols(),m_vecCoeff.cols()) * m_vecCoeff.transpose();
144 }
145
146 //Return filtered data
147 if(!bKeepOverhead) {
148 return vecFilteredTime.segment(m_vecCoeff.cols()/2, vecData.cols());
149 }
150
151 return vecFilteredTime.head(vecData.cols()+m_vecCoeff.cols());
152}
153
154//=============================================================================================================
155
156void FilterKernel::applyFftFilter(RowVectorXd& vecData,
157 bool bKeepOverhead)
158{
159 #ifdef EIGEN_FFTW_DEFAULT
160 fftw_make_planner_thread_safe();
161 #endif
162
163 // Make sure we always have the correct FFT length for the given input data and filter overlap
164 int iFftLength = vecData.cols() + m_vecCoeff.cols();
165 int exp = ceil(Numerics::log2(iFftLength));
166 iFftLength = pow(2, exp);
167
168 // Transform coefficients anew if needed
169 if(m_vecFftCoeff.cols() != (iFftLength/2+1)) {
170 fftTransformCoeffs(iFftLength);
171 }
172
173 //generate fft object
174 Eigen::FFT<double> fft;
175 fft.SetFlag(fft.HalfSpectrum);
176
177 // Zero padd if necessary. Please note: The zero padding in Eigen's FFT is only working for column vectors -> We have to zero pad manually here
178 int iOriginalSize = vecData.cols();
179 if (vecData.cols() < iFftLength) {
180 int iResidual = iFftLength - vecData.cols();
181 vecData.conservativeResize(iFftLength);
182 vecData.tail(iResidual).setZero();
183 }
184
185 //fft-transform data sequence
186 RowVectorXcd vecFreqData;
187 fft.fwd(vecFreqData, vecData, iFftLength);
188
189 //perform frequency-domain filtering
190 vecFreqData = m_vecFftCoeff.array() * vecFreqData.array();
191
192 //inverse-FFT
193 fft.inv(vecData, vecFreqData);
194
195 //Return filtered data
196 if(!bKeepOverhead) {
197 vecData = vecData.segment(m_vecCoeff.cols()/2, iOriginalSize).eval();
198 } else {
199 vecData = vecData.head(iOriginalSize + m_vecCoeff.cols()).eval();
200 }
201}
202
203//=============================================================================================================
204
206{
207 return m_sFilterName;
208}
209
210//=============================================================================================================
211
212void FilterKernel::setName(const QString& sFilterName)
213{
214 m_sFilterName = sFilterName;
215}
216
217//=============================================================================================================
218
220{
221 return m_sFreq;
222}
223
224//=============================================================================================================
225
227{
228 m_sFreq = dSFreq;
229}
230
231//=============================================================================================================
232
234{
235 return m_iFilterOrder;
236}
237
238//=============================================================================================================
239
241{
242 m_iFilterOrder = iOrder;
243}
244
245//=============================================================================================================
246
248{
249 return m_dCenterFreq;
250}
251
252//=============================================================================================================
253
254void FilterKernel::setCenterFrequency(double dCenterFreq)
255{
256 m_dCenterFreq = dCenterFreq;
257}
258
259//=============================================================================================================
260
262{
263 return m_dBandwidth;
264}
265
266//=============================================================================================================
267
268void FilterKernel::setBandwidth(double dBandwidth)
269{
270 m_dBandwidth = dBandwidth;
271}
272
273//=============================================================================================================
274
276{
277 return m_dParksWidth;
278}
279
280//=============================================================================================================
281
282void FilterKernel::setParksWidth(double dParksWidth)
283{
284 m_dParksWidth = dParksWidth;
285}
286
287//=============================================================================================================
288
290{
291 return m_dHighpassFreq;
292}
293
294//=============================================================================================================
295
296void FilterKernel::setHighpassFreq(double dHighpassFreq)
297{
298 m_dHighpassFreq = dHighpassFreq;
299}
300
301//=============================================================================================================
302
304{
305 return m_dLowpassFreq;
306}
307
308//=============================================================================================================
309
310void FilterKernel::setLowpassFreq(double dLowpassFreq)
311{
312 m_dLowpassFreq = dLowpassFreq;
313}
314
315//=============================================================================================================
316
317Eigen::RowVectorXd FilterKernel::getCoefficients() const
318{
319 return m_vecCoeff;
320}
321
322//=============================================================================================================
323
324void FilterKernel::setCoefficients(const Eigen::RowVectorXd& vecCoeff)
325{
326 m_vecCoeff = vecCoeff;
327}
328
329//=============================================================================================================
330
331Eigen::RowVectorXcd FilterKernel::getFftCoefficients() const
332{
333 return m_vecFftCoeff;
334}
335
336//=============================================================================================================
337
338void FilterKernel::setFftCoefficients(const Eigen::RowVectorXcd& vecFftCoeff)
339{
340 m_vecFftCoeff = vecFftCoeff;
341}
342
343//=============================================================================================================
344
345bool FilterKernel::fftTransformCoeffs(int iFftLength)
346{
347 #ifdef EIGEN_FFTW_DEFAULT
348 fftw_make_planner_thread_safe();
349 #endif
350
351 if(m_vecCoeff.cols() > iFftLength) {
352 std::cout <<"[FilterKernel::fftTransformCoeffs] The number of filter taps is bigger than the FFT length."<< std::endl;
353 return false;
354 }
355
356 //generate fft object
357 Eigen::FFT<double> fft;
358 fft.SetFlag(fft.HalfSpectrum);
359
360 // Zero padd if necessary. Please note: The zero padding in Eigen's FFT is only working for column vectors -> We have to zero pad manually here
361 RowVectorXd vecInputFft;
362 if (m_vecCoeff.cols() < iFftLength) {
363 vecInputFft.setZero(iFftLength);
364 vecInputFft.block(0,0,1,m_vecCoeff.cols()) = m_vecCoeff;
365 } else {
366 vecInputFft = m_vecCoeff;
367 }
368
369 //fft-transform filter coeffs
370 RowVectorXcd vecFreqData;
371 fft.fwd(vecFreqData, vecInputFft, iFftLength);
372 m_vecFftCoeff = vecFreqData;;
373
374 return true;
375}
376
377//=============================================================================================================
378
379void FilterKernel::designFilter()
380{
381 // Make sure we only use a minimum needed FFT size
382 int iFftLength = m_iFilterOrder;
383 int exp = ceil(Numerics::log2(iFftLength));
384 iFftLength = pow(2, exp);
385
386 if(m_iDesignMethod == 0) {
387 double dSmallestFeatureHz = m_dParksWidth * (m_sFreq / 2.0);
388 const double dLowEdgeHz = std::max(0.0, (m_dCenterFreq - m_dBandwidth / 2.0) * (m_sFreq / 2.0));
389 const double dHighEdgeHz = std::max(0.0, (m_dCenterFreq + m_dBandwidth / 2.0) * (m_sFreq / 2.0));
390 const double dSingleCutoffHz = std::max(0.0, m_dCenterFreq * (m_sFreq / 2.0));
391
392 if(m_iFilterType == 0 || m_iFilterType == 1) {
393 if(dSingleCutoffHz > 0.0) {
394 dSmallestFeatureHz = std::min(dSmallestFeatureHz, dSingleCutoffHz);
395 }
396 } else {
397 if(dLowEdgeHz > 0.0) {
398 dSmallestFeatureHz = std::min(dSmallestFeatureHz, dLowEdgeHz);
399 }
400 if(dHighEdgeHz > 0.0) {
401 dSmallestFeatureHz = std::min(dSmallestFeatureHz, dHighEdgeHz);
402 }
403 }
404
405 dSmallestFeatureHz = std::max(0.5, dSmallestFeatureHz);
406
407 const int iRecommendedFftLength = static_cast<int>(std::ceil((m_sFreq / dSmallestFeatureHz) * 8.0));
408 if(iRecommendedFftLength > iFftLength) {
409 int iRecommendedExp = ceil(Numerics::log2(iRecommendedFftLength));
410 iFftLength = pow(2, iRecommendedExp);
411 }
412 }
413
414 switch(m_iDesignMethod) {
415 case 1: {
416 ParksMcClellan filter(m_iFilterOrder,
417 m_dCenterFreq,
418 m_dBandwidth,
419 m_dParksWidth,
420 static_cast<ParksMcClellan::TPassType>(m_iFilterType));
421 m_vecCoeff = filter.FirCoeff;
422
423 //fft-transform m_vecCoeff in order to be able to perform frequency-domain filtering
424 fftTransformCoeffs(iFftLength);
425
426 break;
427 }
428
429 case 0: {
430 CosineFilter filtercos;
431
432 switch(m_iFilterType) {
433 case 0:
434 filtercos = CosineFilter(iFftLength,
435 (m_dCenterFreq)*(m_sFreq/2.),
436 m_dParksWidth*(m_sFreq/2),
437 (m_dCenterFreq)*(m_sFreq/2),
438 m_dParksWidth*(m_sFreq/2),
439 m_sFreq,
440 static_cast<CosineFilter::TPassType>(m_iFilterType));
441
442 break;
443
444 case 1:
445 filtercos = CosineFilter(iFftLength,
446 (m_dCenterFreq)*(m_sFreq/2),
447 m_dParksWidth*(m_sFreq/2),
448 (m_dCenterFreq)*(m_sFreq/2),
449 m_dParksWidth*(m_sFreq/2),
450 m_sFreq,
451 static_cast<CosineFilter::TPassType>(m_iFilterType));
452
453 break;
454
455 case 2:
456 filtercos = CosineFilter(iFftLength,
457 (m_dCenterFreq + m_dBandwidth/2)*(m_sFreq/2),
458 m_dParksWidth*(m_sFreq/2),
459 (m_dCenterFreq - m_dBandwidth/2)*(m_sFreq/2),
460 m_dParksWidth*(m_sFreq/2),
461 m_sFreq,
462 static_cast<CosineFilter::TPassType>(m_iFilterType));
463
464 break;
465 }
466
467 //This filter is designed in the frequency domain, hence the time domain impulse response need to be shortend by the users dependent number of taps
468 m_vecCoeff.resize(m_iFilterOrder);
469
470 m_vecCoeff.head(m_iFilterOrder/2) = filtercos.m_vecCoeff.tail(m_iFilterOrder/2);
471 m_vecCoeff.tail(m_iFilterOrder/2) = filtercos.m_vecCoeff.head(m_iFilterOrder/2);
472
473 //Now generate the fft version of the shortened impulse response
474 fftTransformCoeffs(iFftLength);
475
476 break;
477 }
478 }
479
480 switch(m_iFilterType) {
481 case 0:
482 m_dLowpassFreq = 0;
483 m_dHighpassFreq = m_dCenterFreq*(m_sFreq/2);
484 break;
485
486 case 1:
487 m_dLowpassFreq = m_dCenterFreq*(m_sFreq/2);
488 m_dHighpassFreq = 0;
489 break;
490
491 case 2:
492 m_dLowpassFreq = (m_dCenterFreq + m_dBandwidth/2)*(m_sFreq/2);
493 m_dHighpassFreq = (m_dCenterFreq - m_dBandwidth/2)*(m_sFreq/2);
494 break;
495 }
497}
498
499//=============================================================================================================
500
502{
503 QString description(m_designMethods.at(m_iDesignMethod).getName() + " - " + \
504 QString::number(m_dHighpassFreq,'g',4) + "Hz to " + QString::number(m_dLowpassFreq,'g',4) + "Hz - " \
505 "Ord: " + QString::number(m_iFilterOrder));
506 return description;
507}
508
509//=============================================================================================================
510
512{
513 if(m_iDesignMethod < 0){
514 return m_designMethods.at(0);
515 }
516 return m_designMethods.at(m_iDesignMethod);
517}
518
519//=============================================================================================================
520
522{
523 if(m_iFilterType < 0){
524 return m_filterTypes.at(0);
525 }
526 return m_filterTypes.at(m_iFilterType);
527}
528
529//=============================================================================================================
530
531void FilterKernel::setDesignMethod(int iDesignMethod)
532{
533 if(iDesignMethod < 0){
534 m_iDesignMethod = 0;
535 } else {
536 m_iDesignMethod = iDesignMethod;
537 }
538}
539
540//=============================================================================================================
541
542void FilterKernel::setFilterType(int iFilterType)
543{
544 if(iFilterType < 0){
545 m_iFilterType = 0;
546 } else {
547 m_iFilterType = iFilterType;
548 }
549}
550
551//=============================================================================================================
552
557
558//=============================================================================================================
559
561:FilterParameter(sName,"")
562{
563}
564
565//=============================================================================================================
566
568 QString sDescription)
569: m_sName(sName)
570, m_sDescription(sDescription)
571{
572
573}
574
575//=============================================================================================================
576
578{
579 return m_sName;
580}
581
582//=============================================================================================================
General numerical helpers: GCD, log2, histogram binning, baseline rescaling, sparsity tests.
Frequency-domain cosine-tapered (raised-cosine) FIR filter design.
Linear-phase FIR filter kernel with overlap-add FFT convolution back-end.
Parks–McClellan equiripple FIR design via the Remez exchange algorithm.
Shared utilities (I/O helpers, spectral analysis, layout management, warp algorithms).
Eigen::RowVectorXd m_vecCoeff
Named filter-design parameter descriptor holding a human-readable name and description (e....
double getBandwidth() const
void setSamplingFrequency(double dSFreq)
double getParksWidth() const
double getCenterFrequency() const
void setCenterFrequency(double dCenterFreq)
double getSamplingFrequency() const
QString getShortDescription() const
Eigen::RowVectorXcd getFftCoefficients() const
void applyFftFilter(Eigen::RowVectorXd &vecData, bool bKeepOverhead=false)
void setHighpassFreq(double dHighpassFreq)
void setCoefficients(const Eigen::RowVectorXd &vecCoeff)
QString getName() const
static QVector< FilterParameter > m_designMethods
Eigen::RowVectorXd applyConvFilter(const Eigen::RowVectorXd &vecData, bool bKeepOverhead=false) const
void setBandwidth(double dBandwidth)
void prepareFilter(int iDataSize)
void setFilterOrder(int iOrder)
void setFftCoefficients(const Eigen::RowVectorXcd &vecFftCoeff)
void setParksWidth(double dParksWidth)
FilterKernel()
FilterKernel creates a default FilterKernel object.
void setLowpassFreq(double dLowpassFreq)
static QVector< FilterParameter > m_filterTypes
void setName(const QString &sFilterName)
Eigen::RowVectorXd getCoefficients() const
double getLowpassFreq() const
void setFilterType(int iFilterType)
double getHighpassFreq() const
FilterParameter getDesignMethod() const
void setDesignMethod(int iDesignMethod)
FilterParameter getFilterType() const
static double log2(const T d)
Definition numerics.h:204