/*! \file ECModels.h \brief Contains declaration of classes that implements models for fields from eddy currents. \author Jesper Andersson \version 1.0b, Sep., 2012. */ // Declarations of classes that implements a hirearchy // of models for fields from eddy currents induced by // diffusion gradients. // // ECModels.h // // Jesper Andersson, FMRIB Image Analysis Group // // Copyright (C) 2011 University of Oxford // #ifndef ECModels_h #define ECModels_h #include #include #include #include #include #include "armawrap/newmat.h" #include "newimage/newimageall.h" #include "miscmaths/miscmaths.h" #include "EddyHelperClasses.h" namespace EDDY { class SliceDerivModulator { public: SliceDerivModulator() {} SliceDerivModulator(unsigned int n) : _mod(n), _set(n,false) {} SliceDerivModulator(const std::vector& mod) : _mod(mod), _set(mod.size(),true) {} void SetMod(float val, unsigned int i) EddyTry { check(i,"SetMod"); _mod[i]=val; _set[i]=true; } EddyCatch const std::vector& GetMod() const EddyTry { if (!this->AllSet()) throw EddyException("SliceDerivModulator::GetMod: Invalid object"); else return(_mod); } EddyCatch float GetMod(unsigned int i) const EddyTry { check(i,"SetMod"); return(_mod[i]); } EddyCatch bool AllSet() const EddyTry { return(all_of(_set.begin(),_set.end(),[](bool x)->bool{return(x);})); } EddyCatch private: std::vector _mod; std::vector _set; void check(unsigned int i, const std::string& caller) const EddyTry { if (i >= _mod.size()) throw EddyException("SliceDerivModulator::"+caller+": Index out of range"); } EddyCatch }; class SpatialDerivModulator { public: SpatialDerivModulator() : _mod{1,1,1} {} // Default constructor yields invalid modulation SpatialDerivModulator(const std::vector& mod) : _mod(mod) {} const std::vector& GetModulation() const { if (std::all_of(_mod.begin(),_mod.end(),[](int i){return(i==1);})) throw EddyException("SpatialDerivModulator::GetModulation: Invalid modulation object"); return(_mod); } private: std::vector _mod; }; enum class ModulationType { SliceWise, Spatial }; class DerivativeInstructions { public: DerivativeInstructions(unsigned int primi, float prims, ModulationType mt, unsigned int nscnd) : _prim(primi,prims), _mt(mt), _scnd(nscnd), _set(nscnd,false) {} unsigned int GetPrimaryIndex() const { return(_prim._index); } float GetPrimaryScale() const { return(_prim._scale); } unsigned int NSecondary() const { return(_scnd.size()); } bool AllSet() const EddyTry { return(all_of(_set.begin(),_set.end(),[](bool x)->bool{return(x);})); } EddyCatch bool IsSliceMod() const { return(_mt == ModulationType::SliceWise); } bool IsSpatiallyMod() const { return(!this->IsSliceMod()); } unsigned int GetSecondaryIndex(unsigned int i) const EddyTry { this->check_index(i,"GetSecondaryIndex"); if (this->AllSet()) return(_scnd[i]._index); else throw EddyException("DerivativeInstructions::GetSecondaryIndex: Invalid object"); } EddyCatch SpatialDerivModulator GetSpatialModulator(unsigned int i) const EddyTry { this->check_index(i,"GetSpatialModulator"); if (this->AllSet()) return(_scnd[i]._spmod); else throw EddyException("DerivativeInstructions::GetSpatialModulator: Invalid object"); } EddyCatch SliceDerivModulator GetSliceModulator(unsigned int i) const EddyTry { this->check_index(i,"GetSliceModulator"); if (this->AllSet()) return(_scnd[i]._slmod); else throw EddyException("DerivativeInstructions::GetSliceModulator: Invalid object"); } EddyCatch void SetSecondary(unsigned int i, unsigned int index, const SliceDerivModulator& sdm); void SetSecondary(unsigned int i, unsigned int index, const SpatialDerivModulator& sdm); void AddIndexOffset(unsigned int offs) EddyTry { if (this->AllSet()) { _prim._index += offs; for (unsigned int i=0; iNSecondary(); i++) _scnd[i]._index += offs; } else throw EddyException("DerivativeInstructions::AddIndexOffset: Invalid object"); } EddyCatch private: struct Primary { Primary(unsigned int index, float scale) : _index(index), _scale(scale) {} unsigned int _index; float _scale; }; struct Secondary { unsigned int _index; SliceDerivModulator _slmod; SpatialDerivModulator _spmod; }; private: Primary _prim; ModulationType _mt; std::vector _scnd; std::vector _set; void check_index(unsigned int i, const std::string& caller) const EddyTry { if (i>=_scnd.size()) throw EddyException("DerivativeInstructions::"+caller+": Index out of range"); } EddyCatch }; /****************************************************************//** * * \brief Class that is used to manage the movement model/parameters * for the eddy project. * * The movement model is associated with an order, such that an order * of zero indicates a single movement parameter per degree of * freedom, i.e. the traditional rigid-body model. For higher orders * the movement is modeled as a basis-function expansion over time, * using an unnormalised discrete Cosine transform as a basis set. * ********************************************************************/ class ScanMovementModel { public: ScanMovementModel(unsigned int order) EddyTry : _order(order), _mp(static_cast(6*(order+1))) { _mp=0.0; } EddyCatch ScanMovementModel(unsigned int order, const NEWMAT::ColumnVector& mp) EddyTry : _order(order), _mp(mp) { if (mp.Nrows() != static_cast(6*(order+1))) throw EddyException("ScanMovementModel::ScanMovementModel: Mismatch between order and mp"); } EddyCatch ~ScanMovementModel() {} unsigned int Order() const { return(_order); } bool IsSliceToVol() const { return(_order!=0); } unsigned int NParam() const { return(6*(_order+1)); } NEWMAT::ColumnVector GetZeroOrderParams() const EddyTry { return(get_zero_order_mp()); } EddyCatch NEWMAT::ColumnVector GetParams() const EddyTry { return(_mp); } EddyCatch double GetParam(unsigned int indx) const EddyTry { if (int(indx) > _mp.Nrows()) throw EddyException("ScanMovementModel::GetParam: indx out of range"); return(_mp(indx+1)); } EddyCatch /// Get the six RB movement parameters for one group NEWMAT::ColumnVector GetGroupWiseParams(unsigned int grp, unsigned int ngrp) const EddyTry { return(get_gmp(grp,ngrp)); } EddyCatch /// Sets the movement parameter. If order>0 and p.Nrows()==6 the zero-order (const) params will be set. void SetParams(const NEWMAT::ColumnVector& p) EddyTry { if (p.Nrows() == 6) set_zero_order_mp(p); else if (p.Nrows() == _mp.Nrows()) _mp = p; else throw EddyException("ScanMovementModel::SetParams: mismatched p"); } EddyCatch /// Set the parameter indicated by indx void SetParam(unsigned int indx, double val) EddyTry { if (int(indx) > _mp.Nrows()) throw EddyException("ScanMovementModel::SetParam: indx out of range"); _mp(indx+1) = val; } EddyCatch /// Set parameters on a "per group" basis, which means the internal parameters (DCT coefs) have to be calculated/fitted. void SetGroupWiseParameters(const NEWMAT::Matrix& gwmp) EddyTry { if (gwmp.Nrows() != 6) throw EddyException("ScanMovementModel::SetGroupWiseParameters: gwmp must have 6 rows"); NEWMAT::Matrix X = get_design(static_cast(gwmp.Ncols())); NEWMAT::Matrix Hat = (X.t()*X).i()*X.t(); NEWMAT::ColumnVector dctc; for (int i=0; i<6; i++) { dctc &= Hat*gwmp.Row(i+1).t(); } _mp = dctc; } EddyCatch /// Sets the order of movemnt model. If order less than previously the higher order components will be lost. void SetOrder(unsigned int order) EddyTry { NEWMAT::ColumnVector tmp(6*(order+1)); tmp=0.0; unsigned int cpsz = (order < _order) ? order : _order; for (int i=0; i<6; i++) { tmp.Rows(i*(order+1)+1,i*(order+1)+cpsz+1) = _mp.Rows(i*(_order+1)+1,i*(_order+1)+cpsz+1); } _mp=tmp; _order=order; } EddyCatch /// Returns the number of parameters that are being estimated unsigned int NDerivs() const { return(NParam()); } /// Returns sutiable scales for evaluating numerical derivatives double GetDerivScale(unsigned int dindx) const EddyTry { if (dindx>=6*(_order+1)) throw EddyException("ScanMovementModel::GetDerivScale: dindx out of range"); return( (dindx<3*(_order+1)) ? 1e-2 : 1e-5 ); } EddyCatch /// Returns the number of "compound" derivatives unsigned int NCompoundDerivs() const { return(6); } /// Returns the instructions for the ith compound derivative DerivativeInstructions GetCompoundDerivInstructions(unsigned int indx, const EDDY::MultiBandGroups& mbg) const; /// Returns the Hessian for a Laplacian regularisation of the movement NEWMAT::Matrix GetHessian(unsigned int ngrp) const EddyTry { NEWMAT::Matrix hess(NDerivs(),NDerivs()); hess = 0.0; if (_order) { NEWMAT::DiagonalMatrix D(6); for (int i=0; i<3; i++) D(i+1) = 1.0; for (int i=3; i<6; i++) D(i+1) = 100.0; hess = NEWMAT::KP(D,get_design_derivative(ngrp,2).t() * get_design_derivative(ngrp,2)); } return(hess); } EddyCatch /// Returns matrix denoted \mathbf{R} in paper. NEWMAT::Matrix ForwardMovementMatrix(const NEWIMAGE::volume& scan) const; /// Returns matrix denoted \mathbf{R} in paper for mb-group grp. NEWMAT::Matrix ForwardMovementMatrix(const NEWIMAGE::volume& scan, unsigned int grp, unsigned int ngrp) const; /// Returns matrix denoted \nathbf{R}^{-1} in paper. NEWMAT::Matrix InverseMovementMatrix(const NEWIMAGE::volume& scan) const EddyTry { return(ForwardMovementMatrix(scan).i()); } EddyCatch /// Returns matrix denoted \nathbf{R}^{-1} in paper for mb-group grp. NEWMAT::Matrix InverseMovementMatrix(const NEWIMAGE::volume& scan, unsigned int grp, unsigned int ngrp) const EddyTry { if (grp>=ngrp) throw EddyException("ScanMovementModel::InverseMovementMatrix: grp has to be smaller than ngrp"); return(ForwardMovementMatrix(scan,grp,ngrp).i()); } EddyCatch /// The same as ForwardMovementMatrix, but excluding some movement parameters as specified by rindx NEWMAT::Matrix RestrictedForwardMovementMatrix(const NEWIMAGE::volume& scan, const std::vector& rindx) const; /// The same as ForwardMovementMatrix, but excluding some movement parameters as specified by rindx NEWMAT::Matrix RestrictedForwardMovementMatrix(const NEWIMAGE::volume& scan, unsigned int grp, unsigned int ngrp, const std::vector& rindx) const; /// The same as InverseMovementMatrix, but excluding some movement parameters as specified by rindx NEWMAT::Matrix RestrictedInverseMovementMatrix(const NEWIMAGE::volume& scan, const std::vector& rindx) const EddyTry { return(RestrictedForwardMovementMatrix(scan,rindx).i()); } EddyCatch /// The same as InverseMovementMatrix, but excluding some movement parameters as specified by rindx NEWMAT::Matrix RestrictedInverseMovementMatrix(const NEWIMAGE::volume& scan, unsigned int grp, unsigned int ngrp, const std::vector& rindx) const EddyTry { if (grp>=ngrp) throw EddyException("ScanMovementModel::RestrictedInverseMovementMatrix: grp has to be smaller than ngrp"); return(RestrictedForwardMovementMatrix(scan,grp,ngrp,rindx).i()); } EddyCatch private: unsigned int _order; // Order of DCT-set /// Movement parameters organised as [xt_const xt_dct_1 ... xt_dct_order yt_const ... zr_dct_order] NEWMAT::ColumnVector _mp; NEWMAT::ColumnVector get_zero_order_mp() const EddyTry { NEWMAT::ColumnVector zmp(6); zmp=0.0; for (int i=0, j=0; i<6; i++, j+=(int(_order)+1)) zmp(i+1) = _mp(j+1); return(zmp); } EddyCatch void set_zero_order_mp(const NEWMAT::ColumnVector& mp) EddyTry { for (int i=0, j=0; i<6; i++, j+=(int(_order)+1)) _mp(j+1) = mp(i+1); } EddyCatch NEWMAT::ColumnVector get_gmp(unsigned int grp, unsigned int ngrp) const EddyTry { double pi = 3.141592653589793; NEWMAT::ColumnVector gmp(6); gmp=0.0; for (unsigned int i=0; i<6; i++) { for (unsigned int j=0; j<(_order+1); j++) { if (j==0) gmp(i+1) += _mp(i*(_order+1)+j+1); else gmp(i+1) += _mp(i*(_order+1)+j+1) * cos((pi*double(j)*double(2*grp+1))/double(2*ngrp)); } } return(gmp); } EddyCatch NEWMAT::Matrix get_design(unsigned int ngrp) const EddyTry { double pi = 3.141592653589793; NEWMAT::Matrix X(ngrp,_order+1); for (unsigned int i=0; isqr((pi*double(j)/double(ngrp))) * cos((pi*double(j)*double(2*i+1))/double(2*ngrp)); else throw EddyException("ScanMovementModel::get_design_derivative: Invalid derivative"); } } } return(dX); } EddyCatch double sqr(double x) const { return(x*x); } }; /****************************************************************//** * * \brief Virtual base class for classes used to model the fields * that may result from eddy currents. * * The classes in this hierarchy manages eddy current (EC) parameters * for one scan. We can set the parameters with one call and obtain * the resulting field with another. By deriving a set of classes from * a virtual base class we are able to use the same code to estimate * the parameters for different EC-models. * ********************************************************************/ class ScanECModel { public: ScanECModel() EddyTry {} EddyCatch ScanECModel(const NEWMAT::ColumnVector& ep) EddyTry : _ep(ep) {} EddyCatch virtual ~ScanECModel() {} /// Returns which model it is virtual ECModelType WhichModel() const = 0; /// Indicates if a field offset is modeled or not. virtual bool HasFieldOffset() const = 0; /// Returns the field offset. virtual double GetFieldOffset() const = 0; /// Set field offset. virtual void SetFieldOffset(double ofst) = 0; /// Return the total number of parameters unsigned int NParam() const { return(_ep.Nrows()); } /// Get all parameters. NEWMAT::ColumnVector GetParams() const EddyTry { return(_ep); } EddyCatch /// Set all parameters. void SetParams(const NEWMAT::ColumnVector& ep) EddyTry { if (ep.Nrows() != _ep.Nrows()) throw EddyException("ScanECModel::SetParams: Wrong number of parameters"); _ep = ep; } EddyCatch /// Return the number of parameters that are updated as part of the estimation. virtual unsigned int NDerivs() const = 0; /// Returns the number of compound derivatives virtual unsigned int NCompoundDerivs() const = 0; /// Returns instructions for the dindx'th compound derivative virtual EDDY::DerivativeInstructions GetCompoundDerivInstructions(unsigned int indx, const std::vector& pev) const = 0; // The following get/set routines indexes parameters from // 0 - NDerivs()-1 i.e. it ignores any parameters that are // not estimated for the particular model. // 6/5-2020 I added a flag that allows one to index one past NDerivs-1 for the case where // NDerivs() < NParam(), i.e. when no susceptibility field was passed to eddy. This is to allow // estimation of derivatives using the modulation trick for those cases. It is not elegant, and // it might be worth considering a complete redesign as a consquence of the modulated derivatives. /// Get parameter dindx of 0 to NDerivs-1 virtual double GetDerivParam(unsigned int dindx, bool allow_field_offset=false) const = 0; /// Set parameter dindx of 0 to NDerivs-1 virtual void SetDerivParam(unsigned int dindx, double p, bool allow_field_offset=false) = 0; /// Returns sutiable scales for evaluating numerical derivatives virtual double GetDerivScale(unsigned int dindx, bool allow_field_offset=false) const = 0; /// Used to create a polymorphic copy of self virtual std::shared_ptr Clone() const = 0; /// Return eddy current-induced field in Hz. Denoted e(\mathbf{b}) in paper. virtual NEWIMAGE::volume ECField(const NEWIMAGE::volume& scan) const = 0; protected: NEWMAT::ColumnVector _ep; }; //@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ // // Class PolynomialScanECModel (Polynomial Scan Eddy Current Model) // // A virtual base class for polynomial models (linear, quadratic etc). // //@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ class PolynomialScanECModel : public ScanECModel { public: PolynomialScanECModel(bool field=false) EddyTry : ScanECModel() { _nepd = 0; } EddyCatch PolynomialScanECModel(const NEWMAT::ColumnVector& ep, bool field=false) EddyTry : ScanECModel(ep), _nepd(0) {} EddyCatch virtual ~PolynomialScanECModel() {} virtual ECModelType WhichModel() const = 0; virtual bool HasFieldOffset() const = 0; virtual double GetFieldOffset() const EddyTry { if (HasFieldOffset()) return(_ep(_nepd)); else return(0.0); } EddyCatch virtual void SetFieldOffset(double ofst) EddyTry { if (!HasFieldOffset()) throw EddyException("PolynomialScanECModel::SetFieldOffset: Attempting to set offset for model without offset"); _ep(_nepd) = ofst; } EddyCatch virtual NEWMAT::RowVector GetLinearParameters() const EddyTry { return(_ep.Rows(1,3).t()); } EddyCatch virtual unsigned int NDerivs() const { return(_nepd); } /// Returns the number of "compound" derivatives virtual unsigned int NCompoundDerivs() const = 0; /// Returns the instructions for the ith compound derivative virtual EDDY::DerivativeInstructions GetCompoundDerivInstructions(unsigned int indx, const std::vector& pev) const = 0; virtual double GetDerivParam(unsigned int dindx, bool allow_field_offset=false) const EddyTry { if (dindx>=NDerivs() && !allow_field_offset) throw EddyException("PolynomialScanECModel::GetDerivParam(allow_field_offset=false): dindx out of range"); else if (dindx>=NParam()) throw EddyException("PolynomialScanECModel::GetDerivParam(allow_field_offset=true): dindx out of range"); return(_ep(dindx+1)); } EddyCatch virtual void SetDerivParam(unsigned int dindx, double p, bool allow_field_offset=false) EddyTry { if (dindx>=NDerivs() && !allow_field_offset) throw EddyException("PolynomialScanECModel::SetDerivParam(allow_field_offset=false): dindx out of range"); else if (dindx>=NParam()) throw EddyException("PolynomialScanECModel::SetDerivParam(allow_field_offset=true): dindx out of range"); _ep(dindx+1) = p; } EddyCatch virtual double GetDerivScale(unsigned int dindx, bool allow_field_offset=false) const = 0; virtual std::shared_ptr Clone() const = 0; virtual NEWIMAGE::volume ECField(const NEWIMAGE::volume& scan) const = 0; protected: unsigned int _nepd; // Number of Eddy Parameter Derivatives (might depend on if field was set) }; //@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ // // Class LinearScanECModel (Linear Scan Eddy Current Model) // // This class models the eddy curents as a linear combination of // linear gradients in the x-, y- and z-directions. The assumption // behind this is that the eddy currents resides mainly in the // gradient coils and also that the gradient coils are close to // linear. // The _ep field contains: // dfdx (shear), dfdy (zoom), dfdz (z-dependent trans) all in Hz/mm and df (trans in Hz) // Note that the translations (e.g. dfdx (EC gradient in x-direction) to shear) // only makes sense if the phase-encode is in the y-direction. // The df field is there to model any difference between the centre // of the FOV and the iso-centre of the scanner. // // If no susceptibility off-resonance field is specified df is strictly // speaking redundant since it is identical to a subject movement // (y-translation). It is however useful to retain as a parameter // for the sake of modelling EC parameters as a function of // diffusion gradients. In the updates it will not have a derivative // and will hence not be updated. It will instead be set by the // higher level modelling of the parameters. // // If a susceptibility off-resonance field is specified df will enter // into the transform compared to subject y-translation and it should // be possible to directly separate the two. In this case it will // have a derivative and will be updated as part of the first level // estimation. // // {{{ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ class LinearScanECModel : public PolynomialScanECModel { public: LinearScanECModel(bool field=false) EddyTry : PolynomialScanECModel(field) { _ep.ReSize(4); _ep=0.0; _nepd = (field) ? 4 : 3; } EddyCatch LinearScanECModel(const NEWMAT::ColumnVector& ep, bool field=false) EddyTry : PolynomialScanECModel(ep) { _nepd = (field) ? 4 : 3; if (_ep.Nrows() != int(_nepd)) throw EddyException("LinearScanECModel: Wrong number of elements for ep"); } EddyCatch virtual ~LinearScanECModel() {} ECModelType WhichModel() const { return(ECModelType::Linear); } bool HasFieldOffset() const { return(_nepd==4); } virtual std::shared_ptr Clone() const EddyTry { return(std::shared_ptr( new LinearScanECModel(*this))); } EddyCatch virtual unsigned int NCompoundDerivs() const { return(2); } virtual EDDY::DerivativeInstructions GetCompoundDerivInstructions(unsigned int indx, const std::vector& pev) const; virtual double GetDerivScale(unsigned int dindx, bool allow_field_offset=false) const EddyTry { if (dindx>=NDerivs() && !allow_field_offset) throw EddyException("LinearScanECModel::GetDerivScale(allow_field_offset=false): dindx out of range"); else if (dindx>=NParam()) throw EddyException("LinearScanECModel::GetDerivScale(allow_field_offset=true): dindx out of range"); if (dindx < 3) return(1e-3); else return(1e-2); throw EddyException("LinearScanECModel::GetDerivScale: This should not be possible"); } EddyCatch virtual NEWIMAGE::volume ECField(const NEWIMAGE::volume& scan) const; }; //@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ // // Class QuadraticScanECModel (Quadratic Scan Eddy Current Model) // // This class models the eddy curents as a second order polynomial // expansion of combination of gradients in the x-, y- and z-directions. // // The _ep field contains: // dfdx (shear), dfdy (zoom), dfdz (z-dependent trans) all in Hz/mm // followed by dfdx^2, dfdy^2, dfdz^2, dfdx*dfdy, dfdx*dfdz and dfdy*dfdz // and finally df (trans in Hz). // The quadratic components are (arbitrarily) scaled to have ~the same value (in Hz) // at the edge of the FOV as has the linear terms. This is done to ensure an // update matrix with reasonable condition number. // Note that the translations (e.g. dfdx (EC gradient in x-direction) to shear) // only makes sense if the phase-encode is in the y-direction. // The df field is there to model any difference between the centre // of the FOV and the iso-centre of the scanner. // // If no susceptibility off-resonance field is specified df is strictly // speaking redundant since it is identical to a subject movement // (y-translation). It is however useful to retain as a parameter // for the sake of modelling EC parameters as a function of // diffusion gradients. In the updates it will not have a derivative // and will hence not be updated. It will instead be set by the // higher level modelling of the parameters. // // If a susceptibility off-resonance field is specified df will enter // into the transform compared to subject y-translation and it should // be possible to directly separate the two. In this case it will // have a derivative and will be updated as part of the first level // estimation. // // {{{ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ class QuadraticScanECModel : public PolynomialScanECModel { public: QuadraticScanECModel(bool field=false) EddyTry : PolynomialScanECModel(field) { _ep.ReSize(10); _ep=0.0; _nepd = (field) ? 10 : 9; } EddyCatch QuadraticScanECModel(const NEWMAT::ColumnVector& ep, bool field=false) EddyTry : PolynomialScanECModel(ep,field) { _nepd = (field) ? 10 : 9; if (_ep.Nrows() != int(_nepd)) throw EddyException("QuadraticScanECModel: Wrong number of elements for ep"); } EddyCatch virtual ~QuadraticScanECModel() {} ECModelType WhichModel() const { return(ECModelType::Quadratic); } bool HasFieldOffset() const { return(_nepd==10); } std::shared_ptr Clone() const EddyTry { return(std::shared_ptr( new QuadraticScanECModel(*this))); } EddyCatch virtual unsigned int NCompoundDerivs() const { return(3); } virtual EDDY::DerivativeInstructions GetCompoundDerivInstructions(unsigned int indx, const std::vector& pev) const; virtual double GetDerivScale(unsigned int dindx, bool allow_field_offset=false) const EddyTry { if (dindx>=NDerivs() && !allow_field_offset) throw EddyException("QuadraticScanECModel::GetDerivScale(allow_field_offset=false): dindx out of range"); else if (dindx>=NParam()) throw EddyException("QuadraticScanECModel::GetDerivScale(allow_field_offset=true): dindx out of range"); if (dindx < 3) return(1e-3); else if (dindx < 9) return(1e-5); else return(1e-2); throw EddyException("QuadraticScanECModel::GetDerivScale: This should not be possible"); } EddyCatch NEWIMAGE::volume ECField(const NEWIMAGE::volume& scan) const; }; //@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ // // Class CubicScanECModel (Cubic Scan Eddy Current Model) // // This class models the eddy curents as a third order polynomial // expansion of combination of gradients in the x-, y- and z-directions. // // The _ep field contains: // dfdx (shear), dfdy (zoom), dfdz (z-dependent trans) all in Hz/mm // followed by dfdx^2, dfdy^2, dfdz^2, dfdx*dfdy, dfdx*dfdz and dfdy*dfdz, // followed by dfdx^3, dfdy^3, dfdz^3, dfdx^2*dfdy, dfdx^2*dfdz, // dfdy^2*dfdx, dfdy^2*dfdz, dfdz^2*dfdx, dfdz^2*dfdy, dfdx*dfdy*dfdz // and finally df (trans in Hz). // The quadratic and cubic components are (arbitrarily) scaled to have ~the // same value (in Hz) at the edge of the FOV as has the linear terms. // This is done to ensure an update matrix with reasonable condition number. // Note that the translations (e.g. dfdx (EC gradient in x-direction) to shear) // only makes sense if the phase-encode is in the y-direction. // The df field is there to model any difference between the centre // of the FOV and the iso-centre of the scanner. // // If no susceptibility off-resonance field is specified df is strictly // speaking redundant since it is identical to a subject movement // (y-translation). It is however useful to retain as a parameter // for the sake of modelling EC parameters as a function of // diffusion gradients. In the updates it will not have a derivative // and will hence not be updated. It will instead be set by the // higher level modelling of the parameters. // // If a susceptibility off-resonance field is specified df will enter // into the transform compared to subject y-translation and it should // be possible to directly separate the two. In this case it will // have a derivative and will be updated as part of the first level // estimation. // // {{{ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ class CubicScanECModel : public PolynomialScanECModel { public: CubicScanECModel(bool field=false) EddyTry : PolynomialScanECModel(field) { _ep.ReSize(20); _ep=0.0; _nepd = (field) ? 20 : 19; } EddyCatch CubicScanECModel(const NEWMAT::ColumnVector& ep, bool field=false) EddyTry : PolynomialScanECModel(ep) { _nepd = (field) ? 20 : 19; if (_ep.Nrows() != int(_nepd)) throw EddyException("CubicScanECModel: Wrong number of elements for ep"); } EddyCatch virtual ~CubicScanECModel() {} ECModelType WhichModel() const EddyTry { return(ECModelType::Cubic); } EddyCatch bool HasFieldOffset() const EddyTry { return(_nepd==20); } EddyCatch std::shared_ptr Clone() const EddyTry { return(std::shared_ptr( new CubicScanECModel(*this))); } EddyCatch virtual unsigned int NCompoundDerivs() const { return(4); } virtual EDDY::DerivativeInstructions GetCompoundDerivInstructions(unsigned int indx, const std::vector& pev) const; virtual double GetDerivScale(unsigned int dindx, bool allow_field_offset=false) const EddyTry { if (dindx>=NDerivs() && !allow_field_offset) throw EddyException("CubicScanECModel::GetDerivScale(allow_field_offset=false): dindx out of range"); else if (dindx>=NParam()) throw EddyException("CubicScanECModel::GetDerivScale(allow_field_offset=true): dindx out of range"); if (dindx < 3) return(1e-3); else if (dindx < 9) return(1e-5); else if (dindx < 19) return(1e-7); else return(1e-2); throw EddyException("CubicScanECModel::GetDerivScale: This should not be possible"); } EddyCatch NEWIMAGE::volume ECField(const NEWIMAGE::volume& scan) const; }; //@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ // // Class NoECScanECModel (Movement Scan Eddy Current Model) // // This class doesn't model the eddy curents at all, and simply // uses the rigid body model. This is done to create a polymorphism // so we can use the same basic code for the b0 scans as for the // diffusion weighted ones. // //@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ class NoECScanECModel : public ScanECModel { public: NoECScanECModel() EddyTry : ScanECModel() {} EddyCatch NoECScanECModel(const NEWMAT::ColumnVector& ep) EddyTry { if (_ep.Nrows()) throw EddyException("NoECScanScanECModel: ep must have 0 elements"); } EddyCatch virtual ~NoECScanECModel() {} ECModelType WhichModel() const { return(ECModelType::NoEC); } bool HasFieldOffset() const { return(false); } double GetFieldOffset() const { return(0.0); } void SetFieldOffset(double ofst) { } unsigned int NDerivs() const { return(0); } unsigned int NCompoundDerivs() const { return(0); } EDDY::DerivativeInstructions GetCompoundDerivInstructions(unsigned int indx, const std::vector& pev) const { throw EddyException("NoECScanECModel::GetCompoundDerivInstructions: Model has no EC parameters"); }; double GetDerivParam(unsigned int dindx, bool allow_field_offset=false) const { throw EddyException("NoECScanECModel::GetDerivParam: Model has no EC parameters"); } void SetDerivParam(unsigned int dindx, double p, bool allow_field_offset=false) { throw EddyException("NoECScanECModel::SetDerivParam: Model has no EC parameters"); } double GetDerivScale(unsigned int dindx, bool allow_field_offset=false) const { throw EddyException("NoECScanECModel::GetDerivScale: Model has no EC parameters"); } std::shared_ptr Clone() const EddyTry { return(std::shared_ptr( new NoECScanECModel(*this))); } EddyCatch NEWIMAGE::volume ECField(const NEWIMAGE::volume& scan) const EddyTry { NEWIMAGE::volume field=scan; field=0.0; return(field); } EddyCatch }; } // End namespace EDDY #endif // End #ifndef ECModels_h