v2.0.0
Loading...
Searching...
No Matches
mne_source_space.cpp
Go to the documentation of this file.
1//=============================================================================================================
19
20//=============================================================================================================
21// INCLUDES
22//=============================================================================================================
23
24#include "mne_source_space.h"
25#include "mne_nearest.h"
26#include "mne_patch_info.h"
27#include "mne_mgh_tag_group.h"
28#include "mne_surface.h"
29#include "mne_hemisphere.h"
30#include "filter_thread_arg.h"
31
33#include <fiff/fiff_constants.h>
35#include <fiff/fiff_stream.h>
36#include <fiff/fiff_tag.h>
37
38#include <fiff/fiff_byte_swap.h>
39
40#include <QFile>
41#include <QTextStream>
42#include <QtConcurrent>
43#include <QDebug>
44
45#include <cstring>
46#include <memory>
47
48// MSVC builds already define _USE_MATH_DEFINES globally (see src/CMakeLists.txt),
49// so define it here only for the toolchains that do not.
50#ifndef _USE_MATH_DEFINES
51#define _USE_MATH_DEFINES
52#endif
53#include <math.h>
54
56
57constexpr int X = 0;
58constexpr int Y = 1;
59constexpr int Z = 2;
60
61constexpr int FAIL = -1;
62constexpr int OK = 0;
63
64constexpr int NNEIGHBORS = 26;
65
66constexpr int CURVATURE_FILE_MAGIC_NUMBER = 16777215;
67
68constexpr int TAG_OLD_MGH_XFORM = 30;
69constexpr int TAG_OLD_COLORTABLE = 1;
70constexpr int TAG_OLD_USEREALRAS = 2;
71constexpr int TAG_USEREALRAS = 4;
72
73//=============================================================================================================
74// USED NAMESPACES
75//=============================================================================================================
76
77using namespace Eigen;
78using namespace FIFFLIB;
79using namespace MNELIB;
80
81//=============================================================================================================
82// FreeSurfer I/O helpers (file-scope, used only from mne_source_space.cpp)
83//=============================================================================================================
84
85namespace
86{
87
88using PointsT = MNESurfaceOrVolume::PointsT;
89using TrianglesT = MNESurfaceOrVolume::TrianglesT;
90
91//=========================================================================
92// Leaf I/O functions
93//=========================================================================
94
95int read_int3(QFile& in, int& ival)
96/*
97 * Read the strange 3-byte integer
98 */
99{
100 unsigned int s = 0;
101
102 if (in.read(reinterpret_cast<char*>(&s), 3) != 3) {
103 qCritical("read_int3 could not read data");
104 return FAIL;
105 }
106 s = (unsigned int)FIFFLIB::swap_int(s);
107 ival = ((s >> 8) & 0xffffff);
108 return OK;
109}
110
111int read_int(QFile& in, qint32& ival)
112/*
113 * Read a 32-bit integer
114 */
115{
116 qint32 s;
117 if (in.read(reinterpret_cast<char*>(&s), sizeof(qint32)) != static_cast<qint64>(sizeof(qint32))) {
118 qCritical("read_int could not read data");
119 return FAIL;
120 }
121 ival = FIFFLIB::swap_int(s);
122 return OK;
123}
124
125int read_int2(QFile& in, int& ival)
126/*
127 * Read int from short
128 */
129{
130 short s;
131 if (in.read(reinterpret_cast<char*>(&s), sizeof(short)) != static_cast<qint64>(sizeof(short))) {
132 qCritical("read_int2 could not read data");
133 return FAIL;
134 }
135 ival = FIFFLIB::swap_short(s);
136 return OK;
137}
138
139int read_float(QFile& in, float& fval)
140/*
141 * Read float
142 */
143{
144 float f;
145 if (in.read(reinterpret_cast<char*>(&f), sizeof(float)) != static_cast<qint64>(sizeof(float))) {
146 qCritical("read_float could not read data");
147 return FAIL;
148 }
149 fval = FIFFLIB::swap_float(f);
150 return OK;
151}
152
153int read_long(QFile& in, long long& lval)
154/*
155 * Read a 64-bit integer
156 */
157{
158 long long s;
159 if (in.read(reinterpret_cast<char*>(&s), sizeof(long long)) != static_cast<qint64>(sizeof(long long))) {
160 qCritical("read_long could not read data");
161 return FAIL;
162 }
163 lval = FIFFLIB::swap_long(s);
164 return OK;
165}
166
167//=========================================================================
168// Leaf validation / copy
169//=========================================================================
170
171int check_vertex(int no, int maxno)
172{
173 if (no < 0 || no > maxno - 1) {
174 qCritical("Illegal vertex number %d (max %d).", no, maxno);
175 return FAIL;
176 }
177 return OK;
178}
179
180// Deep-copy a volume geometry (kept for documentation / future use).
181[[maybe_unused]] static MNEVolGeom dup_vol_geom(const MNEVolGeom& g)
182{
183 MNEVolGeom dup;
184 dup = g;
185 dup.filename = g.filename;
186 return dup;
187}
188
189//=========================================================================
190// read_vol_geom
191//=========================================================================
192
193std::unique_ptr<MNEVolGeom> read_vol_geom(QFile& fp)
194/*
195 * This the volume geometry reading code from FreeSurfer
196 */
197{
198 int vgRead = 0;
199 int counter = 0;
200 qint64 pos = 0;
201
202 auto vg = std::make_unique<MNEVolGeom>();
203
204 /*
205 * Every line has the shape "<key> = <value>...". Read the values through
206 * QByteArray tokens rather than sscanf("%s"): the widths are unbounded
207 * there, so a long token in an untrusted file overflows the destination
208 * buffer.
209 */
210 const auto readFloats = [](const QList<QByteArray>& tok, float* dest, int n) {
211 for (int i = 0; i < n; ++i) {
212 if (tok.size() <= 2 + i)
213 return;
214 bool ok = false;
215 const float v = tok.at(2 + i).toFloat(&ok);
216 if (!ok)
217 return;
218 dest[i] = v;
219 }
220 };
221
222 while (!fp.atEnd() && counter < 8) {
223 const QByteArray lineData = fp.readLine(256);
224 if (lineData.isEmpty())
225 break;
226
227 const QList<QByteArray> tok = lineData.simplified().split(' ');
228 if (tok.isEmpty() || tok.first().isEmpty())
229 break;
230
231 const QByteArray& param = tok.first();
232 if (param == "valid") {
233 if (tok.size() > 2)
234 vg->valid = tok.at(2).toInt();
235 vgRead = 1;
236 counter++;
237 } else if (param == "filename") {
238 if (tok.size() > 2)
239 vg->filename = QString::fromUtf8(tok.at(2));
240 counter++;
241 } else if (param == "volume") {
242 if (tok.size() > 4) {
243 vg->width = tok.at(2).toInt();
244 vg->height = tok.at(3).toInt();
245 vg->depth = tok.at(4).toInt();
246 }
247 counter++;
248 } else if (param == "voxelsize") {
249 float size[3] = {vg->xsize, vg->ysize, vg->zsize};
250 readFloats(tok, size, 3);
251 /*
252 * We like these to be in meters
253 */
254 vg->xsize = size[0] / 1000.0f;
255 vg->ysize = size[1] / 1000.0f;
256 vg->zsize = size[2] / 1000.0f;
257 counter++;
258 } else if (param == "xras") {
259 readFloats(tok, vg->x_ras, 3);
260 counter++;
261 } else if (param == "yras") {
262 readFloats(tok, vg->y_ras, 3);
263 counter++;
264 } else if (param == "zras") {
265 readFloats(tok, vg->z_ras, 3);
266 counter++;
267 } else if (param == "cras") {
268 readFloats(tok, vg->c_ras, 3);
269 vg->c_ras[0] = vg->c_ras[0] / 1000.0f;
270 vg->c_ras[1] = vg->c_ras[1] / 1000.0f;
271 vg->c_ras[2] = vg->c_ras[2] / 1000.0f;
272 counter++;
273 }
274 /* remember the current position */
275 pos = fp.pos();
276 };
277 if (!fp.atEnd()) { /* we read one more line */
278 if (pos > 0) /* if success in getting pos, then */
279 fp.seek(pos); /* restore the position */
280 /* note that this won't allow compression using pipe */
281 }
282 if (!vgRead) {
283 vg = std::make_unique<MNEVolGeom>();
284 }
285 return vg;
286}
287
288//=========================================================================
289// read_tag_data
290//=========================================================================
291
292int read_tag_data(QFile& fp, int tag, long long nbytes, unsigned char*& val, long long& nbytesp)
293/*
294 * Read the data of one tag
295 */
296{
297 size_t snbytes = nbytes;
298
299 val = nullptr;
300 if (nbytes > 0) {
301 auto dum = std::make_unique<unsigned char[]>(nbytes + 1);
302 if (fp.read(reinterpret_cast<char*>(dum.get()), nbytes) != static_cast<qint64>(snbytes)) {
303 qCritical("Failed to read %d bytes of tag data", static_cast<int>(nbytes));
304 return FAIL;
305 }
306 dum[nbytes] = '\0'; /* Ensure null termination */
307 val = dum.release();
308 nbytesp = nbytes;
309 } else { /* Need to handle special cases */
310 if (tag == TAG_OLD_SURF_GEOM) {
311 auto g = read_vol_geom(fp);
312 if (!g)
313 return FAIL;
314 /*
315 * Serialize MNEVolGeom as POD fields followed by a
316 * null-terminated UTF-8 filename. We must NOT memcpy the
317 * entire MNEVolGeom because it contains a QString member
318 * whose internal pointers become dangling when *g is
319 * destroyed at the end of this block.
320 */
321 struct VolGeomPOD
322 {
323 int valid;
324 int width, height, depth;
325 float xsize, ysize, zsize;
326 float x_ras[3], y_ras[3], z_ras[3];
327 float c_ras[3];
328 };
329 QByteArray fn = g->filename.toUtf8();
330 size_t totalSize = sizeof(VolGeomPOD) + fn.size() + 1;
331 auto buf = std::make_unique<unsigned char[]>(totalSize);
332 VolGeomPOD pod;
333 pod.valid = g->valid;
334 pod.width = g->width;
335 pod.height = g->height;
336 pod.depth = g->depth;
337 pod.xsize = g->xsize;
338 pod.ysize = g->ysize;
339 pod.zsize = g->zsize;
340 std::memcpy(pod.x_ras, g->x_ras, 3 * sizeof(float));
341 std::memcpy(pod.y_ras, g->y_ras, 3 * sizeof(float));
342 std::memcpy(pod.z_ras, g->z_ras, 3 * sizeof(float));
343 std::memcpy(pod.c_ras, g->c_ras, 3 * sizeof(float));
344 std::memcpy(buf.get(), &pod, sizeof(VolGeomPOD));
345 std::memcpy(buf.get() + sizeof(VolGeomPOD), fn.constData(), fn.size() + 1);
346 val = buf.release();
347 nbytesp = static_cast<long long>(totalSize);
348 } else if (tag == TAG_OLD_USEREALRAS || tag == TAG_USEREALRAS) {
349 auto vi = std::make_unique<int[]>(1);
350 if (read_int(fp, vi[0]) == FAIL)
351 return FAIL;
352 val = reinterpret_cast<unsigned char*>(vi.release());
353 nbytesp = sizeof(int);
354 } else {
355 qWarning("Encountered an unknown tag with no length specification : %d\n", tag);
356 val = nullptr;
357 nbytesp = 0;
358 }
359 }
360 return OK;
361}
362
363//=========================================================================
364// add_mgh_tag_to_group
365//=========================================================================
366
367void add_mgh_tag_to_group(std::optional<MNEMghTagGroup>& g, int tag, long long len, unsigned char* data)
368{
369 if (!g)
370 g = MNEMghTagGroup();
371 auto new_tag = std::make_unique<MNEMghTag>();
372 new_tag->tag = tag;
373 new_tag->len = len;
374 new_tag->data = QByteArray(reinterpret_cast<const char*>(data), static_cast<int>(len));
375 delete[] data;
376 g->tags.push_back(std::move(new_tag));
377}
378
379//=========================================================================
380// read_next_tag
381//=========================================================================
382
383int read_next_tag(QFile& fp, int& tagp, long long& lenp, unsigned char*& datap)
384/*
385 * Read the next tag in the file
386 */
387{
388 int ilen = 0, tag = 0;
389 long long len;
390
391 if (read_int(fp, tag) == FAIL) {
392 tagp = 0;
393 return OK;
394 }
395 if (fp.atEnd()) {
396 tagp = 0;
397 return OK;
398 }
399 switch (tag) {
400 case TAG_OLD_MGH_XFORM: /* This is obviously a burden of the past */
401 if (read_int(fp, ilen) == FAIL)
402 return FAIL;
403 len = ilen - 1;
404 break;
408 len = 0;
409 break;
410 default:
411 if (read_long(fp, len) == FAIL)
412 return FAIL;
413 break;
414 }
415 lenp = len;
416 tagp = tag;
417 if (read_tag_data(fp, tag, len, datap, lenp) == FAIL)
418 return FAIL;
419 return OK;
420}
421
422//=========================================================================
423// read_mgh_tags
424//=========================================================================
425
426int read_mgh_tags(QFile& fp, std::optional<MNEMghTagGroup>& tagsp)
427/*
428 * Read all the tags from the file
429 */
430{
431 long long len;
432 int tag;
433 unsigned char* tag_data;
434
435 while (1) {
436 if (read_next_tag(fp, tag, len, tag_data) == FAIL)
437 return FAIL;
438 if (tag == 0)
439 break;
440 add_mgh_tag_to_group(tagsp, tag, len, tag_data);
441 }
442 return OK;
443}
444
445//=========================================================================
446// read_curvature_file
447//=========================================================================
448
449int read_curvature_file(const QString& fname,
450 Eigen::VectorXf& curv)
451
452{
453 QFile fp(fname);
454 int magic = 0;
455
456 float curvmin, curvmax;
457 int ncurv = 0;
458 int nface = 0, val_pervert = 0;
459 int val = 0, k;
460 float fval = 0.0f;
461
462 if (!fp.open(QIODevice::ReadOnly)) {
463 qCritical() << fname;
464 curv.resize(0);
465 return FAIL;
466 }
467 if (read_int3(fp, magic) != 0) {
468 qCritical() << "Bad magic in" << fname;
469 curv.resize(0);
470 return FAIL;
471 }
472 if (magic == CURVATURE_FILE_MAGIC_NUMBER) { /* A new-style curvature file */
473 /*
474 * How many and faces
475 */
476 if (read_int(fp, ncurv) != 0) {
477 curv.resize(0);
478 return FAIL;
479 }
480 if (read_int(fp, nface) != 0) {
481 curv.resize(0);
482 return FAIL;
483 }
484#ifdef DEBUG
485 qInfo("nvert = %d nface = %d\n", ncurv, nface);
486#endif
487 if (read_int(fp, val_pervert) != 0) {
488 curv.resize(0);
489 return FAIL;
490 }
491 if (val_pervert != 1) {
492 qCritical("Values per vertex not equal to one.");
493 curv.resize(0);
494 return FAIL;
495 }
496 /*
497 * Read the curvature values
498 */
499 curv.resize(ncurv);
500 curvmin = curvmax = 0.0;
501 for (k = 0; k < ncurv; k++) {
502 if (read_float(fp, fval) != 0) {
503 curv.resize(0);
504 return FAIL;
505 }
506 curv[k] = fval;
507 if (curv[k] > curvmax)
508 curvmax = curv[k];
509 if (curv[k] < curvmin)
510 curvmin = curv[k];
511 }
512 } else { /* An old-style curvature file */
513 ncurv = magic;
514 /*
515 * How many vertices
516 */
517 if (read_int3(fp, nface) != 0) {
518 curv.resize(0);
519 return FAIL;
520 }
521#ifdef DEBUG
522 qInfo("nvert = %d nface = %d\n", ncurv, nface);
523#endif
524 /*
525 * Read the curvature values
526 */
527 curv.resize(ncurv);
528 curvmin = curvmax = 0.0;
529 for (k = 0; k < ncurv; k++) {
530 if (read_int2(fp, val) != 0) {
531 curv.resize(0);
532 return FAIL;
533 }
534 curv[k] = static_cast<float>(val) / 100.0;
535 if (curv[k] > curvmax)
536 curvmax = curv[k];
537 if (curv[k] < curvmin)
538 curvmin = curv[k];
539 }
540 }
541#ifdef DEBUG
542 qInfo("Curvature range: %f...%f\n", curvmin, curvmax);
543#endif
544 return OK;
545}
546
547//=========================================================================
548// read_triangle_file
549//=========================================================================
550
551int read_triangle_file(const QString& fname,
552 PointsT& vertices,
553 TrianglesT& triangles,
554 std::optional<MNEMghTagGroup>* tagsp)
555/*
556 * Read the FS triangulated surface
557 */
558{
559 QFile fp(fname);
560 int magic = 0;
561 char c;
562
563 qint32 nvert = 0, ntri = 0, nquad = 0;
564 PointsT vert;
565 TrianglesT tri;
566 int k, p;
567 int quad[4];
568 int val = 0;
569 int which;
570
571 if (!fp.open(QIODevice::ReadOnly)) {
572 qCritical() << fname;
573 return FAIL;
574 }
575 if (read_int3(fp, magic) != 0) {
576 qCritical() << "Bad magic in" << fname;
577 return FAIL;
578 }
579 if (magic != TRIANGLE_FILE_MAGIC_NUMBER &&
580 magic != QUAD_FILE_MAGIC_NUMBER &&
582 qCritical() << "Bad magic in" << fname;
583 return FAIL;
584 }
585 if (magic == TRIANGLE_FILE_MAGIC_NUMBER) {
586 /*
587 * Get the comment
588 */
589 qInfo("Triangle file : ");
590 for (fp.getChar(&c); c != '\n'; fp.getChar(&c)) {
591 if (fp.atEnd()) {
592 qCritical() << "Bad triangle file.";
593 return FAIL;
594 }
595 putc(c, stderr);
596 }
597 fp.getChar(&c);
598 /*
599 * How many vertices and triangles?
600 */
601 if (read_int(fp, nvert) != 0)
602 return FAIL;
603 if (read_int(fp, ntri) != 0)
604 return FAIL;
605 qInfo(" nvert = %d ntri = %d\n", nvert, ntri);
606 vert.resize(nvert, 3);
607 tri.resize(ntri, 3);
608 /*
609 * Read the vertices
610 */
611 for (k = 0; k < nvert; k++) {
612 if (read_float(fp, vert(k, 0)) != 0)
613 return FAIL;
614 if (read_float(fp, vert(k, 1)) != 0)
615 return FAIL;
616 if (read_float(fp, vert(k, 2)) != 0)
617 return FAIL;
618 }
619 /*
620 * Read the triangles
621 */
622 for (k = 0; k < ntri; k++) {
623 if (read_int(fp, tri(k, 0)) != 0)
624 return FAIL;
625 if (check_vertex(tri(k, 0), nvert) != OK)
626 return FAIL;
627 if (read_int(fp, tri(k, 1)) != 0)
628 return FAIL;
629 if (check_vertex(tri(k, 1), nvert) != OK)
630 return FAIL;
631 if (read_int(fp, tri(k, 2)) != 0)
632 return FAIL;
633 if (check_vertex(tri(k, 2), nvert) != OK)
634 return FAIL;
635 }
636 } else if (magic == QUAD_FILE_MAGIC_NUMBER ||
638 if (read_int3(fp, nvert) != 0)
639 return FAIL;
640 if (read_int3(fp, nquad) != 0)
641 return FAIL;
642 qInfo("%s file : nvert = %d nquad = %d\n",
643 magic == QUAD_FILE_MAGIC_NUMBER ? "Quad" : "New quad",
644 nvert, nquad);
645 vert.resize(nvert, 3);
646 if (magic == QUAD_FILE_MAGIC_NUMBER) {
647 for (k = 0; k < nvert; k++) {
648 if (read_int2(fp, val) != 0)
649 return FAIL;
650 vert(k, 0) = val / 100.0;
651 if (read_int2(fp, val) != 0)
652 return FAIL;
653 vert(k, 1) = val / 100.0;
654 if (read_int2(fp, val) != 0)
655 return FAIL;
656 vert(k, 2) = val / 100.0;
657 }
658 } else { /* NEW_QUAD_FILE_MAGIC_NUMBER */
659 for (k = 0; k < nvert; k++) {
660 if (read_float(fp, vert(k, 0)) != 0)
661 return FAIL;
662 if (read_float(fp, vert(k, 1)) != 0)
663 return FAIL;
664 if (read_float(fp, vert(k, 2)) != 0)
665 return FAIL;
666 }
667 }
668 ntri = 2 * nquad;
669 tri.resize(ntri, 3);
670 for (k = 0, ntri = 0; k < nquad; k++) {
671 for (p = 0; p < 4; p++) {
672 if (read_int3(fp, quad[p]) != 0)
673 return FAIL;
674 }
675
676 /*
677 * The randomization is borrowed from FreeSurfer code
678 * Strange...
679 */
680#define EVEN(n) ((((n) / 2) * 2) == n)
681#ifdef FOO
682#define WHICH_FACE_SPLIT(vno0, vno1) \
683 (1 * nearbyint(sqrt(1.9 * vno0) + sqrt(3.5 * vno1)))
684
685 which = WHICH_FACE_SPLIT(quad[0], quad[1]);
686#endif
687 which = quad[0];
688 /*
689 qInfo("%f ",sqrt(1.9*quad[0]) + sqrt(3.5*quad[1]));
690 */
691
692 if (EVEN(which)) {
693 tri(ntri, 0) = quad[0];
694 tri(ntri, 1) = quad[1];
695 tri(ntri, 2) = quad[3];
696 ntri++;
697
698 tri(ntri, 0) = quad[2];
699 tri(ntri, 1) = quad[3];
700 tri(ntri, 2) = quad[1];
701 ntri++;
702 } else {
703 tri(ntri, 0) = quad[0];
704 tri(ntri, 1) = quad[1];
705 tri(ntri, 2) = quad[2];
706 ntri++;
707
708 tri(ntri, 0) = quad[0];
709 tri(ntri, 1) = quad[2];
710 tri(ntri, 2) = quad[3];
711 ntri++;
712 }
713 }
714 }
715 /*
716 * Optionally read the tags
717 */
718 if (tagsp) {
719 std::optional<MNEMghTagGroup> tags;
720 if (read_mgh_tags(fp, tags) == FAIL) {
721 return FAIL;
722 }
723 *tagsp = std::move(tags);
724 }
725 /*
726 * Convert mm to m and store as Eigen matrices
727 */
728 vert /= 1000.0f;
729 vertices = std::move(vert);
730 triangles = std::move(tri);
731 return OK;
732}
733
734//=========================================================================
735// get_volume_geom_from_tag
736//=========================================================================
737
738std::optional<MNEVolGeom> get_volume_geom_from_tag(const MNEMghTagGroup* tagsp)
739{
740 if (!tagsp)
741 return std::nullopt;
742
743 struct VolGeomPOD
744 {
745 int valid;
746 int width, height, depth;
747 float xsize, ysize, zsize;
748 float x_ras[3], y_ras[3], z_ras[3];
749 float c_ras[3];
750 };
751
752 for (const auto& t : tagsp->tags) {
753 if (t->tag == TAG_OLD_SURF_GEOM) {
754 if (t->len < static_cast<long long>(sizeof(VolGeomPOD)))
755 return std::nullopt;
756
757 const unsigned char* d = reinterpret_cast<const unsigned char*>(t->data.constData());
758 VolGeomPOD pod;
759 std::memcpy(&pod, d, sizeof(VolGeomPOD));
760
761 MNEVolGeom result;
762 result.valid = pod.valid;
763 result.width = pod.width;
764 result.height = pod.height;
765 result.depth = pod.depth;
766 result.xsize = pod.xsize;
767 result.ysize = pod.ysize;
768 result.zsize = pod.zsize;
769 std::memcpy(result.x_ras, pod.x_ras, 3 * sizeof(float));
770 std::memcpy(result.y_ras, pod.y_ras, 3 * sizeof(float));
771 std::memcpy(result.z_ras, pod.z_ras, 3 * sizeof(float));
772 std::memcpy(result.c_ras, pod.c_ras, 3 * sizeof(float));
773
774 if (t->len > static_cast<long long>(sizeof(VolGeomPOD)))
775 result.filename = QString::fromUtf8(
776 reinterpret_cast<const char*>(d + sizeof(VolGeomPOD)));
777
778 return result;
779 }
780 }
781 return std::nullopt;
782}
783
784} // anonymous namespace
785
786//=============================================================================================================
787// DEFINE MEMBER METHODS
788//=============================================================================================================
789
791{
792 this->np = np;
793 if (np > 0) {
794 rr = PointsT::Zero(np, 3);
795 nn = NormalsT::Zero(np, 3);
796 inuse = VectorXi::Zero(np);
797 vertno = VectorXi::Zero(np);
798 }
799 nuse = 0;
800 ntri = 0;
801 tot_area = 0.0;
802
803 nuse_tri = 0;
804
805 // tris, use_tris are std::vector<MNETriangle> — default-constructed empty
806
807 // neighbor_tri, nneighbor_tri, curv, val,
808 // neighbor_vert, nneighbor_vert, vert_dist
809 // are Eigen/std::vector types — default-constructed empty
810
813 subject = "";
815
816 // nearest is std::vector<MNENearest> — default-constructed empty
817 // patches is std::vector<optional<MNEPatchInfo>> — default-constructed empty
818
820 dist_limit = -1.0;
821
822 voxel_surf_RAS_t.reset();
823 vol_dims[0] = vol_dims[1] = vol_dims[2] = 0;
824
825 MRI_volume = "";
826 MRI_surf_RAS_RAS_t.reset();
827 MRI_voxel_surf_RAS_t.reset();
828 MRI_vol_dims[0] = MRI_vol_dims[1] = MRI_vol_dims[2] = 0;
829 interpolator.reset();
830
831 vol_geom.reset();
832 mgh_tags.reset();
833
834 cm[0] = cm[1] = cm[2] = 0.0;
835}
836
837//=============================================================================================================
838
842
843//=============================================================================================================
844
846{
847 // Base class clone — creates a MNESourceSpace with the same fields.
848 // Derived classes (e.g., MNEHemisphere) override this to preserve their type.
849 auto copy = std::make_shared<MNESourceSpace>(this->np);
850 copy->type = this->type;
851 copy->id = this->id;
852 copy->np = this->np;
853 copy->ntri = this->ntri;
854 copy->coord_frame = this->coord_frame;
855 copy->rr = this->rr;
856 copy->nn = this->nn;
857 copy->nuse = this->nuse;
858 copy->inuse = this->inuse;
859 copy->vertno = this->vertno;
860 copy->itris = this->itris;
861 copy->use_itris = this->use_itris;
862 copy->nuse_tri = this->nuse_tri;
863 copy->dist_limit = this->dist_limit;
864 copy->dist = this->dist;
865 copy->nearest = this->nearest;
866 copy->neighbor_tri = this->neighbor_tri;
867 copy->neighbor_vert = this->neighbor_vert;
868 return copy;
869}
870
871//=============================================================================================================
872
874{
875 int k;
876 for (k = 0; k < np; k++)
877 inuse[k] = 1;
878 nuse = np;
879 return;
880}
881
882//=============================================================================================================
883
885/*
886 * Left or right hemisphere?
887 */
888{
889 int k;
890 float xave;
891
892 for (k = 0, xave = 0.0; k < np; k++)
893 xave += rr(k, 0);
894 if (xave < 0.0)
895 return true;
896 else
897 return false;
898}
899
900//=============================================================================================================
901
903{
904 double xave = rr.col(0).sum();
905 if (xave < 0)
907 else
909}
910
911//=============================================================================================================
912
913void MNESourceSpace::update_inuse(Eigen::VectorXi new_inuse)
914/*
915 * Update the active vertices
916 */
917{
918 int k, p, nuse_count;
919
920 inuse = std::move(new_inuse);
921
922 for (k = 0, nuse_count = 0; k < np; k++)
923 if (inuse[k])
924 nuse_count++;
925
926 nuse = nuse_count;
927 if (nuse > 0) {
928 vertno.conservativeResize(nuse);
929 for (k = 0, p = 0; k < np; k++)
930 if (inuse[k])
931 vertno[p++] = k;
932 } else {
933 vertno.resize(0);
934 }
935 return;
936}
937
938//=============================================================================================================
939
941/*
942 * Transform source space data into another coordinate frame
943 */
944{
945 int k;
946 if (coord_frame == t.to)
947 return OK;
948 if (coord_frame != t.from) {
949 qCritical("Coordinate transformation does not match with the source space coordinate system.");
950 return FAIL;
951 }
952 for (k = 0; k < np; k++) {
955 }
956 if (!tris.empty()) {
957 for (k = 0; k < ntri; k++)
959 }
960 coord_frame = t.to;
961 return OK;
962}
963
964//=============================================================================================================
965
967{
968 MNENearest* nearest_data = nearest.data();
969 MNENearest* this_patch;
970 std::vector<std::optional<MNEPatchInfo>> pinfo(nuse);
971 int nave, p, q, k;
972
973 qInfo("Computing patch statistics...\n");
974 if (neighbor_tri.empty())
975 if (add_geometry_info(false) != OK)
976 return FAIL;
977
978 if (nearest.empty()) {
979 qCritical("The patch information is not available.");
980 return FAIL;
981 }
982 if (nuse == 0) {
983 patches.clear();
984 return OK;
985 }
986 /*
987 * Calculate the average normals and the patch areas
988 */
989 qInfo("\tareas, average normals, and mean deviations...");
990 std::sort(nearest.begin(), nearest.end(),
991 [](const MNENearest& a, const MNENearest& b) { return a.nearest < b.nearest; });
992 nearest_data = nearest.data(); // refresh after sort
993 nave = 1;
994 for (p = 1, q = 0; p < np; p++) {
995 if (nearest_data[p].nearest != nearest_data[p - 1].nearest) {
996 if (nave == 0) {
997 qCritical("No vertices belong to the patch of vertex %d", nearest_data[p - 1].nearest);
998 return FAIL;
999 }
1000 if (q < nuse && vertno[q] == nearest_data[p - 1].nearest) { /* Some source space points may have been omitted since
1001 * the patch information was computed */
1002 pinfo[q] = MNEPatchInfo();
1003 pinfo[q]->vert = nearest_data[p - 1].nearest;
1004 this_patch = nearest_data + p - nave;
1005 pinfo[q]->memb_vert.resize(nave);
1006 for (k = 0; k < nave; k++) {
1007 pinfo[q]->memb_vert[k] = this_patch[k].vert;
1008 this_patch[k].patch = &(*pinfo[q]);
1009 }
1010 pinfo[q]->calculate_area(this);
1011 pinfo[q]->calculate_normal_stats(this);
1012 q++;
1013 }
1014 nave = 0;
1015 }
1016 nave++;
1017 }
1018 if (nave == 0) {
1019 qCritical("No vertices belong to the patch of vertex %d", nearest_data[p - 1].nearest);
1020 return FAIL;
1021 }
1022 if (q < nuse && vertno[q] == nearest_data[p - 1].nearest) {
1023 pinfo[q] = MNEPatchInfo();
1024 pinfo[q]->vert = nearest_data[p - 1].nearest;
1025 this_patch = nearest_data + p - nave;
1026 pinfo[q]->memb_vert.resize(nave);
1027 for (k = 0; k < nave; k++) {
1028 pinfo[q]->memb_vert[k] = this_patch[k].vert;
1029 this_patch[k].patch = &(*pinfo[q]);
1030 }
1031 pinfo[q]->calculate_area(this);
1032 pinfo[q]->calculate_normal_stats(this);
1033 q++;
1034 }
1035 qInfo(" %d/%d [done]\n", q, nuse);
1036
1037 patches = std::move(pinfo);
1038
1039 return OK;
1040}
1041
1042//=============================================================================================================
1043
1045{
1046 int k, p;
1047
1048 for (k = 0, nuse = 0; k < np; k++)
1049 if (inuse[k])
1050 nuse++;
1051
1052 if (nuse == 0) {
1053 vertno.resize(0);
1054 } else {
1055 vertno.conservativeResize(nuse);
1056 for (k = 0, p = 0; k < np; k++)
1057 if (inuse[k])
1058 vertno[p++] = k;
1059 }
1060 if (!nearest.empty())
1062 return;
1063}
1064
1065//=============================================================================================================
1066
1067std::unique_ptr<MNESourceSpace> MNESourceSpace::create_source_space(int np)
1068/*
1069 * Create a new source space and all associated data
1070 */
1071{
1072 auto res = std::make_unique<MNESourceSpace>();
1073 res->np = np;
1074 if (np > 0) {
1075 res->rr = PointsT::Zero(np, 3);
1076 res->nn = NormalsT::Zero(np, 3);
1077 res->inuse = VectorXi::Zero(np);
1078 res->vertno = VectorXi::Zero(np);
1079 }
1080 res->nuse = 0;
1081 res->ntri = 0;
1082 res->tot_area = 0.0;
1083
1084 res->nuse_tri = 0;
1085
1086 res->sigma = -1.0;
1087 res->coord_frame = FIFFV_COORD_MRI;
1088 res->id = FIFFV_MNE_SURF_UNKNOWN;
1089 res->subject.clear();
1090 res->type = FIFFV_MNE_SPACE_SURFACE;
1091
1092 res->dist = FIFFLIB::FiffSparseMatrix();
1093 res->dist_limit = -1.0;
1094
1095 res->voxel_surf_RAS_t.reset();
1096 res->vol_dims[0] = res->vol_dims[1] = res->vol_dims[2] = 0;
1097
1098 res->MRI_volume.clear();
1099 res->MRI_surf_RAS_RAS_t.reset();
1100 res->MRI_voxel_surf_RAS_t.reset();
1101 res->MRI_vol_dims[0] = res->MRI_vol_dims[1] = res->MRI_vol_dims[2] = 0;
1102 res->interpolator.reset();
1103
1104 res->vol_geom.reset();
1105 res->mgh_tags.reset();
1106
1107 res->cm[0] = res->cm[1] = res->cm[2] = 0.0;
1108
1109 return res;
1110}
1111
1112//=============================================================================================================
1113
1114std::unique_ptr<MNESourceSpace> MNESourceSpace::load_surface(const QString& surf_file,
1115 const QString& curv_file)
1116{
1117 return load_surface_geom(surf_file, curv_file, true, true);
1118}
1119
1120//=============================================================================================================
1121
1122std::unique_ptr<MNESourceSpace> MNESourceSpace::load_surface_geom(const QString& surf_file,
1123 const QString& curv_file,
1124 bool add_geometry,
1125 bool check_too_many_neighbors)
1126/*
1127 * Load the surface and add the geometry information
1128 */
1129{
1130 std::unique_ptr<MNESourceSpace> s;
1131 std::optional<MNEMghTagGroup> tags;
1132 Eigen::VectorXf curvs;
1133 PointsT verts;
1135
1136 if (read_triangle_file(surf_file,
1137 verts,
1138 tris,
1139 &tags) == -1)
1140 return nullptr;
1141
1142 if (!curv_file.isEmpty()) {
1143 if (read_curvature_file(curv_file, curvs) == -1)
1144 return nullptr;
1145 if (curvs.size() != verts.rows()) {
1146 qCritical() << "Incorrect number of vertices in the curvature file.";
1147 return nullptr;
1148 }
1149 }
1150
1151 s = std::make_unique<MNESourceSpace>(0);
1152 s->rr = std::move(verts);
1153 s->itris = std::move(tris);
1154 s->ntri = s->itris.rows();
1155 s->np = s->rr.rows();
1156 if (curvs.size() > 0) {
1157 s->curv = std::move(curvs);
1158 }
1159 s->val = Eigen::VectorXf::Zero(s->np);
1160 if (add_geometry) {
1161 if (check_too_many_neighbors) {
1162 if (s->add_geometry_info(true) != OK)
1163 return nullptr;
1164 } else {
1165 if (s->add_geometry_info2(true) != OK)
1166 return nullptr;
1167 }
1168 } else if (s->nn.rows() == 0) { /* Normals only */
1169 if (s->add_vertex_normals() != OK)
1170 return nullptr;
1171 } else
1172 s->add_triangle_data();
1173 s->nuse = s->np;
1174 s->inuse = Eigen::VectorXi::Ones(s->np);
1175 s->vertno = Eigen::VectorXi::LinSpaced(s->np, 0, s->np - 1);
1176 s->mgh_tags = std::move(tags);
1177 s->vol_geom = get_volume_geom_from_tag(s->mgh_tags ? &(*s->mgh_tags) : nullptr);
1178
1179 return s;
1180}
1181
1182//=============================================================================================================
1183
1184static std::optional<FiffCoordTrans> make_voxel_ras_trans(const Eigen::Vector3f& r0,
1185 const Eigen::Vector3f& x_ras,
1186 const Eigen::Vector3f& y_ras,
1187 const Eigen::Vector3f& z_ras,
1188 const Eigen::Vector3f& voxel_size)
1189{
1190 Eigen::Matrix3f rot;
1191 rot.row(0) = x_ras.transpose() * voxel_size[0];
1192 rot.row(1) = y_ras.transpose() * voxel_size[1];
1193 rot.row(2) = z_ras.transpose() * voxel_size[2];
1194
1196}
1197
1198MNESourceSpace* MNESourceSpace::make_volume_source_space(const MNESurface& surf, float grid, float exclude, float mindist)
1199/*
1200 * Make a source space which covers the volume bounded by surf
1201 */
1202{
1203 Eigen::Vector3f minV, maxV, cm;
1204 int minn[3], maxn[3];
1205 float maxdist, dist;
1206 int k, c;
1207 std::unique_ptr<MNESourceSpace> sp;
1208 int np, nplane, nrow;
1209 int nneigh;
1210 int x, y, z;
1211 /*
1212 * Figure out the grid size
1213 */
1214 cm.setZero();
1215 minV = maxV = surf.rr.row(0).transpose();
1216
1217 for (k = 0; k < surf.np; k++) {
1218 Eigen::Vector3f node = surf.rr.row(k).transpose();
1219 cm += node;
1220 minV = minV.cwiseMin(node);
1221 maxV = maxV.cwiseMax(node);
1222 }
1223 cm /= static_cast<float>(surf.np);
1224 /*
1225 * Define the sphere which fits the surface
1226 */
1227 maxdist = 0.0;
1228 for (k = 0; k < surf.np; k++) {
1229 dist = (surf.rr.row(k).transpose() - cm).norm();
1230 if (dist > maxdist)
1231 maxdist = dist;
1232 }
1233 qInfo("FsSurface CM = (%6.1f %6.1f %6.1f) mm\n",
1234 1000 * cm[X], 1000 * cm[Y], 1000 * cm[Z]);
1235 qInfo("FsSurface fits inside a sphere with radius %6.1f mm\n", 1000 * maxdist);
1236 qInfo("FsSurface extent:\n"
1237 "\tx = %6.1f ... %6.1f mm\n"
1238 "\ty = %6.1f ... %6.1f mm\n"
1239 "\tz = %6.1f ... %6.1f mm\n",
1240 1000 * minV[X], 1000 * maxV[X],
1241 1000 * minV[Y], 1000 * maxV[Y],
1242 1000 * minV[Z], 1000 * maxV[Z]);
1243 for (c = 0; c < 3; c++) {
1244 if (maxV[c] > 0)
1245 maxn[c] = floor(std::fabs(maxV[c]) / grid) + 1;
1246 else
1247 maxn[c] = -floor(std::fabs(maxV[c]) / grid) - 1;
1248 if (minV[c] > 0)
1249 minn[c] = floor(std::fabs(minV[c]) / grid) + 1;
1250 else
1251 minn[c] = -floor(std::fabs(minV[c]) / grid) - 1;
1252 }
1253 qInfo("Grid extent:\n"
1254 "\tx = %6.1f ... %6.1f mm\n"
1255 "\ty = %6.1f ... %6.1f mm\n"
1256 "\tz = %6.1f ... %6.1f mm\n",
1257 1000 * (minn[0] * grid), 1000 * (maxn[0] * grid),
1258 1000 * (minn[1] * grid), 1000 * (maxn[1] * grid),
1259 1000 * (minn[2] * grid), 1000 * (maxn[2] * grid));
1260 /*
1261 * Now make the initial grid
1262 */
1263 np = 1;
1264 for (c = 0; c < 3; c++)
1265 np = np * (maxn[c] - minn[c] + 1);
1266 nplane = (maxn[0] - minn[0] + 1) * (maxn[1] - minn[1] + 1);
1267 nrow = (maxn[0] - minn[0] + 1);
1269 sp->type = MNE_SOURCE_SPACE_VOLUME;
1270 sp->nneighbor_vert = Eigen::VectorXi::Constant(sp->np, NNEIGHBORS);
1271 sp->neighbor_vert.resize(sp->np);
1272 for (k = 0; k < sp->np; k++) {
1273 sp->inuse[k] = 1;
1274 sp->vertno[k] = k;
1275 sp->nn(k, 0) = sp->nn(k, 1) = 0.0; /* Source orientation is immaterial */
1276 sp->nn(k, 2) = 1.0;
1277 sp->neighbor_vert[k] = Eigen::VectorXi::Constant(NNEIGHBORS, -1);
1278 sp->nuse++;
1279 }
1280 for (k = 0, z = minn[2]; z <= maxn[2]; z++) {
1281 for (y = minn[1]; y <= maxn[1]; y++) {
1282 for (x = minn[0]; x <= maxn[0]; x++, k++) {
1283 sp->rr(k, 0) = x * grid;
1284 sp->rr(k, 1) = y * grid;
1285 sp->rr(k, 2) = z * grid;
1286 /*
1287 * Figure out the neighborhood:
1288 * 6-neighborhood first
1289 */
1290 Eigen::VectorXi& neigh = sp->neighbor_vert[k];
1291 if (z > minn[2])
1292 neigh[0] = k - nplane;
1293 if (x < maxn[0])
1294 neigh[1] = k + 1;
1295 if (y < maxn[1])
1296 neigh[2] = k + nrow;
1297 if (x > minn[0])
1298 neigh[3] = k - 1;
1299 if (y > minn[1])
1300 neigh[4] = k - nrow;
1301 if (z < maxn[2])
1302 neigh[5] = k + nplane;
1303 /*
1304 * Then the rest to complete the 26-neighborhood
1305 * First the plane below
1306 */
1307 if (z > minn[2]) {
1308 if (x < maxn[0]) {
1309 neigh[6] = k + 1 - nplane;
1310 if (y < maxn[1])
1311 neigh[7] = k + 1 + nrow - nplane;
1312 }
1313 if (y < maxn[1])
1314 neigh[8] = k + nrow - nplane;
1315 if (x > minn[0]) {
1316 if (y < maxn[1])
1317 neigh[9] = k - 1 + nrow - nplane;
1318 neigh[10] = k - 1 - nplane;
1319 if (y > minn[1])
1320 neigh[11] = k - 1 - nrow - nplane;
1321 }
1322 if (y > minn[1]) {
1323 neigh[12] = k - nrow - nplane;
1324 if (x < maxn[0])
1325 neigh[13] = k + 1 - nrow - nplane;
1326 }
1327 }
1328 /*
1329 * Then the same plane
1330 */
1331 if (x < maxn[0] && y < maxn[1])
1332 neigh[14] = k + 1 + nrow;
1333 if (x > minn[0]) {
1334 if (y < maxn[1])
1335 neigh[15] = k - 1 + nrow;
1336 if (y > minn[1])
1337 neigh[16] = k - 1 - nrow;
1338 }
1339 // MNE-C (and mne-python) repeat neighbour 13 here (k + 1 - nrow - nplane).
1340 if (y > minn[1] && x < maxn[0])
1341 neigh[17] = k + 1 - nrow;
1342 /*
1343 * Finally one plane above
1344 */
1345 if (z < maxn[2]) {
1346 if (x < maxn[0]) {
1347 neigh[18] = k + 1 + nplane;
1348 if (y < maxn[1])
1349 neigh[19] = k + 1 + nrow + nplane;
1350 }
1351 if (y < maxn[1])
1352 neigh[20] = k + nrow + nplane;
1353 if (x > minn[0]) {
1354 if (y < maxn[1])
1355 neigh[21] = k - 1 + nrow + nplane;
1356 neigh[22] = k - 1 + nplane;
1357 if (y > minn[1])
1358 neigh[23] = k - 1 - nrow + nplane;
1359 }
1360 if (y > minn[1]) {
1361 neigh[24] = k - nrow + nplane;
1362 if (x < maxn[0])
1363 neigh[25] = k + 1 - nrow + nplane;
1364 }
1365 }
1366 }
1367 }
1368 }
1369 qInfo("%d sources before omitting any.\n", sp->nuse);
1370 /*
1371 * Exclude infeasible points
1372 */
1373 for (k = 0; k < sp->np; k++) {
1374 dist = (sp->rr.row(k).transpose() - cm).norm();
1375 if (dist < exclude || dist > maxdist) {
1376 sp->inuse[k] = 0;
1377 sp->nuse--;
1378 }
1379 }
1380 qInfo("%d sources after omitting infeasible sources.\n", sp->nuse);
1381 {
1382 std::vector<std::unique_ptr<MNESourceSpace>> sp_vec;
1383 sp_vec.push_back(std::move(sp));
1384 if (filter_source_spaces(surf, mindist, FiffCoordTrans(), sp_vec, nullptr) != OK) {
1385 return nullptr;
1386 }
1387 sp = std::move(sp_vec[0]);
1388 }
1389 qInfo("%d sources remaining after excluding the sources outside the surface and less than %6.1f mm inside.\n", sp->nuse, 1000 * mindist);
1390 // vertno must list only the used points (MNE-C leaves it covering the full grid).
1391 {
1392 Eigen::VectorXi used(sp->nuse);
1393 for (k = 0, c = 0; k < sp->np; k++)
1394 if (sp->inuse[k])
1395 used[c++] = k;
1396 sp->vertno = used;
1397 }
1398 /*
1399 * Omit unused vertices from the neighborhoods
1400 */
1401 qInfo("Adjusting the neighborhood info...");
1402 for (k = 0; k < sp->np; k++) {
1403 Eigen::VectorXi& neigh = sp->neighbor_vert[k];
1404 nneigh = sp->nneighbor_vert[k];
1405 if (sp->inuse[k]) {
1406 for (c = 0; c < nneigh; c++)
1407 if (!sp->inuse[neigh[c]])
1408 neigh[c] = -1;
1409 } else {
1410 for (c = 0; c < nneigh; c++)
1411 neigh[c] = -1;
1412 }
1413 }
1414 qInfo("[done]\n");
1415 /*
1416 * Set up the volume data (needed for creating the interpolation matrix)
1417 */
1418 {
1419 Eigen::Vector3f r0(minn[0] * grid, minn[1] * grid, minn[2] * grid);
1420 Eigen::Vector3f voxel_size(grid, grid, grid);
1421 Eigen::Vector3f x_ras = Eigen::Vector3f::UnitX();
1422 Eigen::Vector3f y_ras = Eigen::Vector3f::UnitY();
1423 Eigen::Vector3f z_ras = Eigen::Vector3f::UnitZ();
1424 int width = (maxn[0] - minn[0] + 1);
1425 int height = (maxn[1] - minn[1] + 1);
1426 int depth = (maxn[2] - minn[2] + 1);
1427
1428 sp->voxel_surf_RAS_t = make_voxel_ras_trans(r0, x_ras, y_ras, z_ras, voxel_size);
1429 if (!sp->voxel_surf_RAS_t || sp->voxel_surf_RAS_t->isEmpty())
1430 return nullptr;
1431
1432 sp->vol_dims[0] = width;
1433 sp->vol_dims[1] = height;
1434 sp->vol_dims[2] = depth;
1435 Eigen::Map<Eigen::Vector3f>(sp->voxel_size) = voxel_size;
1436 }
1437
1438 return sp.release();
1439}
1440
1441//=============================================================================================================
1442
1443int MNESourceSpace::filter_source_spaces(const MNESurface& surf, float limit, const FiffCoordTrans& mri_head_t, std::vector<std::unique_ptr<MNESourceSpace>>& spaces, QTextStream* filtered) /* Provide a list of filtered points here */
1444/*
1445 * Remove all source space points closer to the surface than a given limit
1446 */
1447{
1448 MNESourceSpace* s;
1449 int k, p1, p2;
1450 Eigen::Vector3f r1;
1451 float mindist, dist;
1452 int minnode;
1453 int omit, omit_outside;
1454 double tot_angle;
1455 int nspace = static_cast<int>(spaces.size());
1456
1457 if (spaces[0]->coord_frame == FIFFV_COORD_HEAD && mri_head_t.isEmpty()) {
1458 qCritical("Source spaces are in head coordinates and no coordinate transform was provided!");
1459 return FAIL;
1460 }
1461 /*
1462 * How close are the source points to the surface?
1463 */
1464 qInfo("Source spaces are in ");
1465 if (spaces[0]->coord_frame == FIFFV_COORD_HEAD)
1466 qInfo("head coordinates.\n");
1467 else if (spaces[0]->coord_frame == FIFFV_COORD_MRI)
1468 qInfo("MRI coordinates.\n");
1469 else
1470 qWarning("unknown (%d) coordinates.\n", spaces[0]->coord_frame);
1471 qInfo("Checking that the sources are inside the bounding surface ");
1472 if (limit > 0.0)
1473 qInfo("and at least %6.1f mm away", 1000 * limit);
1474 qInfo(" (will take a few...)\n");
1475 omit = 0;
1476 omit_outside = 0;
1477 for (k = 0; k < nspace; k++) {
1478 s = spaces[k].get();
1479 for (p1 = 0; p1 < s->np; p1++)
1480 if (s->inuse[p1]) {
1481 r1 = s->rr.row(p1).transpose(); /* Transform the point to MRI coordinates */
1482 if (s->coord_frame == FIFFV_COORD_HEAD)
1483 FiffCoordTrans::apply_inverse_trans(r1.data(), mri_head_t, FIFFV_MOVE);
1484 /*
1485 * Check that the source is inside the inner skull surface
1486 */
1487 tot_angle = surf.sum_solids(r1) / (4 * M_PI);
1488 if (std::fabs(tot_angle - 1.0) > 1e-5) {
1489 omit_outside++;
1490 s->inuse[p1] = 0;
1491 s->nuse--;
1492 if (filtered)
1493 *filtered << qSetFieldWidth(10) << qSetRealNumberPrecision(3) << Qt::fixed
1494 << 1000 * r1[X] << " " << 1000 * r1[Y] << " " << 1000 * r1[Z] << "\n"
1495 << qSetFieldWidth(0);
1496 } else if (limit > 0.0) {
1497 /*
1498 * Check the distance limit
1499 */
1500 mindist = 1.0;
1501 minnode = 0;
1502 for (p2 = 0; p2 < surf.np; p2++) {
1503 dist = (surf.rr.row(p2).transpose() - r1).norm();
1504 if (dist < mindist) {
1505 mindist = dist;
1506 minnode = p2;
1507 }
1508 }
1509 if (mindist < limit) {
1510 omit++;
1511 s->inuse[p1] = 0;
1512 s->nuse--;
1513 if (filtered)
1514 *filtered << qSetFieldWidth(10) << qSetRealNumberPrecision(3) << Qt::fixed
1515 << 1000 * r1[X] << " " << 1000 * r1[Y] << " " << 1000 * r1[Z] << "\n"
1516 << qSetFieldWidth(0);
1517 }
1518 }
1519 }
1520 }
1521 (void)minnode; // squash compiler warning, this is unused
1522 if (omit_outside > 0)
1523 qInfo("%d source space points omitted because they are outside the inner skull surface.\n",
1524 omit_outside);
1525 if (omit > 0)
1526 qInfo("%d source space points omitted because of the %6.1f-mm distance limit.\n",
1527 omit, 1000 * limit);
1528 qInfo("Thank you for waiting.\n");
1529 return OK;
1530}
1531
1532//=============================================================================================================
1533
1535{
1536 FilterThreadArg* a = arg;
1537 int p1, p2;
1538 double tot_angle;
1539 int omit, omit_outside;
1540 Eigen::Vector3f r1;
1541 float mindist, dist;
1542 int minnode;
1543
1544 QSharedPointer<MNESurface> surf = a->surf.toStrongRef();
1545 if (!surf) {
1546 a->stat = FAIL;
1547 return;
1548 }
1549
1550 omit = 0;
1551 omit_outside = 0;
1552
1553 for (p1 = 0; p1 < a->s->np; p1++) {
1554 if (a->s->inuse[p1]) {
1555 r1 = a->s->rr.row(p1).transpose(); /* Transform the point to MRI coordinates */
1556 if (a->s->coord_frame == FIFFV_COORD_HEAD) {
1557 Q_ASSERT(a->mri_head_t);
1559 }
1560 /*
1561 * Check that the source is inside the inner skull surface
1562 */
1563 tot_angle = surf->sum_solids(r1) / (4 * M_PI);
1564 if (std::fabs(tot_angle - 1.0) > 1e-5) {
1565 omit_outside++;
1566 a->s->inuse[p1] = 0;
1567 a->s->nuse--;
1568 if (a->filtered)
1569 *a->filtered << qSetFieldWidth(10) << qSetRealNumberPrecision(3) << Qt::fixed
1570 << 1000 * r1[X] << " " << 1000 * r1[Y] << " " << 1000 * r1[Z] << "\n"
1571 << qSetFieldWidth(0);
1572 } else if (a->limit > 0.0) {
1573 /*
1574 * Check the distance limit
1575 */
1576 mindist = 1.0;
1577 minnode = 0;
1578 for (p2 = 0; p2 < surf->np; p2++) {
1579 dist = (surf->rr.row(p2).transpose() - r1).norm();
1580 if (dist < mindist) {
1581 mindist = dist;
1582 minnode = p2;
1583 }
1584 }
1585 if (mindist < a->limit) {
1586 omit++;
1587 a->s->inuse[p1] = 0;
1588 a->s->nuse--;
1589 if (a->filtered)
1590 *a->filtered << qSetFieldWidth(10) << qSetRealNumberPrecision(3) << Qt::fixed
1591 << 1000 * r1[X] << " " << 1000 * r1[Y] << " " << 1000 * r1[Z] << "\n"
1592 << qSetFieldWidth(0);
1593 }
1594 }
1595 }
1596 }
1597 (void)minnode; // squash compiler warning, set but unused
1598 if (omit_outside > 0)
1599 qInfo("%d source space points omitted because they are outside the inner skull surface.\n",
1600 omit_outside);
1601 if (omit > 0)
1602 qInfo("%d source space points omitted because of the %6.1f-mm distance limit.\n",
1603 omit, 1000 * a->limit);
1604 a->stat = OK;
1605 return;
1606}
1607
1608//=============================================================================================================
1609
1610int MNESourceSpace::filter_source_spaces(float limit, const QString& bemfile, const FiffCoordTrans& mri_head_t, std::vector<std::unique_ptr<MNESourceSpace>>& spaces, QTextStream* filtered, bool use_threads)
1611/*
1612 * Remove all source space points closer to the surface than a given limit
1613 */
1614{
1615 QSharedPointer<MNESurface> surf;
1616 int k;
1617 int nproc = QThread::idealThreadCount();
1618 int nspace = static_cast<int>(spaces.size());
1619
1620 if (bemfile.isEmpty())
1621 return OK;
1622
1623 {
1624 auto rawSurf = MNESurface::read_bem_surface(bemfile, FIFFV_BEM_SURF_ID_BRAIN, false);
1625 if (!rawSurf) {
1626 qCritical("BEM model does not have the inner skull triangulation!");
1627 return FAIL;
1628 }
1629 surf.reset(rawSurf.release());
1630 }
1631 /*
1632 * How close are the source points to the surface?
1633 */
1634 qInfo("Source spaces are in ");
1635 if (spaces[0]->coord_frame == FIFFV_COORD_HEAD)
1636 qInfo("head coordinates.\n");
1637 else if (spaces[0]->coord_frame == FIFFV_COORD_MRI)
1638 qInfo("MRI coordinates.\n");
1639 else
1640 qWarning("unknown (%d) coordinates.\n", spaces[0]->coord_frame);
1641 qInfo("Checking that the sources are inside the inner skull ");
1642 if (limit > 0.0)
1643 qInfo("and at least %6.1f mm away", 1000 * limit);
1644 qInfo(" (will take a few...)\n");
1645 if (nproc < 2 || nspace == 1 || !use_threads) {
1646 /*
1647 * This is the conventional calculation
1648 */
1649 for (k = 0; k < nspace; k++) {
1650 auto a_ptr = std::make_unique<FilterThreadArg>();
1651 a_ptr->s = spaces[k].get();
1652 a_ptr->mri_head_t = std::make_unique<FiffCoordTrans>(mri_head_t);
1653 a_ptr->surf = surf;
1654 a_ptr->limit = limit;
1655 a_ptr->filtered = filtered;
1656 filter_source_space(a_ptr.get());
1657 spaces[k]->rearrange_source_space();
1658 }
1659 } else {
1660 /*
1661 * Calculate all (both) source spaces simultaneously
1662 */
1663 QList<FilterThreadArg*> args;
1664
1665 std::vector<std::unique_ptr<FilterThreadArg>> arg_owners;
1666 for (k = 0; k < nspace; k++) {
1667 auto a_ptr = std::make_unique<FilterThreadArg>();
1668 a_ptr->s = spaces[k].get();
1669 a_ptr->mri_head_t = std::make_unique<FiffCoordTrans>(mri_head_t);
1670 a_ptr->surf = surf;
1671 a_ptr->limit = limit;
1672 a_ptr->filtered = filtered;
1673 args.append(a_ptr.get());
1674 arg_owners.push_back(std::move(a_ptr));
1675 }
1676 /*
1677 * Ready to start the threads & Wait for them to complete
1678 */
1679 QtConcurrent::blockingMap(args, filter_source_space);
1680
1681 for (k = 0; k < nspace; k++) {
1682 spaces[k]->rearrange_source_space();
1683 }
1684 }
1685 qInfo("Thank you for waiting.\n\n");
1686
1687 return OK;
1688}
1689
1690//=============================================================================================================
1691
1692int MNESourceSpace::read_source_spaces(const QString& name, std::vector<std::unique_ptr<MNESourceSpace>>& spaces)
1693/*
1694 * Read source spaces from a FIFF file
1695 */
1696{
1697 QFile file(name);
1698 FiffStream::SPtr stream(new FiffStream(&file));
1699
1700 std::vector<std::unique_ptr<MNESourceSpace>> local_spaces;
1701 std::unique_ptr<MNESourceSpace> new_space;
1702 QList<FiffDirNode::SPtr> sources;
1703 FiffDirNode::SPtr node;
1704 FiffTag::UPtr t_pTag;
1705 int j, k, p, q;
1706 int ntri;
1707
1708 if (!stream->open()) {
1709 stream->close();
1710 return FIFF_FAIL;
1711 }
1712
1713 sources = stream->dirtree()->dir_tree_find(FIFFB_MNE_SOURCE_SPACE);
1714 if (sources.size() == 0) {
1715 qCritical("No source spaces available here");
1716 stream->close();
1717 return FIFF_FAIL;
1718 }
1719 for (j = 0; j < sources.size(); j++) {
1721 node = sources[j];
1722 /*
1723 * Get the mandatory data first
1724 */
1725 if (!node->find_tag(stream, FIFF_MNE_SOURCE_SPACE_NPOINTS, t_pTag)) {
1726 stream->close();
1727 return FIFF_FAIL;
1728 }
1729 new_space->np = *t_pTag->toInt();
1730 if (new_space->np == 0) {
1731 qCritical("No points in this source space");
1732 stream->close();
1733 return FIFF_FAIL;
1734 }
1735 if (!node->find_tag(stream, FIFF_MNE_SOURCE_SPACE_POINTS, t_pTag)) {
1736 stream->close();
1737 return FIFF_FAIL;
1738 }
1739 MatrixXf tmp_rr = t_pTag->toFloatMatrix().transpose();
1740 new_space->rr = tmp_rr;
1741 if (!node->find_tag(stream, FIFF_MNE_SOURCE_SPACE_NORMALS, t_pTag)) {
1742 stream->close();
1743 return FIFF_FAIL;
1744 }
1745 MatrixXf tmp_nn = t_pTag->toFloatMatrix().transpose();
1746 new_space->nn = tmp_nn;
1747 if (!node->find_tag(stream, FIFF_MNE_COORD_FRAME, t_pTag)) {
1748 new_space->coord_frame = FIFFV_COORD_MRI;
1749 } else {
1750 new_space->coord_frame = *t_pTag->toInt();
1751 }
1752 if (node->find_tag(stream, FIFF_MNE_SOURCE_SPACE_ID, t_pTag)) {
1753 new_space->id = *t_pTag->toInt();
1754 }
1755 if (node->find_tag(stream, FIFF_SUBJ_HIS_ID, t_pTag)) {
1756 new_space->subject = t_pTag->toString();
1757 }
1758 if (node->find_tag(stream, FIFF_MNE_SOURCE_SPACE_TYPE, t_pTag)) {
1759 new_space->type = *t_pTag->toInt();
1760 }
1761 ntri = 0;
1762 if (node->find_tag(stream, FIFF_BEM_SURF_NTRI, t_pTag)) {
1763 ntri = *t_pTag->toInt();
1764 } else if (node->find_tag(stream, FIFF_MNE_SOURCE_SPACE_NTRI, t_pTag)) {
1765 ntri = *t_pTag->toInt();
1766 }
1767 if (ntri > 0) {
1768 if (!node->find_tag(stream, FIFF_BEM_SURF_TRIANGLES, t_pTag)) {
1769 if (!node->find_tag(stream, FIFF_MNE_SOURCE_SPACE_TRIANGLES, t_pTag)) {
1770 stream->close();
1771 return FIFF_FAIL;
1772 }
1773 }
1774
1775 MatrixXi tmp_itris = t_pTag->toIntMatrix().transpose();
1776 tmp_itris.array() -= 1;
1777 new_space->itris = tmp_itris;
1778 new_space->ntri = ntri;
1779 }
1780 if (!node->find_tag(stream, FIFF_MNE_SOURCE_SPACE_NUSE, t_pTag)) {
1781 if (new_space->type == FIFFV_MNE_SPACE_VOLUME) {
1782 /*
1783 * Use all
1784 */
1785 new_space->nuse = new_space->np;
1786 new_space->inuse = Eigen::VectorXi::Ones(new_space->nuse);
1787 new_space->vertno = Eigen::VectorXi::LinSpaced(new_space->nuse, 0, new_space->nuse - 1);
1788 } else {
1789 /*
1790 * None in use
1791 * NOTE: The consequences of this change have to be evaluated carefully
1792 */
1793 new_space->nuse = 0;
1794 new_space->inuse = Eigen::VectorXi::Zero(new_space->np);
1795 new_space->vertno.resize(0);
1796 }
1797 } else {
1798 new_space->nuse = *t_pTag->toInt();
1799 if (!node->find_tag(stream, FIFF_MNE_SOURCE_SPACE_SELECTION, t_pTag)) {
1800 stream->close();
1801 return FIFF_FAIL;
1802 }
1803
1804 {
1805 Eigen::Map<Eigen::VectorXi> inuseMap(t_pTag->toInt(), new_space->np);
1806 new_space->inuse = inuseMap;
1807 }
1808 if (new_space->nuse > 0) {
1809 new_space->vertno = Eigen::VectorXi::Zero(new_space->nuse);
1810 for (k = 0, p = 0; k < new_space->np; k++) {
1811 if (new_space->inuse[k])
1812 new_space->vertno[p++] = k;
1813 }
1814 } else {
1815 new_space->vertno.resize(0);
1816 }
1817 /*
1818 * Selection triangulation
1819 */
1820 ntri = 0;
1821 if (node->find_tag(stream, FIFF_MNE_SOURCE_SPACE_NUSE_TRI, t_pTag)) {
1822 ntri = *t_pTag->toInt();
1823 }
1824 if (ntri > 0) {
1825 if (!node->find_tag(stream, FIFF_MNE_SOURCE_SPACE_USE_TRIANGLES, t_pTag)) {
1826 stream->close();
1827 return FIFF_FAIL;
1828 }
1829
1830 MatrixXi tmp_itris = t_pTag->toIntMatrix().transpose();
1831 tmp_itris.array() -= 1;
1832 new_space->use_itris = tmp_itris;
1833 new_space->nuse_tri = ntri;
1834 }
1835 /*
1836 * The patch information becomes relevant here
1837 */
1838 if (node->find_tag(stream, FIFF_MNE_SOURCE_SPACE_NEAREST, t_pTag)) {
1839 Eigen::Map<Eigen::VectorXi> nearestMap(t_pTag->toInt(), new_space->np);
1840 new_space->nearest.resize(new_space->np);
1841 for (k = 0; k < new_space->np; k++) {
1842 new_space->nearest[k].vert = k;
1843 new_space->nearest[k].nearest = nearestMap[k];
1844 new_space->nearest[k].patch = nullptr;
1845 }
1846
1847 if (!node->find_tag(stream, FIFF_MNE_SOURCE_SPACE_NEAREST_DIST, t_pTag)) {
1848 stream->close();
1849 return FIFF_FAIL;
1850 }
1851 Eigen::Map<const Eigen::VectorXf> nearestDistMap(t_pTag->toFloat(), new_space->np);
1852 for (k = 0; k < new_space->np; k++) {
1853 new_space->nearest[k].dist = nearestDistMap[k];
1854 }
1855 }
1856 /*
1857 * We may have the distance matrix
1858 */
1859 if (node->find_tag(stream, FIFF_MNE_SOURCE_SPACE_DIST_LIMIT, t_pTag)) {
1860 new_space->dist_limit = *t_pTag->toFloat();
1861 if (node->find_tag(stream, FIFF_MNE_SOURCE_SPACE_DIST, t_pTag)) {
1862 // SparseMatrix<double> tmpSparse = t_pTag->toSparseFloatMatrix();
1863 auto dist_lower = FiffSparseMatrix::fiff_get_float_sparse_matrix(t_pTag);
1864 if (!dist_lower) {
1865 stream->close();
1866 return FIFF_FAIL;
1867 }
1868 auto dist_full = dist_lower->mne_add_upper_triangle_rcs();
1869 if (!dist_full) {
1870 stream->close();
1871 return FIFF_FAIL;
1872 }
1873 new_space->dist = std::move(*dist_full);
1874 } else
1875 new_space->dist_limit = 0.0;
1876 }
1877 }
1878 /*
1879 * For volume source spaces we might have the neighborhood information
1880 */
1881 if (new_space->type == FIFFV_MNE_SPACE_VOLUME) {
1882 int ntot, nvert, ntot_count, nneigh;
1883
1884 Eigen::VectorXi neighborsVec;
1885 Eigen::VectorXi nneighborsVec;
1886 ntot = nvert = 0;
1887 if (node->find_tag(stream, FIFF_MNE_SOURCE_SPACE_NEIGHBORS, t_pTag)) {
1888 ntot = static_cast<int>(t_pTag->size() / sizeof(fiff_int_t));
1889 neighborsVec = Eigen::Map<Eigen::VectorXi>(t_pTag->toInt(), ntot);
1890 }
1891 if (node->find_tag(stream, FIFF_MNE_SOURCE_SPACE_NNEIGHBORS, t_pTag)) {
1892 nvert = static_cast<int>(t_pTag->size() / sizeof(fiff_int_t));
1893 nneighborsVec = Eigen::Map<Eigen::VectorXi>(t_pTag->toInt(), nvert);
1894 }
1895 if (neighborsVec.size() > 0 && nneighborsVec.size() > 0) {
1896 if (nvert != new_space->np) {
1897 qCritical("Inconsistent neighborhood data in file.");
1898 stream->close();
1899 return FIFF_FAIL;
1900 }
1901 for (k = 0, ntot_count = 0; k < nvert; k++)
1902 ntot_count += nneighborsVec[k];
1903 if (ntot_count != ntot) {
1904 qCritical("Inconsistent neighborhood data in file.");
1905 stream->close();
1906 return FIFF_FAIL;
1907 }
1908 new_space->nneighbor_vert = Eigen::VectorXi::Zero(nvert);
1909 new_space->neighbor_vert.resize(nvert);
1910 for (k = 0, q = 0; k < nvert; k++) {
1911 new_space->nneighbor_vert[k] = nneigh = nneighborsVec[k];
1912 new_space->neighbor_vert[k] = Eigen::VectorXi(nneigh);
1913 for (p = 0; p < nneigh; p++, q++)
1914 new_space->neighbor_vert[k][p] = neighborsVec[q];
1915 }
1916 }
1917 /*
1918 * There might be a coordinate transformation and dimensions
1919 */
1921 if (node->find_tag(stream, FIFF_MNE_SOURCE_SPACE_VOXEL_DIMS, t_pTag)) {
1922 Eigen::Map<Eigen::Vector3i> volDimsMap(t_pTag->toInt());
1923 Eigen::Map<Eigen::Vector3i>(new_space->vol_dims) = volDimsMap;
1924 }
1925 {
1926 QList<FiffDirNode::SPtr> mris = node->dir_tree_find(FIFFB_MNE_PARENT_MRI_FILE);
1927
1928 if (mris.size() == 0) { /* The old way */
1930 if (node->find_tag(stream, FIFF_MNE_SOURCE_SPACE_MRI_FILE, t_pTag)) {
1931 new_space->MRI_volume = t_pTag->toString();
1932 }
1933 if (node->find_tag(stream, FIFF_MNE_SOURCE_SPACE_INTERPOLATOR, t_pTag)) {
1934 new_space->interpolator = std::move(*FiffSparseMatrix::fiff_get_float_sparse_matrix(t_pTag));
1935 }
1936 } else {
1937 if (mris[0]->find_tag(stream, FIFF_MNE_FILE_NAME, t_pTag)) {
1938 new_space->MRI_volume = t_pTag->toString();
1939 }
1942
1943 if (mris[0]->find_tag(stream, FIFF_MNE_SOURCE_SPACE_INTERPOLATOR, t_pTag)) {
1944 new_space->interpolator = std::move(*FiffSparseMatrix::fiff_get_float_sparse_matrix(t_pTag));
1945 }
1946 if (mris[0]->find_tag(stream, FIFF_MRI_WIDTH, t_pTag)) {
1947 new_space->MRI_vol_dims[0] = *t_pTag->toInt();
1948 }
1949 if (mris[0]->find_tag(stream, FIFF_MRI_HEIGHT, t_pTag)) {
1950 new_space->MRI_vol_dims[1] = *t_pTag->toInt();
1951 }
1952 if (mris[0]->find_tag(stream, FIFF_MRI_DEPTH, t_pTag)) {
1953 new_space->MRI_vol_dims[2] = *t_pTag->toInt();
1954 }
1955 }
1956 }
1957 }
1958 new_space->add_triangle_data();
1959 local_spaces.push_back(std::move(new_space));
1960 }
1961 stream->close();
1962
1963 spaces = std::move(local_spaces);
1964
1965 return FIFF_OK;
1966}
1967
1968//=============================================================================================================
1969
1970int MNESourceSpace::transform_source_spaces_to(int coord_frame, const FiffCoordTrans& t, std::vector<std::unique_ptr<MNESourceSpace>>& spaces)
1971/*
1972 * Facilitate the transformation of the source spaces
1973 */
1974{
1975 MNESourceSpace* s;
1976 int k;
1977 int nspace = static_cast<int>(spaces.size());
1978
1979 for (k = 0; k < nspace; k++) {
1980 s = spaces[k].get();
1981 if (s->coord_frame != coord_frame) {
1982 if (!t.isEmpty()) {
1983 if (s->coord_frame == t.from && t.to == coord_frame) {
1984 if (s->transform_source_space(t) != OK)
1985 return FAIL;
1986 } else if (s->coord_frame == t.to && t.from == coord_frame) {
1987 FiffCoordTrans my_t = t.inverted();
1988 if (s->transform_source_space(my_t) != OK) {
1989 return FAIL;
1990 }
1991 } else {
1992 qCritical("Could not transform a source space because of transformation incompatibility.");
1993 return FAIL;
1994 }
1995 } else {
1996 qCritical("Could not transform a source space because of missing coordinate transformation.");
1997 return FAIL;
1998 }
1999 }
2000 }
2001 return OK;
2002}
2003
2004//=============================================================================================================
2005
2006#define LH_LABEL_TAG "-lh.label"
2007#define RH_LABEL_TAG "-rh.label"
2008
2009int MNESourceSpace::restrict_sources_to_labels(std::vector<std::unique_ptr<MNESourceSpace>>& spaces, const QStringList& labels, int nlabel)
2010/*
2011 * Pick only sources within a label
2012 */
2013{
2014 MNESourceSpace* lh = nullptr;
2015 MNESourceSpace* rh = nullptr;
2016 MNESourceSpace* sp;
2017 Eigen::VectorXi lh_inuse;
2018 Eigen::VectorXi rh_inuse;
2019 Eigen::VectorXi sel;
2020 Eigen::VectorXi* inuse = nullptr;
2021 int k, p;
2022 int nspace = static_cast<int>(spaces.size());
2023
2024 if (nlabel == 0)
2025 return OK;
2026
2027 for (k = 0; k < nspace; k++) {
2028 if (spaces[k]->is_left_hemi()) {
2029 lh = spaces[k].get();
2030 lh_inuse = Eigen::VectorXi::Zero(lh->np);
2031 } else {
2032 rh = spaces[k].get();
2033 rh_inuse = Eigen::VectorXi::Zero(rh->np);
2034 }
2035 }
2036 /*
2037 * Go through each label file
2038 */
2039 for (k = 0; k < nlabel; k++) {
2040 /*
2041 * Which hemi?
2042 */
2043 if (labels[k].contains(LH_LABEL_TAG)) { //strstr(labels[k],LH_LABEL_TAG) != NULL) {
2044 sp = lh;
2045 inuse = &lh_inuse;
2046 } else if (labels[k].contains(RH_LABEL_TAG)) { //strstr(labels[k],RH_LABEL_TAG) != NULL) {
2047 sp = rh;
2048 inuse = &rh_inuse;
2049 } else {
2050 qWarning("\tWarning: cannot assign label file %s to a hemisphere.\n", labels[k].toUtf8().constData());
2051 continue;
2052 }
2053 if (sp) {
2054 if (read_label(labels[k], sel) == FAIL)
2055 return FAIL;
2056 for (p = 0; p < sel.size(); p++) {
2057 if (sel[p] >= 0 && sel[p] < sp->np)
2058 (*inuse)[sel[p]] = sp->inuse[sel[p]];
2059 else
2060 qWarning("vertex number out of range in %s (%d vs %d)\n",
2061 labels[k].toUtf8().constData(), sel[p], sp->np);
2062 }
2063 qInfo("Processed label file %s\n", labels[k].toUtf8().constData());
2064 }
2065 }
2066 if (lh)
2067 lh->update_inuse(std::move(lh_inuse));
2068 if (rh)
2069 rh->update_inuse(std::move(rh_inuse));
2070 return OK;
2071}
2072
2073//=============================================================================================================
2074
2075int MNESourceSpace::read_label(const QString& label, Eigen::VectorXi& sel)
2076/*
2077 * Find the source points within a label
2078 */
2079{
2080 int k, p, nlabel;
2081 char c;
2082 float fdum;
2083 /*
2084 * Read the label file
2085 */
2086 QFile inFile(label);
2087 if (!inFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
2088 qCritical() << label; //err_set_sys_error(label);
2089 sel.resize(0);
2090 return FAIL;
2091 }
2092 inFile.getChar(&c);
2093 if (c != '#') {
2094 qCritical("FsLabel file does not start correctly.");
2095 sel.resize(0);
2096 return FAIL;
2097 }
2098 /*
2099 * Skip the comment line
2100 */
2101 while (inFile.getChar(&c) && c != '\n')
2102 ;
2103 {
2104 QTextStream in(&inFile);
2105 in >> nlabel;
2106 if (in.status() != QTextStream::Ok) {
2107 qCritical("Could not read the number of labelled points.");
2108 sel.resize(0);
2109 return FAIL;
2110 }
2111 sel.resize(nlabel);
2112 for (k = 0; k < nlabel; k++) {
2113 in >> p >> fdum >> fdum >> fdum >> fdum;
2114 if (in.status() != QTextStream::Ok) {
2115 qCritical("Could not read label point # %d", k + 1);
2116 sel.resize(0);
2117 return FAIL;
2118 }
2119 sel[k] = p;
2120 }
2121 }
2122
2123 return OK;
2124}
2125
2126//=============================================================================================================
2127
2128int MNESourceSpace::writeVolumeInfo(FiffStream::SPtr& stream, bool selected_only) const
2129{
2130 int ntot, nvert;
2131 int nneigh;
2132 int k, p;
2133
2135 return OK;
2136 if (neighbor_vert.empty() || nneighbor_vert.size() == 0)
2137 return OK;
2138
2139 Eigen::VectorXi nneighbors;
2140 Eigen::VectorXi neighbors;
2141
2142 if (selected_only) {
2143 Eigen::VectorXi inuse_map = Eigen::VectorXi::Constant(np, -1);
2144 for (k = 0, p = 0, ntot = 0; k < np; k++) {
2145 if (inuse[k]) {
2146 ntot += nneighbor_vert[k];
2147 inuse_map[k] = p++;
2148 }
2149 }
2150 nneighbors.resize(nuse);
2151 neighbors.resize(ntot);
2152 for (k = 0, nvert = 0, ntot = 0; k < np; k++) {
2153 if (inuse[k]) {
2154 const Eigen::VectorXi& neigh = neighbor_vert[k];
2155 nneigh = nneighbor_vert[k];
2156 nneighbors[nvert++] = nneigh;
2157 for (p = 0; p < nneigh; p++)
2158 neighbors[ntot++] = neigh[p] < 0 ? -1 : inuse_map[neigh[p]];
2159 }
2160 }
2161 } else {
2162 for (k = 0, ntot = 0; k < np; k++)
2163 ntot += nneighbor_vert[k];
2164 nneighbors.resize(np);
2165 neighbors.resize(ntot);
2166 nvert = np;
2167 for (k = 0, ntot = 0; k < np; k++) {
2168 const Eigen::VectorXi& neigh = neighbor_vert[k];
2169 nneigh = nneighbor_vert[k];
2170 nneighbors[k] = nneigh;
2171 for (p = 0; p < nneigh; p++)
2172 neighbors[ntot++] = neigh[p];
2173 }
2174 }
2175
2176 stream->write_int(FIFF_MNE_SOURCE_SPACE_NNEIGHBORS, nneighbors.data(), nvert);
2177 stream->write_int(FIFF_MNE_SOURCE_SPACE_NEIGHBORS, neighbors.data(), ntot);
2178
2179 if (!selected_only) {
2180 if (voxel_surf_RAS_t && !voxel_surf_RAS_t->isEmpty()) {
2181 stream->write_coord_trans(*voxel_surf_RAS_t);
2182 stream->write_int(FIFF_MNE_SOURCE_SPACE_VOXEL_DIMS, vol_dims, 3);
2183 }
2184 if (interpolator && !MRI_volume.isEmpty()) {
2185 stream->start_block(FIFFB_MNE_PARENT_MRI_FILE);
2186 if (MRI_surf_RAS_RAS_t && !MRI_surf_RAS_RAS_t->isEmpty())
2187 stream->write_coord_trans(*MRI_surf_RAS_RAS_t);
2188 if (MRI_voxel_surf_RAS_t && !MRI_voxel_surf_RAS_t->isEmpty())
2189 stream->write_coord_trans(*MRI_voxel_surf_RAS_t);
2190 stream->write_string(FIFF_MNE_FILE_NAME, MRI_volume);
2191 if (interpolator)
2192 stream->write_float_sparse_rcs(FIFF_MNE_SOURCE_SPACE_INTERPOLATOR, interpolator->eigen());
2193 if (MRI_vol_dims[0] > 0 && MRI_vol_dims[1] > 0 && MRI_vol_dims[2] > 0) {
2194 stream->write_int(FIFF_MRI_WIDTH, &MRI_vol_dims[0]);
2195 stream->write_int(FIFF_MRI_HEIGHT, &MRI_vol_dims[1]);
2196 stream->write_int(FIFF_MRI_DEPTH, &MRI_vol_dims[2]);
2197 }
2198 stream->end_block(FIFFB_MNE_PARENT_MRI_FILE);
2199 }
2200 } else {
2201 if (interpolator && !MRI_volume.isEmpty()) {
2202 stream->write_string(FIFF_MNE_SOURCE_SPACE_MRI_FILE, MRI_volume);
2203 qCritical("Cannot write the interpolator for selection yet");
2204 return FAIL;
2205 }
2206 }
2207 return OK;
2208}
2209
2210//=============================================================================================================
2211
2212int MNESourceSpace::writeToStream(FiffStream::SPtr& stream, bool selected_only) const
2213{
2214 int p, pp;
2215
2216 if (np <= 0) {
2217 qCritical("No points in the source space being saved");
2218 return FIFF_FAIL;
2219 }
2220
2221 stream->start_block(FIFFB_MNE_SOURCE_SPACE);
2222
2224 stream->write_int(FIFF_MNE_SOURCE_SPACE_TYPE, &type);
2225 if (id != FIFFV_MNE_SURF_UNKNOWN)
2226 stream->write_int(FIFF_MNE_SOURCE_SPACE_ID, &id);
2227 if (!subject.isEmpty() && subject.size() > 0) {
2228 QString subj(subject);
2229 stream->write_string(FIFF_SUBJ_HIS_ID, subj);
2230 }
2231
2232 stream->write_int(FIFF_MNE_COORD_FRAME, &coord_frame);
2233
2234 if (selected_only) {
2235 if (nuse == 0) {
2236 qCritical("No vertices in use. Cannot write active-only vertices from this source space");
2237 return FIFF_FAIL;
2238 }
2239
2240 Eigen::MatrixXf sel(nuse, 3);
2241 stream->write_int(FIFF_MNE_SOURCE_SPACE_NPOINTS, &nuse);
2242
2243 for (p = 0, pp = 0; p < np; p++) {
2244 if (inuse[p]) {
2245 sel.row(pp) = rr.row(p);
2246 pp++;
2247 }
2248 }
2249 stream->write_float_matrix(FIFF_MNE_SOURCE_SPACE_POINTS, sel);
2250
2251 for (p = 0, pp = 0; p < np; p++) {
2252 if (inuse[p]) {
2253 sel.row(pp) = nn.row(p);
2254 pp++;
2255 }
2256 }
2257 stream->write_float_matrix(FIFF_MNE_SOURCE_SPACE_NORMALS, sel);
2258 } else {
2259 stream->write_int(FIFF_MNE_SOURCE_SPACE_NPOINTS, &np);
2260 stream->write_float_matrix(FIFF_MNE_SOURCE_SPACE_POINTS, Eigen::MatrixXf(rr));
2261 stream->write_float_matrix(FIFF_MNE_SOURCE_SPACE_NORMALS, Eigen::MatrixXf(nn));
2262
2263 if (nuse > 0 && inuse.size() > 0) {
2264 stream->write_int(FIFF_MNE_SOURCE_SPACE_SELECTION, inuse.data(), np);
2265 stream->write_int(FIFF_MNE_SOURCE_SPACE_NUSE, &nuse);
2266 }
2267
2268 if (ntri > 0) {
2269 stream->write_int(FIFF_MNE_SOURCE_SPACE_NTRI, &ntri);
2270 Eigen::MatrixXi file_tris = itris.array() + 1;
2271 stream->write_int_matrix(FIFF_MNE_SOURCE_SPACE_TRIANGLES, file_tris);
2272 }
2273
2274 if (nuse_tri > 0) {
2275 stream->write_int(FIFF_MNE_SOURCE_SPACE_NUSE_TRI, &nuse_tri);
2276 Eigen::MatrixXi file_use_tris = use_itris.array() + 1;
2277 stream->write_int_matrix(FIFF_MNE_SOURCE_SPACE_USE_TRIANGLES, file_use_tris);
2278 }
2279
2280 if (!nearest.empty()) {
2281 Eigen::VectorXi nearest_v(np);
2282 Eigen::VectorXf nearest_dist_v(np);
2283
2284 std::sort(const_cast<std::vector<MNENearest>&>(nearest).begin(),
2285 const_cast<std::vector<MNENearest>&>(nearest).end(),
2286 [](const MNENearest& a, const MNENearest& b) { return a.vert < b.vert; });
2287 for (p = 0; p < np; p++) {
2288 nearest_v[p] = nearest[p].nearest;
2289 nearest_dist_v[p] = nearest[p].dist;
2290 }
2291
2292 stream->write_int(FIFF_MNE_SOURCE_SPACE_NEAREST, nearest_v.data(), np);
2293 stream->write_float(FIFF_MNE_SOURCE_SPACE_NEAREST_DIST, nearest_dist_v.data(), np);
2294 }
2295
2296 if (!dist.is_empty()) {
2297 auto m = dist.pickLowerTriangleRcs();
2298 if (!m)
2299 return FIFF_FAIL;
2300 stream->write_float_sparse_rcs(FIFF_MNE_SOURCE_SPACE_DIST, m->eigen());
2301 stream->write_float(FIFF_MNE_SOURCE_SPACE_DIST_LIMIT, &dist_limit);
2302 }
2303 }
2304
2305 if (writeVolumeInfo(stream, selected_only) != OK)
2306 return FIFF_FAIL;
2307
2308 stream->end_block(FIFFB_MNE_SOURCE_SPACE);
2309 return FIFF_OK;
2310}
2311
2312//=============================================================================================================
2313
2318static Eigen::MatrixX3f generateIcoVertices(int grade)
2319{
2320 // The standard icosahedron of MNE-C's icos.fif (poles on z, a vertex on +x), so that the
2321 // subdivisions match the ico-N source spaces of MNE-C and mne-python.
2322 const float z = 1.0f / std::sqrt(5.0f);
2323 const float r = 2.0f * z;
2324 std::vector<Eigen::Vector3f> verts = {{0, 0, 1}};
2325 for (int k = 0; k < 5; ++k)
2326 verts.emplace_back(r * std::cos(0.4f * EIGEN_PI * k), r * std::sin(0.4f * EIGEN_PI * k), z);
2327 for (int k = 0; k < 5; ++k)
2328 verts.emplace_back(r * std::cos(0.4f * EIGEN_PI * k - 0.2f * EIGEN_PI), r * std::sin(0.4f * EIGEN_PI * k - 0.2f * EIGEN_PI), -z);
2329 verts.emplace_back(0, 0, -1);
2330
2331 std::vector<std::array<int, 3>> faces = {
2332 {0, 3, 4}, {0, 4, 5}, {0, 5, 1}, {0, 1, 2}, {0, 2, 3}, {3, 2, 8}, {3, 8, 9}, {3, 9, 4}, {4, 9, 10}, {4, 10, 5}, {5, 10, 6}, {5, 6, 1}, {1, 6, 7}, {1, 7, 2}, {2, 7, 8}, {8, 11, 9}, {9, 11, 10}, {10, 11, 6}, {6, 11, 7}, {7, 11, 8}};
2333
2334 // Subdivide
2335 for (int g = 0; g < grade; ++g) {
2336 std::map<std::pair<int, int>, int> midpointCache;
2337 std::vector<std::array<int, 3>> newFaces;
2338
2339 auto getMidpoint = [&](int i1, int i2) -> int {
2340 auto key = std::make_pair(std::min(i1, i2), std::max(i1, i2));
2341 auto it = midpointCache.find(key);
2342 if (it != midpointCache.end())
2343 return it->second;
2344 Eigen::Vector3f mid = (verts[i1] + verts[i2]).normalized();
2345 int idx = static_cast<int>(verts.size());
2346 verts.push_back(mid);
2347 midpointCache[key] = idx;
2348 return idx;
2349 };
2350
2351 for (const auto& f : faces) {
2352 int a = getMidpoint(f[0], f[1]);
2353 int b = getMidpoint(f[1], f[2]);
2354 int c = getMidpoint(f[2], f[0]);
2355 newFaces.push_back({f[0], a, c});
2356 newFaces.push_back({f[1], b, a});
2357 newFaces.push_back({f[2], c, b});
2358 newFaces.push_back({a, b, c});
2359 }
2360 faces = newFaces;
2361 }
2362
2363 Eigen::MatrixX3f result(static_cast<int>(verts.size()), 3);
2364 for (int i = 0; i < static_cast<int>(verts.size()); ++i)
2365 result.row(i) = verts[i];
2366 return result;
2367}
2368
2369//=============================================================================================================
2370
2372{
2373 MNEHemisphere result(hemi);
2374
2375 if (hemi.np <= 0 || hemi.rr.rows() == 0) {
2376 qWarning("MNESourceSpace::icoDownsample - Hemisphere has no vertices.");
2377 return result;
2378 }
2379
2380 // Generate icosahedral surface at the requested grade
2381 Eigen::MatrixX3f icoVerts = generateIcoVertices(icoGrade);
2382
2383 // Project hemisphere vertices to unit sphere for matching
2384 Eigen::MatrixX3f hemiNorm(hemi.np, 3);
2385 for (int i = 0; i < hemi.np; ++i) {
2386 Eigen::Vector3f v = hemi.rr.row(i);
2387 float len = v.norm();
2388 if (len > 0.0f)
2389 hemiNorm.row(i) = (v / len).transpose();
2390 else
2391 hemiNorm.row(i) = v.transpose();
2392 }
2393
2394 // Clear inuse
2395 result.inuse = Eigen::VectorXi::Zero(result.np);
2396
2397 // For each icosahedral vertex, find the nearest hemisphere vertex
2398 for (int i = 0; i < icoVerts.rows(); ++i) {
2399 Eigen::Vector3f icoV = icoVerts.row(i);
2400 float bestDist = std::numeric_limits<float>::max();
2401 int bestIdx = -1;
2402 for (int j = 0; j < hemi.np; ++j) {
2403 float d = (hemiNorm.row(j).transpose() - icoV).squaredNorm();
2404 if (d < bestDist) {
2405 bestDist = d;
2406 bestIdx = j;
2407 }
2408 }
2409 if (bestIdx >= 0)
2410 result.inuse[bestIdx] = 1;
2411 }
2412
2413 // Recount nuse and rebuild vertno
2414 result.nuse = result.inuse.sum();
2415 result.vertno.resize(result.nuse);
2416 int k = 0;
2417 for (int i = 0; i < result.np; ++i) {
2418 if (result.inuse[i])
2419 result.vertno[k++] = i;
2420 }
2421
2422 return result;
2423}
Symbolic FIFF tag, block, value, unit and channel-type constants shared across FIFFLIB.
#define FIFFV_MNE_SURF_RIGHT_HEMI
#define FIFF_MNE_SOURCE_SPACE_ID
#define FIFF_MNE_SOURCE_SPACE_DIST_LIMIT
#define FIFF_MNE_SOURCE_SPACE_VOXEL_DIMS
#define FIFF_MNE_COORD_FRAME
#define FIFF_OK
#define FIFF_MNE_SOURCE_SPACE_TYPE
#define FIFFV_MNE_SURF_UNKNOWN
#define FIFF_MNE_SOURCE_SPACE_SELECTION
#define FIFFV_MNE_SURF_LEFT_HEMI
#define FIFF_MNE_SOURCE_SPACE_USE_TRIANGLES
#define FIFF_MNE_SOURCE_SPACE_NUSE_TRI
#define FIFF_MNE_SOURCE_SPACE_INTERPOLATOR
#define FIFF_MNE_SOURCE_SPACE_NORMALS
#define FIFFV_MNE_COORD_MRI_VOXEL
#define FIFF_MNE_SOURCE_SPACE_NEAREST_DIST
#define FIFF_MNE_SOURCE_SPACE_DIST
#define FIFF_MNE_SOURCE_SPACE_POINTS
#define FIFF_FAIL
#define FIFFV_MNE_SPACE_SURFACE
#define FIFF_MNE_SOURCE_SPACE_NTRI
#define FIFF_MNE_SOURCE_SPACE_MRI_FILE
#define FIFFB_MNE_SOURCE_SPACE
#define FIFF_MNE_SOURCE_SPACE_NPOINTS
#define FIFFV_MNE_SPACE_VOLUME
#define FIFFV_NO_MOVE
#define FIFFV_COORD_HEAD
#define FIFFV_COORD_MRI
#define FIFF_MNE_SOURCE_SPACE_TRIANGLES
#define FIFFV_MNE_SPACE_UNKNOWN
#define FIFF_MNE_SOURCE_SPACE_NUSE
#define FIFFV_MOVE
#define FIFFV_MNE_COORD_RAS
#define FIFF_MNE_FILE_NAME
#define FIFF_MNE_SOURCE_SPACE_NEAREST
#define FIFFB_MNE_PARENT_MRI_FILE
Endianness swap helpers for the FIFF binary tag I/O layer (FIFF is always written big-endian on disk)...
FIFF tag: the 16-byte tag header (kind, type, size, next) plus its decoded payload.
FIFF binary tag-stream layer: wraps a QIODevice to read and write FIFF tags, directories,...
4x4 affine FIFF coordinate transform (FIFF_COORD_TRANS) annotated with source/destination coordinate-...
#define FIFF_MRI_DEPTH
Definition fiff_file.h:650
#define FIFF_BEM_SURF_TRIANGLES
Definition fiff_file.h:729
#define FIFF_MRI_HEIGHT
Definition fiff_file.h:648
#define FIFF_BEM_SURF_NTRI
Definition fiff_file.h:727
#define FIFFV_BEM_SURF_ID_BRAIN
Definition fiff_file.h:742
#define FIFF_SUBJ_HIS_ID
Definition fiff_file.h:566
#define FIFF_MRI_WIDTH
Definition fiff_file.h:646
return FiffCoordTrans(from_frame, to_frame, R, moveVec)
FIFF sparse matrix: column / row-compressed sparse storage backed by Eigen::SparseMatrix.
#define M_PI
constexpr int FAIL
constexpr int Y
constexpr int Z
constexpr int OK
constexpr int X
#define MNE_SOURCE_SPACE_VOLUME
Definition mne_types.h:82
Per-hemisphere cortical surface bundle with decimation, patch info and rendering buffers.
Patch information (cluster of cortex vertices around each decimated source) used by orientation prior...
Lightweight triangulated surface (vertices, triangles, normals) used by surface-based routines.
Single-hemisphere source space (cortical surface or volume grid) loaded from FIFF.
#define QUAD_FILE_MAGIC_NUMBER
#define NEW_QUAD_FILE_MAGIC_NUMBER
#define FIFFV_MNE_COORD_SURFACE_RAS
#define FIFF_MNE_SOURCE_SPACE_NEIGHBORS
#define FIFF_MNE_SOURCE_SPACE_NNEIGHBORS
#define TAG_OLD_SURF_GEOM
#define TRIANGLE_FILE_MAGIC_NUMBER
constexpr int CURVATURE_FILE_MAGIC_NUMBER
constexpr int TAG_USEREALRAS
constexpr int NNEIGHBORS
constexpr int TAG_OLD_MGH_XFORM
constexpr int TAG_OLD_USEREALRAS
#define LH_LABEL_TAG
constexpr int TAG_OLD_COLORTABLE
#define EVEN(n)
#define RH_LABEL_TAG
Per-source-space-vertex nearest-cortex-vertex mapping.
Ordered group of MNELIB::MNEMghTag entries appended to an MGH/MGZ file.
Argument record passed to a background raw-data filter worker.
Core MNE data structures (source spaces, source estimates, hemispheres).
FIFF file I/O, in-memory data structures and high-level readers/writers.
float swap_float(float source)
qint32 swap_int(qint32 source)
qint32 fiff_int_t
Definition fiff_types.h:86
qint64 swap_long(qint64 source)
qint16 swap_short(qint16 source)
Labelled 4x4 FIFF affine: source frame, destination frame, rotation, translation and cached inverse.
static FiffCoordTrans readTransformFromNode(FiffStream::SPtr &stream, const FiffDirNode::SPtr &node, int from, int to)
Eigen::MatrixX3f apply_inverse_trans(const Eigen::MatrixX3f &rr, bool do_move=true) const
FiffCoordTrans inverted() const
Eigen::MatrixX3f apply_trans(const Eigen::MatrixX3f &rr, bool do_move=true) const
QSharedPointer< FiffDirNode > SPtr
Sparse FIFF matrix: CCS or RCS storage with the value / index / pointer triple as written by FiffStre...
static FiffSparseMatrix::UPtr fiff_get_float_sparse_matrix(const FIFFLIB::FiffTag::UPtr &tag)
FIFF tag-stream reader/writer: wraps a QIODevice and exposes typed read_* / write_* methods for every...
QSharedPointer< FiffStream > SPtr
std::unique_ptr< FiffTag > UPtr
Definition fiff_tag.h:165
Thread-local arguments for parallel raw data filtering (channel range, filter kernel,...
QWeakPointer< MNESurface > surf
std::unique_ptr< FIFFLIB::FiffCoordTrans > mri_head_t
Hemisphere provides geometry information.
Collection of MNEMghTag entries from a FreeSurfer MGH/MGZ file footer.
std::vector< std::unique_ptr< MNEMghTag > > tags
This is used in the patch definitions.
Definition mne_nearest.h:61
MNEPatchInfo * patch
Definition mne_nearest.h:83
Patch information for a single source space point including vertex members and area.
virtual MNESourceSpace::SPtr clone() const
static std::unique_ptr< MNESourceSpace > load_surface(const QString &surf_file, const QString &curv_file)
qint32 find_source_space_hemi() const
std::shared_ptr< MNESourceSpace > SPtr
static int restrict_sources_to_labels(std::vector< std::unique_ptr< MNESourceSpace > > &spaces, const QStringList &labels, int nlabel)
static int filter_source_spaces(const MNESurface &surf, float limit, const FIFFLIB::FiffCoordTrans &mri_head_t, std::vector< std::unique_ptr< MNESourceSpace > > &spaces, QTextStream *filtered)
static int read_source_spaces(const QString &name, std::vector< std::unique_ptr< MNESourceSpace > > &spaces)
static std::unique_ptr< MNESourceSpace > create_source_space(int np)
static std::unique_ptr< MNESourceSpace > load_surface_geom(const QString &surf_file, const QString &curv_file, bool add_geometry, bool check_too_many_neighbors)
static MNEHemisphere icoDownsample(const MNEHemisphere &hemi, int icoGrade)
static void filter_source_space(FilterThreadArg *arg)
int writeToStream(FIFFLIB::FiffStream::SPtr &stream, bool selected_only) const
static MNESourceSpace * make_volume_source_space(const MNESurface &surf, float grid, float exclude, float mindist)
int transform_source_space(const FIFFLIB::FiffCoordTrans &t)
static int read_label(const QString &label, Eigen::VectorXi &sel)
static int transform_source_spaces_to(int coord_frame, const FIFFLIB::FiffCoordTrans &t, std::vector< std::unique_ptr< MNESourceSpace > > &spaces)
void update_inuse(Eigen::VectorXi new_inuse)
Lightweight triangulated surface (vertices, triangles, normals).
Definition mne_surface.h:68
double sum_solids(const Eigen::Vector3f &from) const
static std::unique_ptr< MNESurface > read_bem_surface(const QString &name, int which, bool add_geometry)
std::vector< Eigen::VectorXi > neighbor_tri
std::vector< Eigen::VectorXi > neighbor_vert
std::optional< FIFFLIB::FiffCoordTrans > MRI_surf_RAS_RAS_t
int add_geometry_info(bool do_normals, bool check_too_many_neighbors)
FIFFLIB::FiffSparseMatrix dist
std::vector< MNENearest > nearest
std::optional< FIFFLIB::FiffSparseMatrix > interpolator
Eigen::Matrix< int, Eigen::Dynamic, 3, Eigen::RowMajor > TrianglesT
std::optional< MNEVolGeom > vol_geom
std::vector< std::optional< MNEPatchInfo > > patches
std::optional< FIFFLIB::FiffCoordTrans > MRI_voxel_surf_RAS_t
std::vector< MNETriangle > tris
std::optional< MNEMghTagGroup > mgh_tags
std::optional< FIFFLIB::FiffCoordTrans > voxel_surf_RAS_t
Eigen::Matrix< float, Eigen::Dynamic, 3, Eigen::RowMajor > PointsT
MRI data volume geometry information like FreeSurfer keeps it.