/*! \file DiffusionGP.cpp \brief Contains definitions for class for making Gaussian process based predictions about DWI data. \author Jesper Andersson \version 1.0b, Sep., 2012. */ // Definitions of class to make Gaussian-Process // based predictions about diffusion data. // // DiffusionGP.cpp // // Jesper Andersson, FMRIB Image Analysis Group // // Copyright (C) 2011 University of Oxford // #include #include #include #include #include "newmat.h" #include "newimage/newimageall.h" #include "miscmaths/miscmaths.h" #include "EddyHelperClasses.h" #include "EddyUtils.h" #include "DiffusionGP.h" using namespace EDDY; ///////////////////////////////////////////////////////////////////// // // Class DiffusionGP // ///////////////////////////////////////////////////////////////////// /****************************************************************//** * * Constructs a DiffusionGP object given files containing input data. * When using this constructor the object is immediately ready to do * predictions. * \param scans_fname Name of file containing multiple diffusion * weighted volumes. * \param var_mask_fname * \param dpars Vector of objects of DiffPara type that specifies * diffusion weighting and direction for the volumes in * scans_fname. * * ********************************************************************/ DiffusionGP::DiffusionGP(const std::string& scans_fname, const std::string& var_mask_fname, const std::vector& dpars) { NEWIMAGE::volume4D scans; EddyUtils::read_DWI_volume4D(scans,scans_fname,dpars); for (int s=0; s >(new NEWIMAGE::volume(scans[s]))); _pop = true; _dpars = EddyUtils::GetDWIDiffParas(dpars); NEWIMAGE::volume var_mask; NEWIMAGE::read_volume(var_mask,var_mask_fname); std::vector > grps; std::vector grpb; std::vector vars = VarianceCalculator::GetWMVariances(_sptrs,var_mask,_dpars,grps,grpb); double s2n = VarianceCalculator::GetNoiseVariance(_sptrs,var_mask,_dpars); _gpmod = boost::shared_ptr(new GPModel(_dpars,vars,grps,grpb,s2n)); } bool DiffusionGP::IsPopulated() const { if (_pop) return(true); else { _pop = true; for (unsigned int i=0; i<_sptrs.size(); i++) { if (!_sptrs[i]) { _pop = false; break; } } } return(_pop); } void DiffusionGP::SetNoOfScans(unsigned int n) { if (n == _sptrs.size()) return; // No change else if (n > _sptrs.size()) { // If increasing size _sptrs.resize(n,boost::shared_ptr >()); // New elements populated by NULL _dpars.resize(n); // New elements populated with (arbitrary) [1 0 0] bvec _pop = false; _gpmod = boost::shared_ptr(); } else { // If decreasing size _sptrs.resize(n); _dpars.resize(n); _gpmod = boost::shared_ptr(); if (_pop==false) { // _pop may potentially go from false to true _pop = IsPopulated(); } } return; } // // Ideally one would like to check if array is ready populated // when adding a scan. But that would potentially make it // non-thread reentrant so I have choosen not to. // void DiffusionGP::AddScan(const NEWIMAGE::volume& scan, const DiffPara& dp) { if (_sptrs.size() && !NEWIMAGE::samesize(*_sptrs[0],scan)) throw EddyException("DiffusionGP::AddScan: Wrong image dimension"); _sptrs.push_back(boost::shared_ptr >(new NEWIMAGE::volume(scan))); _dpars.push_back(dp); _gpmod = boost::shared_ptr(); } void DiffusionGP::SetScan(const NEWIMAGE::volume& scan, const DiffPara& dp, unsigned int indx) { if (int(indx) > (int(_sptrs.size())-1)) throw EddyException("DiffusionGP::SetScan: Invalid image index"); if (_sptrs.size() && _sptrs[0] && !NEWIMAGE::samesize(*_sptrs[0],scan)) throw EddyException("DiffusionGP::SetScan: Wrong image dimension"); if (_sptrs[indx] && dp != _dpars[indx]) throw EddyException("DiffusionGP::SetScan: You cannot change shell or direction of scan"); _sptrs[indx] = boost::shared_ptr >(new NEWIMAGE::volume(scan)); _dpars[indx] = dp; } void DiffusionGP::EvaluateModel(const NEWIMAGE::volume& mask) { if (_gpmod == NULL) { std::vector > grps; std::vector grpb; std::vector vars = VarianceCalculator::GetWMVariances(_sptrs,mask,_dpars,grps,grpb); double s2n = VarianceCalculator::GetNoiseVariance(_sptrs,mask,_dpars); _gpmod = boost::shared_ptr(new GPModel(_dpars,vars,grps,grpb,s2n)); } } /****************************************************************//** * \brief Returns prediction for point given by indx with noise variance sigman2. * * Same as NEWIMAGE::volume DiffusionGP::Predict(unsigned int indx) const * except that the noise variance is given as a parameter (rather than * using that which is stored in the object). * \param sigman2 The noise variance of the data. * ********************************************************************/ NEWIMAGE::volume DiffusionGP::Predict(double sigman2, unsigned int indx) const { if (_gpmod == NULL) throw EddyException("DiffusionGP::Predict: invalid predictor"); NEWMAT::RowVector pvec = _gpmod->get_prediction_vector(indx,sigman2); NEWMAT::ColumnVector y(_sptrs.size()); NEWIMAGE::volume pvol = *_sptrs[0]; pvol = 0; for (int k=0; k<_sptrs[0]->zsize(); k++) { for (int j=0; j<_sptrs[0]->ysize(); j++) { for (int i=0; i<_sptrs[0]->xsize(); i++) { if (get_y(i,j,k,y)) { std::vector ymeans = _gpmod->get_group_means(y); _gpmod->subtract_group_means(ymeans,y); pvol(i,j,k) = static_cast((pvec*y).AsScalar()) + ymeans[_gpmod->Group(indx)]; } } } } return(pvol); } /****************************************************************//** * \brief Returns prediction for point with diffusion weighting given by * dpar and with noise variance sigman2. * * Same as NEWIMAGE::volume DiffusionGP::Predict(const DiffPara& dpar) const * except that the noise variance is given as a parameter (rather than * using that which is stored in the object). * \param sigman2 The noise variance of the data. * ********************************************************************/ NEWIMAGE::volume DiffusionGP::Predict(double sigman2, const DiffPara& dpars) const { if (_gpmod == NULL) throw EddyException("DiffusionGP::Predict: invalid predictor"); printf("DiffusionGP::Predict(DiffPara dp): Not fully implemented yet\n"); NEWMAT::RowVector pvec = _gpmod->get_prediction_vector(dpars,sigman2); NEWMAT::ColumnVector y(_sptrs.size()); NEWIMAGE::volume pvol = *_sptrs[0]; pvol = 0.0; for (int k=0; k<_sptrs[0]->zsize(); k++) { for (int j=0; j<_sptrs[0]->ysize(); j++) { for (int i=0; i<_sptrs[0]->xsize(); i++) { if (get_y(i,j,k,y)) pvol(i,j,k) = static_cast((pvec*y).AsScalar()); } } } return(pvol); } /****************************************************************//** * \brief Returns prediction for all loaded points with noise variance * sigman2. * * Same as NEWIMAGE::volume4D DiffusionGP::PredictAll() const * except that the noise variance is given as a parameter (rather than * using that which is stored in the object). * \param sigman2 The noise variance of the data. * ********************************************************************/ NEWIMAGE::volume4D DiffusionGP::PredictAll(double sigman2) const { if (_gpmod == NULL) throw EddyException("DiffusionGP::PredictAll: invalid predictor"); NEWIMAGE::volume4D pvols(_sptrs[0]->xsize(),_sptrs[0]->ysize(),_sptrs[0]->zsize(),_sptrs.size()); pvols = 0; for (unsigned int i=0; i<_sptrs.size(); i++) { pvols[i] = Predict(sigman2,i); } return(pvols); } void DiffusionGP::WriteDebugInfo(const std::string& fname) const { std::ofstream fout; fout.open(fname.c_str(),ios::out|ios::trunc); fout << "There are " << GetWMVariances().size() << " groups of b-values" << endl; fout << "The bvalues are:"; for (unsigned i=0; i& dpars, const std::vector vars, const std::vector > grps, const std::vector grpb, double s2n) : _dpars(dpars), _vars(vars), _grps(grps), _grpb(grpb), _s2n(s2n) { _grp = reorganize_grps(); _K = calculate_K(); } double GPModel::Predict(unsigned int indx, const NEWMAT::ColumnVector& yp, double sigman2) const { if (yp.Nrows() != int(NPoints())) throw EddyException("GPModel::Predict: mismatch between y and GPModel"); NEWMAT::RowVector pvec = get_prediction_vector(indx,sigman2); NEWMAT::ColumnVector y = yp; std::vector ymeans = get_group_means(y); subtract_group_means(ymeans,y); return((pvec*y).AsScalar() + ymeans[_grp[indx]]); } double GPModel::Predict(const DiffPara& dpars, const NEWMAT::ColumnVector& yp, double sigman2) const { if (yp.Nrows() != int(NPoints())) throw EddyException("GPModel::Predict: mismatch between y and GPModel"); NEWMAT::RowVector pvec = get_prediction_vector(dpars,sigman2); // Have to do something about means here. NEWMAT::ColumnVector y = yp; std::vector ymeans = get_group_means(y); subtract_group_means(ymeans,y); return((pvec*y).AsScalar()); // + ymeans[_grp[indx]]); } NEWMAT::RowVector GPModel::get_prediction_vector(unsigned int indx, double sigman2) const { NEWMAT::RowVector K_row = extract_K_row(indx); NEWMAT::Matrix iK = get_iK(sigman2); NEWMAT::RowVector pvec = K_row*iK; return(pvec); } NEWMAT::RowVector GPModel::get_prediction_vector(const DiffPara& dpars, double sigman2) const { NEWMAT::RowVector K_row = calculate_K_row(dpars); NEWMAT::Matrix iK = get_iK(sigman2); NEWMAT::RowVector pvec = K_row*iK; return(pvec); } std::vector GPModel::reorganize_grps() const { std::vector grpi(NPoints()); for (unsigned g=0; g<_grps.size(); g++) { for (unsigned int s=0; s<_grps[g].size(); s++) { grpi[_grps[g][s]] = g; } } return(grpi); } std::vector GPModel::get_group_means(const NEWMAT::ColumnVector& y) const { std::vector means(NPoints(),0); for (unsigned int g=0; g<_grps.size(); g++) { for (unsigned int i=0; i<_grps[g].size(); i++) { means[g] += y(_grps[g][i]+1); } means[g] /= _grps[g].size(); } return(means); } void GPModel::subtract_group_means(const std::vector& means, NEWMAT::ColumnVector& y) const { for (unsigned int g=0; g<_grps.size(); g++) { for (unsigned int i=0; i<_grps[g].size(); i++) { y(_grps[g][i]+1) -= means[g]; } } return; } void GPModel::add_group_means(const std::vector& means, NEWMAT::ColumnVector& y) const { for (unsigned int g=0; g<_grps.size(); g++) { for (unsigned int i=0; i<_grps[g].size(); i++) { y(_grps[g][i]+1) += means[g]; } } return; } NEWMAT::Matrix GPModel::get_iK(double sigman2) const { NEWMAT::IdentityMatrix sn2(NPoints()); sn2 *= sigman2; NEWMAT::Matrix iK = (_K + sn2).i(); return(iK); } /****************************************************************//** * * This will calculate the K-matrix, i.e. the variance-covariance * matrix of the Gaussian Process. It's form has been empirically * derived once and for all, and it depends on two parameters. The * variance of the signal (derived from the scan-to-scan white matter * variance) and the variance of the noise (derived from the * scan-to-scan variance outside the object). * ********************************************************************/ NEWMAT::Matrix GPModel::calculate_K() const { NEWMAT::Matrix K(NPoints(),NPoints()); for (unsigned int i=0; i VarianceCalculator::GetWMVariances(// Input const std::vector > >& sptrs, const NEWIMAGE::volume& mask, const std::vector& dpars, // Output std::vector >& grps, std::vector& grpb) { // Erode mask a little to avoid high variance voxels along edge NEWIMAGE::volume kernel = NEWIMAGE::box_kernel(3,3,3); NEWIMAGE::volume emask = NEWIMAGE::morphfilter(mask,kernel,std::string("erode")); emask = NEWIMAGE::morphfilter(emask,kernel,std::string("erode")); grps = get_groups(dpars,grpb); std::vector var(grps.size(),0.0); for (unsigned int g=0; g tmp4D(sptrs[0]->xsize(),sptrs[0]->ysize(),sptrs[0]->zsize(),grps[g].size()); for (unsigned int s=0; s varvol = NEWIMAGE::variancevol(tmp4D); // Chose 20% of intracerebral voxels with highest variance float thres = varvol.percentile(0.8,emask); NEWIMAGE::volume meanmask = NEWIMAGE::binarise(varvol,thres); var[g] = varvol.mean(meanmask); } return(var); } /****************************************************************//** * * Static member function that calculates the (noise) variance * (across scans) in an area outside the object (brain). * First it uses the supplied mask as an indicator (that is made more * conservative by some dilates) where the object (brain) is. * Finally for each group it calculates the across scan variance for * all voxels within the object and the find the 20% of those with the * highest variance (assuming these to be high FA white matter voxels). * The average of thos 20% will be returned for each of the groups. * \param[in] sprts Vector of (safe) pointers to dwi scans. * \param[in] mask Volume with ones where the object (brain) is. * \param[in] dpars Vector of diffusion paramaters for sprts * \return Vector of white matter variances for each group. * ********************************************************************/ double VarianceCalculator::GetNoiseVariance(// Input const std::vector > >& sptrs, const NEWIMAGE::volume& mask, const std::vector& dpars) { // Dilate mask a little to make sure we are outside object, then invert. NEWIMAGE::volume kernel = NEWIMAGE::box_kernel(3,3,3); NEWIMAGE::volume emask = NEWIMAGE::morphfilter(mask,kernel,std::string("dilate")); emask = NEWIMAGE::morphfilter(emask,kernel,std::string("dilate")); emask *= -1; emask += 1; std::vector > grps = get_groups(dpars); std::vector gvar(grps.size(),0.0); for (unsigned int g=0; g tmp4D(sptrs[0]->xsize(),sptrs[0]->ysize(),sptrs[0]->zsize(),grps[g].size()); for (unsigned int s=0; s varvol = NEWIMAGE::variancevol(tmp4D); // Chose voxels from top half of volume to avoid extracerebral tissue. int n = 0; for (int k=varvol.zsize()/2; k > VarianceCalculator::get_groups(// Input const std::vector& dpars, // Output std::vector& grpb) { std::vector > grps; std::vector templates; // First pass to sort out how many different b-values/shells there are templates.push_back(dpars[0]); for (unsigned int i=1; i