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({FilterParameter(QString("LPF"), QString("An LPF filter")),
60 FilterParameter(QString("HPF"), QString("An HPF filter")),
61 FilterParameter(QString("BPF"), QString("A BPF filter")),
62 FilterParameter(QString("NOTCH"), QString("A NOTCH filter")),
63 FilterParameter(QString("UNKNOWN"), QString("An UNKNOWN filter"))});
64
65//=============================================================================================================
66// DEFINE MEMBER METHODS
67//=============================================================================================================
68
70: m_sFreq(1000)
71, m_dCenterFreq(0.5)
72, m_dBandwidth(0.1)
73, m_dParksWidth(0.1)
74, m_dLowpassFreq(40)
75, m_dHighpassFreq(4)
76, m_iFilterOrder(80)
77, m_iDesignMethod(m_designMethods.indexOf(FilterParameter("Cosine")))
78, m_iFilterType(m_filterTypes.indexOf(FilterParameter("BPF")))
79, m_sFilterName("Unknown")
80, m_sFilterShortDescription("")
81{
82 designFilter();
83}
84
85//=============================================================================================================
86
87FilterKernel::FilterKernel(const QString& sFilterName,
88 int iFilterType,
89 int iOrder,
90 double dCenterfreq,
91 double dBandwidth,
92 double dParkswidth,
93 double dSFreq,
94 int iDesignMethod)
95: m_sFreq(dSFreq)
96, m_dCenterFreq(dCenterfreq)
97, m_dBandwidth(dBandwidth)
98, m_dParksWidth(dParkswidth)
99, m_iFilterOrder(iOrder)
100, m_iDesignMethod(iDesignMethod)
101, m_iFilterType(iFilterType)
102, m_sFilterName(sFilterName)
103, m_sFilterShortDescription()
104{
105 if (iOrder < 9) {
106 qWarning() << "[FilterKernel::FilterKernel] Less than 9 taps were provided. Setting number of taps to 9.";
107 }
108
109 designFilter();
110}
111
112//=============================================================================================================
113
115{
116 int iFftLength, exp;
117
118 iFftLength = iDataSize + m_vecCoeff.cols();
119 exp = ceil(Numerics::log2(iFftLength));
120 iFftLength = pow(2, exp);
121
122 // Transform coefficients anew if needed
123 if (m_vecCoeff.cols() != (iFftLength / 2 + 1)) {
124 fftTransformCoeffs(iFftLength);
125 }
126}
127
128//=============================================================================================================
129
130RowVectorXd FilterKernel::applyConvFilter(const RowVectorXd& vecData,
131 bool bKeepOverhead) const
132{
133 //Do zero padding or mirroring depending on user input
134 RowVectorXd vecDataZeroPad = RowVectorXd::Zero(2 * m_vecCoeff.cols() + vecData.cols());
135 RowVectorXd vecFilteredTime = RowVectorXd::Zero(2 * m_vecCoeff.cols() + vecData.cols());
136
137 vecDataZeroPad.segment(m_vecCoeff.cols(), vecData.cols()) = vecData;
138
139 //Do the convolution
140 for (int i = m_vecCoeff.cols(); i < vecFilteredTime.cols(); i++) {
141 vecFilteredTime(i - m_vecCoeff.cols()) = vecDataZeroPad.segment(i - m_vecCoeff.cols(), m_vecCoeff.cols()) * m_vecCoeff.transpose();
142 }
143
144 //Return filtered data
145 if (!bKeepOverhead) {
146 return vecFilteredTime.segment(m_vecCoeff.cols() / 2, vecData.cols());
147 }
148
149 return vecFilteredTime.head(vecData.cols() + m_vecCoeff.cols());
150}
151
152//=============================================================================================================
153
154void FilterKernel::applyFftFilter(RowVectorXd& vecData,
155 bool bKeepOverhead)
156{
157#ifdef EIGEN_FFTW_DEFAULT
158 fftw_make_planner_thread_safe();
159#endif
160
161 // Make sure we always have the correct FFT length for the given input data and filter overlap
162 int iFftLength = vecData.cols() + m_vecCoeff.cols();
163 int exp = ceil(Numerics::log2(iFftLength));
164 iFftLength = pow(2, exp);
165
166 // Transform coefficients anew if needed
167 if (m_vecFftCoeff.cols() != (iFftLength / 2 + 1)) {
168 fftTransformCoeffs(iFftLength);
169 }
170
171 //generate fft object
172 Eigen::FFT<double> fft;
173 fft.SetFlag(fft.HalfSpectrum);
174
175 // 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
176 int iOriginalSize = vecData.cols();
177 if (vecData.cols() < iFftLength) {
178 int iResidual = iFftLength - vecData.cols();
179 vecData.conservativeResize(iFftLength);
180 vecData.tail(iResidual).setZero();
181 }
182
183 //fft-transform data sequence
184 RowVectorXcd vecFreqData;
185 fft.fwd(vecFreqData, vecData, iFftLength);
186
187 //perform frequency-domain filtering
188 vecFreqData = m_vecFftCoeff.array() * vecFreqData.array();
189
190 //inverse-FFT
191 fft.inv(vecData, vecFreqData);
192
193 //Return filtered data
194 if (!bKeepOverhead) {
195 vecData = vecData.segment(m_vecCoeff.cols() / 2, iOriginalSize).eval();
196 } else {
197 vecData = vecData.head(iOriginalSize + m_vecCoeff.cols()).eval();
198 }
199}
200
201//=============================================================================================================
202
204{
205 return m_sFilterName;
206}
207
208//=============================================================================================================
209
210void FilterKernel::setName(const QString& sFilterName)
211{
212 m_sFilterName = sFilterName;
213}
214
215//=============================================================================================================
216
218{
219 return m_sFreq;
220}
221
222//=============================================================================================================
223
225{
226 m_sFreq = dSFreq;
227}
228
229//=============================================================================================================
230
232{
233 return m_iFilterOrder;
234}
235
236//=============================================================================================================
237
239{
240 m_iFilterOrder = iOrder;
241}
242
243//=============================================================================================================
244
246{
247 return m_dCenterFreq;
248}
249
250//=============================================================================================================
251
252void FilterKernel::setCenterFrequency(double dCenterFreq)
253{
254 m_dCenterFreq = dCenterFreq;
255}
256
257//=============================================================================================================
258
260{
261 return m_dBandwidth;
262}
263
264//=============================================================================================================
265
266void FilterKernel::setBandwidth(double dBandwidth)
267{
268 m_dBandwidth = dBandwidth;
269}
270
271//=============================================================================================================
272
274{
275 return m_dParksWidth;
276}
277
278//=============================================================================================================
279
280void FilterKernel::setParksWidth(double dParksWidth)
281{
282 m_dParksWidth = dParksWidth;
283}
284
285//=============================================================================================================
286
288{
289 return m_dHighpassFreq;
290}
291
292//=============================================================================================================
293
294void FilterKernel::setHighpassFreq(double dHighpassFreq)
295{
296 m_dHighpassFreq = dHighpassFreq;
297}
298
299//=============================================================================================================
300
302{
303 return m_dLowpassFreq;
304}
305
306//=============================================================================================================
307
308void FilterKernel::setLowpassFreq(double dLowpassFreq)
309{
310 m_dLowpassFreq = dLowpassFreq;
311}
312
313//=============================================================================================================
314
315Eigen::RowVectorXd FilterKernel::getCoefficients() const
316{
317 return m_vecCoeff;
318}
319
320//=============================================================================================================
321
322void FilterKernel::setCoefficients(const Eigen::RowVectorXd& vecCoeff)
323{
324 m_vecCoeff = vecCoeff;
325}
326
327//=============================================================================================================
328
329Eigen::RowVectorXcd FilterKernel::getFftCoefficients() const
330{
331 return m_vecFftCoeff;
332}
333
334//=============================================================================================================
335
336void FilterKernel::setFftCoefficients(const Eigen::RowVectorXcd& vecFftCoeff)
337{
338 m_vecFftCoeff = vecFftCoeff;
339}
340
341//=============================================================================================================
342
343bool FilterKernel::fftTransformCoeffs(int iFftLength)
344{
345#ifdef EIGEN_FFTW_DEFAULT
346 fftw_make_planner_thread_safe();
347#endif
348
349 if (m_vecCoeff.cols() > iFftLength) {
350 std::cout << "[FilterKernel::fftTransformCoeffs] The number of filter taps is bigger than the FFT length." << std::endl;
351 return false;
352 }
353
354 //generate fft object
355 Eigen::FFT<double> fft;
356 fft.SetFlag(fft.HalfSpectrum);
357
358 // 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
359 RowVectorXd vecInputFft;
360 if (m_vecCoeff.cols() < iFftLength) {
361 vecInputFft.setZero(iFftLength);
362 vecInputFft.block(0, 0, 1, m_vecCoeff.cols()) = m_vecCoeff;
363 } else {
364 vecInputFft = m_vecCoeff;
365 }
366
367 //fft-transform filter coeffs
368 RowVectorXcd vecFreqData;
369 fft.fwd(vecFreqData, vecInputFft, iFftLength);
370 m_vecFftCoeff = vecFreqData;
371 ;
372
373 return true;
374}
375
376//=============================================================================================================
377
378void FilterKernel::designFilter()
379{
380 // Make sure we only use a minimum needed FFT size
381 int iFftLength = m_iFilterOrder;
382 int exp = ceil(Numerics::log2(iFftLength));
383 iFftLength = pow(2, exp);
384
385 if (m_iDesignMethod == 0) {
386 double dSmallestFeatureHz = m_dParksWidth * (m_sFreq / 2.0);
387 const double dLowEdgeHz = std::max(0.0, (m_dCenterFreq - m_dBandwidth / 2.0) * (m_sFreq / 2.0));
388 const double dHighEdgeHz = std::max(0.0, (m_dCenterFreq + m_dBandwidth / 2.0) * (m_sFreq / 2.0));
389 const double dSingleCutoffHz = std::max(0.0, m_dCenterFreq * (m_sFreq / 2.0));
390
391 if (m_iFilterType == 0 || m_iFilterType == 1) {
392 if (dSingleCutoffHz > 0.0) {
393 dSmallestFeatureHz = std::min(dSmallestFeatureHz, dSingleCutoffHz);
394 }
395 } else {
396 if (dLowEdgeHz > 0.0) {
397 dSmallestFeatureHz = std::min(dSmallestFeatureHz, dLowEdgeHz);
398 }
399 if (dHighEdgeHz > 0.0) {
400 dSmallestFeatureHz = std::min(dSmallestFeatureHz, dHighEdgeHz);
401 }
402 }
403
404 dSmallestFeatureHz = std::max(0.5, dSmallestFeatureHz);
405
406 const int iRecommendedFftLength = static_cast<int>(std::ceil((m_sFreq / dSmallestFeatureHz) * 8.0));
407 if (iRecommendedFftLength > iFftLength) {
408 int iRecommendedExp = ceil(Numerics::log2(iRecommendedFftLength));
409 iFftLength = pow(2, iRecommendedExp);
410 }
411 }
412
413 switch (m_iDesignMethod) {
414 case 1: {
415 ParksMcClellan filter(m_iFilterOrder,
416 m_dCenterFreq,
417 m_dBandwidth,
418 m_dParksWidth,
419 static_cast<ParksMcClellan::TPassType>(m_iFilterType));
420 m_vecCoeff = filter.FirCoeff;
421
422 //fft-transform m_vecCoeff in order to be able to perform frequency-domain filtering
423 fftTransformCoeffs(iFftLength);
424
425 break;
426 }
427
428 case 0: {
429 CosineFilter filtercos;
430
431 switch (m_iFilterType) {
432 case 0:
433 filtercos = CosineFilter(iFftLength,
434 (m_dCenterFreq) * (m_sFreq / 2.),
435 m_dParksWidth * (m_sFreq / 2),
436 (m_dCenterFreq) * (m_sFreq / 2),
437 m_dParksWidth * (m_sFreq / 2),
438 m_sFreq,
439 static_cast<CosineFilter::TPassType>(m_iFilterType));
440
441 break;
442
443 case 1:
444 filtercos = CosineFilter(iFftLength,
445 (m_dCenterFreq) * (m_sFreq / 2),
446 m_dParksWidth * (m_sFreq / 2),
447 (m_dCenterFreq) * (m_sFreq / 2),
448 m_dParksWidth * (m_sFreq / 2),
449 m_sFreq,
450 static_cast<CosineFilter::TPassType>(m_iFilterType));
451
452 break;
453
454 case 2:
455 filtercos = CosineFilter(iFftLength,
456 (m_dCenterFreq + m_dBandwidth / 2) * (m_sFreq / 2),
457 m_dParksWidth * (m_sFreq / 2),
458 (m_dCenterFreq - m_dBandwidth / 2) * (m_sFreq / 2),
459 m_dParksWidth * (m_sFreq / 2),
460 m_sFreq,
461 static_cast<CosineFilter::TPassType>(m_iFilterType));
462
463 break;
464 }
465
466 //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
467 m_vecCoeff.resize(m_iFilterOrder);
468
469 m_vecCoeff.head(m_iFilterOrder / 2) = filtercos.m_vecCoeff.tail(m_iFilterOrder / 2);
470 m_vecCoeff.tail(m_iFilterOrder / 2) = filtercos.m_vecCoeff.head(m_iFilterOrder / 2);
471
472 //Now generate the fft version of the shortened impulse response
473 fftTransformCoeffs(iFftLength);
474
475 break;
476 }
477 }
478
479 switch (m_iFilterType) {
480 case 0:
481 m_dLowpassFreq = 0;
482 m_dHighpassFreq = m_dCenterFreq * (m_sFreq / 2);
483 break;
484
485 case 1:
486 m_dLowpassFreq = m_dCenterFreq * (m_sFreq / 2);
487 m_dHighpassFreq = 0;
488 break;
489
490 case 2:
491 m_dLowpassFreq = (m_dCenterFreq + m_dBandwidth / 2) * (m_sFreq / 2);
492 m_dHighpassFreq = (m_dCenterFreq - m_dBandwidth / 2) * (m_sFreq / 2);
493 break;
494 }
496}
497
498//=============================================================================================================
499
501{
502 QString description(m_designMethods.at(m_iDesignMethod).getName() + " - " +
503 QString::number(m_dHighpassFreq, 'g', 4) + "Hz to " + QString::number(m_dLowpassFreq, 'g', 4) + "Hz - "
504 "Ord: " +
505 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
554: FilterParameter("Unknown", "")
555{
556}
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
577{
578 return m_sName;
579}
580
581//=============================================================================================================
Parks–McClellan equiripple FIR design via the Remez exchange algorithm.
Frequency-domain cosine-tapered (raised-cosine) FIR filter design.
Linear-phase FIR filter kernel with overlap-add FFT convolution back-end.
General numerical helpers: GCD, log2, histogram binning, baseline rescaling, sparsity tests.
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:217