33#include <QRegularExpression>
52constexpr int kNCoeff = 100;
53constexpr double kMegConst = 4e-14 *
M_PI;
54constexpr double kEegConst = 1.0 / (4.0 *
M_PI);
55constexpr double kEegIntradScale = 0.7;
57constexpr float kGradStd = 5e-13f;
58constexpr float kMagStd = 20e-15f;
59constexpr float kEegStd = 1e-6f;
64void computeLegendreDer(
double x,
int ncoeff,
65 double* p,
double* pd,
double* pdd)
75 for (
int n = 2; n < ncoeff; ++n) {
76 double old_p = p[n - 1];
77 double old_pd = pd[n - 1];
78 p[n] = ((2 * n - 1) * x * old_p - (n - 1) * p[n - 2]) / n;
79 pd[n] = n * old_p + x * old_pd;
80 pdd[n] = (n + 1) * old_pd + x * pdd[n - 1];
87void computeLegendreVal(
double x,
int ncoeff,
double* p)
93 for (
int n = 2; n < ncoeff; ++n) {
94 p[n] = ((2 * n - 1) * x * p[n - 1] - (n - 1) * p[n - 2]) / n;
101void compSumsMeg(
double beta,
double ctheta,
double sums[4])
103 double p[kNCoeff], pd[kNCoeff], pdd[kNCoeff];
104 computeLegendreDer(ctheta, kNCoeff, p, pd, pdd);
106 sums[0] = sums[1] = sums[2] = sums[3] = 0.0;
108 for (
int n = 1; n < kNCoeff; ++n) {
110 double dn =
static_cast<double>(n);
111 double multn = dn / (2.0 * dn + 1.0);
112 double mult = multn / (dn + 1.0);
114 sums[0] += (dn + 1.0) * multn * p[n] * betan;
115 sums[1] += multn * pd[n] * betan;
116 sums[2] += mult * pd[n] * betan;
117 sums[3] += mult * pdd[n] * betan;
124double compSumEeg(
double beta,
double ctheta)
127 computeLegendreVal(ctheta, kNCoeff, p);
131 for (
int n = 1; n < kNCoeff; ++n) {
133 double dn =
static_cast<double>(n);
134 double factor = 2.0 * dn + 1.0;
135 sum += p[n] * betan * factor * factor / dn;
143double sphereDotMeg(
double intrad,
144 const Vector3d& rr1,
double lr1,
const Vector3d& cosmag1,
145 const Vector3d& rr2,
double lr2,
const Vector3d& cosmag2)
147 if (lr1 == 0.0 || lr2 == 0.0)
150 double beta = (intrad * intrad) / (lr1 * lr2);
151 double ct = std::clamp(rr1.dot(rr2), -1.0, 1.0);
154 compSumsMeg(beta, ct, sums);
156 double n1c1 = cosmag1.dot(rr1);
157 double n1c2 = cosmag1.dot(rr2);
158 double n2c1 = cosmag2.dot(rr1);
159 double n2c2 = cosmag2.dot(rr2);
160 double n1n2 = cosmag1.dot(cosmag2);
162 double part1 = ct * n1c1 * n2c2;
163 double part2 = n1c1 * n2c1 + n1c2 * n2c2;
165 double result = n1c1 * n2c2 * sums[0] + (2.0 * part1 - part2) * sums[1] + (n1n2 + part1 - part2) * sums[2] + (n1c2 - ct * n1c1) * (n2c1 - ct * n2c2) * sums[3];
167 result *= kMegConst / (lr1 * lr2);
174double sphereDotEeg(
double intrad,
175 const Vector3d& rr1,
double lr1,
176 const Vector3d& rr2,
double lr2)
178 if (lr1 == 0.0 || lr2 == 0.0)
181 double beta = (intrad * intrad) / (lr1 * lr2);
182 double ct = std::clamp(rr1.dot(rr2), -1.0, 1.0);
184 double sum = compSumEeg(beta, ct);
185 return kEegConst * sum / (lr1 * lr2);
193 Eigen::MatrixX3d rmag;
194 Eigen::VectorXd rlen;
195 Eigen::MatrixX3d cosmag;
200 return static_cast<int>(rlen.size());
207CoilData extractCoilData(
const FwdCoil* coil,
const Vector3d& r0)
210 const int n = coil->
np;
211 cd.rmag.resize(n, 3);
213 cd.cosmag.resize(n, 3);
216 for (
int i = 0; i < n; ++i) {
217 Vector3d rel = coil->
rmag.row(i).cast<
double>().transpose() - r0;
218 double len = rel.norm();
220 cd.rmag.row(i) = (rel / len).transpose();
222 cd.rmag.row(i).setZero();
224 cd.cosmag.row(i) = coil->
cosmag.row(i).cast<
double>();
225 cd.w(i) =
static_cast<double>(coil->
w[i]);
233MatrixXd doSelfDots(
double intrad,
const FwdCoilSet& coils,
const Vector3d& r0,
bool isMeg)
235 const int nc = coils.
ncoil();
236 std::vector<CoilData> cdata(nc);
237 for (
int i = 0; i < nc; ++i) {
238 cdata[i] = extractCoilData(coils.
coils[i].get(), r0);
241 MatrixXd products = MatrixXd::Zero(nc, nc);
242 for (
int ci1 = 0; ci1 < nc; ++ci1) {
243 for (
int ci2 = 0; ci2 <= ci1; ++ci2) {
245 const CoilData& c1 = cdata[ci1];
246 const CoilData& c2 = cdata[ci2];
247 for (
int i = 0; i < c1.np(); ++i) {
248 for (
int j = 0; j < c2.np(); ++j) {
249 double ww = c1.w(i) * c2.w(j);
251 dot += ww * sphereDotMeg(intrad, c1.rmag.row(i).transpose(), c1.rlen(i), c1.cosmag.row(i).transpose(), c2.rmag.row(j).transpose(), c2.rlen(j), c2.cosmag.row(j).transpose());
253 dot += ww * sphereDotEeg(intrad, c1.rmag.row(i).transpose(), c1.rlen(i), c2.rmag.row(j).transpose(), c2.rlen(j));
257 products(ci1, ci2) = dot;
258 products(ci2, ci1) = dot;
267MatrixXd doSurfaceDots(
double intrad,
const FwdCoilSet& coils,
268 const MatrixX3f& rr,
const MatrixX3f& nn,
269 const Vector3d& r0,
bool isMeg)
271 const int nc = coils.
ncoil();
272 const int nv = rr.rows();
274 std::vector<CoilData> cdata(nc);
275 for (
int i = 0; i < nc; ++i) {
276 cdata[i] = extractCoilData(coils.
coils[i].get(), r0);
279 MatrixXd products = MatrixXd::Zero(nv, nc);
280 for (
int vi = 0; vi < nv; ++vi) {
282 Vector3d rel = rr.row(vi).cast<
double>() - r0.transpose();
283 double lsurf = rel.norm();
284 Vector3d rsurf = (lsurf > 0.0) ? Vector3d(rel / lsurf) : Vector3d::Zero();
285 Vector3d nsurf = nn.row(vi).cast<
double>();
287 for (
int ci = 0; ci < nc; ++ci) {
288 const CoilData& c = cdata[ci];
290 for (
int j = 0; j < c.np(); ++j) {
292 dot += c.w(j) * sphereDotMeg(intrad, rsurf, lsurf, nsurf, c.rmag.row(j).transpose(), c.rlen(j), c.cosmag.row(j).transpose());
294 dot += c.w(j) * sphereDotEeg(intrad, rsurf, lsurf, c.rmag.row(j).transpose(), c.rlen(j));
297 products(vi, ci) = dot;
308 VectorXd stds(coils.
ncoil());
309 for (
int k = 0; k < coils.
ncoil(); ++k) {
310 stds(k) = coils.
coils[k]->is_axial_coil() ?
static_cast<double>(kMagStd)
311 : static_cast<double>(kGradStd);
319VectorXd adHocEegStds(
int ncoil)
321 return VectorXd::Constant(ncoil,
static_cast<double>(kEegStd));
328std::unique_ptr<MatrixXf> computeMappingMatrix(
const MatrixXd& selfDots,
329 const MatrixXd& surfaceDots,
330 const VectorXd& noiseStds,
332 const MatrixXd& projOp = MatrixXd(),
333 bool applyAvgRef =
false)
335 if (selfDots.rows() == 0 || surfaceDots.rows() == 0) {
339 const int nchan = selfDots.rows();
343 bool hasProj = (projOp.rows() == nchan && projOp.cols() == nchan);
345 projDots = projOp.transpose() * selfDots * projOp;
351 VectorXd whitener(nchan);
352 for (
int i = 0; i < nchan; ++i) {
353 whitener(i) = (noiseStds(i) > 0.0) ? (1.0 / noiseStds(i)) : 0.0;
357 MatrixXd whitenedDots = whitener.asDiagonal() * projDots * whitener.asDiagonal();
360 JacobiSVD<MatrixXd>
svd(whitenedDots, ComputeFullU | ComputeFullV);
361 VectorXd s =
svd.singularValues();
362 if (s.size() == 0 || s(0) <= 0.0) {
367 VectorXd varexp(s.size());
369 for (
int i = 1; i < s.size(); ++i) {
370 varexp(i) = varexp(i - 1) + s(i);
372 double totalVar = varexp(s.size() - 1);
375 for (
int i = 0; i < s.size(); ++i) {
376 if (varexp(i) / totalVar >= (1.0 - miss)) {
383 VectorXd sinv = VectorXd::Zero(s.size());
384 for (
int i = 0; i < n; ++i) {
385 sinv(i) = (s(i) > 0.0) ? (1.0 / s(i)) : 0.0;
387 MatrixXd inv =
svd.matrixV() * sinv.asDiagonal() *
svd.matrixU().transpose();
390 MatrixXd invWhitened = whitener.asDiagonal() * inv * whitener.asDiagonal();
393 MatrixXd invWhitenedProj;
395 invWhitenedProj = projOp.transpose() * invWhitened;
397 invWhitenedProj = invWhitened;
401 MatrixXd mapping = surfaceDots * invWhitenedProj;
405 VectorXd colMeans = mapping.colwise().mean();
406 mapping.rowwise() -= colMeans.transpose();
410 MatrixXf mappingF = mapping.cast<
float>();
411 return std::make_unique<MatrixXf>(std::move(mappingF));
422 const MatrixX3f& vertices,
423 const MatrixX3f& normals,
424 const Vector3f& origin,
428 if (coils.
ncoil() <= 0 || vertices.rows() == 0 || normals.rows() != vertices.rows()) {
432 const Vector3d r0 = origin.cast<
double>();
434 MatrixXd selfDots = doSelfDots(intrad, coils, r0,
true);
435 MatrixXd surfaceDots = doSurfaceDots(intrad, coils, vertices, normals, r0,
true);
436 VectorXd stds = adHocMegStds(coils);
438 return computeMappingMatrix(selfDots, surfaceDots, stds,
static_cast<double>(miss));
445 const MatrixX3f& vertices,
446 const MatrixX3f& normals,
447 const Vector3f& origin,
448 const FIFFLIB::FiffInfo& info,
449 const QStringList& chNames,
453 if (coils.
ncoil() <= 0 || vertices.rows() == 0 || normals.rows() != vertices.rows()) {
457 const Vector3d r0 = origin.cast<
double>();
459 MatrixXd selfDots = doSelfDots(intrad, coils, r0,
true);
460 MatrixXd surfaceDots = doSurfaceDots(intrad, coils, vertices, normals, r0,
true);
461 VectorXd stds = adHocMegStds(coils);
467 return computeMappingMatrix(selfDots, surfaceDots, stds,
468 static_cast<double>(miss), projOp,
false);
475 const MatrixX3f& vertices,
476 const Vector3f& origin,
480 if (coils.
ncoil() <= 0 || vertices.rows() == 0) {
484 const double eegIntrad = intrad * kEegIntradScale;
485 const Vector3d r0 = origin.cast<
double>();
488 MatrixX3f dummyNormals = MatrixX3f::Zero(vertices.rows(), 3);
490 MatrixXd selfDots = doSelfDots(eegIntrad, coils, r0,
false);
491 MatrixXd surfaceDots = doSurfaceDots(eegIntrad, coils, vertices, dummyNormals, r0,
false);
492 VectorXd stds = adHocEegStds(coils.
ncoil());
494 return computeMappingMatrix(selfDots, surfaceDots, stds,
static_cast<double>(miss));
501 const MatrixX3f& vertices,
502 const Vector3f& origin,
503 const FIFFLIB::FiffInfo& info,
504 const QStringList& chNames,
508 if (coils.
ncoil() <= 0 || vertices.rows() == 0) {
512 const double eegIntrad = intrad * kEegIntradScale;
513 const Vector3d r0 = origin.cast<
double>();
515 MatrixX3f dummyNormals = MatrixX3f::Zero(vertices.rows(), 3);
516 MatrixXd selfDots = doSelfDots(eegIntrad, coils, r0,
false);
517 MatrixXd surfaceDots = doSurfaceDots(eegIntrad, coils, vertices, dummyNormals, r0,
false);
518 VectorXd stds = adHocEegStds(coils.
ncoil());
525 bool hasAvgRef =
false;
526 for (
const auto& proj : info.
projs) {
528 proj.desc.contains(QRegularExpression(
"^Average .* reference$",
529 QRegularExpression::CaseInsensitiveOption))) {
535 return computeMappingMatrix(selfDots, surfaceDots, stds,
536 static_cast<double>(miss), projOp, hasAvgRef);
FIFF tag-kind, block-kind and type-code numerical definitions, authoritative for FIFFLIB.
#define FIFFV_PROJ_ITEM_EEG_AVREF
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_...
Sphere-model field interpolator that maps measured MEG/EEG values onto a dense scalp or cortical surf...
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)