// Declarations of classes that implements a scan // or a collection of scans within the EC project. // // ECScanClasses.h // // Jesper Andersson, FMRIB Image Analysis Group // // Copyright (C) 2011 University of Oxford // #include #include #include #include #include #include "newmat.h" #ifndef EXPOSE_TREACHEROUS #define EXPOSE_TREACHEROUS // To allow us to use .set_sform etc #endif #include "newimage/newimageall.h" #include "miscmaths/miscmaths.h" #include "warpfns/warpfns.h" #include "topup/topup_file_io.h" #include "topup/displacement_vector.h" #include "EddyHelperClasses.h" #include "EddyUtils.h" #include "ECScanClasses.h" using namespace EDDY; NEWIMAGE::volume4D ECScan::field_for_scan_to_model_transform(// Input boost::shared_ptr > susc, // Output NEWIMAGE::volume *omask, NEWIMAGE::volume *jac) const { // Get RB matrix and EC field NEWMAT::Matrix iR = this->InverseMovementMatrix(); NEWIMAGE::volume eb = this->ECField(); // Transform EC field using RB NEWIMAGE::volume tot = this->GetIma(); tot = 0.0; NEWIMAGE::volume mask1(tot.xsize(),tot.ysize(),tot.zsize()); NEWIMAGE::copybasicproperties(tot,mask1); mask1 = 1; // mask1 defines where the transformed EC map is valid NEWIMAGE::affine_transform(eb,iR,tot,mask1); // Defined in warpfns.h if (omask) { *omask = EddyUtils::ConvertMaskToFloat(mask1); EddyUtils::SetTrilinearInterp(*omask); } // Add transformed EC and susc if (susc) tot += *susc; // Convert Hz-map to displacement field NEWIMAGE::volume4D dfield = FieldUtils::Hz2VoxelDisplacements(tot,this->GetAcqPara()); // Get Jacobian of tot map if (jac) *jac = FieldUtils::GetJacobian(dfield,this->GetAcqPara()); // Transform dfield from voxels to mm dfield = FieldUtils::Voxel2MMDisplacements(dfield); return(dfield); } NEWIMAGE::volume4D ECScan::field_for_model_to_scan_transform(// Input boost::shared_ptr > susc, // Output NEWIMAGE::volume *omask, NEWIMAGE::volume *jac) const { // Get RB matrix and EC field NEWIMAGE::volume tot = this->ECField(); NEWIMAGE::volume mask1(this->GetIma().xsize(),this->GetIma().ysize(),this->GetIma().zsize()); NEWIMAGE::copybasicproperties(this->GetIma(),mask1); mask1 = 1; // mask1 defines where the transformed susc map is valid if (susc) { NEWMAT::Matrix R = this->ForwardMovementMatrix(); NEWIMAGE::volume tsusc = *susc; tsusc = 0.0; NEWIMAGE::affine_transform(*susc,R,tsusc,mask1); // Defined in warpfns.h tot += tsusc; } // Convert HZ-map to displacement field NEWIMAGE::volume4D dfield = FieldUtils::Hz2VoxelDisplacements(tot,this->GetAcqPara()); // Invert Total displacement field bool own_mask = false; if (!omask) { omask = new NEWIMAGE::volume(mask1.xsize(),mask1.ysize(),mask1.zsize()); own_mask=true; } *omask = 1.0; // omask defines where the inverted total map is valid NEWIMAGE::volume4D idfield = FieldUtils::InvertDisplacementField(dfield,this->GetAcqPara(),EddyUtils::ConvertMaskToFloat(mask1),*omask); EddyUtils::SetTrilinearInterp(*omask); if (own_mask) delete omask; // Get Jacobian of inverted tot map if (jac) *jac = FieldUtils::GetJacobian(idfield,this->GetAcqPara()); // Transform idfield from voxels to mm idfield = FieldUtils::Voxel2MMDisplacements(idfield); return(idfield); } /*! * Returns the original (unsmoothed) image transformed into model space, i.e. undistorted space. * \param[in] susc (Safe) pointer to a susceptibility field (in Hz). * \param[out] omask Mask indicating valid voxels (voxels that fall within the original image). * \return Original (unsmoothed) image transformed into model space, i.e. undistorted space. */ NEWIMAGE::volume ECScan::GetUnwarpedOriginalIma(// Input boost::shared_ptr > susc, // Output NEWIMAGE::volume& omask) const { return(transform_to_model_space(this->GetOriginalIma(),susc,omask)); } /*! * Returns the original (unsmoothed) image transformed into model space, i.e. undistorted space. * \param[in] susc (Safe) pointer to a susceptibility field (in Hz). * \return Original (unsmoothed) image transformed into model space, i.e. undistorted space. */ NEWIMAGE::volume ECScan::GetUnwarpedOriginalIma(// Input boost::shared_ptr > susc) const { NEWIMAGE::volume skrutt = this->GetOriginalIma(); skrutt = 0.0; return(transform_to_model_space(this->GetOriginalIma(),susc,skrutt)); } /*! * Returns the smoothed image transformed into model space, i.e. undistorted space. * \param[in] susc (Safe) pointer to a susceptibility field (in Hz). * \param[out] omask Mask indicating valid voxels (voxels that fall within the original image). * \return Smoothed image transformed into model space, i.e. undistorted space. */ NEWIMAGE::volume ECScan::GetUnwarpedIma(// Input boost::shared_ptr > susc, // Output NEWIMAGE::volume& omask) const { return(transform_to_model_space(this->GetIma(),susc,omask)); } /*! * Returns the smoothed image transformed into model space, i.e. undistorted space. * \param[in] susc (Safe) pointer to a susceptibility field (in Hz). * \return Smoothed image transformed into model space, i.e. undistorted space. */ NEWIMAGE::volume ECScan::GetUnwarpedIma(// Input boost::shared_ptr > susc) const { NEWIMAGE::volume skrutt = this->GetIma(); skrutt = 0.0; return(transform_to_model_space(this->GetIma(),susc,skrutt)); } /*! * Returns a vector with translations (in mm) resulting from a * field of 1Hz. If for example the scan has been acquired with * positive phase-encode blips in the x-direction it will return * a vector with a positive value in the first element and zero * in the other two elements. * \return A 3x1 vector with translations (in mm) */ NEWMAT::ColumnVector ECScan::GetHz2mmVector() const { NEWMAT::ColumnVector hz2mm(3); hz2mm(1) = _ima.xdim() * (_acqp.PhaseEncodeVector())(1) * _acqp.ReadOutTime(); hz2mm(2) = _ima.ydim() * (_acqp.PhaseEncodeVector())(2) * _acqp.ReadOutTime(); hz2mm(3) = _ima.zdim() * (_acqp.PhaseEncodeVector())(3) * _acqp.ReadOutTime(); return(hz2mm); } void ECScan::SetFWHM(double fwhm) { if (_fwhm==fwhm) return; if (_fwhm && !fwhm) { _sima.reinitialize(0,0,0); _fwhm = 0.0; return; } else { _sima = NEWIMAGE::smooth(_ima,fwhm/std::sqrt(8.0*std::log(2.0))); _fwhm = fwhm; return; } } /*! * Routine that replaces selected (from ol) slices from a replacement volume (rep) for valid voxels (given by inmask). Used as part of outlier detection and replacement. Hence the rep volume is typically a predicted volume. * \param[in] rep The volume from which to pick slices to replace those in _ima with. This would typically be a prediction on model space. * \param[in] susc Susceptibilty induced off-resonance field (Hz) * \param[in] inmask Mask that indicates what voxels in rep are valid. Should be in same space as rep, typically model space. * \param[in] ol Vector of indicies (zero-offset) indicating which slices to replace. */ void ECScan::ReplaceSlices(const NEWIMAGE::volume& rep, boost::shared_ptr > susc, const NEWIMAGE::volume& inmask, const std::vector& ol) { // Transform prediction into observation space NEWIMAGE::volume pios = EddyUtils::TransformModelToScanSpace(rep,*this,susc); // Transform binary mask into observation space NEWIMAGE::volume mask = rep; mask = 0.0; NEWIMAGE::volume bios = EddyUtils::transform_model_to_scan_space(inmask,*this,susc,false,mask,NULL,NULL); bios.binarise(0.9); // Value above (arbitrary) 0.9 implies valid voxels mask *= bios; // Volume and input mask falls within FOV // Replace requested slices where the resampled rep is valid for (unsigned int ii=0; ii ECScan::motion_correct(const NEWIMAGE::volume& inima, NEWIMAGE::volume *omask) const { // Transform image using inverse RB NEWMAT::Matrix iR = InverseMovementMatrix(); NEWIMAGE::volume ovol = inima; ovol = 0.0; NEWIMAGE::volume mask(ovol.xsize(),ovol.ysize(),ovol.zsize()); NEWIMAGE::copybasicproperties(inima,mask); mask = 1; NEWIMAGE::affine_transform(inima,iR,ovol,mask); *omask = EddyUtils::ConvertMaskToFloat(mask); EddyUtils::SetTrilinearInterp(*omask); return(ovol); } NEWIMAGE::volume ECScan::transform_to_model_space(// Input const NEWIMAGE::volume& inima, boost::shared_ptr > susc, // Output NEWIMAGE::volume& omask) const { // Get total field from scan NEWIMAGE::volume jac; NEWIMAGE::volume4D dfield = FieldForScanToModelTransform(susc,omask,jac); // Transform image using inverse RB, dfield and Jacobian NEWMAT::Matrix iR = InverseMovementMatrix(); NEWIMAGE::volume ovol = GetIma(); ovol = 0.0; NEWIMAGE::volume mask2(ovol.xsize(),ovol.ysize(),ovol.zsize()); NEWIMAGE::copybasicproperties(inima,mask2); mask2 = 1; NEWIMAGE::general_transform(inima,iR,dfield,ovol,mask2); // Combine all masks omask *= EddyUtils::ConvertMaskToFloat(mask2); EddyUtils::SetTrilinearInterp(omask); return(jac*ovol); } /*! * Constructor for the ECScanManager class. * \param imafname Name of 4D file with diffusion weighted and b=0 images. * \param maskfname Name of image file with mask indicating brain (one) and non-brain (zero). * \param acqpfname Name of text file with acquisition parameters. * \param topupfname Basename for topup output. * \param bvecsfname Name of file containing diffusion gradient direction vectors. * \param bvalsfname Name of file containing b-values. * \param ecmodel Enumerated value specifying what EC-model to use. * \param indicies Vector of indicies so that indicies[i] specifies what row in * the acqpfname file corresponds to scan i (zero-offset). * \param sess Vector of session numbers for each of the scans in imafname. The session numbers should be in the range 1--no_of_sessions. * \param no_of_sessions Number of sessions in which data was acquired. */ ECScanManager::ECScanManager(const std::string& imafname, const std::string& maskfname, const std::string& acqpfname, const std::string& topupfname, const std::string& bvecsfname, const std::string& bvalsfname, EDDY::ECModel ecmodel, const std::vector& indicies, const std::vector& sess, unsigned int no_of_sessions) : _has_topup(false) { // Set number of sessions that data was acquired in _nsess = no_of_sessions; // Read acquisition parameters file TOPUP::TopupDatafileReader tdfr(acqpfname); boost::shared_ptr tfrp; // Read output from topup if (topupfname != string("")) { tfrp = boost::shared_ptr(new TOPUP::TopupFileReader(topupfname)); _topup_field = boost::shared_ptr >(new NEWIMAGE::volume(tfrp->FieldAsVolume())); EddyUtils::SetSplineInterp(*_topup_field); _topup_field->forcesplinecoefcalculation(); // N.B. neccessary if OpenMP is to work _has_topup = true; } // Read bvecs and bvals NEWMAT::Matrix bvecsM = MISCMATHS::read_ascii_matrix(bvecsfname); if (bvecsM.Nrows()>bvecsM.Ncols()) bvecsM = bvecsM.t(); NEWMAT::Matrix bvalsM = MISCMATHS::read_ascii_matrix(bvalsfname); if (bvalsM.Nrows()>bvalsM.Ncols()) bvalsM = bvalsM.t(); // Read mask NEWIMAGE::read_volume(_mask,maskfname); EddyUtils::SetTrilinearInterp(_mask); // Go through and read all image volumes, spliting b0s and dwis NEWIMAGE::volume4D all; NEWIMAGE::read_volume4D(all,imafname); _sf = 100.0 / mean_of_first_b0(all,_mask,bvecsM,bvalsM); _fi.resize(all.tsize()); for (int s=0; s ecp; switch (ecmodel) { case EDDY::Linear: ecp = boost::shared_ptr(new LinearScanECModel(_has_topup)); break; case EDDY::Quadratic: ecp = boost::shared_ptr(new QuadraticScanECModel(_has_topup)); break; case EDDY::Cubic: ecp = boost::shared_ptr(new CubicScanECModel(_has_topup)); break; default: throw EddyException("ECScanManager::ECScanManager: Invalid EC model"); } if (_has_topup) { NEWMAT::Matrix fwd_matrix = TOPUP::MovePar2Matrix(tfrp->MovePar(indicies[s]),all[s]); NEWMAT::ColumnVector bwd_mp = TOPUP::Matrix2MovePar(fwd_matrix.i(),all[s]); ecp->SetParams(bwd_mp,EDDY::MOVEMENT); } NEWIMAGE::volume tmp = all[s]*_sf; EddyUtils::SetSplineInterp(tmp); _scans.push_back(ECScan(tmp,acqp,dp,ecp,sess[s]-1)); _fi[s].first = 0; _fi[s].second = _scans.size() - 1; } else { boost::shared_ptr ecp = boost::shared_ptr(new MovementScanECModel()); if (_has_topup) { NEWMAT::Matrix fwd_matrix = TOPUP::MovePar2Matrix(tfrp->MovePar(indicies[s]),all[s]); NEWMAT::ColumnVector bwd_mp = TOPUP::Matrix2MovePar(fwd_matrix.i(),all[s]); ecp->SetParams(bwd_mp,EDDY::MOVEMENT); } NEWIMAGE::volume tmp = all[s]*_sf; EddyUtils::SetSplineInterp(tmp); _b0scans.push_back(ECScan(tmp,acqp,dp,ecp,sess[s]-1)); _fi[s].first = 1; _fi[s].second = _b0scans.size() - 1; } } } unsigned int ECScanManager::NScans(ScanType st) const { unsigned int rval = 0; switch (st) { case ANY: rval = _scans.size()+_b0scans.size(); break; case DWI: rval = _scans.size(); break; case B0: rval = _b0scans.size(); break; default: break; } return(rval); } unsigned int ECScanManager::NLSRPairs(ScanType st) const { unsigned int rval = 0; if (!CanDoLSRResampling()) throw; else rval = NScans(st)/2; return(rval); } /*! * Returns the mapping between the "type-specific" DWI indexing and the * "global" indexing. The latter also corresponding to the indexing * on disc. * \return A vector of indicies. The length of the vector is NScans(DWI) * and rval[i-1] gives the global index of the i'th dwi scan. */ std::vector ECScanManager::GetDwi2GlobalIndexMapping() const { std::vector i2i(_scans.size()); for (unsigned int i=0; i<_fi.size(); i++) { if (!_fi[i].first) i2i[_fi[i].second] = i; } return(i2i); } /*! * Returns the mapping between the "type-specific" b0 indexing and the * "global" indexing. The latter also corresponding to the indexing * on disc. * \return A vector of indicies. The length of the vector is NScans(b0) * and rval[i-1] gives the global index of the i'th b0 scan. */ std::vector ECScanManager::Getb02GlobalIndexMapping() const { std::vector i2i(_b0scans.size()); for (unsigned int i=0; i<_fi.size(); i++) { if (_fi[i].first) i2i[_fi[i].second] = i; } return(i2i); } /*! * Will return true if there are "sufficient" b=0 scans "sufficiently" * interspersed so that the movement parameters from these can help in * separating field offset from "true" movements. The ""s are there to * indicate the arbitrariness of the code, and that it might need to be * revisited based on empricial experience. */ bool ECScanManager::B0sAreInterspersed() const { unsigned int nall = NScans(); std::vector b02g = Getb02GlobalIndexMapping(); if (b02g.size() > 2 && b02g[0] < 0.25*nall && b02g.back() > 0.75*nall) return(true); else return(false); } std::pair ECScanManager::GetLSRPair(unsigned int i, ScanType st) const { std::pair rval(0,0); if (!CanDoLSRResampling()) throw; else if (i >= NLSRPairs(st)) throw; else { rval.first = i; rval.second = NLSRPairs(st) + i; } return(rval); } bool ECScanManager::CanDoLSRResampling() const { // Do first pass to find where we go from one // blip direction to the next. NEWMAT::ColumnVector blip1 = Scan(0,ANY).GetAcqPara().PhaseEncodeVector(); unsigned int n_dir1 = 1; for (; n_dir1 ECScanManager::LSRResamplePair(// Input unsigned int i, unsigned int j, ScanType st, // Output NEWIMAGE::volume& omask) const { if (!EddyUtils::AreMatchingPair(Scan(i,st),Scan(j,st))) throw EddyException("ECScanManager::LSRResamplePair:: Mismatched pair"); // Resample both images using rigid body parameters NEWIMAGE::volume imask, jmask; NEWIMAGE::volume imai = Scan(i,st).GetMotionCorrectedOriginalIma(imask); NEWIMAGE::volume imaj = Scan(j,st).GetMotionCorrectedOriginalIma(jmask); NEWIMAGE::volume mask = imask*jmask; omask.reinitialize(mask.xsize(),mask.ysize(),mask.zsize()); omask = 1.0; NEWIMAGE::volume ovol(imai.xsize(),imai.ysize(),imai.zsize()); NEWIMAGE::copybasicproperties(imai,ovol); // Get fields NEWIMAGE::volume4D fieldi = Scan(i,st).FieldForScanToModelTransform(GetSuscHzOffResField()); NEWIMAGE::volume4D fieldj = Scan(j,st).FieldForScanToModelTransform(GetSuscHzOffResField()); // Check what direction phase-encode is in bool pex = false; unsigned int sz = fieldi[0].ysize(); if (Scan(i,st).GetAcqPara().PhaseEncodeVector()(1)) { pex = true; sz = fieldi[0].xsize(); } TOPUP::DispVec dvi(sz), dvj(sz); NEWMAT::Matrix StS = dvi.GetS_Matrix(false); StS = StS.t()*StS; NEWMAT::ColumnVector zeros; if (pex) zeros.ReSize(imai.xsize()); else zeros.ReSize(imai.ysize()); zeros = 0.0; for (int k=0; k ECScanManager::GetUnwarpedOrigScan(unsigned int indx, NEWIMAGE::volume& omask, ScanType st) const { if (!index_kosher(indx,st)) throw EddyException("ECScanManager::GetUnwarpedOrigScan: index out of range"); return(Scan(indx,st).GetUnwarpedOriginalIma(_topup_field,omask)); } NEWIMAGE::volume ECScanManager::GetUnwarpedScan(unsigned int indx, NEWIMAGE::volume& omask, ScanType st) const { if (!index_kosher(indx,st)) throw EddyException("ECScanManager::GetUnwarpedScan: index out of range"); return(Scan(indx,st).GetUnwarpedIma(_topup_field,omask)); } /*! * This function attempts to distinguish between actual subject movement and * things that appear as such. The latter can be for example a field-offset * caused by the scanner iso-centre not coinciding with the centre of the * image FOV and field drifts caused by temprature changes. * * It starts by extracting everything that can potentially be explained * as a field offset and putting this into a single vector (one value * per scan) that is scaled in Hz. It does this through a call to * hz_vector_with_everything(). Let us denote this vector by \f$\mathbf{y}\f$ * * As a second step it will create a "design matrix" which (typically) consists of * three columns which are the parameters (slopes) of the linear part of * the EC field and a set of columns that model linear (with time) drift * of the field as a consequence of temprature drift. Let us denote this * matrix by \f$\mathbf{X}\f$. * * The third step consists of modelling \f$\mathbf{y}\f$ by \f$\mathbf{X}\f$ * so that the residuals (i.e. the unexplained part) becomes the new estimate * of the translations, i.e. \f$\mathbf{p}=(\mathbf{y}-\mathbf{X}\mathbf{X}^+\mathbf{y})\mathbf{h}^T\f$ * where \f$\mathbf{X}^+\f$ denotes the pseudo-inverse of \f$\mathbf{X}\f$ and where * \f$\mathbf{h}\f$ is a column-vector translating a (spatially constant) off-resonance * field into translations (in mm). */ void ECScanManager::SeparateFieldOffsetFromMovement(EDDY::HzModel m) { bool debug = false; if (HasFieldOffset()) { // Only an issue of offset has been modeled if (debug) { cout << "Entered SeparateFieldOffsetFromMovement with model "; if (m == ModelMovements) cout << "ModelMovements" << endl; else cout << "ModelOffset" << endl; } // Put everything that can be offset into offset vector if (debug) cout << "Calling hz_vector_with_everything" << endl; NEWMAT::ColumnVector hz = hz_vector_with_everything(); if (debug) { cout << "Writing hz-vector for debugging purposes" << endl; MISCMATHS::write_ascii_matrix("hz_vector_for_debugging.txt",hz); } // Get design explaining movements/offset. NEWMAT::Matrix X; NEWMAT::ColumnVector offset; NEWMAT::ColumnVector mov_in_hz; if (m == EDDY::ModelMovements) { if (debug) cout << "Calling movement_design_matrix" << endl; X = movement_design_matrix(debug); mov_in_hz = (X*MISCMATHS::pinv(X))*hz; offset = hz - mov_in_hz; } else { // means that m == ModelOffset if (debug) cout << "Calling offset_design_matrix" << endl; X = offset_design_matrix(debug); offset = (X*MISCMATHS::pinv(X))*hz; mov_in_hz = hz - offset; } if (debug) { cout << "Writing offset/movement design matrix for debugging purposes" << endl; std::string fname = "offset_design_matrix_for_debugging.txt"; if (m == EDDY::ModelMovements) fname = "movement_design_matrix_for_debugging.txt"; MISCMATHS::write_ascii_matrix(fname,X); } // Reintroduce separated components if (debug) cout << "Adjusting field offsets" << endl; for (unsigned int i=0; i ovol(Scan(0).GetIma().xsize(),Scan(0).GetIma().ysize(),Scan(0).GetIma().zsize(),NScans(st)); NEWIMAGE::copybasicproperties(Scan(0).GetIma(),ovol); for (unsigned int i=0; i ovol(Scan(0,st).GetOriginalIma().xsize(),Scan(0,st).GetIma().ysize(),Scan(0,st).GetIma().zsize(),NScans(st)); NEWIMAGE::copybasicproperties(Scan(0,st).GetOriginalIma(),ovol); for (unsigned int i=0; i tmp = Scan(i,st).GetOriginalIma() / ScaleFactor(); { ovol[i] = tmp; } } NEWIMAGE::write_volume4D(ovol,fname); } /*! * Creates a "design matrix" that allows us to divide the "hz vector" into the part * that can be explained as movement and the part that cannot be explained (and that * we will therefore assume is offset). * The design will consist of a three-dimensional sub-space of the space spanned by * the movement parameters that do not coincide with the translations in the PE * direction (three rotations and one or two translations). This is predicated on * my experience that in reality the dof of rigid body movement is less than six. * * The difference between this routine and offset_design_matrix is that here we * try to find all the movment that can be explained by the other movement parameters, * and then assume that the remainder is offset. * In offset_design_matrix we will instead attempt to find all the offset that * can be explained (by EC) and then we assume that the remainder is movement. * * \return An Nxm design matrix where N is the number of DWIs and m will depend on * the ScanECModel and the number of sessions. */ NEWMAT::Matrix ECScanManager::movement_design_matrix(bool debug) const { // Make an initial design from b-vecs that have // been mean-corrected on a per sessions basis NEWMAT::Matrix X1(NScans(DWI),3); for (unsigned int i=0; i1) linear_part.ReSize(NScans(DWI),NoOfSessions()+1); else linear_part.ReSize(NScans(DWI),1); if (debug) cout << "Initialising linear part" << endl; linear_part = 0.0; for (unsigned int i=0; i1) { for (unsigned int i=0; i= NScans(st)) throw EddyException("ECScanManager::set_reference: ref index out of bounds"); NEWMAT::Matrix Mr = Scan(ref,st).ForwardMovementMatrix(); for (unsigned int i=0; i& vols, const NEWIMAGE::volume& mask, const NEWMAT::Matrix& bvecs, const NEWMAT::Matrix& bvals) const { double rval = 0.0; for (int s=0; s ovol(Scan(0,st).GetIma().xsize(),Scan(0,st).GetIma().ysize(),Scan(0,st).GetIma().zsize(),NScans(st)); NEWIMAGE::copybasicproperties(Scan(0,st).GetIma(),ovol); // # pragma omp parallel for shared(ovol) for (unsigned int i=0; i tmp = GetUnwarpedScan(i,st) / ScaleFactor(); // # pragma omp critical { ovol[i] = tmp; } } NEWIMAGE::write_volume4D(ovol,fname); } void ECScanManager::write_lsr_registered_images(const std::string& fname, ScanType st) { SetFWHM(0.0); NEWIMAGE::volume4D ovol(Scan(0,st).GetIma().xsize(),Scan(0,st).GetIma().ysize(),Scan(0,st).GetIma().zsize(),NLSRPairs(st)); NEWIMAGE::copybasicproperties(Scan(0,st).GetIma(),ovol); // # pragma omp parallel for shared(ovol) for (unsigned int i=0; i par = GetLSRPair(i,st); NEWIMAGE::volume omask; NEWIMAGE::volume tmp = LSRResamplePair(par.first,par.second,st,omask) / ScaleFactor(); // # pragma omp critical { ovol[i] = tmp; } } NEWIMAGE::write_volume4D(ovol,fname); } bool ECScanManager::has_pe_in_direction(unsigned int dir, ScanType st) const { if (dir != 1 && dir != 2) throw EddyException("ECScanManager::has_pe_in_direction: index out of range"); for (unsigned int i=0; i