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