34#include <Eigen/Geometry>
38#ifndef _USE_MATH_DEFINES
39#define _USE_MATH_DEFINES
58[[maybe_unused]]
constexpr int X = 0;
59[[maybe_unused]]
constexpr int Y = 1;
60[[maybe_unused]]
constexpr int Z = 2;
93 Eigen::Vector3f& scales)
96 using FidMatrix = Eigen::Matrix<float, 3, 3, Eigen::RowMajor>;
97 FidMatrix head_fid, mri_fid;
98 bool head_fid_found[3] = {
false,
false,
false};
99 bool mri_fid_found[3] = {
false,
false,
false};
102 float nasion_weight = 5.0;
104 for (j = 0; j < 2; j++) {
106 FidMatrix& fid = (j == 0) ? head_fid : mri_fid;
107 bool* found = (j == 0) ? head_fid_found : mri_fid_found;
109 for (k = 0; k < d.
npoint; k++) {
113 fid.row(0) = Eigen::Map<const Eigen::RowVector3f>(d.
points[k].r);
116 fid.row(1) = Eigen::Map<const Eigen::RowVector3f>(d.
points[k].r);
119 fid.row(2) = Eigen::Map<const Eigen::RowVector3f>(d.
points[k].r);
126 for (k = 0; k < 3; k++) {
127 if (!head_fid_found[k]) {
128 qCritical(
"Some of the MEG fiducials were missing");
132 if (!mri_fid_found[k]) {
133 qCritical(
"Some of the MRI fiducials were missing");
140 qInfo(
"xscale = %.3f yscale = %.3f zscale = %.3f\n", scales[0], scales[1], scales[2]);
142 for (j = 0; j < 3; j++)
143 for (k = 0; k < 3; k++)
144 mri_fid(j, k) = mri_fid(j, k) * scales[k];
151 mri_fid.row(0).data(), mri_fid.row(1).data(), mri_fid.row(2).data()));
157 for (k = 0; k < head_dig.
nfids(); k++)
158 Eigen::Map<Eigen::Vector3f>(head_dig.
mri_fids[k].r) = mri_fid.row(k).transpose();
160 qInfo(
"After simple alignment : \n");
167 for (k = 0; k < niter; k++) {
168 if (
iterate_alignment_once(head_dig, nasion_weight, Eigen::Vector3f(mri_fid.row(1).transpose()), k == niter - 1 && niter > 1) ==
FAIL)
172 qInfo(
"%d / %d iterations done. RMS dist = %7.1f mm\n", k, niter,
174 qInfo(
"After refinement :\n");
185 const Eigen::Matrix<float, 3, 3, Eigen::RowMajor>& mri_fid,
186 Eigen::Vector3f& scales)
189 float simplex_size = 2e-2f;
190 Eigen::VectorXf r0(3);
193 scales[0] = scales[1] = scales[2] = 1.0;
195 Eigen::MatrixXf dig_rr(dig.
npoint, 3);
196 Eigen::MatrixXf head_rr(
np, 3);
199 for (k = 0, ndig = 0; k < dig.
npoint; k++) {
200 if (dig.
points[k].r[2] > 0) {
201 dig_rr.row(ndig++) = Eigen::Map<const Eigen::RowVector3f>(dig.
points[k].r);
209 qInfo(
"Polhemus : (%.1f %.1f %.1f) mm R = %.1f mm\n", 1000 * r0[0], 1000 * r0[1], 1000 * r0[2], 1000 * Rdig);
212 Eigen::Vector3f LR = mri_fid.row(2).transpose() - mri_fid.row(0).transpose();
213 Eigen::Vector3f LN = mri_fid.row(1).transpose() - mri_fid.row(0).transpose();
214 Eigen::Vector3f norm = LR.cross(LN);
217 for (k = 0, nhead = 0; k <
np; k++) {
218 Eigen::Vector3f diff_vec =
rr.row(k).transpose() - mri_fid.row(0).transpose();
219 if (diff_vec.dot(norm) > 0) {
220 head_rr.row(nhead++) =
rr.row(k);
228 qInfo(
"Scalp : (%.1f %.1f %.1f) mm R = %.1f mm\n", 1000 * r0[0], 1000 * r0[1], 1000 * r0[2], 1000 * Rscalp);
230 scales[0] = scales[1] = scales[2] = Rdig / Rscalp;
244 d.dist_valid =
false;
246 for (k = 0; k < d.npoint; k++) {
251 if (std::fabs(d.dist(k)) > maxdist &&
258 qInfo(
"%d points discarded (maxdist = %6.1f mm).\n", discarded, 1000 * maxdist);
266 bool do_all,
bool do_approx)
const
273 Q_ASSERT(dig.head_mri_t);
274 const FiffCoordTrans& t = (dig.head_mri_t_adj && !dig.head_mri_t_adj->isEmpty()) ? *dig.head_mri_t_adj : *dig.head_mri_t;
280 PointsT digPoints(dig.npoint, 3);
282 dig.dist.conservativeResize(dig.npoint);
283 if (dig.closest.size() == 0) {
287 dig.closest = Eigen::VectorXi::Constant(dig.npoint, -1);
290 dig.closest_point.setZero(dig.npoint, 3);
291 Eigen::VectorXi closest(dig.npoint);
292 Eigen::VectorXf dists(dig.npoint);
294 for (k = 0, nactive = 0; k < dig.npoint; k++) {
295 if ((dig.active[k] && !dig.discard[k]) || do_all) {
296 point = dig.points.at(k);
297 digPoints.row(nactive) = Eigen::Map<const Eigen::RowVector3f>(
point.r);
300 closest[nactive] = dig.closest(k);
301 if (closest[nactive] < 0)
304 closest[nactive] = -1;
314 qInfo(
"Inside or outside for %d points...", nactive);
315 for (k = 0, nactive = 0; k < dig.npoint; k++) {
316 if ((dig.active[k] && !dig.discard[k]) || do_all) {
317 dig.dist(k) = dists[nactive];
318 dig.closest(k) = closest[nactive];
320 Eigen::Vector3f pt = Eigen::Map<const Eigen::Vector3f>(digPoints.row(nactive).data());
322 dig.closest_point.row(k) = proj.transpose();
350 constexpr bool bUseSolidAngleSign =
false;
351 if (bUseSolidAngleSign && !do_approx) {
352 Eigen::Vector3f pt = Eigen::Map<const Eigen::Vector3f>(digPoints.row(nactive).data());
354 dig.dist(k) = -std::fabs(dig.dist(k));
356 dig.dist(k) = std::fabs(dig.dist(k));
365 dig.dist_valid =
true;
374 const std::optional<Eigen::Vector3f>& nasion_mri,
380 Eigen::MatrixXf rr_head(dig.npoint, 3);
381 Eigen::MatrixXf rr_mri(dig.npoint, 3);
382 Eigen::VectorXf w = Eigen::VectorXf::Zero(dig.npoint);
386 float max_diff = 40e-3f;
388 if (!dig.head_mri_t_adj) {
389 qCritical() <<
"Not adjusting the transformation";
397 for (k = 0, nactive = 0; k < dig.npoint; k++) {
398 if (dig.active[k] && !dig.discard[k]) {
399 point = dig.points.at(k);
400 rr_head.row(nactive) = Eigen::Map<const Eigen::RowVector3f>(
point.r);
401 rr_mri.row(nactive) = dig.closest_point.row(k);
407 w[nactive] = nasion_weight;
409 rr_mri.row(nactive) = nasion_mri->transpose();
411 Eigen::Vector3f nasion_head = *nasion_mri;
412 Q_ASSERT(dig.head_mri_t || dig.head_mri_t_adj);
414 dig.head_mri_t_adj ? *dig.head_mri_t_adj : *dig.head_mri_t,
416 rr_head.row(nactive) = nasion_head.transpose();
424 qCritical() <<
"Not enough points to do the alignment";
428 rr_head.topRows(nactive),
429 rr_mri.topRows(nactive),
435 if (dig.head_mri_t_adj)
436 dig.head_mri_t_adj = std::make_unique<FiffCoordTrans>(t);
440 dig.dist_valid =
false;
454 for (k = 0, rms = 0.0, nactive = 0; k < dig.
npoint; k++)
456 rms = rms + dig.
dist(k) * dig.
dist(k);
460 rms = rms / (nactive - 1);
473 for (k = 0; k < 3; k++) {
477 for (j = 0; j <
np; j++)
478 for (k = 0; k < 3; k++)
479 rr(j, k) =
rr(j, k) * scales[k];
487 Eigen::Vector3f mn =
rr.row(0).transpose();
488 Eigen::Vector3f mx = mn;
490 for (
int k = 1; k <
np; k++) {
491 Eigen::Vector3f r =
rr.row(k).transpose();
497 qInfo(
"%s:\n", tag.toUtf8().constData());
498 qInfo(
"\tx = %f ... %f mm\n", 1000 * mn[0], 1000 * mx[0]);
499 qInfo(
"\ty = %f ... %f mm\n", 1000 * mn[1], 1000 * mx[1]);
500 qInfo(
"\tz = %f ... %f mm\n", 1000 * mn[2], 1000 * mx[2]);
503 fov = std::max(mn.cwiseAbs().maxCoeff(), mx.cwiseAbs().maxCoeff());
514 if (name.startsWith(
"inflated") || name.startsWith(
"sphere") || name.startsWith(
"white"))
529 const int totalSize = ncolor *
np;
534 [[maybe_unused]]
float curv_sum = 0.0f;
536 for (
int k = 0; k <
np; k++) {
537 const int base = k * ncolor;
538 curv_sum += std::fabs(
curv[k]);
540 for (
int j = 0; j < 3; j++)
546 for (
int k = 0; k <
np; k++) {
547 const int base = k * ncolor;
548 curv_sum += std::fabs(
curv[k]);
549 for (
int j = 0; j < 3; j++)
556 qInfo(
"Average curvature : %f\n", curv_sum /
np);
#define FIFFV_POINT_CARDINAL
#define FIFFV_POINT_NASION
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-...
Single digitization point (FIFF_DIG_POINT) with kind (cardinal/HPI/EEG/extra), identifier and 3D coor...
High-level digitization data: dig points plus the device→head transform and fitting metadata that tog...
Best-fit sphere from a 3-D point cloud with closed-form and Nelder–Mead solvers.
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
One renderable surface (cortex / pial / inflated / BEM) inside an MSH display set.
Lightweight triangulated surface (vertices, triangles, normals) used by surface-based routines.
Sphere-registration morph map of one hemisphere: the sparse matrix taking vertex values from one subj...
Picked vertices / triangles on a mesh display surface.
Mesh-display colour scale definition (FreeSurfer mri_glm style).
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
void pickCardinalFiducials()
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)
mneUserFreeFunc user_data_free
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)
void setup_curvature_colors()
Eigen::VectorXf vertex_colors
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
Eigen::Vector3f project_to_triangle(int tri, float p, float q) const
void find_closest_on_surface_approx(const PointsT &r, int np, Eigen::VectorXi &nearest_tri, Eigen::VectorXf &distances, int nstep) const
Eigen::Matrix< float, Eigen::Dynamic, 3, Eigen::RowMajor > PointsT
Eigen::Map< const Eigen::Vector3f > point(int k) const