33#include <QRegularExpression>
51constexpr int kNCoeff = 100;
52constexpr double kMegConst = 4e-14 *
M_PI;
53constexpr double kEegConst = 1.0 / (4.0 *
M_PI);
54constexpr double kEegIntradScale = 0.7;
56constexpr float kGradStd = 5e-13f;
57constexpr float kMagStd = 20e-15f;
58constexpr float kEegStd = 1e-6f;
63void computeLegendreDer(
double x,
int ncoeff,
64 double* p,
double* pd,
double* pdd)
66 p[0] = 1.0; pd[0] = 0.0; pdd[0] = 0.0;
67 if (ncoeff < 2)
return;
68 p[1] = x; pd[1] = 1.0; pdd[1] = 0.0;
69 for (
int n = 2; n < ncoeff; ++n) {
70 double old_p = p[n - 1];
71 double old_pd = pd[n - 1];
72 p[n] = ((2 * n - 1) * x * old_p - (n - 1) * p[n - 2]) / n;
73 pd[n] = n * old_p + x * old_pd;
74 pdd[n] = (n + 1) * old_pd + x * pdd[n - 1];
81void computeLegendreVal(
double x,
int ncoeff,
double* p)
84 if (ncoeff < 2)
return;
86 for (
int n = 2; n < ncoeff; ++n) {
87 p[n] = ((2 * n - 1) * x * p[n - 1] - (n - 1) * p[n - 2]) / n;
94void compSumsMeg(
double beta,
double ctheta,
double sums[4])
96 double p[kNCoeff], pd[kNCoeff], pdd[kNCoeff];
97 computeLegendreDer(ctheta, kNCoeff, p, pd, pdd);
99 sums[0] = sums[1] = sums[2] = sums[3] = 0.0;
101 for (
int n = 1; n < kNCoeff; ++n) {
103 double dn =
static_cast<double>(n);
104 double multn = dn / (2.0 * dn + 1.0);
105 double mult = multn / (dn + 1.0);
107 sums[0] += (dn + 1.0) * multn * p[n] * betan;
108 sums[1] += multn * pd[n] * betan;
109 sums[2] += mult * pd[n] * betan;
110 sums[3] += mult * pdd[n] * betan;
117double compSumEeg(
double beta,
double ctheta)
120 computeLegendreVal(ctheta, kNCoeff, p);
124 for (
int n = 1; n < kNCoeff; ++n) {
126 double dn =
static_cast<double>(n);
127 double factor = 2.0 * dn + 1.0;
128 sum += p[n] * betan * factor * factor / dn;
136double sphereDotMeg(
double intrad,
137 const Vector3d& rr1,
double lr1,
const Vector3d& cosmag1,
138 const Vector3d& rr2,
double lr2,
const Vector3d& cosmag2)
140 if (lr1 == 0.0 || lr2 == 0.0)
return 0.0;
142 double beta = (intrad * intrad) / (lr1 * lr2);
143 double ct = std::clamp(rr1.dot(rr2), -1.0, 1.0);
146 compSumsMeg(beta, ct, sums);
148 double n1c1 = cosmag1.dot(rr1);
149 double n1c2 = cosmag1.dot(rr2);
150 double n2c1 = cosmag2.dot(rr1);
151 double n2c2 = cosmag2.dot(rr2);
152 double n1n2 = cosmag1.dot(cosmag2);
154 double part1 = ct * n1c1 * n2c2;
155 double part2 = n1c1 * n2c1 + n1c2 * n2c2;
157 double result = n1c1 * n2c2 * sums[0]
158 + (2.0 * part1 - part2) * sums[1]
159 + (n1n2 + part1 - part2) * sums[2]
160 + (n1c2 - ct * n1c1) * (n2c1 - ct * n2c2) * sums[3];
162 result *= kMegConst / (lr1 * lr2);
169double sphereDotEeg(
double intrad,
170 const Vector3d& rr1,
double lr1,
171 const Vector3d& rr2,
double lr2)
173 if (lr1 == 0.0 || lr2 == 0.0)
return 0.0;
175 double beta = (intrad * intrad) / (lr1 * lr2);
176 double ct = std::clamp(rr1.dot(rr2), -1.0, 1.0);
178 double sum = compSumEeg(beta, ct);
179 return kEegConst * sum / (lr1 * lr2);
187 Eigen::MatrixX3d rmag;
188 Eigen::VectorXd rlen;
189 Eigen::MatrixX3d cosmag;
192 int np()
const {
return static_cast<int>(rlen.size()); }
198CoilData extractCoilData(
const FwdCoil* coil,
const Vector3d& r0)
201 const int n = coil->
np;
202 cd.rmag.resize(n, 3);
204 cd.cosmag.resize(n, 3);
207 for (
int i = 0; i < n; ++i) {
208 Vector3d rel = coil->
rmag.row(i).cast<
double>().transpose() - r0;
209 double len = rel.norm();
211 cd.rmag.row(i) = (rel / len).transpose();
213 cd.rmag.row(i).setZero();
215 cd.cosmag.row(i) = coil->
cosmag.row(i).cast<
double>();
216 cd.w(i) =
static_cast<double>(coil->
w[i]);
224MatrixXd doSelfDots(
double intrad,
const FwdCoilSet& coils,
const Vector3d& r0,
bool isMeg)
226 const int nc = coils.
ncoil();
227 std::vector<CoilData> cdata(nc);
228 for (
int i = 0; i < nc; ++i) {
229 cdata[i] = extractCoilData(coils.
coils[i].get(), r0);
232 MatrixXd products = MatrixXd::Zero(nc, nc);
233 for (
int ci1 = 0; ci1 < nc; ++ci1) {
234 for (
int ci2 = 0; ci2 <= ci1; ++ci2) {
236 const CoilData& c1 = cdata[ci1];
237 const CoilData& c2 = cdata[ci2];
238 for (
int i = 0; i < c1.np(); ++i) {
239 for (
int j = 0; j < c2.np(); ++j) {
240 double ww = c1.w(i) * c2.w(j);
242 dot += ww * sphereDotMeg(intrad,
243 c1.rmag.row(i).transpose(), c1.rlen(i), c1.cosmag.row(i).transpose(),
244 c2.rmag.row(j).transpose(), c2.rlen(j), c2.cosmag.row(j).transpose());
246 dot += ww * sphereDotEeg(intrad,
247 c1.rmag.row(i).transpose(), c1.rlen(i),
248 c2.rmag.row(j).transpose(), c2.rlen(j));
252 products(ci1, ci2) = dot;
253 products(ci2, ci1) = dot;
262MatrixXd doSurfaceDots(
double intrad,
const FwdCoilSet& coils,
263 const MatrixX3f& rr,
const MatrixX3f& nn,
264 const Vector3d& r0,
bool isMeg)
266 const int nc = coils.
ncoil();
267 const int nv = rr.rows();
269 std::vector<CoilData> cdata(nc);
270 for (
int i = 0; i < nc; ++i) {
271 cdata[i] = extractCoilData(coils.
coils[i].get(), r0);
274 MatrixXd products = MatrixXd::Zero(nv, nc);
275 for (
int vi = 0; vi < nv; ++vi) {
277 Vector3d rel = rr.row(vi).cast<
double>() - r0.transpose();
278 double lsurf = rel.norm();
279 Vector3d rsurf = (lsurf > 0.0) ? Vector3d(rel / lsurf) : Vector3d::Zero();
280 Vector3d nsurf = nn.row(vi).cast<
double>();
282 for (
int ci = 0; ci < nc; ++ci) {
283 const CoilData& c = cdata[ci];
285 for (
int j = 0; j < c.np(); ++j) {
287 dot += c.w(j) * sphereDotMeg(intrad,
289 c.rmag.row(j).transpose(), c.rlen(j), c.cosmag.row(j).transpose());
291 dot += c.w(j) * sphereDotEeg(intrad,
293 c.rmag.row(j).transpose(), c.rlen(j));
296 products(vi, ci) = dot;
307 VectorXd stds(coils.
ncoil());
308 for (
int k = 0; k < coils.
ncoil(); ++k) {
309 stds(k) = coils.
coils[k]->is_axial_coil() ?
static_cast<double>(kMagStd)
310 : static_cast<double>(kGradStd);
318VectorXd adHocEegStds(
int ncoil)
320 return VectorXd::Constant(ncoil,
static_cast<double>(kEegStd));
327std::unique_ptr<MatrixXf> computeMappingMatrix(
const MatrixXd& selfDots,
328 const MatrixXd& surfaceDots,
329 const VectorXd& noiseStds,
331 const MatrixXd& projOp = MatrixXd(),
332 bool applyAvgRef =
false)
334 if (selfDots.rows() == 0 || surfaceDots.rows() == 0) {
338 const int nchan = selfDots.rows();
342 bool hasProj = (projOp.rows() == nchan && projOp.cols() == nchan);
344 projDots = projOp.transpose() * selfDots * projOp;
350 VectorXd whitener(nchan);
351 for (
int i = 0; i < nchan; ++i) {
352 whitener(i) = (noiseStds(i) > 0.0) ? (1.0 / noiseStds(i)) : 0.0;
356 MatrixXd whitenedDots = whitener.asDiagonal() * projDots * whitener.asDiagonal();
359 JacobiSVD<MatrixXd>
svd(whitenedDots, ComputeFullU | ComputeFullV);
360 VectorXd s =
svd.singularValues();
361 if (s.size() == 0 || s(0) <= 0.0) {
366 VectorXd varexp(s.size());
368 for (
int i = 1; i < s.size(); ++i) {
369 varexp(i) = varexp(i - 1) + s(i);
371 double totalVar = varexp(s.size() - 1);
374 for (
int i = 0; i < s.size(); ++i) {
375 if (varexp(i) / totalVar >= (1.0 - miss)) {
382 VectorXd sinv = VectorXd::Zero(s.size());
383 for (
int i = 0; i < n; ++i) {
384 sinv(i) = (s(i) > 0.0) ? (1.0 / s(i)) : 0.0;
386 MatrixXd inv =
svd.matrixV() * sinv.asDiagonal() *
svd.matrixU().transpose();
389 MatrixXd invWhitened = whitener.asDiagonal() * inv * whitener.asDiagonal();
392 MatrixXd invWhitenedProj;
394 invWhitenedProj = projOp.transpose() * invWhitened;
396 invWhitenedProj = invWhitened;
400 MatrixXd mapping = surfaceDots * invWhitenedProj;
404 VectorXd colMeans = mapping.colwise().mean();
405 mapping.rowwise() -= colMeans.transpose();
409 MatrixXf mappingF = mapping.cast<
float>();
410 return std::make_unique<MatrixXf>(std::move(mappingF));
421 const MatrixX3f& vertices,
422 const MatrixX3f& normals,
423 const Vector3f& origin,
427 if (coils.
ncoil() <= 0 || vertices.rows() == 0 || normals.rows() != vertices.rows()) {
431 const Vector3d r0 = origin.cast<
double>();
433 MatrixXd selfDots = doSelfDots(intrad, coils, r0,
true);
434 MatrixXd surfaceDots = doSurfaceDots(intrad, coils, vertices, normals, r0,
true);
435 VectorXd stds = adHocMegStds(coils);
437 return computeMappingMatrix(selfDots, surfaceDots, stds,
static_cast<double>(miss));
444 const MatrixX3f& vertices,
445 const MatrixX3f& normals,
446 const Vector3f& origin,
447 const FIFFLIB::FiffInfo& info,
448 const QStringList& chNames,
452 if (coils.
ncoil() <= 0 || vertices.rows() == 0 || normals.rows() != vertices.rows()) {
456 const Vector3d r0 = origin.cast<
double>();
458 MatrixXd selfDots = doSelfDots(intrad, coils, r0,
true);
459 MatrixXd surfaceDots = doSurfaceDots(intrad, coils, vertices, normals, r0,
true);
460 VectorXd stds = adHocMegStds(coils);
466 return computeMappingMatrix(selfDots, surfaceDots, stds,
467 static_cast<double>(miss), projOp,
false);
474 const MatrixX3f& vertices,
475 const Vector3f& origin,
479 if (coils.
ncoil() <= 0 || vertices.rows() == 0) {
483 const double eegIntrad = intrad * kEegIntradScale;
484 const Vector3d r0 = origin.cast<
double>();
487 MatrixX3f dummyNormals = MatrixX3f::Zero(vertices.rows(), 3);
489 MatrixXd selfDots = doSelfDots(eegIntrad, coils, r0,
false);
490 MatrixXd surfaceDots = doSurfaceDots(eegIntrad, coils, vertices, dummyNormals, r0,
false);
491 VectorXd stds = adHocEegStds(coils.
ncoil());
493 return computeMappingMatrix(selfDots, surfaceDots, stds,
static_cast<double>(miss));
500 const MatrixX3f& vertices,
501 const Vector3f& origin,
502 const FIFFLIB::FiffInfo& info,
503 const QStringList& chNames,
507 if (coils.
ncoil() <= 0 || vertices.rows() == 0) {
511 const double eegIntrad = intrad * kEegIntradScale;
512 const Vector3d r0 = origin.cast<
double>();
514 MatrixX3f dummyNormals = MatrixX3f::Zero(vertices.rows(), 3);
515 MatrixXd selfDots = doSelfDots(eegIntrad, coils, r0,
false);
516 MatrixXd surfaceDots = doSurfaceDots(eegIntrad, coils, vertices, dummyNormals, r0,
false);
517 VectorXd stds = adHocEegStds(coils.
ncoil());
524 bool hasAvgRef =
false;
525 for (
const auto& proj : info.
projs) {
527 proj.desc.contains(QRegularExpression(
"^Average .* reference$",
528 QRegularExpression::CaseInsensitiveOption))) {
534 return computeMappingMatrix(selfDots, surfaceDots, stds,
535 static_cast<double>(miss), projOp, hasAvgRef);
Sphere-model field interpolator that maps measured MEG/EEG values onto a dense scalp or cortical surf...
Eigen::JacobiSVD< Eigen::Matrix3f > svd(S, Eigen::ComputeFullU|Eigen::ComputeFullV)
SSP projection item: a named projection vector set with active/desired flags, parsed from FIFFB_PROJ_...
FIFF tag-kind, block-kind and type-code numerical definitions, authoritative for FIFFLIB.
#define FIFFV_PROJ_ITEM_EEG_AVREF
Forward modelling — BEM solver, spherical models, sensor/coil definitions and the lead-field assembly...
static fiff_int_t make_projector(const QList< FiffProj > &projs, const QStringList &ch_names, Eigen::MatrixXd &proj, const QStringList &bads=defaultQStringList, Eigen::MatrixXd &U=defaultMatrixXd)
Single MEG sensor coil or EEG electrode — stores the coil-local frame and the (r_mag,...
Eigen::Matrix< float, Eigen::Dynamic, 3, Eigen::RowMajor > cosmag
Eigen::Matrix< float, Eigen::Dynamic, 3, Eigen::RowMajor > rmag
Container of FwdCoil instances acting both as the in-memory image of the coil_def....
std::vector< FwdCoil::UPtr > coils
static std::unique_ptr< Eigen::MatrixXf > computeEegMapping(const FwdCoilSet &coils, const Eigen::MatrixX3f &vertices, const Eigen::Vector3f &origin, float intrad=0.06f, float miss=1e-3f)
static std::unique_ptr< Eigen::MatrixXf > computeMegMapping(const FwdCoilSet &coils, const Eigen::MatrixX3f &vertices, const Eigen::MatrixX3f &normals, const Eigen::Vector3f &origin, float intrad=0.06f, float miss=1e-4f)