55#include <QRegularExpression>
73constexpr int kNCoeff = 100;
74constexpr double kMegConst = 4e-14 *
M_PI;
75constexpr double kEegConst = 1.0 / (4.0 *
M_PI);
76constexpr double kEegIntradScale = 0.7;
78constexpr float kGradStd = 5e-13f;
79constexpr float kMagStd = 20e-15f;
80constexpr float kEegStd = 1e-6f;
85void computeLegendreDer(
double x,
int ncoeff,
86 double* p,
double* pd,
double* pdd)
88 p[0] = 1.0; pd[0] = 0.0; pdd[0] = 0.0;
89 if (ncoeff < 2)
return;
90 p[1] = x; pd[1] = 1.0; pdd[1] = 0.0;
91 for (
int n = 2; n < ncoeff; ++n) {
92 double old_p = p[n - 1];
93 double old_pd = pd[n - 1];
94 p[n] = ((2 * n - 1) * x * old_p - (n - 1) * p[n - 2]) / n;
95 pd[n] = n * old_p + x * old_pd;
96 pdd[n] = (n + 1) * old_pd + x * pdd[n - 1];
103void computeLegendreVal(
double x,
int ncoeff,
double* p)
106 if (ncoeff < 2)
return;
108 for (
int n = 2; n < ncoeff; ++n) {
109 p[n] = ((2 * n - 1) * x * p[n - 1] - (n - 1) * p[n - 2]) / n;
116void compSumsMeg(
double beta,
double ctheta,
double sums[4])
118 double p[kNCoeff], pd[kNCoeff], pdd[kNCoeff];
119 computeLegendreDer(ctheta, kNCoeff, p, pd, pdd);
121 sums[0] = sums[1] = sums[2] = sums[3] = 0.0;
123 for (
int n = 1; n < kNCoeff; ++n) {
125 double dn =
static_cast<double>(n);
126 double multn = dn / (2.0 * dn + 1.0);
127 double mult = multn / (dn + 1.0);
129 sums[0] += (dn + 1.0) * multn * p[n] * betan;
130 sums[1] += multn * pd[n] * betan;
131 sums[2] += mult * pd[n] * betan;
132 sums[3] += mult * pdd[n] * betan;
139double compSumEeg(
double beta,
double ctheta)
142 computeLegendreVal(ctheta, kNCoeff, p);
146 for (
int n = 1; n < kNCoeff; ++n) {
148 double dn =
static_cast<double>(n);
149 double factor = 2.0 * dn + 1.0;
150 sum += p[n] * betan * factor * factor / dn;
158double sphereDotMeg(
double intrad,
159 const Vector3d& rr1,
double lr1,
const Vector3d& cosmag1,
160 const Vector3d& rr2,
double lr2,
const Vector3d& cosmag2)
162 if (lr1 == 0.0 || lr2 == 0.0)
return 0.0;
164 double beta = (intrad * intrad) / (lr1 * lr2);
165 double ct = std::clamp(rr1.dot(rr2), -1.0, 1.0);
168 compSumsMeg(beta, ct, sums);
170 double n1c1 = cosmag1.dot(rr1);
171 double n1c2 = cosmag1.dot(rr2);
172 double n2c1 = cosmag2.dot(rr1);
173 double n2c2 = cosmag2.dot(rr2);
174 double n1n2 = cosmag1.dot(cosmag2);
176 double part1 = ct * n1c1 * n2c2;
177 double part2 = n1c1 * n2c1 + n1c2 * n2c2;
179 double result = n1c1 * n2c2 * sums[0]
180 + (2.0 * part1 - part2) * sums[1]
181 + (n1n2 + part1 - part2) * sums[2]
182 + (n1c2 - ct * n1c1) * (n2c1 - ct * n2c2) * sums[3];
184 result *= kMegConst / (lr1 * lr2);
191double sphereDotEeg(
double intrad,
192 const Vector3d& rr1,
double lr1,
193 const Vector3d& rr2,
double lr2)
195 if (lr1 == 0.0 || lr2 == 0.0)
return 0.0;
197 double beta = (intrad * intrad) / (lr1 * lr2);
198 double ct = std::clamp(rr1.dot(rr2), -1.0, 1.0);
200 double sum = compSumEeg(beta, ct);
201 return kEegConst * sum / (lr1 * lr2);
209 Eigen::MatrixX3d rmag;
210 Eigen::VectorXd rlen;
211 Eigen::MatrixX3d cosmag;
214 int np()
const {
return static_cast<int>(rlen.size()); }
220CoilData extractCoilData(
const FwdCoil* coil,
const Vector3d& r0)
223 const int n = coil->
np;
224 cd.rmag.resize(n, 3);
226 cd.cosmag.resize(n, 3);
229 for (
int i = 0; i < n; ++i) {
230 Vector3d rel = coil->
rmag.row(i).cast<
double>().transpose() - r0;
231 double len = rel.norm();
233 cd.rmag.row(i) = (rel / len).transpose();
235 cd.rmag.row(i).setZero();
237 cd.cosmag.row(i) = coil->
cosmag.row(i).cast<
double>();
238 cd.w(i) =
static_cast<double>(coil->
w[i]);
246MatrixXd doSelfDots(
double intrad,
const FwdCoilSet& coils,
const Vector3d& r0,
bool isMeg)
248 const int nc = coils.
ncoil();
249 std::vector<CoilData> cdata(nc);
250 for (
int i = 0; i < nc; ++i) {
251 cdata[i] = extractCoilData(coils.
coils[i].get(), r0);
254 MatrixXd products = MatrixXd::Zero(nc, nc);
255 for (
int ci1 = 0; ci1 < nc; ++ci1) {
256 for (
int ci2 = 0; ci2 <= ci1; ++ci2) {
258 const CoilData& c1 = cdata[ci1];
259 const CoilData& c2 = cdata[ci2];
260 for (
int i = 0; i < c1.np(); ++i) {
261 for (
int j = 0; j < c2.np(); ++j) {
262 double ww = c1.w(i) * c2.w(j);
264 dot += ww * sphereDotMeg(intrad,
265 c1.rmag.row(i).transpose(), c1.rlen(i), c1.cosmag.row(i).transpose(),
266 c2.rmag.row(j).transpose(), c2.rlen(j), c2.cosmag.row(j).transpose());
268 dot += ww * sphereDotEeg(intrad,
269 c1.rmag.row(i).transpose(), c1.rlen(i),
270 c2.rmag.row(j).transpose(), c2.rlen(j));
274 products(ci1, ci2) = dot;
275 products(ci2, ci1) = dot;
284MatrixXd doSurfaceDots(
double intrad,
const FwdCoilSet& coils,
285 const MatrixX3f& rr,
const MatrixX3f& nn,
286 const Vector3d& r0,
bool isMeg)
288 const int nc = coils.
ncoil();
289 const int nv = rr.rows();
291 std::vector<CoilData> cdata(nc);
292 for (
int i = 0; i < nc; ++i) {
293 cdata[i] = extractCoilData(coils.
coils[i].get(), r0);
296 MatrixXd products = MatrixXd::Zero(nv, nc);
297 for (
int vi = 0; vi < nv; ++vi) {
299 Vector3d rel = rr.row(vi).cast<
double>() - r0.transpose();
300 double lsurf = rel.norm();
301 Vector3d rsurf = (lsurf > 0.0) ? Vector3d(rel / lsurf) : Vector3d::Zero();
302 Vector3d nsurf = nn.row(vi).cast<
double>();
304 for (
int ci = 0; ci < nc; ++ci) {
305 const CoilData& c = cdata[ci];
307 for (
int j = 0; j < c.np(); ++j) {
309 dot += c.w(j) * sphereDotMeg(intrad,
311 c.rmag.row(j).transpose(), c.rlen(j), c.cosmag.row(j).transpose());
313 dot += c.w(j) * sphereDotEeg(intrad,
315 c.rmag.row(j).transpose(), c.rlen(j));
318 products(vi, ci) = dot;
329 VectorXd stds(coils.
ncoil());
330 for (
int k = 0; k < coils.
ncoil(); ++k) {
331 stds(k) = coils.
coils[k]->is_axial_coil() ?
static_cast<double>(kMagStd)
332 : static_cast<double>(kGradStd);
340VectorXd adHocEegStds(
int ncoil)
342 return VectorXd::Constant(ncoil,
static_cast<double>(kEegStd));
349std::unique_ptr<MatrixXf> computeMappingMatrix(
const MatrixXd& selfDots,
350 const MatrixXd& surfaceDots,
351 const VectorXd& noiseStds,
353 const MatrixXd& projOp = MatrixXd(),
354 bool applyAvgRef =
false)
356 if (selfDots.rows() == 0 || surfaceDots.rows() == 0) {
360 const int nchan = selfDots.rows();
364 bool hasProj = (projOp.rows() == nchan && projOp.cols() == nchan);
366 projDots = projOp.transpose() * selfDots * projOp;
372 VectorXd whitener(nchan);
373 for (
int i = 0; i < nchan; ++i) {
374 whitener(i) = (noiseStds(i) > 0.0) ? (1.0 / noiseStds(i)) : 0.0;
378 MatrixXd whitenedDots = whitener.asDiagonal() * projDots * whitener.asDiagonal();
381 JacobiSVD<MatrixXd> svd(whitenedDots, ComputeFullU | ComputeFullV);
382 VectorXd s = svd.singularValues();
383 if (s.size() == 0 || s(0) <= 0.0) {
388 VectorXd varexp(s.size());
390 for (
int i = 1; i < s.size(); ++i) {
391 varexp(i) = varexp(i - 1) + s(i);
393 double totalVar = varexp(s.size() - 1);
396 for (
int i = 0; i < s.size(); ++i) {
397 if (varexp(i) / totalVar >= (1.0 - miss)) {
404 VectorXd sinv = VectorXd::Zero(s.size());
405 for (
int i = 0; i < n; ++i) {
406 sinv(i) = (s(i) > 0.0) ? (1.0 / s(i)) : 0.0;
408 MatrixXd inv = svd.matrixV() * sinv.asDiagonal() * svd.matrixU().transpose();
411 MatrixXd invWhitened = whitener.asDiagonal() * inv * whitener.asDiagonal();
414 MatrixXd invWhitenedProj;
416 invWhitenedProj = projOp.transpose() * invWhitened;
418 invWhitenedProj = invWhitened;
422 MatrixXd mapping = surfaceDots * invWhitenedProj;
426 VectorXd colMeans = mapping.colwise().mean();
427 mapping.rowwise() -= colMeans.transpose();
431 MatrixXf mappingF = mapping.cast<
float>();
432 return std::make_unique<MatrixXf>(std::move(mappingF));
443 const MatrixX3f& vertices,
444 const MatrixX3f& normals,
445 const Vector3f& origin,
449 if (coils.
ncoil() <= 0 || vertices.rows() == 0 || normals.rows() != vertices.rows()) {
453 const Vector3d r0 = origin.cast<
double>();
455 MatrixXd selfDots = doSelfDots(intrad, coils, r0,
true);
456 MatrixXd surfaceDots = doSurfaceDots(intrad, coils, vertices, normals, r0,
true);
457 VectorXd stds = adHocMegStds(coils);
459 return computeMappingMatrix(selfDots, surfaceDots, stds,
static_cast<double>(miss));
466 const MatrixX3f& vertices,
467 const MatrixX3f& normals,
468 const Vector3f& origin,
469 const FIFFLIB::FiffInfo& info,
470 const QStringList& chNames,
474 if (coils.
ncoil() <= 0 || vertices.rows() == 0 || normals.rows() != vertices.rows()) {
478 const Vector3d r0 = origin.cast<
double>();
480 MatrixXd selfDots = doSelfDots(intrad, coils, r0,
true);
481 MatrixXd surfaceDots = doSurfaceDots(intrad, coils, vertices, normals, r0,
true);
482 VectorXd stds = adHocMegStds(coils);
488 return computeMappingMatrix(selfDots, surfaceDots, stds,
489 static_cast<double>(miss), projOp,
false);
496 const MatrixX3f& vertices,
497 const Vector3f& origin,
501 if (coils.
ncoil() <= 0 || vertices.rows() == 0) {
505 const double eegIntrad = intrad * kEegIntradScale;
506 const Vector3d r0 = origin.cast<
double>();
509 MatrixX3f dummyNormals = MatrixX3f::Zero(vertices.rows(), 3);
511 MatrixXd selfDots = doSelfDots(eegIntrad, coils, r0,
false);
512 MatrixXd surfaceDots = doSurfaceDots(eegIntrad, coils, vertices, dummyNormals, r0,
false);
513 VectorXd stds = adHocEegStds(coils.
ncoil());
515 return computeMappingMatrix(selfDots, surfaceDots, stds,
static_cast<double>(miss));
522 const MatrixX3f& vertices,
523 const Vector3f& origin,
524 const FIFFLIB::FiffInfo& info,
525 const QStringList& chNames,
529 if (coils.
ncoil() <= 0 || vertices.rows() == 0) {
533 const double eegIntrad = intrad * kEegIntradScale;
534 const Vector3d r0 = origin.cast<
double>();
536 MatrixX3f dummyNormals = MatrixX3f::Zero(vertices.rows(), 3);
537 MatrixXd selfDots = doSelfDots(eegIntrad, coils, r0,
false);
538 MatrixXd surfaceDots = doSurfaceDots(eegIntrad, coils, vertices, dummyNormals, r0,
false);
539 VectorXd stds = adHocEegStds(coils.
ncoil());
546 bool hasAvgRef =
false;
547 for (
const auto& proj : info.
projs) {
549 proj.desc.contains(QRegularExpression(
"^Average .* reference$",
550 QRegularExpression::CaseInsensitiveOption))) {
556 return computeMappingMatrix(selfDots, surfaceDots, stds,
557 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
FwdFieldMap class declaration.
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.
Eigen::Matrix< float, Eigen::Dynamic, 3, Eigen::RowMajor > cosmag
Eigen::Matrix< float, Eigen::Dynamic, 3, Eigen::RowMajor > rmag
Collection of FwdCoil objects representing a full MEG or EEG sensor array.
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)