/*! \file BiasFieldEstimatorImpl.cpp \brief Contains one implementation of class for estimation of a bias field \author Jesper Andersson \version 1.0b, December, 2017. */ // // BiasFieldEstimatorImpl.h // // Jesper Andersson, FMRIB Image Analysis Group // // Copyright (C) 2017 University of Oxford // #ifndef BiasFieldEstimatorImpl_h #define BiasFieldEstimatorImpl_h #include #include #include #include #include #include #include #include "armawrap/newmat.h" #include "basisfield/basisfield.h" #include "basisfield/splinefield.h" #include "newimage/newimageall.h" #include "miscmaths/SpMat.h" #include "miscmaths/SpMatMatrices.h" #include "EddyHelperClasses.h" #include "BiasFieldEstimator.h" namespace EDDY { class BiasFieldEstimatorImpl { public: BiasFieldEstimatorImpl() : _nima(0) {} /// Set ref scan void SetRefScan(const NEWIMAGE::volume& mask, const EDDY::ImageCoordinates& coords); /// Add scan void AddScan(const NEWIMAGE::volume& predicted, const NEWIMAGE::volume& observed, const NEWIMAGE::volume& mask, const EDDY::ImageCoordinates& coords); /// Calculate and return "direct" representation of the bias-field NEWIMAGE::volume GetField(double lambda) const; /// Calculate and return spline basis-field representation of the bias-field NEWIMAGE::volume GetField(double ksp, double lambda) const; /// Caclulate and return At matrix, for debug purposes MISCMATHS::SpMat GetAtMatrix(const EDDY::ImageCoordinates& coords, const NEWIMAGE::volume& predicted, const NEWIMAGE::volume& mask) const; /// Calculate and return At* matrix, for debug purposes MISCMATHS::SpMat GetAtStarMatrix(const EDDY::ImageCoordinates& coords, const NEWIMAGE::volume& mask) const; /// Calculate and return A*b, where b is the field, for debug purposes NEWIMAGE::volume GetATimesField(const EDDY::ImageCoordinates& coords, const NEWIMAGE::volume& predicted, const NEWIMAGE::volume& mask, const NEWIMAGE::volume& field) const; /// Write out current state for debug purposes void Write(const std::string& basename) const EddyTry { MISCMATHS::write_ascii_matrix(_Aty,"BiasFieldEstimatorImpl_" + basename + "_Aty.txt"); _AtA.Print("BiasFieldEstimatorImpl_" + basename + "_AtA.txt"); } EddyCatch private: unsigned int _nima; /// Number of loaded images std::vector _isz; /// Image matrix size std::vector _vxs; /// Voxel size MISCMATHS::SpMat _rAt; /// A^T matrix for reference volume MISCMATHS::SpMat _AtA; /// \Sum A^T A in paper NEWMAT::ColumnVector _Aty; /// \Sum A^t f-\hat{f} in paper struct PointersForSpMat { PointersForSpMat(unsigned int N) : nz(0), irp(std::unique_ptr(new unsigned int[8*N])), jcp(std::unique_ptr(new unsigned int[N+1])), sp(std::unique_ptr(new double[8*N])) {} PointersForSpMat(PointersForSpMat&& rhs) : nz(rhs.nz), // Moving is fine irp(std::move(rhs.irp)), jcp(std::move(rhs.jcp)), sp(std::move(rhs.sp)) {} PointersForSpMat(const PointersForSpMat& rhs) = delete; // No copying PointersForSpMat& operator=(const PointersForSpMat& rhs) = delete; // No copying unsigned int nz; std::unique_ptr irp; std::unique_ptr jcp; std::unique_ptr sp; }; unsigned int nvox() const { return(_isz[0]*_isz[1]*_isz[2]); } unsigned int ijk2indx(unsigned int i, unsigned int j, unsigned int k, const EDDY::ImageCoordinates& c) const { return(k*c.NX()*c.NY() + j*c.NX() + i); } void get_wgts(const double& dx, const double& dy, const double& dz, double& w000, double& w100, double& w010, double& w110, double& w001, double& w101, double& w011, double& w111) const { w000 = w100 = w010 = w110 = 1.0 - dz; w001 = w101 = w011 = w111 = dz; w000 *= 1.0 - dy; w100 *= 1.0 - dy; w001 *= 1.0 - dy; w101 *= 1.0 - dy; w010 *= dy; w110 *= dy; w011 *= dy; w111 *= dy; w000 *= 1.0 - dx; w010 *= 1.0 - dx; w001 *= 1.0 - dx; w011 *= 1.0 - dx; w100 *= dx; w110 *= dx; w101 *= dx; w111 *= dx; } PointersForSpMat make_At_star_CSC(// Input const EDDY::ImageCoordinates& coords, const NEWIMAGE::volume& mask) const; void multiply_At_star_CSC_by_image(const NEWIMAGE::volume& ima, const NEWIMAGE::volume& mask, unsigned int* const jcp, double* const sp) const; }; BiasFieldEstimatorImpl::PointersForSpMat BiasFieldEstimatorImpl::make_At_star_CSC(// Input const EDDY::ImageCoordinates& coords, const NEWIMAGE::volume& mask) const EddyTry { // Make A^T in a Compressed Column Storage representation PointersForSpMat ptrs(coords.N()); unsigned int ii = 0; // Index intp irp and sp unsigned int ji = 0; // Index into jcp NEWMAT::ColumnVector vmask = mask.vec(); for (unsigned int i=0; i 0.0 && coords.IsInBounds(i)) { // If voxel falls within volume unsigned int xi = floor(coords.x(i)); unsigned int yi = floor(coords.y(i)); unsigned int zi = floor(coords.z(i)); double dx = coords.x(i) - xi; double dy = coords.y(i) - yi; double dz = coords.z(i) - zi; if (dx < 1e-6 && dy < 1e-6 && dz < 1e-6) { // Voxel falls "exactly" on original voxel centre ptrs.irp[ii] = this->ijk2indx(xi,yi,zi,coords); ptrs.sp[ii++] = 1.0; } else { double w000, w100, w010, w110, w001, w101, w011, w111; get_wgts(dx,dy,dz,w000,w100,w010,w110,w001,w101,w011,w111); if (w000 > 1e-6) { ptrs.irp[ii] = this->ijk2indx(xi,yi,zi,coords); ptrs.sp[ii++] = w000; } if (w100 > 1e-6) { ptrs.irp[ii] = this->ijk2indx(xi+1,yi,zi,coords); ptrs.sp[ii++] = w100; } if (w010 > 1e-6) { ptrs.irp[ii] = this->ijk2indx(xi,yi+1,zi,coords); ptrs.sp[ii++] = w010; } if (w110 > 1e-6) { ptrs.irp[ii] = this->ijk2indx(xi+1,yi+1,zi,coords); ptrs.sp[ii++] = w110; } if (w001 > 1e-6) { ptrs.irp[ii] = this->ijk2indx(xi,yi,zi+1,coords); ptrs.sp[ii++] = w001; } if (w101 > 1e-6) { ptrs.irp[ii] = this->ijk2indx(xi+1,yi,zi+1,coords); ptrs.sp[ii++] = w101; } if (w011 > 1e-6) { ptrs.irp[ii] = this->ijk2indx(xi,yi+1,zi+1,coords); ptrs.sp[ii++] = w011; } if (w111 > 1e-6) { ptrs.irp[ii] = this->ijk2indx(xi+1,yi+1,zi+1,coords); ptrs.sp[ii++] = w111; } } } } ptrs.jcp[ji] = ii; ptrs.nz = ii; return(ptrs); } EddyCatch void BiasFieldEstimatorImpl::multiply_At_star_CSC_by_image(const NEWIMAGE::volume& ima, const NEWIMAGE::volume& mask, unsigned int* const jcp, double* const sp) const EddyTry { unsigned int indx = 0; for (int k=0; k& mask, //(mask.xsize()); _isz[1] = static_cast(mask.ysize()); _isz[2] = static_cast(mask.zsize()); _vxs.resize(3); _vxs[0] = static_cast(mask.xdim()); _vxs[1] = static_cast(mask.ydim()); _vxs[2] = static_cast(mask.zdim()); } else { // Check that new ref scan is compatible with old if (static_cast(mask.xsize()) != _isz[0] || static_cast(mask.ysize()) != _isz[1] || static_cast(mask.zsize()) != _isz[2]) { throw EddyException("BiasFieldEstimatorImpl::SetRefScan: Size mismatch between new and previously set ref image"); } } PointersForSpMat ptrs = make_At_star_CSC(coords,mask); _rAt = MISCMATHS::SpMat(coords.N(),coords.N(),ptrs.irp.get(),ptrs.jcp.get(),ptrs.sp.get()); return; } EddyCatch /*! * Used to add a scan to _Aty and _AtA. * \throw EDDY::EddyException if there is a mismatch between input images */ void BiasFieldEstimatorImpl::AddScan(const NEWIMAGE::volume& predicted, //& observed, //& mask, //(predicted.xsize()) != _isz[0] || static_cast(predicted.ysize()) != _isz[1] || static_cast(predicted.zsize()) != _isz[2]) { throw EddyException("BiasFieldEstimatorImpl::AddScan: Size mismatch between predicted and previously set ref image"); } NEWMAT::ColumnVector v_predicted = predicted.vec(); NEWMAT::ColumnVector v_observed = observed.vec(); NEWMAT::ColumnVector v_mask = mask.vec(); _nima++; cnt++; PointersForSpMat ptrs = make_At_star_CSC(coords,mask); MISCMATHS::SpMat At(coords.N(),coords.N(),ptrs.irp.get(),ptrs.jcp.get(),ptrs.sp.get()); At -= _rAt; At.MultiplyColumns(NEWMAT::SP(v_predicted,v_mask)); if (!_Aty.Nrows()) _Aty = At * NEWMAT::SP((v_observed - v_predicted),v_mask); else _Aty += At * NEWMAT::SP((v_observed - v_predicted),v_mask); if (!_AtA.Nrows()) _AtA = At*At.t(); else _AtA += At*At.t(); } EddyCatch /*! * Fits a "direct" representation of the bias-field to the data and return it in newimage format * \return A "direct" representation of the field in newimage format * \throw EDDY::EddyException if object not ready to estimate field */ NEWIMAGE::volume BiasFieldEstimatorImpl::GetField(double lambda) const // StS = MISCMATHS::Sparse3DBendingEnergyHessian(_isz,_vxs,MISCMATHS::PERIODIC); // Solve for field NEWMAT::ColumnVector field = (_AtA + (lambda/static_cast(nvox())) * StS).SolveForx(_Aty,MISCMATHS::SYM_POSDEF,1.0e-6,1000); // Retreive field as NEWIMAGE volume NEWIMAGE::volume fima(static_cast(_isz[0]),static_cast(_isz[1]),static_cast(_isz[2])); fima.setxdim(_vxs[0]); fima.setydim(_vxs[1]); fima.setzdim(_vxs[2]); fima.insert_vec(field); return(fima); } EddyCatch /*! * Fits a spline basis-field of the bias-field to the data and return it in newimage format * \return A basis-field representation of the field in newimage format * \throw EDDY::EddyException if object not ready to estimate field */ NEWIMAGE::volume BiasFieldEstimatorImpl::GetField(double ksp, //voxels std::vector iksp(3); iksp[0] = static_cast((ksp / _vxs[0]) + 0.5); iksp[1] = static_cast((ksp / _vxs[1]) + 0.5); iksp[2] = static_cast((ksp / _vxs[2]) + 0.5); // Make splinefield BASISFIELD::splinefield spf(_isz,_vxs,iksp); _AtA.Save("sum_AtA.txt"); MISCMATHS::write_ascii_matrix(_Aty,"sum_Aty.txt"); // Calculate B^T(\Sum A^T A)B and B^T(\Sum A^T \hat{f}) from paper std::shared_ptr > B = spf.J(); MISCMATHS::SpMat BtAtAB = B->t() * _AtA * *B; BtAtAB += (lambda/static_cast(nvox())) * (*spf.BendEnergyHessAsSpMat()); NEWMAT::ColumnVector BtAtf = B->t() * _Aty; // Solve for spline coefficients of field NEWMAT::ColumnVector b = BtAtAB.SolveForx(BtAtf,MISCMATHS::SYM_POSDEF,1.0e-6,200); // Set coefficients in field spf.SetCoef(b); // Retrieve field as NEWIMAGE volume NEWIMAGE::volume field(static_cast(_isz[0]),static_cast(_isz[1]),static_cast(_isz[2])); field.setxdim(_vxs[0]); field.setydim(_vxs[1]); field.setzdim(_vxs[2]); spf.AsVolume(field); return(field); } EddyCatch MISCMATHS::SpMat BiasFieldEstimatorImpl::GetAtMatrix(const EDDY::ImageCoordinates& coords, const NEWIMAGE::volume& predicted, const NEWIMAGE::volume& mask) const EddyTry { PointersForSpMat ptrs = make_At_star_CSC(coords,mask); multiply_At_star_CSC_by_image(predicted,mask,ptrs.jcp.get(),ptrs.sp.get()); MISCMATHS::SpMat At(coords.N(),coords.N(),ptrs.irp.get(),ptrs.jcp.get(),ptrs.sp.get()); return(At); } EddyCatch MISCMATHS::SpMat BiasFieldEstimatorImpl::GetAtStarMatrix(const EDDY::ImageCoordinates& coords, const NEWIMAGE::volume& mask) const EddyTry { PointersForSpMat ptrs = make_At_star_CSC(coords,mask); MISCMATHS::SpMat At(coords.N(),coords.N(),ptrs.irp.get(),ptrs.jcp.get(),ptrs.sp.get()); return(At); } EddyCatch NEWIMAGE::volume BiasFieldEstimatorImpl::GetATimesField(const EDDY::ImageCoordinates& coords, const NEWIMAGE::volume& predicted, const NEWIMAGE::volume& mask, const NEWIMAGE::volume& field) const EddyTry { if (!_rAt.Nrows()) { // If no ref scan has been set yet throw EddyException("BiasFieldEstimatorImpl::GetATimesField: No ref scan set yet"); } PointersForSpMat ptrs = make_At_star_CSC(coords,mask); MISCMATHS::SpMat At(coords.N(),coords.N(),ptrs.irp.get(),ptrs.jcp.get(),ptrs.sp.get()); At -= _rAt; At.MultiplyColumns(NEWMAT::SP(predicted.vec(),mask.vec())); MISCMATHS::SpMat A = At.t(); NEWMAT::ColumnVector fvec = field.vec(); NEWMAT::ColumnVector Ab = A*fvec; NEWIMAGE::volume Ab_ima = predicted; Ab_ima.insert_vec(Ab); return(Ab_ima); } EddyCatch BiasFieldEstimator::BiasFieldEstimator() EddyTry { _pimpl = new BiasFieldEstimatorImpl(); } EddyCatch BiasFieldEstimator::~BiasFieldEstimator() { delete _pimpl; } void BiasFieldEstimator::SetRefScan(const NEWIMAGE::volume& mask, const EDDY::ImageCoordinates& coords) EddyTry { _pimpl->SetRefScan(mask,coords); } EddyCatch void BiasFieldEstimator::AddScan(const NEWIMAGE::volume& predicted, const NEWIMAGE::volume& observed, const NEWIMAGE::volume& mask, const EDDY::ImageCoordinates& coords) EddyTry { _pimpl->AddScan(predicted,observed,mask,coords); } EddyCatch NEWIMAGE::volume BiasFieldEstimator::GetField(double lambda) const EddyTry { return(_pimpl->GetField(lambda)); } EddyCatch NEWIMAGE::volume BiasFieldEstimator::GetField(double ksp, double lambda) const EddyTry { return(_pimpl->GetField(ksp,lambda)); } EddyCatch MISCMATHS::SpMat BiasFieldEstimator::GetAtMatrix(const EDDY::ImageCoordinates& coords, const NEWIMAGE::volume& predicted, const NEWIMAGE::volume& mask) const EddyTry { return(_pimpl->GetAtMatrix(coords,predicted,mask)); } EddyCatch MISCMATHS::SpMat BiasFieldEstimator::GetAtStarMatrix(const EDDY::ImageCoordinates& coords, const NEWIMAGE::volume& mask) const EddyTry { return(_pimpl->GetAtStarMatrix(coords,mask)); } EddyCatch NEWIMAGE::volume BiasFieldEstimator::GetATimesField(const EDDY::ImageCoordinates& coords, const NEWIMAGE::volume& predicted, const NEWIMAGE::volume& mask, const NEWIMAGE::volume& field) EddyTry { return(_pimpl->GetATimesField(coords,predicted,mask,field)); } EddyCatch void BiasFieldEstimator::Write(const std::string& basename) const EddyTry { _pimpl->Write(basename); } EddyCatch } // End namespace EDDY #endif // End #ifndef BiasFieldEstimatorImpl_h