/*! \file eddy.cpp \brief Contains main() and some very high level functions for eddy */ #if __cplusplus >= 201103L || __clang__ #include using std::array; #else #include using std::tr1::array; #endif #include #include #include #include #include #include #include "nlohmann/json.hpp" #include "armawrap/newmat.h" #include "newimage/newimageall.h" #include "miscmaths/miscmaths.h" #include "utils/stack_dump.h" #include "utils/FSLProfiler.h" #include "EddyHelperClasses.h" #include "ECScanClasses.h" #include "DiffusionGP.h" #include "b0Predictor.h" #include "fmriPredictor.h" #include "EddyUtils.h" #include "EddyCommandLineOptions.h" #include "PostEddyCF.h" #include "PostEddyAlignShellsFunctions.h" #include "MoveBySuscCF.h" #include "BiasFieldEstimator.h" #include "eddy.h" #ifdef COMPILE_GPU #include "cuda/GpuPredictorChunk.h" #include "cuda/EddyGpuUtils.h" #endif namespace EDDY { /// A struct/class that gathers nlohmann::json objects from the various file writing functions. /// These are then written out as a single .json file. Longer term the plan is to move away /// from writing individual text files in favour of a single .json file. It is declared here /// as part of hiding nlohmann from nvcc. class json_out_struct { public: nlohmann::json shell_indicies; nlohmann::json outlier_report; std::vector outlier_maps; nlohmann::json parameters; nlohmann::json movement_over_time; nlohmann::json movement_rms; nlohmann::json restricted_movement_rms; nlohmann::json long_ec_parameters; void Write(const std::string& fname) const; }; } // End namespace EDDY using namespace std; using namespace EDDY; /// The entry point of eddy. int main(int argc, char *argv[]) try { StackDump::Install(); // Gives us informative stack dump if/when program crashes // Parse comand line input EddyCommandLineOptions clo(argc,argv); // Command Line Options // Set range of b-values considered to be in the same shell if (clo.IsDiffusion()) EddyUtils::SetbRange(clo.BValueRange()); // Prime profiler if requested by user if (clo.LogTimings()) Utilities::FSLProfiler::SetProfilingOn(clo.LoggerFname()); Utilities::FSLProfiler prof("_"+string(__FILE__)+"_"+string(__func__)); double total_key = prof.StartEntry("Begins eddy run"); EDDY::json_out_struct json_out; // Structure for gathering json output objects. // Read all available info if (clo.Verbose()) cout << "Reading images" << endl; ECScanManager sm = (clo.IsDiffusion()) ? // Conditional assignment because ECScanManager has no default constructor ECScanManager(clo.ImaFname(),clo.MaskFname(),clo.AcqpFname(),clo.TopupFname(), clo.FieldFname(),clo.FieldMatFname(),clo.BVecsFname(),clo.BValsFname(), clo.BDeltasFname(),clo.RepetitionTime(),clo.EchoTime(),clo.FirstLevelModel(), clo.b0_FirstLevelModel(),clo.LongECModel(),clo.Indicies(),clo.SessionIndicies(), clo.PolationParameters(),clo.MultiBand(),clo.DontCheckShelling()) // Scan Manager for diffusion : // If clo.IsDiffusion() is false ECScanManager(clo.ImaFname(),clo.MaskFname(),clo.AcqpFname(),clo.TopupFname(), clo.FieldFname(),clo.FieldMatFname(),clo.RepetitionTime(), clo.EchoTime(),clo.Indicies(),clo.SessionIndicies(), clo.PolationParameters(),clo.MultiBand()); // Scan Manager for fmri if (clo.FillEmptyPlanes()) { if (clo.Verbose()) cout << "Filling empty planes" << endl; sm.FillEmptyPlanes(); } if (clo.ResamplingMethod() == FinalResamplingType::LSR) { if (!sm.CanDoLSRResampling()) throw EddyException("These data do not support least-squares resampling"); } if (clo.UseB0sToAlignShellsPostEddy() && !sm.B0sAreUsefulForPEAS()) { throw EddyException("These data do not support using b0s for Post Eddy Alignment of Shells"); } if (clo.RefScanNumber()) sm.SetLocationReference(clo.RefScanNumber()); // Write .json file with shell indicies for use by EddyQC and debugging if (clo.IsDiffusion()) json_out.shell_indicies = sm.WriteShellIndicies(clo.ShellIndexOutFname()); // Write topup-field if debug flag is set if (clo.DebugLevel() && sm.HasSuscHzOffResField()) { std::string fname = "EDDY_DEBUG_susc_00_0000"; NEWIMAGE::write_volume(*(sm.GetSuscHzOffResField()),fname); } // Set initial parameters. This option is only for testing/debugging/personal use if (clo.InitFname() != std::string("")) { if (clo.RegisterDWI() && clo.Registerb0()) sm.SetParameters(clo.InitFname(),ScanType::Any); else if (clo.RegisterDWI()) sm.SetParameters(clo.InitFname(),ScanType::DWI); else sm.SetParameters(clo.InitFname(),ScanType::b0); } // Do the registration ////////////////////////////////////////////////////////////////////// // The first, and possibly only, registration step is volume_to_volume ////////////////////////////////////////////////////////////////////// double vol_key = prof.StartEntry("Calling DoVolumeToVolumeRegistration"); if (clo.Verbose()) cout << "Performing volume-to-volume registration" << endl; ReplacementManager *dwi_rm=NULL; if (clo.EstimateMoveBySusc()) { // Restrict EC if we are to eventually estimate MBS EDDY::SecondLevelECModelType b0_slm = clo.b0_SecondLevelModel(); EDDY::SecondLevelECModelType dwi_slm = clo.SecondLevelModel(); if (clo.VeryVerbose()) cout << "Setting linear second level model" << endl; clo.Set_b0_SecondLevelModel(SecondLevelECModelType::Linear); clo.SetSecondLevelModel(SecondLevelECModelType::Linear); dwi_rm = DoVolumeToVolumeRegistration(clo,sm); if (clo.VeryVerbose()) cout << "Resetting second level model" << endl; clo.Set_b0_SecondLevelModel(b0_slm); clo.SetSecondLevelModel(dwi_slm); } else dwi_rm = DoVolumeToVolumeRegistration(clo,sm); prof.EndEntry(vol_key); // The remaining steps we only run if registration was actually run, // i.e. not if we read an initialisation file and did zero iterations. if (clo.NIter() > 0) { sm.ApplyLocationReference(); // Write text-file with MI values if requested (testing/debugging) if (clo.PrintMIValues()) { if (clo.Verbose()) cout << "Writing MI values between shells" << endl; PrintMIValues(clo,sm,clo.MIPrintFname(),clo.PrintMIPlanes()); } // Check for residual position differences between shells if (clo.RegisterDWI()) { double peas_key = prof.StartEntry("Calling PostEddyAlignShells"); if (!clo.SeparateOffsetFromMovement() && !clo.AlignShellsPostEddy()) { if (clo.Verbose()) cout << "Checking shell alignment along PE-direction (running PostEddyAlignShellsAlongPE)" << endl; PEASUtils::PostEddyAlignShellsAlongPE(clo,false,sm); if (clo.Verbose()) cout << "Checking shell alignment (running PostEddyAlignShells)" << endl; PEASUtils::PostEddyAlignShells(clo,false,sm); } else if (clo.SeparateOffsetFromMovement() && !clo.AlignShellsPostEddy()) { if (clo.Verbose()) cout << "Aligning shells along PE-direction (running PostEddyAlignShellsAlongPE)" << endl; PEASUtils::PostEddyAlignShellsAlongPE(clo,true,sm); if (clo.Verbose()) cout << "Checking shell alignment (running PostEddyAlignShells)" << endl; PEASUtils::PostEddyAlignShells(clo,false,sm); } else if (clo.AlignShellsPostEddy()) { if (clo.Verbose()) cout << "Checking shell alignment along PE-direction (running PostEddyAlignShellsAlongPE)" << endl; PEASUtils::PostEddyAlignShellsAlongPE(clo,false,sm); if (clo.Verbose()) cout << "Aligning shells (running PostEddyAlignShells)" << endl; PEASUtils::PostEddyAlignShells(clo,true,sm); } prof.EndEntry(peas_key); } } ////////////////////////////////////////////////////////////////////// // Next, estimate long time-constant EC if requested ////////////////////////////////////////////////////////////////////// bool original_sep_offs_move = clo.SeparateOffsetFromMovement(); if (clo.EstimateLongEC()) { double longEC_key = prof.StartEntry("Calling EstimateLongEC"); if (clo.Verbose()) { cout << "Estimating long time-constant eddy currents" << endl; cout << "Using model: " << clo.LongECModelString() << endl; if (clo.ReestimateECWhenEstimatingLongEC()) { if (clo.Verbose()) cout << "Also jointly re-estimating EC and movement parameters" << endl; } } // The long-EC estimates _should_ break the very strong covariance between estimates // of EC-DC-component and subject movement along PE-direction. There is therefore an // option to "turn off" the attempted separation of EC-DC and subject movement at this // point. N.B. that it will remain turned off after this point. But we will still align // different shells along PE. Messy and ugly, and should be refactored at some point. // I leave it like this for now to avoid changing the interface (breaking scripts). if (!clo.SeparateOffsetFromMovementWhenEstimatingLongEC()) { clo.SetSeparateOffsetFromMovement(false); } dwi_rm = EstimateLongEC(clo,sm,dwi_rm); prof.EndEntry(longEC_key); sm.ApplyLocationReference(); // Check for residual position differences between shells if (clo.RegisterDWI()) { double peas_key = prof.StartEntry("Calling PostEddyAlignShells"); if (!original_sep_offs_move && !clo.AlignShellsPostEddy()) { if (clo.Verbose()) cout << "Checking shell alignment along PE-direction (running PostEddyAlignShellsAlongPE)" << endl; PEASUtils::PostEddyAlignShellsAlongPE(clo,false,sm); if (clo.Verbose()) cout << "Checking shell alignment (running PostEddyAlignShells)" << endl; PEASUtils::PostEddyAlignShells(clo,false,sm); } else if (original_sep_offs_move && !clo.AlignShellsPostEddy()) { if (clo.Verbose()) cout << "Aligning shells along PE-direction (running PostEddyAlignShellsAlongPE)" << endl; PEASUtils::PostEddyAlignShellsAlongPE(clo,true,sm); if (clo.Verbose()) cout << "Checking shell alignment (running PostEddyAlignShells)" << endl; PEASUtils::PostEddyAlignShells(clo,false,sm); } else if (clo.AlignShellsPostEddy()) { if (clo.Verbose()) cout << "Checking shell alignment along PE-direction (running PostEddyAlignShellsAlongPE)" << endl; PEASUtils::PostEddyAlignShellsAlongPE(clo,false,sm); if (clo.Verbose()) cout << "Aligning shells (running PostEddyAlignShells)" << endl; PEASUtils::PostEddyAlignShells(clo,true,sm); } prof.EndEntry(peas_key); } } ////////////////////////////////////////////////////////////////////// // Next do a first round of estimating a bias field if requested ////////////////////////////////////////////////////////////////////// // EstimateBiasField(clo,10.0,1.0,sm); // std::shared_ptr > bfield = sm.GetBiasField(); // exit(EXIT_SUCCESS); // Set initial slice-to-vol parameters. This option is only for testing/debugging/personal use if (clo.IsSliceToVol() && clo.InitS2VFname() != std::string("")) { sm.SetMovementModelOrder(clo.MovementModelOrder(0)); if (clo.RegisterDWI() && clo.Registerb0()) sm.SetS2VMovement(clo.InitS2VFname(),ScanType::Any); else if (clo.RegisterDWI()) sm.SetS2VMovement(clo.InitS2VFname(),ScanType::DWI); else sm.SetS2VMovement(clo.InitS2VFname(),ScanType::b0); } ////////////////////////////////////////////////////////////////////// // Next do the slice-to-vol registration if requested. ////////////////////////////////////////////////////////////////////// if (clo.IsSliceToVol() && clo.S2V_NIter(0) > 0 && clo.InitS2VFname() == std::string("")) { double s2v_key = prof.StartEntry("Calling DoSliceToVolumeRegistration"); if (clo.Verbose()) cout << "Performing slice-to-volume registration" << endl; if (clo.EstimateMoveBySusc()) { // Restrict EC if we are to eventually estimate MBS SecondLevelECModelType b0_slm = clo.b0_SecondLevelModel(); SecondLevelECModelType dwi_slm = clo.SecondLevelModel(); clo.Set_b0_SecondLevelModel(SecondLevelECModelType::Linear); clo.SetSecondLevelModel(SecondLevelECModelType::Linear); for (unsigned int i=0; i mbs_init; NEWIMAGE::read_volume4D(mbs_init,clo.InitMBSFname()); for (unsigned int i=0; i0) { sm.SetUseB0sToInformDWIRegistration(false); // So as to preserve current estimates std::vector b0s; std::vector dwis; if (clo.IsSliceToVol()) { // Use only "steady" volumes if we know who they are EDDY::s2vQuant s2vq(sm,1.0,1.0); // "Steadiness hardcoded to 1mm and 1degree if (clo.Registerb0()) b0s = s2vq.FindStillVolumes(ScanType::b0,clo.MoveBySuscParam()); if (clo.RegisterDWI()) dwis = s2vq.FindStillVolumes(ScanType::DWI,clo.MoveBySuscParam()); } else { // Otherwise use all volumes if (clo.Registerb0()) { b0s.resize(sm.NScans(ScanType::b0)); for (unsigned int i=0; i(1,0.0)); } NEWMAT::ColumnVector spar; EDDY::MoveBySuscCF cf(sm,clo,b0s,dwis,clo.MoveBySuscParam(),clo.MoveBySuscOrder(),clo.MoveBySuscKsp()); for (unsigned int i=0; i 1) cf.WriteSecondOrderFields(clo.MoveBySuscSecondOrderFname()); } else { // Just do a straightforward MBS estimation // Make cost-function object for movement-by-susceptibility EDDY::MoveBySuscCF cf(sm,clo,b0s,dwis,clo.MoveBySuscParam(),clo.MoveBySuscOrder(),clo.MoveBySuscKsp()); NEWMAT::ColumnVector spar = cf.Par(); // Start guesses (all zeros); cf.SetLambda(clo.MoveBySuscLambda()); MISCMATHS::NonlinParam nlp(cf.NPar(),MISCMATHS::NL_LM,spar); nlp.SetMaxIter(clo.MoveBySuscNiter()); nlp.SetGaussNewtonType(MISCMATHS::LM_GN); double mbs_key = prof.StartEntry("Calling nonlin for MBS"); MISCMATHS::nonlin(nlp,cf); // Estimate prof.EndEntry(mbs_key); cf.WriteFirstOrderFields(clo.MoveBySuscFirstOrderFname()); if (clo.MoveBySuscOrder() > 1) cf.WriteSecondOrderFields(clo.MoveBySuscSecondOrderFname()); } // Do another check for residual position differences in case MBS wrecked things if (clo.RegisterDWI()) { double peas_key = prof.StartEntry("Calling PostEddyAlignShells after MBS"); if (!original_sep_offs_move && !clo.AlignShellsPostEddy()) { if (clo.Verbose()) cout << "Checking shell alignment along PE-direction (running PostEddyAlignShellsAlongPE)" << endl; PEASUtils::PostEddyAlignShellsAlongPE(clo,false,sm); if (clo.Verbose()) cout << "Checking shell alignment (running PostEddyAlignShells)" << endl; PEASUtils::PostEddyAlignShells(clo,false,sm); } else if (original_sep_offs_move && !clo.AlignShellsPostEddy()) { if (clo.Verbose()) cout << "Aligning shells along PE-direction (running PostEddyAlignShellsAlongPE)" << endl; PEASUtils::PostEddyAlignShellsAlongPE(clo,true,sm); if (clo.Verbose()) cout << "Checking shell alignment (running PostEddyAlignShells)" << endl; PEASUtils::PostEddyAlignShells(clo,false,sm); } else if (clo.AlignShellsPostEddy()) { if (clo.Verbose()) cout << "Checking shell alignment along PE-direction (running PostEddyAlignShellsAlongPE)" << endl; PEASUtils::PostEddyAlignShellsAlongPE(clo,false,sm); if (clo.Verbose()) cout << "Aligning shells (running PostEddyAlignShells)" << endl; PEASUtils::PostEddyAlignShells(clo,true,sm); } prof.EndEntry(peas_key); } } ////////////////////////////////////////////////////////////////////// // Do final check (and possibly replacement) of outliers with ff=1 // (very little Q-space smoothing). ////////////////////////////////////////////////////////////////////// if (clo.RegisterDWI()) { if (clo.Verbose()) cout << "Performing final outlier check" << endl; double old_hypar_ff = 1.0; if (clo.HyParFudgeFactor() != 1.0) { old_hypar_ff = clo.HyParFudgeFactor(); clo.SetHyParFudgeFactor(1.0); } double folc_key = prof.StartEntry("Calling FinalOLCheck"); dwi_rm = FinalOLCheck(clo,dwi_rm,sm); prof.EndEntry(folc_key); if (old_hypar_ff != 1.0) clo.SetHyParFudgeFactor(old_hypar_ff); // Write outlier information double wol_key = prof.StartEntry("Writing outlier information"); std::vector i2i = sm.GetDwi2GlobalIndexMapping(); json_out.outlier_report = dwi_rm->WriteReport(i2i,clo.OLReportFname(),!clo.SupressOldStyleTextFiles()); json_out.outlier_maps = dwi_rm->WriteMatrixReport(i2i,sm.NScans(),clo.OLMapReportFname(),clo.OLNStDevMapReportFname(), clo.OLNSqrStDevMapReportFname(),!clo.SupressOldStyleTextFiles()); if (clo.WriteOutlierFreeData()) { if (clo.Verbose()) cout << "Running sm.WriteOutlierFreeData" << endl; sm.WriteOutlierFreeData(clo.OLFreeDataFname()); } prof.EndEntry(wol_key); } // Add rotation. Hidden function. ONLY to be used to // test that rotation of b-vecs does the right thing. if (clo.DoTestRot()) { if (clo.Verbose()) cout << "Running sm.AddRotation" << endl; sm.AddRotation(clo.TestRotAngles()); } // Write registration parameters if (clo.Verbose()) cout << "Running sm.WriteParameterFile" << endl; if (clo.RegisterDWI() && clo.Registerb0()) json_out.parameters = sm.WriteParameterFile(clo.ParOutFname(),ScanType::Any,!clo.SupressOldStyleTextFiles()); else if (clo.RegisterDWI()) json_out.parameters = sm.WriteParameterFile(clo.ParOutFname(),ScanType::DWI,!clo.SupressOldStyleTextFiles()); else json_out.parameters = sm.WriteParameterFile(clo.ParOutFname(),ScanType::b0,!clo.SupressOldStyleTextFiles()); if (clo.EstimateLongEC()) { if (clo.Verbose()) cout << "Running sm.LongTimeConstantECModel().WriteReport" << endl; json_out.long_ec_parameters = sm.LongTimeConstantECModel().WriteReport(sm,clo.LongECOutFname(),!clo.SupressOldStyleTextFiles()); } // Write movement-over-time file if SliceToVol registration was performed if (sm.IsSliceToVol()) { if (clo.Verbose()) cout << "Running sm.WriteMovementOverTimeFile" << endl; if (clo.RegisterDWI() && clo.Registerb0()) json_out.movement_over_time = sm.WriteMovementOverTimeFile(clo.MovementOverTimeOutFname(),ScanType::Any,!clo.SupressOldStyleTextFiles()); else if (clo.RegisterDWI()) json_out.movement_over_time = sm.WriteMovementOverTimeFile(clo.MovementOverTimeOutFname(),ScanType::DWI,!clo.SupressOldStyleTextFiles()); else json_out.movement_over_time = sm.WriteMovementOverTimeFile(clo.MovementOverTimeOutFname(),ScanType::b0,!clo.SupressOldStyleTextFiles()); } // Write registered images if (clo.Verbose()) cout << "Running sm.WriteRegisteredImages" << endl; double wri_key = prof.StartEntry("Writing registered images"); if (!clo.ReplaceOutliers()) { if (clo.Verbose()) { cout << "Running sm.RecycleOutliers" << endl; } sm.RecycleOutliers(); } // Bring back original data if (sm.IsSliceToVol()) { // If we need to get predictions to support the resampling NEWIMAGE::volume4D pred; ScanType st; if (clo.RegisterDWI() && clo.Registerb0()) st=ScanType::Any; else if (clo.RegisterDWI()) st=ScanType::DWI; else st=ScanType::b0; GetPredictionsForResampling(clo,st,sm,pred); // Set resampling in slice direction to spline. EDDY::PolationPara old_pp = sm.GetPolation(); EDDY::PolationPara new_pp = old_pp; if (old_pp.GetS2VInterp() != NEWIMAGE::spline) { new_pp.SetS2VInterp(NEWIMAGE::spline); sm.SetPolation(new_pp); } if (clo.RegisterDWI() && clo.Registerb0()) sm.WriteRegisteredImages(clo.IOutFname(),clo.OutMaskFname(),clo.ResamplingMethod(),clo.LSResamplingLambda(),clo.MaskOutput(),pred,clo.NumberOfThreads()); else if (clo.RegisterDWI()) sm.WriteRegisteredImages(clo.IOutFname(),clo.OutMaskFname(),clo.ResamplingMethod(),clo.LSResamplingLambda(),clo.MaskOutput(),pred,clo.NumberOfThreads(),ScanType::DWI); else sm.WriteRegisteredImages(clo.IOutFname(),clo.OutMaskFname(),clo.ResamplingMethod(),clo.LSResamplingLambda(),clo.MaskOutput(),pred,clo.NumberOfThreads(),ScanType::b0); if (old_pp.GetS2VInterp() != NEWIMAGE::spline) sm.SetPolation(old_pp); } else { if (clo.RegisterDWI() && clo.Registerb0()) sm.WriteRegisteredImages(clo.IOutFname(),clo.OutMaskFname(),clo.ResamplingMethod(),clo.LSResamplingLambda(),clo.MaskOutput(),clo.NumberOfThreads()); else if (clo.RegisterDWI()) sm.WriteRegisteredImages(clo.IOutFname(),clo.OutMaskFname(),clo.ResamplingMethod(),clo.LSResamplingLambda(),clo.MaskOutput(),clo.NumberOfThreads(),ScanType::DWI); else sm.WriteRegisteredImages(clo.IOutFname(),clo.OutMaskFname(),clo.ResamplingMethod(),clo.LSResamplingLambda(),clo.MaskOutput(),clo.NumberOfThreads(),ScanType::b0); } prof.EndEntry(wri_key); // Optionally write out a "data set" that consists of the GP predictions. This // was added to demonstrate to Kings that it makes b***er all difference. if (clo.WritePredictions() || clo.WriteScatterBrainPredictions()) { // First write predictions in model space NEWIMAGE::volume4D pred; EDDY::ScanType st; std::vector hypar; if (clo.RegisterDWI() && clo.Registerb0()) st=ScanType::Any; else if (clo.RegisterDWI()) st=ScanType::DWI; else st=ScanType::b0; if (clo.WritePredictions()) { // Do the "regular" fitting to the GP, but ensure spline interpolation in z if s2v if (clo.Verbose()) cout << "Running EDDY::GetPredictionsForResampling" << endl; double wpred_key = prof.StartEntry("Calculating and writing predictions"); EDDY::PolationPara old_pol = sm.GetPolation(); EDDY::PolationPara new_pol = old_pol; if (clo.IsSliceToVol() && new_pol.GetS2VInterp() != NEWIMAGE::spline) new_pol.SetS2VInterp(NEWIMAGE::spline); sm.SetPolation(new_pol); EddyCommandLineOptions tmp_clo = clo; if (!tmp_clo.RotateBVecsDuringEstimation()) tmp_clo.SetRotateBVecsDuringEstimation(true); hypar = GetPredictionsForResampling(tmp_clo,st,sm,pred); pred /= sm.ScaleFactor(); NEWIMAGE::write_volume(pred,clo.PredictionsOutFname()); sm.SetPolation(old_pol); // Next write predictions in scan space(s) pred = 0.0; hypar = GetPredictionsForResampling(clo,st,sm,pred); pred /= sm.ScaleFactor(); for (int s=0; s pred; ScanType st; if (clo.RegisterDWI() && clo.Registerb0()) st=ScanType::Any; else if (clo.RegisterDWI()) st=ScanType::DWI; else st=ScanType::b0; GetPredictionsForResampling(clo,st,sm,pred); if (clo.RegisterDWI() && clo.Registerb0()) sm.WriteRegisteredImages(clo.AdditionalWithOutliersOutFname(),std::string(""),clo.ResamplingMethod(),clo.LSResamplingLambda(),clo.MaskOutput(),pred,clo.NumberOfThreads()); else if (clo.RegisterDWI()) sm.WriteRegisteredImages(clo.AdditionalWithOutliersOutFname(),std::string(""),clo.ResamplingMethod(),clo.LSResamplingLambda(),clo.MaskOutput(),pred,clo.NumberOfThreads(),ScanType::DWI); else sm.WriteRegisteredImages(clo.AdditionalWithOutliersOutFname(),std::string(""),clo.ResamplingMethod(),clo.LSResamplingLambda(),clo.MaskOutput(),pred,clo.NumberOfThreads(),ScanType::b0); } else { if (clo.RegisterDWI() && clo.Registerb0()) sm.WriteRegisteredImages(clo.AdditionalWithOutliersOutFname(),std::string(""),clo.ResamplingMethod(),clo.LSResamplingLambda(),clo.MaskOutput(),clo.NumberOfThreads()); else if (clo.RegisterDWI()) sm.WriteRegisteredImages(clo.AdditionalWithOutliersOutFname(),std::string(""),clo.ResamplingMethod(),clo.LSResamplingLambda(),clo.MaskOutput(),clo.NumberOfThreads(),ScanType::DWI); else sm.WriteRegisteredImages(clo.AdditionalWithOutliersOutFname(),std::string(""),clo.ResamplingMethod(),clo.LSResamplingLambda(),clo.MaskOutput(),clo.NumberOfThreads(),ScanType::b0); } prof.EndEntry(wri_key); } // Write EC fields if (clo.WriteFields()) { if (clo.Verbose()) cout << "Running sm.WriteECFields" << endl; if (clo.RegisterDWI() && clo.Registerb0()) sm.WriteECFields(clo.ECFOutFname()); else if (clo.RegisterDWI()) sm.WriteECFields(clo.ECFOutFname(),ScanType::DWI); else sm.WriteECFields(clo.ECFOutFname(),ScanType::b0); } // Write rotated b-vecs if (clo.WriteRotatedBVecs()) { if (clo.Verbose()) cout << "Running sm.WriteRotatedBVecs" << endl; sm.WriteRotatedBVecs(clo.RotatedBVecsOutFname()); } // Write b-vecs for LSR resampling if (clo.ResamplingMethod() == FinalResamplingType::LSR) { if (clo.Verbose()) cout << "Running sm.WriteBVecsForLSR" << endl; sm.WriteBVecsForLSR(clo.BVecsLSROutFname(),false); if (clo.WriteRotatedBVecs()) { if (clo.Verbose()) cout << "Running sm.WriteBVecsForLSR with rotation" << endl; sm.WriteBVecsForLSR(clo.RotatedBVecsLSROutFname(),true); } } // Write movement RMS if (clo.WriteMovementRMS()) { double rms_key = prof.StartEntry("Writing RMS"); if (clo.Verbose()) cout << "Running sm.WriteMovementRMS" << endl; if (clo.RegisterDWI() && clo.Registerb0()) { json_out.movement_rms = sm.WriteMovementRMS(clo.RMSOutFname(),ScanType::Any,!clo.SupressOldStyleTextFiles()); json_out.restricted_movement_rms = sm.WriteRestrictedMovementRMS(clo.RestrictedRMSOutFname(),ScanType::Any,!clo.SupressOldStyleTextFiles()); } else if (clo.RegisterDWI()) { json_out.movement_rms = sm.WriteMovementRMS(clo.RMSOutFname(),ScanType::DWI,!clo.SupressOldStyleTextFiles()); json_out.restricted_movement_rms = sm.WriteRestrictedMovementRMS(clo.RestrictedRMSOutFname(),ScanType::DWI,!clo.SupressOldStyleTextFiles()); } else { json_out.movement_rms = sm.WriteMovementRMS(clo.RMSOutFname(),ScanType::b0,!clo.SupressOldStyleTextFiles()); json_out.restricted_movement_rms = sm.WriteRestrictedMovementRMS(clo.RestrictedRMSOutFname(),ScanType::b0,!clo.SupressOldStyleTextFiles()); } prof.EndEntry(rms_key); } // Write the .json file json_out.Write(clo.JsonOutFname()); // Write CNR maps if (clo.WriteCNRMaps() || clo.WriteRangeCNRMaps() || clo.WriteResiduals()) { double cnr_key = prof.StartEntry("Writing CNR maps"); double old_hypar_ff = 1.0; if (clo.HyParFudgeFactor() != 1.0) { old_hypar_ff = clo.HyParFudgeFactor(); clo.SetHyParFudgeFactor(1.0); } if (clo.Verbose()) cout << "Running EDDY::WriteCNRMaps" << endl; WriteCNRMaps(clo,sm,clo.CNROutFname(),clo.RangeCNROutFname(),clo.ResidualsOutFname()); if (old_hypar_ff != 1.0) clo.SetHyParFudgeFactor(old_hypar_ff); prof.EndEntry(cnr_key); } // Write 3D displacement fields if (clo.WriteDisplacementFields()) { if (clo.Verbose()) cout << "Running sm.WriteDisplacementFields" << endl; if (clo.RegisterDWI() && clo.Registerb0()) sm.WriteDisplacementFields(clo.DFieldOutFname()); else if (clo.RegisterDWI()) sm.WriteDisplacementFields(clo.DFieldOutFname(),ScanType::DWI); else sm.WriteDisplacementFields(clo.DFieldOutFname(),ScanType::b0); } prof.EndEntry(total_key); return(EXIT_SUCCESS); } catch(const std::exception& e) { cout << "EDDY::: Eddy failed with message " << e.what() << endl; return(EXIT_FAILURE); } catch(...) { cout << "EDDY::: Eddy failed" << endl; return(EXIT_FAILURE); } namespace EDDY { /****************************************************************//** * * A very high-level global function that registers all the scans * (b0 and dwis) in sm using a volume-to-volume model. * \param[in] clo Carries information about the command line options * that eddy was invoked with. * \param[in,out] sm Collection of all scans. Will be updated by this call. * \return A ptr to ReplacementManager that details which slices in * which dwi scans were replaced by their expectations. * ********************************************************************/ ReplacementManager *DoVolumeToVolumeRegistration(// Input const EddyCommandLineOptions& clo, // Input/Output ECScanManager& sm) EddyTry { static Utilities::FSLProfiler prof("_"+string(__FILE__)+"_"+string(__func__)); double total_key = prof.StartEntry("Total"); if (clo.IsDiffusion()) { // Start by registering the b0 scans NEWMAT::Matrix b0_mss, b0_ph; ReplacementManager *b0_rm = nullptr; if (clo.NIter() && clo.Registerb0() && sm.NScans(ScanType::b0)>1) { double b0_key = prof.StartEntry("b0"); if (clo.Verbose()) cout << "Running Register" << endl; b0_rm = Register(clo,ScanType::b0,clo.NIter(),clo.FWHM(),clo.b0_SecondLevelModel(),false,sm,b0_rm,b0_mss,b0_ph); if (clo.IsSliceToVol()) { // Set scan for shape reference if we are to do also slice-to-volume registration if (clo.ShapeReferencesSet()) { // User has specified shape-references if (clo.Verbose()) cout << "Setting user requested scan " << clo.B0ShapeReference() << " as b0 shape-reference."<< endl; sm.SetB0ShapeReference(clo.B0ShapeReference()); } else { // We need to find best scan for shape reference double minmss=1e20; unsigned int mindx=0; for (unsigned int i=0; i 0) { if (clo.Verbose()) cout << "Running sm.PolateB0MovPar" << endl; sm.PolateB0MovPar(); } // Now register the dwi scans NEWMAT::Matrix dwi_mss, dwi_ph; ReplacementManager *dwi_rm = NULL; if (clo.NIter() && clo.RegisterDWI()) { double dwi_key = prof.StartEntry("dwi"); if (clo.Verbose()) cout << "Running Register" << endl; dwi_rm = Register(clo,ScanType::DWI,clo.NIter(),clo.FWHM(),clo.SecondLevelModel(),true,sm,dwi_rm,dwi_mss,dwi_ph); if (clo.IsSliceToVol()) { // Set scan for shape reference if we are to do also slice-to-volume registration if (clo.ShapeReferencesSet()) { // User has specified shape-reference for (unsigned int i=0; i ref = clo.ShellShapeReference(i); if (clo.Verbose()) cout << "Setting user requested scan " << ref.first << " as shell shape-reference for shell "<< i << " with b-value= " << ref.second << endl; sm.SetShellShapeReference(i,ref.first); } } else { // We need to find best scan for shape reference std::vector bvals; std::vector > shindx = sm.GetShellIndicies(bvals); for (unsigned int shell=0; shell i2i = sm.GetDwi2GlobalIndexMapping(); dwi_rm->WriteReport(i2i,clo.OLReportFname()); dwi_rm->WriteMatrixReport(i2i,sm.NScans(),clo.OLMapReportFname(true),clo.OLNStDevMapReportFname(true),clo.OLNSqrStDevMapReportFname(true)); std::ostringstream errtxt; errtxt << "DoVolumeToVolumeRegistration: Unable to find volume with no outliers in shell " << shell << " with b-value=" << bvals[shell]; throw EddyException(errtxt.str()); } if (clo.Verbose()) cout << "Setting scan " << mindx << " as shell shape-reference for shell "<< shell << " with b-value= " << bvals[shell] << endl; sm.SetShellShapeReference(shell,mindx); } } // Apply reference for location } if (clo.Verbose()) cout << "Running sm.ApplyDWILocationReference" << endl; sm.ApplyDWILocationReference(); prof.EndEntry(dwi_key); } // Write history of cost-function and parameter estimates if requested if (clo.NIter() && clo.History()) { if (clo.RegisterDWI()) { MISCMATHS::write_ascii_matrix(clo.DwiMssHistoryFname(),dwi_mss); MISCMATHS::write_ascii_matrix(clo.DwiParHistoryFname(),dwi_ph); } if (clo.Registerb0()) { MISCMATHS::write_ascii_matrix(clo.B0MssHistoryFname(),b0_mss); MISCMATHS::write_ascii_matrix(clo.B0ParHistoryFname(),b0_ph); } } prof.EndEntry(total_key); return(dwi_rm); } else { // If it is fMRI // Now register the dwi scans NEWMAT::Matrix fmri_mss, fmri_ph; ReplacementManager *fmri_rm = NULL; if (clo.NIter()) { double fmri_key = prof.StartEntry("fmri"); if (clo.Verbose()) cout << "Running Register" << endl; fmri_rm = Register(clo,ScanType::fMRI,clo.NIter(),clo.FWHM(),clo.SecondLevelModel(),true,sm,fmri_rm,fmri_mss,fmri_ph); if (clo.IsSliceToVol()) { // Set scan for shape reference if we are to do also slice-to-volume registration if (clo.ShapeReferencesSet()) { // User has specified shape-references if (clo.Verbose()) cout << "Setting user requested scan " << clo.fMRIShapeReference() << " as shape-reference."<< endl; sm.SetfMRIShapeReference(clo.fMRIShapeReference()); } else { // We need to find best scan for shape reference double minmss=1e20; unsigned int mindx=0; bool found_vol_with_no_outliers=false; for (unsigned int i=0; i one2one(sm.NScans(ScanType::fMRI)); std::iota(one2one.begin(),one2one.end(),0); fmri_rm->WriteReport(one2one,clo.OLReportFname()); fmri_rm->WriteMatrixReport(one2one,sm.NScans(),clo.OLMapReportFname(true),clo.OLNStDevMapReportFname(true),clo.OLNSqrStDevMapReportFname(true)); throw EddyException("DoVolumeToVolumeRegistration: Unable to find volume with no outliers."); } if (clo.Verbose()) cout << "Setting scan " << mindx << " as shape-reference."<< endl; sm.SetfMRIShapeReference(mindx); } // Apply reference for location } if (clo.Verbose()) cout << "Running sm.ApplyDWILocationReference" << endl; sm.ApplyfMRILocationReference(); prof.EndEntry(fmri_key); } // Write history of cost-function and parameter estimates if requested if (clo.NIter() && clo.History()) { MISCMATHS::write_ascii_matrix(clo.fMRIMssHistoryFname(),fmri_mss); MISCMATHS::write_ascii_matrix(clo.fMRIParHistoryFname(),fmri_ph); } prof.EndEntry(total_key); return(fmri_rm); } } EddyCatch /****************************************************************//** * * A very high-level global function that estimates long * time-constant eddy currents. It will alternate between estimating * the long EC parameters and updating the EC parameters. * \param[in] clo Carries information about the command line options * that eddy was invoked with. * \param[in,out] sm Collection of all scans. Will be updated by this call. * \param[in,out] rm A ptr to ReplacementManager that details which slices * in which dwi scans were replaced by their expectations. * \return A ptr to ReplacementManager that details which slices in * which dwi scans were replaced by their expectations. * ********************************************************************/ ReplacementManager *EstimateLongEC(// Input const EddyCommandLineOptions& clo, // Input/Output ECScanManager& sm, ReplacementManager *rm) EddyTry { static Utilities::FSLProfiler prof("_"+string(__FILE__)+"_"+string(__func__)); double total_key = prof.StartEntry("Total"); if (sm.IsDiffusion()) { NEWIMAGE::volume mask = sm.Scan(0).GetIma(); EddyUtils::SetTrilinearInterp(mask); mask = 1.0; // FOV mask in model space for (unsigned int iter=0; iter b0_pmp = EddyGpuUtils::LoadPredictionMaker(clo,ScanType::b0,sm,iter,0.0,mask); std::shared_ptr dwi_pmp = EddyGpuUtils::LoadPredictionMaker(clo,ScanType::DWI,sm,iter,0.0,mask); std::vector > pred(sm.NScans()); for (GpuPredictorChunk c(sm,ScanType::b0); c b0i = c.Indicies(); std::vector > chunk = b0_pmp->Predict(b0i); for (const unsigned int& i : b0i) pred[sm.Getb02GlobalIndexMapping(i)] = chunk[i]; } for (GpuPredictorChunk c(sm,ScanType::DWI); c dwii = c.Indicies(); std::vector > chunk = dwi_pmp->Predict(dwii); for (const unsigned int& i : dwii) pred[sm.GetDwi2GlobalIndexMapping(i)] = chunk[i]; } // Now estimate long EC parameters if (clo.DebugLevel()) { std::vector mss = EddyGpuUtils::LongECParamUpdate(pred,mask,0.0,clo.VeryVerbose(),iter,clo.DebugLevel(),clo.DebugIndicies().GetIndicies(),sm); } else { std::vector mss = EddyGpuUtils::LongECParamUpdate(pred,mask,0.0,clo.VeryVerbose(),sm); } #else // Load prediction maker in model space and make all the predictions // Note that for this we need to have predictions both for b0 and dwi std::shared_ptr b0_pmp = EDDY::LoadPredictionMaker(clo,ScanType::b0,sm,iter,0.0,mask); std::shared_ptr dwi_pmp = EDDY::LoadPredictionMaker(clo,ScanType::DWI,sm,iter,0.0,mask); std::vector > pred(sm.NScans()); for (unsigned int s=0; sPredict(s); for (unsigned int s=0; sPredict(s); // Caclulate parameter updates if (clo.DebugLevel()) { std::vector mss = EddyUtils::LongECParamUpdate(pred,mask,0.0,clo.NumberOfThreads(),clo.VeryVerbose(),iter,clo.DebugLevel(),clo.DebugIndicies().GetIndicies(),sm); } else { std::vector mss = EddyUtils::LongECParamUpdate(pred,mask,0.0,clo.NumberOfThreads(),clo.VeryVerbose(),sm); } #endif if (clo.ReestimateECWhenEstimatingLongEC()) { // Next update the "regular" movement and/or EC parameters ReplacementManager *b0_rm = nullptr; NEWMAT::Matrix b0_mss, b0_ph; b0_rm = Register(clo,ScanType::b0,1,std::vector(1,0.0),clo.b0_SecondLevelModel(),false,sm,b0_rm,b0_mss,b0_ph); NEWMAT::Matrix dwi_mss, dwi_ph; rm = Register(clo,ScanType::DWI,1,std::vector(1,0.0),clo.SecondLevelModel(),true,sm,rm,dwi_mss,dwi_ph); } } } else throw EddyException("EstimateLongEC: Called with fMRI data"); prof.EndEntry(total_key); return(rm); } EddyCatch /****************************************************************//** * * A very high-level global function that registers all the scans * (b0 and dwis) in sm using a slice-to-volume model. * \param[in] clo Carries information about the command line options * that eddy was invoked with. * \param[in,out] sm Collection of all scans. Will be updated by this call. * \return A ptr to ReplacementManager that details which slices in * which dwi scans were replaced by their expectations. * ********************************************************************/ ReplacementManager *DoSliceToVolumeRegistration(// Input const EddyCommandLineOptions& clo, unsigned int oi, // Order index bool dol, // Detect outliers? // Input/Output ECScanManager& sm, ReplacementManager *dwi_rm) EddyTry { static Utilities::FSLProfiler prof("_"+string(__FILE__)+"_"+string(__func__)); double total_key = prof.StartEntry("Total"); if (sm.IsDiffusion()) { // Start by registering the b0 scans NEWMAT::Matrix b0_mss, b0_ph; ReplacementManager *b0_rm = NULL; if (clo.S2V_NIter(oi) && clo.Registerb0() && sm.NScans(ScanType::b0)>1) { double b0_key = prof.StartEntry("b0"); if (clo.Verbose()) cout << "Running Register" << endl; b0_rm = Register(clo,ScanType::b0,clo.S2V_NIter(oi),clo.S2V_FWHM(oi),clo.b0_SecondLevelModel(),false,sm,b0_rm,b0_mss,b0_ph); // Set reference for shape if (clo.Verbose()) cout << "Running sm.ApplyB0ShapeReference" << endl; sm.ApplyB0ShapeReference(); // Set reference for location if (clo.Verbose()) cout << "Running sm.ApplyB0LocationReference" << endl; sm.ApplyB0LocationReference(); prof.EndEntry(b0_key); } // Now register the dwi scans NEWMAT::Matrix dwi_mss, dwi_ph; if (clo.S2V_NIter(oi) && clo.RegisterDWI()) { double dwi_key = prof.StartEntry("dwi"); if (clo.Verbose()) cout << "Running Register" << endl; dwi_rm = Register(clo,ScanType::DWI,clo.S2V_NIter(oi),clo.S2V_FWHM(oi),clo.SecondLevelModel(),dol,sm,dwi_rm,dwi_mss,dwi_ph); // Set reference for shape if (clo.Verbose()) cout << "Running sm.ApplyShellShapeReference" << endl; for (unsigned int si=0; si mask = sm.Scan(0,ScanType::Any).GetIma(); EddyUtils::SetTrilinearInterp(mask); mask = 1.0; // FOV-mask in model space #ifdef COMPILE_GPU // Loop over b0s and DWIs for (unsigned int sti=0; sti<2; sti++) { if (sm.NScans(st[sti]) > 1) { // Will only have info on field if 2 or more locations std::shared_ptr pmp = EddyGpuUtils::LoadPredictionMaker(clo,st[sti],sm,0,0.0,mask); // First calculate the average location for this scan type to use as reference EDDY::ImageCoordinates mic = sm.Scan(0,st[sti]).SamplingPoints(); for (unsigned int s=1; s(sm.NScans(st[sti])); bfe.SetRefScan(mask,mic); // Next loop over chunks of predictions and add scans for (GpuPredictorChunk c(sm,st[sti]); c si = c.Indicies(); std::vector > pred = pmp->Predict(si); // Loop over scans within chunk for (unsigned int i=0; i pmp = EDDY::LoadPredictionMaker(clo,st[sti],sm,0,0.0,mask); // Loop over scans #pragma omp parallel for for (int s=0; s pred = pmp->Predict(s); // Get original data in model space NEWIMAGE::volume sm.Scan(s,st[sti]).GetUnwarpedIma(sm.GetSuscHzOffResField(s,st[sti])); // AddScan bfe.AddScan(pred,sm.Scan(s,st[sti]).GetUnwarpedIma(sm.GetSuscHzOffResField(s,st[sti])),mask,ic); } } #endif // Caclulate many fields // double iksp[] = {10.0}; // double ilambda[] = {1e-10, 1e-6, 1e-2, 100, 1e6, 1e10, 1e14}; // for (unsigned int i=0; i<7; i++) { // cout << "Calculating field with lambda = " << ilambda[i] << endl; // NEWIMAGE::volume bfield = bfe.GetField(ilambda[i]); // char fname[256]; sprintf(fname,"bfield_%d",i); // NEWIMAGE::write_volume(bfield,fname); // } // Calculate field NEWIMAGE::volume bfield = bfe.GetField(1e10); NEWIMAGE::write_volume(bfield,"bfield_1e10"); // Set field sm.SetBiasField(bfield); // Do a second step where the unknown offset is calculated by // maximising the SNR/CNR of the corrected data. // float offset = EstimateBiasFieldOffset(); return; } EddyCatch */ /****************************************************************//** * * A very high-level global function that estimates the offset * of an estimated bias field. It does that by finding the offset * that maximises a weighted sum of the SNR (b0-volumes) and CNR * (dwi-volumes) of the corrected data. It will return the estimated * offset, and also set it in the ScanManager. It will also set the * average of the bias field to one within the user defined mask. * \returns offset The estimated offset value * \param[in] clo Carries information about the command line options * that eddy was invoked with. * \param[in,out] sm Collection of all scans. Will be updated by this call. * ********************************************************************/ float EstimatedBiasFieldOffset(// Input const EddyCommandLineOptions& clo, // Input/output ECScanManager& sm) EddyTry { return(1.0); } EddyCatch /****************************************************************//** * * A global function that registers the scans in sm. * \param[in] clo Carries information about the command line options * that eddy was invoked with. * \param[in] st Specifies if we should register the diffusion weighted * images or the b=0 images. * \param[in] niter Specifies how many iterations should be run * \param[in] slm Specifies if a 2nd level model should be used to * constrain the estimates. * \param[in] dol Detect outliers if true * \param[in,out] sm Collection of all scans. Will be updated by this call. * \param[in,out] rm Pointer to ReplacementManager. If NULL on input * one will be allocated and the new pointer value passed on return. * \param[out] msshist Returns the history of the mss. msshist(i,j) * contains the mss for the jth scan on the ith iteration. * \param[out] phist Returns the history of the estimated parameters. * phist(i,j) contains the jth parameter on the ith iteration * \return A ptr to ReplacementManager that details which slices in * which scans were replaced by their expectations. * ********************************************************************/ ReplacementManager *Register(// Input const EddyCommandLineOptions& clo, ScanType st, unsigned int niter, const std::vector& fwhm, SecondLevelECModelType slm, bool dol, // Input/Output ECScanManager& sm, ReplacementManager *rm, // Output NEWMAT::Matrix& msshist, NEWMAT::Matrix& phist) EddyTry { static Utilities::FSLProfiler prof("_"+string(__FILE__)+"_"+string(__func__)); double total_key = prof.StartEntry("Total"); msshist.ReSize(niter,sm.NScans(st)); phist.ReSize(niter,sm.NScans(st)*sm.Scan(0,st).NParam()); double *mss_tmp = new double[sm.NScans(st)]; // Replace by vector if (rm == NULL) { // If no replacement manager passed in if (clo.OLSummaryStatistics() == OLSumStats::ShellWise) { std::vector bvals; std::vector > shi = sm.GetDWIShellIndicies(bvals); rm = new ReplacementManager(shi,bvals,static_cast(sm.Scan(0,st).GetIma().zsize()),clo.OLDef(),clo.OLErrorType(),clo.OLType(),clo.MultiBand()); } else rm = new ReplacementManager(sm.NScans(st),static_cast(sm.Scan(0,st).GetIma().zsize()),clo.OLDef(),clo.OLErrorType(),clo.OLType(),clo.MultiBand()); } NEWIMAGE::volume mask = sm.Scan(0,st).GetIma(); EddyUtils::SetTrilinearInterp(mask); mask = 1.0; // FOV-mask in model space for (unsigned int iter=0; iter pmp = EddyGpuUtils::LoadPredictionMaker(clo,st,sm,iter,fwhm[iter],mask); if (clo.DebugLevel() > 2) EDDY::WritePMDebugInfo(pmp,clo,st,iter,"GPU_","before_repol"); #else std::shared_ptr pmp = EDDY::LoadPredictionMaker(clo,st,sm,iter,fwhm[iter],mask); if (clo.DebugLevel() > 2) EDDY::WritePMDebugInfo(pmp,clo,st,iter,"","before_repol"); #endif prof.EndEntry(load_key); // Detect outliers and replace them DiffStatsVector stats(sm.NScans(st)); if (dol) { double dol_key = prof.StartEntry("Detecting and replacing outliers"); std::shared_ptr od_pmp; #ifdef COMPILE_GPU od_pmp = pmp; if (clo.DebugLevel()) stats = EddyGpuUtils::DetectOutliers(clo,st,od_pmp,mask,sm,iter,clo.DebugLevel(),*rm); else stats = EddyGpuUtils::DetectOutliers(clo,st,od_pmp,mask,sm,*rm); if (iter) { EddyGpuUtils::ReplaceOutliers(clo,st,od_pmp,mask,*rm,false,sm); // EddyGpuUtils::UpdatePredictionMaker(clo,st,sm,rm,mask,pmp); pmp = EddyGpuUtils::LoadPredictionMaker(clo,st,sm,iter,fwhm[iter],mask); if (clo.DebugLevel() > 2) EDDY::WritePMDebugInfo(pmp,clo,st,iter,"GPU_","after_repol"); } #else od_pmp = pmp; if (clo.DebugLevel()) stats = EDDY::DetectOutliers(clo,st,od_pmp,mask,sm,iter,clo.DebugLevel(),*rm); else stats = EDDY::DetectOutliers(clo,st,od_pmp,mask,sm,*rm); if (iter) { EDDY::ReplaceOutliers(clo,st,od_pmp,mask,*rm,false,sm); // EDDY::UpdatePredictionMaker(clo,st,sm,rm,mask,pmp); pmp = EDDY::LoadPredictionMaker(clo,st,sm,iter,fwhm[iter],mask); if (clo.DebugLevel() > 2) EDDY::WritePMDebugInfo(pmp,clo,st,iter,"","after_repol"); } #endif prof.EndEntry(dol_key); } // // Calculate the parameter updates // Note that this section will proceed in three different // ways depending on if it is run in single processor mode, // multi processor mode (OpenMP) or with a GPU-box (CUDA). // if (clo.Verbose()) cout << "Calculating parameter updates" << endl; double update_key = prof.StartEntry("Updating parameters"); #ifdef COMPILE_GPU for (GpuPredictorChunk c(sm,st); c si = c.Indicies(); if (clo.VeryVerbose()) cout << "Making predictions for scans: " << c << endl; std::vector > pred = pmp->Predict(si); prof.EndEntry(predict_chunk_key); if (clo.VeryVerbose()) cout << "Finished making predictions for scans: " << c << endl; double update_chunk_key = prof.StartEntry("Updating parameters for chunk"); for (unsigned int i=0; i mask = sm.Scan(0,ScanType::DWI).GetIma(); EddyUtils::SetTrilinearInterp(mask); mask = 1.0; // FOV-mask in model space if (rm == NULL) { if (clo.OLSummaryStatistics() == OLSumStats::ShellWise) { std::vector bvals; std::vector > shi = sm.GetDWIShellIndicies(bvals); rm = new ReplacementManager(shi,bvals,static_cast(sm.Scan(0,ScanType::DWI).GetIma().zsize()),clo.OLDef(),clo.OLErrorType(),clo.OLType(),clo.MultiBand()); } rm = new ReplacementManager(sm.NScans(ScanType::DWI),static_cast(sm.Scan(0,ScanType::DWI).GetIma().zsize()),clo.OLDef(),clo.OLErrorType(),clo.OLType(),clo.MultiBand()); } // Load prediction maker in model space double load_key = prof.StartEntry("LoadPredictionMaker"); #ifdef COMPILE_GPU std::shared_ptr pmp = EddyGpuUtils::LoadPredictionMaker(clo,ScanType::DWI,sm,0,0.0,mask); #else std::shared_ptr pmp = EDDY::LoadPredictionMaker(clo,ScanType::DWI,sm,0,0.0,mask); #endif prof.EndEntry(load_key); // Detect outliers and replace them DiffStatsVector stats(sm.NScans(ScanType::DWI)); bool add_noise = clo.AddNoiseToReplacements(); double det_key = prof.StartEntry("DetectOutliers"); #ifdef COMPILE_GPU stats = EddyGpuUtils::DetectOutliers(clo,ScanType::DWI,pmp,mask,sm,*rm); #else stats = EDDY::DetectOutliers(clo,ScanType::DWI,pmp,mask,sm,*rm); #endif prof.EndEntry(det_key); double rep_key = prof.StartEntry("ReplaceOutliers"); #ifdef COMPILE_GPU EddyGpuUtils::ReplaceOutliers(clo,ScanType::DWI,pmp,mask,*rm,add_noise,sm); #else EDDY::ReplaceOutliers(clo,ScanType::DWI,pmp,mask,*rm,add_noise,sm); #endif prof.EndEntry(rep_key); prof.EndEntry(total_key); return(rm); } EddyCatch /****************************************************************//** * * A global function that loads up a prediction maker with all scans * of a given type. It will load it with unwarped scans (given the * current estimates of the warps) as served up by sm.GetUnwarpedScan() * or sm.GetUnwarpedOrigScan() depending on the value of use_orig. * \param[in] clo Carries information about the command line options * that eddy was invoked with. * \param[in] st Specifies if we should register the diffusion weighted * images or the b=0 images. If it is set to DWI the function will return * an EDDY::DiffusionGP prediction maker and if it is set to b0 it will * return an EDDY::b0Predictor. * \param[in] sm Collection of all scans. * \param[out] mask Returns a mask that indicates the voxels where data * is present for all input scans in sm. * \param[in] use_orig If set to true it will load it with unwarped "original" * , i.e. un-smoothed, scans. Default is false. * \return A safe pointer to a DWIPredictionMaker that can be used to * make predictions about what the scans should look like in undistorted space. * ********************************************************************/ std::shared_ptr LoadPredictionMaker(// Input const EddyCommandLineOptions& clo, ScanType st, const ECScanManager& sm, unsigned int iter, float fwhm, // Output NEWIMAGE::volume& mask, // Optional input bool use_orig) EddyTry { static Utilities::FSLProfiler prof("_"+string(__FILE__)+"_"+string(__func__)); double total_key = prof.StartEntry("Total"); std::shared_ptr pmp; // Prediction Maker Pointer std::shared_ptr hpcf; // Hyper-parameter cost-function std::shared_ptr hpe; // Hyper-parameter estimator if (st==ScanType::DWI || (st==ScanType::fMRI && !clo.HyperParFixed())) { // If we actually need to estimate hyper-parameters if (clo.HyperParFixed()) hpe = std::shared_ptr(new FixedValueHyParEstimator(clo.HyperParValues())); else { if (clo.HyParCostFunction() == HyParCostFunctionType::CC) hpe = std::shared_ptr(new CheapAndCheerfulHyParEstimator(clo.NVoxHp(),clo.InitRand())); else { if (clo.HyParCostFunction() == HyParCostFunctionType::MML) hpcf = std::shared_ptr(new MMLHyParCF); else if (clo.HyParCostFunction() == HyParCostFunctionType::CV) hpcf = std::shared_ptr(new CVHyParCF); else if (clo.HyParCostFunction() == HyParCostFunctionType::GPP) hpcf = std::shared_ptr(new GPPHyParCF); else throw EddyException("LoadPredictionMaker: Unknown hyperparameter cost-function"); hpe = std::shared_ptr(new FullMontyHyParEstimator(hpcf,clo.HyParFudgeFactor(),clo.NVoxHp(),clo.InitRand(),clo.VeryVerbose())); } } } if (st==ScanType::DWI) { // If diffusion weighted data std::shared_ptr K; if (clo.CovarianceFunction() == CovarianceFunctionType::Spherical) K = std::shared_ptr(new SphericalKMatrix(clo.DontCheckShelling())); else if (clo.CovarianceFunction() == CovarianceFunctionType::Exponential) K = std::shared_ptr(new ExponentialKMatrix(clo.DontCheckShelling())); else if (clo.CovarianceFunction() == CovarianceFunctionType::NewSpherical) K = std::shared_ptr(new NewSphericalKMatrix(clo.DontCheckShelling())); else throw EddyException("LoadPredictionMaker: Unknown covariance function"); pmp = std::shared_ptr(new DiffusionGP(K,hpe)); // GP } else if (st==ScanType::b0) pmp = std::shared_ptr(new b0Predictor); // Silly mean predictor for b=0 data else if (st==ScanType::fMRI) { if (clo.CovarianceFunction() == CovarianceFunctionType::SquaredExponential) { if (clo.HyperParFixed()) pmp = std::shared_ptr(new b0Predictor); // Use mean predictor for now else { std::shared_ptr K = std::shared_ptr(new SqrExpKMatrix()); pmp = std::shared_ptr(new fmriPredictor(K,hpe)); } } else throw EddyException("LoadPredictionMaker: Unknown covariance function"); } else throw EddyException("LoadPredictionMaker: Invalid scan type"); pmp->SetNoOfScans(sm.NScans(st)); mask = sm.Scan(0,st).GetIma(); EddyUtils::SetTrilinearInterp(mask); mask = 1.0; double load_key = prof.StartEntry("Loading"); if (clo.Verbose()) std::cout << "Loading prediction maker" << std::flush; if (clo.VeryVerbose()) std::cout << std::endl << "Scan:" << std::flush; if (clo.NumberOfThreads() == 1) { // If we are to run single threaded for (int s=0; s tmpmask = sm.Scan(s,st).GetIma(); EddyUtils::SetTrilinearInterp(tmpmask); tmpmask = 1.0; if (use_orig) pmp->SetScan(sm.GetUnwarpedOrigScan(s,tmpmask,st),sm.Scan(s,st).GetDiffPara(clo.RotateBVecsDuringEstimation()),s); else pmp->SetScan(sm.GetUnwarpedScan(s,tmpmask,st),sm.Scan(s,st).GetDiffPara(clo.RotateBVecsDuringEstimation()),s); { mask *= tmpmask; } } if (clo.VeryVerbose()) std::cout << std::endl << std::flush; } else { // We are to run multi-threaded // Decide number of volumes per thread std::vector nvols = EddyUtils::ScansPerThread(sm.NScans(st),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; iEvaluateModel(sm.Mask()*mask,fwhm,clo.Verbose()); prof.EndEntry(eval_key); if (clo.DebugLevel() > 2 && st==ScanType::DWI) { char fname[256]; sprintf(fname,"EDDY_DEBUG_K_Mat_Data_%02d",iter); pmp->WriteMetaData(fname); } prof.EndEntry(total_key); return(pmp); } EddyCatch DiffStatsVector DetectOutliers(// Input const EddyCommandLineOptions& clo, ScanType st, std::shared_ptr pmp, const NEWIMAGE::volume& mask, const ECScanManager& sm, // Input/Output ReplacementManager& rm) EddyTry { return(detect_outliers(clo,st,pmp,mask,sm,0,0,rm)); } EddyCatch DiffStatsVector DetectOutliers(// Input const EddyCommandLineOptions& clo, ScanType st, std::shared_ptr pmp, const NEWIMAGE::volume& mask, const ECScanManager& sm, unsigned int iter, unsigned int dl, // Debug Level // Input/Output ReplacementManager& rm) EddyTry { return(detect_outliers(clo,st,pmp,mask,sm,iter,dl,rm)); } EddyCatch DiffStatsVector detect_outliers(// Input const EddyCommandLineOptions& clo, ScanType st, std::shared_ptr pmp, const NEWIMAGE::volume& mask, const ECScanManager& sm, unsigned int iter, unsigned int dl, // Input/Output ReplacementManager& rm) EddyTry { static Utilities::FSLProfiler prof("_"+string(__FILE__)+"_"+string(__func__)); double total_key = prof.StartEntry("Total"); if (clo.VeryVerbose()) cout << "Checking for outliers" << endl; // Generate slice-wise stats on difference between observation and prediction DiffStatsVector stats(sm.NScans(st)); if (clo.NumberOfThreads() == 1) { // If we are to run single threaded for (int s=0; s pred = pmp->Predict(s); stats[s] = EddyUtils::GetSliceWiseStats(clo,st,pred,sm.GetSuscHzOffResField(s,st),mask,sm,s,iter,dl); } } else { // We are running multi-threaded // Decide number of volumes per thread std::vector nvols = EddyUtils::ScansPerThread(sm.NScans(st),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 pmp, const NEWIMAGE::volume& mask, const ReplacementManager& rm, bool add_noise, // Input/Output ECScanManager& sm) EddyTry { static Utilities::FSLProfiler prof("_"+string(__FILE__)+"_"+string(__func__)); double total_key = prof.StartEntry("Total"); // Replace outlier slices with their predictions if (clo.VeryVerbose()) cout << "Replacing outliers with predictions" << endl; if (clo.NumberOfThreads() == 1) { // If we are to run single threaded for (int s=0; s ol = rm.OutliersInScan(s); if (ol.size()) { // If this scan has outlier slices if (clo.VeryVerbose()) { std::cout << "Scan " << sm.GetGlobalIndex(s,st) << " has outlier slices:"; for (unsigned int i=0; i pred = pmp->Predict(s,true); if (add_noise) { double vp = pmp->PredictionVariance(s,true); double ve = pmp->ErrorVariance(s); double stdev = std::sqrt(vp+ve) - std::sqrt(vp); pred += EddyUtils::MakeNoiseIma(pred,0.0,stdev); } sm.Scan(s,st).SetAsOutliers(pred,sm.GetSuscHzOffResField(s,st),mask,ol); } } } else { // We are to run multi-threaded // Decide number of volumes per thread std::vector nvols = EddyUtils::ScansPerThread(sm.NScans(st),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 GetPredictionsForResampling(// Input const EddyCommandLineOptions& clo, ScanType st, const ECScanManager& sm, // Output NEWIMAGE::volume4D& pred) EddyTry { static Utilities::FSLProfiler prof("_"+string(__FILE__)+"_"+string(__func__)); double total_key = prof.StartEntry("Total"); pred.reinitialize(sm.Scan(0,st).GetIma().xsize(),sm.Scan(0,st).GetIma().ysize(),sm.Scan(0,st).GetIma().zsize(),sm.NScans(st)); NEWIMAGE::copybasicproperties(sm.Scan(0,st).GetIma(),pred); NEWIMAGE::volume mask = sm.Scan(0,st).GetIma(); EddyUtils::SetTrilinearInterp(mask); mask = 1.0; // FOV-mask in model space EddyCommandLineOptions lclo = clo; std::vector hypar; if (lclo.HyParFudgeFactor() != 1.0) lclo.SetHyParFudgeFactor(1.0); if (st == ScanType::Any || st == ScanType::b0) { #ifdef COMPILE_GPU std::shared_ptr pmp = EddyGpuUtils::LoadPredictionMaker(lclo,ScanType::b0,sm,0,0.0,mask); #else std::shared_ptr pmp = EDDY::LoadPredictionMaker(lclo,ScanType::b0,sm,0,0.0,mask); #endif for (unsigned int s=0; sPredict(s,true); else pred[sm.Getb02GlobalIndexMapping(s)] = pmp->Predict(s,true); } } if (st == ScanType::Any || st == ScanType::DWI) { #ifdef COMPILE_GPU std::shared_ptr pmp = EddyGpuUtils::LoadPredictionMaker(lclo,ScanType::DWI,sm,0,0.0,mask); #else std::shared_ptr pmp = EDDY::LoadPredictionMaker(lclo,ScanType::DWI,sm,0,0.0,mask); #endif hypar = pmp->GetHyperPar(); for (unsigned int s=0; sPredict(s,true); else pred[sm.GetDwi2GlobalIndexMapping(s)] = pmp->Predict(s,true); } } prof.EndEntry(total_key); return(hypar); } EddyCatch /****************************************************************//** * * A global function that makes ff=1 predictions, in model space, * for all scans of type st. It differs from the "normal" way eddy * makes predictions in that it uses a "scattered data reconstruction" * approach. * \param[in] clo Carries information about the command line options * that eddy was invoked with. * \param[in] st Specifies if we should resample the diffusion weighted * images, the b=0 images or both. * \param[in] sm Collection of all scans. * \param[in] hypar Hyperparameters. Valid only for "spherical" covariance- * function. * \param[out] pred A 4D volume with predictions for all scans of the * type indicated by st. * \param[in] vwbvrot If true means that bvec rotation is per volume * instead of per slice/MB-group which is default. * ********************************************************************/ void GetScatterBrainPredictions(// Input const EddyCommandLineOptions& clo, ScanType st, ECScanManager& sm, const std::vector& hypar, // Output NEWIMAGE::volume4D& pred, // Optional input bool vwbvrot) EddyTry { static Utilities::FSLProfiler prof("_"+string(__FILE__)+"_"+string(__func__)); double total_key = prof.StartEntry("Total"); // Do some checking to ensure that the call and the input makes sense if (clo.CovarianceFunction() != CovarianceFunctionType::NewSpherical) { throw EddyException("EDDY::GetScatterBrainPredictions: Predictions only available for Spherical covariance function"); } if (!clo.IsSliceToVol()) throw EddyException("EDDY::GetScatterBrainPredictions: Predictions only makes sense for slice-to-vol model"); NEWIMAGE::volume mask = sm.Scan(0,st).GetIma(); EddyUtils::SetTrilinearInterp(mask); mask = 1.0; // FOV-mask in model space std::vector dp = sm.GetDiffParas(ScanType::DWI); NewSphericalKMatrix K(clo.DontCheckShelling()); K.SetDiffusionPar(dp); if (hypar.size() != K.NoOfHyperPar()) throw EddyException("EDDY::GetScatterBrainPredictions: Incompatible hypar size"); pred.reinitialize(sm.Scan(0,st).GetIma().xsize(),sm.Scan(0,st).GetIma().ysize(),sm.Scan(0,st).GetIma().zsize(),sm.NScans(st)); NEWIMAGE::copybasicproperties(sm.Scan(0,st).GetIma(),pred); // The b0s predictions are just the means and unaffected by rotations // Should be updated to be closer to the way we calculate the predictions // for the diffusion weighted data. if (st==ScanType::b0 || st==ScanType::Any) { EDDY::PolationPara old_pol = sm.GetPolation(); EDDY::PolationPara new_pol = old_pol; if (clo.IsSliceToVol() && new_pol.GetS2VInterp() != NEWIMAGE::trilinear) new_pol.SetS2VInterp(NEWIMAGE::trilinear); sm.SetPolation(new_pol); #ifdef COMPILE_GPU std::shared_ptr pmp = EddyGpuUtils::LoadPredictionMaker(clo,ScanType::b0,sm,0,0.0,mask); #else std::shared_ptr pmp = EDDY::LoadPredictionMaker(clo,ScanType::b0,sm,0,0.0,mask); #endif for (unsigned int s=0; sPredict(s,true); else pred[sm.Getb02GlobalIndexMapping(s)] = pmp->Predict(s,true); } sm.SetPolation(new_pol); } if (st==ScanType::DWI || st==ScanType::Any) { #ifdef COMPILE_GPU EddyGpuUtils::MakeScatterBrainPredictions(clo,sm,hypar,pred,vwbvrot); #else throw EddyException("EDDY::GetScatterBrainPredictions: Only implemented for GPU"); #endif } prof.EndEntry(total_key); return; } EddyCatch /****************************************************************//** * * A global function that calculates CNR-maps for the dwi volume, * SNR-maps for the b0 volumes and a 4D map of residuals. * for all scans of type st. All the output parameters are optional * and passing in a nullptr means that parameter is not calculated * or returned. * \param[in] clo Carries information about the command line options * that eddy was invoked with. * \param[in] sm Collection of all scans. * \param[out] std_cnr A 4D file with one CNR-map, calculated as * std(pred)/std(res), per shell. * \param[out] range_cnr A 4D file with one CNR-map,calculated as * range(pred)/std(res), per shell. * \param[out] b0_snr A 3D SNR-map for the b0-volumes * \param[out] residuals A 4D-map with the residuals for both dwis and b0s * ********************************************************************/ void CalculateCNRMaps(// Input const EddyCommandLineOptions& clo, const ECScanManager& sm, // Output std::shared_ptr > std_cnr, std::shared_ptr > range_cnr, std::shared_ptr > b0_snr, std::shared_ptr > residuals) EddyTry { static Utilities::FSLProfiler prof("_"+string(__FILE__)+"_"+string(__func__)); double total_key = prof.StartEntry("Total"); if (std_cnr==nullptr && range_cnr==nullptr && b0_snr==nullptr && residuals==nullptr) { throw EddyException("EDDY::CalculateCNRMaps: At least one output parameter must be set."); } if (!sm.IsShelled()) { throw EddyException("EDDY::CalculateCNRMaps: Can only calculate CNR for shelled data."); } if ((std_cnr!=nullptr && (!NEWIMAGE::samesize(sm.Scan(0,ScanType::Any).GetIma(),(*std_cnr)[0]) || (*std_cnr).tsize() != sm.NoOfShells(ScanType::DWI))) || (range_cnr!=nullptr && (!NEWIMAGE::samesize(sm.Scan(0,ScanType::Any).GetIma(),(*range_cnr)[0]) || (*range_cnr).tsize() != sm.NoOfShells(ScanType::DWI))) || (b0_snr!=nullptr && !NEWIMAGE::samesize(sm.Scan(0,ScanType::Any).GetIma(),*b0_snr)) || (residuals!=nullptr && (!NEWIMAGE::samesize(sm.Scan(0,ScanType::Any).GetIma(),(*residuals)[0]) || (*residuals).tsize() != sm.NScans(ScanType::Any)))) { throw EddyException("EDDY::CalculateCNRMaps: Size mismatch between sm and output containers."); } NEWIMAGE::volume mask = sm.Scan(0,ScanType::DWI).GetIma(); // FOV-mask in model space EddyUtils::SetTrilinearInterp(mask); mask = 1.0; std::shared_ptr dwi_pmp; std::shared_ptr b0_pmp; if (std_cnr || range_cnr || residuals) { #ifdef COMPILE_GPU dwi_pmp = EddyGpuUtils::LoadPredictionMaker(clo,ScanType::DWI,sm,0,0.0,mask); #else dwi_pmp = EDDY::LoadPredictionMaker(clo,ScanType::DWI,sm,0,0.0,mask); #endif } if (b0_snr || residuals) { #ifdef COMPILE_GPU b0_pmp = EddyGpuUtils::LoadPredictionMaker(clo,ScanType::b0,sm,0,0.0,mask); #else b0_pmp = EDDY::LoadPredictionMaker(clo,ScanType::b0,sm,0,0.0,mask); #endif } if (std_cnr || range_cnr) { // Calculate and write shell-wise CNR maps std::vector grpb; // b-values of the different dwi shells std::vector > dwi_indx = sm.GetShellIndicies(grpb); // Global indicies of dwi scans std::vector > mvols(grpb.size()); // Volumes of mean predictions for the shells // Calculate mean volumes of predictions for (unsigned int i=0; iPredict(sm.GetGlobal2DWIIndexMapping(dwi_indx[i][0])); for (unsigned int j=1; jPredict(sm.GetGlobal2DWIIndexMapping(dwi_indx[i][j])); } mvols[i] /= float(dwi_indx[i].size()); } std::vector > stdvols(grpb.size()); if (std_cnr) { // Calculate standard deviation volumes of predictions for (unsigned int i=0; i tmp = dwi_pmp->Predict(sm.GetGlobal2DWIIndexMapping(dwi_indx[i][0])) - mvols[i]; stdvols[i] = tmp*tmp; for (unsigned int j=1; jPredict(sm.GetGlobal2DWIIndexMapping(dwi_indx[i][j])) - mvols[i]; stdvols[i] += tmp*tmp; } stdvols[i] /= float(dwi_indx[i].size()-1); stdvols[i] = NEWIMAGE::sqrt(stdvols[i]); } } std::vector > minvols(grpb.size()); std::vector > maxvols(grpb.size()); if (range_cnr) { // Caclculate range (max-min) volumes of predictions for (unsigned int i=0; iPredict(sm.GetGlobal2DWIIndexMapping(dwi_indx[i][0])); maxvols[i] = minvols[i]; for (unsigned int j=1; jPredict(sm.GetGlobal2DWIIndexMapping(dwi_indx[i][j]))); maxvols[i] = NEWIMAGE::max(maxvols[i],dwi_pmp->Predict(sm.GetGlobal2DWIIndexMapping(dwi_indx[i][j]))); } maxvols[i] -= minvols[i]; // Maxvols now contain the range rather than the max } } // Calculate standard deviation of residuals std::vector > stdres(grpb.size()); for (unsigned int i=0; i tmp = dwi_pmp->Predict(sm.GetGlobal2DWIIndexMapping(dwi_indx[i][0])) - dwi_pmp->InputData(sm.GetGlobal2DWIIndexMapping(dwi_indx[i][0])); stdres[i] = tmp*tmp; for (unsigned int j=1; jPredict(sm.GetGlobal2DWIIndexMapping(dwi_indx[i][j])) - dwi_pmp->InputData(sm.GetGlobal2DWIIndexMapping(dwi_indx[i][j])); stdres[i] += tmp*tmp; } stdres[i] /= float(dwi_indx[i].size()-1); stdres[i] = NEWIMAGE::sqrt(stdres[i]); } // Divide prediction std/range with std of residuals to get CNR for (unsigned int i=0; i 1) { std::vector b0_indx = sm.GetB0Indicies(); *b0_snr = b0_pmp->Predict(0) - b0_pmp->InputData(0); // N.B. Predict(i) is mean of all b0 scans *b0_snr *= *b0_snr; for (unsigned int i=1; i tmp = b0_pmp->Predict(i) - b0_pmp->InputData(i); *b0_snr += tmp*tmp; } *b0_snr /= static_cast(b0_indx.size()-1); *b0_snr = NEWIMAGE::sqrt(*b0_snr); *b0_snr = b0_pmp->Predict(0) / *b0_snr; } else (*b0_snr) = 0.0; // Set SNR to zero if we can't estimate it } // Get residuals if (residuals) { for (unsigned int i=0; iInputData(i) - dwi_pmp->Predict(i)) / sm.ScaleFactor(); } for (unsigned int i=0; iInputData(i) - b0_pmp->Predict(i)) / sm.ScaleFactor(); } } prof.EndEntry(total_key); return; } EddyCatch void WriteCNRMaps(// Input const EddyCommandLineOptions& clo, const ECScanManager& sm, const std::string& spatial_fname, const std::string& range_fname, const std::string& residual_fname) EddyTry { static Utilities::FSLProfiler prof("_"+string(__FILE__)+"_"+string(__func__)); double total_key = prof.StartEntry("Total"); if (spatial_fname.empty() && residual_fname.empty()) throw EddyException("EDDY::WriteCNRMaps: At least one of spatial and residual fname must be set"); // Allocate memory for the maps we are interested in std::shared_ptr > std_cnr; std::shared_ptr > range_cnr; std::shared_ptr > b0_snr; std::shared_ptr > residuals; if (!spatial_fname.empty()) { const NEWIMAGE::volume& tmp = sm.Scan(0,ScanType::Any).GetIma(); std_cnr = std::make_shared > (tmp.xsize(),tmp.ysize(),tmp.zsize(),sm.NoOfShells(ScanType::DWI)); NEWIMAGE::copybasicproperties(tmp,*std_cnr); b0_snr = std::make_shared > (tmp.xsize(),tmp.ysize(),tmp.zsize()); NEWIMAGE::copybasicproperties(tmp,*b0_snr); } if (!range_fname.empty()) { const NEWIMAGE::volume& tmp = sm.Scan(0,ScanType::Any).GetIma(); range_cnr = std::make_shared > (tmp.xsize(),tmp.ysize(),tmp.zsize(),sm.NoOfShells(ScanType::DWI)); NEWIMAGE::copybasicproperties(tmp,*range_cnr); if (b0_snr==nullptr) { b0_snr = std::make_shared > (tmp.xsize(),tmp.ysize(),tmp.zsize()); NEWIMAGE::copybasicproperties(tmp,*b0_snr); } } if (!residual_fname.empty()) { const NEWIMAGE::volume& tmp = sm.Scan(0,ScanType::Any).GetIma(); residuals = std::make_shared > (tmp.xsize(),tmp.ysize(),tmp.zsize(),sm.NScans(ScanType::Any)); NEWIMAGE::copybasicproperties(tmp,*residuals); } // Calculate the maps we are interested in EDDY::CalculateCNRMaps(clo,sm,std_cnr,range_cnr,b0_snr,residuals); // Write them out if (!spatial_fname.empty()) { const NEWIMAGE::volume& tmp = sm.Scan(0,ScanType::Any).GetIma(); NEWIMAGE::volume4D ovol(tmp.xsize(),tmp.ysize(),tmp.zsize(),sm.NoOfShells(ScanType::Any)); NEWIMAGE::copybasicproperties(tmp,ovol); ovol[0] = *b0_snr; for (unsigned int i=0; i& tmp = sm.Scan(0,ScanType::Any).GetIma(); NEWIMAGE::volume4D ovol(tmp.xsize(),tmp.ysize(),tmp.zsize(),sm.NoOfShells(ScanType::Any)); NEWIMAGE::copybasicproperties(tmp,ovol); ovol[0] = *b0_snr; for (unsigned int i=0; i pmp, const NEWIMAGE::volume& mask, ScanType st, float fwhm, unsigned int iter, // Input/output ECScanManager& sm, // Output double *mss) EddyTry { static std::mutex cout_mutex; for (unsigned int s=first_vol; s pred = pmp->Predict(s); unsigned int global_indx = sm.GetGlobalIndex(s,st); if (clo.DebugLevel() && clo.DebugIndicies().IsAmongIndicies(global_indx)) { mss[s] = EddyUtils::MovAndECParamUpdate(pred,sm.GetSuscHzOffResField(s,st),sm.GetBiasField(),mask,fwhm,clo.VeryVerbose(),global_indx,iter,clo.DebugLevel(),sm.Scan(s,st)); } else { mss[s] = EddyUtils::MovAndECParamUpdate(pred,sm.GetSuscHzOffResField(s,st),sm.GetBiasField(),mask,fwhm,clo.VeryVerbose(),sm.Scan(s,st)); } if (clo.VeryVerbose()) { cout_mutex.lock(); std::cout << "Iter: " << iter << ", scan: " << global_indx << ", mss = " << mss[s] << std::endl << std::flush; cout_mutex.unlock(); } } } EddyCatch /****************************************************************//** * * A global function that gets an unwarped scan and loads it into a * prediction maker. It does this for a range of scans. It is a wrappper * to facilitate using the C++11 thread library for parallelisation. * \param[in] first_vol Index of first volume to process * \param[in] last_vol Index one past the last volume to process * \param[in] clo Carries information about the command line options * that eddy was invoked with. * \param[in] sm Collection of all scans. * \param[in] st Specifies if we should update the diffusion weighted * images or the b=0 images. * \param[in/out] pmp A safe pointer to a DWIPredictionMaker that is to * be loaded. * \param[in/out] mask A mask specifying where predictions are valid * \return None * ********************************************************************/ void SetUnwarpedScanWrapper(// Input unsigned int first_vol, unsigned int last_vol, const EddyCommandLineOptions& clo, const ECScanManager& sm, ScanType st, bool use_orig, // Input/output std::shared_ptr pmp, NEWIMAGE::volume& mask) EddyTry { static std::mutex cout_mutex; static std::mutex mask_mutex; for (unsigned int s=first_vol; s tmpmask = sm.Scan(s,st).GetIma(); EddyUtils::SetTrilinearInterp(tmpmask); tmpmask = 1.0; if (use_orig) pmp->SetScan(sm.GetUnwarpedOrigScan(s,tmpmask,st),sm.Scan(s,st).GetDiffPara(clo.RotateBVecsDuringEstimation()),s); else pmp->SetScan(sm.GetUnwarpedScan(s,tmpmask,st),sm.Scan(s,st).GetDiffPara(clo.RotateBVecsDuringEstimation()),s); mask_mutex.lock(); mask *= tmpmask; mask_mutex.unlock(); } } EddyCatch /****************************************************************//** * * A global function that calculates slice wise stats for a range * of scans. It is a wrappper to facilitate using the C++11 thread * library for parallelisation. * \param[in] first_vol Index of first volume to process * \param[in] last_vol Index one past the last volume to process * \param[in] sm Collection of all scans. * \param[in] pmp A safe pointer to a DWIPredictionMaker that is to * be loaded. * \param[in] mask A mask to limit the area for which to calculate stats. * \param[in] st Specifies if we should update the diffusion weighted * images or the b=0 images. * \param[out] stats A vector of slice-wise stats. * \return None * ********************************************************************/ void GetSliceWiseStatsWrapper(// Input unsigned int first_vol, unsigned int last_vol, const EddyCommandLineOptions& clo, const ECScanManager& sm, ScanType st, std::shared_ptr pmp, const NEWIMAGE::volume& mask, unsigned int iter, unsigned int dl, // Output DiffStatsVector& stats) EddyTry { for (unsigned int s=first_vol; s pred = pmp->Predict(s); stats[s] = EddyUtils::GetSliceWiseStats(clo,st,pred,sm.GetSuscHzOffResField(s,st),mask,sm,s,iter,dl); } } EddyCatch /****************************************************************//** * * A global function that replaces outliers with predictions for a range * of scans. It is a wrappper to facilitate using the C++11 thread * library for parallelisation. * \param[in] first_vol Index of first volume to process * \param[in] last_vol Index one past the last volume to process * \param[in] pmp A safe pointer to a DWIPredictionMaker that is to * be loaded. * \param[i] mask * \param[in] st Specifies if we should update the diffusion weighted * images or the b=0 images. * \param[in] very_verbose Very Verbose flag * \param[in] add_noise Decides of noise should be added to predictions. * \param[in/out] sm Collection of all scans. * \return None * ********************************************************************/ void SetAsOutliersWrapper(// Input unsigned int first_vol, unsigned int last_vol, std::shared_ptr pmp, const NEWIMAGE::volume& mask, const ReplacementManager& rm, ScanType st, bool very_verbose, bool add_noise, // Input/output ECScanManager& sm) EddyTry { static std::mutex cout_mutex; for (unsigned int s=first_vol; s ol = rm.OutliersInScan(s); if (ol.size()) { // If the scan has outliers if (very_verbose) { cout_mutex.lock(); std::cout << "Scan " << sm.GetGlobalIndex(s,st) << " has outlier slices:"; for (unsigned int i=0; i pred = pmp->Predict(s,true); if (add_noise) { double vp = pmp->PredictionVariance(s,true); double ve = pmp->ErrorVariance(s); double stdev = std::sqrt(vp+ve) - std::sqrt(vp); pred += EddyUtils::MakeNoiseIma(pred,0.0,stdev); } sm.Scan(s,st).SetAsOutliers(pred,sm.GetSuscHzOffResField(s,st),mask,ol); } } } EddyCatch /* void WriteCNRMaps(// Input const EddyCommandLineOptions& clo, const ECScanManager& sm, const std::string& spatial_fname, const std::string& range_fname, const std::string& residual_fname) EddyTry { if (spatial_fname == std::string("") && residual_fname == std::string("")) throw EddyException("EDDY::WriteCNRMaps: At least one of spatial and residual fname must be set"); // Load prediction maker NEWIMAGE::volume mask = sm.Scan(0,ScanType::DWI).GetIma(); EddyUtils::SetTrilinearInterp(mask); mask = 1.0; // FOV-mask in model space #ifdef COMPILE_GPU std::shared_ptr dwi_pmp = EddyGpuUtils::LoadPredictionMaker(clo,ScanType::DWI,sm,0,0.0,mask); std::shared_ptr b0_pmp = EddyGpuUtils::LoadPredictionMaker(clo,ScanType::b0,sm,0,0.0,mask); #else std::shared_ptr dwi_pmp = EDDY::LoadPredictionMaker(clo,ScanType::DWI,sm,0,0.0,mask); std::shared_ptr b0_pmp = EDDY::LoadPredictionMaker(clo,ScanType::b0,sm,0,0.0,mask); #endif if (sm.IsShelled()) { if (spatial_fname != std::string("") || range_fname != std::string("")) { // Calculate and write shell-wise CNR maps std::vector grpb; std::vector > dwi_indx = sm.GetShellIndicies(grpb); // Global indicies of dwi scans std::vector > mvols(grpb.size()); // Calculate mean volumes of predictions for (unsigned int i=0; iPredict(sm.GetGlobal2DWIIndexMapping(dwi_indx[i][0])); for (unsigned int j=1; jPredict(sm.GetGlobal2DWIIndexMapping(dwi_indx[i][j])); } mvols[i] /= float(dwi_indx[i].size()); } // Calculate standard deviation volumes of predictions std::vector > stdvols(grpb.size()); for (unsigned int i=0; i tmp = dwi_pmp->Predict(sm.GetGlobal2DWIIndexMapping(dwi_indx[i][0])) - mvols[i]; stdvols[i] = tmp*tmp; for (unsigned int j=1; jPredict(sm.GetGlobal2DWIIndexMapping(dwi_indx[i][j])) - mvols[i]; stdvols[i] += tmp*tmp; } stdvols[i] /= float(dwi_indx[i].size()-1); stdvols[i] = NEWIMAGE::sqrt(stdvols[i]); } // Calculate range (min--max) volumes of predictions to make dHCP data seem like it has decent CNR std::vector > minvols(grpb.size()); std::vector > maxvols(grpb.size()); for (unsigned int i=0; iPredict(sm.GetGlobal2DWIIndexMapping(dwi_indx[i][0])); maxvols[i] = minvols[i]; for (unsigned int j=1; jPredict(sm.GetGlobal2DWIIndexMapping(dwi_indx[i][j]))); maxvols[i] = NEWIMAGE::max(maxvols[i],dwi_pmp->Predict(sm.GetGlobal2DWIIndexMapping(dwi_indx[i][j]))); } maxvols[i] -= minvols[i]; // Maxvols now contain the range rather than the max } // Calculate standard deviation of residuals std::vector > stdres(grpb.size()); for (unsigned int i=0; i tmp = dwi_pmp->Predict(sm.GetGlobal2DWIIndexMapping(dwi_indx[i][0])) - dwi_pmp->InputData(sm.GetGlobal2DWIIndexMapping(dwi_indx[i][0])); stdres[i] = tmp*tmp; for (unsigned int j=1; jPredict(sm.GetGlobal2DWIIndexMapping(dwi_indx[i][j])) - dwi_pmp->InputData(sm.GetGlobal2DWIIndexMapping(dwi_indx[i][j])); stdres[i] += tmp*tmp; } stdres[i] /= float(dwi_indx[i].size()-1); stdres[i] = NEWIMAGE::sqrt(stdres[i]); } // Divide std and range of predictions with std of residuals to get CNR for (unsigned int i=0; i b0_indx = sm.GetB0Indicies(); NEWIMAGE::volume b0_SNR = b0_pmp->Predict(0) - b0_pmp->InputData(0); if (b0_indx.size() > 1) { b0_SNR *= b0_SNR; for (unsigned int i=1; i tmp = b0_pmp->Predict(i) - b0_pmp->InputData(i); b0_SNR += tmp*tmp; } b0_SNR /= float(b0_indx.size()-1); b0_SNR = NEWIMAGE::sqrt(b0_SNR); b0_SNR = b0_pmp->Predict(0) /= b0_SNR; } else b0_SNR = 0.0; // Set it to zero if we can't assess it. // Put SNR and CNR maps together into 4D file with spatial CNR NEWIMAGE::volume4D spat_cnr(stdvols[0].xsize(),stdvols[0].ysize(),stdvols[0].zsize(),stdvols.size()+1); NEWIMAGE::copybasicproperties(stdvols[0],spat_cnr); spat_cnr[0] = b0_SNR; if (spatial_fname != std::string("")) { for (unsigned int i=0; i residuals(dwi_pmp->InputData(0).xsize(),dwi_pmp->InputData(0).ysize(),dwi_pmp->InputData(0).zsize(),sm.NScans(ScanType::Any)); NEWIMAGE::copybasicproperties(dwi_pmp->InputData(0),residuals); for (unsigned int i=0; iInputData(i) - dwi_pmp->Predict(i); } for (unsigned int i=0; iInputData(i) - b0_pmp->Predict(i); } NEWIMAGE::write_volume(residuals,residual_fname); } } else { throw EddyException("WriteCNRMaps: Cannot calculate CNR for non-shelled data."); } } EddyCatch */ void Diagnostics(// Input const EddyCommandLineOptions& clo, unsigned int iter, ScanType st, const ECScanManager& sm, const double *mss_tmp, const DiffStatsVector& stats, const ReplacementManager& rm, // Output NEWMAT::Matrix& mss, NEWMAT::Matrix& phist) EddyTry { if (clo.Verbose()) { double tss=0.0; for (unsigned int s=0; s dir(6); dir[0]="xt"; dir[1]="yt"; dir[2]="zt"; dir[3]="xr"; dir[4]="yr"; dir[5]="zr"; // First write 1D profiles along main directions for (unsigned int i=0; i<6; i++) { std::vector n(6,1); n[i] = 100; std::vector first(6,0.0); first[i] = -2.5; std::vector last(6,0.0); last[i] = 2.5; if (clo.VeryVerbose()) cout << "Writing MI values for direction " << i << endl; PEASUtils::WritePostEddyBetweenShellMIValues(clo,sm,n,first,last,fname+"_"+dir[i]); } // Write 2D planes if requested for (unsigned int i=0; i<6; i++) { for (unsigned int j=i+1; j<6; j++) { std::vector n(6,1); n[i] = 20; n[j] = 20; std::vector first(6,0.0); first[i] = -1.0; first[j] = -1.0; std::vector last(6,0.0); last[i] = 1.0; last[j] = 1.0; if (clo.VeryVerbose()) cout << "Writing MI values for plane " << i << "-" << j << endl; PEASUtils::WritePostEddyBetweenShellMIValues(clo,sm,n,first,last,fname+"_"+dir[i]+"_"+dir[j]); } } } EddyCatch void json_out_struct::Write(const std::string& fname) const EddyTry { nlohmann::json json_file; json_file["Shell indicies"] = this->shell_indicies; json_file["Outlier report"] = this->outlier_report; json_file["Outlier maps"] = this->outlier_maps; json_file["Parameters"] = this->parameters; json_file["Movement over time"] = this->movement_over_time; json_file["Movement RMS"] = this->movement_rms; json_file["Restricted movement RMS"] = this->restricted_movement_rms; json_file["Long time-constant EC parameters"] = this->long_ec_parameters; try { std::ofstream ofile(fname); ofile << std::setw(4) << json_file << std::endl; ofile.close(); } catch(const std::exception& e) { throw EddyException("json_out_struct::Write: Exception thrown with message " + std::string(e.what())); } } EddyCatch void WritePMDebugInfo(std::shared_ptr pmp, const EddyCommandLineOptions& clo, EDDY::ScanType st, unsigned int iter, const std::string& arch, const std::string& when) EddyTry { std::string prefix("EDDY_DEBUG_"); prefix += arch; if (st==ScanType::b0) prefix += "b0"; else if (st==ScanType::DWI) prefix += "DWI"; else if (st==ScanType::fMRI) prefix += "fMRI"; if (clo.DebugLevel() > 2) { char fname[256]; sprintf(fname,"%s_K_Mat_Data_%s_%02d",prefix.c_str(),when.c_str(),iter); pmp->WriteMetaData(fname); } if (clo.DebugLevel() > 3) { char fname[256]; sprintf(fname,"%s_K_Mat_Ima_%s_%02d",prefix.c_str(),when.c_str(),iter); pmp->WriteImageData(fname); } } EddyCatch } // End namespace EDDY /*! \mainpage * Here goes a description of the eddy project */