diff --git a/CMakeLists.txt b/CMakeLists.txt index 487c3ee..6bd2947 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -35,13 +35,17 @@ set(PROJECT_SOURCES src/qt_serial_datachannel.cpp src/electrocardiograph_tester.cpp - src/filter_algo_mgr.cpp libzqt/widgetplot2d.cpp libzqt/widgetplot2d.ui libzqt/qcustomplot.cpp - src/algo/iflytop_simple_filter.cpp + src/filter_algo_mgr.cpp + src/filter_group/algo/iflytop_simple_filter.cpp + src/filter_group/iir_filter_group.cpp + src/filter_group/fir_filter_group.cpp + src/filter_group/algo/fir_filter.cpp + app.rc ) diff --git a/README.md b/README.md index e6b29c2..46cb817 100644 --- a/README.md +++ b/README.md @@ -43,4 +43,12 @@ V4: regcache.ch1set = ADS129X_SET_BITS(regcache.ch1set, ADS129X_MUXx, ADS129X_CHx_INPUT_TEST); regcache.ch2set = ADS129X_SET_BITS(regcache.ch2set, ADS129X_MUXx, ADS129X_CHx_INPUT_TEST); +``` + + +``` +滤波器参考 +https://github.com/TcheL/Road2Filter/blob/master/FIR/bpFIR.py + + ``` \ No newline at end of file diff --git a/mainwindow.cpp b/mainwindow.cpp index 0ee27b6..5ceeaeb 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -8,7 +8,6 @@ #include "./ui_mainwindow.h" #include "ads129x/ads129x_type.h" -#include "algo/iflytop_simple_filter.h" #include "electrocardiograph_tester.hpp" #include "filter_algo_mgr.hpp" #include "logger.hpp" @@ -170,6 +169,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi // qInstallMessageHandler(log_output); wp2d = new WidgetPlot2D(); wp2d->initGraphName(QStringList() << "心电"); + FilterAlgoMgr::ins()->addFilter("心电"); /******************************************************************************* * 页面逻辑初始化 * @@ -369,6 +369,12 @@ void MainWindow::constructUI() { // rawDataPreviewShow("[CH4 ] %s", zhex2str(hex, hexlen).c_str()); } }); + + // FilterCommon_WindowsType + auto windowsTypes = FilterAlgoMgr::ins()->windowsTypes(); + for (auto &type : windowsTypes) { + ui->FilterCommon_WindowsType->addItem(type.c_str()); + } } void MainWindow::processException(zexception &e) { ishow("%s:%s", e.what(), ify_hrs_error_code_to_string((ify_hrs_error_code_t)e.ecode())); } @@ -695,7 +701,7 @@ void MainWindow::on_resetDevice_clicked() { void MainWindow::on_FilterUpdateParameter_clicked() { instructionPreviewClear(); - FilterAlgoMgr::ins()->FilterCommon_setSampleTimeMs(ui->FilterCommon_SampleTimeMs->text().toFloat()); + FilterAlgoMgr::ins()->setSampleTimeMs(ui->FilterCommon_SampleTimeMs->text().toFloat()); FilterAlgoMgr::ins()->LPFilter_setEnable(ui->LPFilter_Enable->isChecked()); FilterAlgoMgr::ins()->LPFilter_setCutoffFreqHz(ui->LPFilter_CutoffFreqHz->text().toFloat()); FilterAlgoMgr::ins()->LPFilter_setOrder(ui->LPFilter_Order->text().toInt()); @@ -709,6 +715,9 @@ void MainWindow::on_FilterUpdateParameter_clicked() { FilterAlgoMgr::ins()->NOTCHFilter_setNotchWidthHz(ui->NOTCHFilter_NotchWidthHz->text().toFloat()); FilterAlgoMgr::ins()->NOTCHFilter_setOrder(ui->NOTCHFilter_Order->text().toInt()); + FilterAlgoMgr::ins()->setWindowsSize(ui->FilterCommon_windowsSize->text().toInt()); + FilterAlgoMgr::ins()->setWindowsType(ui->FilterCommon_WindowsType->currentText().toStdString()); + FilterAlgoMgr::ins()->updateParameter(); on_buttonTabWidget_currentChanged(0); @@ -716,7 +725,7 @@ void MainWindow::on_FilterUpdateParameter_clicked() { } void MainWindow::on_buttonTabWidget_currentChanged(int index) { - ui->FilterCommon_SampleTimeMs->setText(QString::number(FilterAlgoMgr::ins()->FilterCommon_getSampleTimeMs())); + ui->FilterCommon_SampleTimeMs->setText(QString::number(FilterAlgoMgr::ins()->getSampleTimeMs())); ui->LPFilter_Enable->setChecked(FilterAlgoMgr::ins()->LPFilter_getEnable()); ui->LPFilter_CutoffFreqHz->setText(QString::number(FilterAlgoMgr::ins()->LPFilter_getCutoffFreqHz())); ui->LPFilter_Order->setText(QString::number(FilterAlgoMgr::ins()->LPFilter_getOrder())); @@ -729,6 +738,9 @@ void MainWindow::on_buttonTabWidget_currentChanged(int index) { ui->NOTCHFilter_CenterFreqHz->setText(QString::number(FilterAlgoMgr::ins()->NOTCHFilter_getCenterFreqHz())); ui->NOTCHFilter_NotchWidthHz->setText(QString::number(FilterAlgoMgr::ins()->NOTCHFilter_getNotchWidthHz())); ui->NOTCHFilter_Order->setText(QString::number(FilterAlgoMgr::ins()->NOTCHFilter_getOrder())); + + ui->FilterCommon_windowsSize->setText(QString::number(FilterAlgoMgr::ins()->getWindowsSize())); + ui->FilterCommon_WindowsType->setCurrentText(QString::fromStdString(FilterAlgoMgr::ins()->getWindowType())); } void MainWindow::on_TestCmd_writeSubICAllReg_clicked() { @@ -777,8 +789,7 @@ void MainWindow::on_setEcgInNormalMode_clicked() { } } -void MainWindow::on_setEcgReportDataInRawMode__1_clicked() -{ +void MainWindow::on_setEcgReportDataInRawMode__1_clicked() { instructionPreviewClear(); try { ElectrocardiographTester::ins()->set_ecg_report_data_in_raw_mode(1); @@ -788,9 +799,7 @@ void MainWindow::on_setEcgReportDataInRawMode__1_clicked() } } - -void MainWindow::on_setEcgReportDataInRawMode__0_clicked() -{ +void MainWindow::on_setEcgReportDataInRawMode__0_clicked() { instructionPreviewClear(); try { ElectrocardiographTester::ins()->set_ecg_report_data_in_raw_mode(0); @@ -799,6 +808,4 @@ void MainWindow::on_setEcgReportDataInRawMode__0_clicked() } catch (zexception &exception) { processException(exception); } - } - diff --git a/mainwindow.ui b/mainwindow.ui index 32e8669..c4ce3af 100644 --- a/mainwindow.ui +++ b/mainwindow.ui @@ -869,7 +869,7 @@ p, li { white-space: pre-wrap; } - 0 + 2 @@ -2158,8 +2158,8 @@ p, li { white-space: pre-wrap; } - - + + 1 @@ -2167,11 +2167,24 @@ p, li { white-space: pre-wrap; } - 数据采样周期(ms) + windowsSize - + + + + + 1 + 0 + + + + + + + + Qt::Vertical @@ -2184,6 +2197,35 @@ p, li { white-space: pre-wrap; } + + + + + 1 + 0 + + + + 数据采样周期(ms) + + + + + + + + 1 + 0 + + + + windowsType + + + + + + @@ -2204,6 +2246,13 @@ p, li { white-space: pre-wrap; } + + + GroupBox + + + + Qt::Vertical diff --git a/src/filter_algo_mgr.cpp b/src/filter_algo_mgr.cpp index 76e6eb6..a57c1ac 100644 --- a/src/filter_algo_mgr.cpp +++ b/src/filter_algo_mgr.cpp @@ -6,158 +6,212 @@ #include "zexception.hpp" using namespace iflytop; +/*********************************************************************************************************************** + * FilterAlgoMgr * + ***********************************************************************************************************************/ -void FilterGroup::initialize() {} +FilterAlgoMgr* FilterAlgoMgr::ins() { + static FilterAlgoMgr ins; + return &ins; +} -int32_t FilterGroup::processData(int32_t data) { // - std::lock_guard lock(lock_); +void FilterAlgoMgr::addFilter(string channel) { + IF_FilterGroup* f = new IIRFilterGroup(); + f->initialize(); + m_filter_map[channel] = f; +} - float v0 = (float)data; - for (int i = 0; i < _lpfilter_order; i++) { - v0 = LPFilter_Update(&lpfilter[i], v0); +int32_t FilterAlgoMgr::processData(string channel, int32_t data) { + IF_FilterGroup* f = m_filter_map[channel]; + if (f) { + return f->processData(data); + } else { + return data; } +} - for (int i = 0; i < _hpfilter_order; i++) { - v0 = HPFilter_Update(&hpfilter[i], v0); +void FilterAlgoMgr::LPFilter_setEnable(bool enable) { + for (auto& it : m_filter_map) { + it.second->LPFilter_setEnable(enable); } +} +void FilterAlgoMgr::LPFilter_setCutoffFreqHz(float cutoffFreqHz) { // + // filter.LPFilter_setCutoffFreqHz(cutoffFreqHz); + for (auto& it : m_filter_map) { + it.second->LPFilter_setCutoffFreqHz(cutoffFreqHz); + } +} - for (int i = 0; i < _notchfilter_order; i++) { - v0 = NOTCHFilter_Update(¬chfilter[i], v0); +bool FilterAlgoMgr::LPFilter_getEnable() { // + // return filter.LPFilter_getEnable(); + for (auto& it : m_filter_map) { + return it.second->LPFilter_getEnable(); } + return 0; +} +float FilterAlgoMgr::LPFilter_getCutoffFreqHz() { // + // return filter.LPFilter_getCutoffFreqHz(); + for (auto& it : m_filter_map) { + return it.second->LPFilter_getCutoffFreqHz(); + } + return 0; +} - return (int32_t)v0; +void FilterAlgoMgr::HPFilter_setEnable(bool enable) { // + // filter.HPFilter_setEnable(enable); + for (auto& it : m_filter_map) { + it.second->HPFilter_setEnable(enable); + } +} +void FilterAlgoMgr::HPFilter_setCutoffFreqHz(float cutoffFreqHz) { // + // filter.HPFilter_setCutoffFreqHz(cutoffFreqHz); + for (auto& it : m_filter_map) { + it.second->HPFilter_setCutoffFreqHz(cutoffFreqHz); + } } -void FilterGroup::updateParameter() { - std::lock_guard lock(lock_); +bool FilterAlgoMgr::HPFilter_getEnable() { // + // return filter.HPFilter_getEnable(); + for (auto& it : m_filter_map) { + return it.second->HPFilter_getEnable(); + } + return 0; +} +float FilterAlgoMgr::HPFilter_getCutoffFreqHz() { // + // return filter.HPFilter_getCutoffFreqHz(); + for (auto& it : m_filter_map) { + return it.second->HPFilter_getCutoffFreqHz(); + } + return 0; +} - for (int i = 0; i < FILTER_MAX_ORDER; i++) { - LPFilter_Init(&lpfilter[i], LPFilter_cutoffFreqHz, sampleTimeMs / 1000.0, lpfilter_enable); +void FilterAlgoMgr::NOTCHFilter_setEnable(bool enable) { // + // filter.NOTCHFilter_setEnable(enable); + for (auto& it : m_filter_map) { + it.second->NOTCHFilter_setEnable(enable); } - for (int i = 0; i < FILTER_MAX_ORDER; i++) { - HPFilter_Init(&hpfilter[i], HPFilter_cutoffFreqHz, sampleTimeMs / 1000.0, hpfilter_enable); +} +void FilterAlgoMgr::NOTCHFilter_setCenterFreqHz(float centerFreqHz) { // + // filter.NOTCHFilter_setCenterFreqHz(centerFreqHz); + for (auto& it : m_filter_map) { + it.second->NOTCHFilter_setCenterFreqHz(centerFreqHz); } - for (int i = 0; i < FILTER_MAX_ORDER; i++) { - NOTCHFilter_Init(¬chfilter[i], NOTCHFilter_centerFreqHz, NOTCHFilter_notchWidthHz, sampleTimeMs / 1000.0, notchfilter_enable); +} +void FilterAlgoMgr::NOTCHFilter_setNotchWidthHz(float notchWidthHz) { // + // filter.NOTCHFilter_setNotchWidthHz(notchWidthHz); + for (auto& it : m_filter_map) { + it.second->NOTCHFilter_setNotchWidthHz(notchWidthHz); } - - _lpfilter_order = lpfilter_order; - _hpfilter_order = hpfilter_order; - _notchfilter_order = notchfilter_order; } -/*********************************************************************************************************************** - * 通用参数 * - ***********************************************************************************************************************/ - -void FilterGroup::setSampleTimeMs(float sampleTimeMs) { - if (sampleTimeMs < 0.4) { - throw zexception(kifyhrs_ecode_upper_exception, "sampleTimeMs must be greater than 0.4ms"); +bool FilterAlgoMgr::NOTCHFilter_getEnable() { // + // return filter.NOTCHFilter_getEnable(); + for (auto& it : m_filter_map) { + return it.second->NOTCHFilter_getEnable(); } - this->sampleTimeMs = sampleTimeMs; + return 0; +} +float FilterAlgoMgr::NOTCHFilter_getCenterFreqHz() { // + // return filter.NOTCHFilter_getCenterFreqHz(); + for (auto& it : m_filter_map) { + return it.second->NOTCHFilter_getCenterFreqHz(); + } + return 0; +} +float FilterAlgoMgr::NOTCHFilter_getNotchWidthHz() { // + // return filter.NOTCHFilter_getNotchWidthHz(); + for (auto& it : m_filter_map) { + return it.second->NOTCHFilter_getNotchWidthHz(); + } + return 0; } -float FilterGroup::getSampleTimeMs() { return sampleTimeMs; } -void FilterGroup::LPFilter_setOrder(int order) { - if (order < 1) { - order = 1; +void FilterAlgoMgr::setSampleTimeMs(float sampleTimeMs) { // + // filter.setSampleTimeMs(sampleTimeMs); + for (auto& it : m_filter_map) { + it.second->setSampleTimeMs(sampleTimeMs); } - if (order > FILTER_MAX_ORDER) { - order = FILTER_MAX_ORDER; +} +float FilterAlgoMgr::getSampleTimeMs() { // + // return filter.getSampleTimeMs(); + for (auto& it : m_filter_map) { + return it.second->getSampleTimeMs(); } - lpfilter_order = order; + return 0; } -int FilterGroup::LPFilter_getOrder() { return lpfilter_order; } -void FilterGroup::HPFilter_setOrder(int order) { - if (order < 1) { - order = 1; + +void FilterAlgoMgr::setWindowsSize(int windows_size) { + for (auto& it : m_filter_map) { + it.second->setWindowsSize(windows_size); } - if (order > FILTER_MAX_ORDER) { - order = FILTER_MAX_ORDER; +} +int FilterAlgoMgr::getWindowsSize() { + for (auto& it : m_filter_map) { + return it.second->getWindowsSize(); } - hpfilter_order = order; + return 0; } -int FilterGroup::HPFilter_getOrder() { return hpfilter_order; } -void FilterGroup::NOTCHFilter_setOrder(int order) { - if (order < 1) { - order = 1; +void FilterAlgoMgr::setWindowsType(string windowType) { + for (auto& it : m_filter_map) { + it.second->setWindowsType(windowType); } - if (order > FILTER_MAX_ORDER) { - order = FILTER_MAX_ORDER; +} +string FilterAlgoMgr::getWindowType() { + for (auto& it : m_filter_map) { + return it.second->getWindowType(); } - notchfilter_order = order; + return ""; } -int FilterGroup::NOTCHFilter_getOrder() { return notchfilter_order; } - -/*********************************************************************************************************************** - * 低通滤波器 * - ***********************************************************************************************************************/ - -void FilterGroup::LPFilter_setEnable(bool enable) { lpfilter_enable = enable; } -void FilterGroup::LPFilter_setCutoffFreqHz(float cutoffFreqHz) { LPFilter_cutoffFreqHz = cutoffFreqHz; } - -bool FilterGroup::LPFilter_getEnable() { return lpfilter_enable; } -float FilterGroup::LPFilter_getCutoffFreqHz() { return LPFilter_cutoffFreqHz; } - -/*********************************************************************************************************************** - * 高通滤波器 * - ***********************************************************************************************************************/ -void FilterGroup::HPFilter_setEnable(bool enable) { hpfilter_enable = enable; } -void FilterGroup::HPFilter_setCutoffFreqHz(float cutoffFreqHz) { HPFilter_cutoffFreqHz = cutoffFreqHz; } - -bool FilterGroup::HPFilter_getEnable() { return hpfilter_enable; } -float FilterGroup::HPFilter_getCutoffFreqHz() { return HPFilter_cutoffFreqHz; } - -/*********************************************************************************************************************** - * 带阻滤波器 * - ***********************************************************************************************************************/ -void FilterGroup::NOTCHFilter_setEnable(bool enable) { notchfilter_enable = enable; } -void FilterGroup::NOTCHFilter_setCenterFreqHz(float centerFreqHz) { NOTCHFilter_centerFreqHz = centerFreqHz; } -void FilterGroup::NOTCHFilter_setNotchWidthHz(float notchWidthHz) { NOTCHFilter_notchWidthHz = notchWidthHz; } - -bool FilterGroup::NOTCHFilter_getEnable() { return notchfilter_enable; } -float FilterGroup::NOTCHFilter_getCenterFreqHz() { return NOTCHFilter_centerFreqHz; } -float FilterGroup::NOTCHFilter_getNotchWidthHz() { return NOTCHFilter_notchWidthHz; } - -/*********************************************************************************************************************** - * FilterAlgoMgr * - ***********************************************************************************************************************/ - -FilterAlgoMgr* FilterAlgoMgr::ins() { - static FilterAlgoMgr ins; - return &ins; +vector FilterAlgoMgr::windowsTypes() { + return IF_FilterGroup::windowsTypes(); } -int32_t FilterAlgoMgr::processData(string channel, int32_t data) { return filter.processData(data); } - -void FilterAlgoMgr::LPFilter_setEnable(bool enable) { filter.LPFilter_setEnable(enable); } -void FilterAlgoMgr::LPFilter_setCutoffFreqHz(float cutoffFreqHz) { filter.LPFilter_setCutoffFreqHz(cutoffFreqHz); } - -bool FilterAlgoMgr::LPFilter_getEnable() { return filter.LPFilter_getEnable(); } -float FilterAlgoMgr::LPFilter_getCutoffFreqHz() { return filter.LPFilter_getCutoffFreqHz(); } - -void FilterAlgoMgr::HPFilter_setEnable(bool enable) { filter.HPFilter_setEnable(enable); } -void FilterAlgoMgr::HPFilter_setCutoffFreqHz(float cutoffFreqHz) { filter.HPFilter_setCutoffFreqHz(cutoffFreqHz); } - -bool FilterAlgoMgr::HPFilter_getEnable() { return filter.HPFilter_getEnable(); } -float FilterAlgoMgr::HPFilter_getCutoffFreqHz() { return filter.HPFilter_getCutoffFreqHz(); } - -void FilterAlgoMgr::NOTCHFilter_setEnable(bool enable) { filter.NOTCHFilter_setEnable(enable); } -void FilterAlgoMgr::NOTCHFilter_setCenterFreqHz(float centerFreqHz) { filter.NOTCHFilter_setCenterFreqHz(centerFreqHz); } -void FilterAlgoMgr::NOTCHFilter_setNotchWidthHz(float notchWidthHz) { filter.NOTCHFilter_setNotchWidthHz(notchWidthHz); } +void FilterAlgoMgr::LPFilter_setOrder(int order) { // + // filter.LPFilter_setOrder(order); + for (auto& it : m_filter_map) { + it.second->LPFilter_setOrder(order); + } +} +int FilterAlgoMgr::LPFilter_getOrder() { // + // return filter.LPFilter_getOrder(); + for (auto& it : m_filter_map) { + return it.second->LPFilter_getOrder(); + } + return 0; -bool FilterAlgoMgr::NOTCHFilter_getEnable() { return filter.NOTCHFilter_getEnable(); } -float FilterAlgoMgr::NOTCHFilter_getCenterFreqHz() { return filter.NOTCHFilter_getCenterFreqHz(); } -float FilterAlgoMgr::NOTCHFilter_getNotchWidthHz() { return filter.NOTCHFilter_getNotchWidthHz(); } +} +void FilterAlgoMgr::HPFilter_setOrder(int order) { // + // filter.HPFilter_setOrder(order); + for (auto& it : m_filter_map) { + it.second->HPFilter_setOrder(order); + } +} +int FilterAlgoMgr::HPFilter_getOrder() { // + // return filter.HPFilter_getOrder(); + for (auto& it : m_filter_map) { + return it.second->HPFilter_getOrder(); + } + return 0; -void FilterAlgoMgr::FilterCommon_setSampleTimeMs(float sampleTimeMs) { filter.setSampleTimeMs(sampleTimeMs); } -float FilterAlgoMgr::FilterCommon_getSampleTimeMs() { return filter.getSampleTimeMs(); } +} +void FilterAlgoMgr::NOTCHFilter_setOrder(int order) { // + // filter.NOTCHFilter_setOrder(order); + for (auto& it : m_filter_map) { + it.second->NOTCHFilter_setOrder(order); + } +} +int FilterAlgoMgr::NOTCHFilter_getOrder() { // + // return filter.NOTCHFilter_getOrder(); + for (auto& it : m_filter_map) { + return it.second->NOTCHFilter_getOrder(); + } + return 0; -void FilterAlgoMgr::LPFilter_setOrder(int order) { filter.LPFilter_setOrder(order); } -int FilterAlgoMgr::LPFilter_getOrder() { return filter.LPFilter_getOrder(); } -void FilterAlgoMgr::HPFilter_setOrder(int order) { filter.HPFilter_setOrder(order); } -int FilterAlgoMgr::HPFilter_getOrder() { return filter.HPFilter_getOrder(); } -void FilterAlgoMgr::NOTCHFilter_setOrder(int order) { filter.NOTCHFilter_setOrder(order); } -int FilterAlgoMgr::NOTCHFilter_getOrder() { return filter.NOTCHFilter_getOrder(); } +} -void FilterAlgoMgr::updateParameter() { filter.updateParameter(); } +void FilterAlgoMgr::updateParameter() { // + // filter.updateParameter(); + for (auto& it : m_filter_map) { + it.second->updateParameter(); + } +} diff --git a/src/filter_algo_mgr.hpp b/src/filter_algo_mgr.hpp index 2fd2ae9..0a89cee 100644 --- a/src/filter_algo_mgr.hpp +++ b/src/filter_algo_mgr.hpp @@ -12,109 +12,36 @@ #include #include -#include "algo/iflytop_simple_filter.h" - +#include "filter_group/fir_filter_group.hpp" +#include "filter_group/iir_filter_group.hpp" +// namespace iflytop { using namespace std; #define FILTER_MAX_ORDER 10 -class FilterGroup { - private: - LPFilter_t lpfilter[10] = {0}; - HPFilter_t hpfilter[10] = {0}; - NOTCHFilter_t notchfilter[10] = {0}; - - int _lpfilter_order = 1; - int _hpfilter_order = 1; - int _notchfilter_order = 1; - - /*********************************************************************************************************************** - * 缓存参数 * - ***********************************************************************************************************************/ - - bool lpfilter_enable = false; - bool hpfilter_enable = false; - bool notchfilter_enable = false; - - int lpfilter_order = 1; - int hpfilter_order = 1; - int notchfilter_order = 1; - - float LPFilter_cutoffFreqHz = 0; - float HPFilter_cutoffFreqHz = 0; - float NOTCHFilter_centerFreqHz = 0; - float NOTCHFilter_notchWidthHz = 0; - - float sampleTimeMs = 2; // 1ms - - std::recursive_mutex lock_; - - public: - void initialize(); - - int32_t processData(int32_t data); - - void updateParameter(); - - /*********************************************************************************************************************** - * 通用参数 * - ***********************************************************************************************************************/ - void setSampleTimeMs(float sampleTimeMs); - float getSampleTimeMs(); - - /*********************************************************************************************************************** - * 低通滤波器 * - ***********************************************************************************************************************/ - - void LPFilter_setEnable(bool enable); - void LPFilter_setCutoffFreqHz(float cutoffFreqHz); - void LPFilter_setOrder(int order); - - bool LPFilter_getEnable(); - float LPFilter_getCutoffFreqHz(); - int LPFilter_getOrder(); - - /*********************************************************************************************************************** - * 高通滤波器 * - ***********************************************************************************************************************/ - void HPFilter_setEnable(bool enable); - void HPFilter_setCutoffFreqHz(float cutoffFreqHz); - void HPFilter_setOrder(int order); - - bool HPFilter_getEnable(); - float HPFilter_getCutoffFreqHz(); - int HPFilter_getOrder(); - - /*********************************************************************************************************************** - * 带阻滤波器 * - ***********************************************************************************************************************/ - void NOTCHFilter_setEnable(bool enable); - void NOTCHFilter_setCenterFreqHz(float centerFreqHz); - void NOTCHFilter_setNotchWidthHz(float notchWidthHz); - void NOTCHFilter_setOrder(int order); - - bool NOTCHFilter_getEnable(); - float NOTCHFilter_getCenterFreqHz(); - float NOTCHFilter_getNotchWidthHz(); - int NOTCHFilter_getOrder(); -}; - class FilterAlgoMgr { private: - FilterGroup filter; + map m_filter_map; public: FilterAlgoMgr(/* args */) {} ~FilterAlgoMgr() {} static FilterAlgoMgr* ins(); + void addFilter(string channel); int32_t processData(string channel, int32_t data); void updateParameter(); - void FilterCommon_setSampleTimeMs(float sampleTimeMs); - float FilterCommon_getSampleTimeMs(); + void setSampleTimeMs(float sampleTimeMs); + float getSampleTimeMs(); + + void setWindowsSize(int windows_size); + int getWindowsSize(); + void setWindowsType(string windowType); + string getWindowType(); + vector windowsTypes(); /*********************************************************************************************************************** * 低通滤波器 * diff --git a/src/filter_group/algo/fir_filter.cpp b/src/filter_group/algo/fir_filter.cpp new file mode 100644 index 0000000..78a112b --- /dev/null +++ b/src/filter_group/algo/fir_filter.cpp @@ -0,0 +1,204 @@ +#include "fir_filter.hpp" + +static double sinc(const double x) +{ + if (x == 0) + return 1; + + return sin(M_PI * x) / (M_PI * x); +} + +FIR_filter::FIR_filter( int taps, double f1, double f2, const char* type, + const char* window): h(taps, 0), samples(taps, 0) +{ + this->idx = 0; + this->taps = taps; + + std::vector h; // Buffer FIR coefficients + std::vector w; // Buffer window coefficients + + // Calculate the coefficient corresponding to the filter type + if (!strcmp(type, "lp")) { + h = lowPass_coefficient(taps, f1); + } + else if (!strcmp(type, "hp")) { + h = highPass_coefficient(taps, f1); + } + else if (!strcmp(type, "bp")) { + h = bandPass_coefficient(taps, f1, f2); + } + else if (!strcmp(type, "sb")) { + h = bandStop_coefficient(taps, f1, f2); + } + + // Calculate the window to improve the FIR filter + if (!strcmp(window, "hamming")) { + w = window_hammig(taps); + } + else if (!strcmp(window, "triangle")) { + w = window_triangle(taps); + } + else if (!strcmp(window, "hanning")) { + w = window_hanning(taps); + } + else if (!strcmp(window, "blackman")) { + w = window_blackman(taps); + } + + if (!strcmp(window, "")) { + this->h = h; + } + else + { + for(int n = 0; n < taps; n++) { + this->h[n] = h[n] * w[n]; + } + } +} + +FIR_filter::~FIR_filter() +{ + +} + +std::vector FIR_filter::getCoefficients() +{ + return this->h; +} + +std::vector FIR_filter::lowPass_coefficient(int taps, double f) +{ + std::vector n(taps, 0); + std::vector h(taps, 0); + + for(int i = 0; i < taps; i++) { + n[i] = i - int(taps/2); + } + + for(int i = 0; i < taps; i++) { + h[i] = 2.0*f*sinc(2.0*f*n[i]); + } + + return h; +} + +std::vector FIR_filter::highPass_coefficient(int taps, double f) +{ + std::vector n(taps, 0); + std::vector h(taps, 0); + + for(int i = 0; i < taps; i++) { + n[i] = i - int(taps/2); + } + + for(int i = 0; i < taps; i++) { + h[i] = sinc(n[i]) - 2.0*f*sinc(2.0*f*n[i]); + } + + return h; +} + +std::vector FIR_filter::bandPass_coefficient(int taps, double f1, double f2) +{ + std::vector n(taps, 0); + std::vector h(taps, 0); + + for(int i = 0; i < taps; i++) { + n[i] = i - int(taps/2); + } + + for(int i = 0; i < taps; i++) { + h[i] = 2.0*f1*sinc(2.0*f1*n[i]) - 2.0*f2*sinc(2.0*f2*n[i]); + } + + return h; +} + +std::vector FIR_filter::bandStop_coefficient(int taps, double f1, double f2) +{ + std::vector n(taps, 0); + std::vector h(taps, 0); + + for(int i = 0; i < taps; i++) { + n[i] = i - int(taps/2); + } + + for(int i = 0; i < taps; i++) { + h[i] = 2.0*f1*sinc(2.0*f1*n[i]) - 2.0*f2*sinc(2.0*f2*n[i]) + sinc(n[i]); + } + + return h; +} + +std::vector FIR_filter::window_hammig(int taps) +{ + std::vector n(taps, 0); + std::vector w(taps, 0); + + double alpha = 0.54; + double beta = 0.46; + + for(int i = 0; i < taps; i++) { + w[i] = alpha - beta * cos(2.0 * M_PI * i / (taps - 1)); + } + + return w; +} + +std::vector FIR_filter::window_hanning(int taps) +{ + std::vector w(taps, 0); + + for(int i = 0; i < taps; i++) { + w[i] = sin(((double) M_PI * i) / (taps - 1)) * + sin(((double) M_PI * i) / (taps - 1)); + } + + return w; +} + +std::vector FIR_filter::window_triangle(int taps) +{ + std::vector w(taps, 0); + + double l = taps; + + for(int i = 0; i < taps; i++) { + w[i] = 1 - abs((i - (((double)(taps-1)) / 2.0)) / (((double)l) / 2.0)); + } + + return w; +} + +std::vector FIR_filter::window_blackman(int taps) +{ + std::vector w(taps, 0); + + double alpha0 = 0.42; + double alpha1 = 0.5; + double alpha2 = 0.08; + + for(int i = 0; i < taps; i++) { + w[i] = alpha0 - alpha1 * cos(2.0 * M_PI * i / (taps - 1)) + - alpha2 * cos(4.0 * M_PI * i / (taps - 1)); + } + + return w; +} + +double FIR_filter::filter(double new_sample) +{ + double result = 0; + + // Save the new sample + this->samples[this->idx] = new_sample; + + // Calculate the output + for(int n = 0; n < this->taps; n++) + result += this->samples[(this->idx + n) % this->taps] * this->h[n]; + + // Increase the round robin index + this->idx = (this->idx + 1) % this->taps; + + return result; +} \ No newline at end of file diff --git a/src/filter_group/algo/fir_filter.hpp b/src/filter_group/algo/fir_filter.hpp new file mode 100644 index 0000000..732df1e --- /dev/null +++ b/src/filter_group/algo/fir_filter.hpp @@ -0,0 +1,37 @@ +#ifndef FIR_FILTER_H +#define FIR_FILTER_H + +#include +#include + +#include +#include + +class FIR_filter { + public: + FIR_filter(int taps = 0, double f1 = 0, double f2 = 0, const char* type = "", const char* window = ""); + ~FIR_filter(); + + std::vector getCoefficients(); + + double filter(double new_sample); + + private: + std::vector lowPass_coefficient(int taps, double f); + std::vector highPass_coefficient(int taps, double f); + std::vector bandPass_coefficient(int taps, double f1, double f2); + std::vector bandStop_coefficient(int taps, double f1, double f2); + + std::vector window_hammig(int taps); + std::vector window_triangle(int taps); + std::vector window_hanning(int taps); + std::vector window_blackman(int taps); + + std::vector h; // FIR coefficients + std::vector samples; // FIR delay + + int idx; // Round robin index + int taps; // Number of taps of the filter +}; + +#endif // FIR_FILTER_H \ No newline at end of file diff --git a/src/algo/iflytop_simple_filter.cpp b/src/filter_group/algo/iflytop_simple_filter.cpp similarity index 100% rename from src/algo/iflytop_simple_filter.cpp rename to src/filter_group/algo/iflytop_simple_filter.cpp diff --git a/src/algo/iflytop_simple_filter.h b/src/filter_group/algo/iflytop_simple_filter.h similarity index 100% rename from src/algo/iflytop_simple_filter.h rename to src/filter_group/algo/iflytop_simple_filter.h diff --git a/src/filter_group/fir_filter_group.cpp b/src/filter_group/fir_filter_group.cpp new file mode 100644 index 0000000..33924a4 --- /dev/null +++ b/src/filter_group/fir_filter_group.cpp @@ -0,0 +1,41 @@ +#include "fir_filter_group.hpp" + +using namespace iflytop; + +void FirFilterGroup::initialize() { + lpfilter = new FIR_filter(1000, 0.1, 0, "lp", "hamming"); + hpfilter = new FIR_filter(1000, 0.1, 0, "hp", "hamming"); + notchfilter = new FIR_filter(1000, 0.1, 0, "bp", "hamming"); +} +int32_t FirFilterGroup::processData(int32_t data) { + std::lock_guard lock(lock_); + + float v0 = (float)data; + if (lpfilter) { + v0 = lpfilter->filter(v0); + v0 = lpfilter->filter(v0); + } + + if (lpfilter) { + v0 = hpfilter->filter(v0); + v0 = hpfilter->filter(v0); + } + + if (notchfilter) { + v0 = notchfilter->filter(v0); + v0 = notchfilter->filter(v0); + } + + return (int32_t)v0; +} + +void FirFilterGroup::updateParameter() { + std::lock_guard lock(lock_); + if(lpfilter) delete lpfilter; + if(hpfilter) delete hpfilter; + if(notchfilter) delete notchfilter; + + if (_LPFilter_Enable) lpfilter = new FIR_filter(_windows_size, _LPFilter_CutoffFreqHz, 0, "lp", _windowType.c_str()); + if (_HPFilter_Enable) hpfilter = new FIR_filter(_windows_size, _HPFilter_CutoffFreqHz, 0, "hp", _windowType.c_str()); + if (_NOTCHFilter_Enable) notchfilter = new FIR_filter(_windows_size, _NOTCHFilter_CenterFreqHz - _NOTCHFilter_NotchWidthHz, _NOTCHFilter_CenterFreqHz + _NOTCHFilter_NotchWidthHz, "sb", _windowType.c_str()); +} diff --git a/src/filter_group/fir_filter_group.hpp b/src/filter_group/fir_filter_group.hpp new file mode 100644 index 0000000..f03a94d --- /dev/null +++ b/src/filter_group/fir_filter_group.hpp @@ -0,0 +1,39 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "algo/fir_filter.hpp" +#include "algo/iflytop_simple_filter.h" +#include "if/if_filter_group.hpp" + +namespace iflytop { +using namespace std; + +class FirFilterGroup : public IF_FilterGroup { + private: + std::recursive_mutex lock_; + + FIR_filter* lpfilter = nullptr; + FIR_filter* hpfilter = nullptr; + FIR_filter* notchfilter = nullptr; + + + public: + virtual void initialize() override; + virtual int32_t processData(int32_t data) override; + + virtual void updateParameter() override; + + +}; +} // namespace iflytop \ No newline at end of file diff --git a/src/filter_group/if/if_filter_group.hpp b/src/filter_group/if/if_filter_group.hpp new file mode 100644 index 0000000..06d2ce5 --- /dev/null +++ b/src/filter_group/if/if_filter_group.hpp @@ -0,0 +1,100 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace iflytop { +using namespace std; + +class IF_FilterGroup { + protected: + float _sampleTimeMs; + + bool _LPFilter_Enable = false; + float _LPFilter_CutoffFreqHz = 0; + float _LPFilter_Order = 1; + + bool _HPFilter_Enable = false; + float _HPFilter_CutoffFreqHz = 0; + float _HPFilter_Order = 1; + + bool _NOTCHFilter_Enable = false; + float _NOTCHFilter_CenterFreqHz = 0; + float _NOTCHFilter_NotchWidthHz = 0; + float _NOTCHFilter_Order = 1; + + int _windows_size = 1000; + + const string kwindow_type_hamming = "hamming"; + const string kwindow_type_triangle = "triangle"; + const string kwindow_type_hanning = "hanning"; + const string kwindow_type_blackman = "blackman"; + + string _windowType = "hamming"; + + public: + virtual ~IF_FilterGroup() {} + + virtual void initialize() = 0; + + virtual int32_t processData(int32_t data) = 0; + + virtual void updateParameter() = 0; + + /*********************************************************************************************************************** + * 通用参数 * + ***********************************************************************************************************************/ + virtual void setSampleTimeMs(float sampleTimeMs) { _sampleTimeMs = sampleTimeMs; } + virtual float getSampleTimeMs() { return _sampleTimeMs; } + virtual void setWindowsSize(int windows_size) { _windows_size = windows_size; } + virtual int getWindowsSize() { return _windows_size; } + virtual void setWindowsType(string windowType) { _windowType = windowType; } + virtual string getWindowType() { return _windowType; } + static vector windowsTypes() { return {"hamming", "triangle", "hanning", "blackman"}; } + + /*********************************************************************************************************************** + * 低通滤波器 * + ***********************************************************************************************************************/ + + virtual void LPFilter_setEnable(bool enable) { _LPFilter_Enable = enable; } + virtual void LPFilter_setCutoffFreqHz(float cutoffFreqHz) { _LPFilter_CutoffFreqHz = cutoffFreqHz; } + virtual void LPFilter_setOrder(int order) { _LPFilter_Order = order; } + + virtual bool LPFilter_getEnable() { return _LPFilter_Enable; } + virtual float LPFilter_getCutoffFreqHz() { return _LPFilter_CutoffFreqHz; } + virtual int LPFilter_getOrder() { return _LPFilter_Order; } + + /*********************************************************************************************************************** + * 高通滤波器 * + ***********************************************************************************************************************/ + virtual void HPFilter_setEnable(bool enable) { _HPFilter_Enable = enable; } + virtual void HPFilter_setCutoffFreqHz(float cutoffFreqHz) { _HPFilter_CutoffFreqHz = cutoffFreqHz; } + virtual void HPFilter_setOrder(int order) { _HPFilter_Order = order; } + + virtual bool HPFilter_getEnable() { return _HPFilter_Enable; } + virtual float HPFilter_getCutoffFreqHz() { return _HPFilter_CutoffFreqHz; } + virtual int HPFilter_getOrder() { return _HPFilter_Order; } + + /*********************************************************************************************************************** + * 带阻滤波器 * + ***********************************************************************************************************************/ + virtual void NOTCHFilter_setEnable(bool enable) { _NOTCHFilter_Enable = enable; } + virtual void NOTCHFilter_setCenterFreqHz(float centerFreqHz) { _NOTCHFilter_CenterFreqHz = centerFreqHz; } + virtual void NOTCHFilter_setNotchWidthHz(float notchWidthHz) { _NOTCHFilter_NotchWidthHz = notchWidthHz; } + virtual void NOTCHFilter_setOrder(int order) { _NOTCHFilter_Order = order; } + + virtual bool NOTCHFilter_getEnable() { return _NOTCHFilter_Enable; } + virtual float NOTCHFilter_getCenterFreqHz() { return _NOTCHFilter_CenterFreqHz; } + virtual float NOTCHFilter_getNotchWidthHz() { return _NOTCHFilter_NotchWidthHz; } + virtual int NOTCHFilter_getOrder() { return _NOTCHFilter_Order; } +}; +} // namespace iflytop \ No newline at end of file diff --git a/src/filter_group/iir_filter_group.cpp b/src/filter_group/iir_filter_group.cpp new file mode 100644 index 0000000..b6e767a --- /dev/null +++ b/src/filter_group/iir_filter_group.cpp @@ -0,0 +1,119 @@ +#include "iir_filter_group.hpp" +#include "libzqt\zexception.hpp" +#include "ify_hrs_protocol\heart_rate_sensor_protocol.h" + +#include + +using namespace iflytop; + +void IIRFilterGroup::initialize() {} + +int32_t IIRFilterGroup::processData(int32_t data) { // + std::lock_guard lock(lock_); + + float v0 = (float)data; + for (int i = 0; i < __lpfilter_order; i++) { + v0 = LPFilter_Update(&lpfilter[i], v0); + } + + for (int i = 0; i < __hpfilter_order; i++) { + v0 = HPFilter_Update(&hpfilter[i], v0); + } + + for (int i = 0; i < __notchfilter_order; i++) { + v0 = NOTCHFilter_Update(¬chfilter[i], v0); + } + + return (int32_t)v0; +} + +void IIRFilterGroup::updateParameter() { + std::lock_guard lock(lock_); + + for (int i = 0; i < FILTER_MAX_ORDER; i++) { + LPFilter_Init(&lpfilter[i], LPFilter_cutoffFreqHz, sampleTimeMs / 1000.0, lpfilter_enable); + } + for (int i = 0; i < FILTER_MAX_ORDER; i++) { + HPFilter_Init(&hpfilter[i], HPFilter_cutoffFreqHz, sampleTimeMs / 1000.0, hpfilter_enable); + } + for (int i = 0; i < FILTER_MAX_ORDER; i++) { + NOTCHFilter_Init(¬chfilter[i], NOTCHFilter_centerFreqHz, NOTCHFilter_notchWidthHz, sampleTimeMs / 1000.0, notchfilter_enable); + } + + __lpfilter_order = lpfilter_order; + __hpfilter_order = hpfilter_order; + __notchfilter_order = notchfilter_order; +} + +/*********************************************************************************************************************** + * 通用参数 * + ***********************************************************************************************************************/ + +void IIRFilterGroup::setSampleTimeMs(float sampleTimeMs) { + if (sampleTimeMs < 0.4) { + throw zexception(kifyhrs_ecode_upper_exception, "sampleTimeMs must be greater than 0.4ms"); + } + this->sampleTimeMs = sampleTimeMs; +} +float IIRFilterGroup::getSampleTimeMs() { return sampleTimeMs; } + +void IIRFilterGroup::LPFilter_setOrder(int order) { + if (order < 1) { + order = 1; + } + if (order > FILTER_MAX_ORDER) { + order = FILTER_MAX_ORDER; + } + lpfilter_order = order; +} +int IIRFilterGroup::LPFilter_getOrder() { return lpfilter_order; } +void IIRFilterGroup::HPFilter_setOrder(int order) { + if (order < 1) { + order = 1; + } + if (order > FILTER_MAX_ORDER) { + order = FILTER_MAX_ORDER; + } + hpfilter_order = order; +} +int IIRFilterGroup::HPFilter_getOrder() { return hpfilter_order; } +void IIRFilterGroup::NOTCHFilter_setOrder(int order) { + if (order < 1) { + order = 1; + } + if (order > FILTER_MAX_ORDER) { + order = FILTER_MAX_ORDER; + } + notchfilter_order = order; +} +int IIRFilterGroup::NOTCHFilter_getOrder() { return notchfilter_order; } + +/*********************************************************************************************************************** + * 低通滤波器 * + ***********************************************************************************************************************/ + +void IIRFilterGroup::LPFilter_setEnable(bool enable) { lpfilter_enable = enable; } +void IIRFilterGroup::LPFilter_setCutoffFreqHz(float cutoffFreqHz) { LPFilter_cutoffFreqHz = cutoffFreqHz; } + +bool IIRFilterGroup::LPFilter_getEnable() { return lpfilter_enable; } +float IIRFilterGroup::LPFilter_getCutoffFreqHz() { return LPFilter_cutoffFreqHz; } + +/*********************************************************************************************************************** + * 高通滤波器 * + ***********************************************************************************************************************/ +void IIRFilterGroup::HPFilter_setEnable(bool enable) { hpfilter_enable = enable; } +void IIRFilterGroup::HPFilter_setCutoffFreqHz(float cutoffFreqHz) { HPFilter_cutoffFreqHz = cutoffFreqHz; } + +bool IIRFilterGroup::HPFilter_getEnable() { return hpfilter_enable; } +float IIRFilterGroup::HPFilter_getCutoffFreqHz() { return HPFilter_cutoffFreqHz; } + +/*********************************************************************************************************************** + * 带阻滤波器 * + ***********************************************************************************************************************/ +void IIRFilterGroup::NOTCHFilter_setEnable(bool enable) { notchfilter_enable = enable; } +void IIRFilterGroup::NOTCHFilter_setCenterFreqHz(float centerFreqHz) { NOTCHFilter_centerFreqHz = centerFreqHz; } +void IIRFilterGroup::NOTCHFilter_setNotchWidthHz(float notchWidthHz) { NOTCHFilter_notchWidthHz = notchWidthHz; } + +bool IIRFilterGroup::NOTCHFilter_getEnable() { return notchfilter_enable; } +float IIRFilterGroup::NOTCHFilter_getCenterFreqHz() { return NOTCHFilter_centerFreqHz; } +float IIRFilterGroup::NOTCHFilter_getNotchWidthHz() { return NOTCHFilter_notchWidthHz; } diff --git a/src/filter_group/iir_filter_group.hpp b/src/filter_group/iir_filter_group.hpp new file mode 100644 index 0000000..0645e0f --- /dev/null +++ b/src/filter_group/iir_filter_group.hpp @@ -0,0 +1,102 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "algo/iflytop_simple_filter.h" +#include "if/if_filter_group.hpp" + +namespace iflytop { +using namespace std; +#define FILTER_MAX_ORDER 20 + +class IIRFilterGroup : public IF_FilterGroup { + private: + LPFilter_t lpfilter[FILTER_MAX_ORDER] = {0}; + HPFilter_t hpfilter[FILTER_MAX_ORDER] = {0}; + NOTCHFilter_t notchfilter[FILTER_MAX_ORDER] = {0}; + + int __lpfilter_order = 1; + int __hpfilter_order = 1; + int __notchfilter_order = 1; + + /*********************************************************************************************************************** + * 缓存参数 * + ***********************************************************************************************************************/ + + bool lpfilter_enable = false; + bool hpfilter_enable = false; + bool notchfilter_enable = false; + + int lpfilter_order = 1; + int hpfilter_order = 1; + int notchfilter_order = 1; + + float LPFilter_cutoffFreqHz = 0; + float HPFilter_cutoffFreqHz = 0; + float NOTCHFilter_centerFreqHz = 0; + float NOTCHFilter_notchWidthHz = 0; + + float sampleTimeMs = 2; // 1ms + + std::recursive_mutex lock_; + + public: + virtual void initialize() override; + + virtual int32_t processData(int32_t data) override; + + virtual void updateParameter() override; + + /*********************************************************************************************************************** + * 通用参数 * + ***********************************************************************************************************************/ + virtual void setSampleTimeMs(float sampleTimeMs) override; + virtual float getSampleTimeMs() override; + + /*********************************************************************************************************************** + * 低通滤波器 * + ***********************************************************************************************************************/ + + virtual void LPFilter_setEnable(bool enable) override; + virtual void LPFilter_setCutoffFreqHz(float cutoffFreqHz) override; + virtual void LPFilter_setOrder(int order) override; + + virtual bool LPFilter_getEnable() override; + virtual float LPFilter_getCutoffFreqHz() override; + virtual int LPFilter_getOrder() override; + + /*********************************************************************************************************************** + * 高通滤波器 * + ***********************************************************************************************************************/ + virtual void HPFilter_setEnable(bool enable) override; + virtual void HPFilter_setCutoffFreqHz(float cutoffFreqHz) override; + virtual void HPFilter_setOrder(int order) override; + + virtual bool HPFilter_getEnable() override; + virtual float HPFilter_getCutoffFreqHz() override; + virtual int HPFilter_getOrder() override; + + /*********************************************************************************************************************** + * 带阻滤波器 * + ***********************************************************************************************************************/ + virtual void NOTCHFilter_setEnable(bool enable) override; + virtual void NOTCHFilter_setCenterFreqHz(float centerFreqHz) override; + virtual void NOTCHFilter_setNotchWidthHz(float notchWidthHz) override; + virtual void NOTCHFilter_setOrder(int order) override; + + virtual bool NOTCHFilter_getEnable() override; + virtual float NOTCHFilter_getCenterFreqHz() override; + virtual float NOTCHFilter_getNotchWidthHz() override; + virtual int NOTCHFilter_getOrder() override; +}; +} // namespace iflytop \ No newline at end of file