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