/* Diffusion model fitting Timothy Behrens, Saad Jbabdi, Stam Sotiropoulos - FMRIB Image Analysis Group Copyright (C) 2005 University of Oxford */ /* CCOPYRIGHT */ #include "Bingham_Watson_approx.h" #include "diffmodels.h" //////////////////////////////////////////////// // DIFFUSION TENSOR MODEL //////////////////////////////////////////////// void DTI::linfit(){ ColumnVector logS(npts); ColumnVector Dvec(7); for (int i=1;i<=npts; i++){ if(Y(i)>0) logS(i)=log(Y(i)); else logS(i)=0; } Dvec = -iAmat*logS; if(Dvec(7)>-23) m_s0=exp(-Dvec(7)); else m_s0=Y.MaximumAbsoluteValue(); for (int i=1;i<=Y.Nrows();i++){ if(m_s0<Y.Sum()/Y.Nrows()){ m_s0=Y.MaximumAbsoluteValue(); } logS(i)=(Y(i)/m_s0)>0.01 ? log(Y(i)):log(0.01*m_s0); } Dvec = -iAmat*logS; m_sse = (Amat*Dvec+logS).SumSquare(); m_s0=exp(-Dvec(7)); if(m_s0<Y.Sum()/Y.Nrows()){ m_s0=Y.Sum()/Y.Nrows(); } vec2tens(Dvec); calc_tensor_parameters(); m_covar.ReSize(7); float dof=logS.Nrows()-7; float sig2=m_sse/dof; m_covar << sig2*(Amat.t()*Amat).i(); } ColumnVector DTI::calc_md_grad(const ColumnVector& _tens)const{ ColumnVector g(6); g = 0; g(1) = 1/3.0; g(4) = 1/3.0; g(6) = 1/3.0; return g; } // this will only work if the determinant is strictly positive ReturnMatrix DTI::calc_fa_grad(const ColumnVector& _intens)const{ ColumnVector gradv(6),ik(6),k(6); float m = (_intens(1)+_intens(4)+_intens(6))/3.0; SymmetricMatrix K(3),iK(3),M(3); // rescale input matrix vec2tens(_intens,M); //M /=m; m = M.Trace()/3.0; K = M - m*IdentityMatrix(3); tens2vec(K,k); iK << K.i(); tens2vec(iK,ik); float p = K.SumSquare()/6.0; float q = K.Determinant()/2.0; float h = std::sqrt(p*p*p-q*q)/q; float phi = std::atan(h)/3.0; if(q<0)phi+=M_PI; float _l1 = m + 2.0*std::sqrt(p)*std::cos(phi); float _l2 = m - std::sqrt(p)*(std::cos(phi)+std::sqrt(3.0)*std::sin(phi)); float _l3 = m - std::sqrt(p)*(std::cos(phi)-std::sqrt(3.0)*std::sin(phi)); float t = 6.0/9.0*(_l1*_l1+_l2*_l2+_l3*_l3 - _l1*_l2-_l1*_l3-_l2*_l3); float b = _l1*_l1+_l2*_l2+_l3*_l3; float _fa = std::sqrt(3.0/2.0)*std::sqrt(t/b); float dfadl1 = 3.0/4.0/_fa * ( 6.0/9.0*(2.0*_l1-_l2-_l3)/b - t/b/b*2.0*_l1 ); float dfadl2 = 3.0/4.0/_fa * ( 6.0/9.0*(2.0*_l2-_l1-_l3)/b - t/b/b*2.0*_l2 ); float dfadl3 = 3.0/4.0/_fa * ( 6.0/9.0*(2.0*_l3-_l1-_l2)/b - t/b/b*2.0*_l3 ); // determine dkdx ColumnVector dkdx(6); dkdx << 2.0/3.0 << 1.0 << 1.0 << 2.0/3.0 << 1.0 << 2.0/3.0; for(int i=1;i<=6;i++){ float dL1dx=0,dL2dx=0,dL3dx=0; if(i==1||i==4||i==6){ dL1dx=1.0/3.0;dL2dx=1.0/3.0;dL3dx=1.0/3.0; } // float p_p = k(i)/3.0 * dkdx(i); float q_p = q*ik(i) * dkdx(i); float h_p = (3.0*p*p*p_p - 2.0*q_p*q*(1+h*h))/2.0/h/q/q; float phi_p = h_p/(1+h*h)/3.0; dL1dx += p_p/std::sqrt(p)*std::cos(phi) - 2.0*std::sqrt(p)*phi_p*std::sin(phi); dL2dx -= std::sqrt(p)*(.5*p_p*(m-_l2)+phi_p*(std::sin(phi)-std::sqrt(3.0)*std::cos(phi))); dL3dx -= std::sqrt(p)*(.5*p_p*(m-_l3)+phi_p*(std::sin(phi)+std::sqrt(3.0)*std::cos(phi))); // gradv(i) = dfadl1*dL1dx + dfadl2*dL2dx + dfadl3*dL3dx; } gradv.Release(); return gradv; } float DTI::calc_fa_var()const{ ColumnVector grd; ColumnVector vtens; tens2vec(m_tens,vtens); grd = calc_fa_grad(vtens); ColumnVector g(7); g.SubMatrix(1,6,1,1) = grd; g(7) = 0; return((g.t()*m_covar*g).AsScalar()); } void DTI::rot2angles(const Matrix& rot,float& th1,float& th2,float& th3)const{ if(rot(3,1)!=1 && rot(3,1)!=-1){ th2 = -asin(rot(3,1)); float c=std::cos(th2); th1 = atan2(rot(3,2)/c,rot(3,3)/c); th3 = atan2(rot(2,1)/c,rot(1,1)/c); } else{ th1 = atan2(rot(1,2),rot(1,3)); th2 = -sign(rot(3,1))*M_PI/2; th3 = 0; } } void DTI::angles2rot(const float& th1,const float& th2,const float& th3,Matrix& rot)const{ float c1=std::cos(th1),s1=std::sin(th1); float c2=std::cos(th2),s2=std::sin(th2); float c3=std::cos(th3),s3=std::sin(th3); rot(1,1) = c2*c3; rot(1,2) = s1*s2*c3-c1*s3; rot(3,1) = c1*s2*c3+s1*s3; rot(2,1) = c2*s3; rot(2,2) = s1*s2*s3+c1*c3; rot(3,2) = c1*s2*s3-s1*c3; rot(3,1) = -s2; rot(3,2) = s1*c2; rot(3,3) = c1*c2; } // nonlinear tensor fitting void DTI::nonlinfit(){ // initialise with linear fitting linfit(); print(); // set starting parameters // params = s0, log(l1),log(l2), log(l3), th1, th2, th3 ColumnVector start(nparams); start(1) = m_s0; // eigenvalues start(2) = m_l1>0?std::log(m_l1):std::log(1e-5); start(3) = m_l2>0?std::log(m_l2):std::log(1e-5); start(4) = m_l3>0?std::log(m_l3):std::log(1e-5); // angles float th1,th2,th3; Matrix rot(3,3); rot.Row(1) = m_v1.t(); rot.Row(2) = m_v2.t(); rot.Row(3) = m_v3.t(); rot2angles(rot,th1,th2,th3); start(5) = th1; start(6) = th2; start(7) = th3; // do the fit NonlinParam lmpar(start.Nrows(),NL_LM); lmpar.SetGaussNewtonType(LM_L); lmpar.SetStartingEstimate(start); NonlinOut status; status = nonlin(lmpar,(*this)); ColumnVector final_par(nparams); final_par = lmpar.Par(); // finalise parameters m_s0 = final_par(1); m_l1 = exp(final_par(2)); m_l2 = exp(final_par(3)); m_l3 = exp(final_par(4)); angles2rot(final_par(5),final_par(6),final_par(7),rot); m_v1 = rot.Row(1).t(); m_v2 = rot.Row(2).t(); m_v3 = rot.Row(3).t(); sort(); m_tens << m_l1*m_v1*m_v1.t() + m_l2*m_v2*m_v2.t() + m_l3*m_v3*m_v3.t(); calc_tensor_parameters(); print(); //exit(1); } void DTI::sort(){ vector< pair<float,int> > ls(3); vector<ColumnVector> vs(3); ls[0].first=m_l1; ls[0].second=0; ls[1].first=m_l2; ls[1].second=1; ls[2].first=m_l3; ls[2].second=2; vs[0]=m_v1;vs[1]=m_v2;vs[2]=m_v3; std::sort(ls.begin(),ls.end()); m_l1 = ls[2].first; m_v1 = vs[ ls[2].second ]; m_l2 = ls[1].first; m_v2 = vs[ ls[1].second ]; m_l3 = ls[0].first; m_v3 = vs[ ls[0].second ]; } void DTI::calc_tensor_parameters(){ Matrix Vd; DiagonalMatrix Dd(3); // mean, eigenvalues and eigenvectors EigenValues(m_tens,Dd,Vd); m_md = Dd.Sum()/Dd.Nrows(); m_l1 = Dd(3,3); m_l2 = Dd(2,2); m_l3 = Dd(1,1); m_v1 = Vd.Column(3); m_v2 = Vd.Column(2); m_v3 = Vd.Column(1); // mode float e1=m_l1-m_md, e2=m_l2-m_md, e3=m_l3-m_md; float n = (e1 + e2 - 2*e3)*(2*e1 - e2 - e3)*(e1 - 2*e2 + e3); float d = (e1*e1 + e2*e2 + e3*e3 - e1*e2 - e2*e3 - e1*e3); d = sqrt(bigger(0, d)); d = 2*d*d*d; m_mo = smaller(bigger(d ? n/d : 0.0, -1),1); // fa float numer=1.5*((m_l1-m_md)*(m_l1-m_md)+(m_l2-m_md)*(m_l2-m_md)+(m_l3-m_md)*(m_l3-m_md)); float denom=(m_l1*m_l1+m_l2*m_l2+m_l3*m_l3); if(denom>0) m_fa=numer/denom; else m_fa=0; if(m_fa>0){m_fa=sqrt(m_fa);} else{m_fa=0;} } // now the nonlinear fitting routines double DTI::cf(const NEWMAT::ColumnVector& p)const{ //cout << "CF" << endl; //OUT(p.t()); double cfv = 0.0; double err = 0.0; //////////////////////////////////// ColumnVector ls(3); Matrix rot(3,3); angles2rot(p(5),p(6),p(7),rot); for(int k=2;k<=4;k++){ ls(k-1) = exp(p(k)); } //////////////////////////////////// for(int i=1;i<=Y.Nrows();i++){ err = p(1)*anisoterm(i,ls,rot) - Y(i); cfv += err*err; } //OUT(cfv); //cout<<"--------"<<endl; return(cfv); } NEWMAT::ReturnMatrix DTI::grad(const NEWMAT::ColumnVector& p)const{ NEWMAT::ColumnVector gradv(p.Nrows()); cout<<"grad"<<endl; OUT(p.t()); gradv = 0.0; //////////////////////////////////// ColumnVector ls(3); Matrix rot(3,3); Matrix rot1(3,3),rot2(3,3),rot3(3,3); angles2rot(p(5),p(6),p(7),rot); angles2rot(p(5)+M_PI/2.0,p(6),p(7),rot1); angles2rot(p(5),p(6)+M_PI/2.0,p(7),rot2); angles2rot(p(5),p(6),p(7)+M_PI/2.0,rot3); for(int k=2;k<=4;k++){ ls(k-1) = exp(p(k)); } //////////////////////////////////// Matrix J(npts,nparams); ColumnVector x(3); ColumnVector diff(npts); float sig; for(int i=1;i<=Y.Nrows();i++){ sig = p(1)*anisoterm(i,ls,rot); J(i,1) = sig/p(1); x = rotproduct(bvecs.Column(i),rot); J(i,2) = -bvals(1,i)*x(1)*sig*ls(1); J(i,3) = -bvals(1,i)*x(2)*sig*ls(2); J(i,4) = -bvals(1,i)*x(3)*sig*ls(3); x = rotproduct(bvecs.Column(i),rot1,rot); J(i,5) = -2.0*bvals(1,i)*(ls(1)*x(1)+ls(2)*x(2)+ls(3)*x(3))*sig; x = rotproduct(bvecs.Column(i),rot2,rot); J(i,6) = -2.0*bvals(1,i)*(ls(1)*x(1)+ls(2)*x(2)+ls(3)*x(3))*sig; x = rotproduct(bvecs.Column(i),rot3,rot); J(i,7) = -2.0*bvals(1,i)*(ls(1)*x(1)+ls(2)*x(2)+ls(3)*x(3))*sig; diff(i) = sig - Y(i); } OUT(diff.t()); OUT(J.t()); gradv = 2.0*J.t()*diff; OUT(gradv.t()); cout<<"------"<<endl; gradv.Release(); return gradv; } //this uses Gauss-Newton approximation boost::shared_ptr<BFMatrix> DTI::hess(const NEWMAT::ColumnVector& p,boost::shared_ptr<BFMatrix> iptr)const{ boost::shared_ptr<BFMatrix> hessm; if (iptr && iptr->Nrows()==(unsigned int)p.Nrows() && iptr->Ncols()==(unsigned int)p.Nrows()) hessm = iptr; else hessm = boost::shared_ptr<BFMatrix>(new FullBFMatrix(p.Nrows(),p.Nrows())); cout<<"hess"<<endl; OUT(p.t()); //////////////////////////////////// ColumnVector ls(3); Matrix rot(3,3); Matrix rot1(3,3),rot2(3,3),rot3(3,3); angles2rot(p(5),p(6),p(7),rot); angles2rot(p(5)+M_PI/2,p(6),p(7),rot1); angles2rot(p(5),p(6)+M_PI/2,p(7),rot2); angles2rot(p(5),p(6),p(7)+M_PI/2,rot3); for(int k=2;k<=4;k++){ ls(k-1) = exp(p(k)); } //////////////////////////////////// Matrix J(npts,nparams); ColumnVector x(3); float sig; for(int i=1;i<=Y.Nrows();i++){ sig = p(1)*anisoterm(i,ls,rot); J(i,1) = sig/p(1); x = rotproduct(bvecs.Column(i),rot); J(i,2) = -bvals(1,i)*x(1)*sig*ls(1); J(i,3) = -bvals(1,i)*x(2)*sig*ls(2); J(i,4) = -bvals(1,i)*x(3)*sig*ls(3); x = rotproduct(bvecs.Column(i),rot1,rot); J(i,5) = -2.0*bvals(1,i)*(ls(1)*x(1)+ls(2)*x(2)+ls(3)*x(3))*sig; x = rotproduct(bvecs.Column(i),rot2,rot); J(i,6) = -2.0*bvals(1,i)*(ls(1)*x(1)+ls(2)*x(2)+ls(3)*x(3))*sig; x = rotproduct(bvecs.Column(i),rot3,rot); J(i,7) = -2.0*bvals(1,i)*(ls(1)*x(1)+ls(2)*x(2)+ls(3)*x(3))*sig; } for (int i=1; i<=p.Nrows(); i++){ for (int j=i; j<=p.Nrows(); j++){ sig = 0.0; for(int k=1;k<=J.Nrows();k++) sig += 2.0*(J(k,i)*J(k,j)); hessm->Set(i,j,sig); } } for (int j=1; j<=p.Nrows(); j++) { for (int i=j+1; i<=p.Nrows(); i++) { hessm->Set(i,j,hessm->Peek(j,i)); } } hessm->Print(); cout<<"------"<<endl; return(hessm); } ColumnVector DTI::rotproduct(const ColumnVector& x,const Matrix& R)const{ ColumnVector ret(3); for(int i=1;i<=3;i++) ret(i) = x(1)*x(1)*R(1,i)*R(1,i)+x(2)*x(2)*R(2,i)*R(2,i)+x(3)*x(3)*R(3,i)*R(3,i) +2.0*( x(1)*R(1,i)*(x(2)*R(2,i)+x(3)*R(3,i)) +x(2)*x(3)*R(2,i)*R(3,i) ); return ret; } ColumnVector DTI::rotproduct(const ColumnVector& x,const Matrix& R1,const Matrix& R2)const{ ColumnVector ret(3); for(int i=1;i<=3;i++) ret(i) = x(1)*x(1)*R1(1,i)*R2(1,i)+x(2)*x(2)*R1(2,i)*R2(2,i)+x(3)*x(3)*R1(3,i)*R2(3,i) +( x(1)*R1(1,i)*(x(2)*R2(2,i)+x(3)*R2(3,i)) +x(2)*x(3)*R1(2,i)*R2(3,i) ) +( x(1)*R2(1,i)*(x(2)*R1(2,i)+x(3)*R1(3,i)) +x(2)*x(3)*R2(2,i)*R1(3,i) ); return ret; } float DTI::anisoterm(const int& pt,const ColumnVector& ls,const Matrix& rot)const{ ColumnVector x(3); x = rotproduct(bvecs.Column(pt),rot); return exp(-bvals(1,pt)*(ls(1)*x(1)+ls(2)*x(2)+ls(3)*x(3))); } ///////////////////////////////////////////////////////////////////////// // PARTIAL VOLUME MODEL - SINGLE SHELL // Constrained Optimization for the diffusivity, fractions and their sum<1 ////////////////////////////////////////////////////////////////////////// void PVM_single_c::fit(){ // initialise with a tensor DTI dti(Y,bvecs,bvals); dti.linfit(); // set starting parameters for nonlinear fitting float _th,_ph; cart2sph(dti.get_v1(),_th,_ph); ColumnVector start(nparams); //Initialize the non-linear fitter. Use the DTI estimates for most parameters, apart from the volume fractions start(1) = dti.get_s0(); //start(2) = d2lambda(dti.get_md()>0?dti.get_md()*2:0.001); // empirically found that d~2*MD start(2) = d2lambda(dti.get_l1()>0?dti.get_l1():0.002); // empirically found that d~L1 start(4) = _th; start(5) = _ph; for(int ii=2,i=6;ii<=nfib;ii++,i+=3){ cart2sph(dti.get_v(ii),_th,_ph); start(i+1) = _th; start(i+2) = _ph; } // do a better job for initializing the volume fractions fit_pvf(start); // do the fit NonlinParam lmpar(start.Nrows(),NL_LM); lmpar.SetGaussNewtonType(LM_L); lmpar.SetStartingEstimate(start); NonlinOut status; status = nonlin(lmpar,(*this)); ColumnVector final_par(nparams); final_par = lmpar.Par(); if (m_eval_BIC){ double RSS=cf(final_par); //get the sum of squared residuals m_BIC=npts*log(RSS/npts)+log(npts)*nparams; //evaluate BIC } // finalise parameters m_s0 = final_par(1); m_d = lambda2d(final_par(2)); for(int k=1;k<=nfib;k++){ int kk = 3 + 3*(k-1); m_f(k) = beta2f(final_par(kk))*partial_fsum(m_f,k-1); m_th(k) = final_par(kk+1); m_ph(k) = final_par(kk+2); } if (m_return_fanning) Fanning_angles_from_Hessian(); if (m_include_f0) m_f0=beta2f(final_par(nparams))*partial_fsum(m_f,nfib); sort(); } void PVM_single_c::sort(){ vector< pair<float,int> > fvals(nfib); ColumnVector ftmp(nfib),thtmp(nfib),phtmp(nfib),fantmp; vector<ColumnVector> Hess_vec_tmp; vector<Matrix> Hess; ftmp=m_f;thtmp=m_th;phtmp=m_ph; if (m_return_fanning){ fantmp=m_fanning_angles; Hess_vec_tmp=m_invprHes_e1; Hess=m_Hessian; } for(int i=1;i<=nfib;i++){ pair<float,int> p(m_f(i),i); fvals[i-1] = p; } std::sort(fvals.begin(),fvals.end()); for(int i=1,ii=nfib-1;ii>=0;i++,ii--){ m_f(i) = ftmp(fvals[ii].second); m_th(i) = thtmp(fvals[ii].second); m_ph(i) = phtmp(fvals[ii].second); if (m_return_fanning){ m_fanning_angles(i)=fantmp(fvals[ii].second); m_invprHes_e1[i-1]=Hess_vec_tmp[fvals[ii].second-1]; m_Hessian[i-1]=Hess[fvals[ii].second-1]; } } } //Returns 1-Sum(f_j), 1<=j<=ii. (ii<=nfib) //Used for transforming beta to f and vice versa float PVM_single_c::partial_fsum(ColumnVector& fs, int ii) const{ float fsum=1.0; for(int j=1;j<=ii;j++) fsum-=fs(j); return fsum; } //If the sum of the fractions is >1, then zero as many fractions //as necessary, so that the sum becomes smaller than 1. void PVM_single_c::fix_fsum(ColumnVector& fs)const{ float sumf=0; for(int i=1;i<=nfib;i++){ sumf+=fs(i); if(sumf>=1){ for(int j=i;j<=nfib;j++) fs(j)=FSMALL; //make the fraction almost zero break; } } } //Find the volume fractions given all the other model //parameters using Linear Least Squares void PVM_single_c::fit_pvf(ColumnVector& x)const{ ColumnVector fs(nfib); ColumnVector Y_I(npts); Matrix M(npts,nfib),dir(3,nfib); float s0=x(1),d=lambda2d(x(2)), f0; for(int k=1;k<=nfib;k++){ int kk = 3+3*(k-1); dir(1,k) = sin(x(kk+1))*cos(x(kk+2)); dir(2,k) = sin(x(kk+1))*sin(x(kk+2)); dir(3,k) = cos(x(kk+1)); } //////////////////////////////////// for(int i=1;i<=npts;i++){ float Iso_term=isoterm(i,d); Y_I(i) = Y(i)-s0*Iso_term; for(int k=1;k<=nfib;k++){ M(i,k)=s0*(anisoterm(i,d,dir.Column(k))-Iso_term); } //if (m_include_f0) //M(i,f_num)=s0*(1-Iso_term); } fs = pinv(M)*Y_I; if (m_include_f0){ f0=FSMALL; fs(1)-=f0; //Initialize f0 with a very small value } for(int k=1;k<=nfib;k++) fs(k)=fabs(fs(k)); //make sure that the initial values for the fractions are positive fix_fsum(fs); for(int k=1;k<=nfib;k++){ float tmpr=fs(k)/partial_fsum(fs,k-1); if (tmpr>1.0) tmpr=1; //This can be due to numerical errors x(3+3*(k-1))=f2beta(tmpr); } if (m_include_f0){ float tmpr=f0/partial_fsum(fs,nfib); if (tmpr>1.0) tmpr=1; //This can be due to numerical errors x(nparams)=f2beta(tmpr); } } //Print the final estimates (after having them transformed) void PVM_single_c::print()const{ cout << "PVM (Single) FIT RESULTS " << endl; cout << "S0 :" << m_s0 << endl; cout << "D :" << m_d << endl; for(int i=1;i<=nfib;i++){ cout << "F" << i << " :" << m_f(i) << endl; ColumnVector x(3); x << sin(m_th(i))*cos(m_ph(i)) << sin(m_th(i))*sin(m_ph(i)) << cos(m_th(i)); if(x(3)<0)x=-x; float _th,_ph;cart2sph(x,_th,_ph); cout << "TH" << i << " :" << _th << endl; cout << "PH" << i << " :" << _ph << endl; cout << "DIR" << i << " : " << x(1) << " " << x(2) << " " << x(3) << endl; } if (m_include_f0) cout << "F0 :" << m_f0 << endl; if (m_eval_BIC) cout<< "BIC :"<<m_BIC<<endl; } //Print the estimates using a vector with the untransformed parameter values void PVM_single_c::print(const ColumnVector& p)const{ ColumnVector f(nfib); cout << "PARAMETER VALUES " << endl; cout << "S0 :" << p(1) << endl; cout << "D :" << lambda2d(p(2)) << endl; for(int i=3,ii=1;ii<=nfib;i+=3,ii++){ f(ii) = beta2f(p(i))*partial_fsum(f,ii-1); cout << "F" << ii << " :" << f(ii) << endl; cout << "TH" << ii << " :" << p(i+1)*180.0/M_PI << " deg" << endl; cout << "PH" << ii << " :" << p(i+2)*180.0/M_PI << " deg" << endl; } if (m_include_f0) cout << "F0 :" << beta2f(p(nparams))*partial_fsum(f,nfib); } ReturnMatrix PVM_single_c::get_prediction()const{ ColumnVector pred(npts); ColumnVector p(nparams); ColumnVector fs(nfib); fs=m_f; p(1) = m_s0; p(2) = d2lambda(m_d); for(int i=3,ii=1;ii<=nfib;i+=3,ii++){ float tmpr=m_f(ii)/partial_fsum(fs,ii-1); if (tmpr>1.0) tmpr=1; //This can be due to numerical errors p(i) = f2beta(tmpr); p(i+1) = m_th(ii); p(i+2) = m_ph(ii); } if (m_include_f0){ float tmpr=m_f0/partial_fsum(fs,nfib); if (tmpr>1.0) tmpr=1; //This can be due to numerical errors p(nparams)=f2beta(tmpr); } pred = forwardModel(p); pred.Release(); return pred; } NEWMAT::ReturnMatrix PVM_single_c::forwardModel(const NEWMAT::ColumnVector& p)const{ ColumnVector pred(npts); pred = 0; float val; float _d = lambda2d(p(2)); //////////////////////////////////// ColumnVector fs(nfib); Matrix x(nfib,3); float sumf=0; for(int k=1;k<=nfib;k++){ int kk = 3+3*(k-1); fs(k) = beta2f(p(kk))*partial_fsum(fs,k-1); sumf += fs(k); x(k,1) = sin(p(kk+1))*cos(p(kk+2)); x(k,2) = sin(p(kk+1))*sin(p(kk+2)); x(k,3) = cos(p(kk+1)); } //////////////////////////////////// for(int i=1;i<=Y.Nrows();i++){ val = 0.0; for(int k=1;k<=nfib;k++){ val += fs(k)*anisoterm(i,_d,x.Row(k).t()); } if (m_include_f0){ float temp_f0=beta2f(p(nparams))*partial_fsum(fs,nfib); pred(i) = p(1)*(temp_f0+(1-sumf-temp_f0)*isoterm(i,_d)+val); } else pred(i) = p(1)*((1-sumf)*isoterm(i,_d)+val); } pred.Release(); return pred; } //Cost Function, sum of squared residuals //assume that parameter values p are untransformed (e.g. need to transform them to get d, f's) double PVM_single_c::cf(const NEWMAT::ColumnVector& p)const{ //cout<<"CF"<<endl; //OUT(p.t()); double cfv = 0.0; double err; float _d = lambda2d(p(2)); //////////////////////////////////// ColumnVector fs(nfib); Matrix x(nfib,3); float sumf=0; for(int k=1;k<=nfib;k++){ int kk = 3+3*(k-1); fs(k) = beta2f(p(kk))*partial_fsum(fs,k-1); sumf += fs(k); x(k,1) = sin(p(kk+1))*cos(p(kk+2)); x(k,2) = sin(p(kk+1))*sin(p(kk+2)); x(k,3) = cos(p(kk+1)); } //////////////////////////////////// for(int i=1;i<=Y.Nrows();i++){ err = 0.0; for(int k=1;k<=nfib;k++){ err += fs(k)*anisoterm(i,_d,x.Row(k).t()); } if (m_include_f0){ float temp_f0=beta2f(p(nparams))*partial_fsum(fs,nfib); err = (p(1)*(temp_f0+(1-sumf-temp_f0)*isoterm(i,_d)+err) - Y(i)); } else err = (p(1)*((1-sumf)*isoterm(i,_d)+err) - Y(i)); cfv += err*err; } return(cfv); } NEWMAT::ReturnMatrix PVM_single_c::grad(const NEWMAT::ColumnVector& p)const{ //cout<<"GRAD"<<endl; //OUT(p.t()); NEWMAT::ColumnVector gradv(p.Nrows()); gradv = 0.0; float _d = lambda2d(p(2)); //////////////////////////////////// ColumnVector fs(nfib); ColumnVector bs(nfib); Matrix x(nfib,3);ColumnVector xx(3); ColumnVector yy(3); float sumf=0; for(int k=1;k<=nfib;k++){ int kk = 3+3*(k-1); bs(k)=p(kk); fs(k) = beta2f(p(kk))*partial_fsum(fs,k-1); sumf += fs(k); x(k,1) = sin(p(kk+1))*cos(p(kk+2)); x(k,2) = sin(p(kk+1))*sin(p(kk+2)); x(k,3) = cos(p(kk+1)); } //////////////////////////////////// Matrix f_deriv; //Compute the derivatives with respect to betas, i.e the transformed volume fraction variables f_deriv=fractions_deriv(nfib, fs, bs); Matrix J(npts,nparams); //Get the Jacobian of the model equation. The derivatives of the cost function //for parameter j will then be: Grad_j=Sum(2*(F(x_i)-Y_i)J(i,j)), Sum across data points i ColumnVector diff(npts); float sig, Iso_term; ColumnVector Aniso_terms(nfib); for(int i=1;i<=Y.Nrows();i++){ Iso_term=isoterm(i,_d); //Precompute some terms for this datapoint for(int k=1;k<=nfib;k++){ xx = x.Row(k).t(); Aniso_terms(k)=anisoterm(i,_d,xx); } sig = 0; J.Row(i)=0; for(int k=1;k<=nfib;k++){ int kk = 3+3*(k-1); xx = x.Row(k).t(); sig += fs(k)*Aniso_terms(k); //Total signal // other stuff for derivatives // lambda (i.e. d) J(i,2) += p(1)*fs(k)*anisoterm_lambda(i,p(2),xx); // beta (i.e. f) J(i,kk)=0; for (int j=1; j<=nfib; j++){ if (f_deriv(j,k)!=0) J(i,kk) += p(1)*(Aniso_terms(j)-Iso_term)*f_deriv(j,k); } // th J(i,kk+1) = p(1)*fs(k)*anisoterm_th(i,_d,xx,p(kk+1),p(kk+2)); // ph J(i,kk+2) = p(1)*fs(k)*anisoterm_ph(i,_d,xx,p(kk+1),p(kk+2)); } if (m_include_f0){ float temp_f0=beta2f(p(nparams))*partial_fsum(fs,nfib); //derivative with respect to f0 J(i,nparams)= p(1)*(1-Iso_term)*sin(2*p(nparams))*partial_fsum(fs,nfib); sig=p(1)*(temp_f0+(1-sumf-temp_f0)*Iso_term+sig); J(i,2) += p(1)*(1-sumf-temp_f0)*isoterm_lambda(i,p(2)); } else{ sig = p(1)*((1-sumf)*Iso_term+sig); J(i,2) += p(1)*(1-sumf)*isoterm_lambda(i,p(2)); //lambda } diff(i) = sig - Y(i); J(i,1) = sig/p(1); //S0 } gradv = 2*J.t()*diff; gradv.Release(); return gradv; } //this uses Gauss-Newton approximation, i.e Hij ~ Sum(Gj*Gk), with G the derivative of the cost function with respect to parameter j //and the Sum over all data points. boost::shared_ptr<BFMatrix> PVM_single_c::hess(const NEWMAT::ColumnVector& p,boost::shared_ptr<BFMatrix> iptr)const{ //cout<<"HESS"<<endl; //OUT(p.t()); boost::shared_ptr<BFMatrix> hessm; if (iptr && iptr->Nrows()==(unsigned int)p.Nrows() && iptr->Ncols()==(unsigned int)p.Nrows()) hessm = iptr; else hessm = boost::shared_ptr<BFMatrix>(new FullBFMatrix(p.Nrows(),p.Nrows())); float _d = lambda2d(p(2)); //////////////////////////////////// ColumnVector fs(nfib); ColumnVector bs(nfib); Matrix x(nfib,3);ColumnVector xx(3); ColumnVector yy(3); float sumf=0; for(int k=1;k<=nfib;k++){ int kk = 3+3*(k-1); bs(k)=p(kk); fs(k) = beta2f(p(kk))*partial_fsum(fs,k-1); sumf += fs(k); x(k,1) = sin(p(kk+1))*cos(p(kk+2)); x(k,2) = sin(p(kk+1))*sin(p(kk+2)); x(k,3) = cos(p(kk+1)); } //////////////////////////////////// Matrix f_deriv; f_deriv=fractions_deriv(nfib, fs, bs); Matrix J(npts,nparams); float sig, Iso_term; ColumnVector Aniso_terms(nfib); for(int i=1;i<=Y.Nrows();i++){ Iso_term=isoterm(i,_d); //Precompute some terms for this datapoint for(int k=1;k<=nfib;k++){ xx = x.Row(k).t(); Aniso_terms(k)=anisoterm(i,_d,xx); } sig = 0; J.Row(i)=0; for(int k=1;k<=nfib;k++){ int kk = 3+3*(k-1); xx = x.Row(k).t(); sig += fs(k)*Aniso_terms(k); //Total signal // other stuff for derivatives // lambda (i.e. d) J(i,2) += p(1)*fs(k)*anisoterm_lambda(i,p(2),xx); // beta (i.e. f) J(i,kk)=0; for (int j=1; j<=nfib; j++){ if (f_deriv(j,k)!=0) J(i,kk) += p(1)*(Aniso_terms(j)-Iso_term)*f_deriv(j,k); } // th J(i,kk+1) = p(1)*fs(k)*anisoterm_th(i,_d,xx,p(kk+1),p(kk+2)); // ph J(i,kk+2) = p(1)*fs(k)*anisoterm_ph(i,_d,xx,p(kk+1),p(kk+2)); } if (m_include_f0){ float temp_f0=beta2f(p(nparams))*partial_fsum(fs,nfib); //derivative with respect to f0 J(i,nparams)= p(1)*(1-Iso_term)*sin(2*p(nparams))*partial_fsum(fs,nfib); sig=p(1)*(temp_f0+(1-sumf-temp_f0)*Iso_term+sig); J(i,2) += p(1)*(1-sumf-temp_f0)*isoterm_lambda(i,p(2)); } else{ sig = p(1)*((1-sumf)*Iso_term+sig); J(i,2) += p(1)*(1-sumf)*isoterm_lambda(i,p(2)); //lambda } J(i,1) = sig/p(1); //S0 } for (int i=1; i<=p.Nrows(); i++){ for (int j=i; j<=p.Nrows(); j++){ sig = 0.0; for(int k=1;k<=J.Nrows();k++) sig += 2*(J(k,i)*J(k,j)); hessm->Set(i,j,sig); } } for (int j=1; j<=p.Nrows(); j++) { for (int i=j+1; i<=p.Nrows(); i++) { hessm->Set(i,j,hessm->Peek(j,i)); } } return(hessm); } //Once the model is fitted: For each fibre, compute a 3x3 Hessian of the Cost function at the cartesian (x,y,z) coordinates of the orientation, //evaluated at the estimated parameters. Return the second eigenvector of the (inverse) Hessian - No need to invert though! void PVM_single_c::eval_Hessian_at_peaks(){ vector<ColumnVector> V; ColumnVector temp_vec(3); float fiso=1,dot, Saniso,dSaniso_dx,dSaniso_dy,dSaniso_dz; ColumnVector S(npts), F(npts); Matrix dS_dx(npts,nfib), dS_dy(npts,nfib), dS_dz(npts,nfib); Matrix dS_dxdx(npts,nfib), dS_dydy(npts,nfib), dS_dzdz(npts,nfib), dS_dxdy(npts,nfib),dS_dxdz(npts,nfib),dS_dydz(npts,nfib); for (int n=1; n<=nfib; n++){ temp_vec<< cos(m_ph(n))*sin(m_th(n)) << sin(m_ph(n))*sin(m_th(n)) << cos(m_th(n)); V.push_back(temp_vec); fiso-=m_f(n); } for (int i=1; i<=npts; i++){ float bd=bvals(1,i)*m_d; S(i)=fiso*exp(-bd); for (int n=1; n<=nfib; n++){ dot=V[n-1](1)*bvecs(1,i)+V[n-1](2)*bvecs(2,i)+V[n-1](3)*bvecs(3,i); Saniso=exp(-bd*dot*dot); S(i)=S(i)+m_f(n)*Saniso; float dot1=-2*bd*dot*Saniso; float dot2=-2*bd*dot; float dot3=-2*bd*Saniso; float fS0=m_s0*m_f(n); //First Derivatives of the signal wrt x,y,z dSaniso_dx=dot1*bvecs(1,i); dSaniso_dy=dot1*bvecs(2,i); dSaniso_dz=dot1*bvecs(3,i); dS_dx(i,n)=fS0*dSaniso_dx; dS_dy(i,n)=fS0*dSaniso_dy; dS_dz(i,n)=fS0*dSaniso_dz; //Second Derivatives of the signal wrt x,y,z dS_dxdx(i,n)=fS0*bvecs(1,i)*(dSaniso_dx*dot2+dot3*bvecs(1,i)); dS_dydy(i,n)=fS0*bvecs(2,i)*(dSaniso_dy*dot2+dot3*bvecs(2,i)); dS_dzdz(i,n)=fS0*bvecs(3,i)*(dSaniso_dz*dot2+dot3*bvecs(3,i)); dS_dxdy(i,n)=fS0*bvecs(1,i)*(dSaniso_dy*dot2+dot3*bvecs(2,i)); dS_dxdz(i,n)=fS0*bvecs(1,i)*(dSaniso_dz*dot2+dot3*bvecs(3,i)); dS_dydz(i,n)=fS0*bvecs(2,i)*(dSaniso_dz*dot2+dot3*bvecs(3,i)); } F(i)=Y(i)-m_s0*S(i); } for (int n=1; n<=nfib; n++){ //For each fibre, compute the Hessian matrix of the cost function at (x,y,z) SymmetricMatrix H(3); H=0; for (int i=1; i<=npts; i++){ H(1,1)-=dS_dx(i,n)*dS_dx(i,n)-F(i)*dS_dxdx(i,n); H(2,2)-=dS_dy(i,n)*dS_dy(i,n)-F(i)*dS_dydy(i,n); H(3,3)-=dS_dz(i,n)*dS_dz(i,n)-F(i)*dS_dzdz(i,n); H(1,2)-=dS_dx(i,n)*dS_dy(i,n)-F(i)*dS_dxdy(i,n); H(1,3)-=dS_dx(i,n)*dS_dz(i,n)-F(i)*dS_dxdz(i,n); H(2,3)-=dS_dy(i,n)*dS_dz(i,n)-F(i)*dS_dydz(i,n); } H=-2*H; m_Hessian.push_back(H); //store the Hessian } } //For each fibre, get the projection of the inverse Hessian to the fanning plane. //Its first eigenvector will be utilized to get a fanning angle in [0,pi). void PVM_single_c::Fanning_angles_from_Hessian(){ Matrix Rth(3,3), Rph(3,3), R(3,3), H, A(3,3), P(3,3), E; DiagonalMatrix L; SymmetricMatrix Q(3); ColumnVector e1(3),vfib(3),v2(3),v3(3); eval_Hessian_at_peaks(); //Compute the Hessian for each fibre orientation //Then project the inverse Hessian to the fanning plane (perpendicular to the orientation) and obtain its first eigenvector for (int n=1; n<=nfib; n++){ //For each fitted fibre P << 0 << 0 << 0 << 0 << 1 << 0 << 0 << 0 << 1; H=m_Hessian[n-1]; float sinth=sin(m_th(n)), costh=cos(m_th(n)); float sinph=sin(m_ph(n)), cosph=cos(m_ph(n)); vfib<< sinth*cosph << sinth*sinph << costh; //Corresponding fibre orientation //Define two vectors that are orthogonal to vfib if (vfib(1)==0 && vfib(2)==0) //then, we have a [0 0 1] orientation v2<< 1 << 0 << 0; else v2 << vfib(2) << -vfib(1) << 0; //define v2, so that vfib*v2=0; float mag=sqrt(v2(1)*v2(1)+v2(2)*v2(2)+v2(3)*v2(3)); v2=v2/mag; v3 << vfib(2)*v2(3)-vfib(3)*v2(2) << vfib(3)*v2(1)-vfib(1)*v2(3) << vfib(1)*v2(2)-vfib(2)*v2(1); //Now get the cross product //Define a Projection Matrix to the plane perpendicular to the fibre orientation vfib A.Row(1)<<vfib.t(); A.Row(2)<<v2.t(); A.Row(3)<<v3.t(); P= P*A; try{ //If the Hessian is invertible (might not be in some background voxels) Q << P*H.i()*P.t(); //Project the inverse Hessian to this plane EigenValues(Q,L,E); //Eigendecompose the projected inverse Hessian e1 = E.Column(3); //Projected Hessian 1st eigenvector } catch(...){ //Otherwise give a random value and proceed e1<<1<<0<<0; } e1 << A.t()*e1; m_invprHes_e1.push_back(e1); //float dot=DotProduct(e1,vfib); ColumnVector p; //if (dot<0) {e1=-e1; dot=-dot; } //p=e1-dot*vfib; //Projection of e2 on a plane perpendicular to vfib //p=p/sqrt(p(1)*p(1)+p(2)*p(2)+p(3)*p(3)); //e1=p; Rth<<costh << 0 << -sinth <<0 << 1 << 0 <<sinth << 0 << costh; Rph<<cosph << sinph <<0 <<-sinph<< cosph <<0 <<0 << 0 <<1; R=Rth*Rph; //Rotation Matrix for vfib to become parallel to z e1<< R*e1; //Rotate the fanning plane effectively to xy plane m_fanning_angles(n)=atan2(-e1(1),e1(2)); //this gives [-pi,pi] range if (m_fanning_angles(n)<0) m_fanning_angles(n)+=M_PI; //transform it to [0,pi] //cout<<m_fanning_angles(n)<<endl<<endl; } } //////////////////////////////////////////////// // PARTIAL VOLUME MODEL - SINGLE SHELL (OLD) //////////////////////////////////////////////// void PVM_single::fit(){ // initialise with a tensor DTI dti(Y,bvecs,bvals); dti.linfit(); //dti.print(); // set starting parameters for nonlinear fitting float _th,_ph; cart2sph(dti.get_v1(),_th,_ph); ColumnVector start(nparams); start(1) = dti.get_s0(); //start(2) = dti.get_md()>0?dti.get_md()*2:0.001; // empirically found that d~2*MD start(2) = dti.get_l1()>0?dti.get_l1():0.002; // empirically found that d~L1 start(3) = dti.get_fa()<1?f2x(dti.get_fa()):f2x(0.95); // first pvf = FA start(4) = _th; start(5) = _ph; float sumf=x2f(start(3)); float tmpsumf=sumf; for(int ii=2,i=6;ii<=nfib;ii++,i+=3){ float denom=2; do{ start(i) = f2x(x2f(start(i-3))/denom); denom *= 2; tmpsumf = sumf + x2f(start(i)); }while(tmpsumf>=1); sumf += x2f(start(i)); cart2sph(dti.get_v(ii),_th,_ph); start(i+1) = _th; start(i+2) = _ph; } if (m_include_f0) start(nparams)=f2x(FSMALL); // do the fit NonlinParam lmpar(start.Nrows(),NL_LM); lmpar.SetGaussNewtonType(LM_L); lmpar.SetStartingEstimate(start); NonlinOut status; status = nonlin(lmpar,(*this)); ColumnVector final_par(nparams); final_par = lmpar.Par(); // finalise parameters m_s0 = final_par(1); m_d = std::abs(final_par(2)); for(int k=1;k<=nfib;k++){ int kk = 3 + 3*(k-1); m_f(k) = x2f(final_par(kk)); m_th(k) = final_par(kk+1); m_ph(k) = final_par(kk+2); } if (m_include_f0) m_f0=x2f(final_par(nparams)); sort(); fix_fsum(); } void PVM_single::sort(){ vector< pair<float,int> > fvals(nfib); ColumnVector ftmp(nfib),thtmp(nfib),phtmp(nfib); ftmp=m_f;thtmp=m_th;phtmp=m_ph; for(int i=1;i<=nfib;i++){ pair<float,int> p(m_f(i),i); fvals[i-1] = p; } std::sort(fvals.begin(),fvals.end()); for(int i=1,ii=nfib-1;ii>=0;i++,ii--){ m_f(i) = ftmp(fvals[ii].second); m_th(i) = thtmp(fvals[ii].second); m_ph(i) = phtmp(fvals[ii].second); } } void PVM_single::fix_fsum(){ float sumf=0; if (m_include_f0) sumf=m_f0; for(int i=1;i<=nfib;i++){ sumf+=m_f(i); if(sumf>=1){for(int j=i;j<=nfib;j++)m_f(j)=FSMALL; break;} } } ReturnMatrix PVM_single::get_prediction()const{ ColumnVector pred(npts); ColumnVector p(nparams); p(1) = m_s0; p(2) = m_d; for(int i=3,ii=1;ii<=nfib;i+=3,ii++){ p(i) = f2x(m_f(ii)); p(i+1) = m_th(ii); p(i+2) = m_ph(ii); } if (m_include_f0) p(nparams)=f2x(m_f0); pred = forwardModel(p); pred.Release(); return pred; } NEWMAT::ReturnMatrix PVM_single::forwardModel(const NEWMAT::ColumnVector& p)const{ //cout<<"FORWARD"<<endl; //OUT(p.t()); ColumnVector pred(npts); pred = 0; float val; float _d = std::abs(p(2)); //////////////////////////////////// ColumnVector fs(nfib); Matrix x(nfib,3); float sumf=0; for(int k=1;k<=nfib;k++){ int kk = 3+3*(k-1); fs(k) = x2f(p(kk)); sumf += fs(k); x(k,1) = sin(p(kk+1))*cos(p(kk+2)); x(k,2) = sin(p(kk+1))*sin(p(kk+2)); x(k,3) = cos(p(kk+1)); } //////////////////////////////////// for(int i=1;i<=Y.Nrows();i++){ val = 0.0; for(int k=1;k<=nfib;k++){ val += fs(k)*anisoterm(i,_d,x.Row(k).t()); } if (m_include_f0){ float temp_f0=x2f(p(nparams)); pred(i) = p(1)*(temp_f0+(1-sumf-temp_f0)*isoterm(i,_d)+val); } else pred(i) = p(1)*((1-sumf)*isoterm(i,_d)+val); } pred.Release(); //cout<<"----"<<endl; return pred; } double PVM_single::cf(const NEWMAT::ColumnVector& p)const{ //cout<<"CF"<<endl; //OUT(p.t()); double cfv = 0.0; double err; float _d = std::abs(p(2)); //////////////////////////////////// ColumnVector fs(nfib); Matrix x(nfib,3); float sumf=0; for(int k=1;k<=nfib;k++){ int kk = 3+3*(k-1); fs(k) = x2f(p(kk)); sumf += fs(k); x(k,1) = sin(p(kk+1))*cos(p(kk+2)); x(k,2) = sin(p(kk+1))*sin(p(kk+2)); x(k,3) = cos(p(kk+1)); } //////////////////////////////////// for(int i=1;i<=Y.Nrows();i++){ err = 0.0; for(int k=1;k<=nfib;k++){ err += fs(k)*anisoterm(i,_d,x.Row(k).t()); } if (m_include_f0){ float temp_f0=x2f(p(nparams)); err = (p(1)*(temp_f0+(1-sumf-temp_f0)*isoterm(i,_d)+err) - Y(i)); } else err = (p(1)*((1-sumf)*isoterm(i,_d)+err) - Y(i)); cfv += err*err; } //OUT(cfv); //cout<<"----"<<endl; return(cfv); } NEWMAT::ReturnMatrix PVM_single::grad(const NEWMAT::ColumnVector& p)const{ //cout<<"GRAD"<<endl; //OUT(p.t()); NEWMAT::ColumnVector gradv(p.Nrows()); gradv = 0.0; float _d = std::abs(p(2)); //////////////////////////////////// ColumnVector fs(nfib); Matrix x(nfib,3);ColumnVector xx(3); float sumf=0; for(int k=1;k<=nfib;k++){ int kk = 3+3*(k-1); fs(k) = x2f(p(kk)); sumf += fs(k); x(k,1) = sin(p(kk+1))*cos(p(kk+2)); x(k,2) = sin(p(kk+1))*sin(p(kk+2)); x(k,3) = cos(p(kk+1)); } //////////////////////////////////// Matrix J(npts,nparams); ColumnVector diff(npts); float sig; for(int i=1;i<=Y.Nrows();i++){ sig = 0; J.Row(i)=0; for(int k=1;k<=nfib;k++){ int kk = 3+3*(k-1); xx = x.Row(k).t(); sig += fs(k)*anisoterm(i,_d,xx); // other stuff for derivatives // d J(i,2) += (p(2)>0?1.0:-1.0)*p(1)*fs(k)*anisoterm_d(i,_d,xx); // f J(i,kk) = p(1)*(anisoterm(i,_d,xx)-isoterm(i,_d)) * two_pi*sign(p(kk))*1/(1+p(kk)*p(kk)); // th J(i,kk+1) = p(1)*fs(k)*anisoterm_th(i,_d,xx,p(kk+1),p(kk+2)); // ph J(i,kk+2) = p(1)*fs(k)*anisoterm_ph(i,_d,xx,p(kk+1),p(kk+2)); } if (m_include_f0){ float temp_f0=x2f(p(nparams)); //derivative with respect to f0 J(i,nparams)= p(1)*(1-isoterm(i,_d)) * two_pi*sign(p(nparams))*1/(1+p(nparams)*p(nparams)); sig=p(1)*(temp_f0+(1-sumf-temp_f0)*isoterm(i,_d)+sig); J(i,2) += (p(2)>0?1.0:-1.0)*p(1)*(1-sumf-temp_f0)*isoterm_d(i,_d); } else{ sig = p(1)*((1-sumf)*isoterm(i,_d)+sig); J(i,2) += (p(2)>0?1.0:-1.0)*p(1)*(1-sumf)*isoterm_d(i,_d); } diff(i) = sig - Y(i); J(i,1) = sig/p(1); } gradv = 2*J.t()*diff; gradv.Release(); return gradv; } //this uses Gauss-Newton approximation boost::shared_ptr<BFMatrix> PVM_single::hess(const NEWMAT::ColumnVector& p,boost::shared_ptr<BFMatrix> iptr)const{ //cout<<"HESS"<<endl; //OUT(p.t()); boost::shared_ptr<BFMatrix> hessm; if (iptr && iptr->Nrows()==(unsigned int)p.Nrows() && iptr->Ncols()==(unsigned int)p.Nrows()) hessm = iptr; else hessm = boost::shared_ptr<BFMatrix>(new FullBFMatrix(p.Nrows(),p.Nrows())); float _d = std::abs(p(2)); //////////////////////////////////// ColumnVector fs(nfib); Matrix x(nfib,3);ColumnVector xx(3); float sumf=0; for(int k=1;k<=nfib;k++){ int kk = 3+3*(k-1); fs(k) = x2f(p(kk)); sumf += fs(k); x(k,1) = sin(p(kk+1))*cos(p(kk+2)); x(k,2) = sin(p(kk+1))*sin(p(kk+2)); x(k,3) = cos(p(kk+1)); } //////////////////////////////////// Matrix J(npts,nparams); float sig; for(int i=1;i<=Y.Nrows();i++){ sig = 0; J.Row(i)=0; for(int k=1;k<=nfib;k++){ int kk = 3+3*(k-1); xx = x.Row(k).t(); sig += fs(k)*anisoterm(i,_d,xx); // other stuff for derivatives // d J(i,2) += (p(2)>0?1.0:-1.0)*p(1)*fs(k)*anisoterm_d(i,_d,xx); // f J(i,kk) = p(1)*(anisoterm(i,_d,xx)-isoterm(i,_d)) * two_pi*sign(p(kk))*1/(1+p(kk)*p(kk)); // th J(i,kk+1) = p(1)*fs(k)*anisoterm_th(i,_d,xx,p(kk+1),p(kk+2)); // ph J(i,kk+2) = p(1)*fs(k)*anisoterm_ph(i,_d,xx,p(kk+1),p(kk+2)); } if (m_include_f0){ float temp_f0=x2f(p(nparams)); //derivative with respect to f0 J(i,nparams)= p(1)*(1-isoterm(i,_d)) * two_pi*sign(p(nparams))*1/(1+p(nparams)*p(nparams)); sig=p(1)*(temp_f0+(1-sumf-temp_f0)*isoterm(i,_d)+sig); J(i,2) += (p(2)>0?1.0:-1.0)*p(1)*(1-sumf-temp_f0)*isoterm_d(i,_d); } else{ sig = p(1)*((1-sumf)*isoterm(i,_d)+sig); J(i,2) += (p(2)>0?1.0:-1.0)*p(1)*(1-sumf)*isoterm_d(i,_d); } J(i,1) = sig/p(1); } for (int i=1; i<=p.Nrows(); i++){ for (int j=i; j<=p.Nrows(); j++){ sig = 0.0; for(int k=1;k<=J.Nrows();k++) sig += 2*(J(k,i)*J(k,j)); hessm->Set(i,j,sig); } } for (int j=1; j<=p.Nrows(); j++) { for (int i=j+1; i<=p.Nrows(); i++) { hessm->Set(i,j,hessm->Peek(j,i)); } } //hessm->Print(); //cout<<"----"<<endl; return(hessm); } //////////////////////////////////////////////// // PARTIAL VOLUME MODEL - MULTIPLE SHELLS //////////////////////////////////////////////// void PVM_multi::fit(){ // initialise with simple pvm PVM_single_c pvm1(Y,bvecs,bvals,nfib,false,m_include_f0); pvm1.fit(); //cout<<"Init with single"<<endl; //pvm1.print(); float _a,_b; _a = 1.0; // start with d=d_std _b = pvm1.get_d(); ColumnVector start(nparams); start(1) = pvm1.get_s0(); start(2) = _a; start(3) = _b; for(int i=1,ii=4;i<=nfib;i++,ii+=3){ start(ii) = f2x(pvm1.get_f(i)); start(ii+1) = pvm1.get_th(i); start(ii+2) = pvm1.get_ph(i); } if (m_include_f0) start(nparams)=f2x(pvm1.get_f0()); // do the fit NonlinParam lmpar(start.Nrows(),NL_LM); lmpar.SetGaussNewtonType(LM_L); lmpar.SetStartingEstimate(start); NonlinOut status; status = nonlin(lmpar,(*this)); ColumnVector final_par(nparams); final_par = lmpar.Par(); // finalise parameters m_s0 = final_par(1); m_d = std::abs(final_par(2)*final_par(3)); m_d_std = std::sqrt(std::abs(final_par(2)*final_par(3)*final_par(3))); for(int i=4,k=1;k<=nfib;i+=3,k++){ m_f(k) = x2f(final_par(i)); m_th(k) = final_par(i+1); m_ph(k) = final_par(i+2); } if (m_include_f0) m_f0=x2f(final_par(nparams)); sort(); fix_fsum(); } void PVM_multi::sort(){ vector< pair<float,int> > fvals(nfib); ColumnVector ftmp(nfib),thtmp(nfib),phtmp(nfib); ftmp=m_f;thtmp=m_th;phtmp=m_ph; for(int i=1;i<=nfib;i++){ pair<float,int> p(m_f(i),i); fvals[i-1] = p; } std::sort(fvals.begin(),fvals.end()); for(int i=1,ii=nfib-1;ii>=0;i++,ii--){ m_f(i) = ftmp(fvals[ii].second); m_th(i) = thtmp(fvals[ii].second); m_ph(i) = phtmp(fvals[ii].second); } } void PVM_multi::fix_fsum(){ float sumf=0; if (m_include_f0) sumf=m_f0; for(int i=1;i<=nfib;i++){ if (m_f(i)==0) m_f(i)=FSMALL; sumf+=m_f(i); if(sumf>=1){for(int j=i;j<=nfib;j++)m_f(j)=FSMALL;break;} } } ReturnMatrix PVM_multi::get_prediction()const{ ColumnVector pred(npts); ColumnVector p(nparams); p(1) = m_s0; p(2) = m_d*m_d/m_d_std/m_d_std; p(3) = m_d_std*m_d_std/m_d; // =1/beta for(int k=1;k<=nfib;k++){ int kk = 4+3*(k-1); p(kk) = f2x(m_f(k)); p(kk+1) = m_th(k); p(kk+2) = m_ph(k); } if (m_include_f0) p(nparams)=f2x(m_f0); pred = forwardModel(p); pred.Release(); return pred; } NEWMAT::ReturnMatrix PVM_multi::forwardModel(const NEWMAT::ColumnVector& p)const{ ColumnVector pred(npts); pred = 0; float val; float _a = std::abs(p(2)); float _b = std::abs(p(3)); //////////////////////////////////// ColumnVector fs(nfib); Matrix x(nfib,3); float sumf=0; for(int k=1;k<=nfib;k++){ int kk = 4+3*(k-1); fs(k) = x2f(p(kk)); sumf += fs(k); x(k,1) = sin(p(kk+1))*cos(p(kk+2)); x(k,2) = sin(p(kk+1))*sin(p(kk+2)); x(k,3) = cos(p(kk+1)); } //////////////////////////////////// for(int i=1;i<=Y.Nrows();i++){ val = 0.0; for(int k=1;k<=nfib;k++){ val += fs(k)*anisoterm(i,_a,_b,x.Row(k).t()); } if (m_include_f0){ float temp_f0=x2f(p(nparams)); pred(i) = std::abs(p(1))*(temp_f0+(1-sumf-temp_f0)*isoterm(i,_a,_b)+val); } else pred(i) = std::abs(p(1))*((1-sumf)*isoterm(i,_a,_b)+val); } pred.Release(); return pred; } double PVM_multi::cf(const NEWMAT::ColumnVector& p)const{ //cout<<"CF"<<endl; //OUT(p.t()); double cfv = 0.0; double err; float _a = std::abs(p(2)); float _b = std::abs(p(3)); //////////////////////////////////// ColumnVector fs(nfib); Matrix x(nfib,3); float sumf=0; for(int k=1;k<=nfib;k++){ int kk = 4+3*(k-1); fs(k) = x2f(p(kk)); sumf += fs(k); x(k,1) = sin(p(kk+1))*cos(p(kk+2)); x(k,2) = sin(p(kk+1))*sin(p(kk+2)); x(k,3) = cos(p(kk+1)); } //////////////////////////////////// for(int i=1;i<=Y.Nrows();i++){ err = 0.0; for(int k=1;k<=nfib;k++){ err += fs(k)*anisoterm(i,_a,_b,x.Row(k).t()); } if (m_include_f0){ float temp_f0=x2f(p(nparams)); err = (std::abs(p(1))*(temp_f0+(1-sumf-temp_f0)*isoterm(i,_a,_b)+err) - Y(i)); } else err = (std::abs(p(1))*((1-sumf)*isoterm(i,_a,_b)+err) - Y(i)); cfv += err*err; } //OUT(cfv); //cout<<"----"<<endl; return(cfv); } NEWMAT::ReturnMatrix PVM_multi::grad(const NEWMAT::ColumnVector& p)const{ //cout<<"GRAD"<<endl; //OUT(p.t()); NEWMAT::ColumnVector gradv(p.Nrows()); gradv = 0.0; float _a = std::abs(p(2)); float _b = std::abs(p(3)); //////////////////////////////////// ColumnVector fs(nfib); Matrix x(nfib,3);ColumnVector xx(3); float sumf=0; for(int k=1;k<=nfib;k++){ int kk = 4+3*(k-1); fs(k) = x2f(p(kk)); sumf += fs(k); x(k,1) = sin(p(kk+1))*cos(p(kk+2)); x(k,2) = sin(p(kk+1))*sin(p(kk+2)); x(k,3) = cos(p(kk+1)); } //////////////////////////////////// Matrix J(npts,nparams); ColumnVector diff(npts); float sig; for(int i=1;i<=Y.Nrows();i++){ sig = 0; J.Row(i)=0; for(int k=1;k<=nfib;k++){ int kk = 4+3*(k-1); xx = x.Row(k).t(); sig += fs(k)*anisoterm(i,_a,_b,xx); // other stuff for derivatives // alpha J(i,2) += (p(2)>0?1.0:-1.0)*std::abs(p(1))*fs(k)*anisoterm_a(i,_a,_b,xx); // beta J(i,3) += (p(3)>0?1.0:-1.0)*std::abs(p(1))*fs(k)*anisoterm_b(i,_a,_b,xx); // f J(i,kk) = std::abs(p(1))*(anisoterm(i,_a,_b,xx)-isoterm(i,_a,_b)) * two_pi*sign(p(kk))*1/(1+p(kk)*p(kk)); // th J(i,kk+1) = std::abs(p(1))*fs(k)*anisoterm_th(i,_a,_b,xx,p(kk+1),p(kk+2)); // ph J(i,kk+2) = std::abs(p(1))*fs(k)*anisoterm_ph(i,_a,_b,xx,p(kk+1),p(kk+2)); } if (m_include_f0){ float temp_f0=x2f(p(nparams)); //derivative with respect to f0 J(i,nparams)= std::abs(p(1))*(1-isoterm(i,_a,_b))*two_pi*sign(p(nparams))*1/(1+p(nparams)*p(nparams)); sig=std::abs(p(1))*(temp_f0+(1-sumf-temp_f0)*isoterm(i,_a,_b)+sig); J(i,2) += (p(2)>0?1.0:-1.0)*std::abs(p(1))*(1-sumf-temp_f0)*isoterm_a(i,_a,_b); J(i,3) += (p(3)>0?1.0:-1.0)*std::abs(p(1))*(1-sumf-temp_f0)*isoterm_b(i,_a,_b); } else{ sig = std::abs(p(1))*((1-sumf)*isoterm(i,_a,_b)+sig); J(i,2) += (p(2)>0?1.0:-1.0)*std::abs(p(1))*(1-sumf)*isoterm_a(i,_a,_b); J(i,3) += (p(3)>0?1.0:-1.0)*std::abs(p(1))*(1-sumf)*isoterm_b(i,_a,_b); } diff(i) = sig - Y(i); J(i,1) = (p(1)>0?1.0:-1.0)*sig/p(1); } gradv = 2*J.t()*diff; //OUT(gradv.t()); //cout<<"----"<<endl; gradv.Release(); return gradv; } //this uses Gauss-Newton approximation boost::shared_ptr<BFMatrix> PVM_multi::hess(const NEWMAT::ColumnVector& p,boost::shared_ptr<BFMatrix> iptr)const{ //cout<<"HESS"<<endl; //OUT(p.t()); boost::shared_ptr<BFMatrix> hessm; if (iptr && iptr->Nrows()==(unsigned int)p.Nrows() && iptr->Ncols()==(unsigned int)p.Nrows()) hessm = iptr; else hessm = boost::shared_ptr<BFMatrix>(new FullBFMatrix(p.Nrows(),p.Nrows())); float _a = std::abs(p(2)); float _b = std::abs(p(3)); //////////////////////////////////// ColumnVector fs(nfib); Matrix x(nfib,3);ColumnVector xx(3); float sumf=0; for(int k=1;k<=nfib;k++){ int kk = 4+3*(k-1); fs(k) = x2f(p(kk)); sumf += fs(k); x(k,1) = sin(p(kk+1))*cos(p(kk+2)); x(k,2) = sin(p(kk+1))*sin(p(kk+2)); x(k,3) = cos(p(kk+1)); } //////////////////////////////////// Matrix J(npts,nparams); float sig; for(int i=1;i<=Y.Nrows();i++){ sig = 0; J.Row(i)=0; for(int k=1;k<=nfib;k++){ int kk = 4+3*(k-1); xx = x.Row(k).t(); sig += fs(k)*anisoterm(i,_a,_b,xx); // other stuff for derivatives // change of variable float cov = two_pi*sign(p(kk))*1/(1+p(kk)*p(kk)); // alpha J(i,2) += (p(2)>0?1.0:-1.0)*std::abs(p(1))*fs(k)*anisoterm_a(i,_a,_b,xx); // beta J(i,3) += (p(3)>0?1.0:-1.0)*std::abs(p(1))*fs(k)*anisoterm_b(i,_a,_b,xx); // f J(i,kk) = std::abs(p(1))*(anisoterm(i,_a,_b,xx)-isoterm(i,_a,_b)) * cov; // th J(i,kk+1) = std::abs(p(1))*fs(k)*anisoterm_th(i,_a,_b,xx,p(kk+1),p(kk+2)); // ph J(i,kk+2) = std::abs(p(1))*fs(k)*anisoterm_ph(i,_a,_b,xx,p(kk+1),p(kk+2)); } if (m_include_f0){ float temp_f0=x2f(p(nparams)); //derivative with respect to f0 J(i,nparams)= std::abs(p(1))*(1-isoterm(i,_a,_b))*two_pi*sign(p(nparams))*1/(1+p(nparams)*p(nparams)); sig=std::abs(p(1))*(temp_f0+(1-sumf-temp_f0)*isoterm(i,_a,_b)+sig); J(i,2) += (p(2)>0?1.0:-1.0)*std::abs(p(1))*(1-sumf-temp_f0)*isoterm_a(i,_a,_b); J(i,3) += (p(3)>0?1.0:-1.0)*std::abs(p(1))*(1-sumf-temp_f0)*isoterm_b(i,_a,_b); } else{ sig = std::abs(p(1))*((1-sumf)*isoterm(i,_a,_b)+sig); J(i,2) += (p(2)>0?1.0:-1.0)*std::abs(p(1))*(1-sumf)*isoterm_a(i,_a,_b); J(i,3) += (p(3)>0?1.0:-1.0)*std::abs(p(1))*(1-sumf)*isoterm_b(i,_a,_b); } J(i,1) = (p(1)>0?1.0:-1.0)*sig/p(1); } for (int i=1; i<=p.Nrows(); i++){ for (int j=i; j<=p.Nrows(); j++){ sig = 0.0; for(int k=1;k<=J.Nrows();k++) sig += 2*(J(k,i)*J(k,j)); hessm->Set(i,j,sig); } } for (int j=1; j<=p.Nrows(); j++) { for (int i=j+1; i<=p.Nrows(); i++) { hessm->Set(i,j,hessm->Peek(j,i)); } } //hessm->Print(); //cout<<"----"<<endl; return(hessm); } /////////////////////////////////////////////////////////////////////////// // FANNING MODEL - BALL & BINGHAMS // Constrained Optimization for the diffusivity, fractions and their sum<1, // and the Bingham eigenvalues ////////////////////////////////////////////////////////////////////////// void PVM_Ball_Binghams::fit(){ // Fit the ball & stick first to initialize some of the parameters PVM_single_c pvmbs(Y,bvecs,bvals,nfib,false,m_include_f0,true); //Return a fanning angle estimate as well pvmbs.fit(); // pvmbs.print(); ColumnVector k1_init, w_init; ColumnVector final_par(nparams); double minRSS=1e20; if (!m_gridsearch){ //Initialize the fanning eigenvalues using a grid or a set of intermediate values k1_init.ReSize(1); k1_init<<20; w_init.ReSize(1); w_init<<5.0; } else{ k1_init.ReSize(6); k1_init<< 10 << 20 << 50 << 100 << 500 << 1000; w_init.ReSize(6); w_init<< 1 << 5 << 10 << 20 << 50 << 90; } for (int n1=1; n1<=k1_init.Nrows(); n1++) for (int n2=1; n2<=w_init.Nrows(); n2++){ ColumnVector start(nparams); ColumnVector fs(nfib); fs=0; //Initialize the non-linear fitter. Transform all initial values to the unconstrained parameter space start(1) = pvmbs.get_s0(); start(2) = d2lambda(pvmbs.get_d()); for(int n=1,i=3; n<=nfib; n++,i+=nparams_per_fibre){ fs(n)=pvmbs.get_f(n); float tmpr=fs(n)/partial_fsum(fs,n-1); if (tmpr>1) tmpr=1; //This can be true due to numerical errors start(i) = f2beta(tmpr); start(i+1) = pvmbs.get_th(n); start(i+2) = pvmbs.get_ph(n); start(i+3) = pvmbs.get_fanning_angle(n); start(i+4) = k12l1(k1_init(n1)); start(i+5) = w2gam(w_init(n2)); } if (m_include_f0){ float tmpr=pvmbs.get_f0()/partial_fsum(fs,nfib); if (tmpr>1) tmpr=1; //This can be true due to numerical errors start(nparams)=f2beta(tmpr); } // do the fit NonlinParam lmpar(start.Nrows(),NL_LM); lmpar.SetGaussNewtonType(LM_LM); lmpar.SetStartingEstimate(start); //lmpar.LogCF(true); NonlinOut status; status = nonlin(lmpar,(*this)); ColumnVector tmp_par(nparams); tmp_par = lmpar.Par(); //cout<<"Number of Iterations: "<<lmpar.NIter()<<endl; //vector<double> Cf=lmpar.CFHistory(); //for (int n=0; n<(int)Cf.size(); n++) // cout<<Cf[n]<<" "; //cout<<endl; double RSS=cf(tmp_par); //get the sum of squared residuals if (RSS<=minRSS){ final_par=tmp_par; minRSS=RSS; } } if (m_eval_BIC){ m_BIC=npts*log(minRSS/npts)+log(npts)*nparams; //evaluate BIC //cout<<"RSS="<<RSS<<". BIC="<<m_BIC<<endl; } // finalise parameters m_s0 = final_par(1); m_d = lambda2d(final_par(2)); for(int n=1; n<=nfib; n++){ int kk=3+nparams_per_fibre*(n-1); m_f(n) = beta2f(final_par(kk))*partial_fsum(m_f,n-1); m_th(n) = final_par(kk+1); m_ph(n) = final_par(kk+2); m_psi(n)= final_par(kk+3); m_k1(n) = l12k1(final_par(kk+4)); m_k2(n) = m_k1(n)/gam2w(final_par(kk+5)); } if (m_include_f0) m_f0=beta2f(final_par(nparams))*partial_fsum(m_f,nfib); sort(); } void PVM_Ball_Binghams::sort(){ vector< pair<float,int> > fvals(nfib); ColumnVector ftmp(nfib),thtmp(nfib),phtmp(nfib),psitmp(nfib),k1tmp(nfib),k2tmp(nfib); ftmp=m_f;thtmp=m_th;phtmp=m_ph; psitmp=m_psi; k1tmp=m_k1; k2tmp=m_k2; for(int i=1;i<=nfib;i++){ pair<float,int> p(m_f(i),i); fvals[i-1] = p; } std::sort(fvals.begin(),fvals.end()); for(int i=1,ii=nfib-1;ii>=0;i++,ii--){ m_f(i) = ftmp(fvals[ii].second); m_th(i) = thtmp(fvals[ii].second); m_ph(i) = phtmp(fvals[ii].second); m_psi(i)= psitmp(fvals[ii].second); m_k1(i)= k1tmp(fvals[ii].second); m_k2(i)= k2tmp(fvals[ii].second); } } //Returns 1-Sum(f_j), 1<=j<=ii. (ii<=nfib) //Used for transforming beta to f and vice versa float PVM_Ball_Binghams::partial_fsum(ColumnVector& fs, int ii) const{ float fsum=1.0; for(int j=1;j<=ii;j++) fsum-=fs(j); if (fsum==0) //Very rare cases fsum=tiny; return fsum; } //Print the final estimates (after having them transformed) void PVM_Ball_Binghams::print()const{ cout << endl<<"Ball & Bingham FIT RESULTS " << endl; cout << "S0 :" << m_s0 << endl; cout << "D :" << m_d << endl; for(int i=1;i<=nfib;i++){ cout << "F" << i << " :" << m_f(i) << endl; ColumnVector x(3),fan_vec; x << sin(m_th(i))*cos(m_ph(i)) << sin(m_th(i))*sin(m_ph(i)) << cos(m_th(i)); fan_vec=get_fanning_vector(i); float _th,_ph,_psi;cart2sph(x,_th,_ph); _psi=m_psi(i); if(x(3)<0) {x=-x; _psi=M_PI-m_psi(i); } cout << "TH" << i << " : " << _th*180.0/M_PI << " deg" << endl; cout << "PH" << i << " : " << _ph*180.0/M_PI << " deg" << endl; cout << "PSI" << i << " : " <<_psi<<endl; cout << "DIR" << i << " : " << x(1) << " " << x(2) << " " << x(3) << endl; cout << "FAN_DIR" << i << " : " << fan_vec(1) << " " << fan_vec(2) << " " << fan_vec(3) << endl; cout << "K1_" << i << " : " <<m_k1(i)<<endl; cout << "K2_" << i << " : " <<m_k2(i)<<endl; } if (m_include_f0) cout << "F0 :" << m_f0 << endl; if (m_eval_BIC) cout<< "BIC :"<<m_BIC<<endl; } //Print the estimates using a vector that contains the transformed parameter values //i.e. need to untransform them to get d,f's etc void PVM_Ball_Binghams::print(const ColumnVector& p)const{ ColumnVector f(nfib); cout << "PARAMETER VALUES " << endl; cout << "S0 :" << p(1) << endl; cout << "D :" << lambda2d(p(2)) << endl; for(int i=3,ii=1;ii<=nfib;i+=3,ii++){ f(ii) = beta2f(p(i))*partial_fsum(f,ii-1); float k1=l12k1(p(i+4)); cout << "F" << ii << " :" << f(ii) << endl; cout << "TH" << ii << " :" << p(i+1)*180.0/M_PI << " deg" << endl; cout << "PH" << ii << " :" << p(i+2)*180.0/M_PI << " deg" << endl; cout << "PSI" << ii << " :"<< p(i+3)*180.0/M_PI << " deg" << endl; cout << "k1_" << ii << " :"<< k1 << endl; cout << "k2_" << ii << " :"<< k1/gam2w(p(i+4))<< endl; } if (m_include_f0) cout << "F0 :" << beta2f(p(nparams))*partial_fsum(f,nfib); } //Applies the forward model and gets the model predicted signal using the estimated parameter values (true,non-transformed space) ReturnMatrix PVM_Ball_Binghams::get_prediction()const{ ColumnVector pred(npts); ColumnVector p(nparams); ColumnVector fs(nfib); fs=m_f; p(1) = m_s0; //Transform parameters to the space where they are uncostrained p(2) = d2lambda(m_d); for(int i=3,ii=1;ii<=nfib;i+=nparams_per_fibre,ii++){ float tmpr=m_f(ii)/partial_fsum(fs,ii-1); if (tmpr>1.0) tmpr=1; //This can be due to numerical errors p(i) = f2beta(tmpr); p(i+1) = m_th(ii); p(i+2) = m_ph(ii); p(i+3) = m_psi(ii); p(i+4) = k12l1(m_k1(ii)); p(i+5) = w2gam(m_k1(ii)/m_k2(ii)); } if (m_include_f0){ float tmpr=m_f0/partial_fsum(fs,nfib); if (tmpr>1.0) tmpr=1; //This can be due to numerical errors p(nparams)=f2beta(tmpr); } pred = forwardModel(p); pred.Release(); return pred; } //Applies the forward model and gets a model predicted signal using the parameter values in p (transformed parameter space) NEWMAT::ReturnMatrix PVM_Ball_Binghams::forwardModel(const NEWMAT::ColumnVector& p)const{ ColumnVector pred(npts); pred = 0; float val; float _d = lambda2d(p(2)); //////////////////////////////////// ColumnVector fs(nfib); ColumnVector temp_vec(3), denom(3); Matrix Rpsi(3,3), Rth(3,3), Rph(3,3), R(3,3); vector<Matrix> B; vector<ColumnVector> approx_denomB; DiagonalMatrix L(3); SymmetricMatrix Q(3); L=0; Rpsi=0; Rth=0; Rph=0; Rth(2,2)=1; Rph(3,3)=1; Rpsi(3,3)=1; float sumf=0; fs=0; for(int k=1;k<=nfib;k++){ int kk = 3+nparams_per_fibre*(k-1); fs(k) = beta2f(p(kk))*partial_fsum(fs,k-1); sumf += fs(k); float cosph, sinph,cospsi,sinpsi,costh,sinth,k1,k2; costh=cos(p(kk+1)); sinth=sin(p(kk+1)); cosph=cos(p(kk+2)); sinph=sin(p(kk+2)); cospsi=cos(p(kk+3)); sinpsi=sin(p(kk+3)); k1=l12k1(p(kk+4)); k2=k1/gam2w(p(kk+5)); L(1)=-k1; L(2)=-k2; denom<<L(1)<<L(2)<<0; Rth(1,1)=costh; Rth(1,3)=-sinth; Rth(3,1)=sinth; Rth(3,3)=costh; Rph(1,1)=cosph; Rph(1,2)=sinph; Rph(2,1)=-sinph; Rph(2,2)=cosph; Rpsi(1,1)=cospsi; Rpsi(1,2)=sinpsi; Rpsi(2,1)=-sinpsi; Rpsi(2,2)=cospsi; R=Rpsi*Rth*Rph; R<<R.t()*L*R; B.push_back(R); temp_vec=approx_denominatorB(denom); approx_denomB.push_back(temp_vec); } //////////////////////////////////// for(int i=1;i<=Y.Nrows();i++){ val = 0.0; for(int k=1;k<=nfib;k++){ Q<<B[k-1]-_d*bvecs_dyadic[i-1]; EigenValues(Q,L); temp_vec<<L(1)<<L(2)<<L(3); val += fs(k)*hyp_SratioB_knowndenom(temp_vec,approx_denomB[k-1]); } if (m_include_f0){ float temp_f0=beta2f(p(nparams))*partial_fsum(fs,nfib); pred(i) = p(1)*(temp_f0+(1-sumf-temp_f0)*isoterm(i,_d)+val); } else pred(i) = p(1)*((1-sumf)*isoterm(i,_d)+val); } pred.Release(); return pred; } //Instead of returning the model predicted signal for each direction //returns the individual signal contributions i.e. isotropic, anisotropic1, anisotropic2,etc. //Weighting with the fractions, scaling with S0 and summing those gives the signal. //A Matrix npts x (nfib+1) is returned NEWMAT::ReturnMatrix PVM_Ball_Binghams::forwardModel_compartments(const NEWMAT::ColumnVector& p) const{ Matrix Sig(npts,nfib+1); float _d = lambda2d(p(2)); //////////////////////////////////// ColumnVector fs(nfib); ColumnVector temp_vec(3), denom(3); Matrix Rpsi(3,3), Rth(3,3), Rph(3,3), R(3,3); vector<Matrix> B; vector<ColumnVector> approx_denomB; DiagonalMatrix L(3); SymmetricMatrix Q(3); L=0; Rpsi=0; Rth=0; Rph=0; Rth(2,2)=1; Rph(3,3)=1; Rpsi(3,3)=1; float sumf=0; fs=0; for(int k=1;k<=nfib;k++){ int kk = 3+nparams_per_fibre*(k-1); fs(k) = beta2f(p(kk))*partial_fsum(fs,k-1); sumf += fs(k); float cosph, sinph,cospsi,sinpsi,costh,sinth,k1,k2; costh=cos(p(kk+1)); sinth=sin(p(kk+1)); cosph=cos(p(kk+2)); sinph=sin(p(kk+2)); cospsi=cos(p(kk+3)); sinpsi=sin(p(kk+3)); k1=l12k1(p(kk+4)); k2=k1/gam2w(p(kk+5)); L(1)=-k1; L(2)=-k2; denom<<L(1)<<L(2)<<0; Rth(1,1)=costh; Rth(1,3)=-sinth; Rth(3,1)=sinth; Rth(3,3)=costh; Rph(1,1)=cosph; Rph(1,2)=sinph; Rph(2,1)=-sinph; Rph(2,2)=cosph; Rpsi(1,1)=cospsi; Rpsi(1,2)=sinpsi; Rpsi(2,1)=-sinpsi; Rpsi(2,2)=cospsi; R=Rpsi*Rth*Rph; R<<R.t()*L*R; B.push_back(R); temp_vec=approx_denominatorB(denom); approx_denomB.push_back(temp_vec); } //////////////////////////////////// for(int i=1;i<=Y.Nrows();i++){ Sig(i,1) = isoterm(i,_d); for(int k=1;k<=nfib;k++){ Q<<B[k-1]-_d*bvecs_dyadic[i-1]; EigenValues(Q,L); temp_vec<<L(1)<<L(2)<<L(3); Sig(i,k+1)= hyp_SratioB_knowndenom(temp_vec,approx_denomB[k-1]); } } Sig.Release(); return Sig; } //Builds up the model predicted signal for each direction by using precomputed individual compartment signals, stored in Matrix Sig. //Weights them with the fractions, scales with S0 and sums to get the signal. NEWMAT::ReturnMatrix PVM_Ball_Binghams::pred_from_compartments(const NEWMAT::ColumnVector& p, const NEWMAT::Matrix& Sig) const{ ColumnVector pred(npts); float val; ColumnVector fs(nfib); float sumf=0; fs=0; for(int k=1;k<=nfib;k++){ int kk = 3+nparams_per_fibre*(k-1); fs(k) = beta2f(p(kk))*partial_fsum(fs,k-1); sumf += fs(k); } for(int i=1;i<=Y.Nrows();i++){ val = 0.0; for(int k=1;k<=nfib;k++) val += fs(k)*Sig(i,k+1); if (m_include_f0){ float temp_f0=beta2f(p(nparams))*partial_fsum(fs,nfib); pred(i) = p(1)*(temp_f0+(1-sumf-temp_f0)*Sig(i,1)+val); } else pred(i) = p(1)*((1-sumf)*Sig(i,1)+val); } pred.Release(); return pred; } //Builds up the model predicted signal for each direction by using precomputed individual compartment signals, stored in Matrix Sig. //Weights them with the fractions, scales with S0 and sums to get the signal. //The signal of the fibre compartment with index fib is recalculated. NEWMAT::ReturnMatrix PVM_Ball_Binghams::pred_from_compartments(const NEWMAT::ColumnVector& p, const NEWMAT::Matrix& Sig,const int& fib) const{ ColumnVector pred(npts); Matrix newSig; float val; float _d = lambda2d(p(2)); //////////////////////////////////// ColumnVector fs(nfib); ColumnVector temp_vec(3), denom(3); Matrix Rpsi(3,3), Rth(3,3), Rph(3,3), R(3,3); DiagonalMatrix L(3); SymmetricMatrix Q(3); L=0; Rpsi=0; Rth=0; Rph=0; Rth(2,2)=1; Rph(3,3)=1; Rpsi(3,3)=1; float sumf=0; fs=0; for(int k=1;k<=nfib;k++){ int kk = 3+nparams_per_fibre*(k-1); fs(k) = beta2f(p(kk))*partial_fsum(fs,k-1); sumf += fs(k); } /////////////////////////////////////// int kk = 3+nparams_per_fibre*(fib-1); float cosph, sinph,cospsi,sinpsi,costh,sinth,k1,k2; costh=cos(p(kk+1)); sinth=sin(p(kk+1)); cosph=cos(p(kk+2)); sinph=sin(p(kk+2)); cospsi=cos(p(kk+3)); sinpsi=sin(p(kk+3)); k1=l12k1(p(kk+4)); k2=k1/gam2w(p(kk+5)); L(1)=-k1; L(2)=-k2; denom<<L(1)<<L(2)<<0; Rth(1,1)=costh; Rth(1,3)=-sinth; Rth(3,1)=sinth; Rth(3,3)=costh; Rph(1,1)=cosph; Rph(1,2)=sinph; Rph(2,1)=-sinph; Rph(2,2)=cosph; Rpsi(1,1)=cospsi; Rpsi(1,2)=sinpsi; Rpsi(2,1)=-sinpsi; Rpsi(2,2)=cospsi; R=Rpsi*Rth*Rph; R<<R.t()*L*R; temp_vec=approx_denominatorB(denom); denom=temp_vec; newSig=Sig; //Get the new Signal for compartment fib for(int i=1;i<=Y.Nrows();i++){ Q<<R-_d*bvecs_dyadic[i-1]; EigenValues(Q,L); temp_vec<<L(1)<<L(2)<<L(3); newSig(i,fib+1)=hyp_SratioB_knowndenom(temp_vec,denom); } /////////////////////////////////////// for(int i=1;i<=Y.Nrows();i++){ val = 0.0; for(int k=1;k<=nfib;k++) val += fs(k)*newSig(i,k+1); if (m_include_f0){ float temp_f0=beta2f(p(nparams))*partial_fsum(fs,nfib); pred(i) = p(1)*(temp_f0+(1-sumf-temp_f0)*newSig(i,1)+val); } else pred(i) = p(1)*((1-sumf)*newSig(i,1)+val); } pred.Release(); return pred; } //Cost Function, sum of squared residuals //assume that parameter values p are transformed (e.g. need to untransform them to get d, f's,etc) double PVM_Ball_Binghams::cf(const NEWMAT::ColumnVector& p)const{ double cfv = 0.0; double err; ColumnVector S; S=forwardModel(p); //Model predictions for(int i=1;i<=npts;i++){ err=S(i)-Y(i); //Residual cfv+=err*err; //Sum of squared residuals } //cout<<"CF="<<cfv<<endl; OUT(p.t()); return(cfv); } /* //Slower implementation NEWMAT::ReturnMatrix PVM_Ball_Binghams::grad(const ColumnVector& p)const { ColumnVector gradv(nparams);//This is the gradient of the cost function Matrix J(npts,nparams); //This is the Jacobian matrix of the model equation //The derivative of the cost function w.r.t. parameter j //will then be: Grad_j=Sum(2*(F(x_i)-Y_i)*J(i,j)), with Sum across data points i ColumnVector diff(npts); //Residuals ColumnVector p_plus_h, S_trial,S; //Compute the Jacobian first using finite differences for each element double step; ColumnVector typical_scale(nparams); typical_scale=1; for (int k=1; k<=nfib; k++){ int kk = 3+nparams_per_fibre*(k-1); typical_scale(kk+4)=100; typical_scale(kk+5)=1; } S=forwardModel(p); diff=S-Y; for (int i=1; i<=npts; i++) J(i,1)=S(i)/p(1); //derivatives with respect to S0 are analytic: S_i/S0 for (int n=2; n<=nparams; n++) { p_plus_h = p; step = SQRTtiny*nonzerosign(p(n))*max(fabs(p(n))*typical_scale(n),1.0); step = nonzerosign(tiny)*min(max(fabs(step),1.0e-8),0.1); //check that 1e-8<step<0.1 p_plus_h(n)=p(n)+step; S_trial=forwardModel(p_plus_h); for (int i=1; i<=npts; i++) J(i,n)=(S_trial(i)-S(i))/step; } gradv =2*J.t()*diff; gradv.Release(); return(gradv); } //Slower implementation //this uses Gauss-Newton approximation boost::shared_ptr<BFMatrix> PVM_Ball_Binghams::hess(const NEWMAT::ColumnVector& p,boost::shared_ptr<BFMatrix> iptr)const{ boost::shared_ptr<BFMatrix> hessm; if (iptr && iptr->Nrows()==(unsigned int)p.Nrows() && iptr->Ncols()==(unsigned int)p.Nrows()) hessm = iptr; else hessm = boost::shared_ptr<BFMatrix>(new FullBFMatrix(p.Nrows(),p.Nrows())); Matrix J(npts,nparams); //This is the Jacobian matrix of the model equation ColumnVector p_plus_h, S_trial,S; //Compute the Jacobian first using finite differences for each element double step,sig; ColumnVector typical_scale(nparams); typical_scale=1; for (int k=1; k<=nfib; k++){ int kk = 3+nparams_per_fibre*(k-1); typical_scale(kk+4)=100; typical_scale(kk+5)=1; } S=forwardModel(p); for (int i=1; i<=npts; i++) J(i,1)=S(i)/p(1); //derivatives with respect to S0 are analytic: S_i/S0 for (int n=2; n<=nparams; n++) { p_plus_h = p; step = SQRTtiny*nonzerosign(p(n))*max(fabs(p(n))*typical_scale(n),1.0); step = nonzerosign(tiny)*min(max(fabs(step),1.0e-8),0.1); //check that 1e-8<step<0.1 p_plus_h(n)=p(n)+step; S_trial=forwardModel(p_plus_h); for (int i=1; i<=npts; i++) J(i,n)=(S_trial(i)-S(i))/step; } for (int i=1; i<=p.Nrows(); i++){ for (int j=i; j<=p.Nrows(); j++){ sig = 0.0; for(int k=1;k<=J.Nrows();k++) sig += 2*(J(k,i)*J(k,j)); hessm->Set(i,j,sig); } } for (int j=1; j<=p.Nrows(); j++) { for (int i=j+1; i<=p.Nrows(); i++) { hessm->Set(i,j,hessm->Peek(j,i)); } } return(hessm); } */ NEWMAT::ReturnMatrix PVM_Ball_Binghams::grad(const ColumnVector& p)const { ColumnVector gradv(nparams);//This is the gradient of the cost function Matrix J(npts,nparams); //This is the Jacobian matrix of the model equation //The derivative of the cost function w.r.t. parameter j //will then be: Grad_j=Sum(2*(F(x_i)-Y_i)*J(i,j)), with Sum across data points i ColumnVector diff(npts); //Residuals ColumnVector p_plus_h, S_trial,S; Matrix Sig; ColumnVector fs(nfib), bs(nfib); //Compute the Jacobian first using finite differences for each element. Derivatives are analytic for S0 and the volume fractions double step; ColumnVector typical_scale(nparams); typical_scale=1; for (int k=1; k<=nfib; k++){ int kk = 3+nparams_per_fibre*(k-1); bs(k)=p(kk); fs(k) = beta2f(p(kk))*partial_fsum(fs,k-1); typical_scale(kk+4)=100; typical_scale(kk+5)=1; } //Compute the derivatives with respect to betas, i.e the transformed volume fraction variables Matrix f_deriv; f_deriv=fractions_deriv(nfib, fs, bs); Sig=forwardModel_compartments(p); S=pred_from_compartments(p, Sig); diff=S-Y; for (int i=1; i<=npts; i++) J(i,1)=S(i)/p(1); //derivatives with respect to S0 are analytic: S_i/S0 for (int n=2; n<=nparams; n++) { p_plus_h = p; step = SQRTtiny*nonzerosign(p(n))*max(fabs(p(n))*typical_scale(n),1.0); step = nonzerosign(tiny)*min(max(fabs(step),1.0e-8),0.1); //check that 1e-8<step<0.1 p_plus_h(n)=p(n)+step; if (n<3 || (n==nparams && m_include_f0)){ //for d all compartments will change. Also for f0, if included. Use for those params numerical differentiation S_trial=forwardModel(p_plus_h); for (int i=1; i<=npts; i++) J(i,n)=(S_trial(i)-S(i))/step; } else{ //for the other params, update only the signal of the relevant fibre compartment int fib_indx=(int)ceil((float)(n-2)/(float)nparams_per_fibre); //index indicating in which fibre compartment parameter n belongs to if (n==3+nparams_per_fibre*(fib_indx-1)){ //Then we have a volume fraction, derivative is analytic. for (int i=1; i<=npts; i++){ J(i,n)=0; for (int j=1; j<=nfib; j++){ if (f_deriv(j,fib_indx)!=0) J(i,n) += p(1)*(Sig(i,j+1)-Sig(i,1))*f_deriv(j,fib_indx); } } } else{ //for all other params, use numerical differentiation S_trial=pred_from_compartments(p_plus_h, Sig,fib_indx); for (int i=1; i<=npts; i++){ J(i,n)=(S_trial(i)-S(i))/step; //if (J(i,n)==0) cout<<"Zero gradient!!"<<endl;//J(i,n)=1e-8; //stabilize LM in case differentiation has failed due to step size } } } } gradv =2*J.t()*diff; gradv.Release(); return(gradv); } //this uses Gauss-Newton approximation boost::shared_ptr<BFMatrix> PVM_Ball_Binghams::hess(const NEWMAT::ColumnVector& p,boost::shared_ptr<BFMatrix> iptr)const{ boost::shared_ptr<BFMatrix> hessm; if (iptr && iptr->Nrows()==(unsigned int)p.Nrows() && iptr->Ncols()==(unsigned int)p.Nrows()) hessm = iptr; else hessm = boost::shared_ptr<BFMatrix>(new FullBFMatrix(p.Nrows(),p.Nrows())); Matrix J(npts,nparams); //This is the Jacobian matrix of the model equation ColumnVector p_plus_h, S_trial,S, fs(nfib), bs(nfib); Matrix Sig; //Compute the Jacobian first using finite differences for each element. Derivatives are analytic for S0 and the volume fractions double step,sigt; ColumnVector typical_scale(nparams); typical_scale=1; for (int k=1; k<=nfib; k++){ int kk = 3+nparams_per_fibre*(k-1); bs(k)=p(kk); fs(k) = beta2f(p(kk))*partial_fsum(fs,k-1); typical_scale(kk+4)=100; typical_scale(kk+5)=1; } //Compute the derivatives with respect to betas, i.e the transformed volume fraction variables Matrix f_deriv; f_deriv=fractions_deriv(nfib, fs, bs); Sig=forwardModel_compartments(p); S=pred_from_compartments(p, Sig); for (int i=1; i<=npts; i++) J(i,1)=S(i)/p(1); //derivatives with respect to S0 are analytic: S_i/S0 for (int n=2; n<=nparams; n++) { p_plus_h = p; step = SQRTtiny*nonzerosign(p(n))*max(fabs(p(n))*typical_scale(n),1.0); step = nonzerosign(tiny)*min(max(fabs(step),1.0e-8),0.1); //check that 1e-8<step<0.1 p_plus_h(n)=p(n)+step; if (n<3 || (n==nparams && m_include_f0)){ //for d all compartments will change. Also for f0, if included. Use for those params numerical differentiation S_trial=forwardModel(p_plus_h); for (int i=1; i<=npts; i++) J(i,n)=(S_trial(i)-S(i))/step; } else{ //for the other params, update only the signal of the relevant fibre compartment int fib_indx=(int)ceil((float)(n-2)/(float)nparams_per_fibre); //index indicating in which fibre compartment parameter n belongs to if (n==3+nparams_per_fibre*(fib_indx-1)){ //Then we have a volume fraction, derivative is analytic. for (int i=1; i<=npts; i++){ J(i,n)=0; for (int j=1; j<=nfib; j++){ if (f_deriv(j,fib_indx)!=0) J(i,n) += p(1)*(Sig(i,j+1)-Sig(i,1))*f_deriv(j,fib_indx); } } } else{ //for all other params, use numerical differentiation S_trial=pred_from_compartments(p_plus_h, Sig,fib_indx); for (int i=1; i<=npts; i++){ J(i,n)=(S_trial(i)-S(i))/step; //if (J(i,n)==0) J(i,n)=1e-8; //stabilize LM in case differentiation has failed due to step size } } } } for (int i=1; i<=p.Nrows(); i++){ for (int j=i; j<=p.Nrows(); j++){ sigt = 0.0; for(int k=1;k<=J.Nrows();k++) sigt += 2*(J(k,i)*J(k,j)); hessm->Set(i,j,sigt); } } for (int j=1; j<=p.Nrows(); j++) { for (int i=j+1; i<=p.Nrows(); i++) { hessm->Set(i,j,hessm->Peek(j,i)); } } return(hessm); } float PVM_Ball_Binghams::isoterm(const int& pt,const float& _d)const{ return(std::exp(-bvals(1,pt)*_d)); } NEWMAT::ReturnMatrix PVM_Ball_Binghams::fractions_deriv(const int& nfib, const ColumnVector& fs, const ColumnVector& bs) const{ NEWMAT::Matrix Deriv(nfib,nfib); float fsum; Deriv=0; for (int j=1; j<=nfib; j++) for (int k=1; k<=nfib; k++){ if (j==k){ fsum=1; for (int n=1; n<=j-1; n++) fsum-=fs(n); Deriv(j,k)=sin(2*bs(k))*fsum; } else if (j>k){ fsum=0; for (int n=1; n<=j-1; n++) fsum+=Deriv(n,k); Deriv(j,k)=-pow(sin(bs(j)),2.0)*fsum; } } Deriv.Release(); return Deriv; } //Returns a vector that indicates the fanning orientation NEWMAT::ReturnMatrix PVM_Ball_Binghams:: get_fanning_vector(const int& i) const{ ColumnVector fan_vec(3); float t_th=m_th(i); float t_ph=m_ph(i); float t_psi=m_psi(i); float costh=cos(t_th); float sinth=sin(t_th); float cosph=cos(t_ph); float sinph=sin(t_ph); float cospsi=cos(t_psi); float sinpsi=sin(t_psi); /* Matrix Rpsi(3,3), Rth(3,3), Rph(3,3), R(3,3); Rpsi=0; Rth=0; Rph=0; Rth(2,2)=1; Rph(3,3)=1; Rpsi(3,3)=1; Rth(1,1)=costh; Rth(1,3)=-sinth; Rth(3,1)=sinth; Rth(3,3)=costh; Rph(1,1)=cosph; Rph(1,2)=sinph; Rph(2,1)=-sinph; Rph(2,2)=cosph; Rpsi(1,1)=cospsi; Rpsi(1,2)=sinpsi; Rpsi(2,1)=-sinpsi; Rpsi(2,2)=cospsi; R=Rpsi*Rth*Rph; */ //fan_vec(1)=R(2,1); fan_vec(2)=R(2,2); fan_vec(3)=R(2,3); fan_vec(1)=-sinpsi*costh*cosph-cospsi*sinph; fan_vec(2)=-sinpsi*costh*sinph+cospsi*cosph; fan_vec(3)=sinpsi*sinth; fan_vec.Release(); return fan_vec; } /////////////////////////////////////////////////////////////////////////// // FANNING MODEL - BALL & WATSONS // Constrained Optimization for the diffusivity, fractions and their sum<1, // and the Bingham eigenvalues ////////////////////////////////////////////////////////////////////////// void PVM_Ball_Watsons::fit(){ // Fit the ball & stick first to initialize some of the parameters PVM_single_c pvmbs(Y,bvecs,bvals,nfib,false,m_include_f0); pvmbs.fit(); // pvmbs.print(); ColumnVector k_init; ColumnVector final_par(nparams); double minRSS=1e20; if (!m_gridsearch){ k_init.ReSize(1); k_init<<20; } else{ k_init.ReSize(6); k_init<< 10 << 20 << 50 << 100 << 500 << 1000; } for (int n1=1; n1<=k_init.Nrows(); n1++){ ColumnVector start(nparams); ColumnVector fs(nfib); fs=0; //Initialize the non-linear fitter. Transform all initial values to the uncostrained parameter space start(1) = pvmbs.get_s0(); start(2) = d2lambda(pvmbs.get_d()); for(int n=1,i=3; n<=nfib; n++,i+=nparams_per_fibre){ fs(n)=pvmbs.get_f(n); float tmpr=fs(n)/partial_fsum(fs,n-1); if (tmpr>1) tmpr=1; //This can be true due to numerical errors start(i) = f2beta(tmpr); start(i+1) = pvmbs.get_th(n); start(i+2) = pvmbs.get_ph(n); start(i+3) = k12l1(k_init(n1)); } if (m_include_f0){ float tmpr=pvmbs.get_f0()/partial_fsum(fs,nfib); if (tmpr>1) tmpr=1; //This can be true due to numerical errors start(nparams)=f2beta(tmpr); } // do the fit NonlinParam lmpar(start.Nrows(),NL_LM); lmpar.SetGaussNewtonType(LM_LM); lmpar.SetStartingEstimate(start); //lmpar.LogCF(true); NonlinOut status; status = nonlin(lmpar,(*this)); ColumnVector tmp_par(nparams); tmp_par = lmpar.Par(); /*cout<<"Number of Iterations: "<<lmpar.NIter()<<endl; vector<double> Cf=lmpar.CFHistory(); for (int n=0; n<(int)Cf.size(); n++) cout<<Cf[n]<<" "; cout<<endl; */ double RSS=cf(tmp_par); //get the sum of squared residuals if (RSS<=minRSS){ final_par=tmp_par; minRSS=RSS; } } if (m_eval_BIC){ m_BIC=npts*log(minRSS/npts)+log(npts)*nparams; //evaluate BIC } // finalise parameters m_s0 = final_par(1); m_d = lambda2d(final_par(2)); for(int n=1; n<=nfib; n++){ int kk=3+nparams_per_fibre*(n-1); m_f(n) = beta2f(final_par(kk))*partial_fsum(m_f,n-1); m_th(n) = final_par(kk+1); m_ph(n) = final_par(kk+2); m_k(n) = l12k1(final_par(kk+3)); } if (m_include_f0) m_f0=beta2f(final_par(nparams))*partial_fsum(m_f,nfib); sort(); } void PVM_Ball_Watsons::sort(){ vector< pair<float,int> > fvals(nfib); ColumnVector ftmp(nfib),thtmp(nfib),phtmp(nfib),ktmp(nfib); ftmp=m_f;thtmp=m_th;phtmp=m_ph; ktmp=m_k; for(int i=1;i<=nfib;i++){ pair<float,int> p(m_f(i),i); fvals[i-1] = p; } std::sort(fvals.begin(),fvals.end()); for(int i=1,ii=nfib-1;ii>=0;i++,ii--){ m_f(i) = ftmp(fvals[ii].second); m_th(i) = thtmp(fvals[ii].second); m_ph(i) = phtmp(fvals[ii].second); m_k(i)= ktmp(fvals[ii].second); } } //Returns 1-Sum(f_j), 1<=j<=ii. (ii<=nfib) //Used for transforming beta to f and vice versa float PVM_Ball_Watsons::partial_fsum(ColumnVector& fs, int ii) const{ float fsum=1.0; for(int j=1;j<=ii;j++) fsum-=fs(j); if (fsum==0) //Very rare cases fsum=tiny; return fsum; } //Print the final estimates (after having them transformed) void PVM_Ball_Watsons::print()const{ cout << endl<<"Ball & Watson FIT RESULTS " << endl; cout << "S0 :" << m_s0 << endl; cout << "D :" << m_d << endl; for(int i=1;i<=nfib;i++){ cout << "F" << i << " :" << m_f(i) << endl; ColumnVector x(3); x << sin(m_th(i))*cos(m_ph(i)) << sin(m_th(i))*sin(m_ph(i)) << cos(m_th(i)); float _th,_ph;cart2sph(x,_th,_ph); if(x(3)<0) x=-x; cout << "TH" << i << " : " << _th*180.0/M_PI << " deg" << endl; cout << "PH" << i << " : " << _ph*180.0/M_PI << " deg" << endl; cout << "DIR" << i << " : " << x(1) << " " << x(2) << " " << x(3) << endl; cout << "K_" << i << " : " <<m_k(i)<<endl; } if (m_include_f0) cout << "F0 :" << m_f0 << endl; if (m_eval_BIC) cout<< "BIC :"<<m_BIC<<endl; } //Print the estimates using a vector that contains the transformed parameter values //i.e. need to untransform them to get d,f's etc void PVM_Ball_Watsons::print(const ColumnVector& p)const{ ColumnVector f(nfib); cout << "PARAMETER VALUES " << endl; cout << "S0 :" << p(1) << endl; cout << "D :" << lambda2d(p(2)) << endl; for(int i=3,ii=1;ii<=nfib;i+=3,ii++){ f(ii) = beta2f(p(i))*partial_fsum(f,ii-1); float _k=l12k1(p(i+3)); cout << "F" << ii << " :" << f(ii) << endl; cout << "TH" << ii << " :" << p(i+1)*180.0/M_PI << " deg" << endl; cout << "PH" << ii << " :" << p(i+2)*180.0/M_PI << " deg" << endl; cout << "K_" << ii << " :"<< _k << endl; } if (m_include_f0) cout << "F0 :" << beta2f(p(nparams))*partial_fsum(f,nfib); } //Applies the forward model and gets the model predicted signal using the estimated parameter values (true,non-transformed space) ReturnMatrix PVM_Ball_Watsons::get_prediction()const{ ColumnVector pred(npts); ColumnVector p(nparams); ColumnVector fs(nfib); fs=m_f; p(1) = m_s0; //Transform parameters to the space where they are uncostrained p(2) = d2lambda(m_d); for(int i=3,ii=1;ii<=nfib;i+=nparams_per_fibre,ii++){ float tmpr=m_f(ii)/partial_fsum(fs,ii-1); if (tmpr>1.0) tmpr=1; //This can be due to numerical errors p(i) = f2beta(tmpr); p(i+1) = m_th(ii); p(i+2) = m_ph(ii); p(i+3) = k12l1(m_k(ii)); } if (m_include_f0){ float tmpr=m_f0/partial_fsum(fs,nfib); if (tmpr>1.0) tmpr=1; //This can be due to numerical errors p(nparams)=f2beta(tmpr); } pred = forwardModel(p); pred.Release(); return pred; } //Applies the forward model and gets a model predicted signal using the parameter values in p (transformed parameter space) NEWMAT::ReturnMatrix PVM_Ball_Watsons::forwardModel(const NEWMAT::ColumnVector& p)const{ ColumnVector pred(npts); pred = 0; float val; float _d = lambda2d(p(2)); //////////////////////////////////// ColumnVector fs(nfib), ks(nfib); ColumnVector temp_vec(3), denom(3); Matrix v(nfib,3); vector<ColumnVector> approx_denomW; Matrix A(3,3); float sumf=0; fs=0; for(int n=1;n<=nfib;n++){ int nn = 3+nparams_per_fibre*(n-1); float cosph, sinph,costh,sinth; fs(n) = beta2f(p(nn))*partial_fsum(fs,n-1); sumf += fs(n); costh=cos(p(nn+1)); sinth=sin(p(nn+1)); cosph=cos(p(nn+2)); sinph=sin(p(nn+2)); v(n,1) = cosph*sinth; v(n,2) = sinph*sinth; v(n,3) = costh; ks(n)=l12k1(p(nn+3)); temp_vec=approx_denominatorW(ks(n)); approx_denomW.push_back(temp_vec); } //////////////////////////////////// for(int i=1;i<=Y.Nrows();i++){ val = 0.0; float bd=bvals(1,i)*_d; for(int n=1;n<=nfib;n++){ A=(v.Row(n)).t()*(bvecs.Column(i)).t(); float Q=-2*bd*ks(n)*(pow(A(1,1)+A(2,2)+A(3,3),2.0) - pow(A(2,1)-A(1,2),2.0) - pow(A(1,3)-A(3,1),2.0)-pow(A(2,3)-A(3,2),2.0)); Q=sqrt(ks(n)*ks(n)+bd*bd+Q); temp_vec<<0.5*(ks(n)-bd+Q)<<0.5*(ks(n)-bd-Q)<<0; val+= fs(n)*hyp_SratioW_knowndenom(temp_vec,approx_denomW[n-1]); } if (m_include_f0){ float temp_f0=beta2f(p(nparams))*partial_fsum(fs,nfib); pred(i) = p(1)*(temp_f0+(1-sumf-temp_f0)*isoterm(i,_d)+val); } else pred(i) = p(1)*((1-sumf)*isoterm(i,_d)+val); } pred.Release(); return pred; } //Instead of returning the model predicted signal for each direction //returns the individual signal contributions i.e. isotropic, anisotropic1, anisotropic2,etc. //Weighting with the fractions, scaling with S0 and summing those gives the signal. //A Matrix npts x (nfib+1) is returned NEWMAT::ReturnMatrix PVM_Ball_Watsons::forwardModel_compartments(const NEWMAT::ColumnVector& p) const{ Matrix Sig(npts,nfib+1); float _d = lambda2d(p(2)); //////////////////////////////////// ColumnVector ks(nfib); ColumnVector temp_vec(3), denom(3); Matrix v(nfib,3); vector<ColumnVector> approx_denomW; Matrix A(3,3); for(int n=1;n<=nfib;n++){ int nn = 3+nparams_per_fibre*(n-1); float cosph, sinph,costh,sinth; costh=cos(p(nn+1)); sinth=sin(p(nn+1)); cosph=cos(p(nn+2)); sinph=sin(p(nn+2)); v(n,1) = cosph*sinth; v(n,2) = sinph*sinth; v(n,3) = costh; ks(n)=l12k1(p(nn+3)); temp_vec=approx_denominatorW(ks(n)); approx_denomW.push_back(temp_vec); } //////////////////////////////////// for(int i=1;i<=Y.Nrows();i++){ Sig(i,1) = isoterm(i,_d); float bd=bvals(1,i)*_d; for(int n=1;n<=nfib;n++){ A=(v.Row(n)).t()*(bvecs.Column(i)).t(); float Q=-2*bd*ks(n)*(pow(A(1,1)+A(2,2)+A(3,3),2.0) - pow(A(2,1)-A(1,2),2.0) - pow(A(1,3)-A(3,1),2.0)-pow(A(2,3)-A(3,2),2.0)); Q=sqrt(ks(n)*ks(n)+bd*bd+Q); temp_vec<<0.5*(ks(n)-bd+Q)<<0.5*(ks(n)-bd-Q)<<0; Sig(i,n+1)= hyp_SratioW_knowndenom(temp_vec,approx_denomW[n-1]); } } Sig.Release(); return Sig; } //Builds up the model predicted signal for each direction by using precomputed individual compartment signals, stored in Matrix Sig. //Weights them with the fractions, scales with S0 and sums to get the signal. NEWMAT::ReturnMatrix PVM_Ball_Watsons::pred_from_compartments(const NEWMAT::ColumnVector& p, const NEWMAT::Matrix& Sig) const{ ColumnVector pred(npts); float val; ColumnVector fs(nfib); float sumf=0; fs=0; for(int n=1;n<=nfib;n++){ int nn = 3+nparams_per_fibre*(n-1); fs(n) = beta2f(p(nn))*partial_fsum(fs,n-1); sumf += fs(n); } for(int i=1;i<=Y.Nrows();i++){ val = 0.0; for(int n=1;n<=nfib;n++) val += fs(n)*Sig(i,n+1); if (m_include_f0){ float temp_f0=beta2f(p(nparams))*partial_fsum(fs,nfib); pred(i) = p(1)*(temp_f0+(1-sumf-temp_f0)*Sig(i,1)+val); } else pred(i) = p(1)*((1-sumf)*Sig(i,1)+val); } pred.Release(); return pred; } //Builds up the model predicted signal for each direction by using precomputed individual compartment signals, stored in Matrix Sig. //Weights them with the fractions, scales with S0 and sums to get the signal. //The signal of the fibre compartment with index fib is recalculated. NEWMAT::ReturnMatrix PVM_Ball_Watsons::pred_from_compartments(const NEWMAT::ColumnVector& p, const NEWMAT::Matrix& Sig,const int& fib) const{ ColumnVector pred(npts); Matrix newSig; float val; float _d = lambda2d(p(2)); //////////////////////////////////// ColumnVector fs(nfib); ColumnVector temp_vec(3), denom(3), v(3); Matrix A(3,3); float sumf=0; fs=0; for(int n=1;n<=nfib;n++){ int nn = 3+nparams_per_fibre*(n-1); fs(n) = beta2f(p(nn))*partial_fsum(fs,n-1); sumf += fs(n); } /////////////////////////////////////// int nn = 3+nparams_per_fibre*(fib-1); float cosph, sinph,costh,sinth,_k; costh=cos(p(nn+1)); sinth=sin(p(nn+1)); cosph=cos(p(nn+2)); sinph=sin(p(nn+2)); v(1) = cosph*sinth; v(2) = sinph*sinth; v(3) = costh; _k=l12k1(p(nn+3)); temp_vec=approx_denominatorW(_k); denom=temp_vec; newSig=Sig; //Get the new Signal for compartment fib for(int i=1;i<=Y.Nrows();i++){ float bd=bvals(1,i)*_d; A=v*(bvecs.Column(i)).t(); float Q=-2*bd*_k*(pow(A(1,1)+A(2,2)+A(3,3),2.0) - pow(A(2,1)-A(1,2),2.0) - pow(A(1,3)-A(3,1),2.0)-pow(A(2,3)-A(3,2),2.0)); Q=sqrt(_k*_k+bd*bd+Q); temp_vec<<0.5*(_k-bd+Q)<<0.5*(_k-bd-Q)<<0; newSig(i,fib+1)= hyp_SratioW_knowndenom(temp_vec,denom); } /////////////////////////////////////// for(int i=1;i<=Y.Nrows();i++){ val = 0.0; for(int n=1;n<=nfib;n++) val += fs(n)*newSig(i,n+1); if (m_include_f0){ float temp_f0=beta2f(p(nparams))*partial_fsum(fs,nfib); pred(i) = p(1)*(temp_f0+(1-sumf-temp_f0)*newSig(i,1)+val); } else pred(i) = p(1)*((1-sumf)*newSig(i,1)+val); } pred.Release(); return pred; } //Cost Function, sum of squared residuals //assume that parameter values p are transformed (e.g. need to untransform them to get d, f's,etc) double PVM_Ball_Watsons::cf(const NEWMAT::ColumnVector& p)const{ double cfv = 0.0; double err; ColumnVector S; S=forwardModel(p); //Model predictions for(int i=1;i<=npts;i++){ err=S(i)-Y(i); //Residual cfv+=err*err; //Sum of squared residuals } //cout<<"CF="<<cfv<<endl; OUT(p.t()); return(cfv); } NEWMAT::ReturnMatrix PVM_Ball_Watsons::grad(const ColumnVector& p)const { ColumnVector gradv(nparams);//This is the gradient of the cost function Matrix J(npts,nparams); //This is the Jacobian matrix of the model equation //The derivative of the cost function w.r.t. parameter j //will then be: Grad_j=Sum(2*(F(x_i)-Y_i)*J(i,j)), with Sum across data points i ColumnVector diff(npts); //Residuals ColumnVector p_plus_h, S_trial,S; Matrix Sig; ColumnVector fs(nfib), bs(nfib); //Compute the Jacobian first using finite differences for each element. Derivatives are analytic for S0 and the volume fractions double step; ColumnVector typical_scale(nparams); typical_scale=1; for (int k=1; k<=nfib; k++){ int kk = 3+nparams_per_fibre*(k-1); bs(k)=p(kk); fs(k) = beta2f(p(kk))*partial_fsum(fs,k-1); typical_scale(kk+3)=100; } //Compute the derivatives with respect to betas, i.e the transformed volume fraction variables Matrix f_deriv; f_deriv=fractions_deriv(nfib, fs, bs); Sig=forwardModel_compartments(p); S=pred_from_compartments(p, Sig); diff=S-Y; for (int i=1; i<=npts; i++) J(i,1)=S(i)/p(1); //derivatives with respect to S0 are analytic: S_i/S0 for (int n=2; n<=nparams; n++) { p_plus_h = p; step = SQRTtiny*nonzerosign(p(n))*max(fabs(p(n))*typical_scale(n),1.0); step = nonzerosign(tiny)*min(max(fabs(step),1.0e-8),0.1); //check that 1e-8<step<0.1 p_plus_h(n)=p(n)+step; if (n<3 || (n==nparams && m_include_f0)){ //for d all compartments will change. Also for f0, if included. Use for those params numerical differentiation S_trial=forwardModel(p_plus_h); for (int i=1; i<=npts; i++) J(i,n)=(S_trial(i)-S(i))/step; } else{ //for the other params, update only the signal of the relevant fibre compartment int fib_indx=(int)ceil((float)(n-2)/(float)nparams_per_fibre); //index indicating in which fibre compartment parameter n belongs to if (n==3+nparams_per_fibre*(fib_indx-1)){ //Then we have a volume fraction, derivative is analytic. for (int i=1; i<=npts; i++){ J(i,n)=0; for (int j=1; j<=nfib; j++){ if (f_deriv(j,fib_indx)!=0) J(i,n) += p(1)*(Sig(i,j+1)-Sig(i,1))*f_deriv(j,fib_indx); } } } else{ //for all other params, use numerical differentiation S_trial=pred_from_compartments(p_plus_h, Sig,fib_indx); for (int i=1; i<=npts; i++) J(i,n)=(S_trial(i)-S(i))/step; } } } gradv =2*J.t()*diff; gradv.Release(); return(gradv); } //this uses Gauss-Newton approximation boost::shared_ptr<BFMatrix> PVM_Ball_Watsons::hess(const NEWMAT::ColumnVector& p,boost::shared_ptr<BFMatrix> iptr)const{ boost::shared_ptr<BFMatrix> hessm; if (iptr && iptr->Nrows()==(unsigned int)p.Nrows() && iptr->Ncols()==(unsigned int)p.Nrows()) hessm = iptr; else hessm = boost::shared_ptr<BFMatrix>(new FullBFMatrix(p.Nrows(),p.Nrows())); Matrix J(npts,nparams); //This is the Jacobian matrix of the model equation ColumnVector p_plus_h, S_trial,S, fs(nfib), bs(nfib); Matrix Sig; //Compute the Jacobian first using finite differences for each element. Derivatives are analytic for S0 and the volume fractions double step,sigt; ColumnVector typical_scale(nparams); typical_scale=1; for (int k=1; k<=nfib; k++){ int kk = 3+nparams_per_fibre*(k-1); bs(k)=p(kk); fs(k) = beta2f(p(kk))*partial_fsum(fs,k-1); typical_scale(kk+3)=100; } //Compute the derivatives with respect to betas, i.e the transformed volume fraction variables Matrix f_deriv; f_deriv=fractions_deriv(nfib, fs, bs); Sig=forwardModel_compartments(p); S=pred_from_compartments(p, Sig); for (int i=1; i<=npts; i++) J(i,1)=S(i)/p(1); //derivatives with respect to S0 are analytic: S_i/S0 for (int n=2; n<=nparams; n++) { p_plus_h = p; step = SQRTtiny*nonzerosign(p(n))*max(fabs(p(n))*typical_scale(n),1.0); step = nonzerosign(tiny)*min(max(fabs(step),1.0e-8),0.1); //check that 1e-8<step<0.1 p_plus_h(n)=p(n)+step; if (n<3 || (n==nparams && m_include_f0)){ //for d all compartments will change. Also for f0, if included. Use for those params numerical differentiation S_trial=forwardModel(p_plus_h); for (int i=1; i<=npts; i++) J(i,n)=(S_trial(i)-S(i))/step; } else{ //for the other params, update only the signal of the relevant fibre compartment int fib_indx=(int)ceil((float)(n-2)/(float)nparams_per_fibre); //index indicating in which fibre compartment parameter n belongs to if (n==3+nparams_per_fibre*(fib_indx-1)){ //Then we have a volume fraction, derivative is analytic. for (int i=1; i<=npts; i++){ J(i,n)=0; for (int j=1; j<=nfib; j++){ if (f_deriv(j,fib_indx)!=0) J(i,n) += p(1)*(Sig(i,j+1)-Sig(i,1))*f_deriv(j,fib_indx); } } } else{ //for all other params, use numerical differentiation S_trial=pred_from_compartments(p_plus_h, Sig,fib_indx); for (int i=1; i<=npts; i++) J(i,n)=(S_trial(i)-S(i))/step; } } } for (int i=1; i<=p.Nrows(); i++){ for (int j=i; j<=p.Nrows(); j++){ sigt = 0.0; for(int k=1;k<=J.Nrows();k++) sigt += 2*(J(k,i)*J(k,j)); hessm->Set(i,j,sigt); } } for (int j=1; j<=p.Nrows(); j++) { for (int i=j+1; i<=p.Nrows(); i++) { hessm->Set(i,j,hessm->Peek(j,i)); } } return(hessm); } float PVM_Ball_Watsons::isoterm(const int& pt,const float& _d)const{ return(std::exp(-bvals(1,pt)*_d)); } NEWMAT::ReturnMatrix PVM_Ball_Watsons::fractions_deriv(const int& nfib, const ColumnVector& fs, const ColumnVector& bs) const{ NEWMAT::Matrix Deriv(nfib,nfib); float fsum; Deriv=0; for (int j=1; j<=nfib; j++) for (int k=1; k<=nfib; k++){ if (j==k){ fsum=1; for (int n=1; n<=j-1; n++) fsum-=fs(n); Deriv(j,k)=sin(2*bs(k))*fsum; } else if (j>k){ fsum=0; for (int n=1; n<=j-1; n++) fsum+=Deriv(n,k); Deriv(j,k)=-pow(sin(bs(j)),2.0)*fsum; } } Deriv.Release(); return Deriv; } /////////////////////////////////////////////////////////////////////////////////////////////// // USEFUL FUNCTIONS TO CALCULATE DERIVATIVES /////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////// ///////Model 1 (Constrained) ///////////////////////////////////// // functions float PVM_single_c::isoterm(const int& pt,const float& _d)const{ return(std::exp(-bvals(1,pt)*_d)); } float PVM_single_c::anisoterm(const int& pt,const float& _d,const ColumnVector& x)const{ float dp = bvecs(1,pt)*x(1)+bvecs(2,pt)*x(2)+bvecs(3,pt)*x(3); return(std::exp(-bvals(1,pt)*_d*dp*dp)); } // 1st order derivatives float PVM_single_c::isoterm_lambda(const int& pt,const float& lambda)const{ return(-2*bvals(1,pt)*lambda*std::exp(-bvals(1,pt)*lambda*lambda)); } float PVM_single_c::anisoterm_lambda(const int& pt,const float& lambda,const ColumnVector& x)const{ float dp = bvecs(1,pt)*x(1)+bvecs(2,pt)*x(2)+bvecs(3,pt)*x(3); return(-2*bvals(1,pt)*lambda*dp*dp*std::exp(-bvals(1,pt)*lambda*lambda*dp*dp)); } float PVM_single_c::anisoterm_th(const int& pt,const float& _d,const ColumnVector& x,const float& _th,const float& _ph)const{ float dp = bvecs(1,pt)*x(1)+bvecs(2,pt)*x(2)+bvecs(3,pt)*x(3); float dp1 = cos(_th)*(bvecs(1,pt)*cos(_ph) + bvecs(2,pt)*sin(_ph)) - bvecs(3,pt)*sin(_th); return(-2*bvals(1,pt)*_d*dp*dp1*std::exp(-bvals(1,pt)*_d*dp*dp)); } float PVM_single_c::anisoterm_ph(const int& pt,const float& _d,const ColumnVector& x,const float& _th,const float& _ph)const{ float dp = bvecs(1,pt)*x(1)+bvecs(2,pt)*x(2)+bvecs(3,pt)*x(3); float dp1 = sin(_th)*(-bvecs(1,pt)*sin(_ph) + bvecs(2,pt)*cos(_ph)); return(-2*bvals(1,pt)*_d*dp*dp1*std::exp(-bvals(1,pt)*_d*dp*dp)); } NEWMAT::ReturnMatrix PVM_single_c::fractions_deriv(const int& nfib, const ColumnVector& fs, const ColumnVector& bs) const{ NEWMAT::Matrix Deriv(nfib,nfib); float fsum; Deriv=0; for (int j=1; j<=nfib; j++) for (int k=1; k<=nfib; k++){ if (j==k){ fsum=1; for (int n=1; n<=j-1; n++) fsum-=fs(n); Deriv(j,k)=sin(2*bs(k))*fsum; } else if (j>k){ fsum=0; for (int n=1; n<=j-1; n++) fsum+=Deriv(n,k); Deriv(j,k)=-pow(sin(bs(j)),2.0)*fsum; } } Deriv.Release(); return Deriv; } ///////////////////////////////////// ////////Model 1 (Old) ///////////////////////////////////// //functions float PVM_single::isoterm(const int& pt,const float& _d)const{ return(std::exp(-bvals(1,pt)*_d)); } float PVM_single::anisoterm(const int& pt,const float& _d,const ColumnVector& x)const{ float dp = bvecs(1,pt)*x(1)+bvecs(2,pt)*x(2)+bvecs(3,pt)*x(3); return(std::exp(-bvals(1,pt)*_d*dp*dp)); } float PVM_single::bvecs_fibre_dp(const int& pt,const float& _th,const float& _ph)const{ float angtmp = cos(_ph-beta(pt))*sinalpha(pt)*sin(_th) + cosalpha(pt)*cos(_th); return(angtmp*angtmp); } float PVM_single::bvecs_fibre_dp(const int& pt,const ColumnVector& x)const{ float dp = bvecs(1,pt)*x(1)+bvecs(2,pt)*x(2)+bvecs(3,pt)*x(3); return(dp*dp); } // 1st order derivatives float PVM_single::isoterm_d(const int& pt,const float& _d)const{ return(-bvals(1,pt)*std::exp(-bvals(1,pt)*_d)); } float PVM_single::anisoterm_d(const int& pt,const float& _d,const ColumnVector& x)const{ float dp = bvecs(1,pt)*x(1)+bvecs(2,pt)*x(2)+bvecs(3,pt)*x(3); return(-bvals(1,pt)*dp*dp*std::exp(-bvals(1,pt)*_d*dp*dp)); } float PVM_single::anisoterm_th(const int& pt,const float& _d,const ColumnVector& x,const float& _th,const float& _ph)const{ float dp = bvecs(1,pt)*x(1)+bvecs(2,pt)*x(2)+bvecs(3,pt)*x(3); float dp1 = cos(_th)*(bvecs(1,pt)*cos(_ph) + bvecs(2,pt)*sin(_ph)) - bvecs(3,pt)*sin(_th); return(-2*bvals(1,pt)*_d*dp*dp1*std::exp(-bvals(1,pt)*_d*dp*dp)); } float PVM_single::anisoterm_ph(const int& pt,const float& _d,const ColumnVector& x,const float& _th,const float& _ph)const{ float dp = bvecs(1,pt)*x(1)+bvecs(2,pt)*x(2)+bvecs(3,pt)*x(3); float dp1 = sin(_th)*(-bvecs(1,pt)*sin(_ph) + bvecs(2,pt)*cos(_ph)); return(-2*bvals(1,pt)*_d*dp*dp1*std::exp(-bvals(1,pt)*_d*dp*dp)); } // 2nd order derivatives float PVM_single::isoterm_dd(const int& pt,const float& _d)const{ return(bvals(1,pt)*bvals(1,pt)*std::exp(-bvals(1,pt)*_d)); } float PVM_single::anisoterm_dd(const int& pt,const float& _d,const ColumnVector& x)const{ float dp = bvecs(1,pt)*x(1)+bvecs(2,pt)*x(2)+bvecs(3,pt)*x(3); dp *= dp; return(bvals(1,pt)*dp*bvals(1,pt)*dp*std::exp(-bvals(1,pt)*_d*dp)); } float PVM_single::anisoterm_dth(const int& pt,const float& _d,const ColumnVector& x,const float& _th,const float& _ph)const{ float dp = bvecs(1,pt)*x(1)+bvecs(2,pt)*x(2)+bvecs(3,pt)*x(3); float dp1 = cos(_th)*(bvecs(1,pt)*cos(_ph) + bvecs(2,pt)*sin(_ph)) - bvecs(3,pt)*sin(_th); return( -2*bvals(1,pt)*dp*dp1*(1-bvals(1,pt)*_d*dp*dp)*std::exp(-bvals(1,pt)*_d*dp*dp) ); } float PVM_single::anisoterm_dph(const int& pt,const float& _d,const ColumnVector& x,const float& _th,const float& _ph)const{ float dp = bvecs(1,pt)*x(1)+bvecs(2,pt)*x(2)+bvecs(3,pt)*x(3); float dp1 = sin(_th)*(-bvecs(1,pt)*sin(_ph) + bvecs(2,pt)*cos(_ph)); return( -2*bvals(1,pt)*dp*dp1*(1-bvals(1,pt)*_d*dp*dp)*std::exp(-bvals(1,pt)*_d*dp*dp) ); } float PVM_single::anisoterm_thth(const int& pt,const float& _d,const ColumnVector& x,const float& _th,const float& _ph)const{ float dp = bvecs(1,pt)*x(1)+bvecs(2,pt)*x(2)+bvecs(3,pt)*x(3); return( -2*bvals(1,pt)*_d*std::exp(-bvals(1,pt)*_d*dp*dp)* ( (1-2*bvals(1,pt)*dp*dp) -dp*dp ) ); } float PVM_single::anisoterm_phph(const int& pt,const float& _d,const ColumnVector& x,const float& _th,const float& _ph)const{ float dp = bvecs(1,pt)*x(1)+bvecs(2,pt)*x(2)+bvecs(3,pt)*x(3); float dp1 = (1-cos(2*_th))/2.0; float dp2 = -bvecs(1,pt)*x(1) - bvecs(2,pt)*x(2); return( -2*bvals(1,pt)*_d*std::exp(-bvals(1,pt)*_d*dp*dp)* ( (1-2*bvals(1,pt)*dp*dp)*dp1 +dp*dp2 ) ); } float PVM_single::anisoterm_thph(const int& pt,const float& _d,const ColumnVector& x,const float& _th,const float& _ph)const{ float dp = bvecs(1,pt)*x(1)+bvecs(2,pt)*x(2)+bvecs(3,pt)*x(3); float dp2 = cos(_th)*(-bvecs(1,pt)*sin(_ph) + bvecs(2,pt)*cos(_ph)); return( -2*bvals(1,pt)*_d*std::exp(-bvals(1,pt)*_d*dp*dp)* ( dp*dp2 ) ); } ////// NOW FOR MULTISHELL // functions float PVM_multi::isoterm(const int& pt,const float& _a,const float& _b)const{ return(std::exp(-_a*std::log(1+bvals(1,pt)*_b))); } float PVM_multi::anisoterm(const int& pt,const float& _a,const float& _b,const ColumnVector& x)const{ float dp = bvecs(1,pt)*x(1)+bvecs(2,pt)*x(2)+bvecs(3,pt)*x(3); return(std::exp(-_a*std::log(1+bvals(1,pt)*_b*(dp*dp)))); } // 1st order derivatives float PVM_multi::isoterm_a(const int& pt,const float& _a,const float& _b)const{ return(-std::log(1+bvals(1,pt)*_b)*std::exp(-_a*std::log(1+bvals(1,pt)*_b))); } float PVM_multi::isoterm_b(const int& pt,const float& _a,const float& _b)const{ return(-_a*bvals(1,pt)/(1+bvals(1,pt)*_b)*std::exp(-_a*std::log(1+bvals(1,pt)*_b))); } float PVM_multi::anisoterm_a(const int& pt,const float& _a,const float& _b,const ColumnVector& x)const{ float dp = bvecs(1,pt)*x(1)+bvecs(2,pt)*x(2)+bvecs(3,pt)*x(3); return(-std::log(1+bvals(1,pt)*(dp*dp)*_b)*std::exp(-_a*std::log(1+bvals(1,pt)*(dp*dp)*_b))); } float PVM_multi::anisoterm_b(const int& pt,const float& _a,const float& _b,const ColumnVector& x)const{ float dp = bvecs(1,pt)*x(1)+bvecs(2,pt)*x(2)+bvecs(3,pt)*x(3); return(-_a*bvals(1,pt)*(dp*dp)/(1+bvals(1,pt)*(dp*dp)*_b)*std::exp(-_a*std::log(1+bvals(1,pt)*(dp*dp)*_b))); } float PVM_multi::anisoterm_th(const int& pt,const float& _a,const float& _b,const ColumnVector& x,const float& _th,const float& _ph)const{ float dp = bvecs(1,pt)*x(1)+bvecs(2,pt)*x(2)+bvecs(3,pt)*x(3); float dp1 = cos(_th)*(bvecs(1,pt)*cos(_ph) + bvecs(2,pt)*sin(_ph)) - bvecs(3,pt)*sin(_th); return(-_a*_b*bvals(1,pt)/(1+bvals(1,pt)*(dp*dp)*_b)*std::exp(-_a*std::log(1+bvals(1,pt)*(dp*dp)*_b))*2*dp*dp1); } float PVM_multi::anisoterm_ph(const int& pt,const float& _a,const float& _b,const ColumnVector& x,const float& _th,const float& _ph)const{ float dp = bvecs(1,pt)*x(1)+bvecs(2,pt)*x(2)+bvecs(3,pt)*x(3); float dp1 = sin(_th)*(-bvecs(1,pt)*sin(_ph) + bvecs(2,pt)*cos(_ph)); return(-_a*_b*bvals(1,pt)/(1+bvals(1,pt)*(dp*dp)*_b)*std::exp(-_a*std::log(1+bvals(1,pt)*(dp*dp)*_b))*2*dp*dp1); }