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