// 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); } NEWIMAGE::volume ECScan::GetUnwarpedOriginalIma(// Input boost::shared_ptr > susc, // Output NEWIMAGE::volume& omask) const { return(transform_to_model_space(this->GetOriginalIma(),susc,omask)); } 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)); } NEWIMAGE::volume ECScan::GetUnwarpedIma(// Input boost::shared_ptr > susc, // Output NEWIMAGE::volume& omask) const { return(transform_to_model_space(this->GetIma(),susc,omask)); } 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)); } 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; } } 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); } 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); } 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); } 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)); } void ECScanManager::SeparateFieldOffsetFromMovement() { if (HasFieldOffset()) { // Put everything that can be offset into offset vector NEWMAT::ColumnVector hz = hz_vector_with_everything(); // Get design explaining offset as off-iso-centre + linear with time NEWMAT::Matrix X = offset_design_matrix(); // Get explained component for reintroduction to offset NEWMAT::ColumnVector offset = (X*MISCMATHS::pinv(X))*hz; // Reintroduce explained component 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; i1) linear_part.ReSize(NScans(DWI),NoOfSessions()+1); else linear_part.ReSize(NScans(DWI),1); 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); }