48constexpr int FAIL = -1;
50constexpr int MAXTERMS = 1000;
51constexpr double EPS = 1e-10;
52constexpr double SIN_EPS = 1e-3;
73 if (!p_FwdEegSphereModel.
name.isEmpty())
74 this->
name = p_FwdEegSphereModel.
name;
75 if (p_FwdEegSphereModel.
nlayer() > 0) {
76 for (k = 0; k < p_FwdEegSphereModel.
nlayer(); k++)
79 this->
r0 = p_FwdEegSphereModel.
r0;
80 if (p_FwdEegSphereModel.
nterms > 0) {
81 this->
fn = VectorXd(p_FwdEegSphereModel.
nterms);
83 for (k = 0; k < p_FwdEegSphereModel.
nterms; k++)
84 this->
fn[k] = p_FwdEegSphereModel.
fn[k];
86 if (p_FwdEegSphereModel.
nfit > 0) {
87 this->
mu = VectorXf(p_FwdEegSphereModel.
nfit);
88 this->
lambda = VectorXf(p_FwdEegSphereModel.
nfit);
89 this->
nfit = p_FwdEegSphereModel.
nfit;
90 for (k = 0; k < p_FwdEegSphereModel.
nfit; k++) {
91 this->
mu[k] = p_FwdEegSphereModel.
mu[k];
102 const VectorXf& rads,
103 const VectorXf& sigmas)
108 auto new_model = std::make_unique<FwdEegSphereModel>();
110 new_model->name =
name;
112 for (
int k = 0; k <
nlayer; k++) {
115 layer.
sigma = sigmas[k];
116 new_model->layers.push_back(layer);
126 float R = new_model->layers[
nlayer-1].rad;
127 float rR = new_model->layers[
nlayer-1].rel_rad;
128 for (
int k = 0; k <
nlayer; k++) {
129 new_model->layers[k].rad = new_model->layers[k].rad/
R;
130 new_model->layers[k].rel_rad = new_model->layers[k].rel_rad/rR;
145 if (eeg_model_name.isEmpty())
146 eeg_model_name = QString(
"Default");
149 eeg_models->fwd_list_eeg_sphere_models();
156 if (!eeg_model->fwd_setup_eeg_sphere_model(eeg_sphere_rad,
true,3)) {
160 qInfo(
"Using EEG sphere model \"%s\" with scalp radius %7.1f mm",
161 eeg_model->name.toUtf8().constData(),1000*eeg_sphere_rad);
188 MatrixXd M,Mn,help,Mm;
189 static MatrixXd mat1;
190 static MatrixXd mat2;
191 static MatrixXd mat3;
195 static VectorXd cr_mult;
210 if (this->
nlayer() == 2) {
213 div_mult = 2.0*n + 1;
214 b = pow(this->
layers[0].rel_rad,div_mult);
215 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));
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++) {
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--) {
271 Mm(0,0) = (n + n1*c1[k]);
272 Mm(0,1) = n1*c2[k]/cr[k];
273 Mm(1,0) = n*c2[k]*cr[k];
274 Mm(1,1) = n1 + n*c1[k];
276 Mn(0,0) = Mm(0,0)*M(0,0) + Mm(0,1)*M(1,0);
277 Mn(0,1) = Mm(0,0)*M(0,1) + Mm(0,1)*M(1,1);
278 Mn(1,0) = Mm(1,0)*M(0,0) + Mm(1,1)*M(1,0);
279 Mn(1,1) = Mm(1,0)*M(0,1) + Mm(1,1)*M(1,1);
286 return n*div/(n*M(1,1) + n1*M(1,0));
305 p0 = ((2*n-1)*x*help0 - (n-1)*(p01))/n;
306 p1 = ((2*n-1)*x*help1 - n*(p11))/(n-1);
329 double p0,p01,p1,p11;
334 p0 = p01 = p1 = p11 = 0.0;
335 for (n = 1; n <=
nterms; n++) {
340 multn = betan*
fn[n-1];
342 Vt = Vt + multn*p1/n;
375 Eigen::Vector3f rd = rd_in - m->r0;
376 Eigen::Vector3f Q = Q_in;
379 float pos2,rd_len,pos_len;
380 double beta,cos_gamma,Vr,Vt;
381 Eigen::Vector3f vec1, vec2;
383 float cos_beta,Qr,Qt,Q2,c;
384 float pi4_inv = 0.25/
M_PI;
389 if (m->fn.size() == 0 || m->nterms != MAXTERMS) {
390 m->fn.resize(MAXTERMS);
391 m->nterms = MAXTERMS;
392 for (k = 0; k < MAXTERMS; k++)
393 m->fn[k] = (2*k+3)*m->fwd_eeg_get_multi_sphere_model_coeff(k+1);
403 if (rd_len >= m->layers[0].rad) {
404 for (k = 0; k < neeg; k++)
411 c = rd.dot(Q)/(rd_len*sqrt(Q2));
412 if ((1.0-c*c) < SIN_EPS) {
426 for (k = 0; k < neeg; k++) {
427 pos = el.row(k).transpose() - m->r0;
432 pos_len = m->layers[m->nlayer()-1].rad/pos.norm();
434 qInfo(
"%10.4f %10.4f %10.4f %10.4f",pos_len,1000*pos[0],1000*pos[1],1000*pos[2]);
439 pos_len = sqrt(pos2);
443 cos_gamma = pos.dot(rd)/(rd_len*pos_len);
444 beta = rd_len/pos_len;
450 vec2 = rd.cross(pos);
454 cos_beta = vec1.dot(vec2)/(v1*v2);
458 Qr = Q.dot(rd)/rd_len;
459 Qt = sqrt(Q2 - Qr*Qr);
461 Vval[k] =
static_cast<double>(pi4_inv)*(
static_cast<double>(Qr)*Vr +
static_cast<double>(Qt)*cos_beta*Vt)/pos2;
467 if (m->nlayer() > 0) {
468 sigmaM_inv = 1.0/m->layers[m->nlayer()-1].sigma;
469 for (k = 0; k < neeg; k++)
470 Vval[k] = Vval[k]*sigmaM_inv;
492 Vval.resize(els.
ncoil());
493 for (k = 0; k < els.
ncoil(); k++, el++) {
494 el = els.
coils[k].get();
496 if (el->
np > nvval) {
497 vval_one.resize(el->
np);
503 for (c = 0, val = 0.0; c < el->
np; c++)
504 val += el->
w[c]*vval_one[c];
516 float fact = 0.25f /
static_cast<float>(
M_PI);
517 Eigen::Vector3f a_vec;
519 float rrd,rd2,rd2_inv,r,r2,ra,rda;
523 Eigen::Vector3f orig_rd = rd_in - m->r0;
530 for (k = 0 ; k < neeg ; k++) {
538 if (orig_rd.norm() >= m->layers[0].rad)
545 eeg_set_homog_sphere_model();
550 for (eq = 0; eq < m->nfit; eq++) {
554 rd = m->mu[eq] * orig_rd;
562 for (k = 0; k < neeg ; k++) {
564 pos = el.row(k).transpose() - m->r0;
569 pos_len = m->layers[m->nlayer()-1].rad/pos.norm();
579 a2 = a_vec.dot(a_vec); a = sqrt(a2);
581 r2 = pos.dot(pos); r = sqrt(r2);
589 c1 = a3*rda + 1.0/a - 1.0/r;
590 c2 = a3 + (a+r)/(r*F);
597 Vval_vec(0,k) = Vval_vec(0,k) + m->lambda[eq]*rd2_inv*(m1*rd[0] + m2*pos[0]);
598 Vval_vec(1,k) = Vval_vec(1,k) + m->lambda[eq]*rd2_inv*(m1*rd[1] + m2*pos[1]);
599 Vval_vec(2,k) = Vval_vec(2,k) + m->lambda[eq]*rd2_inv*(m1*rd[2] + m2*pos[2]);
605 for (k = 0; k < neeg; k++) {
606 Vval_vec(0,k) = fact*Vval_vec(0,k);
607 Vval_vec(1,k) = fact*Vval_vec(1,k);
608 Vval_vec(2,k) = fact*Vval_vec(2,k);
617 Eigen::MatrixXf vval_one;
623 for (k = 0; k < els.
ncoil(); k++, el++) {
624 el = els.
coils[k].get();
626 if (el->
np > nvval) {
627 vval_one.resize(3, el->
np);
633 for (p = 0; p < 3; p++) {
634 for (c = 0, val = 0.0; c < el->
np; c++)
635 val += el->
w[c]*vval_one(p,c);
654 Eigen::Vector3f my_rd;
656 float step2 = 2*step;
659 Eigen::Ref<Eigen::VectorXf>* grads[3] = { &xgrad, &ygrad, &zgrad };
661 for (p = 0; p < 3; p++) {
670 for (q = 0; q < coils.
ncoil(); q++)
671 (*grads[p])[q] = ((*grads[p])[q]-Vval[q])/step2;
681 const Eigen::Vector3f& Q_in,
682 const Eigen::Matrix<float, Eigen::Dynamic, 3, Eigen::RowMajor>& el,
694 float fact = 0.25f/
M_PI;
695 Eigen::Vector3f a_vec;
697 float rrd,rd2,rd2_inv,r,r2,ra,rda;
699 float c1,c2,m1,m2,f1,f2;
701 Eigen::Vector3f orig_rd = rd_in - m->r0;
703 Eigen::Vector3f Q = Q_in;
709 for (k = 0 ; k < neeg ; k++)
714 if (orig_rd.norm() >= m->layers[0].rad)
721 eeg_set_homog_sphere_model();
726 for (eq = 0; eq < m->nfit; eq++) {
730 rd = m->mu[eq] * orig_rd;
739 for (k = 0; k < neeg ; k++) {
741 pos = el.row(k).transpose() - m->r0;
746 pos_len = m->layers[m->nlayer()-1].rad/pos.norm();
756 a2 = a_vec.dot(a_vec); a = sqrt(a2);
758 r2 = pos.dot(pos); r = sqrt(r2);
766 c1 = a3*rda + 1.0/a - 1.0/r;
767 c2 = a3 + (a+r)/(r*F);
775 Vval[k] = Vval[k] + m->lambda[eq]*rd2_inv*(m1*f1 + m2*f2);
781 for (k = 0; k < neeg; k++)
782 Vval[k] = fact*Vval[k];
796 for (k = 0; k < els.
ncoil(); k++, el++) {
797 el = els.
coils[k].get();
799 if (el->
np > nvval) {
800 vval_one.resize(el->
np);
806 for (c = 0, val = 0.0; c < el->
np; c++)
807 val += el->
w[c]*vval_one[c];
824 for (
int k = 0; k < this->
nlayer(); k++)
827 if (fit_berg_scherg) {
829 qInfo(
"Equiv. model fitting -> RV = %g %%",100*rv);
830 for (
int k = 0; k <
nfit; k++)
831 qInfo(
"mu%d = %g\tlambda%d = %g", k+1,this->
mu[k],k+1,this->
layers[this->
nlayer()-1].sigma*this->
lambda[k]);
837 qInfo(
"Defined EEG sphere model with rad = %7.2f mm", 1000.0*rad);
841static void compute_svd(Eigen::MatrixXd& mat,
842 Eigen::VectorXd& sing,
851 int udim = std::min(
static_cast<int>(mat.rows()),
static_cast<int>(mat.cols()));
853 Eigen::JacobiSVD<Eigen::MatrixXd>
svd(mat, Eigen::ComputeFullU | Eigen::ComputeFullV);
855 sing =
svd.singularValues();
856 uu =
svd.matrixU().transpose().topRows(udim);
859 *vv =
svd.matrixV().transpose();
881static void sort_parameters(VectorXd& mu,VectorXd& lambda,
int nfit)
883 std::vector<BergSchergPar> pars(nfit);
885 for (
int k = 0; k < nfit; k++) {
887 pars[k].lambda = lambda[k];
894 for (
int k = 0; k < nfit; k++) {
896 lambda[k] = pars[k].lambda;
900static bool report_fit(
int loop,
901 const VectorXd &fitpar,
907 for (
int k = 0; k < fitpar.size(); k++)
914static MatrixXd get_initial_simplex(
const VectorXd &pars,
918 int npar = pars.size();
920 MatrixXd simplex = MatrixXd::Zero(npar+1,npar);
922 simplex.rowwise() += pars.transpose();
924 for (
int k = 1; k < npar+1; k++)
925 simplex(k,k-1) += simplex_size;
938 for (k = 0; k < u->
nterms-1; k++) {
940 mu1n = pow(
mu[0],k1);
941 u->
y[k] = u->
w[k]*(u->
fn[k+1] - mu1n*u->
fn[0]);
942 for (p = 0; p < u->
nfit-1; p++)
943 u->
M(k,p) = u->
w[k]*(pow(
mu[p+1],k1)-mu1n);
957 VectorXd vec(u->
nfit-1);
962 compute_svd(u->
M, u->
sing, u->
uu, &u->
vv);
966 for (k = 0; k < u->
nterms-1; k++)
967 u->
resi[k] = u->
y[k];
969 for (p = 0; p < u->
nfit-1; p++) {
971 for (k = 0; k < u->
nterms-1; k++)
973 vec[p] = vec[p]/u->
sing[p];
976 for (p = 0; p < u->
nfit-1; p++) {
977 for (q = 0, sum = 0.0; q < u->
nfit-1; q++)
978 sum += u->
vv(q,p)*vec[q];
981 for (p = 1, sum = 0.0; p < u->
nfit; p++)
984 return u->
resi.head(u->
nterms-1).squaredNorm() / u->
y.head(u->
nterms-1).squaredNorm();
998 for (k = 0; k < u->
nfit; k++) {
999 if (std::fabs(
mu[k]) > 1.0)
1009 compute_svd(u->
M, u->
sing, u->
uu,
nullptr);
1013 for (k = 0; k < u->
nterms-1; k++)
1014 u->
resi[k] = u->
y[k];
1015 for (p = 0; p < u->
nfit-1; p++) {
1017 for (k = 0; k < u->
nterms-1; k++)
1023 return u->
resi.head(u->
nterms-1).squaredNorm();
1040 double simplex_size = 0.01;
1047 int max_eval = 1000;
1052 qWarning(
"fwd_fit_berg_scherg does not work with less than two equivalent sources.");
1059 for (k = 0; k <
nterms; k++)
1060 u->
fn[k] = this->fwd_eeg_get_multi_sphere_model_coeff(k+1);
1066 for (k = 1; k < this->
nlayer(); k++) {
1069 if (this->
layers[k].rad < rd)
1070 rd = this->
layers[k].rad;
1078 for (k = 1; k <
nterms; k++)
1079 u->
w[k-1] = pow(f,k);
1084 for (k = 1; k <
nterms; k++)
1085 u->
w[k-1] = sqrt((2.0*k+1)*(3.0*k+1.0)/k)*pow(f,(k-1.0));
1091 func_val = VectorXd(
nfit+1);
1097 for (k = 0; k <
nfit; k++) {
1101 mu[k] = (rand() / (RAND_MAX + 1.0))*f;
1104 simplex = get_initial_simplex(
mu,simplex_size);
1105 for (k = 0; k <
nfit+1; k++)
1106 func_val[k] =
one_step(VectorXd(simplex.row(k).transpose()),u);
1109 auto cost = [u](
const VectorXd& x) ->
double {
return one_step(x, u); };
1125 for (k = 0; k <
nfit; k++)
1126 mu[k] = simplex(0,k);
1135 qInfo(
"RV = %g %%",100*rv);
1137 this->mu.resize(
nfit);
1138 this->lambda.resize(
nfit);
1140 for (k = 0; k <
nfit; k++) {
1141 this->mu[k] =
mu[k];
1147 qInfo(
"lambda%d = %g\tmu%d = %g",k+1,
lambda[k],k+1,
mu[k]);
Named container of FwdEegSphereModel objects loaded from an mne_setup_eeg_sphere_model parameter file...
Multi-shell spherical head model with Berg-Scherg equivalent-source approximation for fast EEG forwar...
Eigen::JacobiSVD< Eigen::Matrix3f > svd(S, Eigen::ComputeFullU|Eigen::ComputeFullV)
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)
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)
bool fwd_eeg_fit_berg_scherg(int nterms, int nfit, float &rv)
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)
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)
bool fwd_setup_eeg_sphere_model(float rad, bool fit_berg_scherg, int nfit)
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)