55#include <QRegularExpression>
79constexpr int kNCoeff = 100;
82constexpr double kMegConst = 4e-14 *
M_PI;
85constexpr double kEegConst = 1.0 / (4.0 *
M_PI);
88constexpr double kEegIntradScale = 0.7;
91constexpr float kGradStd = 5e-13f;
92constexpr float kMagStd = 20e-15f;
93constexpr float kEegStd = 1e-6f;
106void computeLegendreDer(
double x,
int ncoeff,
107 double* p,
double* pd,
double* pdd)
109 p[0] = 1.0; pd[0] = 0.0; pdd[0] = 0.0;
110 if (ncoeff < 2)
return;
111 p[1] = x; pd[1] = 1.0; pdd[1] = 0.0;
112 for (
int n = 2; n < ncoeff; ++n) {
113 double old_p = p[n - 1];
114 double old_pd = pd[n - 1];
115 p[n] = ((2 * n - 1) * x * old_p - (n - 1) * p[n - 2]) / n;
116 pd[n] = n * old_p + x * old_pd;
117 pdd[n] = (n + 1) * old_pd + x * pdd[n - 1];
126void computeLegendreVal(
double x,
int ncoeff,
double* p)
129 if (ncoeff < 2)
return;
131 for (
int n = 2; n < ncoeff; ++n) {
132 p[n] = ((2 * n - 1) * x * p[n - 1] - (n - 1) * p[n - 2]) / n;
150void compSumsMeg(
double beta,
double ctheta,
double sums[4])
152 double p[kNCoeff], pd[kNCoeff], pdd[kNCoeff];
153 computeLegendreDer(ctheta, kNCoeff, p, pd, pdd);
155 sums[0] = sums[1] = sums[2] = sums[3] = 0.0;
157 for (
int n = 1; n < kNCoeff; ++n) {
159 double dn =
static_cast<double>(n);
160 double multn = dn / (2.0 * dn + 1.0);
161 double mult = multn / (dn + 1.0);
163 sums[0] += (dn + 1.0) * multn * p[n] * betan;
164 sums[1] += multn * pd[n] * betan;
165 sums[2] += mult * pd[n] * betan;
166 sums[3] += mult * pdd[n] * betan;
174double compSumEeg(
double beta,
double ctheta)
177 computeLegendreVal(ctheta, kNCoeff, p);
181 for (
int n = 1; n < kNCoeff; ++n) {
183 double dn =
static_cast<double>(n);
184 double factor = 2.0 * dn + 1.0;
185 sum += p[n] * betan * factor * factor / dn;
202double sphereDotMeg(
double intrad,
203 const Vector3d& rr1,
double lr1,
const Vector3d& cosmag1,
204 const Vector3d& rr2,
double lr2,
const Vector3d& cosmag2)
206 if (lr1 == 0.0 || lr2 == 0.0)
return 0.0;
208 double beta = (intrad * intrad) / (lr1 * lr2);
209 double ct = std::clamp(rr1.dot(rr2), -1.0, 1.0);
212 compSumsMeg(beta, ct, sums);
214 double n1c1 = cosmag1.dot(rr1);
215 double n1c2 = cosmag1.dot(rr2);
216 double n2c1 = cosmag2.dot(rr1);
217 double n2c2 = cosmag2.dot(rr2);
218 double n1n2 = cosmag1.dot(cosmag2);
220 double part1 = ct * n1c1 * n2c2;
221 double part2 = n1c1 * n2c1 + n1c2 * n2c2;
223 double result = n1c1 * n2c2 * sums[0]
224 + (2.0 * part1 - part2) * sums[1]
225 + (n1n2 + part1 - part2) * sums[2]
226 + (n1c2 - ct * n1c1) * (n2c1 - ct * n2c2) * sums[3];
228 result *= kMegConst / (lr1 * lr2);
235double sphereDotEeg(
double intrad,
236 const Vector3d& rr1,
double lr1,
237 const Vector3d& rr2,
double lr2)
239 if (lr1 == 0.0 || lr2 == 0.0)
return 0.0;
241 double beta = (intrad * intrad) / (lr1 * lr2);
242 double ct = std::clamp(rr1.dot(rr2), -1.0, 1.0);
244 double sum = compSumEeg(beta, ct);
245 return kEegConst * sum / (lr1 * lr2);
256 std::vector<Vector3d> rmag;
257 std::vector<double> rlen;
258 std::vector<Vector3d> cosmag;
259 std::vector<double> w;
263Vector3d toVec3d(
const float* v) {
return Vector3d(v[0], v[1], v[2]); }
271CoilData extractCoilData(
const FwdCoil* coil,
const Vector3d& r0)
275 cd.rmag.resize(cd.np);
276 cd.rlen.resize(cd.np);
277 cd.cosmag.resize(cd.np);
280 for (
int i = 0; i < cd.np; ++i) {
281 Vector3d rel = toVec3d(coil->
rmag[i]) - r0;
282 double len = rel.norm();
283 cd.rmag[i] = (len > 0.0) ? Vector3d(rel / len) : Vector3d::Zero();
285 cd.cosmag[i] = toVec3d(coil->
cosmag[i]);
286 cd.w[i] =
static_cast<double>(coil->
w[i]);
302MatrixXd doSelfDots(
double intrad,
const FwdCoilSet& coils,
const Vector3d& r0,
bool isMeg)
304 const int nc = coils.
ncoil;
305 std::vector<CoilData> cdata(nc);
306 for (
int i = 0; i < nc; ++i) {
307 cdata[i] = extractCoilData(coils.
coils[i], r0);
310 MatrixXd products = MatrixXd::Zero(nc, nc);
311 for (
int ci1 = 0; ci1 < nc; ++ci1) {
312 for (
int ci2 = 0; ci2 <= ci1; ++ci2) {
314 const CoilData& c1 = cdata[ci1];
315 const CoilData& c2 = cdata[ci2];
316 for (
int i = 0; i < c1.np; ++i) {
317 for (
int j = 0; j < c2.np; ++j) {
318 double ww = c1.w[i] * c2.w[j];
320 dot += ww * sphereDotMeg(intrad,
321 c1.rmag[i], c1.rlen[i], c1.cosmag[i],
322 c2.rmag[j], c2.rlen[j], c2.cosmag[j]);
324 dot += ww * sphereDotEeg(intrad,
325 c1.rmag[i], c1.rlen[i],
326 c2.rmag[j], c2.rlen[j]);
330 products(ci1, ci2) = dot;
331 products(ci2, ci1) = dot;
347MatrixXd doSurfaceDots(
double intrad,
const FwdCoilSet& coils,
348 const MatrixX3f& rr,
const MatrixX3f& nn,
349 const Vector3d& r0,
bool isMeg)
351 const int nc = coils.
ncoil;
352 const int nv = rr.rows();
354 std::vector<CoilData> cdata(nc);
355 for (
int i = 0; i < nc; ++i) {
356 cdata[i] = extractCoilData(coils.
coils[i], r0);
359 MatrixXd products = MatrixXd::Zero(nv, nc);
360 for (
int vi = 0; vi < nv; ++vi) {
362 Vector3d rel = rr.row(vi).cast<
double>() - r0.transpose();
363 double lsurf = rel.norm();
364 Vector3d rsurf = (lsurf > 0.0) ? Vector3d(rel / lsurf) : Vector3d::Zero();
365 Vector3d nsurf = nn.row(vi).cast<
double>();
367 for (
int ci = 0; ci < nc; ++ci) {
368 const CoilData& c = cdata[ci];
370 for (
int j = 0; j < c.np; ++j) {
372 dot += c.w[j] * sphereDotMeg(intrad,
374 c.rmag[j], c.rlen[j], c.cosmag[j]);
376 dot += c.w[j] * sphereDotEeg(intrad,
378 c.rmag[j], c.rlen[j]);
381 products(vi, ci) = dot;
395 VectorXd stds(coils.
ncoil);
396 for (
int k = 0; k < coils.
ncoil; ++k) {
398 : static_cast<double>(kGradStd);
404VectorXd adHocEegStds(
int ncoil)
406 return VectorXd::Constant(ncoil,
static_cast<double>(kEegStd));
437QSharedPointer<MatrixXf> computeMappingMatrix(
const MatrixXd& selfDots,
438 const MatrixXd& surfaceDots,
439 const VectorXd& noiseStds,
441 const MatrixXd& projOp = MatrixXd(),
442 bool applyAvgRef =
false)
444 if (selfDots.rows() == 0 || surfaceDots.rows() == 0) {
445 return QSharedPointer<MatrixXf>();
448 const int nchan = selfDots.rows();
453 bool hasProj = (projOp.rows() == nchan && projOp.cols() == nchan);
455 projDots = projOp.transpose() * selfDots * projOp;
461 VectorXd whitener(nchan);
462 for (
int i = 0; i < nchan; ++i) {
463 whitener(i) = (noiseStds(i) > 0.0) ? (1.0 / noiseStds(i)) : 0.0;
467 MatrixXd whitenedDots = whitener.asDiagonal() * projDots * whitener.asDiagonal();
470 JacobiSVD<MatrixXd> svd(whitenedDots, ComputeFullU | ComputeFullV);
471 VectorXd s = svd.singularValues();
472 if (s.size() == 0 || s(0) <= 0.0) {
473 return QSharedPointer<MatrixXf>();
478 VectorXd varexp(s.size());
480 for (
int i = 1; i < s.size(); ++i) {
481 varexp(i) = varexp(i - 1) + s(i);
483 double totalVar = varexp(s.size() - 1);
486 for (
int i = 0; i < s.size(); ++i) {
487 if (varexp(i) / totalVar >= (1.0 - miss)) {
494 VectorXd sinv = VectorXd::Zero(s.size());
495 for (
int i = 0; i < n; ++i) {
496 sinv(i) = (s(i) > 0.0) ? (1.0 / s(i)) : 0.0;
498 MatrixXd inv = svd.matrixV() * sinv.asDiagonal() * svd.matrixU().transpose();
501 MatrixXd invWhitened = whitener.asDiagonal() * inv * whitener.asDiagonal();
505 MatrixXd invWhitenedProj;
507 invWhitenedProj = projOp.transpose() * invWhitened;
509 invWhitenedProj = invWhitened;
513 MatrixXd mapping = surfaceDots * invWhitenedProj;
517 VectorXd colMeans = mapping.colwise().mean();
518 mapping.rowwise() -= colMeans.transpose();
522 MatrixXf mappingF = mapping.cast<
float>();
523 return QSharedPointer<MatrixXf>::create(std::move(mappingF));
534 const MatrixX3f& vertices,
535 const MatrixX3f& normals,
536 const Vector3f& origin,
540 if (coils.
ncoil <= 0 || vertices.rows() == 0 || normals.rows() != vertices.rows()) {
541 return QSharedPointer<MatrixXf>();
544 const Vector3d r0 = origin.cast<
double>();
547 MatrixXd selfDots = doSelfDots(intrad, coils, r0,
true);
550 MatrixXd surfaceDots = doSurfaceDots(intrad, coils, vertices, normals, r0,
true);
553 VectorXd stds = adHocMegStds(coils);
555 return computeMappingMatrix(selfDots, surfaceDots, stds,
static_cast<double>(miss));
560 const MatrixX3f& vertices,
561 const MatrixX3f& normals,
562 const Vector3f& origin,
563 const FIFFLIB::FiffInfo& info,
564 const QStringList& chNames,
568 if (coils.
ncoil <= 0 || vertices.rows() == 0 || normals.rows() != vertices.rows()) {
569 return QSharedPointer<MatrixXf>();
572 const Vector3d r0 = origin.cast<
double>();
574 MatrixXd selfDots = doSelfDots(intrad, coils, r0,
true);
575 MatrixXd surfaceDots = doSurfaceDots(intrad, coils, vertices, normals, r0,
true);
576 VectorXd stds = adHocMegStds(coils);
582 return computeMappingMatrix(selfDots, surfaceDots, stds,
583 static_cast<double>(miss), projOp,
false);
588 const MatrixX3f& vertices,
589 const Vector3f& origin,
593 if (coils.
ncoil <= 0 || vertices.rows() == 0) {
594 return QSharedPointer<MatrixXf>();
598 const double eegIntrad = intrad * kEegIntradScale;
599 const Vector3d r0 = origin.cast<
double>();
603 MatrixX3f dummyNormals = MatrixX3f::Zero(vertices.rows(), 3);
606 MatrixXd selfDots = doSelfDots(eegIntrad, coils, r0,
false);
609 MatrixXd surfaceDots = doSurfaceDots(eegIntrad, coils, vertices, dummyNormals, r0,
false);
612 VectorXd stds = adHocEegStds(coils.
ncoil);
614 return computeMappingMatrix(selfDots, surfaceDots, stds,
static_cast<double>(miss));
619 const MatrixX3f& vertices,
620 const Vector3f& origin,
621 const FIFFLIB::FiffInfo& info,
622 const QStringList& chNames,
626 if (coils.
ncoil <= 0 || vertices.rows() == 0) {
627 return QSharedPointer<MatrixXf>();
630 const double eegIntrad = intrad * kEegIntradScale;
631 const Vector3d r0 = origin.cast<
double>();
633 MatrixX3f dummyNormals = MatrixX3f::Zero(vertices.rows(), 3);
635 MatrixXd selfDots = doSelfDots(eegIntrad, coils, r0,
false);
636 MatrixXd surfaceDots = doSurfaceDots(eegIntrad, coils, vertices, dummyNormals, r0,
false);
637 VectorXd stds = adHocEegStds(coils.
ncoil);
648 bool hasAvgRef =
false;
649 for (
const auto& proj : info.
projs) {
651 proj.desc.contains(QRegularExpression(
"^Average .* reference$",
652 QRegularExpression::CaseInsensitiveOption))) {
658 return computeMappingMatrix(selfDots, surfaceDots, stds,
659 static_cast<double>(miss), projOp, hasAvgRef);
FiffProj class declaration.
Header file describing the numerical values used in fif files.
#define FIFFV_PROJ_ITEM_EEG_AVREF
Sphere-model field mapping.
Forward modelling (BEM, MEG/EEG lead fields).
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 or EEG sensor coil with integration points, weights, and coordinate frame.
bool is_axial_coil() const
Collection of FwdCoil objects representing a full MEG or EEG sensor array.
static QSharedPointer< Eigen::MatrixXf > computeEegMapping(const FwdCoilSet &coils, const Eigen::MatrixX3f &vertices, const Eigen::Vector3f &origin, float intrad=0.06f, float miss=1e-3f)
static QSharedPointer< 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)