From 3368e0e478d6b1dc404f4b0a70b956544e78c5b5 Mon Sep 17 00:00:00 2001
From: Saad Jbabdi <saad@fmrib.ox.ac.uk>
Date: Tue, 13 Oct 2009 11:04:48 +0000
Subject: [PATCH] diffusion models

---
 diffmodels.cc | 1154 +++++++++++++++++++++++++++++++++++++++++++++++++
 diffmodels.h  |  402 +++++++++++++++++
 2 files changed, 1556 insertions(+)
 create mode 100644 diffmodels.cc
 create mode 100644 diffmodels.h

diff --git a/diffmodels.cc b/diffmodels.cc
new file mode 100644
index 0000000..669ba89
--- /dev/null
+++ b/diffmodels.cc
@@ -0,0 +1,1154 @@
+/*  Diffusion model fitting
+
+    Timothy Behrens, Saad Jbabdi  - FMRIB Image Analysis Group
+ 
+    Copyright (C) 2005 University of Oxford  */
+
+/*  CCOPYRIGHT  */
+
+#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
+////////////////////////////////////////////////
+
+void PVM_single::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);
+  start(1) = dti.get_s0();
+  start(2) = dti.get_md()>0?dti.get_md()*2:0.001; // empirically found that d~2*MD
+  start(3) = f2x(dti.get_fa()); // first pvf = FA 
+  start(4) = _th;
+  start(5) = _ph;
+  float sumf=x2f(start(2));
+  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;
+  }
+
+
+  // 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  = 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);
+  }
+  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;
+  for(int i=1;i<=nfib;i++){
+    sumf+=m_f(i);
+    if(sumf>=1){for(int j=i;j<=nfib;j++)m_f(i)=0;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);
+  }
+  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 = 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());
+    }
+    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 = 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());
+    }
+    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 = 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(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));
+    }
+    sig = p(1)*((1-sumf)*isoterm(i,_d)+sig);
+    diff(i) = sig - Y(i);
+    // other stuff for derivatives
+    J(i,1) = sig/p(1);
+    J(i,2) += p(1)*(1-sumf)*isoterm_d(i,_d);
+  }
+  
+  gradv = 2*J.t()*diff;
+  //OUT(gradv.t());
+  //cout<<"----"<<endl;
+  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 = 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);
+  vector<Matrix> J2(npts);
+  ColumnVector diff(npts);
+  float sig;
+  for(int i=1;i<=Y.Nrows();i++){
+    sig = 0;
+    J.Row(i)=0;
+    J2[i-1].ReSize(nparams,nparams);
+    J2[i-1] = 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
+      // change of variable
+      float cov = two_pi*sign(p(kk))*1/(1+p(kk)*p(kk));
+      // d
+      J(i,2) += p(1)*fs(k)*anisoterm_d(i,_d,xx);
+      // f
+      J(i,kk) = p(1)*(anisoterm(i,_d,xx)-isoterm(i,_d)) * cov;
+      // 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));
+      // 2nd order derivatives
+      J2[i-1](1,kk)   = J(i,kk)/p(1) *cov;                  // s0,f
+      J2[i-1](1,kk+1) = J(i,kk+1)/p(1);                // s0,th
+      J2[i-1](1,kk+2) = J(i,kk+2)/p(1);                // s0,ph
+      J2[i-1](2,2)    += p(1)*fs(k)*anisoterm_dd(i,_d,xx); // d,d
+      J2[i-1](2,kk)   = p(1)*(anisoterm_d(i,_d,xx) - isoterm_d(i,_d)) *cov; // d,f
+      J2[i-1](2,kk+1) = p(1)*fs(k)*(anisoterm_dth(i,_d,xx,p(kk+1),p(k+2))); // d,th
+      J2[i-1](2,kk+2) = p(1)*fs(k)*(anisoterm_dph(i,_d,xx,p(kk+1),p(k+2))); // d,ph
+
+      J2[i-1](kk,kk)   = J(i,kk)*(-2*p(kk)*cov); // f,f 
+      J2[i-1](kk,kk+1) = J(i,kk+1)/fs(k) *cov;  // f,th
+      J2[i-1](kk,kk+2) = J(i,kk+2)/fs(k) *cov;  // f,ph
+      J2[i-1](kk+1,kk+1) = p(1)*fs(k)*anisoterm_thth(i,_d,xx,p(kk+1),p(k+2)); // th,th
+      J2[i-1](kk+1,kk+2) = p(1)*fs(k)*anisoterm_thph(i,_d,xx,p(kk+1),p(k+2)); // th,ph
+      J2[i-1](kk+2,kk+2) = p(1)*fs(k)*anisoterm_phph(i,_d,xx,p(kk+1),p(k+2)); // ph,ph
+
+    }
+    sig = p(1)*((1-sumf)*isoterm(i,_d)+sig);
+    diff(i) = sig - Y(i);
+    // other stuff for derivatives
+    J(i,1) = sig/p(1);
+    J(i,2) += p(1)*(1-sumf)*isoterm_d(i,_d);
+
+    // 2nd order derivatives
+    J2[i-1](1,1) = 0;                                      // s0,s0
+    J2[i-1](1,2) = J(i,2)/p(1);                            // s0,d
+    J2[i-1](2,2) += p(1)*(1-sumf)*isoterm_dd(i,_d);        // d,d
+    
+  }
+  
+
+  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));// + J2[k-1](i,j)*diff(k));
+      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 a tensor
+  DTI dti(Y,bvecs,bvals);
+  dti.linfit();
+
+  // set starting parameters for nonlinear fitting
+  float _th,_ph;
+  cart2sph(dti.get_v1(),_th,_ph);
+
+  float _a,_b;
+  _a = 1; // start with d=d_std
+  _b = dti.get_md()>0?dti.get_md()*2:0.001;
+
+  ColumnVector start(nparams);
+  start(1) = dti.get_s0();
+  start(2) = _a;
+  start(3) = _b;
+  start(4) = f2x(dti.get_fa());                   // first pvf = FA 
+  start(5) = _th;
+  start(6) = _ph;
+  float sumf=x2f(start(2));
+  float tmpsumf=sumf;
+  for(int ii=2,i=7;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;
+  }
+  
+  // 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      = final_par(2)*final_par(3);
+  m_d_std  = std::sqrt(final_par(2))*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);
+  }
+  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;
+  for(int i=1;i<=nfib;i++){
+    sumf+=m_f(i);
+    if(sumf>=1){for(int j=i;j<=nfib;j++)m_f(i)=0;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;
+  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);
+  }
+  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 = p(2);
+  float _b = 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());
+    }
+    pred(i) = 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 = p(2);
+  float _b = 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());
+    }
+    err = (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 = p(2);
+  float _b = 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(1)*fs(k)*anisoterm_a(i,_a,_b,xx);
+      // beta
+      J(i,2) += p(1)*fs(k)*anisoterm_b(i,_a,_b,xx) * (-p(3)*p(3)); // change of variable beta=1/beta
+      // f
+      J(i,kk) = 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) = p(1)*fs(k)*anisoterm_th(i,_a,_b,xx,p(kk+1),p(kk+2));
+      // ph
+      J(i,kk+2) = p(1)*fs(k)*anisoterm_ph(i,_a,_b,xx,p(kk+1),p(kk+2));
+    }
+    sig = p(1)*((1-sumf)*isoterm(i,_a,_b)+sig);
+    diff(i) = sig - Y(i);
+    // other stuff for derivatives
+    J(i,1) = sig/p(1);
+    J(i,2) += p(1)*(1-sumf)*isoterm_a(i,_a,_b);
+    J(i,3) += p(1)*(1-sumf)*isoterm_b(i,_a,_b);
+  }
+  
+  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 = p(2);
+  float _b = 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
+      // change of variable
+      float cov = two_pi*sign(p(kk))*1/(1+p(kk)*p(kk));
+      // alpha
+      J(i,2) += p(1)*fs(k)*anisoterm_a(i,_a,_b,xx);
+      // beta
+      J(i,3) += p(1)*fs(k)*anisoterm_b(i,_a,_b,xx);
+      // f
+      J(i,kk) = p(1)*(anisoterm(i,_a,_b,xx)-isoterm(i,_a,_b)) * cov;
+      // th
+      J(i,kk+1) = p(1)*fs(k)*anisoterm_th(i,_a,_b,xx,p(kk+1),p(kk+2));
+      // ph
+      J(i,kk+2) = p(1)*fs(k)*anisoterm_ph(i,_a,_b,xx,p(kk+1),p(kk+2));
+    }
+    sig = p(1)*((1-sumf)*isoterm(i,_a,_b)+sig);
+    diff(i) = sig - Y(i);
+    // other stuff for derivatives
+    J(i,1) = sig/p(1);
+    J(i,2) += p(1)*(1-sumf)*isoterm_a(i,_a,_b);
+    J(i,3) += p(1)*(1-sumf)*isoterm_b(i,_a,_b);
+
+  }
+  
+
+  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);
+}
+
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////
+//               USEFUL FUNCTIONS TO CALCULATE DERIVATIVES
+///////////////////////////////////////////////////////////////////////////////////////////////
+// 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);
+}
+
diff --git a/diffmodels.h b/diffmodels.h
new file mode 100644
index 0000000..25aac74
--- /dev/null
+++ b/diffmodels.h
@@ -0,0 +1,402 @@
+/*  Diffusion model fitting
+
+    Timothy Behrens, Saad Jbabdi  - FMRIB Image Analysis Group
+ 
+    Copyright (C) 2005 University of Oxford  */
+
+/*  CCOPYRIGHT  */
+
+#if !defined (diffmodels_h)
+#define diffmodels_h
+
+#include <iostream>
+#include <fstream>
+#include <iomanip>
+#define WANT_STREAM
+#define WANT_MATH
+#include <string>
+#include "utils/log.h"
+#include "utils/tracer_plus.h"
+#include "miscmaths/miscmaths.h"
+#include "miscmaths/nonlin.h"
+#include "stdlib.h"
+
+
+
+using namespace NEWMAT;
+using namespace MISCMATHS;
+
+
+#define two_pi 0.636619772
+#define f2x(x) (std::tan((x)/two_pi))
+#define x2f(x) (std::abs(two_pi*std::atan((x))))
+#define bigger(a,b) ((a)>(b)?(a):(b))
+#define smaller(a,b) ((a)>(b)?(b):(a))
+
+
+////////////////////////////////////////////////
+//       DIFFUSION TENSOR MODEL
+////////////////////////////////////////////////
+
+class DTI : public NonlinCF{
+public: 
+  DTI(const ColumnVector& iY,
+      const Matrix& ibvecs,const Matrix& ibvals):bvecs(ibvecs),bvals(ibvals){
+    Y = iY;
+    npts = Y.Nrows();
+    m_v1.ReSize(3);
+    m_v2.ReSize(3);
+    m_v3.ReSize(3);
+    form_Amat();
+    nparams=7;
+  }
+  ~DTI(){}
+  void linfit();
+  void nonlinfit();
+  void calc_tensor_parameters();
+  void sort();
+  void set_data(const ColumnVector& data){Y=data;}
+  float get_fa()const{return m_fa;}
+  float get_md()const{return m_md;}
+  float get_s0()const{return m_s0;}
+  float get_mo()const{return m_mo;}
+  ColumnVector get_v1()const{return m_v1;}
+  ColumnVector get_v2()const{return m_v2;}
+  ColumnVector get_v3()const{return m_v3;}
+  float get_l1()const{return m_l1;}
+  float get_l2()const{return m_l2;}
+  float get_l3()const{return m_l3;}
+  ColumnVector get_eigen()const{ColumnVector x(3);x<<m_l1<<m_l2<<m_l3;return x;}
+  ColumnVector get_tensor()const{
+    ColumnVector x(6);
+    x << m_tens(1,1)
+      << m_tens(2,1)
+      << m_tens(3,1)
+      << m_tens(2,2)
+      << m_tens(3,2)
+      << m_tens(3,3);
+    return x;
+  }
+  ColumnVector get_v(const int& i)const{if(i==1)return m_v1;else if(i==2)return m_v2;else return m_v3;}
+  ColumnVector get_prediction()const;
+  SymmetricMatrix get_covar()const{return m_covar;}
+  ColumnVector get_data()const{return Y;}
+  Matrix get_Amat()const{return Amat;}
+
+  // derivatives of tensor functions w.r.t. tensor parameters
+  ReturnMatrix calc_fa_grad(const ColumnVector& _tens)const;
+  float calc_fa_var()const;
+  ColumnVector calc_md_grad(const ColumnVector& _tens)const;
+  ColumnVector calc_mo_grad(const ColumnVector& _tens)const;
+
+  // conversion between rotation matrix and angles
+  void rot2angles(const Matrix& rot,float& th1,float& th2,float& th3)const;
+  void angles2rot(const float& th1,const float& th2,const float& th3,Matrix& rot)const;
+
+  void print()const{
+    cout << "DTI FIT RESULTS " << endl;
+    cout << "S0   :" << m_s0 << endl;
+    cout << "MD   :" << m_md << endl;
+    cout << "FA   :" << m_fa << endl;
+    cout << "MO   :" << m_mo << endl;
+    ColumnVector x(3);
+    x=m_v1;
+    if(x(3)<0)x=-x;
+    float _th,_ph;cart2sph(x,_th,_ph);
+    cout << "TH   :" << _th*180.0/M_PI << " deg" << endl; 
+    cout << "PH   :" << _ph*180.0/M_PI << " deg" << endl; 
+    cout << "V1   : " << x(1) << " " << x(2) << " " << x(3) << endl;
+  }
+  void form_Amat(){
+    Amat.ReSize(bvecs.Ncols(),7);
+    Matrix tmpvec(3,1), tmpmat;
+    for( int i = 1; i <= bvecs.Ncols(); i++){
+      tmpvec << bvecs(1,i) << bvecs(2,i) << bvecs(3,i);
+      tmpmat = tmpvec*tmpvec.t()*bvals(1,i);
+      Amat(i,1) = tmpmat(1,1);
+      Amat(i,2) = 2*tmpmat(1,2);
+      Amat(i,3) = 2*tmpmat(1,3);
+      Amat(i,4) = tmpmat(2,2);
+      Amat(i,5) = 2*tmpmat(2,3);
+      Amat(i,6) = tmpmat(3,3);
+      Amat(i,7) = 1;
+    }
+    iAmat = pinv(Amat);
+  }
+  void vec2tens(const ColumnVector& Vec){
+    m_tens.ReSize(3);
+    m_tens(1,1)=Vec(1);
+    m_tens(2,1)=Vec(2);
+    m_tens(3,1)=Vec(3);
+    m_tens(2,2)=Vec(4);
+    m_tens(3,2)=Vec(5);
+    m_tens(3,3)=Vec(6);
+  }
+  void vec2tens(const ColumnVector& Vec,SymmetricMatrix& Tens)const{
+    Tens.ReSize(3);
+    Tens(1,1)=Vec(1);
+    Tens(2,1)=Vec(2);
+    Tens(3,1)=Vec(3);
+    Tens(2,2)=Vec(4);
+    Tens(3,2)=Vec(5);
+    Tens(3,3)=Vec(6);
+  }
+  void tens2vec(const SymmetricMatrix& Tens,ColumnVector& Vec)const{
+    Vec.ReSize(6);
+    Vec<<Tens(1,1)<<Tens(2,1)<<Tens(3,1)<<Tens(2,2)<<Tens(3,2)<<Tens(3,3);
+  }
+
+  // nonlinear fitting routines
+  NEWMAT::ReturnMatrix grad(const NEWMAT::ColumnVector& p)const;
+  boost::shared_ptr<BFMatrix> hess(const NEWMAT::ColumnVector&p,boost::shared_ptr<BFMatrix> iptr)const;
+  double cf(const NEWMAT::ColumnVector& p)const;
+  NEWMAT::ReturnMatrix forwardModel(const NEWMAT::ColumnVector& p)const;
+  
+  ColumnVector rotproduct(const ColumnVector& x,const Matrix& R)const;
+  ColumnVector rotproduct(const ColumnVector& x,const Matrix& R1,const Matrix& R2)const;
+  float anisoterm(const int& pt,const ColumnVector& ls,const Matrix& xx)const;
+  
+private:
+  const Matrix& bvecs;
+  const Matrix& bvals;
+  ColumnVector Y;
+  Matrix Amat,iAmat;
+  int npts,nparams;
+  ColumnVector m_v1,m_v2,m_v3;
+  float m_l1,m_l2,m_l3;
+  float m_fa,m_s0,m_md,m_mo;
+  float m_sse;
+  SymmetricMatrix m_tens;
+  SymmetricMatrix m_covar;
+};
+
+////////////////////////////////////////////////
+//       Partial Volume Models
+////////////////////////////////////////////////
+
+// Generic class
+class PVM {
+public:
+  PVM(const ColumnVector& iY,
+      const Matrix& ibvecs, const Matrix& ibvals,
+      const int& nfibres):Y(iY),bvecs(ibvecs),bvals(ibvals){
+    
+    npts    = Y.Nrows();
+    nfib    = nfibres;
+    
+    cart2sph(ibvecs,alpha,beta);
+    
+    cosalpha.ReSize(npts);
+    sinalpha.ReSize(npts);
+    for(int i=1;i<=npts;i++){
+      sinalpha(i) = sin(alpha(i));
+      cosalpha(i) = cos(alpha(i));
+    }
+    
+  }
+  virtual ~PVM(){}
+  
+  // PVM virtual routines
+  virtual void fit()  = 0;
+  virtual void sort() = 0;
+  virtual void print()const = 0;
+  virtual void print(const ColumnVector& p)const = 0;
+  
+  virtual ReturnMatrix get_prediction()const = 0;
+
+protected:
+  const ColumnVector& Y;
+  const Matrix& bvecs;
+  const Matrix& bvals;
+  ColumnVector alpha;
+  ColumnVector sinalpha;
+  ColumnVector cosalpha;
+  ColumnVector beta;
+  
+  float npts;
+  int   nfib;
+
+};
+
+// Model 1 : mono-exponential (for single shell)
+class PVM_single : public PVM, public NonlinCF {
+public:
+  PVM_single(const ColumnVector& iY,
+	     const Matrix& ibvecs, const Matrix& ibvals,
+	     const int& nfibres):PVM(iY,ibvecs,ibvals,nfibres){
+
+    nparams = nfib*3 + 2;
+
+    m_f.ReSize(nfib);
+    m_th.ReSize(nfib);
+    m_ph.ReSize(nfib);
+  }
+  ~PVM_single(){}
+
+  // routines from NonlinCF
+  NEWMAT::ReturnMatrix grad(const NEWMAT::ColumnVector& p)const;
+  boost::shared_ptr<BFMatrix> hess(const NEWMAT::ColumnVector&p,boost::shared_ptr<BFMatrix> iptr)const;
+  double cf(const NEWMAT::ColumnVector& p)const;
+  NEWMAT::ReturnMatrix forwardModel(const NEWMAT::ColumnVector& p)const;
+
+  // other routines
+  void fit();
+  void sort();
+  void fix_fsum();
+  void 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*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;
+    }
+  }
+  void print(const ColumnVector& p)const{
+    cout << "PARAMETER VALUES " << endl;
+    cout << "S0   :" << p(1) << endl;
+    cout << "D    :" << p(2) << endl;
+    for(int i=3,ii=1;ii<=nfib;i+=3,ii++){
+      cout << "F" << ii << "   :" << x2f(p(i)) << 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; 
+    }
+  }
+
+  float get_s0()const{return m_s0;}
+  float get_d()const{return m_d;}
+  ColumnVector get_f()const{return m_f;}
+  ColumnVector get_th()const{return m_th;}
+  ColumnVector get_ph()const{return m_ph;}
+  float get_f(const int& i)const{return m_f(i);}
+  float get_th(const int& i)const{return m_th(i);}
+  float get_ph(const int& i)const{return m_ph(i);}
+  ReturnMatrix get_prediction()const;
+
+  // useful functions for calculating signal and its derivatives
+  // functions
+  float isoterm(const int& pt,const float& _d)const;
+  float anisoterm(const int& pt,const float& _d,const ColumnVector& x)const;
+  float bvecs_fibre_dp(const int& pt,const float& _th,const float& _ph)const;
+  float bvecs_fibre_dp(const int& pt,const ColumnVector& x)const;
+  // 1st order derivatives
+  float isoterm_d(const int& pt,const float& _d)const;
+  float anisoterm_d(const int& pt,const float& _d,const ColumnVector& x)const;
+  float anisoterm_th(const int& pt,const float& _d,const ColumnVector& x,const float& _th,const float& _ph)const;
+  float anisoterm_ph(const int& pt,const float& _d,const ColumnVector& x,const float& _th,const float& _ph)const;
+  // 2nd order derivatives
+  float isoterm_dd(const int& pt,const float& _d)const;
+  float anisoterm_dd(const int& pt,const float& _d,const ColumnVector& x)const;
+  float anisoterm_dth(const int& pt,const float& _d,const ColumnVector& x,const float& _th,const float& _ph)const;
+  float anisoterm_dph(const int& pt,const float& _d,const ColumnVector& x,const float& _th,const float& _ph)const;
+  float anisoterm_thth(const int& pt,const float& _d,const ColumnVector& x,const float& _th,const float& _ph)const;
+  float anisoterm_phph(const int& pt,const float& _d,const ColumnVector& x,const float& _th,const float& _ph)const;
+  float anisoterm_thph(const int& pt,const float& _d,const ColumnVector& x,const float& _th,const float& _ph)const;
+
+
+private:  
+  int   nparams;
+  float m_s0;
+  float m_d;
+  ColumnVector m_f;
+  ColumnVector m_th;
+  ColumnVector m_ph;
+};
+
+
+
+////////////////////////////////////////////////
+//       Partial Volume Models
+////////////////////////////////////////////////
+
+// Model 2 : non-mono-exponential (for multiple shells)
+class PVM_multi : public PVM, public NonlinCF {
+public:
+  PVM_multi(const ColumnVector& iY,
+	    const Matrix& ibvecs, const Matrix& ibvals,
+	    const int& nfibres):PVM(iY,ibvecs,ibvals,nfibres){
+
+    nparams = nfib*3 + 3;
+
+    m_f.ReSize(nfib);
+    m_th.ReSize(nfib);
+    m_ph.ReSize(nfib);
+  }
+  ~PVM_multi(){}
+
+  // routines from NonlinCF
+  NEWMAT::ReturnMatrix grad(const NEWMAT::ColumnVector& p)const;
+  boost::shared_ptr<BFMatrix> hess(const NEWMAT::ColumnVector&p,boost::shared_ptr<BFMatrix> iptr)const;
+  double cf(const NEWMAT::ColumnVector& p)const;
+  NEWMAT::ReturnMatrix forwardModel(const NEWMAT::ColumnVector& p)const;
+
+  // other routines
+  void fit();
+  void sort();
+  void fix_fsum();
+  void print()const{
+    cout << "PVM (MULTI) FIT RESULTS " << endl;
+    cout << "S0    :" << m_s0 << endl;
+    cout << "D     :" << m_d << endl;
+    cout << "D_STD :" << m_d_std << endl;
+    for(int i=1;i<=nfib;i++){
+      cout << "F" << i << "    :" << m_f(i) << endl;
+      cout << "TH" << i << "   :" << m_th(i) << endl; 
+      cout << "PH" << i << "   :" << m_ph(i) << endl; 
+    }
+  }
+  void print(const ColumnVector& p)const{
+    cout << "PARAMETER VALUES " << endl;
+    cout << "S0    :" << p(1) << endl;
+    cout << "D     :" << p(2) << endl;
+    cout << "D_STD :" << p(3) << endl;
+    for(int i=3,ii=1;ii<=nfib;i+=3,ii++){
+      cout << "F" << ii << "    :" << x2f(p(i)) << endl;
+      cout << "TH" << ii << "   :" << p(i+1) << endl; 
+      cout << "PH" << ii << "   :" << p(i+2) << endl; 
+    }
+  }
+
+  float get_s0()const{return m_s0;}
+  float get_d()const{return m_d;}
+  float get_d_std()const{return m_d_std;}
+  ColumnVector get_f()const{return m_f;}
+  ColumnVector get_th()const{return m_th;}
+  ColumnVector get_ph()const{return m_ph;}
+  float get_f(const int& i)const{return m_f(i);}
+  float get_th(const int& i)const{return m_th(i);}
+  float get_ph(const int& i)const{return m_ph(i);}
+
+  ReturnMatrix get_prediction()const;
+
+  // useful functions for calculating signal and its derivatives
+  // functions
+  float isoterm(const int& pt,const float& _a,const float& _b)const;
+  float anisoterm(const int& pt,const float& _a,const float& _b,const ColumnVector& x)const;
+  // 1st order derivatives
+  float isoterm_a(const int& pt,const float& _a,const float& _b)const;
+  float anisoterm_a(const int& pt,const float& _a,const float& _b,const ColumnVector& x)const;
+  float isoterm_b(const int& pt,const float& _a,const float& _b)const;
+  float anisoterm_b(const int& pt,const float& _a,const float& _b,const ColumnVector& x)const;
+  float anisoterm_th(const int& pt,const float& _a,const float& _b,const ColumnVector& x,const float& _th,const float& _ph)const;
+  float anisoterm_ph(const int& pt,const float& _a,const float& _b,const ColumnVector& x,const float& _th,const float& _ph)const;
+  
+private:
+  int   nparams;
+  float m_s0;
+  float m_d;
+  float m_d_std;
+  ColumnVector m_f;
+  ColumnVector m_th;
+  ColumnVector m_ph;
+};
+
+
+
+#endif
-- 
GitLab