v2.0.0
Loading...
Searching...
No Matches
mne_msh_display_surface.cpp
Go to the documentation of this file.
1//=============================================================================================================
16
17//=============================================================================================================
18// INCLUDES
19//=============================================================================================================
20
22#include "mne_surface.h"
23#include "mne_morph_map.h"
24#include "mne_msh_picked.h"
26
27#include <fiff/fiff_stream.h>
30#include <fiff/fiff_dig_point.h>
31
32#include <math/sphere.h>
33
34#include <Eigen/Geometry>
35
36#define _USE_MATH_DEFINES
37#include <math.h>
38#include <QDebug>
39
40//=============================================================================================================
41// USED NAMESPACES
42//=============================================================================================================
43
44using namespace Eigen;
45using namespace FIFFLIB;
46using namespace MNELIB;
47
48//============================= local macros =================================
49
50constexpr int FAIL = -1;
51constexpr int OK = 0;
52
53constexpr int X = 0;
54constexpr int Y = 1;
55constexpr int Z = 2;
56
57constexpr int SHOW_CURVATURE_NONE = 0;
58constexpr int SHOW_CURVATURE_OVERLAY = 1;
59constexpr int SHOW_OVERLAY_HEAT = 1;
60
61constexpr float POS_CURV_COLOR = 0.25f;
62constexpr float NEG_CURV_COLOR = 0.375f;
63constexpr float EVEN_CURV_COLOR = 0.375f;
64
65//=============================================================================================================
66// DEFINE MEMBER METHODS
67//=============================================================================================================
68
70
71//=============================================================================================================
72
78
79//=============================================================================================================
80// Alignment functions (moved from MNESurfaceOrVolume)
81//=============================================================================================================
82
84 const FiffDigitizerData& mri_dig,
85 int niter,
86 int scale_head,
87 float omit_dist,
88 Eigen::Vector3f& scales)
89
90{
91 using FidMatrix = Eigen::Matrix<float, 3, 3, Eigen::RowMajor>;
92 FidMatrix head_fid, mri_fid;
93 bool head_fid_found[3] = {false, false, false};
94 bool mri_fid_found[3] = {false, false, false};
95 int j,k;
97 float nasion_weight = 5.0;
98
99 for (j = 0; j < 2; j++) {
100 const FiffDigitizerData& d = (j == 0) ? head_dig : mri_dig;
101 FidMatrix& fid = (j == 0) ? head_fid : mri_fid;
102 bool* found = (j == 0) ? head_fid_found : mri_fid_found;
103
104 for (k = 0; k < d.npoint; k++) {
105 p = d.points[k];
106 if (p.kind == FIFFV_POINT_CARDINAL) {
107 if (p.ident == FIFFV_POINT_LPA) {
108 fid.row(0) = Eigen::Map<const Eigen::RowVector3f>(d.points[k].r);
109 found[0] = true;
110 }
111 else if (p.ident == FIFFV_POINT_NASION) {
112 fid.row(1) = Eigen::Map<const Eigen::RowVector3f>(d.points[k].r);
113 found[1] = true;
114 }
115 else if (p.ident == FIFFV_POINT_RPA) {
116 fid.row(2) = Eigen::Map<const Eigen::RowVector3f>(d.points[k].r);
117 found[2] = true;
118 }
119 }
120 }
121 }
122
123 for (k = 0; k < 3; k++) {
124 if (!head_fid_found[k]) {
125 qCritical("Some of the MEG fiducials were missing");
126 return FAIL;
127 }
128
129 if (!mri_fid_found[k]) {
130 qCritical("Some of the MRI fiducials were missing");
131 return FAIL;
132 }
133 }
134
135 if (scale_head) {
136 get_head_scale(head_dig,mri_fid,scales);
137 qInfo("xscale = %.3f yscale = %.3f zscale = %.3f\n",scales[0],scales[1],scales[2]);
138
139 for (j = 0; j < 3; j++)
140 for (k = 0; k < 3; k++)
141 mri_fid(j,k) = mri_fid(j,k)*scales[k];
142
143 scale(scales);
144 }
145
146 // Initial alignment
148 mri_fid.row(0).data(),mri_fid.row(1).data(),mri_fid.row(2).data()));
149
150 // Populate mri_fids from cardinal digitizer points transformed into MRI coords
151 head_dig.pickCardinalFiducials();
152
153 // Overwrite the fiducial locations with the ones from the MRI digitizer data
154 for (k = 0; k < head_dig.nfids(); k++)
155 Eigen::Map<Eigen::Vector3f>(head_dig.mri_fids[k].r) = mri_fid.row(k).transpose();
156 head_dig.head_mri_t_adj->print();
157 qInfo("After simple alignment : \n");
158
159 if (omit_dist > 0)
160 discard_outlier_digitizer_points(head_dig,omit_dist);
161
162 // Optional iterative refinement
163 if (niter > 0) {
164 for (k = 0; k < niter; k++) {
165 if (iterate_alignment_once(head_dig,nasion_weight,Eigen::Vector3f(mri_fid.row(1).transpose()),k == niter-1 && niter > 1) == FAIL)
166 return FAIL;
167 }
168
169 qInfo("%d / %d iterations done. RMS dist = %7.1f mm\n",k,niter,
170 1000.0*rms_digitizer_distance(head_dig));
171 qInfo("After refinement :\n");
172 head_dig.head_mri_t_adj->print();
173 }
174
175 return OK;
176}
177
178//=============================================================================================================
179
180// Simple head size fit
182 const Eigen::Matrix<float, 3, 3, Eigen::RowMajor>& mri_fid,
183 Eigen::Vector3f& scales)
184{
185 int k,ndig,nhead;
186 float simplex_size = 2e-2;
187 Eigen::VectorXf r0(3);
188 float Rdig,Rscalp;
189
190 scales[0] = scales[1] = scales[2] = 1.0;
191
192 Eigen::MatrixXf dig_rr(dig.npoint, 3);
193 Eigen::MatrixXf head_rr(np, 3);
194
195 // Pick only the points with positive z
196 for (k = 0, ndig = 0; k < dig.npoint; k++) {
197 if (dig.points[k].r[2] > 0) {
198 dig_rr.row(ndig++) = Eigen::Map<const Eigen::RowVector3f>(dig.points[k].r);
199 }
200 }
201
202 if (!UTILSLIB::Sphere::fit_sphere_to_points(dig_rr.topRows(ndig),simplex_size,r0,Rdig)){
203 return;
204 }
205
206 qInfo("Polhemus : (%.1f %.1f %.1f) mm R = %.1f mm\n",1000*r0[0],1000*r0[1],1000*r0[2],1000*Rdig);
207
208 // Pick only the points above the fiducial plane
209 Eigen::Vector3f LR = mri_fid.row(2).transpose() - mri_fid.row(0).transpose();
210 Eigen::Vector3f LN = mri_fid.row(1).transpose() - mri_fid.row(0).transpose();
211 Eigen::Vector3f norm = LR.cross(LN);
212 norm.normalize();
213
214 for (k = 0, nhead = 0; k < np; k++) {
215 Eigen::Vector3f diff_vec = rr.row(k).transpose() - mri_fid.row(0).transpose();
216 if (diff_vec.dot(norm) > 0) {
217 head_rr.row(nhead++) = rr.row(k);
218 }
219 }
220
221 if (!UTILSLIB::Sphere::fit_sphere_to_points(head_rr.topRows(nhead),simplex_size,r0,Rscalp)) {
222 return;
223 }
224
225 qInfo("Scalp : (%.1f %.1f %.1f) mm R = %.1f mm\n",1000*r0[0],1000*r0[1],1000*r0[2],1000*Rscalp);
226
227 scales[0] = scales[1] = scales[2] = Rdig/Rscalp;
228}
229
230//=============================================================================================================
231
233 float maxdist) const
234/*
235 * Discard outlier digitizer points
236 */
237{
238 int discarded = 0;
239 int k;
240
241 d.dist_valid = false;
243 for (k = 0; k < d.npoint; k++) {
244 d.discard[k] = 0;
245 /*
246 * Discard unless cardinal landmark or HPI coil
247 */
248 if (std::fabs(d.dist(k)) > maxdist &&
249 d.points[k].kind != FIFFV_POINT_CARDINAL &&
250 d.points[k].kind != FIFFV_POINT_HPI) {
251 discarded++;
252 d.discard[k] = 1;
253 }
254 }
255 qInfo("%d points discarded (maxdist = %6.1f mm).\n",discarded,1000*maxdist);
256
257 return discarded;
258}
259
260//=============================================================================================================
261
263 bool do_all, bool do_approx) const
264/*
265 * Calculate the distances from the scalp surface
266 */
267{
268 int k,nactive;
270 Q_ASSERT(dig.head_mri_t);
271 const FiffCoordTrans& t = (dig.head_mri_t_adj && !dig.head_mri_t_adj->isEmpty()) ? *dig.head_mri_t_adj : *dig.head_mri_t;
272 int nstep = 4;
273
274 if (dig.dist_valid)
275 return ;
276
277 PointsT rr(dig.npoint, 3);
278
279 dig.dist.conservativeResize(dig.npoint);
280 if (dig.closest.size() == 0) {
281 /*
282 * Ensure that all closest values are initialized correctly
283 */
284 dig.closest = Eigen::VectorXi::Constant(dig.npoint, -1);
285 }
286
287 dig.closest_point.setZero(dig.npoint,3);
288 Eigen::VectorXi closest(dig.npoint);
289 Eigen::VectorXf dists(dig.npoint);
290
291 for (k = 0, nactive = 0; k < dig.npoint; k++) {
292 if ((dig.active[k] && !dig.discard[k]) || do_all) {
293 point = dig.points.at(k);
294 rr.row(nactive) = Eigen::Map<const Eigen::RowVector3f>(point.r);
295 FiffCoordTrans::apply_trans(rr.row(nactive).data(),t,FIFFV_MOVE);
296 if (do_approx) {
297 closest[nactive] = dig.closest(k);
298 if (closest[nactive] < 0)
299 do_approx = false;
300 }
301 else
302 closest[nactive] = -1;
303 nactive++;
304 }
305 }
306
307 find_closest_on_surface_approx(rr,nactive,closest,dists,nstep);
308 /*
309 * Project the points on the triangles
310 */
311 if (!do_approx)
312 qInfo("Inside or outside for %d points...",nactive);
313 for (k = 0, nactive = 0; k < dig.npoint; k++) {
314 if ((dig.active[k] && !dig.discard[k]) || do_all) {
315 dig.dist(k) = dists[nactive];
316 dig.closest(k) = closest[nactive];
317 {
318 Eigen::Vector3f pt = Eigen::Map<const Eigen::Vector3f>(rr.row(nactive).data());
319 Eigen::Vector3f proj = project_to_triangle(dig.closest(k),pt);
320 dig.closest_point.row(k) = proj.transpose();
321 }
322 /*
323 * The above distance is with respect to the closest triangle only
324 * We need to use the solid angle criterion to decide the sign reliably
325 */
326 if (!do_approx && false) {
327 Eigen::Vector3f pt = Eigen::Map<const Eigen::Vector3f>(rr.row(nactive).data());
328 if (sum_solids(pt)/(4*M_PI) > 0.9)
329 dig.dist(k) = - std::fabs(dig.dist(k));
330 else
331 dig.dist(k) = std::fabs(dig.dist(k));
332 }
333 nactive++;
334 }
335 }
336
337 if (!do_approx)
338 qInfo("[done]\n");
339
340 dig.dist_valid = true;
341
342 return;
343}
344
345//=============================================================================================================
346
348 int nasion_weight, /* Weight for the nasion */
349 const std::optional<Eigen::Vector3f>& nasion_mri, /* Fixed correspondence point for the nasion (optional) */
350 int last_step) const /* Is this the last iteration step */
351/*
352 * Find the best alignment of the coordinate frames
353 */
354{
355 Eigen::MatrixXf rr_head(dig.npoint, 3);
356 Eigen::MatrixXf rr_mri(dig.npoint, 3);
357 Eigen::VectorXf w = Eigen::VectorXf::Zero(dig.npoint);
358 int k,nactive;
361 float max_diff = 40e-3;
362
363 if (!dig.head_mri_t_adj) {
364 qCritical()<<"Not adjusting the transformation";
365 return FAIL;
366 }
367 /*
368 * Calculate initial distances
369 */
370 calculate_digitizer_distances(dig,false,true);
371
372 for (k = 0, nactive = 0; k < dig.npoint; k++) {
373 if (dig.active[k] && !dig.discard[k]) {
374 point = dig.points.at(k);
375 rr_head.row(nactive) = Eigen::Map<const Eigen::RowVector3f>(point.r);
376 rr_mri.row(nactive) = dig.closest_point.row(k);
377 /*
378 * Special handling for the nasion
379 */
380 if (point.kind == FIFFV_POINT_CARDINAL &&
381 point.ident == FIFFV_POINT_NASION) {
382 w[nactive] = nasion_weight;
383 if (nasion_mri) {
384 rr_mri.row(nactive) = nasion_mri->transpose();
385 rr_head.row(nactive) = nasion_mri->transpose();
386 Q_ASSERT(dig.head_mri_t || dig.head_mri_t_adj);
387 FiffCoordTrans::apply_inverse_trans(rr_head.row(nactive).data(),
388 dig.head_mri_t_adj ? *dig.head_mri_t_adj : *dig.head_mri_t,
389 FIFFV_MOVE);
390 }
391 }
392 else
393 w[nactive] = 1.0;
394 nactive++;
395 }
396 }
397 if (nactive < 3) {
398 qCritical() << "Not enough points to do the alignment";
399 return FAIL;
400 }
402 rr_head.topRows(nactive),
403 rr_mri.topRows(nactive),
404 w.head(nactive),
405 max_diff)).isEmpty())
406 return FAIL;
407
408 if (dig.head_mri_t_adj)
409 dig.head_mri_t_adj = std::make_unique<FiffCoordTrans>(t);
410 /*
411 * Calculate final distances
412 */
413 dig.dist_valid = false;
414 calculate_digitizer_distances(dig,false,!last_step);
415 return OK;
416}
417
418//=============================================================================================================
419
421{
422 float rms;
423 int k,nactive;
424
425 calculate_digitizer_distances(dig,false,true);
426
427 for (k = 0, rms = 0.0, nactive = 0; k < dig.npoint; k++)
428 if (dig.active[k] && !dig.discard[k]) {
429 rms = rms + dig.dist(k)*dig.dist(k);
430 nactive++;
431 }
432 if (nactive > 1)
433 rms = rms/(nactive-1);
434 return sqrt(rms);
435}
436
437//=============================================================================================================
438
439void MNEMshDisplaySurface::scale(const Eigen::Vector3f& scales)
440/*
441 * Not quite complete yet
442 */
443{
444 int j,k;
445
446 for (k = 0; k < 3; k++) {
447 minv[k] = scales[k]*minv[k];
448 maxv[k] = scales[k]*maxv[k];
449 }
450 for (j = 0; j < np; j++)
451 for (k = 0; k < 3; k++)
452 rr(j,k) = rr(j,k)*scales[k];
453 return;
454}
455
456//=============================================================================================================
457
459{
460 Eigen::Vector3f mn = rr.row(0).transpose();
461 Eigen::Vector3f mx = mn;
462
463 for (int k = 1; k < np; k++) {
464 Eigen::Vector3f r = rr.row(k).transpose();
465 mn = mn.cwiseMin(r);
466 mx = mx.cwiseMax(r);
467 }
468
469#ifdef DEBUG
470 qInfo("%s:\n",tag.toUtf8().constData());
471 qInfo("\tx = %f ... %f mm\n",1000*mn[0],1000*mx[0]);
472 qInfo("\ty = %f ... %f mm\n",1000*mn[1],1000*mx[1]);
473 qInfo("\tz = %f ... %f mm\n",1000*mn[2],1000*mx[2]);
474#endif
475
476 fov = std::max(mn.cwiseAbs().maxCoeff(), mx.cwiseAbs().maxCoeff());
477
478 minv = mn;
479 maxv = mx;
480 fov_scale = 1.1f;
481}
482
483//=============================================================================================================
484
486{
487 if (name.startsWith("inflated") || name.startsWith("sphere") || name.startsWith("white"))
489 else
492}
493
494//=============================================================================================================
495
497{
498 if (np == 0)
499 return;
500
501 const int ncolor = nvertex_colors;
502 const int totalSize = ncolor * np;
503
504 if (vertex_colors.size() == 0)
505 vertex_colors.resize(totalSize);
506
507 float curv_sum = 0.0f;
509 for (int k = 0; k < np; k++) {
510 const int base = k * ncolor;
511 curv_sum += std::fabs(curv[k]);
512 const float c = (curv[k] > 0) ? POS_CURV_COLOR : NEG_CURV_COLOR;
513 for (int j = 0; j < 3; j++)
514 vertex_colors[base + j] = c;
515 if (ncolor == 4)
516 vertex_colors[base + 3] = 1.0f;
517 }
518 }
519 else {
520 for (int k = 0; k < np; k++) {
521 const int base = k * ncolor;
522 curv_sum += std::fabs(curv[k]);
523 for (int j = 0; j < 3; j++)
524 vertex_colors[base + j] = EVEN_CURV_COLOR;
525 if (ncolor == 4)
526 vertex_colors[base + 3] = 1.0f;
527 }
528 }
529#ifdef DEBUG
530 qInfo("Average curvature : %f\n",curv_sum/np);
531#endif
532}
533
constexpr int FAIL
constexpr int Y
constexpr int Z
constexpr int OK
constexpr int X
Lightweight triangulated surface (vertices, triangles, normals) used by surface-based routines.
Sparse linear operator that morphs a source estimate between two subjects.
One renderable surface (cortex / pial / inflated / BEM) inside an MSH display set.
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
Mesh-display colour scale definition (FreeSurfer mri_glm style).
Picked vertices / triangles on a mesh display surface.
#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
High-level digitization data: dig points plus the device→head transform and fitting metadata that tog...
4x4 affine FIFF coordinate transform (FIFF_COORD_TRANS) annotated with source/destination coordinate-...
FIFF binary tag-stream layer: wraps a QIODevice to read and write FIFF tags, directories,...
Single digitization point (FIFF_DIG_POINT) with kind (cardinal/HPI/EEG/extra), identifier and 3D coor...
Best-fit sphere from a 3-D point cloud with closed-form and Nelder–Mead solvers.
Core MNE data structures (source spaces, source estimates, hemispheres).
FIFF file I/O, in-memory data structures and high-level readers/writers.
Labelled 4x4 FIFF affine: source frame, destination frame, rotation, translation and cached inverse.
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
static FiffCoordTrans fromCardinalPoints(int from, int to, const float *rL, const float *rN, const float *rR)
One digitizer point: kind (cardinal/HPI/EEG/extra), ident, 3D position in FIFFV_COORD_HEAD.
Registration-ready digitization data: dig points, device→head transform and HPI fit metadata.
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