/*! \file PostEddyAlignShellsFunctions.cpp \brief Contains some high level global functions used to align shells to each other and to b0 after the "main eddy" has completed. */ #include #include #include #include #include #include #include #include "armawrap/newmat.h" #include "topup/topup_file_io.h" #include "EddyCommandLineOptions.h" #include "PostEddyCF.h" #include "PostEddyAlignShellsFunctions.h" #ifdef COMPILE_GPU #include "cuda/EddyGpuUtils.h" #endif using namespace std; namespace EDDY { void PEASUtils::PostEddyAlignShells(// Input const EddyCommandLineOptions& clo, bool upe, // Update parameter estimates or not // Input/Output ECScanManager& sm) EddyTry { std::vector grpb; std::vector mi_mov_par; std::vector mi_mp_for_updates; std::vector > mi_cmov_par; std::vector b0_mov_par; std::vector b0_mp_for_updates; if (sm.B0sAreUsefulForPEAS()) { if (clo.VeryVerbose()) cout << "Using interspersed b0's to estimate between shell movement" << endl; PEASUtils::align_shells_using_interspersed_B0_scans(clo,sm,grpb,b0_mov_par,b0_mp_for_updates); } if (clo.VeryVerbose()) cout << "Using MI to estimate between shell movement" << endl; PEASUtils::align_shells_using_MI(clo,false,sm,grpb,mi_mov_par,mi_cmov_par,mi_mp_for_updates); // Align based on MI between shell means // Write report PEASUtils::write_post_eddy_align_shells_report(mi_mov_par,mi_mp_for_updates,mi_cmov_par,b0_mov_par,b0_mp_for_updates,grpb,upe,clo); // Update parameter estimates if requested if (upe) { std::vector mp_for_updates; if (clo.UseB0sToAlignShellsPostEddy()) mp_for_updates = b0_mp_for_updates; else mp_for_updates = mi_mp_for_updates; std::vector > dwi_indx = sm.GetShellIndicies(grpb); for (unsigned int i=0; i grpb; std::vector mi_mov_par; std::vector mi_mp_for_updates; std::vector > mi_cmov_par; if (clo.VeryVerbose()) cout << "Using MI to estimate between shell translation along PE" << endl; PEASUtils::align_shells_using_MI(clo,true,sm,grpb,mi_mov_par,mi_cmov_par,mi_mp_for_updates); // Align based on MI between shell means // Write report PEASUtils::write_post_eddy_align_shells_along_PE_report(mi_mov_par,mi_mp_for_updates,mi_cmov_par,grpb,upe,clo); // Update parameter estimates if requested if (upe) { std::vector > dwi_indx = sm.GetShellIndicies(grpb); for (unsigned int i=0; i& n, const std::vector& first, const std::vector& last, const std::string& bfname) EddyTry { // Get mean b0 volume NEWIMAGE::volume b0_mask = sm.Scan(0,ScanType::Any).GetIma(); b0_mask=1.0; std::vector b0_indx = sm.GetB0Indicies(); NEWIMAGE::volume b0_mean = PEASUtils::get_mean_scan(clo,sm,b0_indx,b0_mask); // NEWIMAGE::write_volume(b0_mean,"mean_b0_volume"); // Get mean volumes for all shells if (clo.VeryVerbose()) cout << "Calculating shell means" << endl; std::vector > dwi_means; std::vector grpb; NEWIMAGE::volume mask = sm.Scan(0,ScanType::Any).GetIma(); mask=1.0; if (sm.IsShelled()) { // Get shell means std::vector > dwi_indx = sm.GetShellIndicies(grpb); dwi_means.resize(dwi_indx.size()); NEWIMAGE::volume tmpmask; for (unsigned int i=0; i(std::round(grpb[i]/100.0))); write_between_shell_MI_values(b0_mean,dwi_means[i],mask,ofname,n,first,last); } } EddyCatch /****************************************************************//** * * A static and private function in PEASUtils * \param[in] clo Command line options * \param[in] sm Collection of all scans. * \param[in] indx List of indices that the mean should be based on. * \param[out] mask A mask for which the mean is valid. * \return A mean volume * ********************************************************************/ NEWIMAGE::volume PEASUtils::get_mean_scan(// Input const EddyCommandLineOptions& clo, const ECScanManager& sm, const std::vector& indx, // Output NEWIMAGE::volume& mask) EddyTry { NEWIMAGE::volume mean = sm.Scan(0,ScanType::Any).GetIma(); mean = 0.0; mask = sm.Scan(0,ScanType::Any).GetIma(); EddyUtils::SetTrilinearInterp(mask); mask = 1.0; if (clo.VeryVerbose()) cout << "Calculating shell means"; if (clo.VeryVerbose()) cout << endl << "Scan: " << endl; #ifdef COMPILE_GPU NEWIMAGE::volume tmpmask = sm.Scan(0,ScanType::Any).GetIma(); EddyUtils::SetTrilinearInterp(tmpmask); for (unsigned int i=0; i tmpmask = sm.Scan(0,ScanType::Any).GetIma(); EddyUtils::SetTrilinearInterp(tmpmask); tmpmask = 1.0; mean += sm.GetUnwarpedScan(indx[i],tmpmask,ScanType::Any); mask *= tmpmask; } } else { // We are to run multi-threaded // Decide number of volumes per thread std::vector nvols = EddyUtils::ScansPerThread(indx.size(),clo.NumberOfThreads()); // Next spawn threads to do the calculations std::vector threads(clo.NumberOfThreads()-1); // + main thread makes clo.NumberOfThreads() for (unsigned int i=0; i& indx, bool very_verbose, // Output NEWIMAGE::volume& mean, NEWIMAGE::volume& mask) EddyTry { static std::mutex cout_mutex; static std::mutex add_mutex; static std::mutex mul_mutex; for (unsigned int i=first_index; i tmpmask = sm.Scan(0,ScanType::Any).GetIma(); EddyUtils::SetTrilinearInterp(tmpmask); tmpmask = 1.0; NEWIMAGE::volume tmpima = sm.GetUnwarpedScan(indx[i],tmpmask,ScanType::Any); add_mutex.lock(); mean += tmpima; add_mutex.unlock(); mul_mutex.lock(); mask *= tmpmask; mul_mutex.unlock(); } } EddyCatch NEWMAT::ColumnVector PEASUtils::register_volumes(// Input const NEWIMAGE::volume& ref, const NEWIMAGE::volume& ima, const NEWIMAGE::volume& mask, // Output NEWIMAGE::volume& rima) EddyTry { EddyUtils::SetSplineInterp(ima); EddyUtils::SetTrilinearInterp(mask); MISCMATHS::NonlinParam nlpar(6,MISCMATHS::NL_NM); // Set starting simplex to 1mm and 1 degree. NEWMAT::ColumnVector ss(6); ss=1.0; for (int i=3; i<6; i++) ss(i+1) = 3.1415 / 180.0; nlpar.SetStartAmoeba(ss); // Make MI cost-function object with 256 bins // 256 is MJ's recommendation, and also looks the smoothest in my testing. PostEddyCF ecf(ref,ima,mask,256); // Run optimisation MISCMATHS::nonlin(nlpar,ecf); rima = ecf.GetTransformedIma(nlpar.Par()); return(nlpar.Par()); } EddyCatch NEWMAT::ColumnVector PEASUtils::register_volumes_along_PE(// Input const NEWIMAGE::volume& ref, const NEWIMAGE::volume& ima, const NEWIMAGE::volume& mask, unsigned int pe_dir, // Output NEWIMAGE::volume& rima) EddyTry { EddyUtils::SetSplineInterp(ima); EddyUtils::SetTrilinearInterp(mask); MISCMATHS::NonlinParam nlpar(1,MISCMATHS::NL_NM); // Set starting simplex (actually just a line in this case) to 1mm NEWMAT::ColumnVector ss(1); ss(1)=1.0; nlpar.SetStartAmoeba(ss); // Make MI cost-function object with 256 bins // 256 is MJ's recommendation, and also looks the smoothest in my testing. PostEddyCF ecf(ref,ima,mask,256,pe_dir); // Run optimisation MISCMATHS::nonlin(nlpar,ecf); rima = ecf.GetTransformedIma(nlpar.Par()); return(nlpar.Par()); } EddyCatch void PEASUtils::register_volumes_wrapper(// Input unsigned int first_reg, unsigned int last_reg, const std::vector >& regs, bool pe_only, unsigned int pe_dir, const NEWIMAGE::volume& b0, const std::vector >& dwis, const NEWIMAGE::volume& mask, const std::vector& grpb, bool vv, // Output std::vector& mov_par, std::vector >& cmov_par) EddyTry { static std::mutex cout_mutex; NEWIMAGE::volume rima; for (unsigned int i=first_reg; i lock(cout_mutex); cout << "Registered shell " << grpb[regs[i].second] << " along PE to b0" << endl << std::flush; cout << "PE-translation = " << PEASUtils::format_mp(mov_par[regs[i].second]) << endl << std::flush; } } else { // If we are to register shells to each other cmov_par[regs[i].first][regs[i].second] = PEASUtils::register_volumes_along_PE(dwis[regs[i].first],dwis[regs[i].second],mask,pe_dir,rima); if (vv) { // If very verbose const std::lock_guard lock(cout_mutex); cout << "Registered shell " << regs[i].second << " along PE to shell " << regs[i].first << endl << std::flush; cout << "Registered shell " << grpb[regs[i].second] << " along PE to shell " << grpb[regs[i].first] << endl << std::flush; cout << "PE-translation = " << PEASUtils::format_mp(cmov_par[regs[i].first][regs[i].second]) << endl << std::flush; } } } else { // We are to do a full rigid body registration if (regs[i].first < 0) { // We are to register to b0 mov_par[regs[i].second] = PEASUtils::register_volumes(b0,dwis[regs[i].second],mask,rima); if (vv) { // If very verbose const std::lock_guard lock(cout_mutex); cout << "Registered shell " << grpb[regs[i].second] << " along PE to b0" << endl << std::flush; cout << "Movement parameters: " << PEASUtils::format_mp(mov_par[regs[i].second]) << endl << std::flush; } } else { // If we are to register shells to each other cmov_par[regs[i].first][regs[i].second] = PEASUtils::register_volumes(dwis[regs[i].first],dwis[regs[i].second],mask,rima); if (vv) { // If very verbose const std::lock_guard lock(cout_mutex); cout << "Registered shell " << regs[i].second << " along PE to shell " << regs[i].first << endl << std::flush; cout << "Registered shell " << grpb[regs[i].second] << " along PE to shell " << grpb[regs[i].first] << endl << std::flush; cout << "Movement parameters: " << PEASUtils::format_mp(cmov_par[regs[i].first][regs[i].second]) << endl << std::flush; } } } } } EddyCatch std::string PEASUtils::format_mp(const NEWMAT::ColumnVector& mp) EddyTry { char str[256]; if (mp.Nrows() == 1) { sprintf(str,"%5.2fmm",mp(1)); } else if (mp.Nrows() == 6) { sprintf(str,"xt = %5.2fmm, yt = %5.2fmm, zt = %5.2fmm, xr = %5.2fdeg, yr = %5.2fdeg, zr = %5.2fdeg",mp(1),mp(2),mp(3),180*mp(4)/M_PI,180*mp(5)/M_PI,180*mp(6)/M_PI); } else throw EddyException("EDDY::PEASUtils::format_mp: mp must have 1 or 6 elements"); return(std::string(str)); } EddyCatch std::vector PEASUtils::collate_mov_par_estimates_for_use(const std::vector& mp, const std::vector >& cmp, const NEWIMAGE::volume& ima) EddyTry { /* // This bit of code combines estimates of lowest b-val shell to // b0 with estimates from all other shells to lowest b-val std::vector omp = mp; for (unsigned int i=1; i omp = mp; /* // This bit of code uses the direct estimates of the lowest shell to b0 // and then assumes that the shells are already aligned to each other. std::vector omp = mp; for (unsigned int i=1; i& mi_dmp, const std::vector& mi_ump, const std::vector >& mi_cmp, const std::vector& b0_dmp, const std::vector& b0_ump, const std::vector& grpb, bool upe, const EddyCommandLineOptions& clo) EddyTry { try { std::ofstream file; double pi = 3.14159; file.open(clo.PeasReportFname().c_str(),ios::out|ios::trunc); file << "These between shell parameters were calculated using MI between shell means" << endl; file << setprecision(3) << "Movement parameters relative b0-shell from direct registration to mean b0" << endl; for (unsigned int i=0; i ump; if (clo.UseB0sToAlignShellsPostEddy()) ump = b0_ump; else ump = mi_ump; for (unsigned int i=0; i& mi_dmp, const std::vector& mi_ump, const std::vector >& mi_cmp, const std::vector& grpb, bool upe, const EddyCommandLineOptions& clo) EddyTry { try { std::ofstream file; file.open(clo.PeasAlongPEReportFname().c_str(),ios::out|ios::trunc); file << "These between shell PE-translations were calculated using MI between shell means" << endl; file << setprecision(3) << "PE-translations (mm) relative b0-shell from direct registration to mean b0" << endl; for (unsigned int i=0; i& indx, // Input/output ECScanManager& sm) EddyTry { NEWMAT::Matrix A; if (mp.Nrows()==1) { NEWMAT::ColumnVector tmp(6); tmp = 0.0; if (sm.HasPEinX()) tmp(1) = mp(1); else tmp(2) = mp(1); A = TOPUP::MovePar2Matrix(tmp,sm.Scan(0,ScanType::Any).GetIma()); } else if (mp.Nrows()==6) A = TOPUP::MovePar2Matrix(mp,sm.Scan(0,ScanType::Any).GetIma()); else throw EddyException("EDDY::PostEddyCFImpl::GetTransformedIma: size of p must be 1 or 6"); for (unsigned int i=0; i& grpb, std::vector& mov_par, std::vector >& cmov_par, std::vector& mp_for_updates) EddyTry { std::vector > dwi_indx; // Get mean b0 volume NEWIMAGE::volume b0_mask = sm.Scan(0,ScanType::Any).GetIma(); b0_mask=1.0; std::vector b0_indx = sm.GetB0Indicies(); NEWIMAGE::volume b0_mean = PEASUtils::get_mean_scan(clo,sm,b0_indx,b0_mask); // NEWIMAGE::write_volume(b0_mean,"mean_b0_volume"); // Get mean volumes for all shells if (clo.VeryVerbose()) cout << "Calculating shell means" << endl; std::vector > dwi_means; NEWIMAGE::volume mask = sm.Scan(0,ScanType::Any).GetIma(); mask=1.0; if (sm.IsShelled()) { // Get shell means dwi_indx = sm.GetShellIndicies(grpb); dwi_means.resize(dwi_indx.size()); NEWIMAGE::volume tmpmask; for (unsigned int i=0; i rima; for (unsigned int i=0; i 1 && dwi_means.size() > 1) { // Decide number of registrations and number of threads unsigned int num_regs = dwi_means.size() + (dwi_means.size()-1)*dwi_means.size()/2u; unsigned int num_threads = std::min(clo.NumberOfThreads(),num_regs); // Specify all registrations, where the index -1 is used to indicate the b0 // as target for the registration. pairs are std::vector > regs(num_regs); // Registrations to b0. for (unsigned int i=0; i(-1,i); cmov_par[i].resize(dwi_means.size()); } // Registrations of dwi shells to each other. unsigned int cntr = dwi_means.size(); for (unsigned int i=0; i(i,j); } // Decide number of registrations for each thread std::vector nregs_pt = EddyUtils::ScansPerThread(num_regs,num_threads); // Next spawn threads to do the calculations std::vector threads(num_threads-1); unsigned int pe_dir = sm.HasPEinX() ? 0 : 1; for (unsigned int i=0; i& ref, const NEWIMAGE::volume& ima, const NEWIMAGE::volume& mask, const std::string& fname, const std::vector& n, const std::vector& first, const std::vector& last) EddyTry { EddyUtils::SetSplineInterp(ima); EddyUtils::SetTrilinearInterp(mask); PostEddyCF ecf(ref,ima,mask,256); std::ofstream file; double pi = 3.14159; file.open(fname,ios::out|ios::trunc); file << setprecision(8); NEWMAT::ColumnVector mp(6); for (unsigned int xti=0; xti1) ? first[0] + xti * ((last[0]-first[0]) / (n[0]-1)) : first[0]; for (unsigned int yti=0; yti1) ? first[1] + yti * ((last[1]-first[1]) / (n[1]-1)) : first[1]; for (unsigned int zti=0; zti1) ? first[2] + zti * ((last[2]-first[2]) / (n[2]-1)) : first[2]; for (unsigned int xri=0; xri1) ? first[3] + xri * ((last[3]-first[3]) / (n[3]-1)) : first[3]; mp(4) = pi * mp(4) / 180.0; for (unsigned int yri=0; yri1) ? first[4] + yri * ((last[4]-first[4]) / (n[4]-1)) : first[4]; mp(5) = pi * mp(5) / 180.0; for (unsigned int zri=0; zri1) ? first[5] + zri * ((last[5]-first[5]) / (n[5]-1)) : first[5]; mp(6) = pi * mp(6) / 180.0; file << setw(16) << mp(1) << setw(16) << mp(2) << setw(16) << mp(3); file << setw(16) << 180.0*mp(4)/pi << setw(16) << 180.0*mp(5)/pi << setw(16) << 180.0*mp(6)/pi; file << setw(16) << ecf.cf(mp) << endl; } } } } } } return; } EddyCatch void PEASUtils::align_shells_using_interspersed_B0_scans(// Input const EddyCommandLineOptions& clo, // Input/Output ECScanManager& sm, // Output std::vector& grpb, std::vector& mov_par, std::vector& mp_for_updates) EddyTry { std::vector dpv = sm.GetDiffParas(); std::vector grpi; // A vector with a group index for each Scan in sm. Group 0 pertains to b0. EddyUtils::GetGroups(dpv,grpi,grpb); // N.B. This version of grpb has not had the b0 removed std::vector b0_indx = sm.GetB0Indicies(); mov_par.resize(grpb.size()-1); for (unsigned int i=0; i