v2.0.0
Loading...
Searching...
No Matches
python_test_helper.cpp
Go to the documentation of this file.
1//=============================================================================================================
19
20//=============================================================================================================
21// INCLUDES
22//=============================================================================================================
23
24#include "python_test_helper.h"
25
26#include <QCoreApplication>
27#include <QDir>
28#include <QFile>
29#include <QProcessEnvironment>
30#include <QTextStream>
31#include <QRegularExpression>
32
33//=============================================================================================================
34// USED NAMESPACES
35//=============================================================================================================
36
37using namespace UTILSLIB;
38using namespace Eigen;
39
40//=============================================================================================================
41// DEFINE MEMBER METHODS
42//=============================================================================================================
43
47
48//=============================================================================================================
49
51{
52 return isPythonAvailable() && hasPackage("mne");
53}
54
55//=============================================================================================================
56
58{
59 return m_runner.isPythonAvailable();
60}
61
62//=============================================================================================================
63
64bool PythonTestHelper::hasPackage(const QString& packageName) const
65{
66 return m_runner.isPackageAvailable(packageName);
67}
68
69//=============================================================================================================
70
71PythonRunnerResult PythonTestHelper::eval(const QString& code, int timeoutMs) const
72{
73 Q_UNUSED(timeoutMs);
74 return m_runner.runCode(code);
75}
76
77//=============================================================================================================
78
79double PythonTestHelper::evalDouble(const QString& code, bool* ok, int timeoutMs) const
80{
81 PythonRunnerResult result = eval(code, timeoutMs);
82 if (!result.success) {
83 if (ok)
84 *ok = false;
85 return 0.0;
86 }
87
88 bool parseOk = false;
89 double value = result.stdOut.trimmed().toDouble(&parseOk);
90 if (ok)
91 *ok = parseOk;
92 return parseOk ? value : 0.0;
93}
94
95//=============================================================================================================
96
97VectorXd PythonTestHelper::evalVector(const QString& code, bool* ok, int timeoutMs) const
98{
99 PythonRunnerResult result = eval(code, timeoutMs);
100 if (!result.success) {
101 if (ok)
102 *ok = false;
103 return VectorXd();
104 }
105
106 // Parse stdout: one value per line, or space-separated on one line
107 QString output = result.stdOut.trimmed();
108 if (output.isEmpty()) {
109 if (ok)
110 *ok = false;
111 return VectorXd();
112 }
113
114 QStringList lines = output.split('\n', Qt::SkipEmptyParts);
115
116 // If single line, split by whitespace
117 QList<double> values;
118 for (const QString& line : lines) {
119 QStringList tokens = line.trimmed().split(QRegularExpression("\\s+"), Qt::SkipEmptyParts);
120 for (const QString& token : tokens) {
121 bool parseOk = false;
122 double val = token.toDouble(&parseOk);
123 if (!parseOk) {
124 if (ok)
125 *ok = false;
126 return VectorXd();
127 }
128 values.append(val);
129 }
130 }
131
132 VectorXd vec(values.size());
133 for (int i = 0; i < values.size(); ++i) {
134 vec(i) = values[i];
135 }
136
137 if (ok)
138 *ok = true;
139 return vec;
140}
141
142//=============================================================================================================
143
144MatrixXd PythonTestHelper::evalMatrix(const QString& code, bool* ok, int timeoutMs) const
145{
146 PythonRunnerResult result = eval(code, timeoutMs);
147 if (!result.success) {
148 if (ok)
149 *ok = false;
150 return MatrixXd();
151 }
152
153 QString output = result.stdOut.trimmed();
154 if (output.isEmpty()) {
155 if (ok)
156 *ok = false;
157 return MatrixXd();
158 }
159
160 QStringList lines = output.split('\n', Qt::SkipEmptyParts);
161 int nRows = lines.size();
162 int nCols = -1;
163
164 QList<QList<double>> rowData;
165 for (const QString& line : lines) {
166 QStringList tokens = line.trimmed().split(QRegularExpression("\\s+"), Qt::SkipEmptyParts);
167 if (nCols < 0) {
168 nCols = tokens.size();
169 } else if (tokens.size() != nCols) {
170 if (ok)
171 *ok = false;
172 return MatrixXd();
173 }
174
175 QList<double> row;
176 for (const QString& token : tokens) {
177 bool parseOk = false;
178 double val = token.toDouble(&parseOk);
179 if (!parseOk) {
180 if (ok)
181 *ok = false;
182 return MatrixXd();
183 }
184 row.append(val);
185 }
186 rowData.append(row);
187 }
188
189 if (nCols <= 0) {
190 if (ok)
191 *ok = false;
192 return MatrixXd();
193 }
194
195 MatrixXd mat(nRows, nCols);
196 for (int r = 0; r < nRows; ++r) {
197 for (int c = 0; c < nCols; ++c) {
198 mat(r, c) = rowData[r][c];
199 }
200 }
201
202 if (ok)
203 *ok = true;
204 return mat;
205}
206
207//=============================================================================================================
208
210 const QStringList& args,
211 int timeoutMs) const
212{
213 Q_UNUSED(timeoutMs);
214 return m_runner.run(scriptPath, args);
215}
216
217//=============================================================================================================
218
220{
221 return QCoreApplication::applicationDirPath() + "/../resources/data/mne-cpp-test-data/";
222}
223
224//=============================================================================================================
225
227{
228 const QString val = QProcessEnvironment::systemEnvironment().value("MNE_REQUIRE_PYTHON").toLower();
229 return val == "true" || val == "1";
230}
231
232//=============================================================================================================
233
234bool PythonTestHelper::writeMatrix(const QString& filePath, const MatrixXd& mat)
235{
236 QFile file(filePath);
237 if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
238 return false;
239 }
240
241 QTextStream out(&file);
242 out.setRealNumberNotation(QTextStream::ScientificNotation);
243 out.setRealNumberPrecision(17);
244
245 for (int r = 0; r < mat.rows(); ++r) {
246 for (int c = 0; c < mat.cols(); ++c) {
247 if (c > 0)
248 out << ' ';
249 out << mat(r, c);
250 }
251 out << '\n';
252 }
253
254 file.close();
255 return true;
256}
257
258//=============================================================================================================
259
260MatrixXd PythonTestHelper::readMatrix(const QString& filePath, bool* ok)
261{
262 QFile file(filePath);
263 if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
264 if (ok)
265 *ok = false;
266 return MatrixXd();
267 }
268
269 QTextStream in(&file);
270 QList<QList<double>> rowData;
271 int nCols = -1;
272
273 while (!in.atEnd()) {
274 QString line = in.readLine().trimmed();
275 if (line.isEmpty())
276 continue;
277
278 QStringList tokens = line.split(QRegularExpression("\\s+"), Qt::SkipEmptyParts);
279 if (nCols < 0) {
280 nCols = tokens.size();
281 } else if (tokens.size() != nCols) {
282 if (ok)
283 *ok = false;
284 return MatrixXd();
285 }
286
287 QList<double> row;
288 for (const QString& token : tokens) {
289 bool parseOk = false;
290 double val = token.toDouble(&parseOk);
291 if (!parseOk) {
292 if (ok)
293 *ok = false;
294 return MatrixXd();
295 }
296 row.append(val);
297 }
298 rowData.append(row);
299 }
300
301 file.close();
302
303 if (rowData.isEmpty() || nCols <= 0) {
304 if (ok)
305 *ok = false;
306 return MatrixXd();
307 }
308
309 MatrixXd mat(rowData.size(), nCols);
310 for (int r = 0; r < rowData.size(); ++r) {
311 for (int c = 0; c < nCols; ++c) {
312 mat(r, c) = rowData[r][c];
313 }
314 }
315
316 if (ok)
317 *ok = true;
318 return mat;
319}
320
321//=============================================================================================================
322
323MatrixXd PythonTestHelper::evalMatrixViaFile(const QString& code,
324 const QString& outputFilePath,
325 bool* ok,
326 int timeoutMs) const
327{
328 PythonRunnerResult result = eval(code, timeoutMs);
329 if (!result.success) {
330 if (ok)
331 *ok = false;
332 return MatrixXd();
333 }
334
335 return readMatrix(outputFilePath, ok);
336}
QtTest convenience layer on top of UTILSLIB::PythonRunner for cross-validating MNE-CPP results agains...
Shared utilities (I/O helpers, spectral analysis, layout management, warp algorithms).
Script execution result container.
Eigen::VectorXd evalVector(const QString &code, bool *ok=nullptr, int timeoutMs=60000) const
static bool writeMatrix(const QString &filePath, const Eigen::MatrixXd &mat)
double evalDouble(const QString &code, bool *ok=nullptr, int timeoutMs=30000) const
PythonRunnerResult runScript(const QString &scriptPath, const QStringList &args={}, int timeoutMs=120000) const
Eigen::MatrixXd evalMatrixViaFile(const QString &code, const QString &outputFilePath, bool *ok=nullptr, int timeoutMs=120000) const
bool hasPackage(const QString &packageName) const
PythonRunnerResult eval(const QString &code, int timeoutMs=30000) const
static Eigen::MatrixXd readMatrix(const QString &filePath, bool *ok=nullptr)
Eigen::MatrixXd evalMatrix(const QString &code, bool *ok=nullptr, int timeoutMs=60000) const