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) *ok = false;
84 return 0.0;
85 }
86
87 bool parseOk = false;
88 double value = result.stdOut.trimmed().toDouble(&parseOk);
89 if (ok) *ok = parseOk;
90 return parseOk ? value : 0.0;
91}
92
93//=============================================================================================================
94
95VectorXd PythonTestHelper::evalVector(const QString& code, bool* ok, int timeoutMs) const
96{
97 PythonRunnerResult result = eval(code, timeoutMs);
98 if (!result.success) {
99 if (ok) *ok = false;
100 return VectorXd();
101 }
102
103 // Parse stdout: one value per line, or space-separated on one line
104 QString output = result.stdOut.trimmed();
105 if (output.isEmpty()) {
106 if (ok) *ok = false;
107 return VectorXd();
108 }
109
110 QStringList lines = output.split('\n', Qt::SkipEmptyParts);
111
112 // If single line, split by whitespace
113 QList<double> values;
114 for (const QString& line : lines) {
115 QStringList tokens = line.trimmed().split(QRegularExpression("\\s+"), Qt::SkipEmptyParts);
116 for (const QString& token : tokens) {
117 bool parseOk = false;
118 double val = token.toDouble(&parseOk);
119 if (!parseOk) {
120 if (ok) *ok = false;
121 return VectorXd();
122 }
123 values.append(val);
124 }
125 }
126
127 VectorXd vec(values.size());
128 for (int i = 0; i < values.size(); ++i) {
129 vec(i) = values[i];
130 }
131
132 if (ok) *ok = true;
133 return vec;
134}
135
136//=============================================================================================================
137
138MatrixXd PythonTestHelper::evalMatrix(const QString& code, bool* ok, int timeoutMs) const
139{
140 PythonRunnerResult result = eval(code, timeoutMs);
141 if (!result.success) {
142 if (ok) *ok = false;
143 return MatrixXd();
144 }
145
146 QString output = result.stdOut.trimmed();
147 if (output.isEmpty()) {
148 if (ok) *ok = false;
149 return MatrixXd();
150 }
151
152 QStringList lines = output.split('\n', Qt::SkipEmptyParts);
153 int nRows = lines.size();
154 int nCols = -1;
155
156 QList<QList<double>> rowData;
157 for (const QString& line : lines) {
158 QStringList tokens = line.trimmed().split(QRegularExpression("\\s+"), Qt::SkipEmptyParts);
159 if (nCols < 0) {
160 nCols = tokens.size();
161 } else if (tokens.size() != nCols) {
162 if (ok) *ok = false;
163 return MatrixXd();
164 }
165
166 QList<double> row;
167 for (const QString& token : tokens) {
168 bool parseOk = false;
169 double val = token.toDouble(&parseOk);
170 if (!parseOk) {
171 if (ok) *ok = false;
172 return MatrixXd();
173 }
174 row.append(val);
175 }
176 rowData.append(row);
177 }
178
179 if (nCols <= 0) {
180 if (ok) *ok = false;
181 return MatrixXd();
182 }
183
184 MatrixXd mat(nRows, nCols);
185 for (int r = 0; r < nRows; ++r) {
186 for (int c = 0; c < nCols; ++c) {
187 mat(r, c) = rowData[r][c];
188 }
189 }
190
191 if (ok) *ok = true;
192 return mat;
193}
194
195//=============================================================================================================
196
198 const QStringList& args,
199 int timeoutMs) const
200{
201 Q_UNUSED(timeoutMs);
202 return m_runner.run(scriptPath, args);
203}
204
205//=============================================================================================================
206
208{
209 return QCoreApplication::applicationDirPath() + "/../resources/data/mne-cpp-test-data/";
210}
211
212//=============================================================================================================
213
215{
216 const QString val = QProcessEnvironment::systemEnvironment().value("MNE_REQUIRE_PYTHON").toLower();
217 return val == "true" || val == "1";
218}
219
220//=============================================================================================================
221
222bool PythonTestHelper::writeMatrix(const QString& filePath, const MatrixXd& mat)
223{
224 QFile file(filePath);
225 if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
226 return false;
227 }
228
229 QTextStream out(&file);
230 out.setRealNumberNotation(QTextStream::ScientificNotation);
231 out.setRealNumberPrecision(17);
232
233 for (int r = 0; r < mat.rows(); ++r) {
234 for (int c = 0; c < mat.cols(); ++c) {
235 if (c > 0) out << ' ';
236 out << mat(r, c);
237 }
238 out << '\n';
239 }
240
241 file.close();
242 return true;
243}
244
245//=============================================================================================================
246
247MatrixXd PythonTestHelper::readMatrix(const QString& filePath, bool* ok)
248{
249 QFile file(filePath);
250 if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
251 if (ok) *ok = false;
252 return MatrixXd();
253 }
254
255 QTextStream in(&file);
256 QList<QList<double>> rowData;
257 int nCols = -1;
258
259 while (!in.atEnd()) {
260 QString line = in.readLine().trimmed();
261 if (line.isEmpty()) continue;
262
263 QStringList tokens = line.split(QRegularExpression("\\s+"), Qt::SkipEmptyParts);
264 if (nCols < 0) {
265 nCols = tokens.size();
266 } else if (tokens.size() != nCols) {
267 if (ok) *ok = false;
268 return MatrixXd();
269 }
270
271 QList<double> row;
272 for (const QString& token : tokens) {
273 bool parseOk = false;
274 double val = token.toDouble(&parseOk);
275 if (!parseOk) {
276 if (ok) *ok = false;
277 return MatrixXd();
278 }
279 row.append(val);
280 }
281 rowData.append(row);
282 }
283
284 file.close();
285
286 if (rowData.isEmpty() || nCols <= 0) {
287 if (ok) *ok = false;
288 return MatrixXd();
289 }
290
291 MatrixXd mat(rowData.size(), nCols);
292 for (int r = 0; r < rowData.size(); ++r) {
293 for (int c = 0; c < nCols; ++c) {
294 mat(r, c) = rowData[r][c];
295 }
296 }
297
298 if (ok) *ok = true;
299 return mat;
300}
301
302//=============================================================================================================
303
304MatrixXd PythonTestHelper::evalMatrixViaFile(const QString& code,
305 const QString& outputFilePath,
306 bool* ok,
307 int timeoutMs) const
308{
309 PythonRunnerResult result = eval(code, timeoutMs);
310 if (!result.success) {
311 if (ok) *ok = false;
312 return MatrixXd();
313 }
314
315 return readMatrix(outputFilePath, ok);
316}
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