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