/*! \file HyParEstimator.cpp \brief Contains definitions of classes implementing hyper parameter estimation for DWI GPs \author Jesper Andersson \version 1.0b, Nov., 2013. */ // Definitions of classes implementing hyper parameter estimation for DWI GPs // // HyParEstimator.cpp // // Jesper Andersson, FMRIB Image Analysis Group // // Copyright (C) 2013 University of Oxford // #include #include #include #include #include #include #include #include "armawrap/newmat.h" #include "miscmaths/miscmaths.h" #include "EddyHelperClasses.h" #include "EddyUtils.h" #include "HyParEstimator.h" using namespace std; using namespace EDDY; DataSelector& DataSelector::ConcatToMe(const DataSelector& ds) EddyTry { this->_data.reserve(this->_data.size()+ds._data.size()); this->_coords.reserve(this->_coords.size()+ds._coords.size()); this->_data.insert(this->_data.end(),ds._data.begin(),ds._data.begin()); this->_coords.insert(this->_coords.end(),ds._coords.begin(),ds._coords.begin()); return(*this); } EddyCatch NEWMAT::Matrix DataSelector::AsNEWMAT() const EddyTry { NEWMAT::Matrix m(_data[0].Nrows(),_data.size()); for (unsigned int i=0; i<_data.size(); i++) { m.Column(i+1) = _data[i]; } return(m); } EddyCatch NEWMAT::Matrix DataSelector::CoordsAsNEWMAT() const EddyTry { NEWMAT::Matrix m(4,_coords.size()); for (unsigned int i=0; i<_coords.size(); i++) { m(1,i+1) = _coords[i][0]; m(2,i+1) = _coords[i][1]; m(3,i+1) = _coords[i][2]; m(4,i+1) = _coords[i][3]; } return(m); } EddyCatch void DataSelector::common_constructor(const std::vector > >& idata, const NEWIMAGE::volume& mask, unsigned int rnvox, float fwhm, unsigned int d4, int rndinit) EddyTry { // Initialise rand if no explicit seed given if (rndinit) srand(rndinit); else srand(time(NULL)); // Check that there are at least rnvox voxels available unsigned int nnz = 0; // Number of non-zero voxels in mask for (auto it = mask.fbegin(); it!=mask.fend(); ++it) nnz += (*it) ? 1 : 0; if (nnz < rnvox) throw EddyException("DataSelector::common_constructor: rnvox greater than number of non-zero voxels in mask."); // Select rnvox voxel coordinates within mask unsigned int nx = static_cast(mask.xsize()); unsigned int ny = static_cast(mask.ysize()); unsigned int nz = static_cast(mask.zsize()); std::vector > coords(rnvox); unsigned int nvox = 0; if (rnvox) { unsigned int l=0; unsigned int maxtry=1e8; for (l=0; l nc(4); nc[0] = rand() % nx; nc[1] = rand() % ny; nc[2] = rand() % nz; nc[3] = d4; if (mask(nc[0],nc[1],nc[2])) { unsigned int vox; for (vox=0; vox kernels(3); // Make convolution kernels if fwhm>0 if (fwhm > 0.0) { float sx = (fwhm/std::sqrt(8.0*std::log(2.0)))/idata[0]->xdim(); float sy = (fwhm/std::sqrt(8.0*std::log(2.0)))/idata[0]->ydim(); float sz = (fwhm/std::sqrt(8.0*std::log(2.0)))/idata[0]->zdim(); int nx=((int) (sx-0.001))*2 + 3; int ny=((int) (sy-0.001))*2 + 3; int nz=((int) (sz-0.001))*2 + 3; kernels[0] = NEWIMAGE::gaussian_kernel1D(sx,nx); kernels[1] = NEWIMAGE::gaussian_kernel1D(sy,ny); kernels[2] = NEWIMAGE::gaussian_kernel1D(sz,nz); } // Get time-series from the selected coordinates NEWMAT::ColumnVector vec(idata.size()); _data.resize(nvox,vec); for (unsigned int i=0; i& vol = *idata[i]; for (unsigned int j=0; j 0.0) _data[j](i+1) = get_smooth(vol,mask,coords[j],kernels); else _data[j](i+1) = vol(coords[j][0],coords[j][1],coords[j][2]); } } // Save coordinates _coords = coords; } EddyCatch float DataSelector::get_smooth(const NEWIMAGE::volume& ima, const NEWIMAGE::volume& mask, const std::vector& coords, const std::vector& kernels) EddyTry { float oval = 0.0; float totwgt = 0.0; for (int k=0; k=0 && kk=0 && jj=0 && ii hp(p.Nrows()); for (int i=0; iSetHyperPar(hp); if (!_K->IsValid()) return(std::numeric_limits::max()); double ldKy = _K->LogDet(); double neg_log_marg_ll = 0.0; if (_nthreads == 1) { // If we are to run single threaded for (unsigned int i=0; i<_data.size(); i++) neg_log_marg_ll += NEWMAT::DotProduct(_data[i],_K->iKy(_data[i])); } else { // We are to run multi-threaded std::vector nvecs = EddyUtils::ScansPerThread(_data.size(),_nthreads); std::vector threads(_nthreads-1); // + main thread makes _nthreads std::vector neg_log_marg_ll_vec(_nthreads,0.0); _K->CalculateInvK(); // Needed to allow for use of const _K->iKy for thread safety for (unsigned int i=0; i<_nthreads-1; i++) { threads[i] = std::thread(&MMLHyParCF::cf_helper,this,nvecs[i],nvecs[i+1], std::ref(_data),_K,std::ref(neg_log_marg_ll_vec[i])); } this->cf_helper(nvecs[_nthreads-1],nvecs[_nthreads],_data,_K,neg_log_marg_ll_vec[_nthreads-1]); std::for_each(threads.begin(),threads.end(),std::mem_fn(&std::thread::join)); neg_log_marg_ll = std::accumulate(neg_log_marg_ll_vec.begin(),neg_log_marg_ll_vec.end(),0.0); } neg_log_marg_ll += _data.size() * ldKy; neg_log_marg_ll *= 0.5; return(neg_log_marg_ll); } EddyCatch void MMLHyParCF::cf_helper(// Input unsigned int first, unsigned int last, const std::vector& data, const std::shared_ptr K, // Output double& neg_log_marg_ll) const EddyTry { neg_log_marg_ll = 0.0; for (unsigned int i=first; iiKy(_data[i])); return; } EddyCatch double CVHyParCF::cf(const NEWMAT::ColumnVector& p) const EddyTry { std::vector hp(p.Nrows()); for (int i=0; iSetHyperPar(hp); if (!_K->IsValid()) return(std::numeric_limits::max()); NEWMAT::ColumnVector ssd_vec(_K->NoOfScans()); ssd_vec = 0.0; if (_nthreads == 1) { // If we are to run single threaded for (unsigned int i=0; i<_data.size(); i++) { NEWMAT::ColumnVector qn = _K->iKy(_data[i]); ssd_vec += NEWMAT::SP(qn,qn); } } else { // We are to run multi threaded std::vector nvecs = EddyUtils::ScansPerThread(_data.size(),_nthreads); std::vector threads(_nthreads-1); // + main thread makes _nthreads std::vector ssd_vec_vec(_nthreads,NEWMAT::ColumnVector(_K->NoOfScans())); _K->CalculateInvK(); // Needed to allow for use of const _K->iKy for thread safety for (unsigned int i=0; i<_nthreads-1; i++) { threads[i] = std::thread(&CVHyParCF::cf_helper,this,nvecs[i],nvecs[i+1],i,_nthreads, std::ref(threads),std::ref(_data),_K,std::ref(ssd_vec_vec)); } this->cf_helper(nvecs[_nthreads-1],nvecs[_nthreads],_nthreads-1,_nthreads,threads,_data,_K,ssd_vec_vec); // Takes care of joining ssd_vec = ssd_vec_vec[_nthreads-1]; } NEWMAT::SymmetricMatrix iK = _K->iK(); double ssd = 0.0; for (unsigned int i=0; i<_K->NoOfScans(); i++) ssd += ssd_vec(i+1) / sqr(iK(i+1,i+1)); ssd /= _K->NoOfScans(); return(ssd); } EddyCatch void CVHyParCF::cf_helper(// Input unsigned int first, unsigned int last, unsigned int tid, // Thread number unsigned int numt, // Number of threads std::vector& threads, const std::vector& data, const std::shared_ptr K, // Output std::vector& ssd_vec_vec) const EddyTry { // static std::mutex cout_mutex; // Do the multiplications and additions you are responsible for ssd_vec_vec[tid] = 0.0; for (unsigned int i=first; iiKy(_data[i]); ssd_vec_vec[tid] += NEWMAT::SP(qn,qn); } // Now add up results with other threads int step = 1; // How far to the left your neighbour is while (step < numt) { if (is_odd(((int(numt)-1)-int(tid))/step)) return; // Is not a candidate int neighbour = int(tid)-step; // Check who your neighbour to the left is if (neighbour < 0) return; // Return if neighbour is out of bounds threads[neighbour].join(); // Make sure that neighbour has finished // cout_mutex.lock(); // std::cout << "I am thread # " << tid << ", and I will add thread # " << neighbour << " to myself" << std::endl; // cout_mutex.unlock(); ssd_vec_vec[tid] += ssd_vec_vec[neighbour]; step *= 2; } return; // At this stage the results whould have been summed up in ssd_vec_vec[numt-1] } EddyCatch double GPPHyParCF::cf(const NEWMAT::ColumnVector& p) const EddyTry { std::vector hp(p.Nrows()); for (int i=0; iSetHyperPar(hp); if (!_K->IsValid()) return(std::numeric_limits::max()); NEWMAT::ColumnVector gpp_vec(_K->NoOfScans()); gpp_vec = 0.0; for (unsigned int i=0; i<_data.size(); i++) { NEWMAT::ColumnVector qn = _K->iKy(_data[i]); gpp_vec += NEWMAT::SP(qn,qn); } NEWMAT::SymmetricMatrix iK = _K->iK(); for (unsigned int i=0; i<_K->NoOfScans(); i++) { gpp_vec(i+1) /= iK(i+1,i+1); gpp_vec(i+1) -= _data.size() * std::log(iK(i+1,i+1)); } double gpp = 0.5 * gpp_vec.Sum() / _K->NoOfScans(); return(gpp); } EddyCatch void CheapAndCheerfulHyParEstimator::Estimate(std::shared_ptr K, bool verbose) EddyTry { std::shared_ptr lK = K->Clone(); // Local copy of K lK->SetHyperPar(lK->GetHyperParGuess(_data)); lK->MulErrVarBy(5.0); // Corresponds to ~ ff=5 set_hpar(stl_2_newmat(lK->GetHyperPar())); if (verbose) cout << "Hyperparameters guesstimated to be: " << get_hpar() << endl; } EddyCatch void FullMontyHyParEstimator::Estimate(std::shared_ptr K, bool vbs) EddyTry { std::shared_ptr lK = K->Clone(); // Local copy of K set_hpar(stl_2_newmat(lK->GetHyperParGuess(_data))); if (_v) cout << "Initial guess for hyperparameters: " << get_hpar() << endl; _cf->SetData(_data); _cf->SetKMatrix(lK); MISCMATHS::NonlinParam nlpar(get_hpar().Nrows(),MISCMATHS::NL_NM,get_hpar()); nlpar.SetVerbose(_v); nlpar.SetPrintWidthPrecision(10,5,15,10); nlpar.SetMaxIter(MITER); MISCMATHS::nonlin(nlpar,*_cf); set_hpar(nlpar.Par()); if (_evff > 1.0) { if (_v) { cout << "Fudging parameters" << endl; cout << "Parameters start out as " << get_hpar() << endl; } std::shared_ptr KK = lK->Clone(); std::vector hpar(get_hpar().Nrows()); for (unsigned int i=0; iSetHyperPar(hpar); KK->MulErrVarBy(_evff); hpar = KK->GetHyperPar(); NEWMAT::ColumnVector tmp_par(get_hpar().Nrows()); for (unsigned int i=0; i