v2.0.0
Loading...
Searching...
No Matches
layoutmaker.cpp
Go to the documentation of this file.
1//=============================================================================================================
18
19//=============================================================================================================
20// INCLUDES
21//=============================================================================================================
22
23#include "layoutmaker.h"
25#include <math/sphere.h>
26
27// MSVC builds already define _USE_MATH_DEFINES globally (see src/CMakeLists.txt),
28// so define it here only for the toolchains that do not.
29#ifndef _USE_MATH_DEFINES
30#define _USE_MATH_DEFINES
31#endif
32#include <math.h>
33#include <iostream>
34
35//=============================================================================================================
36// QT INCLUDES
37//=============================================================================================================
38
39#include <QTextStream>
40#include <QDebug>
41
42//=============================================================================================================
43// USED NAMESPACES
44//=============================================================================================================
45
46using namespace UTILSLIB;
47using namespace Eigen;
48
49//=============================================================================================================
50// DEFINES
51//=============================================================================================================
52
53constexpr double EPS = 1e-6;
54
55//=============================================================================================================
56// DEFINE MEMBER METHODS
57//=============================================================================================================
58
59bool LayoutMaker::makeLayout(const QList<QVector<float>>& inputPoints,
60 QList<QVector<float>>& outputPoints,
61 const QStringList& names,
62 QFile& outFile,
63 bool do_fit,
64 float prad,
65 float w,
66 float h,
67 bool writeFile,
68 bool mirrorXAxis,
69 bool mirrorYAxis)
70{
71 /*
72 * Automatically make a layout according to the
73 * channel locations in inputPoints
74 */
75 VectorXf r0 = VectorXf::Zero(3);
76 VectorXf rr(3);
77 float rad, th, phi;
78
79 float xmin, xmax, ymin, ymax;
80 int k;
81 int nchan = inputPoints.size();
82
83 MatrixXf rrs(nchan, 3);
84 VectorXf xx(nchan);
85 VectorXf yy(nchan);
86
87 if (nchan <= 0) {
88 std::cout << "No input points to lay out.\n";
89 return false;
90 }
91
92 //Fill matrix with 3D points
93 for (k = 0; k < nchan; k++) {
94 rrs(k, 0) = inputPoints.at(k)[0]; //x
95 rrs(k, 1) = inputPoints.at(k)[1]; //y
96 rrs(k, 2) = inputPoints.at(k)[2]; //z
97 }
98
99 std::cout << "Channels found for layout: " << nchan << "\n";
100
101 //Fit to sphere if wanted by the user
102 if (!do_fit) {
103 std::cout << "Using default origin:" << r0[0] << ", " << r0[1] << ", " << r0[2] << "\n";
104 } else {
105 Sphere sphere = Sphere::fit_sphere_simplex(rrs, 0.05);
106
107 r0 = sphere.center();
108 rad = sphere.radius();
109
110 std::cout << "best fitting sphere:\n";
111 std::cout << "torigin: " << r0[0] << ", " << r0[1] << ", " << r0[2] << std::endl
112 << "; tradius: " << rad << "\n";
113 }
114
115 /*
116 * Do the azimuthal equidistant projection
117 */
118 for (k = 0; k < nchan; k++) {
119 rr = static_cast<VectorXf>(rrs.row(k)) - r0; // MNE-C VEC_DIFF(r0, rrs[k], rr): rrs[k] - r0
120 sphere_coord(rr[0], rr[1], rr[2], &rad, &th, &phi);
121 xx[k] = prad * (2.0 * th / M_PI) * cos(phi);
122 yy[k] = prad * (2.0 * th / M_PI) * sin(phi);
123 }
124
125 /*
126 * Find suitable range of viewports
127 */
128 xmin = xmax = xx[0];
129 ymin = ymax = yy[0];
130
131 for (k = 1; k < nchan; k++) {
132 if (xx[k] > xmax)
133 xmax = xx[k];
134 else if (xx[k] < xmin)
135 xmin = xx[k];
136 if (yy[k] > ymax)
137 ymax = yy[k];
138 else if (yy[k] < ymin)
139 ymin = yy[k];
140 }
141
142 if (xmin == xmax || ymin == ymax) {
143 std::cout << "Cannot make a layout. All positions are identical\n";
144 return false;
145 }
146
147 xmax = xmax + 0.6 * w;
148 xmin = xmin - 0.6 * w;
149
150 ymax = ymax + 0.6 * h;
151 ymin = ymin - 0.6 * h;
152
153 /*
154 * Compose the viewports
155 */
156 QVector<float> point;
157 QTextStream out;
158
159 if (writeFile) {
160 if (!outFile.open(QIODevice::WriteOnly)) {
161 std::cout << "Could not open output file!\n";
162 return false;
163 }
164
165 out.setDevice(&outFile);
166 }
167
168#if QT_VERSION > QT_VERSION_CHECK(6, 0, 0)
169 using Qt::endl;
170#endif
171
172 out << "0.000000 0.000000 0.000000 0.000000" << endl;
173
174 for (k = 0; k < nchan; k++) {
175 point.clear();
176
177 if (mirrorXAxis)
178 point.append(-(xx[k] - 0.5 * w));
179 else
180 point.append(xx[k] - 0.5 * w);
181
182 if (mirrorYAxis)
183 point.append(-(yy[k] - 0.5 * h));
184 else
185 point.append(yy[k] - 0.5 * h);
186
187 outputPoints.append(point);
188
189 if (writeFile) {
190 if (k < names.size()) {
191 out << k + 1 << " " << point[0] << " " << point[1] << " " << w << " " << h << " " << names.at(k) << endl;
192 } else {
193 out << k + 1 << " " << point[0] << " " << point[1] << " " << w << " " << h << endl;
194 }
195 }
196 }
197
198 if (writeFile) {
199 std::cout << "Success while wrtiting to output file.\n";
200
201 outFile.close();
202 }
203
204 return true;
205}
206
207//=============================================================================================================
208
209bool LayoutMaker::makeLayout(const std::vector<std::vector<float>>& inputPoints,
210 std::vector<std::vector<float>>& outputPoints,
211 const std::vector<std::string>& names,
212 const std::string& outFilePath,
213 bool do_fit,
214 float prad,
215 float w,
216 float h,
217 bool writeFile,
218 bool mirrorXAxis,
219 bool mirrorYAxis)
220{
221 /*
222 * Automatically make a layout according to the
223 * channel locations in inputPoints
224 */
225 VectorXf r0 = VectorXf::Zero(3);
226 VectorXf rr(3);
227 float rad, th, phi;
228
229 float xmin, xmax, ymin, ymax;
230 // std::vector::size() is size_t; the channel count is used as an Eigen
231 // index and compared against int loop counters throughout.
232 int nchan = static_cast<int>(inputPoints.size());
233
234 MatrixXf rrs(nchan, 3);
235 VectorXf xx(nchan);
236 VectorXf yy(nchan);
237
238 if (nchan <= 0) {
239 std::cout << "No input points to lay out.\n";
240 return false;
241 }
242
243 //Fill matrix with 3D points
244 for (int k = 0; k < nchan; k++) {
245 rrs(k, 0) = inputPoints.at(k)[0]; //x
246 rrs(k, 1) = inputPoints.at(k)[1]; //y
247 rrs(k, 2) = inputPoints.at(k)[2]; //z
248 }
249
250 std::cout << "Channels found for layout: " << nchan << "\n";
251
252 //Fit to sphere if wanted by the user
253 if (!do_fit) {
254 std::cout << "Using default origin:" << r0[0] << ", " << r0[1] << ", " << r0[2] << "\n";
255 } else {
256 Sphere sphere = Sphere::fit_sphere_simplex(rrs, 0.05);
257
258 r0 = sphere.center();
259 rad = sphere.radius();
260
261 std::cout << "best fitting sphere:\n";
262 std::cout << "torigin: " << r0[0] << ", " << r0[1] << ", " << r0[2] << std::endl
263 << "; tradius: " << rad << "\n";
264 }
265
266 /*
267 * Do the azimuthal equidistant projection
268 */
269 for (int k = 0; k < nchan; k++) {
270 rr = static_cast<VectorXf>(rrs.row(k)) - r0; // MNE-C VEC_DIFF(r0, rrs[k], rr): rrs[k] - r0
271 sphere_coord(rr[0], rr[1], rr[2], &rad, &th, &phi);
272 xx[k] = prad * (2.0 * th / M_PI) * cos(phi);
273 yy[k] = prad * (2.0 * th / M_PI) * sin(phi);
274 }
275
276 /*
277 * Find suitable range of viewports
278 */
279 xmin = xmax = xx[0];
280 ymin = ymax = yy[0];
281
282 for (int k = 1; k < nchan; k++) {
283 if (xx[k] > xmax)
284 xmax = xx[k];
285 else if (xx[k] < xmin)
286 xmin = xx[k];
287 if (yy[k] > ymax)
288 ymax = yy[k];
289 else if (yy[k] < ymin)
290 ymin = yy[k];
291 }
292
293 if (xmin == xmax || ymin == ymax) {
294 std::cout << "Cannot make a layout. All positions are identical\n";
295 return false;
296 }
297
298 xmax = xmax + 0.6 * w;
299 xmin = xmin - 0.6 * w;
300
301 ymax = ymax + 0.6 * h;
302 ymin = ymin - 0.6 * h;
303
304 /*
305 * Compose the viewports
306 */
307 std::vector<float> point;
308 std::ofstream outFile;
309
310 if (writeFile) {
311 outFile.open(outFilePath);
312 if (!outFile.is_open()) {
313 std::cout << "Could not open output file!\n";
314 return false;
315 }
316 outFile << "0.000000 0.000000 0.000000 0.000000" << std::endl;
317 }
318
319 for (int k = 0; k < nchan; k++) {
320 point.clear();
321
322 if (mirrorXAxis)
323 point.push_back(-(xx[k] - 0.5 * w));
324 else
325 point.push_back(xx[k] - 0.5 * w);
326
327 if (mirrorYAxis)
328 point.push_back(-(yy[k] - 0.5 * h));
329 else
330 point.push_back(yy[k] - 0.5 * h);
331
332 outputPoints.push_back(point);
333
334 if (writeFile) {
335 if ((k) < (int)names.size()) {
336 outFile << k + 1 << " " << point[0] << " " << point[1] << " " << w << " " << h << " " << names.at(k) << std::endl;
337 } else {
338 outFile << k + 1 << " " << point[0] << " " << point[1] << " " << w << " " << h << std::endl;
339 }
340 }
341 }
342
343 if (writeFile) {
344 std::cout << "Success while wrtiting to output file.\n";
345 }
346
347 return true;
348}
349
350//=============================================================================================================
351
352void LayoutMaker::sphere_coord(float x,
353 float y,
354 float z,
355 float* r,
356 float* theta,
357 float* phi)
358{
359 /* Rectangular to spherical coordinates */
360 float rxy = sqrt(x * x + y * y);
361 if (rxy < EPS) { /* Let's hope this is reasonable */
362 *r = z;
363 *theta = 0.0;
364 *phi = 0.0;
365 } else {
366 *r = sqrt(x * x + y * y + z * z);
367 *theta = acos(z / (*r));
368 *phi = atan2(y, x);
369 if (*phi < 0.0)
370 *phi = *phi + 2.0 * M_PI;
371 }
372}
constexpr double EPS
Generator that projects 3-D electrode positions onto a 2-D .lout topographic layout via least-squares...
#define M_PI
Best-fit sphere from a 3-D point cloud with closed-form and Nelder–Mead solvers.
Header-only Nelder–Mead simplex minimiser with pluggable cost and report callables.
Shared utilities (I/O helpers, spectral analysis, layout management, warp algorithms).
3-D sphere value type with algebraic and Nelder–Mead best-fit factories.
Definition sphere.h:75
static Sphere fit_sphere_simplex(const Eigen::MatrixX3f &points, double simplex_size=2e-2)
Definition sphere.cpp:103
Eigen::Vector3f & center()
Definition sphere.h:113
float & radius()
Definition sphere.h:124
static bool makeLayout(const QList< QVector< float > > &inputPoints, QList< QVector< float > > &outputPoints, const QStringList &names, QFile &outFile, bool do_fit, float prad, float w, float h, bool writeFile=false, bool mirrorXAxis=false, bool mirrorYAxis=false)