49constexpr int FAIL = -1;
51constexpr int MAXTERMS = 1000;
52constexpr double EPS = 1e-10;
53constexpr double SIN_EPS = 1e-3;
74 if (!p_FwdEegSphereModel.
name.isEmpty())
75 this->
name = p_FwdEegSphereModel.
name;
76 if (p_FwdEegSphereModel.
nlayer() > 0) {
77 for (k = 0; k < p_FwdEegSphereModel.
nlayer(); k++)
80 this->
r0 = p_FwdEegSphereModel.
r0;
81 if (p_FwdEegSphereModel.
nterms > 0) {
82 this->
fn = VectorXd(p_FwdEegSphereModel.
nterms);
84 for (k = 0; k < p_FwdEegSphereModel.
nterms; k++)
85 this->
fn[k] = p_FwdEegSphereModel.
fn[k];
87 if (p_FwdEegSphereModel.
nfit > 0) {
88 this->
mu = VectorXf(p_FwdEegSphereModel.
nfit);
89 this->
lambda = VectorXf(p_FwdEegSphereModel.
nfit);
90 this->
nfit = p_FwdEegSphereModel.
nfit;
91 for (k = 0; k < p_FwdEegSphereModel.
nfit; k++) {
92 this->
mu[k] = p_FwdEegSphereModel.
mu[k];
103 const VectorXf& rads,
104 const VectorXf& sigmas)
109 auto new_model = std::make_unique<FwdEegSphereModel>();
111 new_model->name =
name;
113 for (
int k = 0; k <
nlayer; k++) {
116 layer.
sigma = sigmas[k];
117 new_model->layers.push_back(layer);
127 float R = new_model->layers[
nlayer - 1].rad;
128 float rR = new_model->layers[
nlayer - 1].rel_rad;
129 for (
int k = 0; k <
nlayer; k++) {
130 new_model->layers[k].rad = new_model->layers[k].rad /
R;
131 new_model->layers[k].rel_rad = new_model->layers[k].rel_rad / rR;
146 if (eeg_model_name.isEmpty())
147 eeg_model_name = QString(
"Default");
150 eeg_models->fwd_list_eeg_sphere_models();
157 if (!eeg_model->fwd_setup_eeg_sphere_model(eeg_sphere_rad,
true, 3)) {
161 qInfo(
"Using EEG sphere model \"%s\" with scalp radius %7.1f mm",
162 eeg_model->name.toUtf8().constData(), 1000 * eeg_sphere_rad);
189 MatrixXd M, Mn, help, Mm;
190 static MatrixXd mat1;
191 static MatrixXd mat2;
192 static MatrixXd mat3;
196 static VectorXd cr_mult;
197 double div, div_mult;
211 if (this->
nlayer() == 2) {
214 div_mult = 2.0 * n + 1;
215 b = pow(this->
layers[0].rel_rad, div_mult);
216 return div_mult / ((n1 + n * rel1) + b * n1 * (rel1 - 1.0));
217 }
else if (this->
nlayer() == 3) {
221 div_mult = 2.0 * n + 1.0;
222 b = pow(this->
layers[0].rel_rad, div_mult);
223 c = pow(this->
layers[1].rel_rad, div_mult);
224 div_mult = div_mult * div_mult;
225 div = (b * n * n1 * (rel1 - 1.0) * (rel2 - 1.0) + c * (rel1 * n + n1) * (rel2 * n + n1)) / c +
226 n1 * (b * (rel1 - 1.0) * (rel2 * n1 + n) + c * (rel1 * n + n1) * (rel2 - 1.0));
227 return div_mult / div;
234 c1.resize(this->
nlayer() - 1);
235 c2.resize(this->
nlayer() - 1);
236 cr.resize(this->
nlayer() - 1);
237 cr_mult.resize(this->
nlayer() - 1);
238 for (k = 0; k < this->
nlayer() - 1; k++) {
239 c1[k] = this->
layers[k].sigma / this->
layers[k + 1].sigma;
241 cr_mult[k] = this->
layers[k].rel_rad;
243 cr_mult[k] = cr_mult[k] * cr_mult[k];
245 if (mat1.cols() == 0)
246 mat1 = MatrixXd(2, 2);
247 if (mat2.cols() == 0)
248 mat2 = MatrixXd(2, 2);
249 if (mat3.cols() == 0)
250 mat3 = MatrixXd(2, 2);
255 for (k = 0; k < this->
nlayer() - 1; k++)
256 cr[k] = cr[k] * cr_mult[k];
263 M(0, 0) = M(1, 1) = 1.0;
264 M(0, 1) = M(1, 0) = 0.0;
266 div_mult = 2.0 * n + 1.0;
269 for (k = this->
nlayer() - 2; k >= 0; k--) {
270 Mm(0, 0) = (n + n1 * c1[k]);
271 Mm(0, 1) = n1 * c2[k] / cr[k];
272 Mm(1, 0) = n * c2[k] * cr[k];
273 Mm(1, 1) = n1 + n * c1[k];
275 Mn(0, 0) = Mm(0, 0) * M(0, 0) + Mm(0, 1) * M(1, 0);
276 Mn(0, 1) = Mm(0, 0) * M(0, 1) + Mm(0, 1) * M(1, 1);
277 Mn(1, 0) = Mm(1, 0) * M(0, 0) + Mm(1, 1) * M(1, 0);
278 Mn(1, 1) = Mm(1, 0) * M(0, 1) + Mm(1, 1) * M(1, 1);
282 div = div * div_mult;
284 return n * div / (n * M(1, 1) + n1 * M(1, 0));
303 p0 = ((2 * n - 1) * x * help0 - (n - 1) * (p01)) / n;
304 p1 = ((2 * n - 1) * x * help1 - n * (p11)) / (n - 1);
314 p1 = sqrt(1.0 - x * x);
325 double p0, p01, p1, p11;
330 p0 = p01 = p1 = p11 = 0.0;
331 for (n = 1; n <=
nterms; n++) {
336 multn = betan *
fn[n - 1];
337 Vr = Vr + multn * p0;
338 Vt = Vt + multn * p1 / n;
339 betan = beta * betan;
371 Eigen::Vector3f rd = rd_in - m->r0;
372 Eigen::Vector3f Q = Q_in;
375 float pos2, rd_len, pos_len;
376 double beta, cos_gamma, Vr, Vt;
377 Eigen::Vector3f vec1, vec2;
379 float cos_beta, Qr, Qt, Q2, c;
380 float pi4_inv =
static_cast<float>(0.25 /
M_PI);
385 if (m->fn.size() == 0 || m->nterms != MAXTERMS) {
386 m->fn.resize(MAXTERMS);
387 m->nterms = MAXTERMS;
388 for (k = 0; k < MAXTERMS; k++)
389 m->fn[k] = (2 * k + 3) * m->fwd_eeg_get_multi_sphere_model_coeff(k + 1);
399 if (rd_len >= m->layers[0].rad) {
400 for (k = 0; k < neeg; k++)
407 c = rd.dot(Q) / (rd_len * sqrt(Q2));
408 if ((1.0 - c * c) < SIN_EPS) {
421 for (k = 0; k < neeg; k++) {
422 pos = el.row(k).transpose() - m->r0;
427 pos_len = m->layers[m->nlayer() - 1].rad / pos.norm();
429 qInfo(
"%10.4f %10.4f %10.4f %10.4f", pos_len, 1000 * pos[0], 1000 * pos[1], 1000 * pos[2]);
434 pos_len = sqrt(pos2);
438 cos_gamma = pos.dot(rd) / (rd_len * pos_len);
439 beta = rd_len / pos_len;
445 vec2 = rd.cross(pos);
449 cos_beta = vec1.dot(vec2) / (v1 * v2);
453 Qr = Q.dot(rd) / rd_len;
454 Qt = sqrt(Q2 - Qr * Qr);
456 Vval[k] =
static_cast<double>(pi4_inv) * (
static_cast<double>(Qr) * Vr +
static_cast<double>(Qt) * cos_beta * Vt) / pos2;
462 if (m->nlayer() > 0) {
463 sigmaM_inv = 1.0 / m->layers[m->nlayer() - 1].sigma;
464 for (k = 0; k < neeg; k++)
465 Vval[k] = Vval[k] * sigmaM_inv;
487 Vval.resize(els.
ncoil());
488 for (k = 0; k < els.
ncoil(); k++, el++) {
489 el = els.
coils[k].get();
491 if (el->
np > nvval) {
492 vval_one.resize(el->
np);
498 for (c = 0, val = 0.0; c < el->
np; c++)
499 val += el->
w[c] * vval_one[c];
511 float fact = 0.25f /
static_cast<float>(
M_PI);
512 Eigen::Vector3f a_vec;
514 float rrd, rd2, rd2_inv, r, r2, ra, rda;
516 float c1, c2, m1, m2;
518 Eigen::Vector3f orig_rd = rd_in - m->r0;
525 for (k = 0; k < neeg; k++) {
526 Vval_vec(0, k) = 0.0;
527 Vval_vec(1, k) = 0.0;
528 Vval_vec(2, k) = 0.0;
533 if (orig_rd.norm() >= m->layers[0].rad)
540 eeg_set_homog_sphere_model();
545 for (eq = 0; eq < m->nfit; eq++) {
549 rd = m->mu[eq] * orig_rd;
557 for (k = 0; k < neeg; k++) {
558 pos = el.row(k).transpose() - m->r0;
563 pos_len = m->layers[m->nlayer() - 1].rad / pos.norm();
573 a2 = a_vec.dot(a_vec);
584 F = a * (r * a + ra);
585 c1 = a3 * rda + 1.0 / a - 1.0 / r;
586 c2 = a3 + (a + r) / (r * F);
590 m1 = (c1 - c2 * rrd);
593 Vval_vec(0, k) = Vval_vec(0, k) + m->lambda[eq] * rd2_inv * (m1 * rd[0] + m2 * pos[0]);
594 Vval_vec(1, k) = Vval_vec(1, k) + m->lambda[eq] * rd2_inv * (m1 * rd[1] + m2 * pos[1]);
595 Vval_vec(2, k) = Vval_vec(2, k) + m->lambda[eq] * rd2_inv * (m1 * rd[2] + m2 * pos[2]);
601 for (k = 0; k < neeg; k++) {
602 Vval_vec(0, k) = fact * Vval_vec(0, k);
603 Vval_vec(1, k) = fact * Vval_vec(1, k);
604 Vval_vec(2, k) = fact * Vval_vec(2, k);
613 Eigen::MatrixXf vval_one;
619 for (k = 0; k < els.
ncoil(); k++, el++) {
620 el = els.
coils[k].get();
622 if (el->
np > nvval) {
623 vval_one.resize(3, el->
np);
629 for (p = 0; p < 3; p++) {
630 for (c = 0, val = 0.0; c < el->
np; c++)
631 val += el->
w[c] * vval_one(p, c);
632 Vval_vec(p, k) = val;
650 Eigen::Vector3f my_rd;
651 float step = 0.0005f;
652 float step2 = 2 * step;
655 Eigen::Ref<Eigen::VectorXf>* grads[3] = {&xgrad, &ygrad, &zgrad};
657 for (p = 0; p < 3; p++) {
666 for (q = 0; q < coils.
ncoil(); q++)
667 (*grads[p])[q] = ((*grads[p])[q] - Vval[q]) / step2;
677 const Eigen::Vector3f& Q_in,
678 const Eigen::Matrix<float, Eigen::Dynamic, 3, Eigen::RowMajor>& el,
690 float fact =
static_cast<float>(0.25 /
M_PI);
691 Eigen::Vector3f a_vec;
693 float rrd, rd2, rd2_inv, r, r2, ra, rda;
695 float c1, c2, m1, m2, f1, f2;
697 Eigen::Vector3f orig_rd = rd_in - m->r0;
699 Eigen::Vector3f Q = Q_in;
705 for (k = 0; k < neeg; k++)
710 if (orig_rd.norm() >= m->layers[0].rad)
717 eeg_set_homog_sphere_model();
722 for (eq = 0; eq < m->nfit; eq++) {
726 rd = m->mu[eq] * orig_rd;
735 for (k = 0; k < neeg; k++) {
736 pos = el.row(k).transpose() - m->r0;
741 pos_len = m->layers[m->nlayer() - 1].rad / pos.norm();
751 a2 = a_vec.dot(a_vec);
762 F = a * (r * a + ra);
763 c1 = a3 * rda + 1.0 / a - 1.0 / r;
764 c2 = a3 + (a + r) / (r * F);
768 m1 = (c1 - c2 * rrd);
772 Vval[k] = Vval[k] + m->lambda[eq] * rd2_inv * (m1 * f1 + m2 * f2);
778 for (k = 0; k < neeg; k++)
779 Vval[k] = fact * Vval[k];
793 for (k = 0; k < els.
ncoil(); k++, el++) {
794 el = els.
coils[k].get();
796 if (el->
np > nvval) {
797 vval_one.resize(el->
np);
803 for (c = 0, val = 0.0; c < el->
np; c++)
804 val += el->
w[c] * vval_one[c];
821 for (
int k = 0; k < this->
nlayer(); k++)
824 if (fit_berg_scherg) {
826 qInfo(
"Equiv. model fitting -> RV = %g %%", 100 * rv);
827 for (
int k = 0; k < nFit; k++)
828 qInfo(
"mu%d = %g\tlambda%d = %g", k + 1, this->
mu[k], k + 1, this->
layers[this->
nlayer() - 1].sigma * this->
lambda[k]);
833 qInfo(
"Defined EEG sphere model with rad = %7.2f mm", 1000.0 * rad);
837static void compute_svd(Eigen::MatrixXd& mat,
838 Eigen::VectorXd& sing,
847 int udim = std::min(
static_cast<int>(mat.rows()),
static_cast<int>(mat.cols()));
849 Eigen::JacobiSVD<Eigen::MatrixXd>
svd(mat, Eigen::ComputeFullU | Eigen::ComputeFullV);
851 sing =
svd.singularValues();
852 uu =
svd.matrixU().transpose().topRows(udim);
855 *vv =
svd.matrixV().transpose();
878static void sort_parameters(VectorXd& mu, VectorXd& lambda,
int nfit)
880 std::vector<BergSchergPar> pars(nfit);
882 for (
int k = 0; k < nfit; k++) {
884 pars[k].lambda = lambda[k];
891 for (
int k = 0; k < nfit; k++) {
893 lambda[k] = pars[k].lambda;
897static bool report_fit([[maybe_unused]]
int loop,
898 [[maybe_unused]]
const VectorXd& fitpar,
899 [[maybe_unused]]
double Smin,
904 for (
int k = 0; k < fitpar.size(); k++)
911static MatrixXd get_initial_simplex(
const VectorXd& pars,
915 int npar = pars.size();
917 MatrixXd simplex = MatrixXd::Zero(npar + 1, npar);
919 simplex.rowwise() += pars.transpose();
921 for (
int k = 1; k < npar + 1; k++)
922 simplex(k, k - 1) += simplex_size;
935 for (k = 0; k < u->
nterms - 1; k++) {
937 mu1n = pow(
mu[0], k1);
938 u->
y[k] = u->
w[k] * (u->
fn[k + 1] - mu1n * u->
fn[0]);
939 for (p = 0; p < u->
nfit - 1; p++)
940 u->
M(k, p) = u->
w[k] * (pow(
mu[p + 1], k1) - mu1n);
954 VectorXd vec(u->
nfit - 1);
959 compute_svd(u->
M, u->
sing, u->
uu, &u->
vv);
963 for (k = 0; k < u->
nterms - 1; k++)
964 u->
resi[k] = u->
y[k];
966 for (p = 0; p < u->
nfit - 1; p++) {
967 vec[p] = u->
uu.row(p).head(u->
nterms - 1).dot(u->
y.head(u->
nterms - 1));
968 for (k = 0; k < u->
nterms - 1; k++)
969 u->
resi[k] = u->
resi[k] - u->
uu(p, k) * vec[p];
970 vec[p] = vec[p] / u->
sing[p];
973 for (p = 0; p < u->
nfit - 1; p++) {
974 for (q = 0, sum = 0.0; q < u->
nfit - 1; q++)
975 sum += u->
vv(q, p) * vec[q];
978 for (p = 1, sum = 0.0; p < u->
nfit; p++)
981 return u->
resi.head(u->
nterms - 1).squaredNorm() / u->
y.head(u->
nterms - 1).squaredNorm();
995 for (k = 0; k < u->
nfit; k++) {
996 if (std::fabs(
mu[k]) > 1.0)
1006 compute_svd(u->
M, u->
sing, u->
uu,
nullptr);
1010 for (k = 0; k < u->
nterms - 1; k++)
1011 u->
resi[k] = u->
y[k];
1012 for (p = 0; p < u->
nfit - 1; p++) {
1014 for (k = 0; k < u->
nterms - 1; k++)
1020 return u->
resi.head(u->
nterms - 1).squaredNorm();
1037 double simplex_size = 0.01;
1044 int max_eval = 1000;
1049 qWarning(
"fwd_fit_berg_scherg does not work with less than two equivalent sources.");
1056 for (k = 0; k < nTerms; k++)
1057 u->
fn[k] = this->fwd_eeg_get_multi_sphere_model_coeff(k + 1);
1063 for (k = 1; k < this->
nlayer(); k++) {
1066 if (this->
layers[k].rad < rd)
1067 rd = this->
layers[k].rad;
1075 for (k = 1; k < nTerms; k++)
1076 u->
w[k - 1] = pow(f, k);
1081 for (k = 1; k < nTerms; k++)
1082 u->
w[k - 1] = sqrt((2.0 * k + 1) * (3.0 * k + 1.0) / k) * pow(f, (k - 1.0));
1088 func_val = VectorXd(nFit + 1);
1089 lambdaFit = VectorXd(nFit);
1090 muFit = VectorXd(nFit);
1094 for (k = 0; k < nFit; k++) {
1095 muFit[k] = (k + 1) * 0.1 * f;
1098 simplex = get_initial_simplex(muFit, simplex_size);
1099 for (k = 0; k < nFit + 1; k++)
1100 func_val[k] =
one_step(VectorXd(simplex.row(k).transpose()), u);
1103 auto cost = [u](
const VectorXd& x) ->
double {
1121 for (k = 0; k < nFit; k++)
1122 muFit[k] = simplex(0, k);
1129 sort_parameters(muFit, lambdaFit, nFit);
1131 qInfo(
"RV = %g %%", 100 * rv);
1133 this->mu.resize(nFit);
1134 this->lambda.resize(nFit);
1136 for (k = 0; k < nFit; k++) {
1137 this->mu[k] = muFit[k];
1141 this->lambda[k] = lambdaFit[k] / this->
layers[this->
nlayer() - 1].sigma;
1143 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...
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 fitUser new_fit_user(int nfit, 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 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, const void *user_data)
static FwdEegSphereModelSet * fwd_load_eeg_sphere_models(const QString &p_sFileName, FwdEegSphereModelSet *now)
std::unique_ptr< FwdEegSphereModelSet > UPtr
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)