/*! \file fmriPredictor.cpp \brief Contains definitions for class for making Gaussian process based predictions about fMRI data. \author Jesper Andersson \version 1.0b, Feb., 2022. */ // Definitions of class to make Gaussian-Process // based predictions about diffusion data. // // fmriPredictor.cpp // // Jesper Andersson, FMRIB Image Analysis Group // // Copyright (C) 2022 University of Oxford // #include #include #include #include #include "armawrap/newmat.h" #include "newimage/newimageall.h" #include "miscmaths/miscmaths.h" #include "EddyHelperClasses.h" #include "EddyUtils.h" #include "KMatrix.h" #include "HyParEstimator.h" #include "fmriPredictor.h" using namespace EDDY; NEWIMAGE::volume fmriPredictor::Predict(unsigned int indx, bool exclude) const EddyTry { if (!IsPopulated()) throw EddyException("fmriPredictor::Predict:const: Not yet fully populated"); if (!IsValid()) throw EddyException("fmriPredictor::Predict:const: Not yet ready for predictions"); arma::rowvec pv = _Kmats[_glist[indx]._sess]->PredVec(_glist[indx]._sindx,exclude); // Calls const version NEWIMAGE::volume pi = *(_slist[0].SPtr(0)); pi = 0.0; #ifdef COMPILE_GPU predict_image_gpu(indx,exclude,pv,pi); #else predict_image_cpu(indx,exclude,pv,pi); #endif return(pi); } EddyCatch NEWIMAGE::volume fmriPredictor::Predict(unsigned int indx, bool exclude) EddyTry { if (!IsPopulated()) throw EddyException("fmriPredictor::Predict:non-const: Not yet fully populated"); if (!IsValid()) throw EddyException("fmriPredictor::Predict:non-const: Not yet ready for predictions"); arma::rowvec pv = _Kmats[_glist[indx]._sess]->PredVec(_glist[indx]._sindx,exclude); // Calls non-const version NEWIMAGE::volume pi = *(_slist[0].SPtr(0)); pi = 0.0; #ifdef COMPILE_GPU predict_image_gpu(indx,exclude,pv,pi); #else predict_image_cpu(indx,exclude,pv,pi); #endif return(pi); } EddyCatch NEWIMAGE::volume fmriPredictor::PredictCPU(unsigned int indx, bool exclude) EddyTry { if (!IsPopulated()) throw EddyException("fmriPredictor::Predict:non-const: Not yet fully populated"); if (!IsValid()) throw EddyException("fmriPredictor::Predict:non-const: Not yet ready for predictions"); arma::rowvec pv = _Kmats[_glist[indx]._sess]->PredVec(_glist[indx]._sindx,exclude); // Calls non-const version NEWIMAGE::volume pi = *(_slist[0].SPtr(0)); pi = 0.0; predict_image_cpu(indx,exclude,pv,pi); return(pi); } EddyCatch std::vector > fmriPredictor::Predict(const std::vector& indicies, bool exclude) EddyTry { if (!IsPopulated()) throw EddyException("fmriPredictor::Predict: Not yet fully populated"); if (!IsValid()) throw EddyException("fmriPredictor::Predict: Not yet ready for predictions"); std::vector > pi(indicies.size()); std::vector pvecs(indicies.size()); for (unsigned int i=0; iPredVec(_glist[indicies[i]]._sindx,exclude); #ifdef COMPILE_GPU predict_images_gpu(indicies,exclude,pvecs,pi); #else predict_images_cpu(indicies,exclude,pvecs,pi); #endif return(pi); } EddyCatch NEWIMAGE::volume fmriPredictor::InputData(unsigned int indx) const EddyTry { if (!IsPopulated()) throw EddyException("fmriPredictor::InputData: Not yet fully populated"); if (indx >= _glist.size()) throw EddyException("DiffusionGP::InputData: indx out of range"); return(*(_slist[_glist[indx]._sess].SPtr(_glist[indx]._sindx)) + *(_mptrs[_glist[indx]._sess])); } EddyCatch std::vector > fmriPredictor::InputData(const std::vector& indx) const EddyTry { if (!IsPopulated()) throw EddyException("fmriPredictor::InputData: Not yet fully populated"); for (unsigned int i=0; i= _glist.size()) throw EddyException("fmriPredictor::InputData: indx out of range"); std::vector > rval(indx.size()); for (unsigned int i=0; iPredVar(_glist[indx]._sindx,exclude); return(pv); } EddyCatch double fmriPredictor::ErrorVariance(unsigned int indx) const EddyTry { if (!IsPopulated()) throw EddyException("fmriPredictor::ErrorVariance:const: Not yet fully populated"); if (!IsValid()) throw EddyException("fmriPredictor::ErrorVariance:const: Not yet ready for predictions"); double ev = _Kmats[_glist[indx]._sess]->ErrVar(_glist[indx]._sindx); return(ev); } EddyCatch void fmriPredictor::SetNoOfScans(unsigned int n) EddyTry { if (n == _glist.size()) return; // No change else if (n > _glist.size()) { // If increasing size std::lock_guard lg(_set_mut); _glist.resize(n); // New elements populated according to default constructor _lak = false; for (unsigned int i; i<_slist.size(); i++) _Kmats[i]->Reset(); } else { // Decreasing size not allowed throw EddyException("fmriPredictor::SetNoOfScans: Decreasing size not allowed"); } return; } EddyCatch void fmriPredictor::SetScan(const NEWIMAGE::volume& scan, const DiffPara& dp, unsigned int indx, unsigned int sess) EddyTry { std::lock_guard lg(_set_mut); // First just sanity checks if (indx >= _glist.size()) throw EddyException("fmriPredictor::SetScan: Invalid image index"); if (_tr < 0.0) _tr = dp.TR(); else if (std::abs(_tr - dp.TR()) > 1e-6) throw EddyException("fmriPredictor::SetScan: You cannot mix scans with different repetition times"); if (_glist.size() && !NEWIMAGE::samesize(*_slist[_glist[0]._sess].SPtr(0),scan)) throw EddyException("fmriPredictor::SetScan: Wrong image dimension"); if (_glist[indx]._sess >= 0 && _glist[indx]._sess != sess) throw EddyException("fmriPredictor::SetScan: You cannot change session of a scan"); if (_glist[indx]._sess >= 0 && _slist[_glist[indx]._sess].Gndx(_glist[indx]._sindx) != static_cast(indx)) throw EddyException("fmriPredictor::SetScan: Lists are inconsistent"); // Next we do the job if (_glist[indx]._sess >= 0) { // If the same index has already been loaded before _slist[_glist[indx]._sess].SPtr(_glist[indx]._sindx) = std::make_shared >(scan); } else { // If this is the first time this index is loaded _glist[indx]._sess = sess; _glist[indx]._sindx = _slist[sess].Size(); _slist[sess].PushBack(scan,indx); } _lak = false; } EddyCatch void fmriPredictor::EvaluateModel(const NEWIMAGE::volume& mask, float fwhm, bool verbose) EddyTry { // The first thing we want to do is to "clean" up the lists. if (!lists_are_kosher()) throw EddyException("fmriPredictor::lists_are_kosher: Lists are inconsistent"); // Next make one K-matrix per session _Kmats.resize(_slist.size()); for (unsigned int i=1; i<_slist.size(); i++) { _Kmats[i] = _Kmats[0]->Clone(); } // Populate K-matrices with distances. Note that we have shoe-horned it into a "diffusion syntax" for (unsigned int i=0; i<_slist.size(); i++) { std::vector dpars(_slist[i].Size(),DiffPara(-100,_tr)); _Kmats[i]->SetDiffusionPar(dpars); } mean_correct(); // Mean correct (on a per session basis) // Next we need to select data for the estimation of hyper-parameters. // If all sessions have the same number of scans we can mix-and-match data // from different sessions. If not we pick a session at random, but with // probability weighted by the number of scans in the session. if (same_no_of_scans_in_all_sessions()) { // Mix-and-match DataSelector ds = DataSelector(_slist[0].SPtr_list(),mask,(_hpe->GetNVox()/_slist.size())+1,FourthDimension(0),fwhm,_hpe->RndInit()); for (unsigned int i=1; i<_slist.size(); i++) { ds.ConcatToMe(DataSelector(_slist[i].SPtr_list(),mask,(_hpe->GetNVox()/_slist.size())+1,FourthDimension(i),fwhm,_hpe->RndInit())); } _hpe->SetData(ds.GetData()); _hpe->Estimate(_Kmats[0],verbose); for (unsigned int i=0; i<_slist.size(); i++) { _Kmats[i]->SetHyperPar(_hpe->GetHyperParameters()); _Kmats[i]->CalculateInvK(); } } else { // Pick random session // Draw random number 0--no_of_scans-1 int seed = (_hpe->RndInit()) ? _hpe->RndInit() : static_cast(time(NULL)); std::mt19937 gen(seed); std::uniform_int_distribution<> distr(0,static_cast(no_of_scans())-1); int rndnr = distr(gen); // Determine which session this corresponds to int cumscans = 0; unsigned int si; for (si=0; si<_slist.size(); si++) { cumscans += _slist[si].Size(); if (cumscans >= rndnr) break; } DataSelector ds = DataSelector(_slist[si].SPtr_list(),mask,_hpe->GetNVox(),FourthDimension(si),fwhm,_hpe->RndInit()); _hpe->SetData(ds.GetData()); _hpe->Estimate(_Kmats[si],verbose); for (unsigned int i=0; i<_slist.size(); i++) { _Kmats[i]->SetHyperPar(_hpe->GetHyperParameters()); _Kmats[i]->CalculateInvK(); } } return; } EddyCatch void fmriPredictor::WriteImageData(const std::string& fname) const EddyTry { char ofname[256]; if (!IsPopulated() || !_lak) throw EddyException("fmriPredictor::WriteImageData: Not yet fully populated"); // For practical reasons the volumes are written individually for (unsigned int i=0; i<_glist.size(); i++) { sprintf(ofname,"%s_%03d_%02d_%03d",fname.c_str(),i,_glist[i]._sess,_glist[i]._sindx); NEWIMAGE::write_volume(*(_slist[_glist[i]._sess].SPtr(_glist[i]._sindx)),ofname); } for (unsigned int i=0; i<_mptrs.size(); i++) { sprintf(ofname,"%s_mean_%02d",fname.c_str(),i); NEWIMAGE::write_volume(*(_mptrs[i]),ofname); } } EddyCatch void fmriPredictor::WriteMetaData(const std::string& fname) const EddyTry { if (!IsPopulated() || !_lak) throw EddyException("fmriPredictor::WriteMetaData: Not yet fully populated"); char ofname[256]; for (unsigned int i=0; i<_slist.size(); i++) { sprintf(ofname,"%s_%02d",fname.c_str(),i); _Kmats[i]->Write(ofname); } } EddyCatch void fmriPredictor::mean_correct() EddyTry { NEWIMAGE::volume mean = *_slist[_glist[0]._sess].SPtr(_glist[0]._sindx); for (unsigned int li=0; li<_slist.size(); li++) { mean = *_slist[li].SPtr(0); for (unsigned int i=1; i<_slist[li].Size(); i++) mean += *_slist[li].SPtr(i); mean /= static_cast(_slist[li].Size()); for (unsigned int i=0; i<_slist[li].Size(); i++) *_slist[li].SPtr(i) -= mean; } } EddyCatch bool fmriPredictor::is_populated() const EddyTry { for (unsigned int i=0; i<_glist.size(); i++) if (_glist[i]._sess < 0) return(false); return(true); } EddyCatch bool fmriPredictor::same_no_of_scans_in_all_sessions() const EddyTry { for (unsigned int i=1; i<_slist.size(); i++) if (_slist[i].Size() != _slist[0].Size()) return(false); return(true); } EddyCatch /****************************************************************//** * * This ensures that the entries in the _slist are sorted in ascending * order of "global index". But when reorganising the _slist we also * need to make sure that the entries in the _glist still points to * the right _slist entries. * ********************************************************************/ bool fmriPredictor::lists_are_kosher() EddyTry { if (!_lak) { // First sort the entries in the _slists with the _gindx as key for (unsigned int li=0; li<_slist.size(); li++) _slist[li].SortByGndx(); // Then make sure that _glist and _slist are consistent for (unsigned int li=0; li<_slist.size(); li++) { for (unsigned int i=0; i<_slist[li].Size(); i++) { if (_glist[_slist[li].Gndx(i)]._sess != li) return(false); _glist[_slist[li].Gndx(i)]._sindx = i; } } // Then do a little sanity check of _glist for (unsigned int i=0; i<_glist.size(); i++) { if (_glist[i]._sess >= _slist.size() || _glist[i]._sindx >= _slist[_glist[i]._sess].Size()) return(false); } // And finally check that _glist don't have duplicates if (glist_has_duplicates()) return(false); } return(true); } EddyCatch bool fmriPredictor::glist_has_duplicates() const EddyTry { for (unsigned int i=0; i<_glist.size()-1; i++) { for (unsigned int j=i+1; j<_glist.size(); j++) { if (_glist[i]._sess == _glist[j]._sess && _glist[i]._sindx == _glist[j]._sindx) return(true); } } return(false); } EddyCatch void fmriPredictor::predict_image_cpu(// Input unsigned int indx, bool exclude, const arma::rowvec& pv, // Output NEWIMAGE::volume& pi) const EddyTry { unsigned int ys = (exclude) ? _slist[_glist[indx]._sess].Size()-1 : _slist[_glist[indx]._sess].Size(); arma::colvec y(ys); pi = *_mptrs[_glist[indx]._sess]; for (int k=0; k(arma::as_scalar(pv*y)); else pi(i,j,k) = 0.0; } } } } EddyCatch void fmriPredictor::predict_images_cpu(// Input const std::vector& indicies, bool exclude, const std::vector& pvecs, // Output std::vector >& pi) const EddyTry { if (indicies.size() != pvecs.size() || indicies.size() != pi.size()) { throw EDDY::EddyException("fmriPredictor::predict_images_cpu: mismatch among indicies, pvecs and pi"); } for (unsigned int i=0; i > >& sptrl = _slist[_glist[indx]._sess].SPtr_list(); unsigned int sindx = _glist[indx]._sindx; for (unsigned int t=0, tt=0; t indicies(this->Size()); std::iota(indicies.begin(),indicies.end(),0); std::sort(indicies.begin(),indicies.end(), [this](int a, int b){ return(this->Gndx(a) < this->Gndx(b)); }); std::vector > > sptr_lst(this->Size()); std::vector gndx_lst(this->Size()); for (unsigned int i=0; iSize(); i++) { sptr_lst[i] = this->_sptr_lst[indicies[i]]; gndx_lst[i] = this->_gndx_lst[indicies[i]]; } this->_sptr_lst = sptr_lst; this->_gndx_lst = gndx_lst; } EddyCatch