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 <math/sphere.h>
52
53#include <Eigen/Geometry>
54
55#define _USE_MATH_DEFINES
56#include <math.h>
57#include <QDebug>
58
59//=============================================================================================================
60// USED NAMESPACES
61//=============================================================================================================
62
63using namespace Eigen;
64using namespace FIFFLIB;
65using namespace MNELIB;
66
67//============================= local macros =================================
68
69constexpr int FAIL = -1;
70constexpr int OK = 0;
71
72constexpr int X = 0;
73constexpr int Y = 1;
74constexpr int Z = 2;
75
76constexpr int SHOW_CURVATURE_NONE = 0;
77constexpr int SHOW_CURVATURE_OVERLAY = 1;
78constexpr int SHOW_OVERLAY_HEAT = 1;
79
80constexpr float POS_CURV_COLOR = 0.25f;
81constexpr float NEG_CURV_COLOR = 0.375f;
82constexpr float EVEN_CURV_COLOR = 0.375f;
83
84//=============================================================================================================
85// DEFINE MEMBER METHODS
86//=============================================================================================================
87
89
90//=============================================================================================================
91
97
98//=============================================================================================================
99// Alignment functions (moved from MNESurfaceOrVolume)
100//=============================================================================================================
101
103 const FiffDigitizerData& mri_dig,
104 int niter,
105 int scale_head,
106 float omit_dist,
107 Eigen::Vector3f& scales)
108
109{
110 using FidMatrix = Eigen::Matrix<float, 3, 3, Eigen::RowMajor>;
111 FidMatrix head_fid, mri_fid;
112 bool head_fid_found[3] = {false, false, false};
113 bool mri_fid_found[3] = {false, false, false};
114 int j,k;
115 FiffDigPoint p;
116 float nasion_weight = 5.0;
117
118 for (j = 0; j < 2; j++) {
119 const FiffDigitizerData& d = (j == 0) ? head_dig : mri_dig;
120 FidMatrix& fid = (j == 0) ? head_fid : mri_fid;
121 bool* found = (j == 0) ? head_fid_found : mri_fid_found;
122
123 for (k = 0; k < d.npoint; k++) {
124 p = d.points[k];
125 if (p.kind == FIFFV_POINT_CARDINAL) {
126 if (p.ident == FIFFV_POINT_LPA) {
127 fid.row(0) = Eigen::Map<const Eigen::RowVector3f>(d.points[k].r);
128 found[0] = true;
129 }
130 else if (p.ident == FIFFV_POINT_NASION) {
131 fid.row(1) = Eigen::Map<const Eigen::RowVector3f>(d.points[k].r);
132 found[1] = true;
133 }
134 else if (p.ident == FIFFV_POINT_RPA) {
135 fid.row(2) = Eigen::Map<const Eigen::RowVector3f>(d.points[k].r);
136 found[2] = true;
137 }
138 }
139 }
140 }
141
142 for (k = 0; k < 3; k++) {
143 if (!head_fid_found[k]) {
144 qCritical("Some of the MEG fiducials were missing");
145 return FAIL;
146 }
147
148 if (!mri_fid_found[k]) {
149 qCritical("Some of the MRI fiducials were missing");
150 return FAIL;
151 }
152 }
153
154 if (scale_head) {
155 get_head_scale(head_dig,mri_fid,scales);
156 qInfo("xscale = %.3f yscale = %.3f zscale = %.3f\n",scales[0],scales[1],scales[2]);
157
158 for (j = 0; j < 3; j++)
159 for (k = 0; k < 3; k++)
160 mri_fid(j,k) = mri_fid(j,k)*scales[k];
161
162 scale(scales);
163 }
164
165 // Initial alignment
167 mri_fid.row(0).data(),mri_fid.row(1).data(),mri_fid.row(2).data()));
168
169 // Populate mri_fids from cardinal digitizer points transformed into MRI coords
170 head_dig.pickCardinalFiducials();
171
172 // Overwrite the fiducial locations with the ones from the MRI digitizer data
173 for (k = 0; k < head_dig.nfids(); k++)
174 Eigen::Map<Eigen::Vector3f>(head_dig.mri_fids[k].r) = mri_fid.row(k).transpose();
175 head_dig.head_mri_t_adj->print();
176 qInfo("After simple alignment : \n");
177
178 if (omit_dist > 0)
179 discard_outlier_digitizer_points(head_dig,omit_dist);
180
181 // Optional iterative refinement
182 if (niter > 0) {
183 for (k = 0; k < niter; k++) {
184 if (iterate_alignment_once(head_dig,nasion_weight,Eigen::Vector3f(mri_fid.row(1).transpose()),k == niter-1 && niter > 1) == FAIL)
185 return FAIL;
186 }
187
188 qInfo("%d / %d iterations done. RMS dist = %7.1f mm\n",k,niter,
189 1000.0*rms_digitizer_distance(head_dig));
190 qInfo("After refinement :\n");
191 head_dig.head_mri_t_adj->print();
192 }
193
194 return OK;
195}
196
197//=============================================================================================================
198
199// Simple head size fit
201 const Eigen::Matrix<float, 3, 3, Eigen::RowMajor>& mri_fid,
202 Eigen::Vector3f& scales)
203{
204 int k,ndig,nhead;
205 float simplex_size = 2e-2;
206 Eigen::VectorXf r0(3);
207 float Rdig,Rscalp;
208
209 scales[0] = scales[1] = scales[2] = 1.0;
210
211 Eigen::MatrixXf dig_rr(dig.npoint, 3);
212 Eigen::MatrixXf head_rr(np, 3);
213
214 // Pick only the points with positive z
215 for (k = 0, ndig = 0; k < dig.npoint; k++) {
216 if (dig.points[k].r[2] > 0) {
217 dig_rr.row(ndig++) = Eigen::Map<const Eigen::RowVector3f>(dig.points[k].r);
218 }
219 }
220
221 if (!UTILSLIB::Sphere::fit_sphere_to_points(dig_rr.topRows(ndig),simplex_size,r0,Rdig)){
222 return;
223 }
224
225 qInfo("Polhemus : (%.1f %.1f %.1f) mm R = %.1f mm\n",1000*r0[0],1000*r0[1],1000*r0[2],1000*Rdig);
226
227 // Pick only the points above the fiducial plane
228 Eigen::Vector3f LR = mri_fid.row(2).transpose() - mri_fid.row(0).transpose();
229 Eigen::Vector3f LN = mri_fid.row(1).transpose() - mri_fid.row(0).transpose();
230 Eigen::Vector3f norm = LR.cross(LN);
231 norm.normalize();
232
233 for (k = 0, nhead = 0; k < np; k++) {
234 Eigen::Vector3f diff_vec = rr.row(k).transpose() - mri_fid.row(0).transpose();
235 if (diff_vec.dot(norm) > 0) {
236 head_rr.row(nhead++) = rr.row(k);
237 }
238 }
239
240 if (!UTILSLIB::Sphere::fit_sphere_to_points(head_rr.topRows(nhead),simplex_size,r0,Rscalp)) {
241 return;
242 }
243
244 qInfo("Scalp : (%.1f %.1f %.1f) mm R = %.1f mm\n",1000*r0[0],1000*r0[1],1000*r0[2],1000*Rscalp);
245
246 scales[0] = scales[1] = scales[2] = Rdig/Rscalp;
247}
248
249//=============================================================================================================
250
252 float maxdist) const
253/*
254 * Discard outlier digitizer points
255 */
256{
257 int discarded = 0;
258 int k;
259
260 d.dist_valid = false;
262 for (k = 0; k < d.npoint; k++) {
263 d.discard[k] = 0;
264 /*
265 * Discard unless cardinal landmark or HPI coil
266 */
267 if (std::fabs(d.dist(k)) > maxdist &&
268 d.points[k].kind != FIFFV_POINT_CARDINAL &&
269 d.points[k].kind != FIFFV_POINT_HPI) {
270 discarded++;
271 d.discard[k] = 1;
272 }
273 }
274 qInfo("%d points discarded (maxdist = %6.1f mm).\n",discarded,1000*maxdist);
275
276 return discarded;
277}
278
279//=============================================================================================================
280
282 bool do_all, bool do_approx) const
283/*
284 * Calculate the distances from the scalp surface
285 */
286{
287 int k,nactive;
289 Q_ASSERT(dig.head_mri_t);
290 const FiffCoordTrans& t = (dig.head_mri_t_adj && !dig.head_mri_t_adj->isEmpty()) ? *dig.head_mri_t_adj : *dig.head_mri_t;
291 int nstep = 4;
292
293 if (dig.dist_valid)
294 return ;
295
296 PointsT rr(dig.npoint, 3);
297
298 dig.dist.conservativeResize(dig.npoint);
299 if (dig.closest.size() == 0) {
300 /*
301 * Ensure that all closest values are initialized correctly
302 */
303 dig.closest = Eigen::VectorXi::Constant(dig.npoint, -1);
304 }
305
306 dig.closest_point.setZero(dig.npoint,3);
307 Eigen::VectorXi closest(dig.npoint);
308 Eigen::VectorXf dists(dig.npoint);
309
310 for (k = 0, nactive = 0; k < dig.npoint; k++) {
311 if ((dig.active[k] && !dig.discard[k]) || do_all) {
312 point = dig.points.at(k);
313 rr.row(nactive) = Eigen::Map<const Eigen::RowVector3f>(point.r);
314 FiffCoordTrans::apply_trans(rr.row(nactive).data(),t,FIFFV_MOVE);
315 if (do_approx) {
316 closest[nactive] = dig.closest(k);
317 if (closest[nactive] < 0)
318 do_approx = false;
319 }
320 else
321 closest[nactive] = -1;
322 nactive++;
323 }
324 }
325
326 find_closest_on_surface_approx(rr,nactive,closest,dists,nstep);
327 /*
328 * Project the points on the triangles
329 */
330 if (!do_approx)
331 qInfo("Inside or outside for %d points...",nactive);
332 for (k = 0, nactive = 0; k < dig.npoint; k++) {
333 if ((dig.active[k] && !dig.discard[k]) || do_all) {
334 dig.dist(k) = dists[nactive];
335 dig.closest(k) = closest[nactive];
336 {
337 Eigen::Vector3f pt = Eigen::Map<const Eigen::Vector3f>(rr.row(nactive).data());
338 Eigen::Vector3f proj = project_to_triangle(dig.closest(k),pt);
339 dig.closest_point.row(k) = proj.transpose();
340 }
341 /*
342 * The above distance is with respect to the closest triangle only
343 * We need to use the solid angle criterion to decide the sign reliably
344 */
345 if (!do_approx && false) {
346 Eigen::Vector3f pt = Eigen::Map<const Eigen::Vector3f>(rr.row(nactive).data());
347 if (sum_solids(pt)/(4*M_PI) > 0.9)
348 dig.dist(k) = - std::fabs(dig.dist(k));
349 else
350 dig.dist(k) = std::fabs(dig.dist(k));
351 }
352 nactive++;
353 }
354 }
355
356 if (!do_approx)
357 qInfo("[done]\n");
358
359 dig.dist_valid = true;
360
361 return;
362}
363
364//=============================================================================================================
365
367 int nasion_weight, /* Weight for the nasion */
368 const std::optional<Eigen::Vector3f>& nasion_mri, /* Fixed correspondence point for the nasion (optional) */
369 int last_step) const /* Is this the last iteration step */
370/*
371 * Find the best alignment of the coordinate frames
372 */
373{
374 Eigen::MatrixXf rr_head(dig.npoint, 3);
375 Eigen::MatrixXf rr_mri(dig.npoint, 3);
376 Eigen::VectorXf w = Eigen::VectorXf::Zero(dig.npoint);
377 int k,nactive;
380 float max_diff = 40e-3;
381
382 if (!dig.head_mri_t_adj) {
383 qCritical()<<"Not adjusting the transformation";
384 return FAIL;
385 }
386 /*
387 * Calculate initial distances
388 */
389 calculate_digitizer_distances(dig,false,true);
390
391 for (k = 0, nactive = 0; k < dig.npoint; k++) {
392 if (dig.active[k] && !dig.discard[k]) {
393 point = dig.points.at(k);
394 rr_head.row(nactive) = Eigen::Map<const Eigen::RowVector3f>(point.r);
395 rr_mri.row(nactive) = dig.closest_point.row(k);
396 /*
397 * Special handling for the nasion
398 */
399 if (point.kind == FIFFV_POINT_CARDINAL &&
400 point.ident == FIFFV_POINT_NASION) {
401 w[nactive] = nasion_weight;
402 if (nasion_mri) {
403 rr_mri.row(nactive) = nasion_mri->transpose();
404 rr_head.row(nactive) = nasion_mri->transpose();
405 Q_ASSERT(dig.head_mri_t || dig.head_mri_t_adj);
406 FiffCoordTrans::apply_inverse_trans(rr_head.row(nactive).data(),
407 dig.head_mri_t_adj ? *dig.head_mri_t_adj : *dig.head_mri_t,
408 FIFFV_MOVE);
409 }
410 }
411 else
412 w[nactive] = 1.0;
413 nactive++;
414 }
415 }
416 if (nactive < 3) {
417 qCritical() << "Not enough points to do the alignment";
418 return FAIL;
419 }
421 rr_head.topRows(nactive),
422 rr_mri.topRows(nactive),
423 w.head(nactive),
424 max_diff)).isEmpty())
425 return FAIL;
426
427 if (dig.head_mri_t_adj)
428 dig.head_mri_t_adj = std::make_unique<FiffCoordTrans>(t);
429 /*
430 * Calculate final distances
431 */
432 dig.dist_valid = false;
433 calculate_digitizer_distances(dig,false,!last_step);
434 return OK;
435}
436
437//=============================================================================================================
438
440{
441 float rms;
442 int k,nactive;
443
444 calculate_digitizer_distances(dig,false,true);
445
446 for (k = 0, rms = 0.0, nactive = 0; k < dig.npoint; k++)
447 if (dig.active[k] && !dig.discard[k]) {
448 rms = rms + dig.dist(k)*dig.dist(k);
449 nactive++;
450 }
451 if (nactive > 1)
452 rms = rms/(nactive-1);
453 return sqrt(rms);
454}
455
456//=============================================================================================================
457
458void MNEMshDisplaySurface::scale(const Eigen::Vector3f& scales)
459/*
460 * Not quite complete yet
461 */
462{
463 int j,k;
464
465 for (k = 0; k < 3; k++) {
466 minv[k] = scales[k]*minv[k];
467 maxv[k] = scales[k]*maxv[k];
468 }
469 for (j = 0; j < np; j++)
470 for (k = 0; k < 3; k++)
471 rr(j,k) = rr(j,k)*scales[k];
472 return;
473}
474
475//=============================================================================================================
476
478{
479 Eigen::Vector3f mn = rr.row(0).transpose();
480 Eigen::Vector3f mx = mn;
481
482 for (int k = 1; k < np; k++) {
483 Eigen::Vector3f r = rr.row(k).transpose();
484 mn = mn.cwiseMin(r);
485 mx = mx.cwiseMax(r);
486 }
487
488#ifdef DEBUG
489 qInfo("%s:\n",tag.toUtf8().constData());
490 qInfo("\tx = %f ... %f mm\n",1000*mn[0],1000*mx[0]);
491 qInfo("\ty = %f ... %f mm\n",1000*mn[1],1000*mx[1]);
492 qInfo("\tz = %f ... %f mm\n",1000*mn[2],1000*mx[2]);
493#endif
494
495 fov = std::max(mn.cwiseAbs().maxCoeff(), mx.cwiseAbs().maxCoeff());
496
497 minv = mn;
498 maxv = mx;
499 fov_scale = 1.1f;
500}
501
502//=============================================================================================================
503
505{
506 if (name.startsWith("inflated") || name.startsWith("sphere") || name.startsWith("white"))
508 else
511}
512
513//=============================================================================================================
514
516{
517 if (np == 0)
518 return;
519
520 const int ncolor = nvertex_colors;
521 const int totalSize = ncolor * np;
522
523 if (vertex_colors.size() == 0)
524 vertex_colors.resize(totalSize);
525
526 float curv_sum = 0.0f;
528 for (int k = 0; k < np; k++) {
529 const int base = k * ncolor;
530 curv_sum += std::fabs(curv[k]);
531 const float c = (curv[k] > 0) ? POS_CURV_COLOR : NEG_CURV_COLOR;
532 for (int j = 0; j < 3; j++)
533 vertex_colors[base + j] = c;
534 if (ncolor == 4)
535 vertex_colors[base + 3] = 1.0f;
536 }
537 }
538 else {
539 for (int k = 0; k < np; k++) {
540 const int base = k * ncolor;
541 curv_sum += std::fabs(curv[k]);
542 for (int j = 0; j < 3; j++)
543 vertex_colors[base + j] = EVEN_CURV_COLOR;
544 if (ncolor == 4)
545 vertex_colors[base + 3] = 1.0f;
546 }
547 }
548#ifdef DEBUG
549 qInfo("Average curvature : %f\n",curv_sum/np);
550#endif
551}
552
#define M_PI
Sphere class declaration.
constexpr int FAIL
constexpr int Y
constexpr int Z
constexpr int OK
constexpr int X
FiffDigitizerData class declaration.
FiffCoordTrans class declaration.
FiffStream class declaration.
#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
FiffDigPoint class declaration.
constexpr float NEG_CURV_COLOR
constexpr int SHOW_CURVATURE_OVERLAY
constexpr float POS_CURV_COLOR
constexpr int SHOW_CURVATURE_NONE
constexpr int SHOW_OVERLAY_HEAT
constexpr float EVEN_CURV_COLOR
MNEMorphMap class declaration.
MNEMshColorScaleDef class declaration.
MNEMshDisplaySurface class declaration.
MNEMshPicked class declaration.
MNESurface class declaration.
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
static bool fit_sphere_to_points(const Eigen::MatrixXf &rr, float simplex_size, Eigen::VectorXf &r0, float &R)
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 scale(const Eigen::Vector3f &scales)
void calculate_digitizer_distances(FIFFLIB::FiffDigitizerData &dig, bool do_all, bool do_approx) const
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
Eigen::Map< const Eigen::Vector3f > point(int k) const
static FiffCoordTrans procrustesAlign(int from_frame, int to_frame, const Eigen::MatrixXf &fromp, const Eigen::MatrixXf &top, const Eigen::VectorXf &w, 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