/*! \file EddyHelperClasses.h \brief Contains declaration of classes that implements useful functionality for the eddy project. \author Jesper Andersson \version 1.0b, Sep., 2012. */ // Declarations of classes that implements useful // concepts for the eddy current project. // // EddyHelperClasses.h // // Jesper Andersson, FMRIB Image Analysis Group // // Copyright (C) 2011 University of Oxford // #ifndef EddyHelperClasses_h #define EddyHelperClasses_h #include #include #include #include #include #include #include #include #include #include #include "armawrap/newmat.h" #include "newimage/newimageall.h" #include "miscmaths/miscmaths.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 // Macro to time and report a task #ifndef TicToc #define TicToc(task) { timeval tim; \ gettimeofday(&tim,NULL); \ task; \ timeval tim2; \ gettimeofday(&tim2,NULL); \ std::cout << "Call to " #task " took " << 1000000*(tim2.tv_sec-tim.tv_sec) + tim2.tv_usec - tim.tv_usec << " usec" << std::endl; } #endif #define UseEddyTry #ifdef UseEddyTry #ifndef EddyTry #define EddyTry try #define EddyCatch catch (const std::exception& e) { std::cout << e.what() << std::endl; throw EDDY::EddyException(std::string(__FILE__) + "::: " + std::string(BOOST_CURRENT_FUNCTION) + ": Exception thrown"); } #endif #else // If not UseEddyTry #ifndef EddyTry #define EddyTry #define EddyCatch #endif # endif // End #ifdef UseEddyTry namespace EDDY { enum class CovarianceFunctionType { Spherical, Exponential, NewSpherical, SquaredExponential, Unknown }; // MML = MarginalMaximumLikelihood, CV = CrossValidation, // GPP = GeissersSurrogatePredictiveProbability, CC = Cheap&Cheerful enum class HyParCostFunctionType { MML, CV, GPP, CC, Unknown }; // N.B. "All" below mean movement and EV, it does _not_ include longEC (retrofitting :( ) enum class ParametersType { ZeroOrderMovement, Movement, EC, All, LongEC }; enum class ECModelType { NoEC, Linear, Quadratic, Cubic, Unknown }; enum class SecondLevelECModelType { None, Linear, Quadratic, Unknown }; enum class OffsetModelType { Linear, LinearPlusLag, Quadratic, QuadraticPlusLinearLag, QuadraticPlusLag, Unknown }; enum class OLType { SliceWise, GroupWise, Both }; enum class OLSumStats { ShellWise, Pooled, Unknown}; enum class ScanType { DWI, b0 , fMRI, Any }; enum class FinalResamplingType { Jac, Jac_NN, LSR, Unknown}; enum class LongECModelType { None, Individual, Joint, IndividualTimeConstant, JointTimeConstant }; /****************************************************************//** * * \brief This is the exception that is being thrown by routines * in the core code of eddy. * * This is the exception that is being thrown by routines * in the core code of eddy. * ********************************************************************/ class EddyException: public std::exception { public: EddyException(const std::string& msg) noexcept : message(std::string("EDDY::") + msg) {} ~EddyException() noexcept {} virtual const char * what() const noexcept { return(message.c_str()); } private: std::string message; }; /****************************************************************//** * * \brief This is a stopwatch class that can be used to time * command/commands when tictoc above isn't suitable. * ********************************************************************/ class EddyStopwatch { public: EddyStopwatch() : _start(std::chrono::steady_clock::now()) {} void Start() { _start = std::chrono::steady_clock::now(); } // Return time in since creation or latest call to Start double Read() { auto delta = std::chrono::duration_cast(std::chrono::steady_clock::now() - _start); return(static_cast(delta.count()) / 1.0e9); } private: std::chrono::time_point _start; }; /****************************************************************//** * * \brief This class reads a JSON file. * * This class reads a JSON file and can subsequently be interrogated * for a very limited subset of parameters from that file. * ********************************************************************/ class JsonReader { public: JsonReader(); JsonReader(const std::string& fname) EddyTry : _fname(fname) { common_read(); } EddyCatch ~JsonReader(); void Read(const std::string& fname) EddyTry { _fname = fname; common_read(); } EddyCatch /// Return a slice order matrix in the same format as a --slspec text file NEWMAT::Matrix SliceOrdering() const; /// Return a Phase-encode vector in the format expected in an --acqp file NEWMAT::ColumnVector PEVector() const; /// Return the total readout time (fourth column in an --acqp file). double TotalReadoutTime() const; /// Return the echo-time (in seconds) double EchoTime() const; /// Repetition time (seconds) double RepetitionTime() const; private: void common_read(); std::string _fname; nlohmann::json *_content; // Couldn't get it to work with smart pointer }; /****************************************************************//** * * \brief This class manages the diffusion parameters for one scan * * This class manages the diffusion parameters for one scan. Note that * it also manages the repetition time. This is an ugly effect of * shoe-horning fMRI into an existing diffusion framework. * ********************************************************************/ enum class DDEType{ Linear, Planar, Spherical }; class DiffPara { public: /// Default constructor. Sets b-vector to [1 0 0] and b-value to zero. DiffPara() EddyTry : DiffPara(0.0) { } EddyCatch /// Constructs a diffpara object with b-vec [1 0 0] and specified b-value DiffPara(double bval) EddyTry : _bval(bval), _b_delta(DDEType::Linear) { _bvec.ReSize(3); _bvec(1)=1; _bvec(2)=0; _bvec(3)=0; } EddyCatch /// Constructs a Diffpara object from a b-value and a repetition time. DiffPara(double bval, double tr) EddyTry : _bval(bval), _b_delta(DDEType::Linear), _tr(tr) { _bvec.ReSize(3); _bvec(1)=1; _bvec(2)=0; _bvec(3)=0; } EddyCatch /// Constructs a DiffPara object from a b-vector and a b-value. DiffPara(const NEWMAT::ColumnVector& bvec, double bval) EddyTry : DiffPara(bvec,bval,1.0) { } EddyCatch /// Constructs a DiffPara object from a b-vector, b-value and b_delta DiffPara(const NEWMAT::ColumnVector& bvec, double bval, double bdelta) EddyTry : DiffPara(bvec,bval,bdelta,3.0) {} EddyCatch /// Constructs a DiffPara object from a b-vector, b-value, b_delta and repetition time DiffPara(const NEWMAT::ColumnVector& bvec, double bval, double bdelta, double tr) EddyTry : _bvec(bvec), _bval(bval), _b_delta(bdelta), _tr(tr) { if (_bvec.Nrows() != 3) throw EddyException("DiffPara::DiffPara: b-vector must be three elements long"); if (_bval) _bvec /= _bvec.NormFrobenius(); } EddyCatch /// Prints out b-vector and b-value in formatted way friend std::ostream& operator<<(std::ostream& op, const DiffPara& dp) EddyTry { op << "b-vector: " << dp._bvec.t() << std::endl << "b-value: " << dp._bval << std::endl << "b-delta: " << dp.ddeVal() << std::endl << "Repetition time: " << dp.TR() << std::endl; return(op); } EddyCatch /// Returns true if the b-value AND the direction are the same bool operator==(const DiffPara& rhs) const; /// Same as !(a==b) bool operator!=(const DiffPara& rhs) const EddyTry { return(!(*this==rhs)); } EddyCatch /// Compares the b-values bool operator>(const DiffPara& rhs) const EddyTry { return(this->bVal()>rhs.bVal()); } EddyCatch /// Compares the b-values bool operator<(const DiffPara& rhs) const EddyTry { return(this->bVal() std::vector BinarisedPhaseEncodeVector() const; /// Returns the readout-time in seconds double ReadOutTime() const { return(_rotime); } private: NEWMAT::ColumnVector _pevec; double _rotime; }; class PolationPara { public: PolationPara() EddyTry : _int(NEWIMAGE::spline), _ext(NEWIMAGE::periodic), _evip(true), _s2v_int(NEWIMAGE::trilinear), _sp_lambda(0.005) {} EddyCatch PolationPara(NEWIMAGE::interpolation ip, NEWIMAGE::extrapolation ep, bool evip, NEWIMAGE::interpolation s2v_ip, double sp_lambda=0.005) EddyTry : _sp_lambda(sp_lambda) { SetInterp(ip); SetExtrap(ep); SetExtrapValidity(evip); SetS2VInterp(s2v_ip); } EddyCatch NEWIMAGE::interpolation GetInterp() const { return(_int); } NEWIMAGE::interpolation GetS2VInterp() const { return(_s2v_int); } NEWIMAGE::extrapolation GetExtrap() const { return(_ext); } bool GetExtrapValidity() const { return(_evip); } double GetSplineInterpLambda() const { return(_sp_lambda); } void SetInterp(NEWIMAGE::interpolation ip) EddyTry { if (ip!=NEWIMAGE::trilinear && ip!=NEWIMAGE::spline) throw EddyException("PolationPara::SetInterp: Invalid interpolation"); _int = ip; } EddyCatch void SetS2VInterp(NEWIMAGE::interpolation ip) EddyTry { if (ip!=NEWIMAGE::trilinear && ip!=NEWIMAGE::spline) throw EddyException("PolationPara::SetS2VInterp: Invalid interpolation"); _s2v_int = ip; } EddyCatch void SetExtrap(NEWIMAGE::extrapolation ep) EddyTry { if (ep!=NEWIMAGE::mirror && ep!=NEWIMAGE::periodic) throw EddyException("PolationPara::SetExtrap: Invalid extrapolation"); if (ep!=NEWIMAGE::periodic && _evip) throw EddyException("PolationPara::SetExtrap: Invalid extrapolation and validity combo"); _ext = ep; } EddyCatch void SetExtrapValidity(bool evip) EddyTry { if (evip && _ext!=NEWIMAGE::periodic) throw EddyException("PolationPara::SetExtrapValidity: Invalid extrapolation and validity combo"); _evip = evip; } EddyCatch /// Writes some useful debug info friend std::ostream& operator<<(std::ostream& out, const PolationPara& pp) EddyTry { out << "PolationPara:" << std::endl; if (pp._int == NEWIMAGE::trilinear) out << "Interpolation: trilinear" << std::endl; else out << "Interpolation: spline" << std::endl; if (pp._ext == NEWIMAGE::mirror) out << "Extrapolation: mirror" << std::endl; else out << "Extrapolation: periodic" << std::endl; if (pp._evip) out << "Extrapolation along EP is valid" << std::endl; else out << "Extrapolation along EP is not valid" << std::endl; if (pp._s2v_int == NEWIMAGE::trilinear) out << "Slice-to-vol interpolation: trilinear" << std::endl; else out << "Slice-to-vol interpolation: spline" << std::endl; out << "Lambda for spline interpolation in z-direction: " << pp._sp_lambda << std::endl; return(out); } EddyCatch private: NEWIMAGE::interpolation _int; ///< Interpolation method NEWIMAGE::extrapolation _ext; ///< Extrapolation method bool _evip; ///< Specifies if extrapolation is valid in PE-direction NEWIMAGE::interpolation _s2v_int; ///< z-direction interpolation for slice-to-vol double _sp_lambda; ///< Lambda when doing spline interpolation in z-direction for slice-to-vol }; class JacMasking { public: JacMasking(bool doit, double ll, double ul) : _doit(doit), _ll(ll), _ul(ul) {} ~JacMasking() {} bool DoIt() const { return(_doit); } double LowerLimit() const { return(_ll); } double UpperLimit() const { return(_ul); } private: bool _doit; double _ll; double _ul; }; class ReferenceScans { public: ReferenceScans() EddyTry : _valid(false), _is_diff(false) {} EddyCatch // Constructor for fMRI scans ReferenceScans(unsigned int indx) EddyTry : _valid(true), _is_diff(false), _loc_ref(indx), _fmri_loc_ref(indx), _fmri_shape_ref(indx) {} EddyCatch // Constructor for diffusion scans ReferenceScans(std::vector b0indx, std::vector > shindx) EddyTry : _valid(true), _is_diff(true), _loc_ref(0), _shell_loc_ref(shindx.size()), _shell_shape_ref(shindx.size()) { _b0_loc_ref = (b0indx.size() > 0 ? b0indx[0] : 0); _b0_shape_ref = (b0indx.size() > 0 ? b0indx[0] : 0); _dwi_loc_ref = ((shindx.size() && shindx[0].size()) ? shindx[0][0] : 0); for (unsigned int i=0; i=_shell_loc_ref.size()) throw EddyException("ReferenceScans::GetShellLocationReference: Shell index out of range"); else return(_shell_loc_ref[si]); } else throw EddyException("ReferenceScans::GetShellLocationReference: Invalid object"); } EddyCatch unsigned int GetShellShapeReference(unsigned int si) const EddyTry { if (_valid && _is_diff) { if (si>=_shell_shape_ref.size()) throw EddyException("ReferenceScans::GetShellShapeReference: Shell index out of range"); else return(_shell_shape_ref[si]); } else throw EddyException("ReferenceScans::GetShellShapeReference: Invalid object"); } EddyCatch void SetLocationReference(unsigned int indx) EddyTry { if (!_valid) throw EddyException("ReferenceScans::SetLocationReference: Invalid object"); if (_is_diff) _loc_ref=indx; else { // Assume fMRI _fmri_loc_ref = indx; _loc_ref = indx; } } EddyCatch void SetB0LocationReference(unsigned int indx) { if (_valid && _is_diff) _b0_loc_ref=indx; else throw EddyException("ReferenceScans::SetB0LocationReference: Invalid object"); } void SetB0ShapeReference(unsigned int indx) { if (_valid && _is_diff) _b0_shape_ref=indx; else throw EddyException("ReferenceScans::SetB0ShapeReference: Invalid object"); } void SetDWILocationReference(unsigned int indx) { if (_valid && _is_diff) _dwi_loc_ref=indx; else throw EddyException("ReferenceScans::SetDWILocationReference: Invalid object"); } void SetfMRILocationReference(unsigned int indx) { if (_valid && !_is_diff) { _fmri_loc_ref=indx; _loc_ref=indx; } else throw EddyException("ReferenceScans::SetfMRILocationReference: Invalid object"); } void SetfMRIShapeReference(unsigned int indx) { if (_valid && !_is_diff) _fmri_shape_ref=indx; else throw EddyException("ReferenceScans::SetfMRIShapeReference: Invalid object"); } void SetShellLocationReference(unsigned int si, unsigned int indx) EddyTry { if (_valid && _is_diff) { if (si>=_shell_loc_ref.size()) throw EddyException("ReferenceScans::SetShellLocationReference: Shell index out of range"); _shell_loc_ref[si] = indx; } else throw EddyException("ReferenceScans::SetShellLocationReference: Invalid object"); } EddyCatch void SetShellShapeReference(unsigned int si, unsigned int indx) EddyTry { if (_valid && _is_diff) { if (si>=_shell_shape_ref.size()) throw EddyException("ReferenceScans::SetShellShapeReference: Shell index out of range"); _shell_shape_ref[si] = indx; } else throw EddyException("ReferenceScans::SetShellShapeReference: Invalid object"); } EddyCatch private: /// All indicies are indicies of type ScanType ANY bool _valid; ///< True if constructed with fmri or diffusion constructor bool _is_diff; ///< True if constructed with diffusion constructor unsigned int _loc_ref; ///< Overall location reference scan unsigned int _b0_loc_ref; ///< Index for location reference b0 scan unsigned int _b0_shape_ref; ///< Index for shape reference b0 scan unsigned int _dwi_loc_ref; ///< Index for overall dwi location reference scan unsigned int _fmri_loc_ref; ///< Index for overall fmri location reference unsigned int _fmri_shape_ref; ///< Index for overall fmri shape reference std::vector _shell_loc_ref; ///< Indicies for shell location reference scans std::vector _shell_shape_ref; ///< Indicies for shell shape reference scans }; //@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ // // Class MaskManager // // This class manages an And-mask. // scan. // //@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ class MaskManager { public: MaskManager(const NEWIMAGE::volume& mask) EddyTry : _mask(mask) {} EddyCatch MaskManager(int xs, int ys, int zs) EddyTry : _mask(xs,ys,zs) { _mask = 1.0; } EddyCatch void ResetMask() EddyTry { _mask = 1.0; } EddyCatch void SetMask(const NEWIMAGE::volume& mask) EddyTry { if (!NEWIMAGE::samesize(_mask,mask)) throw EddyException("EDDY::MaskManager::SetMask: Wrong dimension"); else _mask = mask;} EddyCatch void UpdateMask(const NEWIMAGE::volume& mask) EddyTry { if (!NEWIMAGE::samesize(_mask,mask)) throw EddyException("EDDY::MaskManager::UpdateMask: Wrong dimension"); else _mask *= mask;} EddyCatch const NEWIMAGE::volume& GetMask() const EddyTry { return(_mask); } EddyCatch private: NEWIMAGE::volume _mask; }; /****************************************************************//** * * \brief This class manages stats on slice wise differences. * * This class calculates and serves up information about slice-wise * (in observation space) statistics on the difference between * an observation (scan) and the prediction. * ********************************************************************/ class DiffStats { public: DiffStats() {} /// Constructs a Diffstats object given a difference volume and a mask. DiffStats(const NEWIMAGE::volume& diff, const NEWIMAGE::volume& mask); /// Returns the mean (across all valid voxels in the volume) difference. double MeanDifference() const EddyTry { return(mean_stat(_md)); } EddyCatch /// Returns the mean (across all valid voxels in slice sl (zero-offset)) difference. double MeanDifference(int sl) const EddyTry { if (index_ok(sl)) return(_md[sl]); else return(0.0); } EddyCatch /// Returns a vector with the mean difference for all slices NEWMAT::RowVector MeanDifferenceVector() const EddyTry { return(get_vector(_md)); } EddyCatch /// Returns the mean (across all valid voxels in the volume) squared difference. double MeanSqrDiff() const EddyTry { return(mean_stat(_msd)); } EddyCatch /// Returns the mean (across all valid voxels in slice sl (zero-offset)) squared difference. double MeanSqrDiff(int sl) const EddyTry { if (index_ok(sl)) return(_msd[sl]); else return(0.0); } EddyCatch /// Returns a vector with the mean squared difference for all slices NEWMAT::RowVector MeanSqrDiffVector() const EddyTry { return(get_vector(_msd)); } EddyCatch /// Number of valid voxels in the whole volume (as determined by the mask passed to the constructor) unsigned int NVox() const EddyTry { unsigned int n=0; for (int i=0; i _md; // Slice wise mean difference std::vector _msd; // Slice wise mean squared difference std::vector _n; // Slice wise # of valid pixels bool index_ok(int sl) const EddyTry { if (sl<0 || sl>=int(_n.size())) throw EddyException("DiffStats::index_ok: Index out of range"); return(true); } EddyCatch double mean_stat(const std::vector& stat) const EddyTry { double ms=0; for (int i=0; i NEWMAT::RowVector get_vector(const std::vector& stat) const EddyTry { NEWMAT::RowVector ov(stat.size()); for (unsigned int i=0; i& to) EddyTry { if (to.size() != _to.size()) throw EddyException("MultiBandGroups::SetTemporalOrder: to size mismatch"); else _to=to; } EddyCatch unsigned int NSlices() const { return(_nsl); } unsigned int MBFactor() const { return(_mb); } unsigned int NGroups() const EddyTry { return(_grps.size()); } EddyCatch unsigned int WhichGroupIsSliceIn(unsigned int sl) const EddyTry { if (sl >= _nsl) throw EddyException("MultiBandGroups::WhichGroupIsSliceIn: Slice index out of range"); for (unsigned int g=0; g<_grps.size(); g++) { for (unsigned int sli=0; sli<_grps[g].size(); sli++) { if (_grps[g][sli] == sl) return(g); } } throw EddyException("MultiBandGroups::WhichGroupIsSliceIn: Slice not found"); } EddyCatch const std::vector& SlicesInGroup(unsigned int grp_i) const EddyTry { if (grp_i >= _grps.size()) throw EddyException("MultiBandGroups::SlicesInGroup: Group index out of range"); else return(_grps[grp_i]); } EddyCatch const std::vector& SlicesAtTimePoint(unsigned int time_i) const EddyTry { if (time_i >= _grps.size()) throw EddyException("MultiBandGroups::SlicesAtTimePoint: Time index out of range"); else return(_grps[_to[time_i]]); } EddyCatch friend std::ostream& operator<<(std::ostream& os, const MultiBandGroups& mbg) EddyTry { for (unsigned int i=0; i > _grps; /// /// Temporal order. For example if _to[0]==5 it means that the sixth slice/group was aquired first. std::vector _to; /// Checks _grps for internal consistency, like no duplicate slices, all slices accounted for etc. void assert_grps(); }; /****************************************************************//** * * \brief This class manages a set (one for each scan) of DiffStats objects. * * This class manages a vector of DiffStats objects (one for each scan). * This means that it can look across scans (for a given slice) and * build up statistics of the statistics from the DiffStats objects. * It can for example calculate the mean and standard deviation (across) * subjects of the slice-wise mean differences from the DiffStat objects. * From that it can the determine how many standard deviations away * a given scan and slice is from the mean and hence identify outliers. * ********************************************************************/ class DiffStatsVector { public: /// Constructs an object with n (empty) slots for DiffStats objects. DiffStatsVector(unsigned int n) EddyTry : _n(n) { _ds = new DiffStats[_n]; } EddyCatch /// Copy constructor DiffStatsVector(const DiffStatsVector& rhs) EddyTry : _n(rhs._n) { _ds = new DiffStats[_n]; for (unsigned int i=0; i<_n; i++) _ds[i] = rhs._ds[i]; } EddyCatch ~DiffStatsVector() { delete [] _ds; } /// Assignment DiffStatsVector& operator=(const DiffStatsVector& rhs) EddyTry { delete [] _ds; _n = rhs._n; _ds = new DiffStats[_n]; for (unsigned int i=0; i<_n; i++) _ds[i] = rhs._ds[i]; return(*this); } EddyCatch /// Gives read-access to the ith (zero offset) DiffStats object in the vector. const DiffStats& operator[](unsigned int i) const EddyTry { throw_if_oor(i); return(_ds[i]); } EddyCatch /// Gives read/write-access to the ith (zero offset) DiffStats object in the vector. This is used to populate the vector. DiffStats& operator[](unsigned int i) EddyTry { throw_if_oor(i); return(_ds[i]); } EddyCatch /// Returns the number of DiffStats objects in the vector. unsigned int NScan() const { return(_n); } /// Returns the number of slices in each of the DiffStats objects. unsigned int NSlice() const EddyTry { return(_ds[0].NSlice()); } EddyCatch /// Returns the mean difference in scan sc, slice sl double MeanDiff(unsigned int sc, unsigned int sl) const EddyTry { throw_if_oor(sc); return(_ds[sc].MeanDifference(int(sl))); } EddyCatch /// Returns the mean square difference in scan sc, slice sl double MeanSqrDiff(unsigned int sc, unsigned int sl) const EddyTry { throw_if_oor(sc); return(_ds[sc].MeanSqrDiff(int(sl))); } EddyCatch /// Returns the number of "inside mask" voxels in scan sc, slice sl unsigned int NVox(unsigned int sc, unsigned int sl) const EddyTry { throw_if_oor(sc); return(_ds[sc].NVox(int(sl))); } EddyCatch /// Writes three files with information relevant for debugging. void Write(const std::string& bfname) const; private: unsigned int _n; // Length of vector DiffStats *_ds; // Old fashioned C vector of DiffStats objects void throw_if_oor(unsigned int i) const EddyTry { if (i >= _n) throw EddyException("DiffStatsVector::throw_if_oor: Index out of range"); } EddyCatch }; /****************************************************************//** * * \brief This class defines what is considered an outlier. * ********************************************************************/ class OutlierDefinition { public: OutlierDefinition(double nstdev, // # of std-dev away to qualify as outlier unsigned int minn, // min # of intracerebral voxels to be considered bool pos, // Flag also positive outliers if true bool sqr) // Flag also sum-of-squares outliers if true : _nstdev(nstdev), _minn(minn), _pos(pos), _sqr(sqr) {} OutlierDefinition() : _nstdev(4.0), _minn(250), _pos(false), _sqr(false) {} double NStdDev() const { return(_nstdev); } unsigned int MinVoxels() const { return(_minn); } bool ConsiderPosOL() const { return(_pos); } bool ConsiderSqrOL() const { return(_sqr); } private: double _nstdev; // # of std-dev away to qualify as outlier unsigned int _minn; // min # of intracerebral voxels to be considered bool _pos; // Flag also positive outliers if true bool _sqr; // Flag also sum-of-squares outliers if true }; /****************************************************************//** * * \brief This class decides and keeps track of which slices in which * scans should be replaced by their predictions * ********************************************************************/ class ReplacementManager { public: // Constructor for pooling outlier statistics across shells ReplacementManager(unsigned int nscan, // # of scans unsigned int nsl, // # of slices const OutlierDefinition& old, // Class defining an outlier unsigned int etc, // =1 -> constant (across slices) type 1 error, =2 -> const type 2 error OLType olt, // Slice-wise, group-wise or both. const MultiBandGroups& mbg) // multi-band structure EddyTry : _old(old), _etc(etc), _olt(olt), _mbg(mbg), _bvs(1,-999.0), _sws(nsl,nscan), _gws(mbg.NGroups(),nscan), _swo(nsl,nscan), _gwo(mbg.NGroups(),nscan) { if (_etc != 1 && _etc != 2) throw EddyException("ReplacementManager::ReplacementManager: etc must be 1 or 2"); _shi.resize(1); _shi[0].resize(nscan); std::iota(_shi[0].begin(),_shi[0].end(),0); } EddyCatch // Constructor for using shell-wise outlier statistics ReplacementManager(const std::vector >& shi, // Shell indicies const std::vector& bvs, // b-values of the shells unsigned int nsl, // # of slices const OutlierDefinition& old, // Class defining an outlier unsigned int etc, // =1 -> constant (across slices) type 1 error, =2 -> const type 2 error OLType olt, // Slice-wise, group-wise or both. const MultiBandGroups& mbg); // multi-band structure ~ReplacementManager() {} unsigned int NSlice() const EddyTry { return(_swo._ovv.size()); } EddyCatch unsigned int NScan() const EddyTry { unsigned int rval = (_swo._ovv.size()) ? _swo._ovv[0].size() : 0; return(rval); } EddyCatch unsigned int NGroup() const EddyTry { return(_mbg.NGroups()); } EddyCatch unsigned int NShells() const EddyTry { return(_shi.size()); } EddyCatch void Update(const DiffStatsVector& dsv); std::vector OutliersInScan(unsigned int scan) const; bool ScanHasOutliers(unsigned int scan) const; bool IsAnOutlier(unsigned int slice, unsigned int scan) const EddyTry { return(_swo._ovv[slice][scan]); } EddyCatch nlohmann::json WriteReport(const std::vector& i2i, const std::string& bfname, bool write_old_style_file=true) const; // The json-objects are returned in the order "outlier-map", "Stdev of mean map", "Stdev of squared mean map" std::vector WriteMatrixReport(const std::vector& i2i, unsigned int nscan, const std::string& om_fname, const std::string& nstdev_fname, const std::string& n_sqr_stdev_fname, bool write_old_style_file=true) const; // For debugging void DumpOutlierMaps(const std::string& fname) const; void WriteDebugInfo(const std::string& fname, const std::vector& i2i, unsigned int nscan) const; // Struct that is instantiated in the private section struct OutlierInfo { std::vector > _ovv; // _ovv[slice/group][scan] is an outlier if set std::vector > _nsv; // _nsv[slice/group][scan] tells how many stdev off that slice-scan is. std::vector > _nsq; // _nsq[slice/group][scan] tells how many stdev off the squared differences of that slice-scan is. OutlierInfo() EddyTry {} EddyCatch // Must have a default constructor OutlierInfo(unsigned int nsl, unsigned int nscan) EddyTry : _ovv(nsl), _nsv(nsl), _nsq(nsl) { for (unsigned int i=0; i > _nvox; // _nvox[slice/group][scan] is # of valid voxels in that slice-scan std::vector > _mdiff; // _mdiff[slice/group][scan] is the mean difference in that slice-scan std::vector > _msqrd; // _msqrd[slice/group][scan] is the mean squared difference in that slice-scan StatsInfo() EddyTry {} EddyCatch // Must have a default constructor StatsInfo(unsigned int nsl, unsigned int nscan) EddyTry : _nvox(nsl), _mdiff(nsl), _msqrd(nsl) { for (unsigned int i=0; i > _shi; // Shell indicies std::vector _bvs; // b-values of the shells StatsInfo _sws; // Slice-wise stats StatsInfo _gws; // Group-wise stats OutlierInfo _swo; // Slice-wise outlier info OutlierInfo _gwo; // group-wise outlier info std::vector _sss; // Slice-wise summary stats std::vector _gss; // Group-wise summary stats void throw_if_oor(unsigned int scan) const EddyTry { if (scan >= this->NScan()) throw EddyException("ReplacementManager::throw_if_oor: Scan index out of range"); } EddyCatch double sqr(double a) const EddyTry { return(a*a); } EddyCatch std::vector mean_and_std(const EDDY::ReplacementManager::StatsInfo& sws, const std::vector >& shi, unsigned int minvox, unsigned int etc, const std::vector >& ovv) const; // Used to unpack stats matrices to transform them and also include b=0 volumes. template std::vector > unpack(const std::vector >& mat, unsigned int nscan, const std::vector& i2i) const; }; /****************************************************************//** * * \brief Helper class that manages a set of image coordinates in a way * that, among other things, enables calculation/implementation of partial * derivatives of images w.r.t. transformation parameters. * ********************************************************************/ class ImageCoordinates { public: ImageCoordinates(const NEWIMAGE::volume& ima) EddyTry : ImageCoordinates(static_cast(ima.xsize()),static_cast(ima.ysize()),static_cast(ima.zsize())) {} EddyCatch ImageCoordinates(unsigned int xn, unsigned int yn, unsigned int zn) EddyTry : _xn(xn), _yn(yn), _zn(zn) { _x = new float[_xn*_yn*_zn]; _y = new float[_xn*_yn*_zn]; _z = new float[_xn*_yn*_zn]; for (unsigned int k=0, indx=0; k<_zn; k++) { for (unsigned int j=0; j<_yn; j++) { for (unsigned int i=0; i<_xn; i++) { _x[indx] = float(i); _y[indx] = float(j); _z[indx++] = float(k); } } } } EddyCatch ImageCoordinates(const ImageCoordinates& inp) EddyTry : _xn(inp._xn), _yn(inp._yn), _zn(inp._zn) { _x = new float[_xn*_yn*_zn]; std::memcpy(_x,inp._x,_xn*_yn*_zn*sizeof(float)); _y = new float[_xn*_yn*_zn]; std::memcpy(_y,inp._y,_xn*_yn*_zn*sizeof(float)); _z = new float[_xn*_yn*_zn]; std::memcpy(_z,inp._z,_xn*_yn*_zn*sizeof(float)); } EddyCatch ImageCoordinates(ImageCoordinates&& inp) EddyTry : _xn(inp._xn), _yn(inp._yn), _zn(inp._zn) { _x = inp._x; inp._x = nullptr; _y = inp._y; inp._y = nullptr; _z = inp._z; inp._z = nullptr; } EddyCatch ~ImageCoordinates() { delete[] _x; delete[] _y; delete[] _z; } ImageCoordinates& operator=(const ImageCoordinates& rhs) EddyTry { if (this == &rhs) return(*this); delete[] _x; delete[] _y; delete[] _z; _xn = rhs._xn; _yn = rhs._yn; _zn = rhs._zn; _x = new float[_xn*_yn*_zn]; std::memcpy(_x,rhs._x,_xn*_yn*_zn*sizeof(float)); _y = new float[_xn*_yn*_zn]; std::memcpy(_y,rhs._y,_xn*_yn*_zn*sizeof(float)); _z = new float[_xn*_yn*_zn]; std::memcpy(_z,rhs._z,_xn*_yn*_zn*sizeof(float)); return(*this); } EddyCatch ImageCoordinates& operator=(ImageCoordinates&& rhs) EddyTry { if (this != &rhs) { delete[] _x; delete[] _y; delete[] _z; _xn = rhs._xn; _yn = rhs._yn; _zn = rhs._zn; _x = rhs._x; rhs._x = nullptr; _y = rhs._y; rhs._y = nullptr; _z = rhs._z; rhs._z = nullptr; } return(*this); } EddyCatch ImageCoordinates& operator+=(const ImageCoordinates& rhs) EddyTry { if (_xn != rhs._xn || _yn != rhs._yn || _zn != rhs._zn) throw EddyException("ImageCoordinates::operator-= size mismatch"); for (unsigned int i=0; i<_xn*_yn*_zn; i++) { _x[i]+=rhs._x[i]; _y[i]+=rhs._y[i]; _z[i]+=rhs._z[i]; } return(*this); } EddyCatch ImageCoordinates& operator-=(const ImageCoordinates& rhs) EddyTry { if (_xn != rhs._xn || _yn != rhs._yn || _zn != rhs._zn) throw EddyException("ImageCoordinates::operator-= size mismatch"); for (unsigned int i=0; i<_xn*_yn*_zn; i++) { _x[i]-=rhs._x[i]; _y[i]-=rhs._y[i]; _z[i]-=rhs._z[i]; } return(*this); } EddyCatch ImageCoordinates operator+(const ImageCoordinates& rhs) const EddyTry { return(ImageCoordinates(*this)+=rhs); } EddyCatch ImageCoordinates operator-(const ImageCoordinates& rhs) const EddyTry { return(ImageCoordinates(*this)-=rhs); } EddyCatch ImageCoordinates& operator/=(double div) EddyTry { if (div==0) throw EddyException("ImageCoordinates::operator/= attempt to divide by zero"); for (unsigned int i=0; i<_xn*_yn*_zn; i++) { _x[i]/=div; _y[i]/=div; _z[i]/=div; } return(*this); } EddyCatch NEWIMAGE::volume operator*(const NEWIMAGE::volume4D& vol) EddyTry { if (int(_xn) != vol.xsize() || int(_yn) != vol.ysize() || int(_zn) != vol.zsize() || vol.tsize() != 3) { throw EddyException("ImageCoordinates::operator* size mismatch"); } NEWIMAGE::volume ovol = vol[0]; for (unsigned int k=0, indx=0; k<_zn; k++) { for (unsigned int j=0; j<_yn; j++) { for (unsigned int i=0; i<_xn; i++) { ovol(i,j,k) = _x[indx]*vol(i,j,k,0) + _y[indx]*vol(i,j,k,1) + _z[indx]*vol(i,j,k,2); indx++; } } } return(ovol); } EddyCatch void Transform(const NEWMAT::Matrix& M) EddyTry { if (M.Nrows() != 4 || M.Ncols() != 4) throw EddyException("ImageCoordinates::Transform: Matrix M must be 4x4"); float M11 = M(1,1); float M12 = M(1,2); float M13 = M(1,3); float M14 = M(1,4); float M21 = M(2,1); float M22 = M(2,2); float M23 = M(2,3); float M24 = M(2,4); float M31 = M(3,1); float M32 = M(3,2); float M33 = M(3,3); float M34 = M(3,4); float *xp = _x; float *yp = _y; float *zp = _z; for (unsigned int i=0; i& M, // Array of matrices const std::vector >& grps) // Array of MB-groups of slices EddyTry { if (M.size() != grps.size()) throw EddyException("ImageCoordinates::Transform: Mismatch between M and grps"); for (unsigned int grp=0; grp slices = grps[grp]; float M11 = M[grp](1,1); float M12 = M[grp](1,2); float M13 = M[grp](1,3); float M14 = M[grp](1,4); float M21 = M[grp](2,1); float M22 = M[grp](2,2); float M23 = M[grp](2,3); float M24 = M[grp](2,4); float M31 = M[grp](3,1); float M32 = M[grp](3,2); float M33 = M[grp](3,3); float M34 = M[grp](3,4); for (unsigned int i=0; i& M, const std::vector >& grps) const EddyTry { ImageCoordinates rval = *this; rval.Transform(M,grps); return(rval); } EddyCatch void Write(const std::string& fname) const EddyTry { NEWMAT::Matrix omat(N(),3); for (unsigned int i=0; i= 0 && _x[i] <= (_xn-1) && _y[i] >= 0 && _y[i] <= (_yn-1) && _z[i] >= 0 && _z[i] <= (_zn-1)); } EddyCatch const float& x(unsigned int i) const EddyTry { return(_x[i]); } EddyCatch const float& y(unsigned int i) const EddyTry { return(_y[i]); } EddyCatch const float& z(unsigned int i) const EddyTry { return(_z[i]); } EddyCatch float& x(unsigned int i) EddyTry { return(_x[i]); } EddyCatch float& y(unsigned int i) EddyTry { return(_y[i]); } EddyCatch float& z(unsigned int i) EddyTry { return(_z[i]); } EddyCatch private: unsigned int _xn; unsigned int _yn; unsigned int _zn; float *_x; float *_y; float *_z; unsigned int slstart(unsigned int sl) const EddyTry { return(sl*_xn*_yn); } EddyCatch unsigned int slend(unsigned int sl) const EddyTry { return((sl+1)*_xn*_yn); } EddyCatch }; /****************************************************************//** * * \brief Helper class that manages histograms and calculates * mutual information. * ********************************************************************/ class MutualInfoHelper { public: MutualInfoHelper(unsigned int nbins) EddyTry : _nbins(nbins), _lset(false) { _mhist1 = new double[_nbins]; _mhist2 = new double[_nbins]; _jhist = new double[_nbins*_nbins]; } EddyCatch MutualInfoHelper(unsigned int nbins, float min1, float max1, float min2, float max2) EddyTry : _nbins(nbins), _min1(min1), _max1(max1), _min2(min2), _max2(max2), _lset(true) { _mhist1 = new double[_nbins]; _mhist2 = new double[_nbins]; _jhist = new double[_nbins*_nbins]; } EddyCatch virtual ~MutualInfoHelper() { delete[] _mhist1; delete[] _mhist2; delete[] _jhist; } void SetLimits(float min1, float max1, float min2, float max2) EddyTry { _min1 = min1; _max1 = max1; _min2 = min2; _max2 = max2; _lset = true; } EddyCatch double MI(const NEWIMAGE::volume& ima1, const NEWIMAGE::volume& ima2, const NEWIMAGE::volume& mask) const; double SoftMI(const NEWIMAGE::volume& ima1, const NEWIMAGE::volume& ima2, const NEWIMAGE::volume& mask) const; private: double plogp(double p) const EddyTry { if (p) return( - p*std::log(p)); else return(0.0); } EddyCatch unsigned int val_to_indx(float val, float min, float max, unsigned int nbins) const EddyTry { int tmp = static_cast((val-min)*static_cast(nbins-1)/(max-min) + 0.5); if (tmp < 0) tmp = 0; else if (static_cast(tmp) > (nbins-1)) tmp = nbins-1; return(static_cast(tmp)); } EddyCatch unsigned int val_to_floor_indx(float val, float min, float max, unsigned int nbins, float *rem) const EddyTry { unsigned int rval=0; float x = (val-min)*static_cast(nbins)/(max-min); // 0 <= x <= nbins for min <= val <= max if (x <= 0.5) { *rem = 0.0; rval = 0; } else if (x >= static_cast(nbins-0.5)) { *rem = 0.0; rval = nbins - 1; } else { rval = static_cast(x-0.5); *rem = x - 0.5 - static_cast(rval); } return(rval); } EddyCatch unsigned int _nbins; /// No. of bins of histograms float _min1; /// Minimum of ima1 float _max1; /// Maximum of ima1 float _min2; /// Minimum of ima2 float _max2; /// Maximum of ima2 bool _lset; /// True if the _min _max values are to be used mutable double *_mhist1; /// Marginal histogram of ima1 mutable double *_mhist2; /// Marginal histogram of ima2 mutable double *_jhist; /// Joint histogram of ima1 and ima2 }; /****************************************************************//** * * \brief Helper class that repacks from a stack of 2D images and a * stack of z-coords to a format suitable for doing a scattered * data recon GP-prediction. * ********************************************************************/ class Stacks2YVecsAndWgts { public: Stacks2YVecsAndWgts(unsigned int zsize, unsigned int tsize) : _y(zsize), _wgt(zsize), _sqrtwgt(zsize), _bvi(zsize), _n(zsize,0) { for (unsigned int i=0; i& stacks, const NEWIMAGE::volume4D& masks, const NEWIMAGE::volume4D& zcoord, unsigned int i, unsigned int j); NEWMAT::ColumnVector YVec(unsigned int indx) const { if (indx >= _y.size()) throw EddyException("Stacks2YVecsAndWgts::YVec: indx out of range"); return(return_vec(_y[indx],_n[indx])); } NEWMAT::ColumnVector Wgt(unsigned int indx) const { if (indx >= _wgt.size()) throw EddyException("Stacks2YVecsAndWgts::Wgt: indx out of range"); return(return_vec(_wgt[indx],_n[indx])); } NEWMAT::ColumnVector SqrtWgt(unsigned int indx) const { if (indx >= _sqrtwgt.size()) throw EddyException("Stacks2YVecsAndWgts::SqrtWgt: indx out of range"); return(return_vec(_sqrtwgt[indx],_n[indx])); } std::vector StdSqrtWgt(unsigned int indx) const { if (indx >= _sqrtwgt.size()) throw EddyException("Stacks2YVecsAndWgts::StdSqrtWgt: indx out of range"); std::vector rval(_n[indx]); std::copy_n(_sqrtwgt[indx].begin(),_n[indx],rval.begin()); return(rval); } NEWMAT::ColumnVector SqrtWgtYVec(unsigned int indx) const { if (indx >= _y.size()) throw EddyException("Stacks2YVecsAndWgts::SqrtWgtYVec: indx out of range"); return(NEWMAT::SP(return_vec(_sqrtwgt[indx],_n[indx]),return_vec(_y[indx],_n[indx]))); } const std::vector >& Indx(unsigned int indx) const { return(_bvi[indx]); } unsigned int NVal(unsigned int indx) const { return(_n[indx]); } unsigned int NVox() const { return(_y.size()); } private: std::vector > _y; // array of y-vectors std::vector > _wgt; // array of weight-vectors std::vector > _sqrtwgt; // array of square root of weight-vectors std::vector > > _bvi; // array of vectors of indicies into volume and slice std::vector _n; // Number of elements for a given voxel NEWMAT::ColumnVector return_vec(const std::vector& vec, unsigned int n) const { NEWMAT::ColumnVector rval(n); for (unsigned int i=0; i >& bvecs, const std::vector& grpi, const std::vector& grpb, const std::vector >& indx, unsigned int nval, const std::vector& hpar) EddyTry { common_construction(bvecs,grpi,grpb,indx,nval,hpar,nullptr); } EddyCatch Indicies2KMatrix(const std::vector >& bvecs, const std::vector& grpi, const std::vector& grpb, const std::vector >& indx, const std::vector& wgt, unsigned int nval, const std::vector& hpar) EddyTry { common_construction(bvecs,grpi,grpb,indx,nval,hpar,&wgt); } EddyCatch const NEWMAT::Matrix& GetKMatrix() const { return(_K); } NEWMAT::RowVector GetkVector(const NEWMAT::ColumnVector& bvec, unsigned int grp) const; private: std::vector _bvecs; std::vector _grpb; std::vector _log_grpb; std::vector _grpi; std::vector _hpar; std::vector _thpar; std::vector _wgt; NEWMAT::Matrix _K; void common_construction(const std::vector >& bvecs, const std::vector& grpi, const std::vector& grpb, const std::vector >& indx, unsigned int nval, const std::vector& hpar, const std::vector *wgt); }; } // End namespace EDDY #endif // End #ifndef EddyHelperClasses_h //////////////////////////////////////////////// // // Here starts Doxygen documentation // //////////////////////////////////////////////// /*! * \fn EDDY::DiffPara::DiffPara(const NEWMAT::ColumnVector& bvec, double bval) * Contructs a DiffPara object from a b-vector and a b-value. * \param bvec ColumnVector with three elements. Will be normalised, but must have norm different from zero. * \param bval b-value. Must be non-negative. */ /*! * \fn EDDY::DiffPara::operator==(const DiffPara& rhs) const * Will return true if calls to both EddyUtils::AreInSameShell and EddyUtils::HaveSameDirection are true. */ /*! * \fn AcqPara::AcqPara(const NEWMAT::ColumnVector& pevec, double rotime) * Constructs an AcqPara object from phase-encode direction and total read-out time. * \param pevec Normalised vector desribing the direction of the phase-encoding. At present the third element * (the z-direction) must be zero (i.e. it only allows phase-encoding in the xy-plane. * \param rotime The time between the collection of the midpoint of the first echo and the last echo. */