v2.0.0
Loading...
Searching...
No Matches
mne_bem_surface.cpp
Go to the documentation of this file.
1//=============================================================================================================
36
37//=============================================================================================================
38// INCLUDES
39//=============================================================================================================
40
41#include "mne_bem_surface.h"
42#include <fstream>
43
44//=============================================================================================================
45// USED NAMESPACES
46//=============================================================================================================
47
48using namespace MNELIB;
49using namespace Eigen;
50using namespace FIFFLIB;
51
52//=============================================================================================================
53// DEFINE MEMBER METHODS
54//=============================================================================================================
55
57: MNESurface()
58, tri_cent(MatrixX3d::Zero(0,3))
59, tri_nn(MatrixX3d::Zero(0,3))
60, tri_area(VectorXd::Zero(0))
61{
62 id = -1;
63 np = -1;
64 ntri = -1;
65 coord_frame = -1;
66 sigma = -1;
67}
68
69//=============================================================================================================
70
72: MNESurface()
73, tri_cent(p_MNEBemSurface.tri_cent)
74, tri_nn(p_MNEBemSurface.tri_nn)
75, tri_area(p_MNEBemSurface.tri_area)
76{
77 id = p_MNEBemSurface.id;
78 np = p_MNEBemSurface.np;
79 ntri = p_MNEBemSurface.ntri;
80 coord_frame = p_MNEBemSurface.coord_frame;
81 sigma = p_MNEBemSurface.sigma;
82 rr = p_MNEBemSurface.rr;
83 nn = p_MNEBemSurface.nn;
84 itris = p_MNEBemSurface.itris;
85 neighbor_tri = p_MNEBemSurface.neighbor_tri;
86 neighbor_vert = p_MNEBemSurface.neighbor_vert;
87}
88
89//=============================================================================================================
90
94
95//=============================================================================================================
96
98{
99 id = -1;
100 np = -1;
101 ntri = -1;
102 coord_frame = -1;
103 sigma = -1;
104 rr.resize(0, 3);
105 nn.resize(0, 3);
106 itris.resize(0, 3);
107 tri_cent = MatrixX3d::Zero(0,3);
108 tri_nn = MatrixX3d::Zero(0,3);
109 tri_area = VectorXd::Zero(0);
110 neighbor_tri.clear();
111 neighbor_vert.clear();
112}
113
114//=============================================================================================================
115
117{
118 //
119 // Main triangulation
120 //
121 qInfo("\tCompleting triangulation info...");
122 this->tri_cent = MatrixX3d::Zero(this->ntri,3);
123 this->tri_nn = MatrixX3d::Zero(this->ntri,3);
124 this->tri_area = VectorXd::Zero(this->ntri);
125
126 Matrix3d r;
127 Vector3d a, b;
128 int k = 0;
129 float size = 0;
130 for (qint32 i = 0; i < this->ntri; ++i)
131 {
132 for ( qint32 j = 0; j < 3; ++j)
133 {
134 k = this->itris(i, j);
135
136 r(j,0) = this->rr(k, 0);
137 r(j,1) = this->rr(k, 1);
138 r(j,2) = this->rr(k, 2);
139
140 this->tri_cent(i, 0) += this->rr(k, 0);
141 this->tri_cent(i, 1) += this->rr(k, 1);
142 this->tri_cent(i, 2) += this->rr(k, 2);
143 }
144 this->tri_cent.row(i) /= 3.0f;
145
146 //cross product {cross((r2-r1),(r3-r1))}
147 a = (r.row(1) - r.row(0 )).transpose();
148 b = (r.row(2) - r.row(0)).transpose();
149 this->tri_nn(i,0) = a(1)*b(2)-a(2)*b(1);
150 this->tri_nn(i,1) = a(2)*b(0)-a(0)*b(2);
151 this->tri_nn(i,2) = a(0)*b(1)-a(1)*b(0);
152
153 //area
154 size = this->tri_nn.row(i)*this->tri_nn.row(i).transpose();
155 size = std::pow(size, 0.5f );
156
157 this->tri_area(i) = size/2.0f;
158 this->tri_nn.row(i) /= size;
159 }
160
161 std::fstream doc("./Output/tri_area.dat", std::ofstream::out | std::ofstream::trunc);
162 if(doc) // if succesfully opened
163 {
164 // instructions
165 doc << this->tri_area << "\n";
166 doc.close();
167 }
168
169 qInfo("Adding additional geometry info\n");
171
172 qInfo("[done]\n");
173
174 return true;
175}
176
177//=============================================================================================================
178
180{
181 int k,c,p,q;
182 bool found;
183
184 //Create neighboring triangle vector using temporary std::vector for efficient appending
185 {
186 std::vector<std::vector<int>> temp_ntri(this->itris.rows());
187 for (p = 0; p < this->itris.rows(); p++) {
188 for (k = 0; k < 3; k++) {
189 temp_ntri[this->itris(p,k)].push_back(p);
190 }
191 }
192 neighbor_tri.resize(this->itris.rows());
193 for (k = 0; k < static_cast<int>(temp_ntri.size()); k++) {
194 neighbor_tri[k] = Eigen::Map<Eigen::VectorXi>(temp_ntri[k].data(), temp_ntri[k].size());
195 }
196 }
197
198 //Create the neighboring vertices vector using temporary std::vector
199 {
200 std::vector<std::vector<int>> temp_nvert(this->np);
201 for (k = 0; k < this->np; k++) {
202 for (p = 0; p < neighbor_tri[k].size(); p++) {
203 //Fit in the other vertices of the neighboring triangle
204 for (c = 0; c < 3; c++) {
205 int vert = this->itris(neighbor_tri[k][p], c);
206
207 if (vert != k) {
208 found = false;
209
210 for (q = 0; q < static_cast<int>(temp_nvert[k].size()); q++) {
211 if (temp_nvert[k][q] == vert) {
212 found = true;
213 break;
214 }
215 }
216
217 if(!found) {
218 temp_nvert[k].push_back(vert);
219 }
220 }
221 }
222 }
223 }
224 neighbor_vert.resize(this->np);
225 for (k = 0; k < this->np; k++) {
226 neighbor_vert[k] = Eigen::Map<Eigen::VectorXi>(temp_nvert[k].data(), temp_nvert[k].size());
227 }
228 }
229
230 return true;
231}
232
233//=============================================================================================================
234
236{
237
238 //
239 // Accumulate the vertex normals
240 //
241
242// this->nn.resize(this->np,3);
243
244 for (qint32 p = 0; p < this->ntri; ++p) //check each triangle
245 {
246 for (qint32 j = 0; j < 3 ; ++j)
247 {
248 int nodenr;
249 nodenr = this->itris(p,j); //find the corners(nodes) of the triangles
250 this->nn(nodenr,0) += this->tri_nn(p,0); //add the triangle normal to the nodenormal
251 this->nn(nodenr,1) += this->tri_nn(p,1);
252 this->nn(nodenr,2) += this->tri_nn(p,2);
253 }
254 }
255
256 // normalize
257 for (qint32 p = 0; p < this->np; ++p)
258 {
259 float size = 0;
260 size = this->nn.row(p)*this->nn.row(p).transpose();
261 size = std::pow(size, 0.5f );
262 this->nn.row(p) /= size;
263 }
264
265return true;
266}
267
268//=============================================================================================================
269
271{
272 if(this->id <=0)
273 this->id=FIFFV_MNE_SURF_UNKNOWN;
274 if(this->sigma>0.0)
275 p_pStream->write_float(FIFF_BEM_SIGMA, &this->sigma);
276 p_pStream->write_int(FIFF_BEM_SURF_ID, &this->id);
277 p_pStream->write_int(FIFF_MNE_COORD_FRAME, &this->coord_frame);
278 p_pStream->write_int(FIFF_BEM_SURF_NNODE, &this->np);
279 p_pStream->write_int(FIFF_BEM_SURF_NTRI, &this->ntri);
280 p_pStream->write_float_matrix(FIFF_BEM_SURF_NODES, Eigen::MatrixXf(this->rr));
281 if (this->ntri > 0)
282 p_pStream->write_int_matrix(FIFF_BEM_SURF_TRIANGLES, Eigen::MatrixXi(this->itris.array() + 1));
283 p_pStream->write_float_matrix(FIFF_BEM_SURF_NORMALS, Eigen::MatrixXf(this->nn));
284}
285
286//=============================================================================================================
287
289{
290 switch(id) {
291 case FIFFV_BEM_SURF_ID_BRAIN: return "Brain";
292 case FIFFV_BEM_SURF_ID_SKULL: return "Skull";
293 case FIFFV_BEM_SURF_ID_HEAD: return "Head";
294 case FIFFV_BEM_SURF_ID_UNKNOWN: return "Unknown";
295 default: return "Unknown";
296 }
297}
298
299//=============================================================================================================
300
304static double edgeLenSq(const Eigen::MatrixX3d& rr, int v0, int v1)
305{
306 return (rr.row(v0) - rr.row(v1)).squaredNorm();
307}
308
309//=============================================================================================================
310
312 const MNEBemSurface& outerSkin,
313 const QList<int>& targetVertices)
314{
315 QList<MNEBemSurface> results;
316
317 if (outerSkin.np <= 0 || outerSkin.ntri <= 0) {
318 qWarning("MNEBemSurface::makeScalpSurfaces - Input surface is empty.");
319 return results;
320 }
321
322 for (int target : targetVertices) {
323 if (target >= outerSkin.np) {
324 // No decimation needed, just copy
325 results.append(MNEBemSurface(outerSkin));
326 continue;
327 }
328
329 // Work on a copy
330 MNEBemSurface surf(outerSkin);
331
332 // Build vertex-referenced rr as doubles, triangles as ints
333 // Use iterative edge collapse: find shortest edge, collapse to midpoint
334 Eigen::MatrixX3d rr = surf.rr.cast<double>();
335 Eigen::MatrixX3d nn = surf.nn.cast<double>();
336
337 // Build triangle list as std::vector for easy manipulation
338 int nTri = surf.ntri;
339 std::vector<std::array<int,3>> tris(nTri);
340 for (int i = 0; i < nTri; ++i) {
341 tris[i] = {surf.itris(i, 0), surf.itris(i, 1), surf.itris(i, 2)};
342 }
343
344 // Track which vertices are alive
345 int currentNp = surf.np;
346 std::vector<bool> vertAlive(currentNp, true);
347 // Redirect map: when a vertex is collapsed, redirect to its replacement
348 std::vector<int> redirect(currentNp);
349 for (int i = 0; i < currentNp; ++i)
350 redirect[i] = i;
351
352 auto resolve = [&](int v) -> int {
353 while (redirect[v] != v)
354 v = redirect[v];
355 return v;
356 };
357
358 while (currentNp > target) {
359 // Find shortest edge in the mesh
360 double bestLen = std::numeric_limits<double>::max();
361 int bestTri = -1;
362 int bestEdge = -1;
363
364 for (int t = 0; t < static_cast<int>(tris.size()); ++t) {
365 int v0 = resolve(tris[t][0]);
366 int v1 = resolve(tris[t][1]);
367 int v2 = resolve(tris[t][2]);
368 // Skip degenerate triangles
369 if (v0 == v1 || v1 == v2 || v0 == v2)
370 continue;
371
372 double d01 = edgeLenSq(rr, v0, v1);
373 double d12 = edgeLenSq(rr, v1, v2);
374 double d20 = edgeLenSq(rr, v2, v0);
375
376 if (d01 < bestLen) { bestLen = d01; bestTri = t; bestEdge = 0; }
377 if (d12 < bestLen) { bestLen = d12; bestTri = t; bestEdge = 1; }
378 if (d20 < bestLen) { bestLen = d20; bestTri = t; bestEdge = 2; }
379 }
380
381 if (bestTri < 0)
382 break;
383
384 // Collapse edge: merge the two vertices into the midpoint
385 int va = resolve(tris[bestTri][bestEdge]);
386 int vb = resolve(tris[bestTri][(bestEdge + 1) % 3]);
387
388 // Place midpoint at va
389 rr.row(va) = (rr.row(va) + rr.row(vb)) * 0.5;
390 nn.row(va) = (nn.row(va) + nn.row(vb)).normalized();
391
392 // Redirect vb -> va
393 redirect[vb] = va;
394 vertAlive[vb] = false;
395 currentNp--;
396 }
397
398 // Rebuild compact vertex array and triangle array
399 std::vector<int> oldToNew(surf.np, -1);
400 int newNp = 0;
401 for (int i = 0; i < surf.np; ++i) {
402 if (vertAlive[i] && resolve(i) == i) {
403 oldToNew[i] = newNp++;
404 }
405 }
406
407 MNEBemSurface decimated;
408 decimated.id = surf.id;
409 decimated.coord_frame = surf.coord_frame;
410 decimated.sigma = surf.sigma;
411 decimated.np = newNp;
412
413 // Use Eigen types matching MNESurfaceOrVolume
414 MNESurfaceOrVolume::PointsT newRr(newNp, 3);
415 Eigen::MatrixX3f newNn(newNp, 3);
416 for (int i = 0; i < surf.np; ++i) {
417 if (oldToNew[i] >= 0) {
418 newRr.row(oldToNew[i]) = rr.row(i).cast<float>();
419 newNn.row(oldToNew[i]) = nn.row(i).cast<float>();
420 }
421 }
422 decimated.rr = newRr;
423 decimated.nn = newNn;
424
425 // Rebuild triangles
426 std::vector<std::array<int,3>> newTris;
427 for (const auto& tri : tris) {
428 int a = oldToNew[resolve(tri[0])];
429 int b = oldToNew[resolve(tri[1])];
430 int c = oldToNew[resolve(tri[2])];
431 if (a >= 0 && b >= 0 && c >= 0 && a != b && b != c && a != c) {
432 newTris.push_back({a, b, c});
433 }
434 }
435
436 decimated.ntri = static_cast<int>(newTris.size());
437 MNESurfaceOrVolume::TrianglesT newItris(decimated.ntri, 3);
438 for (int i = 0; i < decimated.ntri; ++i) {
439 newItris(i, 0) = newTris[i][0];
440 newItris(i, 1) = newTris[i][1];
441 newItris(i, 2) = newTris[i][2];
442 }
443 decimated.itris = newItris;
444
445 // Recompute triangle data
446 decimated.addTriangleData();
447
448 results.append(decimated);
449 }
450
451 return results;
452}
#define FIFFV_BEM_SURF_ID_UNKNOWN
Definition fiff_file.h:748
#define FIFF_BEM_SURF_NNODE
Definition fiff_file.h:733
#define FIFFV_BEM_SURF_ID_SKULL
Definition fiff_file.h:751
#define FIFF_BEM_SURF_NODES
Definition fiff_file.h:735
#define FIFF_BEM_SURF_TRIANGLES
Definition fiff_file.h:736
#define FIFF_BEM_SURF_ID
Definition fiff_file.h:731
#define FIFF_BEM_SURF_NTRI
Definition fiff_file.h:734
#define FIFF_BEM_SIGMA
Definition fiff_file.h:744
#define FIFFV_BEM_SURF_ID_HEAD
Definition fiff_file.h:752
#define FIFF_BEM_SURF_NORMALS
Definition fiff_file.h:737
#define FIFFV_BEM_SURF_ID_BRAIN
Definition fiff_file.h:749
#define FIFF_MNE_COORD_FRAME
#define FIFFV_MNE_SURF_UNKNOWN
MNEBemSurface class declaration.
Core MNE data structures (source spaces, source estimates, hemispheres).
FIFF file I/O and data structures (raw, epochs, evoked, covariance, forward).
FIFF File I/O routines.
fiff_long_t write_float_matrix(fiff_int_t kind, const Eigen::MatrixXf &mat)
fiff_long_t write_int_matrix(fiff_int_t kind, const Eigen::MatrixXi &mat)
fiff_long_t write_int(fiff_int_t kind, const fiff_int_t *data, fiff_int_t nel=1, fiff_int_t next=FIFFV_NEXT_SEQ)
fiff_long_t write_float(fiff_int_t kind, const float *data, fiff_int_t nel=1)
Eigen::VectorXd tri_area
void writeToStream(FIFFLIB::FiffStream *p_pStream)
Eigen::MatrixX3d tri_nn
Eigen::MatrixX3d tri_cent
static QString id_name(int id)
static QList< MNEBemSurface > makeScalpSurfaces(const MNEBemSurface &outerSkin, const QList< int > &targetVertices={2562, 10242, 40962})
std::vector< Eigen::VectorXi > neighbor_tri
std::vector< Eigen::VectorXi > neighbor_vert
Eigen::Matrix< int, Eigen::Dynamic, 3, Eigen::RowMajor > TrianglesT
std::vector< MNETriangle > tris
Eigen::Matrix< float, Eigen::Dynamic, 3, Eigen::RowMajor > PointsT