1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
|
/*
File : FourierTest.cpp
Project : LabPlot
Description : Tests for fourier filtering
--------------------------------------------------------------------
SPDX-FileCopyrightText: 2022 Martin Marmsoler <martin.marmsoler@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "FourierTest.h"
#include "backend/core/Project.h"
#include "backend/core/column/Column.h"
#include "backend/spreadsheet/Spreadsheet.h"
#include "backend/worksheet/Worksheet.h"
#include "backend/worksheet/plots/cartesian/CartesianPlot.h"
#include "backend/worksheet/plots/cartesian/XYAnalysisCurvePrivate.h"
#include "backend/worksheet/plots/cartesian/XYCurvePrivate.h"
#include "backend/worksheet/plots/cartesian/XYFourierFilterCurve.h"
// ##############################################################################
const QString dataPath = QStringLiteral("data/"); // relative path
#define READ_DATA(filename) \
auto filepath = QFINDTESTDATA(dataPath + filename); \
QVector<QVector<double>> data; \
do { \
QFile f(filepath); \
QCOMPARE(f.open(QIODevice::ReadOnly), true); \
\
while (!f.atEnd()) { \
const QString line = QLatin1String(f.readLine().simplified()); \
const auto entries = line.split(QLatin1Char(',')); \
QVector<double> values; \
for (const auto& entry : entries) { \
bool ok; \
double v = entry.toDouble(&ok); \
QVERIFY(ok); \
values.append(v); \
} \
data.append(values); \
} \
QVERIFY(data.count() > 0); \
/* Check that all rows have the same number of values */ \
const auto length = data.at(0).length(); \
for (const auto& v : data) \
QCOMPARE(v.length(), length); \
} while (false);
/*!
* Writing the results to a file to validate them
*/
#define WRITE_RESULT_DATA(xData, yData, filteredDataRef, filteredData) \
do { \
QFile f(filepath + QStringLiteral("_testOutput.csv")); \
QCOMPARE(f.open(QIODevice::WriteOnly), true); \
\
f.write("x,y,filtered_reference, filtered_calculated\n"); /* header */ \
for (int i = 0; i < xData.length(); i++) { \
f.write(QStringLiteral("%1,%2,%3,%4\n").arg(xData.at(i)).arg(yData.at(i)).arg(filteredDataRef.at(i)).arg(filteredData.at(i)).toLatin1()); \
} \
} while (false);
void FourierTest::addCurve() {
Project project;
auto* ws = new Worksheet(QStringLiteral("Worksheet"));
project.addChild(ws);
auto* plot = new CartesianPlot(QStringLiteral("plot"));
ws->addChild(plot);
auto* sheet = new Spreadsheet(QStringLiteral("sheet"));
project.addChild(sheet);
sheet->setColumnCount(2);
sheet->column(0)->setName(QStringLiteral("x"));
sheet->column(1)->setName(QStringLiteral("y"));
auto* curve = new XYCurve(QStringLiteral("curve"));
plot->addChild(curve);
curve->d_func()->setSelected(true);
QCOMPARE(plot->currentCurve(), curve);
plot->addFourierFilterCurve(); // Should not crash and curve should be assigned accordingly
const auto& analysisCurves = plot->children<XYAnalysisCurve>();
QCOMPARE(analysisCurves.count(), 1);
QCOMPARE(analysisCurves.at(0)->d_func()->dataSourceCurve, curve);
}
void FourierTest::lowPassButterWorth() {
const QString filename = QStringLiteral("butterworth.csv");
READ_DATA(filename);
// data
const auto xData = data.at(0);
const auto yData = data.at(1);
const auto filteredDataRef = data.at(2);
// data source columns
Column xDataColumn(QStringLiteral("x"), AbstractColumn::ColumnMode::Double);
xDataColumn.replaceValues(0, xData);
Column yDataColumn(QStringLiteral("y"), AbstractColumn::ColumnMode::Double);
yDataColumn.replaceValues(0, yData);
XYFourierFilterCurve curve(QStringLiteral("fourierFiltering"));
curve.setXDataColumn(&xDataColumn);
curve.setYDataColumn(&yDataColumn);
// prepare the filtering
auto filterData = curve.filterData();
filterData.type = nsl_filter_type_low_pass;
filterData.form = nsl_filter_form_butterworth;
filterData.autoRange = true; // use complete data range
filterData.cutoff = 100; // [Hz]
// filterData.cutoff2; // Not relevant for lowpass filter
filterData.order = 1;
filterData.unit = nsl_filter_cutoff_unit_frequency;
// filterData.unit2; // Not relevant for lowpass filter
// filterData.xRange; // Not relevant because autoRange is true
curve.setFilterData(filterData);
// perform the differentiation
curve.recalculate();
const XYFourierFilterCurve::FilterResult& results = curve.filterResult();
QVERIFY(results.available);
QVERIFY(results.valid);
// This does not work yet, because the implementation uses two side filtering,
// considering knowing of future values (values after the current processed index)
// Octave is using an IIR filter
// const auto xVec = *(curve.d_func()->xVector);
// const auto yVec = *(curve.d_func()->yVector);
// WRITE_RESULT_DATA(xData, yData, filteredDataRef, yVec);
// COMPARE_DOUBLE_VECTORS(xVec, xData);
// COMPARE_DOUBLE_VECTORS(yVec, filteredDataRef);
}
QTEST_MAIN(FourierTest)
|