v2.0.0
Loading...
Searching...
No Matches
mne_bem_surface.cpp
Go to the documentation of this file.
1//=============================================================================================================
21
22//=============================================================================================================
23// INCLUDES
24//=============================================================================================================
25
26#include "mne_bem_surface.h"
27
28//=============================================================================================================
29// USED NAMESPACES
30//=============================================================================================================
31
32using namespace MNELIB;
33using namespace Eigen;
34using namespace FIFFLIB;
35
36//=============================================================================================================
37// DEFINE MEMBER METHODS
38//=============================================================================================================
39
41: MNESurface()
42, tri_cent(MatrixX3d::Zero(0, 3))
43, tri_nn(MatrixX3d::Zero(0, 3))
44, tri_area(VectorXd::Zero(0))
45{
46 id = -1;
47 np = -1;
48 ntri = -1;
49 coord_frame = -1;
50 sigma = -1;
51}
52
53//=============================================================================================================
54
56: MNESurface()
57, tri_cent(p_MNEBemSurface.tri_cent)
58, tri_nn(p_MNEBemSurface.tri_nn)
59, tri_area(p_MNEBemSurface.tri_area)
60{
61 id = p_MNEBemSurface.id;
62 np = p_MNEBemSurface.np;
63 ntri = p_MNEBemSurface.ntri;
64 coord_frame = p_MNEBemSurface.coord_frame;
65 sigma = p_MNEBemSurface.sigma;
66 rr = p_MNEBemSurface.rr;
67 nn = p_MNEBemSurface.nn;
68 itris = p_MNEBemSurface.itris;
69 neighbor_tri = p_MNEBemSurface.neighbor_tri;
70 neighbor_vert = p_MNEBemSurface.neighbor_vert;
71}
72
73//=============================================================================================================
74
78
79//=============================================================================================================
80
82{
83 id = -1;
84 np = -1;
85 ntri = -1;
86 coord_frame = -1;
87 sigma = -1;
88 rr.resize(0, 3);
89 nn.resize(0, 3);
90 itris.resize(0, 3);
91 tri_cent = MatrixX3d::Zero(0, 3);
92 tri_nn = MatrixX3d::Zero(0, 3);
93 tri_area = VectorXd::Zero(0);
94 neighbor_tri.clear();
95 neighbor_vert.clear();
96}
97
98//=============================================================================================================
99
101{
102 //
103 // Main triangulation
104 //
105 qInfo("\tCompleting triangulation info...");
106 this->tri_cent = MatrixX3d::Zero(this->ntri, 3);
107 this->tri_nn = MatrixX3d::Zero(this->ntri, 3);
108 this->tri_area = VectorXd::Zero(this->ntri);
109
110 Matrix3d r;
111 Vector3d a, b;
112 int k = 0;
113 float size = 0;
114 for (qint32 i = 0; i < this->ntri; ++i) {
115 for (qint32 j = 0; j < 3; ++j) {
116 k = this->itris(i, j);
117
118 r(j, 0) = this->rr(k, 0);
119 r(j, 1) = this->rr(k, 1);
120 r(j, 2) = this->rr(k, 2);
121
122 this->tri_cent(i, 0) += this->rr(k, 0);
123 this->tri_cent(i, 1) += this->rr(k, 1);
124 this->tri_cent(i, 2) += this->rr(k, 2);
125 }
126 this->tri_cent.row(i) /= 3.0f;
127
128 //cross product {cross((r2-r1),(r3-r1))}
129 a = (r.row(1) - r.row(0)).transpose();
130 b = (r.row(2) - r.row(0)).transpose();
131 this->tri_nn(i, 0) = a(1) * b(2) - a(2) * b(1);
132 this->tri_nn(i, 1) = a(2) * b(0) - a(0) * b(2);
133 this->tri_nn(i, 2) = a(0) * b(1) - a(1) * b(0);
134
135 //area
136 size = this->tri_nn.row(i) * this->tri_nn.row(i).transpose();
137 size = std::pow(size, 0.5f);
138
139 this->tri_area(i) = size / 2.0f;
140 this->tri_nn.row(i) /= size;
141 }
142
143 qInfo("Adding additional geometry info\n");
145
146 qInfo("[done]\n");
147
148 return true;
149}
150
151//=============================================================================================================
152
154{
155 int k, c, p, q;
156 bool found;
157
158 //Create neighboring triangle vector using temporary std::vector for efficient appending
159 {
160 // One list per vertex, as mne-python's neighbor_tri
161 std::vector<std::vector<int>> temp_ntri(this->np);
162 for (p = 0; p < this->itris.rows(); p++) {
163 for (k = 0; k < 3; k++) {
164 temp_ntri[this->itris(p, k)].push_back(p);
165 }
166 }
167 neighbor_tri.resize(this->np);
168 for (k = 0; k < static_cast<int>(temp_ntri.size()); k++) {
169 neighbor_tri[k] = Eigen::Map<Eigen::VectorXi>(temp_ntri[k].data(), temp_ntri[k].size());
170 }
171 }
172
173 //Create the neighboring vertices vector using temporary std::vector
174 {
175 std::vector<std::vector<int>> temp_nvert(this->np);
176 for (k = 0; k < this->np; k++) {
177 for (p = 0; p < neighbor_tri[k].size(); p++) {
178 //Fit in the other vertices of the neighboring triangle
179 for (c = 0; c < 3; c++) {
180 int vert = this->itris(neighbor_tri[k][p], c);
181
182 if (vert != k) {
183 found = false;
184
185 for (q = 0; q < static_cast<int>(temp_nvert[k].size()); q++) {
186 if (temp_nvert[k][q] == vert) {
187 found = true;
188 break;
189 }
190 }
191
192 if (!found) {
193 temp_nvert[k].push_back(vert);
194 }
195 }
196 }
197 }
198 }
199 neighbor_vert.resize(this->np);
200 for (k = 0; k < this->np; k++) {
201 neighbor_vert[k] = Eigen::Map<Eigen::VectorXi>(temp_nvert[k].data(), temp_nvert[k].size());
202 }
203 }
204
205 return true;
206}
207
208//=============================================================================================================
209
211{
212 //
213 // Accumulate the vertex normals from scratch (MNE-C mne_add_vertex_normals)
214 //
215 this->nn = NormalsT::Zero(this->np, 3);
216
217 for (qint32 p = 0; p < this->ntri; ++p) //check each triangle
218 {
219 for (qint32 j = 0; j < 3; ++j) {
220 int nodenr;
221 nodenr = this->itris(p, j); //find the corners(nodes) of the triangles
222 this->nn(nodenr, 0) += this->tri_nn(p, 0); //add the triangle normal to the nodenormal
223 this->nn(nodenr, 1) += this->tri_nn(p, 1);
224 this->nn(nodenr, 2) += this->tri_nn(p, 2);
225 }
226 }
227
228 // normalize
229 for (qint32 p = 0; p < this->np; ++p) {
230 const float size = this->nn.row(p).norm();
231 if (size > 0.0f)
232 this->nn.row(p) /= size;
233 }
234
235 return true;
236}
237
238
239//=============================================================================================================
240
242{
243 switch (id) {
245 return "Brain";
247 return "Skull";
249 return "Head";
251 return "Unknown";
252 default:
253 return "Unknown";
254 }
255}
256
257//=============================================================================================================
258
262static double edgeLenSq(const Eigen::MatrixX3d& rr, int v0, int v1)
263{
264 return (rr.row(v0) - rr.row(v1)).squaredNorm();
265}
266
267//=============================================================================================================
268
270 const MNEBemSurface& outerSkin,
271 const QList<int>& targetVertices)
272{
273 QList<MNEBemSurface> results;
274
275 if (outerSkin.np <= 0 || outerSkin.ntri <= 0) {
276 qWarning("MNEBemSurface::makeScalpSurfaces - Input surface is empty.");
277 return results;
278 }
279
280 for (int target : targetVertices) {
281 if (target >= outerSkin.np) {
282 // No decimation needed, just copy
283 results.append(MNEBemSurface(outerSkin));
284 continue;
285 }
286
287 // Work on a copy
288 MNEBemSurface surf(outerSkin);
289
290 // Build vertex-referenced rr as doubles, triangles as ints
291 // Use iterative edge collapse: find shortest edge, collapse to midpoint
292 Eigen::MatrixX3d rr = surf.rr.cast<double>();
293 Eigen::MatrixX3d nn = surf.nn.cast<double>();
294
295 // Build triangle list as std::vector for easy manipulation
296 int nTri = surf.ntri;
297 std::vector<std::array<int, 3>> tris(nTri);
298 for (int i = 0; i < nTri; ++i) {
299 tris[i] = {surf.itris(i, 0), surf.itris(i, 1), surf.itris(i, 2)};
300 }
301
302 // Track which vertices are alive
303 int currentNp = surf.np;
304 std::vector<bool> vertAlive(currentNp, true);
305 // Redirect map: when a vertex is collapsed, redirect to its replacement
306 std::vector<int> redirect(currentNp);
307 for (int i = 0; i < currentNp; ++i)
308 redirect[i] = i;
309
310 auto resolve = [&](int v) -> int {
311 while (redirect[v] != v)
312 v = redirect[v];
313 return v;
314 };
315
316 while (currentNp > target) {
317 // Find shortest edge in the mesh
318 double bestLen = std::numeric_limits<double>::max();
319 int bestTri = -1;
320 int bestEdge = -1;
321
322 for (int t = 0; t < static_cast<int>(tris.size()); ++t) {
323 int v0 = resolve(tris[t][0]);
324 int v1 = resolve(tris[t][1]);
325 int v2 = resolve(tris[t][2]);
326 // Skip degenerate triangles
327 if (v0 == v1 || v1 == v2 || v0 == v2)
328 continue;
329
330 double d01 = edgeLenSq(rr, v0, v1);
331 double d12 = edgeLenSq(rr, v1, v2);
332 double d20 = edgeLenSq(rr, v2, v0);
333
334 if (d01 < bestLen) {
335 bestLen = d01;
336 bestTri = t;
337 bestEdge = 0;
338 }
339 if (d12 < bestLen) {
340 bestLen = d12;
341 bestTri = t;
342 bestEdge = 1;
343 }
344 if (d20 < bestLen) {
345 bestLen = d20;
346 bestTri = t;
347 bestEdge = 2;
348 }
349 }
350
351 if (bestTri < 0)
352 break;
353
354 // Collapse edge: merge the two vertices into the midpoint
355 int va = resolve(tris[bestTri][bestEdge]);
356 int vb = resolve(tris[bestTri][(bestEdge + 1) % 3]);
357
358 // Place midpoint at va
359 rr.row(va) = (rr.row(va) + rr.row(vb)) * 0.5;
360 nn.row(va) = (nn.row(va) + nn.row(vb)).normalized();
361
362 // Redirect vb -> va
363 redirect[vb] = va;
364 vertAlive[vb] = false;
365 currentNp--;
366 }
367
368 // Rebuild compact vertex array and triangle array
369 std::vector<int> oldToNew(surf.np, -1);
370 int newNp = 0;
371 for (int i = 0; i < surf.np; ++i) {
372 if (vertAlive[i] && resolve(i) == i) {
373 oldToNew[i] = newNp++;
374 }
375 }
376
377 MNEBemSurface decimated;
378 decimated.id = surf.id;
379 decimated.coord_frame = surf.coord_frame;
380 decimated.sigma = surf.sigma;
381 decimated.np = newNp;
382
383 // Use Eigen types matching MNESurfaceOrVolume
384 MNESurfaceOrVolume::PointsT newRr(newNp, 3);
385 Eigen::MatrixX3f newNn(newNp, 3);
386 for (int i = 0; i < surf.np; ++i) {
387 if (oldToNew[i] >= 0) {
388 newRr.row(oldToNew[i]) = rr.row(i).cast<float>();
389 newNn.row(oldToNew[i]) = nn.row(i).cast<float>();
390 }
391 }
392 decimated.rr = newRr;
393 decimated.nn = newNn;
394
395 // Rebuild triangles
396 std::vector<std::array<int, 3>> newTris;
397 for (const auto& tri : tris) {
398 int a = oldToNew[resolve(tri[0])];
399 int b = oldToNew[resolve(tri[1])];
400 int c = oldToNew[resolve(tri[2])];
401 if (a >= 0 && b >= 0 && c >= 0 && a != b && b != c && a != c) {
402 newTris.push_back({a, b, c});
403 }
404 }
405
406 decimated.ntri = static_cast<int>(newTris.size());
407 MNESurfaceOrVolume::TrianglesT newItris(decimated.ntri, 3);
408 for (int i = 0; i < decimated.ntri; ++i) {
409 newItris(i, 0) = newTris[i][0];
410 newItris(i, 1) = newTris[i][1];
411 newItris(i, 2) = newTris[i][2];
412 }
413 decimated.itris = newItris;
414
415 // Recompute triangle data
416 decimated.addTriangleData();
417
418 results.append(decimated);
419 }
420
421 return results;
422}
constexpr auto FIFFV_BEM_SURF_ID_HEAD
Definition fiff_file.h:743
constexpr auto FIFFV_BEM_SURF_ID_UNKNOWN
Definition fiff_file.h:739
constexpr auto FIFFV_BEM_SURF_ID_SKULL
Definition fiff_file.h:742
constexpr auto FIFFV_BEM_SURF_ID_BRAIN
Definition fiff_file.h:740
Single closed BEM surface (triangulation, normals, conductivity).
Core MNE data structures (source spaces, source estimates, hemispheres).
FIFF file I/O, in-memory data structures and high-level readers/writers.
Eigen::VectorXd tri_area
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