49constexpr int FAIL = -1;
51constexpr int MAXTERMS = 1000;
52constexpr double EPS = 1e-10;
53constexpr double SIN_EPS = 1e-3;
77 const VectorXf& sigmas)
82 auto new_model = std::make_unique<FwdEegSphereModel>();
84 new_model->name =
name;
86 for (
int k = 0; k <
nlayer; k++) {
89 layer.
sigma = sigmas[k];
90 new_model->layers.push_back(layer);
100 float R = new_model->layers[
nlayer - 1].rad;
101 float rR = new_model->layers[
nlayer - 1].rel_rad;
102 for (
int k = 0; k <
nlayer; k++) {
103 new_model->layers[k].rad = new_model->layers[k].rad /
R;
104 new_model->layers[k].rel_rad = new_model->layers[k].rel_rad / rR;
119 if (eeg_model_name.isEmpty())
120 eeg_model_name = QString(
"Default");
123 eeg_models->fwd_list_eeg_sphere_models();
130 if (!eeg_model->fwd_setup_eeg_sphere_model(eeg_sphere_rad,
true, 3)) {
134 qInfo(
"Using EEG sphere model \"%s\" with scalp radius %7.1f mm",
135 eeg_model->name.toUtf8().constData(), 1000 * eeg_sphere_rad);
143 auto u = std::make_unique<fitUserRec>();
145 u->resi.resize(
nterms - 1);
148 u->vv = MatrixXd::Zero(
nfit - 1,
nfit - 1);
149 u->sing.resize(
nfit);
161 MatrixXd M, Mn, help, Mm;
162 static MatrixXd mat1;
163 static MatrixXd mat2;
164 static MatrixXd mat3;
168 static VectorXd cr_mult;
169 double div, div_mult;
183 if (this->
nlayer() == 2) {
186 div_mult = 2.0 * n + 1;
187 b = pow(this->
layers[0].rel_rad, div_mult);
188 return div_mult / ((n1 + n * rel1) + b * n1 * (rel1 - 1.0));
189 }
else if (this->
nlayer() == 3) {
193 div_mult = 2.0 * n + 1.0;
194 b = pow(this->
layers[0].rel_rad, div_mult);
195 c = pow(this->
layers[1].rel_rad, div_mult);
196 div_mult = div_mult * div_mult;
197 div = (b * n * n1 * (rel1 - 1.0) * (rel2 - 1.0) + c * (rel1 * n + n1) * (rel2 * n + n1)) / c +
198 n1 * (b * (rel1 - 1.0) * (rel2 * n1 + n) + c * (rel1 * n + n1) * (rel2 - 1.0));
199 return div_mult / div;
206 c1.resize(this->
nlayer() - 1);
207 c2.resize(this->
nlayer() - 1);
208 cr.resize(this->
nlayer() - 1);
209 cr_mult.resize(this->
nlayer() - 1);
210 for (k = 0; k < this->
nlayer() - 1; k++) {
211 c1[k] = this->
layers[k].sigma / this->
layers[k + 1].sigma;
213 cr_mult[k] = this->
layers[k].rel_rad;
215 cr_mult[k] = cr_mult[k] * cr_mult[k];
217 if (mat1.cols() == 0)
218 mat1 = MatrixXd(2, 2);
219 if (mat2.cols() == 0)
220 mat2 = MatrixXd(2, 2);
221 if (mat3.cols() == 0)
222 mat3 = MatrixXd(2, 2);
227 for (k = 0; k < this->
nlayer() - 1; k++)
228 cr[k] = cr[k] * cr_mult[k];
235 M(0, 0) = M(1, 1) = 1.0;
236 M(0, 1) = M(1, 0) = 0.0;
238 div_mult = 2.0 * n + 1.0;
241 for (k = this->
nlayer() - 2; k >= 0; k--) {
242 Mm(0, 0) = (n + n1 * c1[k]);
243 Mm(0, 1) = n1 * c2[k] / cr[k];
244 Mm(1, 0) = n * c2[k] * cr[k];
245 Mm(1, 1) = n1 + n * c1[k];
247 Mn(0, 0) = Mm(0, 0) * M(0, 0) + Mm(0, 1) * M(1, 0);
248 Mn(0, 1) = Mm(0, 0) * M(0, 1) + Mm(0, 1) * M(1, 1);
249 Mn(1, 0) = Mm(1, 0) * M(0, 0) + Mm(1, 1) * M(1, 0);
250 Mn(1, 1) = Mm(1, 0) * M(0, 1) + Mm(1, 1) * M(1, 1);
254 div = div * div_mult;
256 return n * div / (n * M(1, 1) + n1 * M(1, 0));
275 p0 = ((2 * n - 1) * x * help0 - (n - 1) * (p01)) / n;
276 p1 = ((2 * n - 1) * x * help1 - n * (p11)) / (n - 1);
286 p1 = sqrt(1.0 - x * x);
297 double p0, p01, p1, p11;
302 p0 = p01 = p1 = p11 = 0.0;
303 for (n = 1; n <=
nterms; n++) {
308 multn = betan *
fn[n - 1];
309 Vr = Vr + multn * p0;
310 Vt = Vt + multn * p1 / n;
311 betan = beta * betan;
343 Eigen::Vector3f rd = rd_in - m->r0;
344 Eigen::Vector3f Q = Q_in;
347 float pos2, rd_len, pos_len;
348 double beta, cos_gamma, Vr, Vt;
349 Eigen::Vector3f vec1, vec2;
351 float cos_beta, Qr, Qt, Q2, c;
352 float pi4_inv =
static_cast<float>(0.25 / M_PI);
357 if (m->fn.size() == 0 || m->nterms != MAXTERMS) {
358 m->fn.resize(MAXTERMS);
359 m->nterms = MAXTERMS;
360 for (k = 0; k < MAXTERMS; k++)
361 m->fn[k] = (2 * k + 3) * m->fwd_eeg_get_multi_sphere_model_coeff(k + 1);
371 if (rd_len >= m->layers[0].rad) {
372 for (k = 0; k < neeg; k++)
379 c = rd.dot(Q) / (rd_len * sqrt(Q2));
380 if ((1.0 - c * c) < SIN_EPS) {
393 for (k = 0; k < neeg; k++) {
394 pos = el.row(k).transpose() - m->r0;
399 pos_len = m->layers[m->nlayer() - 1].rad / pos.norm();
401 qInfo(
"%10.4f %10.4f %10.4f %10.4f", pos_len, 1000 * pos[0], 1000 * pos[1], 1000 * pos[2]);
406 pos_len = sqrt(pos2);
410 cos_gamma = pos.dot(rd) / (rd_len * pos_len);
411 beta = rd_len / pos_len;
417 vec2 = rd.cross(pos);
421 cos_beta = vec1.dot(vec2) / (v1 * v2);
425 Qr = Q.dot(rd) / rd_len;
426 Qt = sqrt(Q2 - Qr * Qr);
428 Vval[k] =
static_cast<double>(pi4_inv) * (
static_cast<double>(Qr) * Vr +
static_cast<double>(Qt) * cos_beta * Vt) / pos2;
434 if (m->nlayer() > 0) {
435 sigmaM_inv = 1.0 / m->layers[m->nlayer() - 1].sigma;
436 for (k = 0; k < neeg; k++)
437 Vval[k] = Vval[k] * sigmaM_inv;
459 Vval.resize(els.
ncoil());
460 for (k = 0; k < els.
ncoil(); k++) {
461 el = els.
coils[k].get();
463 if (el->
np > nvval) {
464 vval_one.resize(el->
np);
470 for (c = 0, val = 0.0; c < el->
np; c++)
471 val += el->
w[c] * vval_one[c];
483 float fact = 0.25f /
static_cast<float>(M_PI);
484 Eigen::Vector3f a_vec;
486 float rrd, rd2, rd2_inv, r, r2, ra, rda;
488 float c1, c2, m1, m2;
490 Eigen::Vector3f orig_rd = rd_in - m->r0;
497 for (k = 0; k < neeg; k++) {
498 Vval_vec(0, k) = 0.0;
499 Vval_vec(1, k) = 0.0;
500 Vval_vec(2, k) = 0.0;
505 if (orig_rd.norm() >= m->layers[0].rad)
512 eeg_set_homog_sphere_model();
517 for (eq = 0; eq < m->nfit; eq++) {
521 rd = m->mu[eq] * orig_rd;
529 for (k = 0; k < neeg; k++) {
530 pos = el.row(k).transpose() - m->r0;
535 pos_len = m->layers[m->nlayer() - 1].rad / pos.norm();
545 a2 = a_vec.dot(a_vec);
556 F = a * (r * a + ra);
557 c1 = a3 * rda + 1.0 / a - 1.0 / r;
558 c2 = a3 + (a + r) / (r * F);
562 m1 = (c1 - c2 * rrd);
565 Vval_vec(0, k) = Vval_vec(0, k) + m->lambda[eq] * rd2_inv * (m1 * rd[0] + m2 * pos[0]);
566 Vval_vec(1, k) = Vval_vec(1, k) + m->lambda[eq] * rd2_inv * (m1 * rd[1] + m2 * pos[1]);
567 Vval_vec(2, k) = Vval_vec(2, k) + m->lambda[eq] * rd2_inv * (m1 * rd[2] + m2 * pos[2]);
573 for (k = 0; k < neeg; k++) {
574 Vval_vec(0, k) = fact * Vval_vec(0, k);
575 Vval_vec(1, k) = fact * Vval_vec(1, k);
576 Vval_vec(2, k) = fact * Vval_vec(2, k);
585 Eigen::MatrixXf vval_one;
591 for (k = 0; k < els.
ncoil(); k++) {
592 el = els.
coils[k].get();
594 if (el->
np > nvval) {
595 vval_one.resize(3, el->
np);
601 for (p = 0; p < 3; p++) {
602 for (c = 0, val = 0.0; c < el->
np; c++)
603 val += el->
w[c] * vval_one(p, c);
604 Vval_vec(p, k) = val;
622 Eigen::Vector3f my_rd;
623 float step = 0.0005f;
624 float step2 = 2 * step;
627 Eigen::Ref<Eigen::VectorXf>* grads[3] = {&xgrad, &ygrad, &zgrad};
629 for (p = 0; p < 3; p++) {
638 for (q = 0; q < coils.
ncoil(); q++)
639 (*grads[p])[q] = ((*grads[p])[q] - Vval[q]) / step2;
649 const Eigen::Vector3f& Q_in,
650 const Eigen::Matrix<float, Eigen::Dynamic, 3, Eigen::RowMajor>& el,
662 float fact =
static_cast<float>(0.25 / M_PI);
663 Eigen::Vector3f a_vec;
665 float rrd, rd2, rd2_inv, r, r2, ra, rda;
667 float c1, c2, m1, m2, f1, f2;
669 Eigen::Vector3f orig_rd = rd_in - m->r0;
671 Eigen::Vector3f Q = Q_in;
677 for (k = 0; k < neeg; k++)
682 if (orig_rd.norm() >= m->layers[0].rad)
689 eeg_set_homog_sphere_model();
694 for (eq = 0; eq < m->nfit; eq++) {
698 rd = m->mu[eq] * orig_rd;
707 for (k = 0; k < neeg; k++) {
708 pos = el.row(k).transpose() - m->r0;
713 pos_len = m->layers[m->nlayer() - 1].rad / pos.norm();
723 a2 = a_vec.dot(a_vec);
734 F = a * (r * a + ra);
735 c1 = a3 * rda + 1.0 / a - 1.0 / r;
736 c2 = a3 + (a + r) / (r * F);
740 m1 = (c1 - c2 * rrd);
744 Vval[k] = Vval[k] + m->lambda[eq] * rd2_inv * (m1 * f1 + m2 * f2);
750 for (k = 0; k < neeg; k++)
751 Vval[k] = fact * Vval[k];
765 for (k = 0; k < els.
ncoil(); k++) {
766 el = els.
coils[k].get();
768 if (el->
np > nvval) {
769 vval_one.resize(el->
np);
775 for (c = 0, val = 0.0; c < el->
np; c++)
776 val += el->
w[c] * vval_one[c];
793 for (
int k = 0; k < this->
nlayer(); k++)
796 if (fit_berg_scherg) {
798 qInfo(
"Equiv. model fitting -> RV = %g %%", 100 * rv);
799 for (
int k = 0; k < nFit; k++)
800 qInfo(
"mu%d = %g\tlambda%d = %g", k + 1, this->
mu[k], k + 1, this->
layers[this->
nlayer() - 1].sigma * this->
lambda[k]);
805 qInfo(
"Defined EEG sphere model with rad = %7.2f mm", 1000.0 * rad);
809static void compute_svd(Eigen::MatrixXd& mat,
810 Eigen::VectorXd& sing,
819 int udim = std::min(
static_cast<int>(mat.rows()),
static_cast<int>(mat.cols()));
821 Eigen::JacobiSVD<Eigen::MatrixXd>
svd(mat, Eigen::ComputeFullU | Eigen::ComputeFullV);
823 sing =
svd.singularValues();
824 uu =
svd.matrixU().transpose().topRows(udim);
827 *vv =
svd.matrixV().transpose();
850static void sort_parameters(VectorXd& mu, VectorXd& lambda,
int nfit)
852 std::vector<BergSchergPar> pars(nfit);
854 for (
int k = 0; k < nfit; k++) {
856 pars[k].lambda = lambda[k];
863 for (
int k = 0; k < nfit; k++) {
865 lambda[k] = pars[k].lambda;
869static bool report_fit([[maybe_unused]]
int loop,
870 [[maybe_unused]]
const VectorXd& fitpar,
871 [[maybe_unused]]
double Smin,
876 for (
int k = 0; k < fitpar.size(); k++)
883static MatrixXd get_initial_simplex(
const VectorXd& pars,
887 int npar = pars.size();
889 MatrixXd simplex = MatrixXd::Zero(npar + 1, npar);
891 simplex.rowwise() += pars.transpose();
893 for (
int k = 1; k < npar + 1; k++)
894 simplex(k, k - 1) += simplex_size;
907 for (k = 0; k < u->
nterms - 1; k++) {
909 mu1n = pow(
mu[0], k1);
910 u->
y[k] = u->
w[k] * (u->
fn[k + 1] - mu1n * u->
fn[0]);
911 for (p = 0; p < u->
nfit - 1; p++)
912 u->
M(k, p) = u->
w[k] * (pow(
mu[p + 1], k1) - mu1n);
926 VectorXd vec(u->
nfit - 1);
931 compute_svd(u->
M, u->
sing, u->
uu, &u->
vv);
935 for (k = 0; k < u->
nterms - 1; k++)
936 u->
resi[k] = u->
y[k];
938 for (p = 0; p < u->
nfit - 1; p++) {
939 vec[p] = u->
uu.row(p).head(u->
nterms - 1).dot(u->
y.head(u->
nterms - 1));
940 for (k = 0; k < u->
nterms - 1; k++)
941 u->
resi[k] = u->
resi[k] - u->
uu(p, k) * vec[p];
942 vec[p] = vec[p] / u->
sing[p];
945 for (p = 0; p < u->
nfit - 1; p++) {
946 for (q = 0, sum = 0.0; q < u->
nfit - 1; q++)
947 sum += u->
vv(q, p) * vec[q];
950 for (p = 1, sum = 0.0; p < u->
nfit; p++)
953 return u->
resi.head(u->
nterms - 1).squaredNorm() / u->
y.head(u->
nterms - 1).squaredNorm();
967 for (k = 0; k < u->
nfit; k++) {
968 if (std::fabs(
mu[k]) > 1.0)
978 compute_svd(u->
M, u->
sing, u->
uu,
nullptr);
982 for (k = 0; k < u->
nterms - 1; k++)
983 u->
resi[k] = u->
y[k];
984 for (p = 0; p < u->
nfit - 1; p++) {
986 for (k = 0; k < u->
nterms - 1; k++)
992 return u->
resi.head(u->
nterms - 1).squaredNorm();
1009 double simplex_size = 0.01;
1016 int max_eval = 1000;
1021 qWarning(
"fwd_fit_berg_scherg does not work with less than two equivalent sources.");
1028 for (k = 0; k < nTerms; k++)
1029 u->fn[k] = this->fwd_eeg_get_multi_sphere_model_coeff(k + 1);
1035 for (k = 1; k < this->
nlayer(); k++) {
1038 if (this->
layers[k].rad < rd)
1039 rd = this->
layers[k].rad;
1047 for (k = 1; k < nTerms; k++)
1048 u->w[k - 1] = pow(f, k);
1053 for (k = 1; k < nTerms; k++)
1054 u->w[k - 1] = sqrt((2.0 * k + 1) * (3.0 * k + 1.0) / k) * pow(f, (k - 1.0));
1060 func_val = VectorXd(nFit + 1);
1061 lambdaFit = VectorXd(nFit);
1062 muFit = VectorXd(nFit);
1066 for (k = 0; k < nFit; k++) {
1067 muFit[k] = (k + 1) * 0.1 * f;
1070 simplex = get_initial_simplex(muFit, simplex_size);
1071 for (k = 0; k < nFit + 1; k++)
1072 func_val[k] =
one_step(VectorXd(simplex.row(k).transpose()), *u);
1075 auto cost = [&u](
const VectorXd& x) ->
double {
1093 for (k = 0; k < nFit; k++)
1094 muFit[k] = simplex(0, k);
1101 sort_parameters(muFit, lambdaFit, nFit);
1103 qInfo(
"RV = %g %%", 100 * rv);
1105 this->mu.resize(nFit);
1106 this->lambda.resize(nFit);
1108 for (k = 0; k < nFit; k++) {
1109 this->mu[k] = muFit[k];
1113 this->lambda[k] = lambdaFit[k] / this->
layers[this->
nlayer() - 1].sigma;
1115 qInfo(
"lambdaFit%d = %g\tmu%d = %g", k + 1, lambdaFit[k], k + 1, muFit[k]);
Eigen::JacobiSVD< Eigen::Matrix3f > svd(S, Eigen::ComputeFullU|Eigen::ComputeFullV)
Multi-shell spherical head model with Berg-Scherg equivalent-source approximation for fast EEG forwar...
Named container of FwdEegSphereModel objects loaded from an mne_setup_eeg_sphere_model parameter file...
Header-only Nelder–Mead simplex minimiser with pluggable cost and report callables.
Shared utilities (I/O helpers, spectral analysis, layout management, warp algorithms).
Forward modelling — BEM solver, spherical models, sensor/coil definitions and the lead-field assembly...
constexpr int FWD_COILC_EEG
Single MEG sensor coil or EEG electrode — stores the coil-local frame and the (r_mag,...
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
One concentric shell (outer radius rad, conductivity sigma and the derived ratios) of a multi-shell d...
static bool comp_layers(const FwdEegSphereLayer &v1, const FwdEegSphereLayer &v2)
Berg-Scherg parameter pair (magnitude and distance multiplier) for an equivalent dipole in the EEG sp...
Workspace for the linear least-squares fit of Berg-Scherg parameters in the EEG sphere model (SVD mat...
Multi-shell concentric-sphere head model holding the Berg-Scherg equivalent-source parameters that ac...
static double compute_linear_parameters(const Eigen::VectorXd &mu, Eigen::VectorXd &lambda, fitUser u)
static int fwd_eeg_multi_spherepot_coil1(const Eigen::Vector3f &rd, const Eigen::Vector3f &Q, FwdCoilSet &els, Eigen::Ref< Eigen::VectorXf > Vval, void *client)
static FwdEegSphereModel::UPtr setup_eeg_sphere_model(const QString &eeg_model_file, QString eeg_model_name, float eeg_sphere_rad)
bool fwd_setup_eeg_sphere_model(float rad, bool fit_berg_scherg, int nFit)
static void calc_pot_components(double beta, double cgamma, double &Vrp, double &Vtp, const Eigen::VectorXd &fn, int nterms)
static int fwd_eeg_spherepot(const Eigen::Vector3f &rd, const Eigen::Vector3f &Q, const Eigen::Matrix< float, Eigen::Dynamic, 3, Eigen::RowMajor > &el, int neeg, Eigen::VectorXf &Vval, void *client)
static bool fwd_eeg_spherepot_vec(const Eigen::Vector3f &rd, const Eigen::Matrix< float, Eigen::Dynamic, 3, Eigen::RowMajor > &el, int neeg, Eigen::MatrixXf &Vval_vec, void *client)
static void compose_linear_fitting_data(const Eigen::VectorXd &mu, fitUser u)
static int fwd_eeg_spherepot_grad_coil(const Eigen::Vector3f &rd, const Eigen::Vector3f &Q, FwdCoilSet &coils, Eigen::Ref< Eigen::VectorXf > Vval, Eigen::Ref< Eigen::VectorXf > xgrad, Eigen::Ref< Eigen::VectorXf > ygrad, Eigen::Ref< Eigen::VectorXf > zgrad, void *client)
static int fwd_eeg_multi_spherepot(const Eigen::Vector3f &rd, const Eigen::Vector3f &Q, const Eigen::Matrix< float, Eigen::Dynamic, 3, Eigen::RowMajor > &el, int neeg, Eigen::VectorXf &Vval, void *client)
bool fwd_eeg_fit_berg_scherg(int nTerms, int nFit, float &rv)
static int fwd_eeg_spherepot_coil_vec(const Eigen::Vector3f &rd, FwdCoilSet &els, Eigen::Ref< Eigen::MatrixXf > Vval_vec, void *client)
static FwdEegSphereModel::UPtr fwd_create_eeg_sphere_model(const QString &name, int nlayer, const Eigen::VectorXf &rads, const Eigen::VectorXf &sigmas)
std::vector< FwdEegSphereLayer > layers
virtual ~FwdEegSphereModel()
static void next_legen(int n, double x, double &p0, double &p01, double &p1, double &p11)
std::unique_ptr< FwdEegSphereModel > UPtr
static std::unique_ptr< fitUserRec > new_fit_user(int nfit, int nterms)
static int fwd_eeg_spherepot_coil(const Eigen::Vector3f &rd, const Eigen::Vector3f &Q, FwdCoilSet &els, Eigen::Ref< Eigen::VectorXf > Vval, void *client)
double fwd_eeg_get_multi_sphere_model_coeff(int n)
static double one_step(const Eigen::VectorXd &mu, fitUserRec &u)
std::unique_ptr< FwdEegSphereModelSet > UPtr
static UPtr fwd_load_eeg_sphere_models(const QString &p_sFileName, UPtr now)
static bool simplex_minimize(Eigen::Matrix< T, Eigen::Dynamic, Eigen::Dynamic > &p, Eigen::Matrix< T, Eigen::Dynamic, 1 > &y, T ftol, T stol, CostFunc &&func, int max_eval, int &neval, int report, ReportFunc &&report_func)