From 894f4c6a136f981c97080d491cfc811c16f7262b Mon Sep 17 00:00:00 2001 From: Jesper Andersson <jesper@fmrib.ox.ac.uk> Date: Fri, 27 May 2011 15:21:42 +0000 Subject: [PATCH] Performing non-linear tensor fitting --- RLR_MAP_routines.cpp | 1198 ++++++++++++++++++++++++++++++++++++++++++ RLR_MAP_routines.h | 263 ++++++++++ nldtifit.cpp | 210 ++++++++ nldtifit.h | 111 ++++ 4 files changed, 1782 insertions(+) create mode 100644 RLR_MAP_routines.cpp create mode 100644 RLR_MAP_routines.h create mode 100644 nldtifit.cpp create mode 100644 nldtifit.h diff --git a/RLR_MAP_routines.cpp b/RLR_MAP_routines.cpp new file mode 100644 index 0000000..feb46d4 --- /dev/null +++ b/RLR_MAP_routines.cpp @@ -0,0 +1,1198 @@ +// Nonlinear estimation of diffusion tensor +// using a Gaussian or Rician noise model +// +// RLR_MAP_routines.cpp +// +// Jesper Andersson, FMRIB Image Analysis Group +// +// Copyright (C) 2011 University of Oxford +// +// These routines were originally written so as to +// be compiled into a mex-file callable from within +// Matlab. Hence it was written using Matlab/FORTRAN +// style matrices, was written in "pure" C (i.e. did +// not use any C++ features), used LAPACK for matrix +// inversion etc etc. Since the project was considered +// a "dead end" but the software still useful I +// decided on a minimal re-write. Hence all the +// original C quirkiness has been retained and a minimal +// interface has been added. +// + +#include <cstdlib> +#include <iostream> +#include <string> +#include <ctime> +#include <cmath> +#include "newmat.h" +#include "newmatio.h" +#include "RLR_MAP_routines.h" + +using namespace std; +using namespace NEWMAT; +namespace NLDTIFIT { + +/* + This file contains routines used for ML/MAP estimation + of DTI data. It works directly with a spectral decomposition + D=R*L*R' of the diffusion tensor and estimates three angles + (R being a function of these) and three eigenvalues (the + diagonal elements of L). + There is a quite strict procedural division between code + for the Rician noise model and that for the gaussian noise + model. +*/ +/* + Given a set of gradients, b-values, data-points and initial + guesses it will evaluate the MAP estimate of the parameters + pertaining to our RLR data model with a Rician noise-model + as described in the paper. +*/ +int fit_RLR(/* Input */ + double *g, /* Gradients, nx3 Matlab matrix. */ + double *b, /* b-values. */ + double *y, /* Data. */ + int n, /* # of data points. */ + int nvox, /* # of voxels. */ + double *pmu, /* Means of priors. */ + double *pvar, /* Variances of priors. */ + int *pi, /* Indicies (into theta) of priors. */ + int np, /* # of parameters with priors. */ + int nm, /* Noise model 1->Gauss, 2->Rician. */ + /* Input/Output */ + double *theta, /* Parameters. */ + double *ei, /* Expected intensities. */ + double *hist) /* History of log-likelihoods. */ +{ + double gg[3]; /* Used to repack gradients. A drag! */ + double L[3]; /* Diagonal 3x3 matrix of eigenvalues. */ + double R[9]; /* 3x3 rotation matrix. */ + double f[MLEN]; /* f_l as defined in paper. */ + double z[MLEN*MVOX];/* z_l as defined in paper. */ + double ef[MLEN]; /* exp(f_l). */ + double e2f[MLEN]; /* exp(2*f_l). */ + double ll=0.0; /* Current log-likelihood. */ + double bll=0.0; /* Best log-likelihood. */ + /* + These are a set of variables intended to hold various + instances of the gradient and the information matrix. + For the single voxel case we attempt to estimate 8 + parameters and and the sizes of corresponding variables + are 8 and 8*8 respectively. For the more general case + where we attempt to estimate parameters for multiple + voxels, pooling the variance estimate, we attempt to + estimate 1+7*nvox parameters. + */ + double grad[1+7*MVOX]; /* Gradient of cost-function. */ + double finf[(1+7*MVOX)*(1+7*MVOX)]; /* Iformation matrix. */ + double btheta[1+7*MVOX]; /* Best theta. */ + double bgrad[1+7*MVOX]; /* Best gradient. */ + double bfinf[(1+7*MVOX)*(1+7*MVOX)]; /* Best information matrix. */ + double ttheta[8]; /* Tmp storage for single voxel parameters. */ + double dw = 0.1; /* Extra weight to diagonal. */ + double es2 = 0.0; + int i=0, l=0; + int vox=0; + int iter=0; + int npar = 1+7*nvox; + int indx[8]; + + // Newmat Matrix and vectors for solving equation system + // Replaces the original LAPACK based solution. + Matrix nmH(npar,npar); + ColumnVector nmGrad(npar); + ColumnVector nmDTheta(npar); + + if (nm==GAUS && nvox!=1) + { + printf("\nMulti-voxel fitting not supported for Gaussian noise model (pointless)"); + return(-1); + } + if (nm==RICE) + { + for (vox=0; vox<nvox; vox++) + { + for (l=0; l<n; l++) + { + if (y[vox*n+l] < 0.0) + { + printf("\nNegative data, voxel #%d, measurement #%d",vox+1,l+1); + return(-1); + } + } + } + } + + memset(hist,0,MAXITER*sizeof(double)); + fix_phi2(theta,nvox); /* Make sure phi_2 within valid range. */ + + for (iter=0; iter<MAXITER; iter++) + { + /* + Get likelihood. In the process we calculate also + values that may be useful for later calculating + the gradient and the information matrix. + */ + ll = 0; + for (vox=0; vox<nvox; vox++) + { + indx[0] = 0; ttheta[0] = theta[0]; + for (i=1; i<8; i++) + { + indx[i] = vox*7 + i; + ttheta[i] = theta[indx[i]]; + } + es2 = exp(ttheta[S2_I]); + for (i=0; i<3; i++) {L[i] = exp(ttheta[L_I+i]);} /* L */ + make_R(&(ttheta[PHI_I]),R); /* R */ + for (l=0; l<n; l++) + { + for (i=0; i<3; i++) {gg[i] = g[gindx(l,i,n)];} + f[l] = make_f(gg,L,R,b[l]); /* Calculate f_l. */ + } + if (nm==RICE) + { + make_ei(ttheta,f,n,&(ei[vox*n])); /* Expected intensities. */ + make_z(&(ei[vox*n]),&(y[vox*n]),es2,n,&(z[vox*MLEN])); /* Calculate z_l. */ + ll += log_like_ric(ttheta,&(y[vox*n]),&(z[vox*MLEN]),&(ei[vox*n]),n);/* neg-log-likelihood. */ + } + else if (nm==GAUS) + { + make_ei_ef_e2f(theta,f,n,ei,ef,e2f); + ll += log_like_gauss(theta,y,ei,n); /* Gaussian neg-log-likelihood. */ + } + ll += priors_ll(ttheta,pmu,pvar,pi,np); /* Add effect of priors. */ + } + + /* + See if the last step was successful. + */ + if (!iter || (ll < bll)) /* If in right direction or first iteration. */ + { + bll = ll; + dw *= 0.1; + + memset(grad,0,npar*sizeof(double)); + memset(finf,0,SQR(npar)*sizeof(double)); + for (vox=0; vox<nvox; vox++) + { + indx[0] = 0; ttheta[0] = theta[0]; + for (i=1; i<8; i++) + { + indx[i] = vox*7 + i; + ttheta[i] = theta[indx[i]]; + } + for (i=0; i<3; i++) {L[i] = exp(ttheta[L_I+i]);} /* L */ + make_R(&(ttheta[PHI_I]),R); /* R */ + + /* New gradient and Hessian. */ + + if (nm==RICE) + { + make_ric_grad(ttheta,g,L,R,b,&(y[vox*n]),&(z[vox*MLEN]),&(ei[vox*n]),n,npar,indx,grad,finf); + } + else if (nm==GAUS) + { + make_gauss_grad(theta,g,L,R,b,y,ei,ef,e2f,n,grad,finf); + } + + /* Add contribution from priors. */ + + priors_grad(theta,pmu,pvar,pi,indx,np,npar,grad,finf); + } + + memcpy(btheta,theta,npar*sizeof(double)); + memcpy(bgrad,grad,npar*sizeof(double)); + memcpy(bfinf,finf,SQR(npar)*sizeof(double)); + } + else + { + ll = bll; + dw *= 10; + memcpy(theta,btheta,npar*sizeof(double)); + memcpy(grad,bgrad,npar*sizeof(double)); + memcpy(finf,bfinf,SQR(npar)*sizeof(double)); + if (dw > 1e20) {break;} + } + hist[iter] = ll; + + /* + Make information matrix more diagonal dominant. + */ + for (i=0; i<npar; i++) {finf[Hindx(i,i,npar)] *= (1.0 + dw);} + // + // Repack into NEWMAT matrix and vector + // + for (int ii=0; ii<npar; ii++) { nmGrad(ii+1) = grad[ii]; for (int jj=0; jj<npar; jj++) nmH(ii+1,jj+1) = finf[Hindx(ii,jj,npar)]; } + /* + Solve for new values of theta. + */ + try { + nmDTheta = nmH.i() * nmGrad; + for (int ii=0; ii<npar; ii++) theta[ii] -= nmDTheta(ii+1); + fix_phi2(theta,nvox); /* Fix phi_2 range error. */ + } + catch(...) { + // Deliberately empty. Doing nothing means theta is unchanged + // and the information matrix will be made more diagonal dominant. + } + } + + /* + See if we want to use latest theta or not. + */ + ll = 0; + for (vox=0; vox<nvox; vox++) + { + indx[0] = 0; ttheta[0] = theta[0]; + for (i=1; i<8; i++) + { + indx[i] = vox*7 + i; + ttheta[i] = theta[indx[i]]; + } + es2 = exp(ttheta[S2_I]); + for (i=0; i<3; i++) {L[i] = exp(ttheta[L_I+i]);} /* L */ + make_R(&(ttheta[PHI_I]),R); /* R */ + for (l=0; l<n; l++) + { + for (i=0; i<3; i++) {gg[i] = g[gindx(l,i,n)];} + f[l] = make_f(gg,L,R,b[l]); /* Calculate f_l. */ + } + + if (nm==RICE) + { + make_ei(ttheta,f,n,&(ei[vox*n])); /* Calculate expected intensities. */ + make_z(&(ei[vox*n]),&(y[vox*n]),es2,n,&(z[vox*MLEN])); /* Calculate z_l. */ + ll += log_like_ric(ttheta,&(y[vox*n]),&(z[vox*MLEN]),&(ei[vox*n]),n); /* Rician neg-log-likelihood. */ + } + else if (nm==GAUS) + { + make_ei_ef_e2f(theta,f,n,ei,ef,e2f); + ll += log_like_gauss(theta,y,ei,n); /* Gaussian neg-log-likelihood. */ + } + ll += priors_ll(theta,pmu,pvar,pi,np); /* Add effect of priors. */ + } + + if (bll < ll) + { + memcpy(theta,btheta,npar*sizeof(double)); + /* + Use best theta to calculate expected intensities. + Mainly for debugging purposes. + */ + for (vox=0; vox<nvox; vox++) + { + indx[0] = 0; ttheta[0] = theta[0]; + for (i=1; i<8; i++) + { + indx[i] = vox*7 + i; + ttheta[i] = theta[indx[i]]; + } + es2 = exp(ttheta[S2_I]); + for (i=0; i<3; i++) {L[i] = exp(ttheta[L_I+i]);} /* L */ + make_R(&(ttheta[PHI_I]),R); /* R */ + for (l=0; l<n; l++) + { + for (i=0; i<3; i++) {gg[i] = g[gindx(l,i,n)];} + f[l] = make_f(gg,L,R,b[l]); /* Calculate f_l. */ + } + make_ei(ttheta,f,n,&(ei[vox*n])); /* Calculate expected intensities. */ + } + } + + return(1); +} + +/* + Given a set of gradients, b-values, data-points and parameters + it will return the log-likelihood its gradient, information + matrix and expected data values. +*/ +int get_ric_ll_info(/* Input */ + double *theta, + double *y, + double *g, + double *b, + int n, + /* Input/output */ + double *ll, + double *grad, + double *info, + double *ey) +{ + int i= 0; + int l = 0; + int indx[8]; + double es2 = 0.0; + double gg[3]; /* Used to repack gradients. A drag! */ + double L[3]; /* Diagonal 3x3 matrix of eigenvalues. */ + double R[9]; /* 3x3 rotation matrix. */ + double f[MLEN]; /* f_l as defined in paper. */ + double z[MLEN*MVOX];/* z_l as defined in paper. */ + + memset(grad,0,8*sizeof(double)); + memset(info,0,8*8*sizeof(double)); + memset(ey,0,n*sizeof(double)); + es2 = exp(theta[S2_I]); + for (i=0; i<8; i++) + { + indx[i] = i; + } + + for (i=0; i<3; i++) {L[i] = exp(theta[L_I+i]);} /* L */ + make_R(&(theta[PHI_I]),R); /* R */ + for (l=0; l<n; l++) + { + for (i=0; i<3; i++) {gg[i] = g[gindx(l,i,n)];} + f[l] = make_f(gg,L,R,b[l]); /* Calculate f_l. */ + } + make_ei(theta,f,n,ey); /* Expected intensities. */ + make_z(ey,y,es2,n,z); /* Calculate z_l. */ + *ll = log_like_ric(theta,y,z,ey,n); /* Rician neg-log-likelihood. */ + make_ric_grad(theta,g,L,R,b,y,z,ey,n,8,indx,grad,info); + + + return(1); +} + +int get_gaus_ll_info(/* Input */ + double *theta, + double *y, + double *g, + double *b, + int n, + /* Input/output */ + double *ll, + double *grad, + double *info, + double *ey) +{ + int i= 0; + int l = 0; + double es2 = 0.0; + double gg[3]; /* Used to repack gradients. A drag! */ + double L[3]; /* Diagonal 3x3 matrix of eigenvalues. */ + double R[9]; /* 3x3 rotation matrix. */ + double f[MLEN]; /* f_l as defined in paper. */ + double ef[MLEN]; /* exp(f_l). */ + double e2f[MLEN]; /* exp(2*f_l). */ + + memset(grad,0,8*sizeof(double)); + memset(info,0,8*8*sizeof(double)); + memset(ey,0,n*sizeof(double)); + + es2 = exp(theta[S2_I]); + for (i=0; i<3; i++) {L[i] = exp(theta[L_I+i]);} /* L */ + make_R(&(theta[PHI_I]),R); /* R */ + for (l=0; l<n; l++) + { + for (i=0; i<3; i++) {gg[i] = g[gindx(l,i,n)];} + f[l] = make_f(gg,L,R,b[l]); /* Calculate f_l. */ + } + make_ei_ef_e2f(theta,f,n,ey,ef,e2f); + *ll = log_like_gauss(theta,y,ey,n); /* Gaussian neg-log-likelihood. */ + make_gauss_grad(theta,g,L,R,b,y,ey,ef,e2f,n,grad,info); + + return(1); +} + +/* + Calculate the gradient and information matrix for a Rician noise-model. + There is a quite ugly redundancy in the input parameters. In principle + we could have calculated L, R, zl and ei given the other parameters. + However, there are good efficiency reasons for separating the calculation + of the likelihood and its derivatives. But that means that in the name of + efficiency we also need to calculate outside these routines and pass to + them any calculations that are common to them. +*/ +void make_ric_grad(/* Input */ + double *theta, /* Parameters. */ + double *g, /* Gradient vectors. */ + double *L, /* L matrix, diagonal represented as 3x1. */ + double *R, /* Rotation matrix. */ + double *b, /* b-values. */ + double *y, /* data. */ + double *z, /* z_l as defined in paper. */ + double *ei, /* Expected intensities. */ + int n, /* # of data points. */ + int npar, /* Total # of parameters. */ + int *indx, /* Indicies into grad and finf. */ + /* Output */ + double *grad, /* Gradient, 8x1. */ + double *finf) /* Information matrix, 8x8. */ +{ + double dfdphi[3]; + double dfdl[3]; + double *dRdphi[3]; + double dRdx[9]; + double dRdy[9]; + double dRdz[9]; + double gg[3]; + double es2 = 0.0; + double ei2_es2 = 0.0; + double ei2_es2_2 = 0.0; + double zl_I1I0 = 0.0; + double te = 0.0; + int l=0,i=0,j=0; + + grad[indx[S2_I]] += ((double) n); + finf[Hindx(indx[S2_I],indx[S2_I],npar)] += ((double) n); + + es2 = exp(theta[S2_I]); + + make_dR(&(theta[PHI_I]),dRdx,dRdy,dRdz); + dRdphi[0]=dRdx; dRdphi[1]=dRdy; dRdphi[2]=dRdz; + + for (l=0; l<n; l++) + { + /* + Repack g, assume g is nx3 Matlab matrix. + */ + for (i=0; i<3; i++) {gg[i] = g[gindx(l,i,n)];} + /* + Calculate df_l/dphi_i and df_l/dl_i + */ + for (i=0; i<3; i++) + { + dfdphi[i] = make_dfdphi(gg,L,R,dRdphi[i],b[l]); + dfdl[i] = make_dfdl(gg,L[i],R,b[l],i); + } + ei2_es2 = (SQR(ei[l])) / es2; /* Square of expected intensity / sigma^2. */ + ei2_es2_2 = SQR(ei2_es2); /* And squared. */ + zl_I1I0 = z[l] * make_I1I0(z[l]); + te = make_tricky(ei[l],es2); /* Tricky expectation. */ + /* + Start building the gradient. + */ + grad[indx[S2_I]] += (zl_I1I0 - 0.5*((SQR(y[l])/es2) + ei2_es2)); + grad[indx[S0_I]] += (ei2_es2 - zl_I1I0); + for (i=0; i<3; i++) + { + grad[indx[PHI_I+i]] += (dfdphi[i] * (zl_I1I0 - ei2_es2)); + grad[indx[L_I+i]] += (dfdl[i] * (zl_I1I0 - ei2_es2)); + } + /* + And the lower left part of information matrix. + This (sadly) imposes some assumptions on the order + of the parameters in theta. I assume + theta = [s2 s0 phi lambda]. + */ + /* Partials containing s2. */ + finf[Hindx(indx[S2_I],indx[S2_I],npar)] += (te - ei2_es2_2 - ei2_es2); + finf[Hindx(indx[S0_I],indx[S2_I],npar)] += (ei2_es2_2 + ei2_es2 - te); + for (i=0; i<3; i++) + { + finf[Hindx(indx[PHI_I+i],indx[S2_I],npar)] += dfdphi[i] * (te - ei2_es2_2 - ei2_es2); + finf[Hindx(indx[L_I+i],indx[S2_I],npar)] += dfdl[i] * (te - ei2_es2_2 - ei2_es2); + } + /* Partials containing s0. */ + finf[Hindx(indx[S0_I],indx[S0_I],npar)] += (te - ei2_es2_2); + for (i=0; i<3; i++) + { + finf[Hindx(indx[PHI_I+i],indx[S0_I],npar)] += dfdphi[i] * (ei2_es2_2 - te); + finf[Hindx(indx[L_I+i],indx[S0_I],npar)] += dfdl[i] * (ei2_es2_2 - te); + } + /* Partials containing phi. */ + for (i=0; i<3; i++) + { + for (j=i; j<3; j++) + { + finf[Hindx(indx[PHI_I+j],indx[PHI_I+i],npar)] += dfdphi[i]*dfdphi[j]*(te - ei2_es2_2); + } + for (j=0; j<3; j++) + { + finf[Hindx(indx[L_I+j],indx[PHI_I+i],npar)] += dfdphi[i]*dfdl[j]*(te - ei2_es2_2); + } + } + /* Partials containing lambda. */ + for (i=0; i<3; i++) + { + for (j=i; j<3; j++) + { + finf[Hindx(indx[L_I+j],indx[L_I+i],npar)] += dfdl[i]*dfdl[j]*(te - ei2_es2_2); + } + } + } + /* + Mirror information matrix. + */ + for (i=0; i<npar; i++) + { + for (j=i+1; j<npar; j++) + { + finf[Hindx(i,j,npar)] = finf[Hindx(j,i,npar)]; + } + } + + /* Hurrah! */ + + return; +} + + +/* + Calculate the gradient and information matrix for a Gaussian noise-model. + The same comments as made for the rician case are pretty much valid here too. +*/ +void make_gauss_grad(/* Input */ + double *theta, /* Parameters. */ + double *g, /* Gradient vectors. */ + double *L, /* L matrix, diagonal represented as 3x1. */ + double *R, /* Rotation matrix. */ + double *b, /* b-values. */ + double *y, /* data. */ + double *ei, /* Expected intensities. */ + double *ef, /* exp(-fl). */ + double *e2f, /* exp(-2*fl). */ + int n, /* # of data points. */ + /* Output */ + double *grad, /* Gradient, 8x1. */ + double *finf) /* Information matrix, 8x8. */ +{ + double dfdphi[3]; + double dfdl[3]; + double *dRdphi[3]; + double dRdx[9]; + double dRdy[9]; + double dRdz[9]; + double gg[3]; + double es2 = 0.0; + double es0_es2 = 0.0; + double e2s0_es2 = 0.0; + double diff = 0.0; + double ef_diff = 0.0; + double e2f_l = 0.0; + int l=0,i=0,j=0; + + memset(grad,0,8*sizeof(double)); + memset(finf,0,8*8*sizeof(double)); + + finf[Hindx(S2_I,S2_I,8)] = (((double) n) / 2.0); + + es2 = exp(theta[S2_I]); + es0_es2 = exp(theta[S0_I]) / es2; + e2s0_es2 = exp(2.0*theta[S0_I]) / es2; + + make_dR(&(theta[PHI_I]),dRdx,dRdy,dRdz); + dRdphi[0]=dRdx; dRdphi[1]=dRdy; dRdphi[2]=dRdz; + + for (l=0; l<n; l++) + { + /* + Repack g, assume g is nx3 Matlab matrix. + */ + for (i=0; i<3; i++) {gg[i] = g[gindx(l,i,n)];} + /* + Calculate df_l/dphi_i and df_l/dl_i + */ + for (i=0; i<3; i++) + { + dfdphi[i] = make_dfdphi(gg,L,R,dRdphi[i],b[l]); + dfdl[i] = make_dfdl(gg,L[i],R,b[l],i); + } + /* + Start building the gradient. + */ + diff = y[l]-ei[l]; + ef_diff = ef[l]*diff; + e2f_l = e2f[l]; + grad[S2_I] -= SQR(diff); + grad[S0_I] -= ef_diff; + for (i=0; i<3; i++) + { + grad[PHI_I+i] += dfdphi[i] * ef_diff; + grad[L_I+i] += dfdl[i] * ef_diff; + } + /* + And the lower left part of information matrix. + This (sadly) imposes some assumptions on the order + of the parameters in theta. I assume + theta = [s2 s0 phi lambda]. + */ + /* The only non-zero partial containing s2 is alreday done. */ + /* Partials containing s0. */ + finf[Hindx(S0_I,S0_I,8)] += e2f_l; + for (i=0; i<3; i++) + { + finf[Hindx(PHI_I+i,S0_I,8)] -= dfdphi[i] * e2f_l; + finf[Hindx(L_I+i,S0_I,8)] -= dfdl[i] * e2f_l; + } + /* Partials containing phi. */ + for (i=0; i<3; i++) + { + for (j=i; j<3; j++) + { + finf[Hindx(PHI_I+j,PHI_I+i,8)] += dfdphi[i]*dfdphi[j]*e2f_l; + } + for (j=0; j<3; j++) + { + finf[Hindx(L_I+j,PHI_I+i,8)] += dfdphi[i]*dfdl[j]*e2f_l; + } + } + /* Partials containing lambda. */ + for (i=0; i<3; i++) + { + for (j=i; j<3; j++) + { + finf[Hindx(L_I+j,L_I+i,8)] += dfdl[i]*dfdl[j]*e2f_l; + } + } + } + + /* Fix some scalings and stuff. */ + + grad[S2_I] /= (2.0 * es2); + grad[S2_I] += (((double) n) / 2.0); + for (i=S0_I; i<8; i++) + { + grad[i] *= es0_es2; + for (j=i; j<8; j++) + { + finf[Hindx(j,i,8)] *= e2s0_es2; + } + } + + /* + Mirror information matrix. + */ + for (i=0; i<8; i++) + { + for (j=i+1; j<8; j++) + { + finf[Hindx(i,j,8)] = finf[Hindx(j,i,8)]; + } + } + + return; +} + + +/* + Calculate the efects of priors on the log-likelihood. +*/ +double priors_ll(/* Input. */ + double *theta, /* Parameters. */ + double *mu, /* Means of priors. */ + double *var, /* Variance of priors. */ + int *indx, /* Indicies (into theta) of priors. */ + int n) /* # of parameters that have priors. */ +{ + double ll=0.0; + int i=0; + + /* Always add prior pertaining to phi_2. */ + + ll -= log(cos(theta[PHI_I+1])); + + /* Then add priors pertaining to "optional priors". */ + + for (i=0; i<n; i++) + { + ll += ((1.0/(2.0*var[i])) * SQR(mu[i] - theta[indx[i]])); + } + + return(ll); +} + +/* + Add the effects of priors onto gradient + and information matrix. +*/ +void priors_grad(/* Input. */ + double *theta, /* Parameters. */ + double *mu, /* Means of priors. */ + double *var, /* Variance of priors. */ + int *tindx, /* Indicies (into theta) of priors. */ + int *indx, /* Indicies (into grad) of priors. */ + int n, /* # of parameters that have priors. */ + int npar, /* Total # of parameters. */ + /* Output. */ + double *grad, /* Gradient vector npar*1. */ + double *finf) /* Information matrix npar*npar. */ +{ + double s2 = sin(theta[indx[PHI_I+1]]); + double c2 = cos(theta[indx[PHI_I+1]]); + int i = 0; + + /* Always add prior pertaining to phi_2. */ + + grad[indx[PHI_I+1]] += (s2/c2); + finf[Hindx(indx[PHI_I+1],indx[PHI_I+1],npar)] += (1.0 + SQR(s2/c2)); + + /* Then add "optional" priors. */ + + for (i=0; i<n; i++) + { + grad[indx[tindx[i]]] -= ((1.0/var[i]) * (mu[i] - theta[indx[tindx[i]]])); + finf[Hindx(indx[tindx[i]],indx[tindx[i]],npar)] += (1.0/var[i]); + } + + return; +} + +/* + Create rotation matrix R from three angles phi. +*/ +double *make_R(/* Input */ + double *phi, /* Angles in radians. */ + /* Output */ + double *R) /* Rotation matrix. */ +{ + double s1 = sin(phi[0]); + double c1 = cos(phi[0]); + double s2 = sin(phi[1]); + double c2 = cos(phi[1]); + double s3 = sin(phi[2]); + double c3 = cos(phi[2]); + + R[Rindx(0,0)] = c3*c2; + R[Rindx(1,0)] = s3*c2; + R[Rindx(2,0)] = s2; + R[Rindx(0,1)] = -s3*c1-c3*s2*s1; + R[Rindx(1,1)] = c3*c1-s3*s2*s1; + R[Rindx(2,1)] = c2*s1; + R[Rindx(0,2)] = s3*s1-c3*s2*c1; + R[Rindx(1,2)] = -c3*s1-s3*s2*c1; + R[Rindx(2,2)] = c2*c1; + + return(R); +} + +/* + Create partial derivatives of rotation + matrix R from three angles phi. +*/ +void make_dR(/* Input */ + double *phi, /* Angles in radians. */ + /* Output */ + double *dRdx, + double *dRdy, + double *dRdz) +{ + double s1 = sin(phi[0]); + double c1 = cos(phi[0]); + double s2 = sin(phi[1]); + double c2 = cos(phi[1]); + double s3 = sin(phi[2]); + double c3 = cos(phi[2]); + + dRdx[Rindx(0,0)] = 0.0; + dRdx[Rindx(1,0)] = 0.0; + dRdx[Rindx(2,0)] = 0.0; + dRdx[Rindx(0,1)] = s3*s1-c3*s2*c1; + dRdx[Rindx(1,1)] = -c3*s1-s3*s2*c1; + dRdx[Rindx(2,1)] = c2*c1; + dRdx[Rindx(0,2)] = s3*c1+c3*s2*s1; + dRdx[Rindx(1,2)] = -c3*c1+s3*s2*s1; + dRdx[Rindx(2,2)] = -c2*s1; + + dRdy[Rindx(0,0)] = -c3*s2; + dRdy[Rindx(1,0)] = -s3*s2; + dRdy[Rindx(2,0)] = c2; + dRdy[Rindx(0,1)] = -c3*c2*s1; + dRdy[Rindx(1,1)] = -s3*c2*s1; + dRdy[Rindx(2,1)] = -s2*s1; + dRdy[Rindx(0,2)] = -c3*c2*c1; + dRdy[Rindx(1,2)] = -s3*c2*c1; + dRdy[Rindx(2,2)] = -s2*c1; + + dRdz[Rindx(0,0)] = -s3*c2; + dRdz[Rindx(1,0)] = c3*c2; + dRdz[Rindx(2,0)] = 0.0; + dRdz[Rindx(0,1)] = -c3*c1+s3*s2*s1; + dRdz[Rindx(1,1)] = -s3*c1-c3*s2*s1; + dRdz[Rindx(2,1)] = 0.0; + dRdz[Rindx(0,2)] = c3*s1+s3*s2*c1; + dRdz[Rindx(1,2)] = s3*s1-c3*s2*c1; + dRdz[Rindx(2,2)] = 0.0; + + return; +} + +/* + Efficient calculation of the vector g'*R. Note + that this is the same vector as R'*g (transposed) +*/ +double *make_gR(/* Input */ + double *g, /* 3x1 vector. */ + double *R, /* 3x3 matrix. */ + /* Output. */ + double *gR) /* g'*R or R'*g */ +{ + gR[0] = g[0]*R[Rindx(0,0)] + g[1]*R[Rindx(1,0)] + g[2]*R[Rindx(2,0)]; + gR[1] = g[0]*R[Rindx(0,1)] + g[1]*R[Rindx(1,1)] + g[2]*R[Rindx(2,1)]; + gR[2] = g[0]*R[Rindx(0,2)] + g[1]*R[Rindx(1,2)] + g[2]*R[Rindx(2,2)]; + + return(gR); +} + +/* + Efficient calculation of the scalar g'*R*L*R'*g + where g is a 3x1 vector, R a full 3x3 matrix and + L a 3x3 diagonal matrix (represented by a 3x1 vector). +*/ +double make_gRLRg(/* Input */ + double *g, + double *L, + double *R) +{ + double gR[3]; + + make_gR(g,R,gR); + + return(SQR(gR[0])*L[0] + SQR(gR[1])*L[1] + SQR(gR[2])*L[2]); +} + +/* + Efficient calculation of the scalar g'*(R1*L*R2'+R2*L*R1')*g + where g is a 3x1 vector, R1 and R2 are full 3x3 matrices and + L a 3x3 diagonal matrix (represented by a 3x1 vector). +*/ +double make_gR1LR2g(/* Input */ + double *g, + double *L, + double *R1, + double *R2) +{ + double gR1[3]; + double gR2[3]; + + make_gR(g,R1,gR1); + make_gR(g,R2,gR2); + + return(2.0*(gR1[0]*gR2[0]*L[0] + gR1[1]*gR2[1]*L[1] + gR1[2]*gR2[2]*L[2])); +} + +/* + Efficient (could actually be more efficient) calculation of + the scalar g'*R*dL*R'*g where g is a 3x1 vector, R is a full + 3x3 matrix and L a 3x3 diagonal matrix with a single non-zero + element. L is represented by that elemnt (l) and the index (li) + of the non-zero element. +*/ +double make_gRdLRg(/* Input */ + double *g, + double *R, + double l, + int li) +{ + double gR[3]; + + make_gR(g,R,gR); + + return(l * SQR(gR[li])); +} + +/* + Calculates f_l as defined in paper. +*/ +double make_f(/* Input */ + double *g, + double *L, + double *R, + double b) +{ + return(b * make_gRLRg(g,L,R)); +} + +/* + Calculates z_l as defined in paper. +*/ +double *make_z(/* Input */ + double *ei, /* Expected intensities */ + double *y, /* Data (observed intensities). */ + double es2, /* The estimate of the variance.*/ + int n, /* Length of the data. */ + /* Output. */ + double *z) +{ + int i=0; + + for (i=0; i<n; i++) + { + z[i] = (y[i]*ei[i])/es2; + } + + return(z); +} + +/* + Calculates \frac{\partial f_l}{\partial \phi} + as defined in paper. +*/ +double make_dfdphi(/* Input */ + double *g, + double *L, + double *R, + double *dRdphi, + double b) +{ + return(b * make_gR1LR2g(g,L,dRdphi,R)); +} + +/* + Calculates \frac{\partial f_l}{\partial \lambda} + as defined in paper. +*/ +double make_dfdl(/* Input */ + double *g, + double dRdl, + double *R, + double b, + int dRdl_i) +{ + return(b * make_gRdLRg(g,R,dRdl,dRdl_i)); +} + +/* + Calculates the neg-log-likelihood of the data + assuming a Rician distribution. +*/ +double log_like_ric(/* Input */ + double *theta, /* Parameters. */ + double *y, /* Data. */ + double *z, /* z_l as defined in paper. */ + double *ei, /* Expected intensities. */ + int n) /* Number of data points. */ +{ + double ll = 0.0; + int i = 0; + + for (i=0; i<n; i++) {ll += SQR(y[i]) + SQR(ei[i]);} + ll /= (2.0 * exp(theta[S2_I])); + ll += ((double) n) * theta[S2_I]; + for (i=0; i<n; i++) + { + ll -= (log(y[i]) + logI0(z[i])); + } + + return(ll); +} + +/* + Calculates the neg-log-likelihood of the data + assuming a gaussian distribution. +*/ +double log_like_gauss(/* Input */ + double *theta, /* Parameters. */ + double *y, /* Data. */ + double *ei, /* Expected intensities. */ + int n) /* Number of data points. */ +{ + double ll = 0.0; + int i = 0; + + for (i=0; i<n; i++) {ll += SQR(y[i] - ei[i]);} + ll /= (2.0 * exp(theta[S2_I])); + ll += (((double) n)/2.0) * (log(2.0*M_PI)+theta[S2_I]); + + return(ll); +} + +/* + Expected intensities. +*/ +double *make_ei(/* Input */ + double *theta, + double *fl, + int n, + /* Output */ + double *ei) +{ + double tg[3]; + double es0 = 0.0; + int i = 0; + + es0 = exp(theta[S0_I]); + + for (i=0; i<n; i++) + { + ei[i] = es0 * exp(-fl[i]); + } + return(ei); +} + +/* + Expected intensities, exp(-fl) and exp(-2*fl). + These are needed to calculate the gradient and + information matrix for the Gaussian case. +*/ +void make_ei_ef_e2f(/* Input */ + double *theta, + double *fl, + int n, + /* Output */ + double *ei, + double *ef, + double *e2f) +{ + double tg[3]; + double es0 = 0.0; + int i = 0; + + es0 = exp(theta[S0_I]); + + for (i=0; i<n; i++) + { + ef[i] = exp(-fl[i]); + e2f[i] = SQR(ef[i]); + ei[i] = es0 * ef[i]; + } + return; +} + +/* + Returns the log of the incomplete zeroth + order Bessel function I_0(x). Constants + from Abramowitz & Stegun. +*/ +double logI0(double x) +{ + double t = 0.0; + double t2 = 0.0; + double tpow = 0.0; + double li0 = 0.0; + + if (fabs(x) < 3.75) + { + t2 = SQR(x / 3.75); + li0 = 1; + li0 += 3.5156229 * (tpow = t2); + li0 += 3.0899424 * (tpow *= t2); + li0 += 1.2067492 * (tpow *= t2); + li0 += 0.2659732 * (tpow *= t2); + li0 += 0.0360768 * (tpow *= t2); + li0 += 0.0045813 * (tpow *= t2); + li0 = log(li0); + } + else + { + t = 3.75 / x; /* Inverse of t. */ + li0 = 0.39894228; + li0 += 0.01328592 * (tpow = t); + li0 += 0.00225319 * (tpow *= t); + li0 -= 0.00157565 * (tpow *= t); + li0 += 0.00916281 * (tpow *= t); + li0 -= 0.02057706 * (tpow *= t); + li0 += 0.02635537 * (tpow *= t); + li0 -= 0.01647633 * (tpow *= t); + li0 += 0.00392377 * (tpow *= t); + + li0 = log(li0) - 0.5*log(x) + x; + } + + return(li0); +} + +/* + Returns the log of the incomplete first + order Bessel function I_1(x). Constants + from Abramowitz & Stegun. +*/ +double logI1(double x) +{ + double t = 0.0; + double t2 = 0.0; + double tpow = 0.0; + double li1 = 0.0; + + if (fabs(x) < 3.75) + { + t2 = SQR(x / 3.75); + li1 = 0.5; + li1 += 0.87890594 * (tpow = t2); + li1 += 0.51498869 * (tpow *= t2); + li1 += 0.15084934 * (tpow *= t2); + li1 += 0.02658733 * (tpow *= t2); + li1 += 0.00301532 * (tpow *= t2); + li1 += 0.00032411 * (tpow *= t2); + li1 = log(li1) + log(x); + } + else + { + t = 3.75 / x; /* Inverse of t. */ + li1 = 0.39894228; + li1 -= 0.03988024 * (tpow = t); + li1 -= 0.00362018 * (tpow *= t); + li1 += 0.00163801 * (tpow *= t); + li1 -= 0.01031555 * (tpow *= t); + li1 += 0.02282967 * (tpow *= t); + li1 -= 0.02895312 * (tpow *= t); + li1 += 0.01787654 * (tpow *= t); + li1 -= 0.00420059 * (tpow *= t); + + li1 = log(li1) - 0.5*log(x) + x; + } + + return(li1); +} + +/* + Calculates I_1(1)/I_0(x) +*/ +double make_I1I0(double x) +{ + return(exp(logI1(x) - logI0(x))); +} + +/* + Calculates the expectation we could not + work out analytically. +*/ +double make_tricky(double A, + double s2) +{ + static double b1[] = {0.0, 0.02071246146216, -0.27072813226432, 0.95032573184007, 0.82139386588719}; + static double b2[] = {-0.38563380475534, -0.18815651963905, 1.06556710921996, -0.00835947755286, 1.00035792923264}; + static double b3[] = {-0.50446345284581, 0.00031463569030, 0.99999186450441, 0.00000008937218, 0.99999999964833}; + double x = A/sqrt(s2); + + if (x <= 1.0) {return(b1[0] + x*(b1[1] + x*(b1[2] + x*(b1[3] + x*b1[4]))));} + else if (x <= 10) {return(b2[0] + x*(b2[1] + x*(b2[2] + x*(b2[3] + x*b2[4]))));} + else {return(b3[0] + x*(b3[1] + x*(b3[2] + x*(b3[3] + x*b3[4]))));} +} + +/* + This routine will make sure phi_2 (rotation around y-axis) + stays within the range -pi/2 < phi_2 < pi/2. It does so in + a very simplistic way, by resetting it to zero. In reality + there exist a phi_n and L_n such that + + g'*R(phi_n)*L_n*R(phi_n)'*g = g'*R(phi)*L*R(phi)'*g + + for all possible vectors g, where phi_n and L_n is the new + set of angles and eigenvalues with phi_2 within bounds. But + I haven't been able to find that set. Anyone who wants to try, + please do. + It will also set phi_1 and phi_3 to be in the range 0--2*pi, + but that's more of cosmetics really. +*/ + +void fix_phi2(double *theta, + int nvox) +{ + int i=0; + int j=0; + + for (i=0; i<nvox; i++) + { + if (fabs(theta[i*7+3]) >= M_PI_2) /* phi_2. */ + { + theta[i*7+3] = 0.0; + } + for (j=2; j<5; j+=2) + { + if (fabs(theta[i*7+j]) > 2.0*M_PI) + { + theta[i*7+j] = fmod(theta[i*7+j],2.0*M_PI); + if (theta[i*7+j] > 0.0) + { + theta[i*7+j] += 2.0*M_PI; + } + } + } + } + return; +} + +} // end namespace NLDTIFIT diff --git a/RLR_MAP_routines.h b/RLR_MAP_routines.h new file mode 100644 index 0000000..01f7dd8 --- /dev/null +++ b/RLR_MAP_routines.h @@ -0,0 +1,263 @@ +// Nonlinear estimation of diffusion tensor +// using a Gaussian or Rician noise model +// +// RLR_MAP_routines.h +// +// Jesper Andersson, FMRIB Image Analysis Group +// +// Copyright (C) 2011 University of Oxford +// +// These routines were originally written so as to +// be compiled into a mex-file callable from within +// Matlab. Hence it was written using Matlab/FORTRAN +// style "matrices", was written in "pure" C (i.e. did +// not use any C++ features), used LAPACK for matrix +// inversion etc etc. Since the project was considered +// a "dead end" but the software still useful I +// decided on a minimal re-write. Hence all the +// original C quirkiness has been retained and a minmal +// interface has been added. +// + +namespace NLDTIFIT { + +#ifndef M_PI +#define M_PI 3.14159265358979323846 /* pi */ +#endif + +#ifndef M_PI_2 +#define M_PI_2 1.57079632679489661923 /* pi/2 */ +#endif + +/* Need macros for different platforms here. */ + +#define DGESV dgesv_ + +/* Used to index Hessian/Information matrix. */ +#ifndef Hindx +#define Hindx(R,C,SZ) ((C)*(SZ) + (R)) /* Column first for Fortran (and Matlab). */ +#endif + +/* Used to index rotation matrix R. */ +#ifndef Rindx +#define Rindx(R,C) ((C)*3 + (R)) /* Column first for Fortran (and Matlab). */ +#endif + +/* Used to index gradient matrix g. */ +#ifndef gindx +#define gindx(R,C,M) (((C)*(M)) + (R)) /* Column first for Fortran (and Matlab). */ +#endif + +/* Used to index eigenvalue matrix L. */ +#ifndef Lindx +#define Lindx(R,C) ((C)*3 + (R)) /* Column first for Fortran (and Matlab). */ +#endif + +#ifndef SQR +#define SQR(A) ((A)*(A)) +#endif + +#ifndef S2_I +#define S2_I 0 +#define S0_I 1 +#define PHI_I 2 +#define L_I 5 +#endif + +#ifndef MLEN /* Longest "time-series" we ever expect. */ +#define MLEN 1024 +#endif + +#ifndef MVOX /* Largest # of voxels estimated together. */ +#define MVOX 27 +#endif + +#ifndef MAXITER +#define MAXITER 100 +#endif + +#ifndef GAUSS +#define GAUS 1 +#define RICE 2 +#endif + + +int get_ric_ll_info(/* Input */ + double *theta, + double *y, + double *g, + double *b, + int n, + /* Input/output */ + double *ll, + double *grad, + double *info, + double *ey); + +int get_gaus_ll_info(/* Input */ + double *theta, + double *y, + double *g, + double *b, + int n, + /* Input/output */ + double *ll, + double *grad, + double *info, + double *ey); + +void make_ric_grad(/* Input */ + double *theta, /* Parameters. */ + double *g, /* Gradient vectors. */ + double *L, /* L matrix, diagonal represented as 3x1. */ + double *R, /* Rotation matrix. */ + double *b, /* b-values. */ + double *y, /* data. */ + double *z, /* z_l as defined in paper. */ + double *ei, /* Expected intensities. */ + int n, /* # of data points. */ + int npar, /* Total # of parameters. */ + int *indx, /* Indicies into grad and finf. */ + /* Output */ + double *grad, /* Gradient, 8x1. */ + double *finf); /* Information matrix, 8x8. */ + +void make_gauss_grad(/* Input */ + double *theta, /* Parameters. */ + double *g, /* Gradient vectors. */ + double *L, /* L matrix, diagonal represented as 3x1. */ + double *R, /* Rotation matrix. */ + double *b, /* b-values. */ + double *y, /* data. */ + double *ei, /* Expected intensities. */ + double *ef, /* exp(-fl). */ + double *e2f, /* exp(-2*fl). */ + int n, /* # of data points. */ + /* Output */ + double *grad, /* Gradient, 8x1. */ + double *finf); /* Information matrix, 8x8. */ + +double priors_ll(/* Input. */ + double *theta, /* Parameters. */ + double *mu, /* Means of priors. */ + double *var, /* Variance of priors. */ + int *indx, /* Indicies (into theta) of priors. */ + int n); /* # of parameters that have priors. */ + +void priors_grad(/* Input. */ + double *theta, /* Parameters. */ + double *mu, /* Means of priors. */ + double *var, /* Variance of priors. */ + int *tindx, /* Indicies (into theta) of priors. */ + int *indx, /* Indicies (into grad) of priors. */ + int n, /* # of parameters that have priors. */ + int npar, /* Total # of parameters. */ + /* Output. */ + double *grad, /* Gradient vector npar*1. */ + double *finf); /* Information matrix npar*npar. */ + +double *make_R(/* Input */ + double *phi, /* Angles in radians. */ + /* Output */ + double *R); /* Rotation matrix. */ + +void make_dR(/* Input */ + double *phi, /* Angles in radians. */ + /* Output */ + double *dRdx, + double *dRdy, + double *dRdz); + +double *make_gR(/* Input */ + double *g, /* 3x1 vector. */ + double *R, /* 3x3 matrix. */ + /* Output. */ + double *gR); /* g'*R or R'*g */ + +double make_gRLRg(/* Input */ + double *g, + double *L, + double *R); + +double make_gR1LR2g(/* Input */ + double *g, + double *L, + double *R1, + double *R2); + +double make_gRdLRg(/* Input */ + double *g, + double *R, + double l, + int li); + +double make_f(/* Input */ + double *g, + double *L, + double *R, + double b); + +double *make_z(/* Input */ + double *ei, /* Expected intensities */ + double *y, /* Data (observed intensities). */ + double es2, /* The estimate of the variance.*/ + int n, /* Length of the data. */ + /* Output. */ + double *z); + +double make_dfdphi(/* Input */ + double *g, + double *L, + double *R, + double *dRdphi, + double b); + +double make_dfdl(/* Input */ + double *g, + double dRdl, + double *R, + double b, + int dRdl_i); + +double log_like_ric(/* Input */ + double *theta, /* Parameters. */ + double *y, /* Data. */ + double *z, /* z_l as defined in paper. */ + double *ei, /* Expected intensities. */ + int n); /* Number of data points. */ + +double log_like_gauss(/* Input */ + double *theta, /* Parameters. */ + double *y, /* Data. */ + double *ei, /* Expected intensities. */ + int n); /* Number of data points. */ + +double *make_ei(/* Input */ + double *theta, + double *fl, + int n, + /* Output */ + double *ei); + +void make_ei_ef_e2f(/* Input */ + double *theta, + double *fl, + int n, + /* Output */ + double *ei, + double *ef, + double *e2f); + +double logI0(double x); + +double logI1(double x); + +double make_I1I0(double x); + +double make_tricky(double A, + double s2); + +void fix_phi2(double *theta, + int nvox); + +} // end namespace NLDTIFIT diff --git a/nldtifit.cpp b/nldtifit.cpp new file mode 100644 index 0000000..dd9cc40 --- /dev/null +++ b/nldtifit.cpp @@ -0,0 +1,210 @@ +// Nonlinear estimation of diffusion tensor +// using a Gaussian or Rician noise model +// +// nldtifit +// +// Jesper Andersson & Stam Rastapopoulos, FMRIB Image Analysis Group +// +// Copyright (C) 2011 University of Oxford +// + +#include <cstdlib> +#include <iostream> +#include <string> +#include <ctime> +#include <cmath> +#include "newmat.h" +#include "newmatio.h" +#include "newimage/newimageall.h" +#include "RLR_MAP_routines.h" +#include "nldtifit.h" + +using namespace std; +using namespace NEWMAT; +using namespace NEWIMAGE; + +int main(int argc, + char *argv[]) +{ + // To be written by Stam +} + +namespace NLDTIFIT { + +NEWIMAGE::volume4D<float> nldtifit(// Data, b=0 and diffusion weighted + const NEWIMAGE::volume4D<float>& scans, + // Initial guesses of the parameters in the order s2, s0, R1, R2, R3, L1, L2, L3. + // N.B. that s2, s0 and L* must all be positive + const NEWIMAGE::volume4D<float>& inpar, + // Mask indicating where we want calculations to be performed + const NEWIMAGE::volume<char>& mask, + // nx3 matrix with gradient directions in same order as in scans + const NEWMAT::Matrix& bvec, + // nx1 vector with b-values in same order as in scans. N.B. that wa can have many different b-values + const NEWMAT::ColumnVector& bval, + // Indicates what neighbourhood of voxels we want to base s2 estimate on. + NbHood nbhood, + // Gaussian or Rician + NoiseModel nm) +{ + verify_input(scans,inpar,mask,bvec,bval,nbhood,nm); + + NEWIMAGE::volume4D<float> ovol = inpar; + ovol = 0.0; + int n = scans.tsize(); + double *y = new double[nbhood*n]; + double *theta = new double[nbhood*7+1]; + double *g = new double[3*n]; + double *b = new double[n]; + repack_g_and_b(bvec,bval,g,b); + int np = 3; + double pmu[3] = {-7.29, -7.30, -7.31}; + double pvar[3] = {4.0, 4.0, 4.0}; + int pi[3] = {5, 6, 7}; + double *ei = new double[nbhood*n]; + double hist[MAXITER]; + + for (int k=0; k<scans.zsize(); k++) { + for (int j=0; j<scans.ysize(); j++) { + for (int i=0; i<scans.xsize(); i++) { + if (mask(i,j,k)) { + int nvox = load_data_and_initial_guesses(scans,inpar,mask,i,j,k,nbhood,y,theta); + if (fit_RLR(g,b,y,n,nvox,pmu,pvar,pi,np,nm,theta,ei,hist) > 0) { + deal_results(theta,i,j,k,ovol); + } + } + } + } + } + + delete[] y; + delete[] theta; + delete[] g; + delete[] b; + delete[] ei; + + return(ovol); +} + +void verify_input(const NEWIMAGE::volume4D<float>& scans, + const NEWIMAGE::volume4D<float>& inpar, + const NEWIMAGE::volume<char>& mask, + const NEWMAT::Matrix& bvec, + const NEWMAT::ColumnVector& bval, + NbHood nbhood, + NoiseModel nm) +{ + int n = scans.tsize(); + if (!samesize(scans[0],inpar[0])) throw nldtifitException("verify_input: Data and intial guesses must have same dimensions"); + if (!samesize(scans[0],mask)) throw nldtifitException("verify_input: Data and mask must have same dimensions"); + if (inpar.tsize() != 8) throw nldtifitException("verify_input: There must be initial guesses for eight parameters per voxel"); + if (bvec.Nrows() != n) throw nldtifitException("verify_input: The number of gradient vectors must match the number of scans"); + if (bvec.Ncols() != 3) throw nldtifitException("verify_input: Each gradient vector must have three elements"); + if (bval.Nrows() != n) throw nldtifitException("verify_input: The number of b-values must match the number of scans"); + if (nbhood > NONE && nm == GAUSSIAN) throw nldtifitException("verify_input: You cannot combine a neighbourhood for s2 with Gaussian noise model"); +} + +void deal_results(// Input + const double *theta, + int ii, + int jj, + int kk, + // Output + NEWIMAGE::volume4D<float>& ovol) +{ + for (int i=0; i<8; i++) ovol(ii,jj,kk,i) = theta[i]; +} + +void repack_g_and_b(// Input + const NEWMAT::Matrix& bvec, + const NEWMAT::ColumnVector& bval, + // Output + double *g, + double *b) +{ + for (int r=1; r<=bvec.Nrows(); r++) { + *b++ = bval(r); + for (int c=1; c<=bvec.Ncols(); c++) { + *g++ = bvec(r,c); + } + } + return; +} + +int load_data_and_initial_guesses(// Input + const NEWIMAGE::volume4D<float>& scans, + const NEWIMAGE::volume4D<float>& inpar, + const NEWIMAGE::volume<char>& mask, + int ii, + int jj, + int kk, + NbHood nbhood, + // Output + double *y, + double *ig) +{ + int nvox = 0; + if (mask(ii,jj,kk)) { // This SHOULD be true + y = load_data_single_voxel(scans,ii,jj,kk,y); + *ig++ = inpar(ii,jj,kk,0); + ig = load_guess_single_voxel(inpar,ii,jj,kk,ig); + nvox++; + if (nbhood > NONE) { // Add face voxels + if (ii && mask(ii-1,jj,kk)) {y = load_data_single_voxel(scans,ii-1,jj,kk,y); ig = load_guess_single_voxel(inpar,ii-1,jj,kk,ig); nvox++;} + if (jj && mask(ii,jj-1,kk)) {y = load_data_single_voxel(scans,ii,jj-1,kk,y); ig = load_guess_single_voxel(inpar,ii,jj-1,kk,ig); nvox++;} + if (kk && mask(ii,jj,kk-1)) {y = load_data_single_voxel(scans,ii,jj,kk-1,y); ig = load_guess_single_voxel(inpar,ii,jj,kk-1,ig); nvox++;} + if (ii<scans.xsize()-1 && mask(ii+1,jj,kk)) {y = load_data_single_voxel(scans,ii+1,jj,kk,y); ig = load_guess_single_voxel(inpar,ii+1,jj,kk,ig); nvox++;} + if (jj<scans.ysize()-1 && mask(ii,jj+1,kk)) {y = load_data_single_voxel(scans,ii,jj+1,kk,y); ig = load_guess_single_voxel(inpar,ii,jj+1,kk,ig); nvox++;} + if (kk<scans.zsize()-1 && mask(ii,jj,kk+1)) {y = load_data_single_voxel(scans,ii,jj,kk+1,y); ig = load_guess_single_voxel(inpar,ii,jj,kk+1,ig); nvox++;} + } + if (nbhood > FACE) { // Add edge voxels + if (ii && jj && mask(ii-1,jj-1,kk)) {y = load_data_single_voxel(scans,ii-1,jj-1,kk,y); ig = load_guess_single_voxel(inpar,ii-1,jj-1,kk,ig); nvox++;} + if (ii && kk && mask(ii-1,jj,kk-1)) {y = load_data_single_voxel(scans,ii-1,jj,kk-1,y); ig = load_guess_single_voxel(inpar,ii-1,jj,kk-1,ig); nvox++;} + if (ii && jj<scans.ysize()-1 && mask(ii-1,jj+1,kk)) {y = load_data_single_voxel(scans,ii-1,jj+1,kk,y); ig = load_guess_single_voxel(inpar,ii-1,jj+1,kk,ig); nvox++;} + if (ii && kk<scans.zsize()-1 && mask(ii-1,jj,kk+1)) {y = load_data_single_voxel(scans,ii-1,jj,kk+1,y); ig = load_guess_single_voxel(inpar,ii-1,jj,kk+1,ig); nvox++;} + if (ii<scans.xsize()-1 && jj && mask(ii+1,jj-1,kk)) {y = load_data_single_voxel(scans,ii+1,jj-1,kk,y); ig = load_guess_single_voxel(inpar,ii+1,jj-1,kk,ig); nvox++;} + if (ii<scans.xsize()-1 && kk && mask(ii+1,jj,kk-1)) {y = load_data_single_voxel(scans,ii+1,jj,kk-1,y); ig = load_guess_single_voxel(inpar,ii+1,jj,kk-1,ig); nvox++;} + if (ii<scans.xsize()-1 && jj<scans.ysize()-1 && mask(ii+1,jj+1,kk)) {y = load_data_single_voxel(scans,ii+1,jj+1,kk,y); ig = load_guess_single_voxel(inpar,ii+1,jj+1,kk,ig); nvox++;} + if (ii<scans.xsize()-1 && kk<scans.zsize()-1 && mask(ii+1,jj,kk+1)) {y = load_data_single_voxel(scans,ii+1,jj,kk+1,y); ig = load_guess_single_voxel(inpar,ii+1,jj,kk+1,ig); nvox++;} + if (jj && kk && mask(ii,jj-1,kk-1)) {y = load_data_single_voxel(scans,ii,jj-1,kk-1,y); ig = load_guess_single_voxel(inpar,ii,jj-1,kk-1,ig); nvox++;} + if (jj && kk<scans.zsize()-1 && mask(ii,jj-1,kk+1)) {y = load_data_single_voxel(scans,ii,jj-1,kk+1,y); ig = load_guess_single_voxel(inpar,ii,jj-1,kk+1,ig); nvox++;} + if (jj<scans.ysize()-1 && kk && mask(ii,jj+1,kk-1)) {y = load_data_single_voxel(scans,ii,jj+1,kk-1,y); ig = load_guess_single_voxel(inpar,ii,jj+1,kk-1,ig); nvox++;} + if (jj<scans.ysize()-1 && kk<scans.zsize()-1 && mask(ii,jj+1,kk+1)) {y = load_data_single_voxel(scans,ii,jj+1,kk+1,y); ig = load_guess_single_voxel(inpar,ii,jj+1,kk+1,ig); nvox++;} + } + if (nbhood > EDGE) { + if (ii && jj && kk && mask(ii-1,jj-1,kk-1)) {y = load_data_single_voxel(scans,ii-1,jj-1,kk-1,y); ig = load_guess_single_voxel(inpar,ii-1,jj-1,kk-1,ig); nvox++;} + if (ii<scans.xsize()-1 && jj && kk && mask(ii+1,jj-1,kk-1)) {y = load_data_single_voxel(scans,ii+1,jj-1,kk-1,y); ig = load_guess_single_voxel(inpar,ii+1,jj-1,kk-1,ig); nvox++;} + if (ii && jj<scans.ysize()-1 && kk && mask(ii-1,jj+1,kk-1)) {y = load_data_single_voxel(scans,ii-1,jj+1,kk-1,y); ig = load_guess_single_voxel(inpar,ii-1,jj+1,kk-1,ig); nvox++;} + if (ii && jj && kk<scans.zsize()-1 && mask(ii-1,jj-1,kk+1)) {y = load_data_single_voxel(scans,ii-1,jj-1,kk+1,y); ig = load_guess_single_voxel(inpar,ii-1,jj-1,kk+1,ig); nvox++;} + if (ii<scans.xsize()-1 && jj<scans.ysize()-1 && kk && mask(ii+1,jj+1,kk-1)) {y = load_data_single_voxel(scans,ii+1,jj+1,kk-1,y); ig = load_guess_single_voxel(inpar,ii+1,jj+1,kk-1,ig); nvox++;} + if (ii<scans.xsize()-1 && jj && kk<scans.zsize()-1 && mask(ii+1,jj-1,kk+1)) {y = load_data_single_voxel(scans,ii+1,jj-1,kk+1,y); ig = load_guess_single_voxel(inpar,ii+1,jj-1,kk+1,ig); nvox++;} + if (ii && jj<scans.ysize()-1 && kk<scans.zsize()-1 && mask(ii-1,jj+1,kk+1)) {y = load_data_single_voxel(scans,ii-1,jj+1,kk+1,y); ig = load_guess_single_voxel(inpar,ii-1,jj+1,kk+1,ig); nvox++;} + if (ii<scans.xsize()-1 && jj<scans.ysize()-1 && kk<scans.zsize()-1 && mask(ii+1,jj+1,kk+1)) {y = load_data_single_voxel(scans,ii+1,jj+1,kk+1,y); ig = load_guess_single_voxel(inpar,ii+1,jj+1,kk+1,ig); nvox++;} + } + } + return(nvox); +} + +double *load_data_single_voxel(const NEWIMAGE::volume4D<float>& scans, + int ii, + int jj, + int kk, + // Output + double *y) +{ + for (int i = 0; i<scans.tsize(); i++) *y++ = scans(ii,jj,kk,i); + return(y); +} + +double *load_guess_single_voxel(const NEWIMAGE::volume4D<float>& inpar, + int ii, + int jj, + int kk, + // Output + double *ig) +{ + for (int i = 1; i<inpar.tsize(); i++) *ig++ = inpar(ii,jj,kk,i); // N.B. starting at 1 (after s2) + return(ig); +} + +} // end namespace NLDTIFIT diff --git a/nldtifit.h b/nldtifit.h new file mode 100644 index 0000000..ec7f874 --- /dev/null +++ b/nldtifit.h @@ -0,0 +1,111 @@ +// Nonlinear estimation of diffusion tensor +// using a Gaussian or Rician noise model +// +// nldtifit +// +// Jesper Andersson, FMRIB Image Analysis Group +// +// Copyright (C) 2011 University of Oxford +// + +namespace NLDTIFIT { + +class nldtifitException: public std::exception +{ +private: + std::string m_msg; +public: + nldtifitException(const std::string& msg) throw(): m_msg(msg) {} + + virtual const char * what() const throw() { + return string("nldtifit: msg=" + m_msg).c_str(); + } + + ~nldtifitException() throw() {} +}; + +enum NbHood {NONE = 1, FACE=7, EDGE=19, CORNER=27}; +enum NoiseModel {GAUSSIAN=1, RICIAN=2}; + +NEWIMAGE::volume4D<float> nldtifit(// Data, b=0 and diffusion weighted + const NEWIMAGE::volume4D<float>& scans, + // Initial guesses of the parameters in the order s2, s0, R1, R2, R3, L1, L2, L3. + // N.B. that s2, s0 and L* must all be positive + const NEWIMAGE::volume4D<float>& inpar, + // Mask indicating where we want calculations to be performed + const NEWIMAGE::volume<char>& mask, + // nx3 matrix with gradient directions in same order as in scans + const NEWMAT::Matrix& bvec, + // nx1 vector with b-values in same order as in scans. N.B. that wa can have many different b-values + const NEWMAT::ColumnVector& bval, + // Indicates what neighbourhood of voxels we want to base s2 estimate on. + NbHood nbhood, + // Gaussian or Rician + NoiseModel nm); + +void verify_input(const NEWIMAGE::volume4D<float>& scans, + const NEWIMAGE::volume4D<float>& inpar, + const NEWIMAGE::volume<char>& mask, + const NEWMAT::Matrix& bvec, + const NEWMAT::ColumnVector& bval, + NbHood nbhood, + NoiseModel nm); + +void deal_results(// Input + const double *theta, + int ii, + int jj, + int kk, + // Output + NEWIMAGE::volume4D<float>& ovol); + +void repack_g_and_b(// Input + const NEWMAT::Matrix& bvec, + const NEWMAT::ColumnVector& bval, + // Output + double *g, + double *b); + +int load_data_and_initial_guesses(// Input + const NEWIMAGE::volume4D<float>& scans, + const NEWIMAGE::volume4D<float>& inpar, + const NEWIMAGE::volume<char>& mask, + int ii, + int jj, + int kk, + NbHood nbhood, + // Output + double *y, + double *ig); + +double *load_data_single_voxel(const NEWIMAGE::volume4D<float>& scans, + int ii, + int jj, + int kk, + // Output + double *y); + +double *load_guess_single_voxel(const NEWIMAGE::volume4D<float>& inpar, + int ii, + int jj, + int kk, + // Output + double *ig); + +int fit_RLR(/* Input */ + double *g, /* Gradients, nx3 Matlab matrix. */ + double *b, /* b-values. */ + double *y, /* Data. */ + int n, /* # of data points. */ + int nvox, /* # of voxels. */ + double *pmu, /* Means of priors. */ + double *pvar, /* Variances of priors. */ + int *pi, /* Indicies (into theta) of priors. */ + int np, /* # of parameters with priors. */ + int nm, /* Noise model 1->Gauss, 2->Rician. */ + /* Input/Output */ + double *theta, /* Parameters. */ + double *ei, /* Expected intensities. */ + double *hist); /* History of log-likelihoods. */ + +} // end namespace NLDTIFIT -- GitLab