///////////////////////////////////////////////////////////////////// /// \file ECScanClasses.h /// \brief Declarations of classes that implements a scan or a collection of scans within the EC project. /// /// \author Jesper Andersson /// \version 1.0b, Sep., 2012. /// \Copyright (C) 2012 University of Oxford /// ///////////////////////////////////////////////////////////////////// // // Copyright (C) 2011 University of Oxford // #pragma GCC diagnostic ignored "-Wunknown-pragmas" // Ignore the OpenMP pragmas #ifndef ECScanClasses_h #define ECScanClasses_h #include #include #include #include #include #include "armawrap/newmat.h" #include "newimage/newimageall.h" #include "miscmaths/miscmaths.h" #include "EddyHelperClasses.h" #include "ECModels.h" #include "LongECModels.h" // Cumbersome forward declaration of nlohmann::json to avoid exposing it to the nvcc compiler // I _hope_ the macro below doesn't change in json.hpp #ifndef INCLUDE_NLOHMANN_JSON_HPP_ namespace nlohmann { class json; } #endif namespace EDDY { ///////////////////////////////////////////////////////////////////// /// /// \brief This class manages one diffusion weighted or b=0 scan /// for the eddy project. /// ///////////////////////////////////////////////////////////////////// class ECScan { public: /// Constructs an object for an fMRI volume. N.B. these are classed as b0 scans ECScan(const NEWIMAGE::volume& ima, const AcqPara& acqp, const ScanMovementModel& mp, const MultiBandGroups& mbg, int si, double mrl=1.0) EddyTry : _ima(ima), _ols(ima.zsize(),0ul), _acqp(acqp), _diffp(EDDY::DiffPara(-100.0)), _mp(mp), _mbg(mbg), _ecp(std::make_shared()), _lml(1.0e-6), _pp(EDDY::PolationPara()), _mrl(mrl), _si(si), _prev(nullptr), _wp(mbg.NGroups(),arma::fill::zeros), _wc(mbg.NGroups(),arma::fill::ones) {} EddyCatch /// Constructs an object from volume, acquisition parameters, diffusion parameters, EC-Model and session number. ECScan(const NEWIMAGE::volume& ima, const AcqPara& acqp, const DiffPara& diffp, const ScanMovementModel& mp, const MultiBandGroups& mbg, std::shared_ptr ecp, int si, const ECScan* prev=nullptr, double mrl=1.0) EddyTry : _ima(ima), _ols(ima.zsize(),0ul), _acqp(acqp), _diffp(diffp), _mp(mp), _mbg(mbg), _ecp(ecp->Clone()), _lml(1.0e-6), _pp(EDDY::PolationPara()), _mrl(mrl), _si(si), _prev(prev), _wp(mbg.NGroups(),arma::fill::zeros), _wc(mbg.NGroups(),arma::fill::ones) {} EddyCatch /// Copy constructor. ECScan(const ECScan& inp) EddyTry : _ima(inp._ima), _ols(inp._ols), _acqp(inp._acqp), _diffp(inp._diffp), _mp(inp._mp), _mbg(inp._mbg), _ecp(inp._ecp->Clone()), _lml(inp._lml), _pp(inp._pp), _mrl(inp._mrl), _si(inp._si), _prev(inp._prev), _wp(inp._wp), _wc(inp._wc) { for (int sl=0; sl<_ima.zsize(); sl++) { if (_ols[sl]) { _ols[sl] = new float[_ima.xsize()*_ima.ysize()]; std::memcpy(_ols[sl],inp._ols[sl],_ima.xsize()*_ima.ysize()*sizeof(float)); } } } EddyCatch virtual ~ECScan() { for (int sl=0; sl<_ima.zsize(); sl++) { if (_ols[sl]) delete[] _ols[sl]; } } /// Assignment. ECScan& operator=(const ECScan& rhs) EddyTry { if (this == &rhs) return(*this); _ima=rhs._ima; _ols=rhs._ols; _acqp=rhs._acqp; _diffp=rhs._diffp; _mp=rhs._mp; _mbg=rhs._mbg; _ecp=rhs._ecp->Clone(); _lml=rhs._lml; _pp=rhs._pp; _mrl=rhs._mrl; _si=rhs._si; _prev=rhs._prev; _wp=rhs._wp; _wc=rhs._wc; for (int sl=0; sl<_ima.zsize(); sl++) { if (_ols[sl]) { _ols[sl] = new float[_ima.xsize()*_ima.ysize()]; std::memcpy(_ols[sl],rhs._ols[sl],_ima.xsize()*_ima.ysize()*sizeof(float)); } } return(*this); } EddyCatch /// Returns the EC model being used. ECModelType Model() const EddyTry { return(_ecp->WhichModel()); } EddyCatch /// Set movement model order (0 for rigid body model). void SetMovementModelOrder(unsigned int order) EddyTry { if (int(order)>_ima.zsize()) throw EddyException("ECScan::SetMovementModelOrder: order too high"); else _mp.SetOrder(order); } EddyCatch /// Get movement model order (0 for rigid body model) unsigned int GetMovementModelOrder() const EddyTry { return(_mp.Order()); } EddyCatch /// Get read/write reference to movement model ScanMovementModel& GetMovementModel() EddyTry { return(_mp); } EddyCatch /// Returns true if model order > 0 bool IsSliceToVol() const EddyTry { return(GetMovementModelOrder()); } EddyCatch /// Returns pointer to preceeding ECScan const ECScan* PreviousPointer() const EddyTry { return(this->_prev); } EddyCatch /// Returns across slice/group std of movement parameter double GetMovementStd(unsigned int mi) const EddyTry { std::vector empty; return(this->GetMovementStd(mi,empty)); } EddyCatch /// Returns across slice std of movement parameter. Only valid to call with non-empty icsl if no multi-band double GetMovementStd(unsigned int mi, std::vector icsl) const; /// Returns session number int Session() const EddyTry { return(_si); } EddyCatch /// Returns multi-band info const MultiBandGroups& GetMBG() const EddyTry { return(_mbg); } EddyCatch /// Returns true if any slices has been replaced bool HasOutliers() const EddyTry { for (unsigned int i=0; i<_ols.size(); i++) { if (_ols[i]) return(true); } return(false); } EddyCatch /// Returns true if slice sl has been replaced bool IsOutlier(unsigned int sl) const EddyTry { if (_ols.at(sl)) return(true); else return(false); } EddyCatch /// Will return true if an offset (DC part) is included in the EC model. bool HasFieldOffset() const EddyTry { return(_ecp->HasFieldOffset()); } EddyCatch /// Returns the field offset (in Hz). double GetFieldOffset() const EddyTry { return(_ecp->GetFieldOffset()); } EddyCatch /// Sets the field offset. The value of ofst should be in Hz. void SetFieldOffset(double ofst) EddyTry { _ecp->SetFieldOffset(ofst); } EddyCatch /// Get inter/extrapolation options PolationPara GetPolation() const EddyTry { return(_pp); } EddyCatch /// Set inter/extrapolation options void SetPolation(const PolationPara& pp); /// Checks for the presence of empty planes in the x- and y-directions. bool HasEmptyPlane(std::vector& pi) const; /// Fills empty planes in the x- and y-directions. void FillEmptyPlane(const std::vector& pi); /// Returns the original ("unreplaced" and untransformed) volume. NEWIMAGE::volume GetOriginalIma() const; /// Returns the original ("unreplaced") volume after correction for rigid-body movement. NEWIMAGE::volume GetMotionCorrectedOriginalIma(NEWIMAGE::volume& omask) const EddyTry { return(motion_correct(GetOriginalIma(),&omask)); } EddyCatch /// Returns the original ("unreplaced") volume after correction for rigid-body movement. NEWIMAGE::volume GetMotionCorrectedOriginalIma() const EddyTry { return(motion_correct(GetOriginalIma(),NULL)); } EddyCatch /// Returns the original (unsmoothed) volume after correction for rigid-body movement and EC distortions. NEWIMAGE::volume GetUnwarpedOriginalIma(// Input std::shared_ptr > susc, const NEWIMAGE::volume& pred, // Output NEWIMAGE::volume& omask) const; /// Returns the original (unsmoothed) volume after correction for rigid-body movement and EC distortions. NEWIMAGE::volume GetUnwarpedOriginalIma(// Input std::shared_ptr > susc, // Output NEWIMAGE::volume& omask) const; /// Returns the original (unsmoothed) volume after correction for rigid-body movement and EC distortions. NEWIMAGE::volume GetUnwarpedOriginalIma(// Input std::shared_ptr > susc) const; /// Returns the volume (with replacements) in the original space (no corrections) const NEWIMAGE::volume& GetIma() const EddyTry { return(_ima); } EddyCatch /// Returns the ("outlier replaced") volume after correction for rigid-body movement. NEWIMAGE::volume GetMotionCorrectedIma(NEWIMAGE::volume& omask) const EddyTry { return(motion_correct(GetIma(),&omask)); } EddyCatch /// Returns the ("outlier replaced") volume after correction for rigid-body movement. NEWIMAGE::volume GetMotionCorrectedIma() const EddyTry { return(motion_correct(GetIma(),NULL)); } EddyCatch /// Returns the smoothed and corrected (for movement and EC distortions) volume. NEWIMAGE::volume GetUnwarpedIma(// Input std::shared_ptr > susc, const NEWIMAGE::volume pred, // Output NEWIMAGE::volume& omask) const; /// Returns the smoothed and corrected (for movement and EC distortions) volume. NEWIMAGE::volume GetUnwarpedIma(// Input std::shared_ptr > susc, // Output NEWIMAGE::volume& omask) const; /// Returns the smoothed and corrected (for movement and EC distortions) volume. NEWIMAGE::volume GetUnwarpedIma(// Input std::shared_ptr > susc) const; /// Return volumetrically unwarped ima (for when s2v is implemented on the CPU) with optional derivative images NEWIMAGE::volume GetVolumetricUnwarpedIma(// Input std::shared_ptr > susc, // Output NEWIMAGE::volume4D& deriv) const; /// Returns the acquisition parameters for the scan AcqPara GetAcqPara() const EddyTry { return(_acqp); } EddyCatch /// Returns the diffusion parameters for the scan DiffPara GetDiffPara(bool rot=false) const; /// Return the diffusion parameters for a slice within a scan DiffPara GetDiffPara(unsigned int sl, bool rot=true) const; /// Returns a vector of displacements (in mm) from a field offset of 1Hz NEWMAT::ColumnVector GetHz2mmVector() const; /// Return Levenberg lambda for estimation of vol2vol movement and EC double GetLevenbergLambda() const EddyTry { return(_lml); } EddyCatch /// Set Levenberg lambda for estimation of vol2vol movement and EC void SetLevenbergLambda(double lml) EddyTry { _lml=lml; } EddyCatch /// Reset Levenberg lambda to default value void ResetLevenbergLambda() EddyTry { _lml=1.0e-6; } EddyCatch /// Number of parameters for the EC model (including rigid body movement). unsigned int NParam(ParametersType whichp=ParametersType::All) const EddyTry { if (whichp==ParametersType::ZeroOrderMovement) return(static_cast(_mp.GetZeroOrderParams().Nrows())); if (whichp==ParametersType::Movement) return(_mp.NParam()); else if (whichp==ParametersType::EC) return(_ecp->NParam()); else return(_mp.NParam()+_ecp->NParam()); } EddyCatch /// Returns all the current parameters for the EC model. NEWMAT::ColumnVector GetParams(ParametersType whichp=ParametersType::All) const EddyTry { if (whichp==ParametersType::ZeroOrderMovement) return(_mp.GetZeroOrderParams()); else if (whichp==ParametersType::Movement) return(_mp.GetParams()); else if (whichp==ParametersType::EC) return(_ecp->GetParams()); else return(_mp.GetParams() & _ecp->GetParams()); } EddyCatch /// Number of parameters that are being optimised in the current ScanECModel. unsigned int NDerivs(ParametersType whichp=ParametersType::All) const EddyTry { if (whichp==ParametersType::Movement) return(_mp.NDerivs()); else if (whichp==ParametersType::EC) return(_ecp->NDerivs()); else return(_mp.NDerivs() + _ecp->NDerivs()); } EddyCatch /// Number of "compound" derivatives. See ECModels.h for an explanation of "compound" unsigned int NCompoundDerivs(ParametersType whichp=ParametersType::All) const EddyTry { if (whichp==ParametersType::Movement) return(_mp.NCompoundDerivs()); else if (whichp==ParametersType::EC) return(_ecp->NCompoundDerivs()); else return(_mp.NCompoundDerivs() + _ecp->NCompoundDerivs()); } EddyCatch /// Set lambda (weight) of regularisation cost for DCT movement void SetRegLambda(double lambda) { _mrl=lambda; } /// Return lambda (weight) of regularisation cost for DCT movement double GetRegLambda() const { return(_mrl); } /// Return value of regularisation cost for DCT movement double GetReg(ParametersType whichp=ParametersType::All) const; /// Return gradient of regularisation cost for DCT movement NEWMAT::ColumnVector GetRegGrad(ParametersType whichp=ParametersType::All) const; /// Return Hessian of regularisation cost for DCT movement NEWMAT::Matrix GetRegHess(ParametersType whichp=ParametersType::All) const; /// Get the parameter (of those being optimised) given by indx double GetDerivParam(unsigned int indx, ParametersType whichp=ParametersType::All, bool allow_field_offset=false) const; /// Get the scale for the parameter (of those being optimised) given by indx double GetDerivScale(unsigned int indx, ParametersType whichp=ParametersType::All, bool allow_field_offset=false) const; /// Get indx'th set of instructions for "compound" derivatives. See ECModels.h for an explanation of "compound" EDDY::DerivativeInstructions GetCompoundDerivInstructions(unsigned int indx, ParametersType whichp=ParametersType::All) const; /// Set/Update the parameter (of those being optimised) given by indx void SetDerivParam(unsigned int indx, double p, ParametersType whichp=ParametersType::All, bool allow_field_offset=false); /// Returns matrix denoted \f$\mathbf{R}\f$ in paper. Returns const part in the case of SliceToVol. NEWMAT::Matrix ForwardMovementMatrix() const EddyTry { return(_mp.ForwardMovementMatrix(_ima)); } EddyCatch /// Returns matrix denoted \f$\mathbf{R}\f$ in paper, but only for group of slices indicated by grp NEWMAT::Matrix ForwardMovementMatrix(unsigned int grp) const EddyTry { return(_mp.ForwardMovementMatrix(_ima,grp,_mbg.NGroups())); } EddyCatch /// Returns matrix denoted \f$\mathbf{R}\f$ in paper, but excludes parameters indicated by rindx. Returns const part in the case of SliceToVol. NEWMAT::Matrix RestrictedForwardMovementMatrix(const std::vector& rindx) const EddyTry { return(_mp.RestrictedForwardMovementMatrix(_ima,rindx)); } EddyCatch /// Returns matrix denoted \f$\mathbf{R}\f$ in paper, but only for group of slices indicated by grp. Excludes parameters indicated by rindx. NEWMAT::Matrix RestrictedForwardMovementMatrix(unsigned int grp, const std::vector& rindx) const EddyTry { return(_mp.RestrictedForwardMovementMatrix(_ima,grp,_mbg.NGroups(),rindx)); } EddyCatch /// Returns matrix denoted \f$\mathbf{R}^{-1}\f$ in paper. Returns const part in the case of SliceToVol. NEWMAT::Matrix InverseMovementMatrix() const EddyTry { return(_mp.InverseMovementMatrix(_ima)); } EddyCatch /// Returns matrix denoted \f$\mathbf{R}^{-1}\f$ in paper, but only for group of slices indicated by grp NEWMAT::Matrix InverseMovementMatrix(unsigned int grp) const EddyTry { return(_mp.InverseMovementMatrix(_ima,grp,_mbg.NGroups())); } EddyCatch /// Returns matrix denoted \f$\mathbf{R}^{-1}\f$ in paper, but excludes parameters indicated by rindx. Returns const part in the case of SliceToVol. NEWMAT::Matrix RestrictedInverseMovementMatrix(const std::vector& rindx) const EddyTry { return(_mp.RestrictedInverseMovementMatrix(_ima,rindx)); } EddyCatch /// Returns matrix denoted \f$\mathbf{R}^{-1}\f$ in paper, but only for group of slices indicated by grp. Excludes parameters indicated by rindx. NEWMAT::Matrix RestrictedInverseMovementMatrix(unsigned int grp, const std::vector& rindx) const EddyTry { return(_mp.RestrictedInverseMovementMatrix(_ima,grp,_mbg.NGroups(),rindx)); } EddyCatch /// Returns the actual sampling points of all the points in the model space EDDY::ImageCoordinates SamplingPoints() const; /// Returns the EC-field relevant for the Observation->Model transform. NEWIMAGE::volume ECField() const; /// Returns the total field (in mm) relevant for the Observation->Model transform NEWIMAGE::volume4D FieldForScanToModelTransform(// Input std::shared_ptr > susc, // Output NEWIMAGE::volume& omask, NEWIMAGE::volume& jac) const EddyTry { return(field_for_scan_to_model_transform(susc,&omask,&jac)); } EddyCatch /// Returns the total field (in mm) relevant for the Observation->Model transform NEWIMAGE::volume4D FieldForScanToModelTransform(// Input std::shared_ptr > susc, // Output NEWIMAGE::volume& omask) const EddyTry { return(field_for_scan_to_model_transform(susc,&omask,NULL)); } EddyCatch /// Returns the total field relevant for the Observation->Model transform NEWIMAGE::volume4D FieldForScanToModelTransform(std::shared_ptr > susc) const EddyTry { return(field_for_scan_to_model_transform(susc,NULL,NULL)); } EddyCatch /// Returns the total field relevant for the Model->Observation transform NEWIMAGE::volume4D FieldForModelToScanTransform(// Input std::shared_ptr > susc, // Output NEWIMAGE::volume& omask, NEWIMAGE::volume& jac) const EddyTry { return(field_for_model_to_scan_transform(susc,&omask,&jac)); } EddyCatch /// Returns the total field relevant for the Model->Observation transform NEWIMAGE::volume4D FieldForModelToScanTransform(// Input std::shared_ptr > susc, // Output NEWIMAGE::volume& omask) const EddyTry { return(field_for_model_to_scan_transform(susc,&omask,NULL)); } EddyCatch /// Returns the total field relevant for the Model->Observation transform NEWIMAGE::volume4D FieldForModelToScanTransform(std::shared_ptr > susc) const EddyTry { return(field_for_model_to_scan_transform(susc,NULL,NULL)); } EddyCatch /// Returns the total field relevant for the Model->Observation transform and the Jacobian associated with it. NEWIMAGE::volume4D FieldForModelToScanTransformWithJac(// Input std::shared_ptr > susc, // Output NEWIMAGE::volume& jac) const EddyTry { return(field_for_model_to_scan_transform(susc,NULL,&jac)); } EddyCatch /// Returns the total displacements (including subject movement) relevant for the Observation->Model transform. NEWIMAGE::volume4D TotalDisplacementToModelSpace(std::shared_ptr > susc) const EddyTry { return(total_displacement_to_model_space(GetOriginalIma(),susc)); } EddyCatch /// Returns the movement induced displacements relevant for the Observation->Model transform. NEWIMAGE::volume4D MovementDisplacementToModelSpace(std::shared_ptr > susc) const EddyTry { return(total_displacement_to_model_space(GetOriginalIma(),susc,true)); } EddyCatch /// Returns the movement induced displacements relevant for the Observation->Model transform, excluding any translation in PE-direction. NEWIMAGE::volume4D RestrictedMovementDisplacementToModelSpace(std::shared_ptr > susc) const EddyTry { return(total_displacement_to_model_space(GetOriginalIma(),susc,true,true)); } EddyCatch /// Set/update the parameters for the EC model. void SetParams(const NEWMAT::ColumnVector& mpep, ParametersType whichp=ParametersType::All); /// Set the movement trace (movement over time) for slice-to-vol void SetS2VMovement(const NEWMAT::Matrix& mt); /// Get the weights for the EC-field from the previous volume const arma::colvec& GetWeightsForPrevious() const EddyTry { return(_wp); } EddyCatch /// Get the weights for the EC-field from the current volume const arma::colvec& GetWeightsForCurrent() const EddyTry { return(_wc); } EddyCatch /// Replace selected slices AND recycle previous outliers. rep should be in model space. void SetAsOutliers(const NEWIMAGE::volume& rep, std::shared_ptr > susc, const NEWIMAGE::volume& inmask, const std::vector& ol); /// Replace selected slices AND recycle previous outliers. rep should be in observation space. void SetAsOutliers(const NEWIMAGE::volume& rep, const NEWIMAGE::volume& mask, const std::vector& ol); /// Bring back previously discarded slices into _ima void RecycleOutliers(); /// Return a volume with _only_ those slices set as outliers being non-zero NEWIMAGE::volume GetOutliers() const; private: NEWIMAGE::volume _ima; ///< The original volume, possibly with (outlier) replacements. std::vector _ols; ///< Discarded (for now) slices. AcqPara _acqp; ///< The acquisition parameters associated with the volume. DiffPara _diffp; ///< The diffusion parameters associated with the volume. ScanMovementModel _mp; ///< The movement model associated with the volume. EDDY::MultiBandGroups _mbg; ///< Multi-band structure std::shared_ptr _ecp; ///< The EC model. double _lml; ///< Levenberg-Marquardt lambda for estimation of vol2vol movement and EC EDDY::PolationPara _pp; ///< Inter/extrapolation parameters for volumetric and slice-to-vol double _mrl; ///< Lambda for slice-to-vol movement regularisation int _si; ///< Session index /// The next three variables are used when modelling time-varying eddy currents with long time constants. /// It is a "retro-fit", so potentially (definitely) not as elegant as one might wish. /// Much of the work for the long time constant EC modelling is done by sub-classes of LongECModel. /// The ScanManager will have an exemplar of one of those sub-classes. const ECScan* _prev; ///< Pointer to scan that was acquired immediately prior to this arma::colvec _wp; ///< Weight of the previous EC-field for each MB-group arma::colvec _wc; ///< Weight of the current EC-field for each MB-group NEWIMAGE::volume motion_correct(// Input const NEWIMAGE::volume& inima, // Output (optional) NEWIMAGE::volume *omask) const; NEWIMAGE::volume transform_to_model_space(// Input const NEWIMAGE::volume& inima, std::shared_ptr > susc, // Output NEWIMAGE::volume& omask, // Optional input bool jacmod=true) const; NEWIMAGE::volume transform_slice_to_vol_to_model_space(// Input const NEWIMAGE::volume& inima, std::shared_ptr > susc, const NEWIMAGE::volume *pred_ptr, // Output NEWIMAGE::volume& omask, // Optional input bool jacmod=true) const; void get_slice_stack_and_zcoords(// Input const NEWIMAGE::volume& inima, std::shared_ptr > susc, // Output NEWIMAGE::volume& slice_stack, NEWIMAGE::volume& z_coord, NEWIMAGE::volume& stack_mask, // Optional input bool jacmod=true) const; NEWIMAGE::volume transform_volumetric_to_model_space(// Input const NEWIMAGE::volume& inima, std::shared_ptr > susc, // Output NEWIMAGE::volume& omask, NEWIMAGE::volume4D& deriv, // Optional input bool jacmod=true) const; NEWIMAGE::volume4D total_displacement_to_model_space(// Input const NEWIMAGE::volume& inima, std::shared_ptr > susc, // Optional input bool movement_only=false, bool exclude_PE_tr=false) const; NEWIMAGE::volume4D field_for_scan_to_model_transform(// Input std::shared_ptr > susc, // Output NEWIMAGE::volume *omask, NEWIMAGE::volume *jac) const; void resample_and_combine_ec_and_susc_for_s2v(// Input std::shared_ptr > susc, // Output NEWIMAGE::volume& tot, NEWIMAGE::volume *omask) const; NEWIMAGE::volume4D field_for_model_to_scan_transform(// Input std::shared_ptr > susc, // Output NEWIMAGE::volume *omask, NEWIMAGE::volume *jac) const; bool in_list(unsigned int s, const std::vector& l) const EddyTry { for (unsigned int i=0; i& indicies, const std::vector& session_indicies, const EDDY::PolationPara& pp, EDDY::MultiBandGroups mbg, bool fsh); /// Constructor for the ECScanManager class for fMRI data. ECScanManager(const std::string& imafname, const std::string& maskfname, const std::string& acqpfname, const std::string& topupfname, const std::string& fieldfname, const std::string& field_mat_fname, double repetition_time, double echo_time, const std::vector& acqp_indicies, const std::vector& session_indicies, const EDDY::PolationPara& pp, EDDY::MultiBandGroups mbg); ~ECScanManager() {} /// Disable assignment and copy constructor to reduce risk of mistakes ECScanManager(const ECScanManager& sm) = delete; ECScanManager& operator=(const ECScanManager& sm) = delete; /// Force use of default move constructor ECScanManager(ECScanManager&&) = default; /// Number of scans of type st unsigned int NScans(ScanType st=ScanType::Any) const; /// Returns true if this is a diffusion data set bool IsDiffusion() const EddyTry { return(_is_diff); } EddyCatch /// Returns true if this is an fMRI data set. bool IsfMRI() const EddyTry { return(!IsDiffusion()); } EddyCatch /// Returns repetition time double RepetitionTime() const EddyTry { return(_tr); } EddyCatch /// Returns echo time double EchoTime() const EddyTry { return(_te); } EddyCatch /// Returns the type of model for long time constant (time varying) EC LongECModelType LongTimeConstantECModelType() const EddyTry { return(_lecm->Model()); } EddyCatch /// Returns a read-only reference to the model for long time constant (time varying) EC const LongECModel& LongTimeConstantECModel() const EddyTry { return(*_lecm); } EddyCatch /// Returns a read/write reference to the model for long time constant (time varying) EC LongECModel& LongTimeConstantECModel() EddyTry { return(*_lecm); } EddyCatch /// Get multi-band structure. Assumes the same MBG for all scans const MultiBandGroups& MultiBand() const EddyTry { return(Scan(0).GetMBG()); } EddyCatch /// Set movement model order for all scans void SetMovementModelOrder(unsigned int order) EddyTry { for (unsigned int i=0; i GetDiffParas(ScanType st=ScanType::Any) const; /// Returns a vector of indicies (valid for st=ScanType::Any) for b0-scans std::vector GetB0Indicies() const; /// Returns a vector of vectors of indicies (valid for st=ScanType::Any) into b!=0 shells. std::vector > GetShellIndicies(std::vector& bvals) const; /// Returns a vector of vectors of indicies (valid for st=ScanType::DWI) into b!=0 (i.e. DWI) shells. std::vector > GetDWIShellIndicies(std::vector& bvals) const; /// Returns a vector of indicies (valid for st=ScanType::Any) into fMRI scans. std::vector GetfMRIIndicies() const; /// Number of LSR pairs of type st unsigned int NLSRPairs(ScanType st=ScanType::Any) const; /// Return true if any scan has a PE component in the x-direction bool HasPEinX() const EddyTry { return(has_pe_in_direction(1)); } EddyCatch /// Return true if any scan has a PE component in the y-direction bool HasPEinY() const EddyTry { return(has_pe_in_direction(2)); } EddyCatch /// Return true if any scan has a PE component in the x- AND any scan in the y-direction bool HasPEinXandY() const EddyTry { return(HasPEinX() && HasPEinY()); } EddyCatch /// Returns true if the scan indicated by indx (zero-offset) is diffusion weighted. bool IsDWI(unsigned int indx) const EddyTry { if (indx>_fi.size()-1) throw EddyException("ECScanManager::IsDWI: index out of range"); else return(!_fi[indx].first); } EddyCatch /// Returns true if the scan indicated by indx (zero-offset) is a b=0 scan. bool IsB0(unsigned int indx) const EddyTry { return(!IsDWI(indx)); } EddyCatch /// Returns scale factor that has been applied to data prior to registration process. double ScaleFactor() const { return(_sf); } /// Sets inter- and extrapolation options void SetPolation(const PolationPara& pp) EddyTry { _pp=pp; set_polation(pp); } EddyCatch /// Returns inter- and extrapolation options PolationPara GetPolation() const EddyTry { return(_pp); } EddyCatch /// Fills empty planes in the x-y directions void FillEmptyPlanes(); /// Returns vector of Global (file) indicies for all dwi indicies. std::vector GetDwi2GlobalIndexMapping() const; /// Returns Global (file) index for specified dwi index. unsigned int GetDwi2GlobalIndexMapping(unsigned int dwindx) const; /// Returns vector of Global (file) indicies for all b0 indicies. std::vector Getb02GlobalIndexMapping() const; /// Returns Global (file) index for specified b0 index. unsigned int Getb02GlobalIndexMapping(unsigned int b0indx) const; /// Returns DWI index for specified global index unsigned int GetGlobal2DWIIndexMapping(unsigned int gindx) const; /// Returns b0 index for specified global index unsigned int GetGlobal2b0IndexMapping(unsigned int gindx) const; /// Returns gobal index given an index of type ts unsigned int GetGlobalIndex(unsigned int indx, EDDY::ScanType st) const { if (st==EDDY::ScanType::DWI) return(GetDwi2GlobalIndexMapping(indx)); else if (st==EDDY::ScanType::b0) return(Getb02GlobalIndexMapping(indx)); return(indx); } /// Returns current inter- and extrapolation methods PolationPara GetPolationMethods(); /// Sets inter- and extrapolation methods void SetPolationMethods(const PolationPara& pp); /// Returns true if b0's have been spaced out in a way that can help with dwi movements. bool B0sAreInterspersed() const; /// Returns true if we have enough interspersed B0s to base the Post Eddy Align Shells on it bool B0sAreUsefulForPEAS() const; /// Use b0 movement estimates as starting guess for dwis. void PolateB0MovPar(); /// Returns true if we want to use B0 movement estimates as starting guess for dwis. bool UseB0sToInformDWIRegistration() const { return(_use_b0_4_dwi); } /// Sets if we want to use B0 movement estimates as starting guess for dwis or not. void SetUseB0sToInformDWIRegistration(bool use_b0_4_dwi) { _use_b0_4_dwi = use_b0_4_dwi; } /// Returns true if a susceptibilty induced off-resonance field has been set bool HasSuscHzOffResField() const { return(_has_susc_field); } /// Returns true if a derivative susceptibility field has been set bool HasSuscHzOffResDerivField() const EddyTry { return(this->has_move_by_susc_fields()); } EddyCatch /// Returns true if a recieve bias field has been set bool HasBiasField() const { return(_bias_field.IsValid()); } /// Returns true if the EC model includes a field offset bool HasFieldOffset(ScanType st) const EddyTry { if (st==ScanType::b0 || st==ScanType::DWI) return(Scan(0,st).HasFieldOffset()); else return(Scan(0,ScanType::b0).HasFieldOffset() || Scan(0,ScanType::DWI).HasFieldOffset()); } EddyCatch /// Returns true if data allows for LSR resampling bool CanDoLSRResampling() const; /// Returns the individual indicies for scans that constitute the i'th LSR pair std::pair GetLSRPair(unsigned int i, ScanType st) const; /// Sets parameters for all scans void SetParameters(const NEWMAT::Matrix& pM, ScanType st=ScanType::Any); /// Sets parameters for all scans from values in file given by fname void SetParameters(const std::string& fname, ScanType st=ScanType::Any) EddyTry { NEWMAT::Matrix pM = MISCMATHS::read_ascii_matrix(fname); SetParameters(pM,st); } EddyCatch /// Sets S2V movement trace for all scans void SetS2VMovement(const NEWMAT::Matrix& s2v_pM, ScanType st=ScanType::Any); /// Sets S2V movement trace for all scans from values in file given by fname void SetS2VMovement(const std::string& fname, ScanType st=ScanType::Any) EddyTry { NEWMAT::Matrix s2v_pM = MISCMATHS::read_ascii_matrix(fname); SetS2VMovement(s2v_pM,st); } EddyCatch /// Returns a read-only reference to a scan given by indx const ECScan& Scan(unsigned int indx, ScanType st=ScanType::Any) const; /// Returns a read-write reference to a scan given by indx ECScan& Scan(unsigned int indx, ScanType st=ScanType::Any); // Returns an "original" (no smoothing) image "unwarped" into model space NEWIMAGE::volume GetUnwarpedOrigScan(unsigned int indx, const NEWIMAGE::volume& pred, NEWIMAGE::volume& omask, ScanType st=ScanType::Any) const; NEWIMAGE::volume GetUnwarpedOrigScan(unsigned int indx, NEWIMAGE::volume& omask, ScanType st=ScanType::Any) const; NEWIMAGE::volume GetUnwarpedOrigScan(unsigned int indx, ScanType st=ScanType::Any) const EddyTry { NEWIMAGE::volume mask=_scans[0].GetIma(); mask=1.0; return(GetUnwarpedOrigScan(indx,mask,st)); } EddyCatch // Returns an image "unwarped" into model space NEWIMAGE::volume GetUnwarpedScan(unsigned int indx, const NEWIMAGE::volume& pred, NEWIMAGE::volume& omask, ScanType st=ScanType::Any) const; NEWIMAGE::volume GetUnwarpedScan(unsigned int indx, NEWIMAGE::volume& omask, ScanType st=ScanType::Any) const; NEWIMAGE::volume GetUnwarpedScan(unsigned int indx, ScanType st=ScanType::Any) const EddyTry { NEWIMAGE::volume mask=_scans[0].GetIma(); mask=1.0; return(GetUnwarpedScan(indx,mask,st)); } EddyCatch /// Resamples a matching pair of images using least-squares resampling. NEWIMAGE::volume LSRResamplePair(// Input unsigned int i, unsigned int j, ScanType st, // Output NEWIMAGE::volume& omask) const; /// Sets movement and EC parameters for a scan given by indx void SetScanParameters(unsigned int indx, const NEWMAT::ColumnVector& p, ScanType st=ScanType::Any) EddyTry { Scan(indx,st).SetParams(p,ParametersType::All); } EddyCatch /// Add rotation to all scans (for testing rotation of b-vecs) void AddRotation(const std::vector& rot); /// Returns the user defined mask (that was passed to the constructor). const NEWIMAGE::volume& Mask() const EddyTry { return(_mask); } EddyCatch /// Returns a list of slices with more than nvox brain voxels (as assessed by _mask) std::vector IntraCerebralSlices(unsigned int nvox) const; /// Returns a pointer to susceptibility induced off-resonance field (in Hz). Returns NULL when no field set. std::shared_ptr > GetSuscHzOffResField() const EddyTry { return(_susc_field); } EddyCatch /// Returns a susceptibility induced off-resonance field for scan indx. Will be different from above only when modelling susc-by-movement. std::shared_ptr > GetSuscHzOffResField(unsigned int indx, ScanType st=ScanType::Any) const; /// Returns a multiplicative bias field. Returns nullptr when no field set std::shared_ptr > GetBiasField() const EddyTry { return(_bias_field.GetField()); } EddyCatch /// Gets value of current offset used for bias-field float GetBiasFieldOffset() const EddyTry { return(_bias_field.GetOffset()); } EddyCatch /// Set partial derivate field dfield w.r.t. movement parameter pi void SetDerivSuscField(unsigned int pi, const NEWIMAGE::volume& dfield); /// Set second partial derivate field dfield w.r.t. movement parameters pi and pj void Set2ndDerivSuscField(unsigned int pi, unsigned int pj, const NEWIMAGE::volume& dfield); /// Set bias field void SetBiasField(const NEWIMAGE::volume& bfield) EddyTry { _bias_field.SetField(bfield); } EddyCatch /// Set offset for bias field. Will not allow an offset that yields negative field values. bool SetBiasFieldOffset(float offset) EddyTry { return(_bias_field.SetOffset(offset)); } EddyCatch /// Set a scalefactor for bias field by prescribing a mean void SetBiasFieldMean(float mean) EddyTry { _bias_field.SetMean(mean); } EddyCatch /// Set a scalefactor for bias field by prescribing a mean within a mask void SetBiasFieldMean(float mean, const NEWIMAGE::volume& mask) EddyTry { _bias_field.SetMean(mean,mask); } EddyCatch /// Reset bias field, i.e. implicitly set it to one everywhere void ResetBiasField() EddyTry { _bias_field.Reset(); } EddyCatch /// Returns off-resonance field pertaining to EC only. Movements are not included and its main use is for visualiation/demonstration. NEWIMAGE::volume GetScanHzECOffResField(unsigned int indx, ScanType st=ScanType::Any) const EddyTry { return(Scan(indx,st).ECField()); } EddyCatch /// Separate field offset (image FOV centre not coinciding with scanner iso-centre) from actual subject movement in PE direction. void SeparateFieldOffsetFromMovement(ScanType st, OffsetModelType m=OffsetModelType::Linear); /// Recycle (bring back) all slices labeled as outliers void RecycleOutliers() EddyTry { for (unsigned int s=0; sGetPolation(); if (resmethod==EDDY::FinalResamplingType::Jac_NN) { PolationPara pp(NEWIMAGE::nearestneighbour,NEWIMAGE::periodic,true,NEWIMAGE::nearestneighbour); this->SetPolation(pp); } else { PolationPara pp(NEWIMAGE::spline,NEWIMAGE::periodic,true,NEWIMAGE::spline); this->SetPolation(pp); } if (resmethod==EDDY::FinalResamplingType::Jac) write_jac_registered_images(fname,maskfname,mask_output,nthreads,st); else if (resmethod==EDDY::FinalResamplingType::LSR) { write_lsr_registered_images(fname,LSR_lambda,st,nthreads); } else throw EddyException("ECScanManager::WriteRegisteredImages: Unknown resampling method"); this->SetPolation(old_pp); } EddyCatch /// Writes distortion corrected images to disk, using predictions to fill in slice-to-vol gaps void WriteRegisteredImages(const std::string& fname, const std::string& maskfname, FinalResamplingType resmethod, double LSR_lambda, bool mask_output, const NEWIMAGE::volume4D& pred, unsigned int nthreads, ScanType st=ScanType::Any) EddyTry { if (resmethod==EDDY::FinalResamplingType::LSR && !mask_output) throw EddyException("ECScanManager::WriteRegisteredImages: Must mask images when resampling method is LSR"); if (pred.tsize() != int(NScans(st))) throw EddyException("ECScanManager::WriteRegisteredImages: Size mismatch between pred and NScans"); PolationPara old_pp = this->GetPolation(); PolationPara pp(NEWIMAGE::spline,NEWIMAGE::periodic,true,NEWIMAGE::spline); this->SetPolation(pp); if (resmethod==EDDY::FinalResamplingType::Jac) write_jac_registered_images(fname,maskfname,mask_output,pred,nthreads,st); else if (resmethod==EDDY::FinalResamplingType::LSR) { std::cout << "ECScanManager::WriteRegisteredImages: Warning. Predictions provided for resampling method LSR." << std::endl; write_lsr_registered_images(fname,LSR_lambda,st,nthreads); } else throw EddyException("ECScanManager::WriteRegisteredImages: Unknown resampling method"); this->SetPolation(old_pp); } EddyCatch /// Writes file with movement and EC parameters. nlohmann::json WriteParameterFile(const std::string& fname, ScanType st=ScanType::Any, bool write_old_style_file=true) const; /// Writes file with one set of movement parameters per time-point (slice or MB-group) nlohmann::json WriteMovementOverTimeFile(const std::string& fname, ScanType st=ScanType::Any, bool write_old_style_file=true) const; /// Writes eddy-current induced fields to disc void WriteECFields(const std::string& fname, ScanType st=ScanType::Any) const; /// Writes rotated b-vecs (ascii) to disc void WriteRotatedBVecs(const std::string& fname, ScanType st=ScanType::Any) const; /// Writes b-vecs for SLR resampling to disc, rotated or not. void WriteBVecsForLSR(const std::string& fname, bool rot=true, ScanType st=ScanType::Any) const; /// Retruns a json object with movement induced RMS. Optionally writes it as an ascii file. nlohmann::json WriteMovementRMS(const std::string& fname, ScanType st=ScanType::Any, bool write_old_style_file=true) const; /// Writes an ascii file with movement induced RMS, but excluding any translation in the PE-direction nlohmann::json WriteRestrictedMovementRMS(const std::string& fname, ScanType st=ScanType::Any, bool write_old_style_file=true) const; /// Writes 3D displacement fields (in mm). void WriteDisplacementFields(const std::string& basefname, ScanType st=ScanType::Any) const; /// Writes original data with outliers replaced by predictions void WriteOutlierFreeData(const std::string& fname, ScanType st=ScanType::Any) const; /// Writes a .json file with shell indicies nlohmann::json WriteShellIndicies(const std::string& fname) const; /// Writes out data currently labeled as outliers. For debugging only. void WriteOutliers(const std::string& fname, ScanType st=ScanType::Any) const; /// Nested class used for the bias-field class BiasField { public: BiasField() : _rawfield(nullptr), _field(nullptr), _offset(0.0), _scale(1.0) {} bool IsValid() const { return(_field != nullptr); } void SetField(const NEWIMAGE::volume& field) { _rawfield = std::make_shared >(field); _field = std::make_shared >(field); _offset = 0.1 - _field->min(); // Ensure smallest value is 0.1 (*_field) += _offset; } bool SetOffset(float offs) { if (!this->IsValid()) throw EddyException("ScanManager::BiasField::SetOffset: Invalid bias-field"); if ((offs + _rawfield->min()) < 1e-6) return(false); else { _offset = offs; _scale = 1.0; (*_field) = _scale * ((*_rawfield) + offs); return(true); } } float GetOffset() const { return(_offset); } void SetMean(float mean) { if (!this->IsValid()) throw EddyException("ScanManager::BiasField::SetMean: Invalid bias-field"); _scale = (mean / _field->mean()); (*_field) *= _scale; } void SetMean(float mean, const NEWIMAGE::volume& mask) { if (!this->IsValid()) throw EddyException("ScanManager::BiasField::SetMean: Invalid bias-field"); if (!samesize((*_rawfield),mask)) throw EddyException("ScanManager::BiasField::SetMean: Size-mismatch between field and mask"); _scale = (mean / _field->mean(mask)); (*_field) *= _scale; } std::shared_ptr > GetField() const { return(_field); } void Reset() { if (_rawfield != nullptr) { _rawfield.reset(); _field.reset(); _offset=0.0; _scale=1.0;} } private: std::shared_ptr > _rawfield; std::shared_ptr > _field; float _offset; float _scale; }; // End of nested class BiasField private: bool _has_susc_field; ///< Is true if object contains a valid susceptibility field std::shared_ptr > _susc_field; ///< Susceptibility field (in Hz). std::vector > > _susc_d1; ///< First derivative susc fields in order xt yt zt xr yr zr std::vector > > > _susc_d2; ///< Second derivative susc fields. Organised as sub-diagonal matrix BiasField _bias_field; ///< Multiplicative recieve bias field NEWIMAGE::volume _mask; ///< User supplied mask double _sf; ///< Scale factor applied to scans std::vector > _fi; ///< Used to keep track of index into file std::vector _scans; ///< Vector of diffusion weighted scans std::vector _b0scans; ///< Vector of b=0 scans. std::vector _fmriscans; ///< Vector of fmri/GE-EPI scans ReferenceScans _refs; ///< Has info on location and shape references PolationPara _pp; ///< Inter- and extrapolation settings double _tr; ///< Repetition time double _te; ///< Echo time bool _fsh; ///< If true, user guarantees shelling bool _use_b0_4_dwi; ///< Decides if b0s are to inform dwi registration bool _is_diff; ///< True if it contains diffusion data std::shared_ptr _lecm; ///< Model for long time constant EC /// Extracts everything that can potentially be a field offset scaled in Hz. NEWMAT::ColumnVector hz_vector_with_everything(ScanType st) const; /// Creates a design matrix for separating movement and DC-component arma::mat design_matrix(EDDY::ScanType st, EDDY::OffsetModelType m) const; /// Creates a design matrix modelling parameters as a linear function of diffusion gradients arma::mat linear_design_matrix(ScanType st) const; /// Creates a design matrix modelling parameters as a linear function of one-lag diffusion gradients arma::mat linear_one_lag_design_matrix(ScanType st) const; /// Creates a design matrix modelling parameters as a quadratic function of diffusion gradients arma::mat quadratic_design_matrix(ScanType st) const; /// Creates a design matrix modelling parameters as a quadratic function of one-lag diffusion gradients arma::mat quadratic_one_lag_design_matrix(ScanType st) const; /// Demeans (columnwise) a matrix. Can be replaced by MISCMATHS::detrend if bug fixed in that NEWMAT::Matrix demean_matrix(const NEWMAT::Matrix& X) const; /// Returns a vector of movement parameters for the b=0 scans inter/extrapolated onto the dwis. NEWMAT::Matrix get_b0_movement_vector(ScanType st=ScanType::DWI) const; /// Sets scan indicated by ref as reference (position zero) for all scans of type st. void set_reference(unsigned int ref, ScanType st); /// Sets ref scan (as above) for shell si. Additionally also sets reference shape by ensuring ref has no higher order mp. void set_slice_to_vol_reference(unsigned int ref, ScanType st, int si=-1); double mean_of_first_b0(const NEWIMAGE::volume4D& vols, const NEWIMAGE::volume& mask, const NEWMAT::Matrix& bvecs, const NEWMAT::Matrix& bvals) const; bool index_kosher(unsigned int indx, ScanType st) const EddyTry { if (st==ScanType::DWI) return(indx<_scans.size()); else if (st==ScanType::b0) return(indx<_b0scans.size()); else return(indx<_fi.size()); } EddyCatch /// Sets inter/extrapolation parameters for all scans. void set_polation(const PolationPara& pp) EddyTry { for (unsigned int i=0; i<_scans.size(); i++) _scans[i].SetPolation(pp); for (unsigned int i=0; i<_b0scans.size(); i++) _b0scans[i].SetPolation(pp); } EddyCatch /// Writes registered images using Jacobian modulation. Has GPU implementation and multi-threaded implementations void write_jac_registered_images(const std::string& fname, const std::string& maskfname, bool mask_output, unsigned int nthreads, ScanType st) const; /// Helper function for multi-threaded implementation of the function above void get_unwarped_scan_wrapper(unsigned int first_vol, unsigned int last_vol, ScanType st, NEWIMAGE::volume4D& ovol, NEWIMAGE::volume& omask, NEWIMAGE::volume4D& mask_4D) const; /// Writes slice-to-vol registered images using Jacobian modulation. Has GPU implementation and multi-threaded implementations void write_jac_registered_images(const std::string& fname, const std::string& maskfname, bool mask_output, const NEWIMAGE::volume4D& pred, unsigned int nthreads, ScanType st) const; /// Helper function for multi-threaded implementation of the function above void get_unwarped_scan_s2v_wrapper(unsigned int first_vol, unsigned int last_vol, ScanType st, const NEWIMAGE::volume4D& pred, NEWIMAGE::volume4D& ovol, NEWIMAGE::volume& omask, NEWIMAGE::volume4D& mask_4D) const; void write_lsr_registered_images(const std::string& fname, double lambda, ScanType st, unsigned int nthreads) const; void get_unwarped_scan_lsr_wrapper(unsigned int first_pair, unsigned int last_pair, ScanType st, double lambda, NEWIMAGE::volume4D& ovol, NEWIMAGE::volume& omask) const; bool has_pe_in_direction(unsigned int dir, ScanType st=ScanType::Any) const; /// Brackets an index. A value of -1 of either element indicates end index. std::pair bracket(unsigned int i, const std::vector& ii) const; /// Will inter/extrapolate movement parameters. NEWMAT::ColumnVector interp_movpar(unsigned int i, const std::pair& br) const; /// Reads ascii file with rigid body (RB) matrix, and makes sure it is RB NEWMAT::Matrix read_rb_matrix(const std::string& fname) const; bool indicies_clustered(const std::vector& indicies, unsigned int N) const; /// Checks if there are any derivative fields bool has_move_by_susc_fields() const; }; } // End namespace EDDY #endif // End #ifndef ECScanClasses_h //////////////////////////////////////////////// // // Here starts Doxygen documentation // //////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////// /// /// \var ECScan::_ecp /// This is an object determining the model that is being used to model the eddy currents. /// It is stored as a (safe) pointer to a virtual base class ScanECModel. /// In any instantiation of an ECScan object it will contain an object of a class derived /// from ScanECModel, for example LinearScanECModel. This means that ECScan do not need to /// know which model is actually being used since it will only interact with _ecp through /// the interface specified by ScanECModel. /// /// \var ECScanManager::_fi /// This is an array of pairs keeping track of the relationship between on the one hand /// DWI and b0 indexing and on the other hand global indexing (corresponding to the indexing /// on disk). So if we for example have a diffusion weighted volume that is the i'th volume /// in the 4D file it was read from and that also is the j'th diffusion weighted volume then: /// _fi[i-1].first==0 (to indicate that it is diffusion weighted) and _fi[i-1].second==j-1 to /// indicate it is the j'th diffusion weighted volume. Correspondingly a b=0 volume that is /// the l'th volume on disc and the m'th b=0 volume then: _fi[l-1].first==1 and /// _fi[l-1].second==m-1. /// /////////////////////////////////////////////////////////////////////