v2.0.0
Loading...
Searching...
No Matches
mne_msh_display_surface.cpp
Go to the documentation of this file.
1//=============================================================================================================
35
36//=============================================================================================================
37// INCLUDES
38//=============================================================================================================
39
41#include "mne_surface.h"
42#include "mne_morph_map.h"
43#include "mne_msh_picked.h"
45
46#include <fiff/fiff_stream.h>
49#include <fiff/fiff_dig_point.h>
50
51#include <utils/sphere.h>
52
53#define _USE_MATH_DEFINES
54#include <math.h>
55
56//=============================================================================================================
57// USED NAMESPACES
58//=============================================================================================================
59
60using namespace Eigen;
61using namespace FIFFLIB;
62using namespace MNELIB;
63
64//============================= local macros =================================
65
66#ifndef TRUE
67#define TRUE 1
68#endif
69
70#ifndef FALSE
71#define FALSE 0
72#endif
73
74#ifndef FAIL
75#define FAIL -1
76#endif
77
78#ifndef OK
79#define OK 0
80#endif
81
82constexpr int SHOW_CURVATURE_NONE = 0;
83constexpr int SHOW_CURVATURE_OVERLAY = 1;
84constexpr int SHOW_OVERLAY_HEAT = 1;
85
86constexpr float POS_CURV_COLOR = 0.25f;
87constexpr float NEG_CURV_COLOR = 0.375f;
88constexpr float EVEN_CURV_COLOR = 0.375f;
89
90#define X_17 0
91#define Y_17 1
92#define Z_17 2
93
94#define VEC_DOT_17(x,y) ((x)[X_17]*(y)[X_17] + (x)[Y_17]*(y)[Y_17] + (x)[Z_17]*(y)[Z_17])
95
96#define VEC_LEN_17(x) sqrt(VEC_DOT_17(x,x))
97
98#define VEC_DIFF_17(from,to,diff) {\
99 (diff)[X_17] = (to)[X_17] - (from)[X_17];\
100 (diff)[Y_17] = (to)[Y_17] - (from)[Y_17];\
101 (diff)[Z_17] = (to)[Z_17] - (from)[Z_17];\
102 }
103
104#define VEC_COPY_17(to,from) {\
105 (to)[X_17] = (from)[X_17];\
106 (to)[Y_17] = (from)[Y_17];\
107 (to)[Z_17] = (from)[Z_17];\
108 }
109
110#define CROSS_PRODUCT_17(x,y,xy) {\
111 (xy)[X_17] = (x)[Y_17]*(y)[Z_17]-(y)[Y_17]*(x)[Z_17];\
112 (xy)[Y_17] = -((x)[X_17]*(y)[Z_17]-(y)[X_17]*(x)[Z_17]);\
113 (xy)[Z_17] = (x)[X_17]*(y)[Y_17]-(y)[X_17]*(x)[Y_17];\
114 }
115
116#define MALLOC_17(x,t) (t *)malloc((x)*sizeof(t))
117
118#define ALLOC_CMATRIX_17(x,y) mne_cmatrix_ds_17((x),(y))
119
120static void matrix_error_ds_17(int kind, int nr, int nc)
121{
122 if (kind == 1)
123 printf("Failed to allocate memory pointers for a %d x %d matrix\n",nr,nc);
124 else if (kind == 2)
125 printf("Failed to allocate memory for a %d x %d matrix\n",nr,nc);
126 else
127 printf("Allocation error for a %d x %d matrix\n",nr,nc);
128 if (sizeof(void *) == 4) {
129 printf("This is probably because you seem to be using a computer with 32-bit architecture.\n");
130 printf("Please consider moving to a 64-bit platform.");
131 }
132 printf("Cannot continue. Sorry.\n");
133 exit(1);
134}
135
136static float** mne_cmatrix_ds_17(int numPoints,int numDim)
137{
138 float** m;
139 float* whole;
140
141 m = MALLOC_17(numPoints, float *);
142 if (!m) matrix_error_ds_17(1, numPoints, numDim);
143
144 whole = MALLOC_17(numPoints * numDim, float);
145 if (!whole) matrix_error_ds_17(2, numPoints, numDim);
146
147 for(int i = 0; i < numPoints; ++i)
148 m[i] = &whole[ i * numDim ];
149
150 return m;
151}
152
153#define FREE_17(x) if ((char *)(x) != NULL) free((char *)(x))
154
155#define FREE_CMATRIX_17(m) mne_free_cmatrix_ds_17((m))
156
157static void mne_free_cmatrix_ds_17(float **m)
158{
159 if (m) {
160 FREE_17(*m);
161 FREE_17(m);
162 }
163}
164
165//=============================================================================================================
166// DEFINE MEMBER METHODS
167//=============================================================================================================
168
170
171//=============================================================================================================
172
178
179//=============================================================================================================
180// Alignment functions (moved from MNESurfaceOrVolume)
181//=============================================================================================================
182
184 const FiffDigitizerData& mri_dig,
185 int niter,
186 int scale_head,
187 float omit_dist,
188 Eigen::Vector3f& scales)
189
190{
191 using FidMatrix = Eigen::Matrix<float, 3, 3, Eigen::RowMajor>;
192 FidMatrix head_fid, mri_fid;
193 bool head_fid_found[3] = {false, false, false};
194 bool mri_fid_found[3] = {false, false, false};
195 int j,k;
196 FiffDigPoint p;
197 float nasion_weight = 5.0;
198
199 for (j = 0; j < 2; j++) {
200 const FiffDigitizerData& d = (j == 0) ? head_dig : mri_dig;
201 FidMatrix& fid = (j == 0) ? head_fid : mri_fid;
202 bool* found = (j == 0) ? head_fid_found : mri_fid_found;
203
204 for (k = 0; k < d.npoint; k++) {
205 p = d.points[k];
206 if (p.kind == FIFFV_POINT_CARDINAL) {
207 if (p.ident == FIFFV_POINT_LPA) {
208 fid.row(0) = Eigen::Map<const Eigen::RowVector3f>(d.points[k].r);
209 found[0] = true;
210 }
211 else if (p.ident == FIFFV_POINT_NASION) {
212 fid.row(1) = Eigen::Map<const Eigen::RowVector3f>(d.points[k].r);
213 found[1] = true;
214 }
215 else if (p.ident == FIFFV_POINT_RPA) {
216 fid.row(2) = Eigen::Map<const Eigen::RowVector3f>(d.points[k].r);
217 found[2] = true;
218 }
219 }
220 }
221 }
222
223 for (k = 0; k < 3; k++) {
224 if (!head_fid_found[k]) {
225 qCritical("Some of the MEG fiducials were missing");
226 goto bad;
227 }
228
229 if (!mri_fid_found[k]) {
230 qCritical("Some of the MRI fiducials were missing");
231 goto bad;
232 }
233 }
234
235 if (scale_head) {
236 get_head_scale(head_dig,mri_fid,scales);
237 printf("xscale = %.3f yscale = %.3f zscale = %.3f\n",scales[0],scales[1],scales[2]);
238
239 for (j = 0; j < 3; j++)
240 for (k = 0; k < 3; k++)
241 mri_fid(j,k) = mri_fid(j,k)*scales[k];
242
243 scale(scales);
244 }
245
246 // Initial alignment
248 mri_fid.row(0).data(),mri_fid.row(1).data(),mri_fid.row(2).data()));
249
250 // Populate mri_fids from cardinal digitizer points transformed into MRI coords
251 head_dig.pickCardinalFiducials();
252
253 // Overwrite the fiducial locations with the ones from the MRI digitizer data
254 for (k = 0; k < head_dig.nfids(); k++)
255 VEC_COPY_17(head_dig.mri_fids[k].r,mri_fid.row(k).data());
256 head_dig.head_mri_t_adj->print();
257 printf("After simple alignment : \n");
258
259 if (omit_dist > 0)
260 discard_outlier_digitizer_points(head_dig,omit_dist);
261
262 // Optional iterative refinement
263 if (niter > 0) {
264 for (k = 0; k < niter; k++) {
265 if (iterate_alignment_once(head_dig,nasion_weight,Eigen::Vector3f(mri_fid.row(1).transpose()),k == niter-1 && niter > 1) == FAIL)
266 goto bad;
267 }
268
269 printf("%d / %d iterations done. RMS dist = %7.1f mm\n",k,niter,
270 1000.0*rms_digitizer_distance(head_dig));
271 printf("After refinement :\n");
272 head_dig.head_mri_t_adj->print();
273 }
274
275 return OK;
276
277bad :
278 return FAIL;
279}
280
281//=============================================================================================================
282
283// Simple head size fit
285 const Eigen::Matrix<float, 3, 3, Eigen::RowMajor>& mri_fid,
286 Eigen::Vector3f& scales)
287{
288 float **dig_rr = NULL;
289 float **head_rr = NULL;
290 int k,ndig,nhead;
291 float simplex_size = 2e-2;
292 float r0[3],Rdig,Rscalp;
293 float LR[3],LN[3],len,norm[3],diff[3];
294
295 scales[0] = scales[1] = scales[2] = 1.0;
296
297 dig_rr = MALLOC_17(dig.npoint,float *);
298 head_rr = MALLOC_17(np,float *);
299
300 // Pick only the points with positive z
301 for (k = 0, ndig = 0; k < dig.npoint; k++) {
302 if (dig.points[k].r[Z_17] > 0) {
303 dig_rr[ndig++] = dig.points[k].r;
304 }
305 }
306
307 if (!UTILSLIB::Sphere::fit_sphere_to_points(dig_rr,ndig,simplex_size,r0,&Rdig)){
308 goto out;
309 }
310
311 printf("Polhemus : (%.1f %.1f %.1f) mm R = %.1f mm\n",1000*r0[X_17],1000*r0[Y_17],1000*r0[Z_17],1000*Rdig);
312
313 // Pick only the points above the fiducial plane
314 VEC_DIFF_17(mri_fid.row(0).data(),mri_fid.row(2).data(),LR);
315 VEC_DIFF_17(mri_fid.row(0).data(),mri_fid.row(1).data(),LN);
316 CROSS_PRODUCT_17(LR,LN,norm);
317 len = VEC_LEN_17(norm);
318 norm[0] = norm[0]/len;
319 norm[1] = norm[1]/len;
320 norm[2] = norm[2]/len;
321
322 for (k = 0, nhead = 0; k < np; k++) {
323 VEC_DIFF_17(mri_fid.row(0).data(),&rr(k,0),diff);
324 if (VEC_DOT_17(diff,norm) > 0) {
325 head_rr[nhead++] = &rr(k,0);
326 }
327 }
328
329 if (!UTILSLIB::Sphere::fit_sphere_to_points(head_rr,nhead,simplex_size,r0,&Rscalp)) {
330 goto out;
331 }
332
333 printf("Scalp : (%.1f %.1f %.1f) mm R = %.1f mm\n",1000*r0[X_17],1000*r0[Y_17],1000*r0[Z_17],1000*Rscalp);
334
335 scales[0] = scales[1] = scales[2] = Rdig/Rscalp;
336
337out : {
338 FREE_17(dig_rr);
339 FREE_17(head_rr);
340 return;
341 }
342}
343
344//=============================================================================================================
345
347 float maxdist) const
348/*
349 * Discard outlier digitizer points
350 */
351{
352 int discarded = 0;
353 int k;
354
355 d.dist_valid = false;
357 for (k = 0; k < d.npoint; k++) {
358 d.discard[k] = 0;
359 /*
360 * Discard unless cardinal landmark or HPI coil
361 */
362 if (std::fabs(d.dist(k)) > maxdist &&
363 d.points[k].kind != FIFFV_POINT_CARDINAL &&
364 d.points[k].kind != FIFFV_POINT_HPI) {
365 discarded++;
366 d.discard[k] = 1;
367 }
368 }
369 printf("%d points discarded (maxdist = %6.1f mm).\n",discarded,1000*maxdist);
370
371 return discarded;
372}
373
374//=============================================================================================================
375
377 int do_all, int do_approx) const
378/*
379 * Calculate the distances from the scalp surface
380 */
381{
382 int k,nactive;
383 FiffDigPoint point;
384 Q_ASSERT(dig.head_mri_t);
385 const FiffCoordTrans& t = (dig.head_mri_t_adj && !dig.head_mri_t_adj->isEmpty()) ? *dig.head_mri_t_adj : *dig.head_mri_t;
386 int nstep = 4;
387
388 if (dig.dist_valid)
389 return ;
390
391 PointsT rr(dig.npoint, 3);
392
393 dig.dist.conservativeResize(dig.npoint);
394 if (dig.closest.size() == 0) {
395 /*
396 * Ensure that all closest values are initialized correctly
397 */
398 dig.closest = Eigen::VectorXi::Constant(dig.npoint, -1);
399 }
400
401 dig.closest_point.setZero(dig.npoint,3);
402 Eigen::VectorXi closest(dig.npoint);
403 Eigen::VectorXf dists(dig.npoint);
404
405 for (k = 0, nactive = 0; k < dig.npoint; k++) {
406 if ((dig.active[k] && !dig.discard[k]) || do_all) {
407 point = dig.points.at(k);
408 rr.row(nactive) = Eigen::Map<const Eigen::RowVector3f>(point.r);
409 FiffCoordTrans::apply_trans(rr.row(nactive).data(),t,FIFFV_MOVE);
410 if (do_approx) {
411 closest[nactive] = dig.closest(k);
412 if (closest[nactive] < 0)
413 do_approx = FALSE;
414 }
415 else
416 closest[nactive] = -1;
417 nactive++;
418 }
419 }
420
421 find_closest_on_surface_approx(rr,nactive,closest,dists,nstep);
422 /*
423 * Project the points on the triangles
424 */
425 if (!do_approx)
426 printf("Inside or outside for %d points...",nactive);
427 for (k = 0, nactive = 0; k < dig.npoint; k++) {
428 if ((dig.active[k] && !dig.discard[k]) || do_all) {
429 dig.dist(k) = dists[nactive];
430 dig.closest(k) = closest[nactive];
431 {
432 Eigen::Vector3f pt = Eigen::Map<const Eigen::Vector3f>(rr.row(nactive).data());
433 Eigen::Vector3f proj = project_to_triangle(dig.closest(k),pt);
434 dig.closest_point.row(k) = proj.transpose();
435 }
436 /*
437 * The above distance is with respect to the closest triangle only
438 * We need to use the solid angle criterion to decide the sign reliably
439 */
440 if (!do_approx && false) {
441 Eigen::Vector3f pt = Eigen::Map<const Eigen::Vector3f>(rr.row(nactive).data());
442 if (sum_solids(pt)/(4*M_PI) > 0.9)
443 dig.dist(k) = - std::fabs(dig.dist(k));
444 else
445 dig.dist(k) = std::fabs(dig.dist(k));
446 }
447 nactive++;
448 }
449 }
450
451 if (!do_approx)
452 printf("[done]\n");
453
454 dig.dist_valid = true;
455
456 return;
457}
458
459//=============================================================================================================
460
462 int nasion_weight, /* Weight for the nasion */
463 const std::optional<Eigen::Vector3f>& nasion_mri, /* Fixed correspondence point for the nasion (optional) */
464 int last_step) const /* Is this the last iteration step */
465/*
466 * Find the best alignment of the coordinate frames
467 */
468{
469 int res = FAIL;
470 float **rr_head = NULL;
471 float **rr_mri = NULL;
472 float *w = NULL;
473 int k,nactive;
474 FiffDigPoint point;
476 float max_diff = 40e-3;
477
478 if (!dig.head_mri_t_adj) {
479 qCritical()<<"Not adjusting the transformation";
480 goto out;
481 }
482 /*
483 * Calculate initial distances
484 */
486
487 /*
488 * Set up the alignment
489 */
490 rr_head = ALLOC_CMATRIX_17(dig.npoint,3);
491 rr_mri = ALLOC_CMATRIX_17(dig.npoint,3);
492 w = MALLOC_17(dig.npoint,float);
493
494 for (k = 0, nactive = 0; k < dig.npoint; k++) {
495 if (dig.active[k] && !dig.discard[k]) {
496 point = dig.points.at(k);
497 VEC_COPY_17(rr_head[nactive],point.r);
498 VEC_COPY_17(rr_mri[nactive],dig.closest_point.row(k).data());
499 /*
500 * Special handling for the nasion
501 */
502 if (point.kind == FIFFV_POINT_CARDINAL &&
503 point.ident == FIFFV_POINT_NASION) {
504 w[nactive] = nasion_weight;
505 if (nasion_mri) {
506 VEC_COPY_17(rr_mri[nactive],nasion_mri->data());
507 VEC_COPY_17(rr_head[nactive],nasion_mri->data());
508 Q_ASSERT(dig.head_mri_t || dig.head_mri_t_adj);
510 dig.head_mri_t_adj ? *dig.head_mri_t_adj : *dig.head_mri_t,
511 FIFFV_MOVE);
512 }
513 }
514 else
515 w[nactive] = 1.0;
516 nactive++;
517 }
518 }
519 if (nactive < 3) {
520 qCritical() << "Not enough points to do the alignment";
521 goto out;
522 }
524 rr_head, rr_mri, w, nactive, max_diff)).isEmpty())
525 goto out;
526
527 if (dig.head_mri_t_adj)
528 dig.head_mri_t_adj = std::make_unique<FiffCoordTrans>(t);
529 /*
530 * Calculate final distances
531 */
532 dig.dist_valid = false;
533 calculate_digitizer_distances(dig,FALSE,!last_step);
534 res = OK;
535 goto out;
536
537out : {
538 FREE_CMATRIX_17(rr_head);
539 FREE_CMATRIX_17(rr_mri);
540 FREE_17(w);
541 return res;
542 }
543}
544
545//=============================================================================================================
546
548{
549 float rms;
550 int k,nactive;
551
553
554 for (k = 0, rms = 0.0, nactive = 0; k < dig.npoint; k++)
555 if (dig.active[k] && !dig.discard[k]) {
556 rms = rms + dig.dist(k)*dig.dist(k);
557 nactive++;
558 }
559 if (nactive > 1)
560 rms = rms/(nactive-1);
561 return sqrt(rms);
562}
563
564//=============================================================================================================
565
566void MNEMshDisplaySurface::scale(const Eigen::Vector3f& scales)
567/*
568 * Not quite complete yet
569 */
570{
571 int j,k;
572
573 for (k = 0; k < 3; k++) {
574 minv[k] = scales[k]*minv[k];
575 maxv[k] = scales[k]*maxv[k];
576 }
577 for (j = 0; j < np; j++)
578 for (k = 0; k < 3; k++)
579 rr(j,k) = rr(j,k)*scales[k];
580 return;
581}
582
583//=============================================================================================================
584
586{
587 Eigen::Vector3f mn = rr.row(0).transpose();
588 Eigen::Vector3f mx = mn;
589
590 for (int k = 1; k < np; k++) {
591 Eigen::Vector3f r = rr.row(k).transpose();
592 mn = mn.cwiseMin(r);
593 mx = mx.cwiseMax(r);
594 }
595
596#ifdef DEBUG
597 printf("%s:\n",tag.toUtf8().constData());
598 printf("\tx = %f ... %f mm\n",1000*mn[0],1000*mx[0]);
599 printf("\ty = %f ... %f mm\n",1000*mn[1],1000*mx[1]);
600 printf("\tz = %f ... %f mm\n",1000*mn[2],1000*mx[2]);
601#endif
602
603 fov = std::max(mn.cwiseAbs().maxCoeff(), mx.cwiseAbs().maxCoeff());
604
605 minv = mn;
606 maxv = mx;
607 fov_scale = 1.1f;
608}
609
610//=============================================================================================================
611
613{
614 if (name.startsWith("inflated") || name.startsWith("sphere") || name.startsWith("white"))
616 else
619}
620
621//=============================================================================================================
622
624{
625 if (np == 0)
626 return;
627
628 const int ncolor = nvertex_colors;
629 const int totalSize = ncolor * np;
630
631 if (vertex_colors.size() == 0)
632 vertex_colors.resize(totalSize);
633
634 float curv_sum = 0.0f;
636 for (int k = 0; k < np; k++) {
637 const int base = k * ncolor;
638 curv_sum += std::fabs(curv[k]);
639 const float c = (curv[k] > 0) ? POS_CURV_COLOR : NEG_CURV_COLOR;
640 for (int j = 0; j < 3; j++)
641 vertex_colors[base + j] = c;
642 if (ncolor == 4)
643 vertex_colors[base + 3] = 1.0f;
644 }
645 }
646 else {
647 for (int k = 0; k < np; k++) {
648 const int base = k * ncolor;
649 curv_sum += std::fabs(curv[k]);
650 for (int j = 0; j < 3; j++)
651 vertex_colors[base + j] = EVEN_CURV_COLOR;
652 if (ncolor == 4)
653 vertex_colors[base + 3] = 1.0f;
654 }
655 }
656#ifdef DEBUG
657 printf("Average curvature : %f\n",curv_sum/np);
658#endif
659}
660
#define M_PI
#define FIFFV_POINT_CARDINAL
#define FIFFV_POINT_RPA
#define FIFFV_COORD_HEAD
#define FIFFV_COORD_MRI
#define FIFFV_MOVE
#define FIFFV_POINT_LPA
#define FIFFV_POINT_HPI
#define FIFFV_POINT_NASION
FiffStream class declaration.
FiffDigitizerData class declaration.
FiffCoordTrans class declaration.
FiffDigPoint class declaration.
Sphere class declaration.
MNEMshPicked class declaration.
MNESurface class declaration.
#define MALLOC_17(x, t)
#define VEC_COPY_17(to, from)
#define Z_17
constexpr float NEG_CURV_COLOR
#define FREE_17(x)
#define Y_17
#define X_17
#define CROSS_PRODUCT_17(x, y, xy)
#define FREE_CMATRIX_17(m)
constexpr float POS_CURV_COLOR
#define VEC_DIFF_17(from, to, diff)
#define VEC_LEN_17(x)
#define ALLOC_CMATRIX_17(x, y)
#define VEC_DOT_17(x, y)
constexpr float EVEN_CURV_COLOR
MNEMshColorScaleDef class declaration.
MNEMshDisplaySurface class declaration.
MNEMorphMap class declaration.
#define TRUE
#define FALSE
#define OK
#define FAIL
#define SHOW_CURVATURE_NONE
#define SHOW_CURVATURE_OVERLAY
#define SHOW_OVERLAY_HEAT
Core MNE data structures (source spaces, source estimates, hemispheres).
FIFF file I/O and data structures (raw, epochs, evoked, covariance, forward).
Coordinate transformation description.
static FiffCoordTrans fromCardinalPoints(int from, int to, const float *rL, const float *rN, const float *rR)
Digitization point description.
Digitization points container and description.
QList< FIFFLIB::FiffDigPoint > points
std::unique_ptr< FiffCoordTrans > head_mri_t_adj
QList< FIFFLIB::FiffDigPoint > mri_fids
void get_head_scale(FIFFLIB::FiffDigitizerData &dig, const Eigen::Matrix< float, 3, 3, Eigen::RowMajor > &mri_fid, Eigen::Vector3f &scales)
float rms_digitizer_distance(FIFFLIB::FiffDigitizerData &dig) const
void decide_surface_extent(const QString &tag)
void calculate_digitizer_distances(FIFFLIB::FiffDigitizerData &dig, int do_all, int do_approx) const
void scale(const Eigen::Vector3f &scales)
int align_fiducials(FIFFLIB::FiffDigitizerData &head_dig, const FIFFLIB::FiffDigitizerData &mri_dig, int niter, int scale_head, float omit_dist, Eigen::Vector3f &scales)
int discard_outlier_digitizer_points(FIFFLIB::FiffDigitizerData &d, float maxdist) const
int iterate_alignment_once(FIFFLIB::FiffDigitizerData &dig, int nasion_weight, const std::optional< Eigen::Vector3f > &nasion_mri, int last_step) const
void decide_curv_display(const QString &name)
double sum_solids(const Eigen::Vector3f &from) const
void find_closest_on_surface_approx(const PointsT &r, int np, Eigen::VectorXi &nearest_tri, Eigen::VectorXf &dist, int nstep) const
Eigen::Vector3f project_to_triangle(int tri, float p, float q) const
Eigen::Matrix< float, Eigen::Dynamic, 3, Eigen::RowMajor > PointsT
static bool fit_sphere_to_points(const Eigen::MatrixXf &rr, float simplex_size, Eigen::VectorXf &r0, float &R)
static FiffCoordTrans procrustesAlign(int from_frame, int to_frame, float **fromp, float **top, float *w, int np, float max_diff)
Eigen::MatrixX3f apply_inverse_trans(const Eigen::MatrixX3f &rr, bool do_move=true) const
Eigen::MatrixX3f apply_trans(const Eigen::MatrixX3f &rr, bool do_move=true) const