Commit b9e18ed6 authored by wangkx1's avatar wangkx1
Browse files

init

parents
/*! \file KMatrix.cpp
\brief Contains definitions for classes implementing covariance matrices for DWI GPs.
\author Jesper Andersson
\version 1.0b, Oct., 2013.
*/
// Definitions of class implementing covariance matrices for DWI GPs
//
// KMatrix.cpp
//
// Jesper Andersson, FMRIB Image Analysis Group
//
// Copyright (C) 2013 University of Oxford
//
#include <cstdlib>
#include <string>
#include <vector>
#include <cmath>
#include "armawrap/newmat.h"
#include "newimage/newimageall.h"
#include "miscmaths/miscmaths.h"
#include "EddyHelperClasses.h"
#include "EddyUtils.h"
#include "KMatrix.h"
using namespace std;
using namespace EDDY;
/////////////////////////////////////////////////////////////////////
//
// Class MultiShellKMatrix
//
/////////////////////////////////////////////////////////////////////
/****************************************************************//**
*
* Constructs a MultiShellKMatrix object given b-vecs, shell information
* and values of hyperparameters.
* Note that MultiShellKMatrix is still a virtual class that cannot
* be instantiated. Despite that its constructor does a significant
* amount of work.
* \param dpars Vector of DiffPara objects giving b-value and b-vector
* for each scan.
* \param npps Number of hyper-parameters per shell.
*
********************************************************************/
MultiShellKMatrix::MultiShellKMatrix(const std::vector<DiffPara>& dpars,
bool dcsh) EddyTry
: _dpars(dpars), _K_ok(false), _iK_ok(false), _pv(dpars.size()), _pv_ok(dpars.size(),false), _dcsh(dcsh)
{
// Get information on grouping
if (!EddyUtils::GetGroups(dpars,_grps,_grpi,_grpb) && !_dcsh) throw EddyException("MultiShellKMatrix::MultiShellKMatrix: Data not shelled");
_ngrp = _grpb.size();
// Populate lower diagonal of matrix of angles between b-vecs
make_angle_mat();
} EddyCatch
NEWMAT::ColumnVector MultiShellKMatrix::iKy(const NEWMAT::ColumnVector& y) const EddyTry
{
if (y.Nrows() != _K.Nrows()) throw EddyException("MultiShellKMatrix::iKy:const: Invalid size of vector y");
if (!_K_ok) throw EddyException("MultiShellKMatrix::iKy:const: Attempting to use invalid (NPD) K-matrix");
if (!_iK_ok) throw EddyException("MultiShellKMatrix::iKy:const: Cannot use lazy evaluation for const object");
return(_iK*y);
} EddyCatch
NEWMAT::ColumnVector MultiShellKMatrix::iKy(const NEWMAT::ColumnVector& y) EddyTry
{
if (y.Nrows() != _K.Nrows()) throw EddyException("MultiShellKMatrix::iKy: Invalid size of vector y");
if (!_K_ok) throw EddyException("MultiShellKMatrix::iKy: Attempting to use invalid (NPD) K-matrix");
if (!_iK_ok) calculate_iK();
return(_iK*y);
} EddyCatch
NEWMAT::RowVector MultiShellKMatrix::PredVec(unsigned int i,
bool excl) const EddyTry
{
if (int(i)>(_K.Nrows()-1)) throw EddyException("MultiShellKMatrix::PredVec:const: Invalid index");
if (!_K_ok) throw EddyException("MultiShellKMatrix::PredVec:const: Attempting to use invalid (NPD) K-matrix");
if (!excl) {
if (!_iK_ok) throw EddyException("MultiShellKMatrix::PredVec:const: Cannot use lazy evaluation for const object");
NEWMAT::RowVector v = k_row(i,excl,_grpi,_ngrp,_thpar,_angle_mat);
return(v*_iK);
}
else {
if (!_pv_ok[i]) throw EddyException("MultiShellKMatrix::PredVec:const: Cannot use lazy evaluation for const object");
return(_pv[i]);
}
} EddyCatch
NEWMAT::RowVector MultiShellKMatrix::PredVec(unsigned int i,
bool excl) EddyTry
{
if (int(i)>(_K.Nrows()-1)) throw EddyException("MultiShellKMatrix::PredVec: Invalid index");
if (!_K_ok) throw EddyException("MultiShellKMatrix::PredVec: Attempting to use invalid (NPD) K-matrix");
if (!excl) {
if (!_iK_ok) calculate_iK();
NEWMAT::RowVector v = k_row(i,excl,_grpi,_ngrp,_thpar,_angle_mat);
return(v*_iK);
}
else {
if (!_pv_ok[i]) {
NEWMAT::RowVector v = k_row(i,excl,_grpi,_ngrp,_thpar,_angle_mat);
_pv[i] = v*calculate_iK_index(i);
_pv_ok[i] = true;
}
return(_pv[i]);
}
} EddyCatch
double MultiShellKMatrix::PredVar(unsigned int i,
bool excl) EddyTry
{
if (int(i)>(_K.Nrows()-1)) throw EddyException("MultiShellKMatrix::PredVar: Invalid index");
if (!_K_ok) throw EddyException("MultiShellKMatrix::PredVar: Attempting to use invalid (NPD) K-matrix");
if (!excl) {
if (!_iK_ok) calculate_iK();
NEWMAT::RowVector v = k_row(i,excl,_grpi,_ngrp,_thpar,_angle_mat);
double sv = sig_var(i,_grpi,_ngrp,_thpar);
return(sv - (v*_iK*v.t()).AsScalar());
}
else {
NEWMAT::RowVector v = k_row(i,excl,_grpi,_ngrp,_thpar,_angle_mat);
double sv = sig_var(i,_grpi,_ngrp,_thpar);
NEWMAT::SymmetricMatrix iKi = calculate_iK_index(i);
return(sv - (v*calculate_iK_index(i)*v.t()).AsScalar());
}
} EddyCatch
NEWMAT::SymmetricMatrix MultiShellKMatrix::iK() const EddyTry
{
if (!_K_ok) throw EddyException("MultiShellKMatrix::iK:const: Attempting to use invalid (NPD) K-matrix");
if (!_iK_ok) throw EddyException("MultiShellKKMatrix::iK:const: Cannot use lazy evaluation for const object");
return(_iK);
} EddyCatch
NEWMAT::SymmetricMatrix MultiShellKMatrix::iK() EddyTry
{
if (!_K_ok) throw EddyException("MultiShellKMatrix::iK: Attempting to use invalid (NPD) K-matrix");
if (!_iK_ok) calculate_iK();
return(_iK);
} EddyCatch
void MultiShellKMatrix::CalculateInvK() EddyTry
{
if (!_K_ok) throw EddyException("MultiShellKMatrix::CalculateInvK: Attempting to use invalid (NPD) K-matrix");
if (!_iK_ok) calculate_iK();
} EddyCatch
void MultiShellKMatrix::Reset() EddyTry
{
_dpars.clear(); _grpi.clear(); _grps.clear(); _hpar.clear(); _thpar.clear(); _pv.clear(); _pv_ok.clear();
_ngrp = 0;
_K_ok = _iK_ok = false;
_angle_mat.CleanUp(); _K.CleanUp(); _cK.CleanUp(); _iK.CleanUp();
} EddyCatch
void MultiShellKMatrix::SetDiffusionPar(const std::vector<DiffPara>& dpars) EddyTry
{
_dpars = dpars;
if (!EddyUtils::GetGroups(_dpars,_grps,_grpi,_grpb) && !_dcsh) throw EddyException("MultiShellKMatrix::SetDiffusionPar: Data not shelled");
_ngrp = _grpb.size();
// Populate (lower diagonal of) matrix of angles between b-vecs
make_angle_mat();
// Set arbitrary hyper-parameters (using functions supplied by derived class)
_hpar = get_arbitrary_hpar(_ngrp);
_thpar = exp_hpar(_ngrp,_hpar);
// Calculate K-matrix (using function supplied by derived class)
calculate_K_matrix(_grpi,_ngrp,_thpar,_angle_mat,_K);
validate_K_matrix();
_iK_ok = false;
_pv_ok.assign(_dpars.size(),false);
_pv.resize(_dpars.size());
} EddyCatch
void MultiShellKMatrix::SetHyperPar(const std::vector<double>& hpar) EddyTry
{
if (hpar.size() != n_par(_ngrp)) throw EddyException("MultiShellKMatrix::SetHyperPar: Invalid # of hyperparameters");
_hpar = hpar;
_thpar = exp_hpar(_ngrp,_hpar);
calculate_K_matrix(_grpi,_ngrp,_thpar,_angle_mat,_K);
validate_K_matrix();
_iK_ok = false;
_pv_ok.assign(_dpars.size(),false);
} EddyCatch
void MultiShellKMatrix::MulErrVarBy(double ff) EddyTry
{
if (ff < 1.0) throw EddyException("MultiShellKMatrix::MulErrVarBy: Fudge factor must be > 1.0");
else{
std::vector<double> hpar = GetHyperPar();
for (unsigned int j=0; j<NoOfGroups(); j++) {
for (unsigned int i=j; i<NoOfGroups(); i++) {
unsigned int pi = ij_to_parameter_index(i,j,NoOfGroups());
if (i==j) hpar[pi+2] += std::log(ff);
}
}
set_hpar(hpar);
set_thpar(exp_hpar(NoOfGroups(),GetHyperPar()));
}
} EddyCatch
double MultiShellKMatrix::LogDet() const EddyTry
{
if (!_K_ok) throw EddyException("MultiShellKMatrix::LogDet: Attempting to use invalid (NPD) K-matrix");
double ld = 0.0;
for (int i=0; i<_cK.Nrows(); i++) ld += log(_cK(i+1,i+1));
return(2.0*ld);
} EddyCatch
NEWMAT::SymmetricMatrix MultiShellKMatrix::GetDeriv(unsigned int di) const EddyTry
{
std::pair<unsigned int, unsigned int> ij = parameter_index_to_ij(di,_ngrp);
unsigned int bdi = ij_to_parameter_index(ij.first,ij.second,_ngrp);
NEWMAT::SymmetricMatrix dK(NoOfScans());
calculate_dK_matrix(_grpi,_ngrp,_thpar,_angle_mat,ij.first,ij.second,di-bdi,dK);
return(dK);
} EddyCatch
void MultiShellKMatrix::GetAllDerivs(std::vector<NEWMAT::SymmetricMatrix>& derivs) const EddyTry
{
for (unsigned int di=0; di<NoOfHyperPar(); di++) {
if (derivs[di].Nrows() != int(NoOfScans())) derivs[di].ReSize(NoOfScans());
std::pair<unsigned int, unsigned int> ij = parameter_index_to_ij(di,_ngrp);
unsigned int bdi = ij_to_parameter_index(ij.first,ij.second,_ngrp);
calculate_dK_matrix(_grpi,_ngrp,_thpar,_angle_mat,ij.first,ij.second,di-bdi,derivs[di]);
}
return;
} EddyCatch
void MultiShellKMatrix::Print() const EddyTry
{
cout << "_ngrp = " << _ngrp << endl;
cout << "_K_ok = " << _K_ok << endl;
cout << "_iK_ok = " << _iK_ok << endl;
} EddyCatch
void MultiShellKMatrix::Write(const std::string& basefname) const EddyTry
{
std::string fname = basefname + ".angle_mat.txt";
MISCMATHS::write_ascii_matrix(_angle_mat,fname);
fname = basefname + ".K.txt";
MISCMATHS::write_ascii_matrix(_K,fname);
fname = basefname + ".cK.txt";
MISCMATHS::write_ascii_matrix(_cK,fname);
fname = basefname + ".iK.txt";
MISCMATHS::write_ascii_matrix(_iK,fname);
fname = basefname + ".pv.txt";
NEWMAT::Matrix pvs = make_pred_vec_matrix();
MISCMATHS::write_ascii_matrix(pvs,fname);
} EddyCatch
void MultiShellKMatrix::validate_K_matrix() EddyTry
{
try {
_cK = Cholesky(_K);
_K_ok = true;
}
catch (NEWMAT::NPDException& e) {
_K_ok = false;
}
return;
} EddyCatch
void MultiShellKMatrix::calculate_iK() EddyTry
{
NEWMAT::LowerTriangularMatrix iL = _cK.i();
_iK << iL.t()*iL; // N.B. _ik = iL.t()*iL; does NOT work
_iK_ok = true;
return;
} EddyCatch
NEWMAT::SymmetricMatrix MultiShellKMatrix::calculate_iK_index(unsigned int i) const EddyTry
{
int n = _K.Nrows(); // # of rows AND # of columns
NEWMAT::Matrix top = (_K.SubMatrix(1,i,1,i) | _K.SubMatrix(1,i,i+2,n));
NEWMAT::Matrix bottom = (_K.SubMatrix(i+2,n,1,i) | _K.SubMatrix(i+2,n,i+2,n));
top &= bottom;
NEWMAT::SymmetricMatrix K_indx;
K_indx << top;
NEWMAT::SymmetricMatrix iK_indx = K_indx.i();
return(iK_indx);
} EddyCatch
NEWMAT::Matrix MultiShellKMatrix::make_pred_vec_matrix(bool excl) const EddyTry
{
NEWMAT::Matrix M(_K.Nrows(),_K.Ncols());
for (int i=0; i<M.Nrows(); i++) M.Row(i+1) = PredVec(i,excl);
return(M);
} EddyCatch
/*
unsigned int MultiShellKMatrix::ij_to_parameter_index(unsigned int i,
unsigned int j,
unsigned int n) const
{
if (n==1) { // The one--three shell cases hardcoded for speed
return(0);
}
else if (n==2) {
if (!j) return((_npps)*i);
else return(_npps+_npps-1);
}
else if (n==3) {
if (!j) { if (i) return(i*_npps-i+1); else return(0); }
else if (j==1) return(2*_npps+i*_npps-2);
else return(5*_npps-3); // for the i==j==2 case
}
else { // Not hardcoded. Would be nice if I could find a faster algorithm.
unsigned int rval = 0;
for (unsigned int ii=0; ii<j; ii++) { // Sum up index of preceeding columns
rval += _npps + (_npps-1)*(n-1-ii);
}
if (i>j) rval += _npps + (_npps-1)*(i-j-1);
return(rval);
}
return(0); // To stop compiler complaining.
}
*/
std::pair<unsigned int, unsigned int> MultiShellKMatrix::parameter_index_to_ij(unsigned int pi,
unsigned int ngrp) const EddyTry
{
std::pair<unsigned int, unsigned int> ij;
for (unsigned int j=0; j<ngrp; j++) {
for (unsigned int i=j; i<ngrp; i++) {
// ij_to_parameter_index will be defined in a derived class
if (ij_to_parameter_index(i,j,ngrp) > pi) { // Will alawys fail first time
break;
}
ij.first = i; ij.second = j;
}
}
return(ij);
} EddyCatch
void MultiShellKMatrix::make_angle_mat() EddyTry
{
_angle_mat.ReSize(_dpars.size());
for (unsigned int j=0; j<_dpars.size(); j++) {
for (unsigned int i=j; i<_dpars.size(); i++) {
_angle_mat(i+1,j+1) = std::acos(std::min(1.0,std::abs(NEWMAT::DotProduct(_dpars[i].bVec(),_dpars[j].bVec()))));
}
}
} EddyCatch
double MultiShellKMatrix::mean(const NEWMAT::ColumnVector& data,
const std::vector<unsigned int>& indx) const EddyTry
{
double m = 0.0;
for (unsigned int i=0; i<indx.size(); i++) m += data(indx[i]+1);
return(m / indx.size());
} EddyCatch
double MultiShellKMatrix::variance(const NEWMAT::ColumnVector& data,
const std::vector<unsigned int>& indx) const EddyTry
{
double m = mean(data,indx);
double v = 0.0;
for (unsigned int i=0; i<indx.size(); i++) v += sqr(data(indx[i]+1) - m);
return(v / (indx.size() - 1));
} EddyCatch
bool MultiShellKMatrix::valid_hpars(const std::vector<double>& hpar) const EddyTry
{
std::vector<double> thpar = exp_hpar(NoOfGroups(),hpar);
NEWMAT::SymmetricMatrix K(NoOfScans());
calculate_K_matrix(grpi(),NoOfGroups(),thpar,angle_mat(),K);
try { NEWMAT::LowerTriangularMatrix cK = Cholesky(K); }
catch (NEWMAT::NPDException& e) { return(false); }
return(true);
} EddyCatch
NewSphericalKMatrix::NewSphericalKMatrix(const std::vector<DiffPara>& dpars,
bool dcsh) EddyTry
: MultiShellKMatrix(dpars,dcsh)
{
// Set (arbitrary) hyper-parameters that will at least yield a valid K (Gram) matrix
set_hpar(get_arbitrary_hpar(NoOfGroups()));
// Transform variance related hyperparameters
set_thpar(exp_hpar(NoOfGroups(),GetHyperPar()));
// Calculate K matrix
NEWMAT::SymmetricMatrix& K = give_me_a_K(); // Read/Write reference to _K in parent class.
if (K.Nrows() != int(NoOfScans())) K.ReSize(NoOfScans());
calculate_K_matrix(grpi(),NoOfGroups(),thpar(),angle_mat(),K);
// Make sure it is positive definite
validate_K_matrix();
} EddyCatch
std::vector<double> NewSphericalKMatrix::GetHyperParGuess(const std::vector<NEWMAT::ColumnVector>& data) const EddyTry
{
// Calculate group-wise variances (across directions) averaged over voxels
std::vector<double> vars(NoOfGroups(),0.0);
double var = 0.0;
for (unsigned int i=0; i<NoOfGroups(); i++) {
for (unsigned int j=0; j<data.size(); j++) {
vars[i] += variance(data[j],grps()[i]);
}
vars[i] /= data.size();
var += vars[i];
}
var /= NoOfGroups();
// Make (semi-educated) guesses for hyper parameters based on findings in GP paper
std::vector<double> hpar(n_par(NoOfGroups()));
hpar[0] = 0.9*std::log(var/3.0);
hpar[1] = 0.45;
if (NoOfGroups() == 1) hpar[2] = std::log(var/3.0);
else {
hpar[2] = 0.0;
double delta = 0.2 / (NoOfGroups()-1);
for (unsigned int i=0; i<NoOfGroups(); i++) hpar[3+i] = (1.1 - i*delta) * std::log(var/3.0);
}
// Make sure they result in a valid K matrix
if (!valid_hpars(hpar)) throw EddyException("NewSphericalKMatrix::GetHyperParGuess: Unable to find valid hyperparameters");
return(hpar);
} EddyCatch
void NewSphericalKMatrix::SetHyperPar(const std::vector<double>& hpar) EddyTry
{
MultiShellKMatrix::SetHyperPar(hpar);
// if (std::exp(hpar[1]) > 3.141592653589793 || (NoOfGroups() > 1 && std::abs(hpar[2]) > 1.0)) set_K_matrix_invalid();
return;
} EddyCatch
void NewSphericalKMatrix::MulErrVarBy(double ff) EddyTry
{
std::vector<double> hpar = GetHyperPar();
double *ev = NULL;
ev = (NoOfGroups() > 1) ? &(hpar[3]) : &(hpar[2]);
for (unsigned int i=0; i<NoOfGroups(); i++) ev[i] += std::log(ff);
set_hpar(hpar);
set_thpar(exp_hpar(NoOfGroups(),GetHyperPar()));
} EddyCatch
void NewSphericalKMatrix::calculate_K_matrix(const std::vector<unsigned int>& grpi,
unsigned int ngrp,
const std::vector<double>& thpar,
const NEWMAT::SymmetricMatrix& angle_mat,
NEWMAT::SymmetricMatrix& K) const EddyTry
{
if (K.Nrows() != angle_mat.Nrows()) K.ReSize(angle_mat.Nrows());
// First pass for angular covariance
double sm = thpar[0]; double a = thpar[1];
for (int j=0; j<K.Ncols(); j++) {
for (int i=j; i<K.Nrows(); i++) {
double th = angle_mat(i+1,j+1);
if (a>th) K(i+1,j+1) = sm * (1.0 - 1.5*th/a + 0.5*(th*th*th)/(a*a*a));
else K(i+1,j+1) = 0.0;
}
}
// Second pass for b-value covariance
if (ngrp > 1) {
std::vector<double> log_grpb = grpb(); for (unsigned int i=0; i<grpb().size(); i++) log_grpb[i] = std::log(grpb()[i]);
double l = thpar[2];
for (int j=0; j<K.Ncols(); j++) {
for (int i=j+1; i<K.Nrows(); i++) {
double bvdiff = log_grpb[grpi[i]] - log_grpb[grpi[j]];
if (bvdiff) K(i+1,j+1) *= std::exp(-(bvdiff*bvdiff) / (2*l*l));
}
}
}
// Third pass for error variances
const double *ev = NULL;
ev = (ngrp > 1) ? &(thpar[3]) : &(thpar[2]);
for (int i=0; i<K.Ncols(); i++) {
K(i+1,i+1) += ev[grpi[i]];
}
return;
} EddyCatch
void NewSphericalKMatrix::calculate_dK_matrix(const std::vector<unsigned int>& grpi,
unsigned int ngrp,
const std::vector<double>& thpar,
const NEWMAT::SymmetricMatrix& angle_mat,
unsigned int gi,
unsigned int gj,
unsigned int off,
NEWMAT::SymmetricMatrix& dK) const EddyTry
{
throw EddyException("NewSphericalKMatrix::calculate_dK_matrix: NYI");
return;
} EddyCatch
NEWMAT::RowVector NewSphericalKMatrix::k_row(unsigned int indx,
bool excl,
const std::vector<unsigned int>& grpi,
unsigned int ngrp,
const std::vector<double>& thpar,
const NEWMAT::SymmetricMatrix& angle_mat) const EddyTry
{
NEWMAT::RowVector kr(angle_mat.Ncols());
// First pass for angular covariance
double sm = thpar[0]; double a = thpar[1];
for (int j=0; j<angle_mat.Ncols(); j++) {
double th = angle_mat(indx+1,j+1);
if (a>th) kr(j+1) = sm * (1.0 - 1.5*th/a + 0.5*(th*th*th)/(a*a*a));
else kr(j+1) = 0.0;
}
// Second pass for b-value covariance
if (ngrp > 1) {
std::vector<double> log_grpb = grpb(); for (unsigned int i=0; i<grpb().size(); i++) log_grpb[i] = std::log(grpb()[i]);
double l = thpar[2]; double log_b = log_grpb[grpi[indx]];
for (int j=0; j<angle_mat.Ncols(); j++) {
double bvdiff = log_grpb[grpi[j]] - log_b;
if (bvdiff) kr(j+1) *= std::exp(-(bvdiff*bvdiff) / (2*l*l));
}
}
if (excl) kr = kr.Columns(1,indx) | kr.Columns(indx+2,angle_mat.Ncols());
return(kr);
} EddyCatch
std::vector<double> NewSphericalKMatrix::get_arbitrary_hpar(unsigned int ngrp) const EddyTry
{
std::vector<double> hpar(n_par(ngrp));
if (ngrp == 1) { hpar[0] = 1.0; hpar[1] = 0.0; hpar[2] = 1.0; }
else {
hpar[0] = 1.0; hpar[1] = 0.0; hpar[2] = 0.0;
for (unsigned int i=0; i<ngrp; i++) hpar[3+i] = 1.0;
}
return(hpar);
} EddyCatch
SphericalKMatrix::SphericalKMatrix(const std::vector<DiffPara>& dpars,
bool dcsh) EddyTry
: MultiShellKMatrix(dpars,dcsh)
{
// Set (arbitrary) hyper-parameters that will at least yield a valid K (Gram) matrix
set_hpar(get_arbitrary_hpar(NoOfGroups()));
// Transform variance related hyperparameters
set_thpar(exp_hpar(NoOfGroups(),GetHyperPar()));
// Calculate K matrix
NEWMAT::SymmetricMatrix& K = give_me_a_K(); // Read/Write reference to _K in parent class.
if (K.Nrows() != int(NoOfScans())) K.ReSize(NoOfScans());
calculate_K_matrix(grpi(),NoOfGroups(),thpar(),angle_mat(),K);
// Make sure it is positive definite
validate_K_matrix();
} EddyCatch
std::vector<double> SphericalKMatrix::GetHyperParGuess(const std::vector<NEWMAT::ColumnVector>& data) const EddyTry
{
// Calculate group-wise variances (across directions) averaged over voxels
std::vector<double> vars(NoOfGroups(),0.0);
for (unsigned int i=0; i<NoOfGroups(); i++) {
for (unsigned int j=0; j<data.size(); j++) {
vars[i] += variance(data[j],grps()[i]);
}
vars[i] /= data.size();
}
// Make (semi-educated) guesses for hyper parameters based on findings in GP paper
std::vector<double> hpar(n_par(NoOfGroups()));
double sn_wgt = 0.25; // Determines the noise-variance
unsigned int cntr = 0; // Break-out counter to see how many adjustments has been made
for (cntr=0; cntr<100; cntr++) { // Gradually adjust hyp-pars until they yield a valid K-matrix
for (unsigned int j=0; j<NoOfGroups(); j++) {
for (unsigned int i=j; i<NoOfGroups(); i++) {
unsigned int pi = ij_to_parameter_index(i,j,NoOfGroups());
if (i==j) {
hpar[pi] = std::log(vars[i] / 2.7);
hpar[pi+1] = 1.5;
hpar[pi+2] = std::log(sn_wgt * vars[i]);
}
else {
hpar[pi] = std::log(0.8 * std::min(vars[i],vars[j]) / 2.7);
hpar[pi+1] = 1.5;
}
}
}
if (!valid_hpars(hpar)) sn_wgt *= 1.1;
else break;
}
if (cntr==100) { // If we just can't find valid hyperparamaters
throw EddyException("SphericalKMatrix::GetHyperParGuess: Unable to find valid hyperparameters");
}
return(hpar);
} EddyCatch
unsigned int SphericalKMatrix::ij_to_parameter_index(unsigned int i,
unsigned int j,
unsigned int n) const EddyTry
{
if (n==1) { // The one--three shell cases hardcoded for speed
return(0);
}
else if (n==2) {
if (!j) return(3*i);
else return(5);
}
else if (n==3) {
if (!j) { if (i) return(2*i+1); else return(0); }
else if (j==1) return(4+3*i);
else return(12); // for the i==j==2 case
}
else { // Not hardcoded. Would be nice if I could find a faster algorithm.
unsigned int rval = 0;
for (unsigned int ii=0; ii<j; ii++) { // Sum up index of preceeding columns
rval += 1 + 2*(n-ii);
}
if (i>j) rval += 1 + 2*(i-j);
return(rval);
}
return(0); // To stop compiler complaining.
} EddyCatch
void SphericalKMatrix::calculate_K_matrix(const std::vector<unsigned int>& grpi,
unsigned int ngrp,
const std::vector<double>& thpar,
const NEWMAT::SymmetricMatrix& angle_mat,
NEWMAT::SymmetricMatrix& K) const EddyTry
{
if (K.Nrows() != angle_mat.Nrows()) K.ReSize(angle_mat.Nrows());
for (int j=0; j<K.Ncols(); j++) {
for (int i=j; i<K.Nrows(); i++) {
unsigned int pindx = ij_to_parameter_index(grpi[i],grpi[j],ngrp);
double sm = thpar[pindx]; double a = thpar[pindx+1]; double th = angle_mat(i+1,j+1);
if (a>th) K(i+1,j+1) = sm * (1.0 - 1.5*th/a + 0.5*(th*th*th)/(a*a*a));
else K(i+1,j+1) = 0.0;
if (i==j) K(i+1,j+1) += thpar[pindx+2];
}
}
return;
} EddyCatch
void SphericalKMatrix::calculate_dK_matrix(const std::vector<unsigned int>& grpi,
unsigned int ngrp,
const std::vector<double>& thpar,
const NEWMAT::SymmetricMatrix& angle_mat,
unsigned int gi,
unsigned int gj,
unsigned int off,
NEWMAT::SymmetricMatrix& dK) const EddyTry
{
if (dK.Nrows() != angle_mat.Nrows()) dK.ReSize(angle_mat.Nrows());
unsigned int pindx = ij_to_parameter_index(gi,gj,ngrp);
double a = thpar[pindx+1]; double a2 = a*a; double a3 = a*a2; double a4 = a*a3;
dK = 0;
for (int j=0; j<dK.Ncols(); j++) {
for (int i=j; i<dK.Nrows(); i++) {
if (grpi[i] == gi && grpi[j] == gj) { // If this entry pertains to the parameter we want derivative wrt
double sm = thpar[pindx]; double th = angle_mat(i+1,j+1);
if (off == 0) { // If variable denoted \sigma_m in paper
if (a>th) dK(i+1,j+1) = sm * (1.0 - 1.5*th/a + 0.5*(th*th*th)/a3);
}
else if (off == 1) { // If variable denoted a in paper
if (a>th) dK(i+1,j+1) = sm * (1.5*th/a2 - 1.5*(th*th*th)/a4);
}
else if (off == 2 && i==j) { // If variable denoted \sigma_n in paper
dK(i+1,j+1) = thpar[pindx+2];
}
}
}
}
return;
} EddyCatch
NEWMAT::RowVector SphericalKMatrix::k_row(unsigned int indx,
bool excl,
const std::vector<unsigned int>& grpi,
unsigned int ngrp,
const std::vector<double>& thpar,
const NEWMAT::SymmetricMatrix& angle_mat) const EddyTry
{
NEWMAT::RowVector kr(angle_mat.Ncols());
for (int j=0; j<angle_mat.Ncols(); j++) {
unsigned int pindx = ij_to_parameter_index(grpi[indx],grpi[j],ngrp);
double sm = thpar[pindx]; double a = thpar[pindx+1]; double th = angle_mat(indx+1,j+1);
if (a>th) kr(j+1) = sm * (1.0 - 1.5*th/a + 0.5*(th*th*th)/(a*a*a));
else kr(j+1) = 0.0;
}
if (excl) kr = kr.Columns(1,indx) | kr.Columns(indx+2,angle_mat.Ncols());
return(kr);
} EddyCatch
std::vector<double> SphericalKMatrix::exp_hpar(unsigned int ngrp,
const std::vector<double>& hpar) const EddyTry
{
std::vector<double> thpar = hpar;
for (unsigned int j=0; j<ngrp; j++) {
for (unsigned int i=j; i<ngrp; i++) {
unsigned int pi = ij_to_parameter_index(i,j,ngrp);
thpar[pi] = exp(thpar[pi]);
if (i==j) thpar[pi+2] = exp(thpar[pi+2]);
}
}
return(thpar);
} EddyCatch
std::vector<double> SphericalKMatrix::get_arbitrary_hpar(unsigned int ngrp) const EddyTry
{
std::vector<double> hpar(n_par(ngrp));
for (unsigned int j=0; j<ngrp; j++) {
for (unsigned int i=j; i<ngrp; i++) {
unsigned int pi = ij_to_parameter_index(i,j,ngrp);
if (i==j) {
hpar[pi] = 1.0;
hpar[pi+1] = 1.5;
hpar[pi+2] = 1.0;
}
else {
hpar[pi] = 0.5;
hpar[pi+1] = 1.5;
}
}
}
return(hpar);
} EddyCatch
ExponentialKMatrix::ExponentialKMatrix(const std::vector<DiffPara>& dpars,
bool dcsh) EddyTry
: MultiShellKMatrix(dpars,dcsh)
{
// Set (arbitrary) hyper-parameters that will at least yield a valid K (Gram) matrix
set_hpar(get_arbitrary_hpar(NoOfGroups()));
// Transform variance related hyperparameters
set_thpar(exp_hpar(NoOfGroups(),GetHyperPar()));
// Calculate K matrix
NEWMAT::SymmetricMatrix& K = give_me_a_K(); // Read/Write reference to _K in parent class.
if (K.Nrows() != int(NoOfScans())) K.ReSize(NoOfScans());
calculate_K_matrix(grpi(),NoOfGroups(),thpar(),angle_mat(),K);
// Make sure it is positive definite
validate_K_matrix();
} EddyCatch
std::vector<double> ExponentialKMatrix::GetHyperParGuess(const std::vector<NEWMAT::ColumnVector>& data) const EddyTry
{
// Calculate group-wise variances (across directions) averaged over voxels
std::vector<double> vars(NoOfGroups(),0.0);
for (unsigned int i=0; i<NoOfGroups(); i++) {
for (unsigned int j=0; j<data.size(); j++) {
vars[i] += variance(data[j],grps()[i]);
}
vars[i] /= grps()[i].size();
}
// Make (semi-educated) guesses for hyper parameters based on findings in GP paper
std::vector<double> hpar(n_par(NoOfGroups()));
for (unsigned int j=0; j<NoOfGroups(); j++) {
for (unsigned int i=j; i<NoOfGroups(); i++) {
unsigned int pi = ij_to_parameter_index(i,j,NoOfGroups());
if (i==j) {
hpar[pi] = std::log(vars[i] / 2.0);
hpar[pi+1] = 1.2;
hpar[pi+2] = std::log(vars[i] / 4.0);
}
else {
hpar[pi] = std::log(0.8 * std::min(vars[i],vars[j]) / 2.0);
hpar[pi+1] = 1.2;
}
}
}
return(hpar);
} EddyCatch
unsigned int ExponentialKMatrix::ij_to_parameter_index(unsigned int i,
unsigned int j,
unsigned int n) const EddyTry
{
if (n==1) { // The one--three shell cases hardcoded for speed
return(0);
}
else if (n==2) {
if (!j) return(3*i);
else return(5);
}
else if (n==3) {
if (!j) { if (i) return(2*i+1); else return(0); }
else if (j==1) return(4+3*i);
else return(12); // for the i==j==2 case
}
else { // Not hardcoded. Would be nice if I could find a faster algorithm.
unsigned int rval = 0;
for (unsigned int ii=0; ii<j; ii++) { // Sum up index of preceeding columns
rval += 1 + 2*(n-ii);
}
if (i>j) rval += 1 + 2*(i-j);
return(rval);
}
return(0); // To stop compiler complaining.
} EddyCatch
void ExponentialKMatrix::calculate_K_matrix(const std::vector<unsigned int>& grpi,
unsigned int ngrp,
const std::vector<double>& thpar,
const NEWMAT::SymmetricMatrix& angle_mat,
NEWMAT::SymmetricMatrix& K) const EddyTry
{
if (K.Nrows() != angle_mat.Nrows()) K.ReSize(angle_mat.Nrows());
for (int j=0; j<K.Ncols(); j++) {
for (int i=j; i<K.Nrows(); i++) {
unsigned int pindx = ij_to_parameter_index(grpi[i],grpi[j],ngrp);
K(i+1,j+1) = thpar[pindx] * exp(-angle_mat(i+1,j+1)/thpar[pindx+1]);
if (i==j) K(i+1,j+1) += thpar[pindx+2];
}
}
return;
} EddyCatch
void ExponentialKMatrix::calculate_dK_matrix(const std::vector<unsigned int>& grpi,
unsigned int ngrp,
const std::vector<double>& thpar,
const NEWMAT::SymmetricMatrix& angle_mat,
unsigned int gi,
unsigned int gj,
unsigned int off,
NEWMAT::SymmetricMatrix& dK) const EddyTry
{
unsigned int pindx = ij_to_parameter_index(gi,gj,ngrp);
for (int j=0; j<dK.Ncols(); j++) {
for (int i=j; i<dK.Nrows(); i++) {
if (grpi[i] == gi && grpi[j] == gj) { // If this entry pertains to the parameter we want derivative wrt
if (off == 0) { // If variable denoted \sigma_m in paper
dK(i+1,j+1) = thpar[pindx] * exp(-angle_mat(i+1,j+1)/thpar[pindx+1]);
}
else if (off == 1) { // If variable denoted a in paper
double a = thpar[pindx+1]; double th = angle_mat(i+1,j+1);
dK(i+1,j+1) = th * thpar[pindx] * exp(-th/a) / (a*a);
}
else if (off == 2) { // If variable denoted \sigma_n in paper
if (i==j) dK(i+1,j+1) = thpar[pindx+2];
else dK(i+1,j+1) = 0.0;
}
}
}
}
return;
} EddyCatch
NEWMAT::RowVector ExponentialKMatrix::k_row(unsigned int indx,
bool excl,
const std::vector<unsigned int>& grpi,
unsigned int ngrp,
const std::vector<double>& thpar,
const NEWMAT::SymmetricMatrix& angle_mat) const EddyTry
{
NEWMAT::RowVector kr(angle_mat.Ncols());
for (int j=0; j<angle_mat.Ncols(); j++) {
unsigned int pindx = ij_to_parameter_index(grpi[indx],grpi[j],ngrp);
kr(j+1) = thpar[pindx] * exp(-angle_mat(indx+1,j+1)/thpar[pindx+1]);
}
if (excl) kr = kr.Columns(1,indx) | kr.Columns(indx+2,angle_mat.Ncols());
return(kr);
} EddyCatch
std::vector<double> ExponentialKMatrix::exp_hpar(unsigned int ngrp,
const std::vector<double>& hpar) const EddyTry
{
std::vector<double> thpar = hpar;
for (unsigned int j=0; j<ngrp; j++) {
for (unsigned int i=j; i<ngrp; i++) {
unsigned int pi = ij_to_parameter_index(i,j,ngrp);
thpar[pi] = exp(thpar[pi]);
if (i==j) thpar[pi+2] = exp(thpar[pi+2]);
}
}
return(thpar);
} EddyCatch
std::vector<double> ExponentialKMatrix::get_arbitrary_hpar(unsigned int ngrp) const EddyTry
{
std::vector<double> hpar(n_par(ngrp));
for (unsigned int j=0; j<ngrp; j++) {
for (unsigned int i=j; i<ngrp; i++) {
unsigned int pi = ij_to_parameter_index(i,j,ngrp);
if (i==j) {
hpar[pi] = 1.0;
hpar[pi+1] = 1.2;
hpar[pi+2] = 1.0;
}
else {
hpar[pi] = 0.5;
hpar[pi+1] = 1.2;
}
}
}
return(hpar);
} EddyCatch
NEWMAT::ColumnVector SqrExpKMatrix::iKy(const NEWMAT::ColumnVector& y) const EddyTry
{
if (y.Nrows() != _K.n_rows) throw EddyException("SqrExpKMatrix::iKy:const: Invalid size of vector y");
if (!_K_ok) throw EddyException("SqrExpKMatrix::iKy:const: Attempting to use invalid (NPD) K-matrix");
if (!_iK_ok) throw EddyException("SqrExpKMatrix::iKy:const: Cannot use lazy evaluation for const object");
return(_iK*y);
} EddyCatch
NEWMAT::ColumnVector SqrExpKMatrix::iKy(const NEWMAT::ColumnVector& y) EddyTry
{
if (y.Nrows() != _K.n_rows) throw EddyException("SqrExpKMatrix::iKy: Invalid size of vector y");
if (!_K_ok) throw EddyException("SqrExpKMatrix::iKy: Attempting to use invalid (NPD) K-matrix");
if (!_iK_ok) calculate_iK();
return(_iK*y);
} EddyCatch
NEWMAT::RowVector SqrExpKMatrix::PredVec(unsigned int i,
bool excl) const EddyTry
{
if (int(i)>(_K.n_rows-1)) throw EddyException("SqrExpKMatrix::PredVec:const: Invalid index");
if (!_K_ok) throw EddyException("SqrExpKMatrix::PredVec:const: Attempting to use invalid (NPD) K-matrix");
if (!excl) {
if (!_iK_ok) throw EddyException("SqrExpKMatrix::PredVec:const: Cannot use lazy evaluation for const object");
arma::rowvec v = k_row(i,excl,_hpar,_dist_mat);
return(v*_iK);
}
else {
if (!_pv_ok[i]) throw EddyException("SqrExpKMatrix::PredVec:const: Cannot use lazy evaluation for const object");
return(_pv[i]);
}
} EddyCatch
NEWMAT::RowVector SqrExpKMatrix::PredVec(unsigned int i,
bool excl) EddyTry
{
if (int(i)>(_K.n_rows-1)) throw EddyException("SqrExpKMatrix::PredVec: Invalid index");
if (!_K_ok) throw EddyException("SqrExpKMatrix::PredVec: Attempting to use invalid (NPD) K-matrix");
if (!excl) {
if (!_iK_ok) calculate_iK();
arma::rowvec v = k_row(i,excl,_hpar,_dist_mat);
return(v*_iK);
}
else {
if (!_pv_ok[i]) {
arma::rowvec v = k_row(i,excl,_hpar,_dist_mat);
_pv[i] = v*calculate_iK_index(i);
_pv_ok[i] = true;
}
return(_pv[i]);
}
} EddyCatch
double SqrExpKMatrix::PredVar(unsigned int i,
bool excl) EddyTry
{
if (int(i)>(_K.n_rows-1)) throw EddyException("SqrExpKMatrix::PredVar: Invalid index");
if (!_K_ok) throw EddyException("SqrExpKMatrix::PredVar: Attempting to use invalid (NPD) K-matrix");
if (!excl) {
if (!_iK_ok) calculate_iK();
arma::rowvec v = k_row(i,excl,_hpar,_dist_mat);
double sv = this->sqr(_hpar[0]);
return(sv - arma::as_scalar(v*_iK*v.t()));
}
else {
arma::rowvec v = k_row(i,excl,_hpar,_dist_mat);
double sv = this->sqr(_hpar[0]);
arma::mat iKi = calculate_iK_index(i);
return(sv - arma::as_scalar(v*calculate_iK_index(i)*v.t()));
}
} EddyCatch
NEWMAT::SymmetricMatrix SqrExpKMatrix::iK() const EddyTry
{
if (!_K_ok) throw EddyException("SqrExpKMatrix::iK:const: Attempting to use invalid (NPD) K-matrix");
if (!_iK_ok) throw EddyException("SqrExpKMatrix::iK:const: Cannot use lazy evaluation for const object");
return(_iK);
} EddyCatch
NEWMAT::SymmetricMatrix SqrExpKMatrix::iK() EddyTry
{
if (!_K_ok) throw EddyException("SqrExpKMatrix::iK: Attempting to use invalid (NPD) K-matrix");
if (!_iK_ok) calculate_iK();
return(_iK);
} EddyCatch
void SqrExpKMatrix::Reset() EddyTry
{
_hpar.clear(); _pv.clear(); _pv_ok.clear();
_K_ok = _iK_ok = false;
_dist_mat.reset(); _K.reset(); _cK.reset(); _iK.reset();
} EddyCatch
void SqrExpKMatrix::SetHyperPar(const std::vector<double>& hpar) EddyTry
{
if (hpar.size() != 3) throw EddyException("SqrExpKMatrix::SetHyperPar: Invalid # of hyperparameters");
_hpar = hpar;
calculate_K_matrix(_hpar,_dist_mat,_K);
validate_K_matrix();
_iK_ok = false;
_pv_ok.assign(_pv_ok.size(),false);
} EddyCatch
void SqrExpKMatrix::MulErrVarBy(double ff) EddyTry
{
if (ff < 1.0) throw EddyException("SqrExpKMatrix::MulErrVarBy: Fudge factor must be > 1.0");
else _hpar[2] *= std::sqrt(ff);
} EddyCatch
void SqrExpKMatrix::CalculateInvK() EddyTry
{
if (!_K_ok) throw EddyException("SqrExpKMatrix::CalculateInvK: Attempting to use invalid (NPD) K-matrix");
if (!_iK_ok) calculate_iK();
} EddyCatch
// This guesstimate is based on experiments I did on task data from HCP
// and UK Biobank. The length parameter seems to be slightly longer for
// UK biobank (~7sec) compared to HCP (~5sec), but reasonably consistent.
// I also found that if the starting estimates for sigma_s (standard
// deviation of signal) and sigma_n (standard deviation of noise) were
// "large" the estimation converged both for LOO and MML. I will therefore
// use the estimate of standard deviation from the data as starting estimate
// for both.
std::vector<double> SqrExpKMatrix::GetHyperParGuess(const std::vector<NEWMAT::ColumnVector>& data) const EddyTry
{
// Calculate variance (over time) averaged over voxels
double sd = 0.0;
for (unsigned int j=0; j<data.size(); j++) {
arma::colvec tmp = data[j];
sd += arma::stddev(tmp);
}
sd /= static_cast<double>(data.size());
// Set hyper-parameter guesses
std::vector<double> hpar(3);
hpar[0] = sd;
hpar[1] = 6.0;
hpar[2] = sd;
// Make sure they result in a valid K matrix
if (!this->valid_hpars(hpar)) throw EddyException("SqrExpKMatrix::GetHyperParGuess: Unable to find valid hyperparameters");
return(hpar);
} EddyCatch
std::vector<double> SqrExpKMatrix::GetHyperPar() const EddyTry
{
std::vector<double> rval = _hpar;
for (double& par : rval) par = std::fabs(par);
return(rval);
} EddyCatch
double SqrExpKMatrix::LogDet() const EddyTry
{
if (!_K_ok) throw EddyException("SqrExpKMatrix::LogDet: Attempting to use invalid (NPD) K-matrix");
double ld = 0.0;
for (unsigned int i=0; i<_cK.n_rows; i++) ld += std::log(_cK(i+1,i+1));
return(2.0*ld);
} EddyCatch
NEWMAT::SymmetricMatrix SqrExpKMatrix::GetDeriv(unsigned int di) const EddyTry
{
arma::mat dK(this->NoOfScans(),this->NoOfScans());
calculate_dK_matrix(_hpar,_dist_mat,di,dK);
return(dK);
} EddyCatch
void SqrExpKMatrix::GetAllDerivs(std::vector<NEWMAT::SymmetricMatrix>& derivs) const EddyTry
{
if (derivs.size() != this->NoOfHyperPar()) derivs.resize(this->NoOfHyperPar());
for (unsigned int i=0; i<this->NoOfHyperPar(); i++) {
if (derivs[i].n_rows != this->NoOfScans() || derivs[i].n_cols != this->NoOfScans()) {
derivs[i].set_size(this->NoOfScans(),this->NoOfScans());
}
calculate_dK_matrix(_hpar,_dist_mat,i,derivs[i]);
}
return;
} EddyCatch
void SqrExpKMatrix::Print() const EddyTry
{
cout << "_hpar = " << _hpar[0] << ", " << _hpar[1] << ", " << _hpar[2] << endl;
cout << "_K_ok = " << _K_ok << endl;
cout << "_iK_ok = " << _iK_ok << endl;
cout << "NoOfScans() = " << this->NoOfScans() << endl;
} EddyCatch
void SqrExpKMatrix::Write(const std::string& basefname) const EddyTry
{
std::string fname = basefname + ".dist_mat.txt";
MISCMATHS::write_ascii_matrix(_dist_mat,fname);
fname = basefname + ".K.txt";
MISCMATHS::write_ascii_matrix(_K,fname);
fname = basefname + ".cK.txt";
MISCMATHS::write_ascii_matrix(_cK,fname);
fname = basefname + ".iK.txt";
MISCMATHS::write_ascii_matrix(_iK,fname);
fname = basefname + ".pv.txt";
arma::mat pvs = make_pred_vec_matrix();
MISCMATHS::write_ascii_matrix(pvs,fname);
} EddyCatch
void SqrExpKMatrix::calculate_K_matrix(const std::vector<double>& hpar,
const arma::mat& dist_mat,
arma::mat& K) const EddyTry
{
if (K.n_rows != dist_mat.n_rows) K.set_size(dist_mat.n_rows,dist_mat.n_rows);
double s2 = this->sqr(hpar[0]); // Signal variance
double ell2 = 2.0*this->sqr(hpar[1]); // 2*l^2, l=length scale
for (unsigned int j=0; j<K.n_cols; j++) {
for (unsigned int i=j; i<K.n_rows; i++) {
double dist2 = this->sqr(dist_mat(i,j));
K(i,j) = s2*std::exp(-dist2/ell2);
if (i != j) K(j,i) = K(i,j); // If below diagonal
else K(i,j) += this->sqr(hpar[2]); // If on diagonal
}
}
} EddyCatch
void SqrExpKMatrix::calculate_dK_matrix(// Input
const std::vector<double>& hpar,
const arma::mat& dist_mat,
unsigned int pindx,
// Output
arma::mat& dK) const EddyTry
{
if (pindx >= this->n_par()) throw ;
if (dK.n_rows != dist_mat.n_rows) dK.set_size(dist_mat.n_rows,dist_mat.n_rows);
double s2 = this->sqr(hpar[0]); // Signal variance
double ell2 = this->sqr(hpar[1]); // l^2, l=length scale
double ell3 = ell2 * hpar[1]; // l^3, l=length scale
for (unsigned int j=0; j<dK.n_cols; j++) {
for (unsigned int i=j; i<dK.n_rows; i++) {
double dist2 = this->sqr(dist_mat(i,j));
if (pindx == 0) dK(i,j) = 2.0 * hpar[0] * std::exp(-dist2/(2.0*ell2));
else if (pindx == 1) dK(i,j) = (dist2*s2/ell3) * exp(-dist2/(2.0*ell2));
else if (pindx == 2) {
if (i == j) dK(i,j) = 2.0 * hpar[2];
else dK(i,j) = 0.0;
}
if (i != j) dK(j,i) = dK(i,j);
}
}
} EddyCatch
arma::rowvec SqrExpKMatrix::k_row(unsigned int indx,
bool excl,
const std::vector<double>& hpar,
const arma::mat& dist_mat) const
{
arma::rowvec kr((excl) ? dist_mat.n_rows : dist_mat.n_rows-1);
double s2 = this->sqr(hpar[0]); // Signal variance
double ell2 = 2.0*this->sqr(hpar[1]); // 2*l^2, l=length scale
for (unsigned int i=0,j=0; i<dist_mat.n_rows; i++) {
if (!excl || i != indx) {
double dist2 = this->sqr(dist_mat(indx,i));
kr(j++) = s2*std::exp(-dist2/ell2);
}
}
return(kr);
}
void SqrExpKMatrix::validate_K_matrix() EddyTry
{
try {
_cK = arma::chol(_K);
_K_ok = true;
}
catch (std::runtime_error& e) {
_K_ok = false;
}
return;
} EddyCatch
void SqrExpKMatrix::calculate_iK() EddyTry
{
arma::mat iR = _cK.i();
_iK = iR*iR.t();
_iK_ok = true;
return;
} EddyCatch
arma::mat SqrExpKMatrix::calculate_iK_index(unsigned int i) const EddyTry
{
unsigned int n = _K.n_rows; // # of rows AND # of columns
arma::mat K_indx(n-1,n-1);
K_indx.submat(0,0,i-1,i-1) = _K.submat(0,0,i-1,i-1); // Top left
K_indx.submat(0,i,i-1,n-2) = _K.submat(0,i+1,i-1,n-1); // Top right
K_indx.submat(i,0,n-2,i-1) = _K.submat(i+1,0,n-1,i-1); // Lower left
K_indx.submat(i,i,n-2,n-2) = _K.submat(i+1,i+1,n-1,n-1); // Lower right
return(K_indx.i());
} EddyCatch
arma::mat SqrExpKMatrix::make_pred_vec_matrix(bool excl) const EddyTry
{
arma::mat M(_K.n_rows,(excl) ? _K.n_cols-1 : _K.n_cols);
for (unsigned int i=0; i<M.n_rows; i++) M.row(i) = PredVec(i,excl);
return(M);
} EddyCatch
bool SqrExpKMatrix::valid_hpars(const std::vector<double>& hpar) const EddyTry
{
arma::mat K(this->NoOfScans(),this->NoOfScans());
calculate_K_matrix(hpar,dist_mat(),K);
arma::mat R;
return(arma::chol(R,K));
} EddyCatch
/*! \file KMatrix.h
\brief Contains declaration of virtual base class and a derived class for Covariance matrices for GP
\author Jesper Andersson
\version 1.0b, Oct., 2013.
*/
// Declarations of virtual base class for
// Covariance matrices for DWI data.
//
// KMatrix.h
//
// Jesper Andersson, FMRIB Image Analysis Group
//
// Copyright (C) 2013 University of Oxford
//
#ifndef KMatrix_h
#define KMatrix_h
#include <cstdlib>
#include <string>
#include <exception>
#include <vector>
#include <cmath>
#include <memory>
#include "armawrap/newmat.h"
#include "miscmaths/miscmaths.h"
#include "EddyHelperClasses.h"
namespace EDDY {
/****************************************************************//**
*
* \brief Virtual base class for classes implementing Covariance
* matrices (Gram matrices) for Gaussian processes for diffusion data.
*
* This virtual base class has many non-virtual methods that perform
* a very significant of the work of any derived class. It is only
* a few private methods that are virtual. These are the ones that
* implement the details of how the hyperparameters are translated
* into a K-matrix (Gram matrix).
* All indicies in the interface are zero-offset.
********************************************************************/
class KMatrix
{
public:
KMatrix() {}
virtual ~KMatrix() {}
/// Returns a pointer to a clone of self.
virtual std::shared_ptr<KMatrix> Clone() const = 0;
/// Multiplication of K^{-1} with column vector
virtual NEWMAT::ColumnVector iKy(const NEWMAT::ColumnVector& y) const = 0;
/// Multiplication of K^{-1} with column vector
virtual NEWMAT::ColumnVector iKy(const NEWMAT::ColumnVector& y) = 0;
/// Get prediction vector for the ith scan.
virtual NEWMAT::RowVector PredVec(unsigned int i, bool excl) const = 0;
/// Get prediction vector for the ith scan.
virtual NEWMAT::RowVector PredVec(unsigned int i, bool excl) = 0;
/// Get variance of prediction for the ith scan
virtual double PredVar(unsigned int i, bool excl) = 0;
/// Get error variance of the ith scan
virtual double ErrVar(unsigned int i) const = 0;
/// Returns K^{-1}
virtual NEWMAT::SymmetricMatrix iK() const = 0;
/// Reset (to state after construction by default constructor)
virtual void Reset() = 0;
/// Set diffusion parameters
virtual void SetDiffusionPar(const std::vector<DiffPara>& dpars) = 0;
/// Return indicies for calculating means
virtual std::vector<std::vector<unsigned int> > GetMeanIndicies() const = 0;
/// Set hyperparameters
virtual void SetHyperPar(const std::vector<double>& hpar) = 0;
/// Multiply error-variance hyperpar by factor > 1.0
virtual void MulErrVarBy(double ff) = 0;
/// Pre-calculate inverse to allow for (thread safe) const version of PredVec
virtual void CalculateInvK() = 0;
/// Get starting guess for hyperparameters based on data
virtual std::vector<double> GetHyperParGuess(const std::vector<NEWMAT::ColumnVector>& data) const = 0;
/// Get hyperparameters
virtual std::vector<double> GetHyperPar() const = 0;
/// Get No of Scans
virtual unsigned int NoOfScans() const = 0;
/// Get No of hyperparameters
virtual unsigned int NoOfHyperPar() const = 0;
/// Check for validity (i.e. positive defitness)
virtual bool IsValid() const = 0;
/// Returns log of determinant of K
virtual double LogDet() const = 0;
/// Returns derivative w.r.t. ith hyperparameter.
virtual NEWMAT::SymmetricMatrix GetDeriv(unsigned int i) const = 0;
/// Returns derivatives w.r.t. all hyperparameters
virtual void GetAllDerivs(std::vector<NEWMAT::SymmetricMatrix>& derivs) const = 0;
/// Return K-matrix
virtual NEWMAT::SymmetricMatrix AsNewmat() const = 0;
/// Writes useful debug info to the screen
virtual void Print() const = 0;
/// Writes useful (and more bountiful) debug info to a file
virtual void Write(const std::string& basefname) const = 0;
};
/****************************************************************//**
*
* \brief Virtual class for classes implementing Covariance
* matrices (Gram matrices) for Gaussian processes for "shelled"
* diffusion data, where shelled can be single or multi-shell data.
*
* This virtual class has many non-virtual methods that perform
* a very significant par of the work of any derived class. It is only
* a few private methods that are virtual. These are the ones that
* implement the details of how the hyperparameters are translated
* into a K-matrix (Gram matrix).
* Several methods have both const and non-const implementations.
* This is so that we shall have a set of const versions to be
* used with const objects in a thread safe way.
* All indicies in the interface are zero-offset.
********************************************************************/
class MultiShellKMatrix : public KMatrix
{
public:
MultiShellKMatrix(bool dcsh) EddyTry : _ngrp(0), _K_ok(false), _iK_ok(false), _dcsh(dcsh) {} EddyCatch
MultiShellKMatrix(const std::vector<DiffPara>& dpars,
bool dcsh);
~MultiShellKMatrix() {}
/// Multiplication of K^{-1} with column vector
NEWMAT::ColumnVector iKy(const NEWMAT::ColumnVector& y) const;
/// Multiplication of K^{-1} with column vector
NEWMAT::ColumnVector iKy(const NEWMAT::ColumnVector& y);
/// Get prediction vector for the ith scan.
NEWMAT::RowVector PredVec(unsigned int i, bool excl) const;
/// Get prediction vector for the ith scan.
NEWMAT::RowVector PredVec(unsigned int i, bool excl);
/// Get variance of prediction for the ith scan
double PredVar(unsigned int i, bool excl);
/// Get error variance of the ith scan
double ErrVar(unsigned int i) const EddyTry { return(err_var(i,_grpi,_ngrp,_thpar)); } EddyCatch
/// Returns K^{-1}
NEWMAT::SymmetricMatrix iK() const;
/// Returns K^{-1}
NEWMAT::SymmetricMatrix iK();
/// Reset (to state after construction by default constructor)
void Reset();
/// Set diffusion parameters
void SetDiffusionPar(const std::vector<DiffPara>& dpars);
/// Return indicies for calculating means
std::vector<std::vector<unsigned int> > GetMeanIndicies() const EddyTry { return(_grps); } EddyCatch
/// Set hyperparameters
virtual void SetHyperPar(const std::vector<double>& hpar);
/// Multiply error-variance hyperpar by factor > 1.0
virtual void MulErrVarBy(double ff);
/// Pre-calculate inverse to allow for (thread safe) const version of PredVec
void CalculateInvK();
/// Get starting guess for hyperparameters based on data
virtual std::vector<double> GetHyperParGuess(const std::vector<NEWMAT::ColumnVector>& data) const = 0;
/// Get hyperparameters
std::vector<double> GetHyperPar() const EddyTry { return(_hpar); } EddyCatch
/// Check for validity (i.e. positive definitness)
bool IsValid() const { return(_K_ok); }
/// Returns the # of scans
unsigned int NoOfScans() const { return(_dpars.size()); }
/// Returns the # of groups (shells)
unsigned int NoOfGroups() const { return(_ngrp); }
/// Returns the # of hyperparameters
unsigned int NoOfHyperPar() const { return(_hpar.size()); }
/// Returns log of determinant of K
double LogDet() const;
/// Returns derivative w.r.t. ith hyperparameter.
NEWMAT::SymmetricMatrix GetDeriv(unsigned int di) const;
/// Returns derivatives w.r.t. all hyperparameters
void GetAllDerivs(std::vector<NEWMAT::SymmetricMatrix>& derivs) const;
/// Return K-matrix
NEWMAT::SymmetricMatrix AsNewmat() const EddyTry { return(_K); } EddyCatch
/// Print out some information that can be useful for debugging
void Print() const;
/// Writes useful (and more bountiful) debug info to a file
void Write(const std::string& basefname) const;
protected:
std::pair<unsigned int, unsigned int> parameter_index_to_ij(unsigned int pi,
unsigned int ngrp) const;
/*
unsigned int ij_to_parameter_index(unsigned int i,
unsigned int j,
unsigned int n) const;
unsigned int n_par(unsigned int ngrp) const { return(_npps*ngrp + (_npps-1)*(ngrp*ngrp - ngrp)/2); }
*/
double variance(const NEWMAT::ColumnVector& data,
const std::vector<unsigned int>& indx) const;
/// Allow derived class to set _hpar
void set_hpar(const std::vector<double>& hpar) EddyTry { _hpar = hpar; } EddyCatch
/// Allow derived class to set _thpar
void set_thpar(const std::vector<double>& thpar) EddyTry { _thpar = thpar; } EddyCatch
/// Allow derived class to set _npps
// void set_npps(unsigned int npps) { _npps = npps; }
/// Give full access to _K to derived class.
NEWMAT::SymmetricMatrix& give_me_a_K() EddyTry { if (_K_ok) return(_K); else throw EddyException("MultiShellKMatrix::give_me_a_K: invalid K"); } EddyCatch
/// Allow derived class to validate _K
void validate_K_matrix();
/// Allow derived classes to explicitly invalidate K matrix
void set_K_matrix_invalid() EddyTry { _K_ok = false; _iK_ok = false; _pv_ok.assign(_dpars.size(),false); } EddyCatch
/// Enable derived class to check matrix for validity (positive defitness).
bool valid_hpars(const std::vector<double>& hpar) const;
/// Give derived class read access to selected members
const std::vector<std::vector<unsigned int> >& grps() const EddyTry { return(_grps); } EddyCatch
const std::vector<unsigned int>& grpi() const EddyTry { return(_grpi); } EddyCatch
const std::vector<double>& grpb() const EddyTry { return(_grpb); } EddyCatch
const std::vector<double>& thpar() const EddyTry { return(_thpar); } EddyCatch
const NEWMAT::SymmetricMatrix& angle_mat() const EddyTry { return(_angle_mat); } EddyCatch
private:
std::vector<DiffPara> _dpars; // Diffusion parameters
std::vector<unsigned int> _grpi; // Array of group (shell) indicies
std::vector<std::vector<unsigned int> > _grps; // Arrays of indicies into _dpars indicating groups
std::vector<double> _grpb; // Array of group-mean b-values
unsigned int _ngrp; // Number of groups (shells)
std::vector<double> _hpar; // Hyperparameters
std::vector<double> _thpar; // "Transformed" (for example exponentiated) hyperparameters
NEWMAT::SymmetricMatrix _angle_mat; // Matrix of angles between scans.
bool _K_ok; // True if K has been confirmed to be positive definite
NEWMAT::SymmetricMatrix _K; // K
NEWMAT::LowerTriangularMatrix _cK; // L from a Cholesky decomp of K
bool _iK_ok; // True if K^{-1} has been calculated
NEWMAT::SymmetricMatrix _iK; // K^{-1}
std::vector<NEWMAT::RowVector> _pv; // Array of prediction vectors for the case of exclusion.
std::vector<bool> _pv_ok; // Indicates which prediction vectors are valid at any time
bool _dcsh; // Don't check that data is shelled if set.
virtual unsigned int ij_to_parameter_index(unsigned int i,
unsigned int j,
unsigned int n) const = 0;
virtual unsigned int n_par(unsigned int ngrp) const = 0;
virtual void calculate_K_matrix(const std::vector<unsigned int>& grpi,
unsigned int ngrp,
const std::vector<double>& thpar,
const NEWMAT::SymmetricMatrix& angle_mat,
NEWMAT::SymmetricMatrix& K) const = 0;
virtual void calculate_dK_matrix(const std::vector<unsigned int>& grpi,
unsigned int ngrp,
const std::vector<double>& thpar,
const NEWMAT::SymmetricMatrix& angle_mat,
unsigned int i,
unsigned int j,
unsigned int off,
NEWMAT::SymmetricMatrix& dK) const = 0;
virtual NEWMAT::RowVector k_row(unsigned int indx,
bool excl,
const std::vector<unsigned int>& grpi,
unsigned int ngrp,
const std::vector<double>& thpar,
const NEWMAT::SymmetricMatrix& angle_mat) const = 0;
virtual std::vector<double> exp_hpar(unsigned int ngrp,
const std::vector<double>& hpar) const = 0;
virtual std::vector<double> get_arbitrary_hpar(unsigned int ngrp) const = 0;
virtual double sig_var(unsigned int i,
const std::vector<unsigned int>& grpi,
unsigned int ngrp,
const std::vector<double>& thpar) const = 0;
virtual double err_var(unsigned int i,
const std::vector<unsigned int>& grpi,
unsigned int ngrp,
const std::vector<double>& thpar) const = 0;
void calculate_iK();
NEWMAT::SymmetricMatrix calculate_iK_index(unsigned int i) const;
NEWMAT::Matrix make_pred_vec_matrix(bool excl=false) const;
void make_angle_mat();
double mean(const NEWMAT::ColumnVector& data,
const std::vector<unsigned int>& indx) const;
double sqr(double a) const { return(a*a); }
};
/****************************************************************//**
*
* \brief Concrete descendant of virtual MultiShellKMatrix class
* implementing the spherical covariance function.
*
* This class implements exactly the same model as SphericalKMatrix
* (below) in the single shell-case. However, for the multi-shell case
* it is different and unlike that it is "guaranteed" to always yield
* a valid Gram-matrix. Will probably replace SphericalKMatrix.
* The number of hyperparameters is 3 for the single shell case
* and 3+no_of_shells for the multi-shell case.
* The hyperparameters are ordered as:
* Single shell case:
* [log(signal_variance) log(angular_length_scale) log(error_variance)]
* Multi shell case:
* [log(signal_variance) log(angular_length_scale) log(b_length_scale)
* log(error_variance_shell_1) log(error_variance_shell_2) ...]
*
* All indicies in the interface are zero-offset.
********************************************************************/
class NewSphericalKMatrix : public MultiShellKMatrix
{
public:
NewSphericalKMatrix(bool dcsh=false) EddyTry : MultiShellKMatrix(dcsh) {} EddyCatch
NewSphericalKMatrix(const std::vector<DiffPara>& dpars,
bool dcsh=false);
std::shared_ptr<KMatrix> Clone() const EddyTry { return(std::shared_ptr<NewSphericalKMatrix>(new NewSphericalKMatrix(*this))); } EddyCatch
std::vector<double> GetHyperParGuess(const std::vector<NEWMAT::ColumnVector>& data) const;
void SetHyperPar(const std::vector<double>& hpar);
void MulErrVarBy(double ff);
private:
unsigned int n_par(unsigned int ngrp) const { return(ngrp==1 ? 3 : 3 + ngrp); }
unsigned int ij_to_parameter_index(unsigned int i,
unsigned int j,
unsigned int n) const EddyTry { throw EddyException("NewSphericalKMatrix::ij_to_parameter_index: Invalid call"); } EddyCatch
void calculate_K_matrix(const std::vector<unsigned int>& grpi,
unsigned int ngrp,
const std::vector<double>& thpar,
const NEWMAT::SymmetricMatrix& angle_mat,
NEWMAT::SymmetricMatrix& K) const;
void calculate_dK_matrix(const std::vector<unsigned int>& grpi,
unsigned int ngrp,
const std::vector<double>& thpar,
const NEWMAT::SymmetricMatrix& angle_mat,
unsigned int gi,
unsigned int gj,
unsigned int off,
NEWMAT::SymmetricMatrix& dK) const;
NEWMAT::RowVector k_row(unsigned int indx,
bool excl,
const std::vector<unsigned int>& grpi,
unsigned int ngrp,
const std::vector<double>& thpar,
const NEWMAT::SymmetricMatrix& angle_mat) const;
std::vector<double> exp_hpar(unsigned int ngrp,
const std::vector<double>& hpar) const EddyTry
{ std::vector<double> epar = hpar; for (unsigned int i=0; i<hpar.size(); i++) epar[i] = std::exp(hpar[i]); return(epar); } EddyCatch
std::vector<double> get_arbitrary_hpar(unsigned int ngrp) const;
double sig_var(unsigned int i,
const std::vector<unsigned int>& grpi,
unsigned int ngrp,
const std::vector<double>& thpar) const EddyTry { return(thpar[0]); } EddyCatch
double err_var(unsigned int i,
const std::vector<unsigned int>& grpi,
unsigned int ngrp,
const std::vector<double>& thpar) const EddyTry { const double *ev = (ngrp > 1) ? &(thpar[3]) : &(thpar[2]); return(ev[grpi[i]]); } EddyCatch
};
/****************************************************************//**
*
* \brief Concrete descendant of virtual MultiShellKMatrix class
* implementing the spherical covariance function.
*
* All indicies in the interface are zero-offset.
********************************************************************/
class SphericalKMatrix : public MultiShellKMatrix
{
public:
SphericalKMatrix(bool dcsh=false) EddyTry : MultiShellKMatrix(dcsh) {} EddyCatch
SphericalKMatrix(const std::vector<DiffPara>& dpars,
bool dcsh=false);
std::shared_ptr<KMatrix> Clone() const EddyTry { return(std::shared_ptr<SphericalKMatrix>(new SphericalKMatrix(*this))); } EddyCatch
std::vector<double> GetHyperParGuess(const std::vector<NEWMAT::ColumnVector>& data) const;
private:
unsigned int n_par(unsigned int ngrp) const { return(3*ngrp + (ngrp*ngrp - ngrp)); }
unsigned int ij_to_parameter_index(unsigned int i,
unsigned int j,
unsigned int n) const;
void calculate_K_matrix(const std::vector<unsigned int>& grpi,
unsigned int ngrp,
const std::vector<double>& thpar,
const NEWMAT::SymmetricMatrix& angle_mat,
NEWMAT::SymmetricMatrix& K) const;
void calculate_dK_matrix(const std::vector<unsigned int>& grpi,
unsigned int ngrp,
const std::vector<double>& thpar,
const NEWMAT::SymmetricMatrix& angle_mat,
unsigned int gi,
unsigned int gj,
unsigned int off,
NEWMAT::SymmetricMatrix& dK) const;
NEWMAT::RowVector k_row(unsigned int indx,
bool excl,
const std::vector<unsigned int>& grpi,
unsigned int ngrp,
const std::vector<double>& thpar,
const NEWMAT::SymmetricMatrix& angle_mat) const;
std::vector<double> exp_hpar(unsigned int ngrp,
const std::vector<double>& hpar) const;
std::vector<double> get_arbitrary_hpar(unsigned int ngrp) const;
double sig_var(unsigned int i,
const std::vector<unsigned int>& grpi,
unsigned int ngrp,
const std::vector<double>& thpar) const EddyTry { return(thpar[ij_to_parameter_index(grpi[i],grpi[i],ngrp)]); } EddyCatch
double err_var(unsigned int i,
const std::vector<unsigned int>& grpi,
unsigned int ngrp,
const std::vector<double>& thpar) const EddyTry { return(thpar[ij_to_parameter_index(grpi[i],grpi[i],ngrp)+2]); } EddyCatch
};
/****************************************************************//**
*
* \brief Concrete descendant of virtual MultiShellKMatrix class
* implementing the exponential covariance function.
*
* All indicies in the interface are zero-offset.
********************************************************************/
class ExponentialKMatrix : public MultiShellKMatrix
{
public:
ExponentialKMatrix(bool dcsh=false) EddyTry : MultiShellKMatrix(dcsh) {} EddyCatch
ExponentialKMatrix(const std::vector<DiffPara>& dpars,
bool dcsh=false);
std::shared_ptr<KMatrix> Clone() const EddyTry { return(std::shared_ptr<ExponentialKMatrix>(new ExponentialKMatrix(*this))); } EddyCatch
std::vector<double> GetHyperParGuess(const std::vector<NEWMAT::ColumnVector>& data) const;
private:
unsigned int n_par(unsigned int ngrp) const { return(3*ngrp + (ngrp*ngrp - ngrp)); }
unsigned int ij_to_parameter_index(unsigned int i,
unsigned int j,
unsigned int n) const;
void calculate_K_matrix(const std::vector<unsigned int>& grpi,
unsigned int ngrp,
const std::vector<double>& thpar,
const NEWMAT::SymmetricMatrix& angle_mat,
NEWMAT::SymmetricMatrix& K) const;
void calculate_dK_matrix(const std::vector<unsigned int>& grpi,
unsigned int ngrp,
const std::vector<double>& thpar,
const NEWMAT::SymmetricMatrix& angle_mat,
unsigned int gi,
unsigned int gj,
unsigned int off,
NEWMAT::SymmetricMatrix& dK) const;
NEWMAT::RowVector k_row(unsigned int indx,
bool excl,
const std::vector<unsigned int>& grpi,
unsigned int ngrp,
const std::vector<double>& thpar,
const NEWMAT::SymmetricMatrix& angle_mat) const;
std::vector<double> exp_hpar(unsigned int ngrp,
const std::vector<double>& hpar) const;
std::vector<double> get_arbitrary_hpar(unsigned int ngrp) const;
double sig_var(unsigned int i,
const std::vector<unsigned int>& grpi,
unsigned int ngrp,
const std::vector<double>& thpar) const EddyTry { return(thpar[ij_to_parameter_index(grpi[i],grpi[i],ngrp)]); } EddyCatch
double err_var(unsigned int i,
const std::vector<unsigned int>& grpi,
unsigned int ngrp,
const std::vector<double>& thpar) const EddyTry { return(thpar[ij_to_parameter_index(grpi[i],grpi[i],ngrp)+2]); } EddyCatch
};
/****************************************************************//**
*
* \brief Concrete descendant of virtual MultiShellKMatrix class
* implementing the spherical covariance function.
*
* This class implements exactly the same model as SphericalKMatrix
* (below) in the single shell-case. However, for the multi-shell case
* it is different and unlike that it is "guaranteed" to always yield
* a valid Gram-matrix. Will probably replace SphericalKMatrix.
* The number of hyperparameters is 3 for the single shell case
* and 3+no_of_shells for the multi-shell case.
* The hyperparameters are ordered as:
* Single shell case:
* [log(signal_variance) log(angular_length_scale) log(error_variance)]
* Multi shell case:
* [log(signal_variance) log(angular_length_scale) log(b_length_scale)
* log(error_variance_shell_1) log(error_variance_shell_2) ...]
*
* All indicies in the interface are zero-offset.
********************************************************************/
/****************************************************************//**
*
* \brief Concrete descendant of virtual Matrix class for fMRI.
* Implements a squared-exponential covariance function.
*
* The hyperparameters are ordered as:
* [log(signal_variance) log(angular_length_scale) log(error_variance)]
*
* All indicies in the interface are zero-offset.
********************************************************************/
class SqrExpKMatrix : public KMatrix
{
public:
SqrExpKMatrix() EddyTry : _K_ok(false), _iK_ok(false) {} EddyCatch
~SqrExpKMatrix() {}
std::shared_ptr<KMatrix> Clone() const EddyTry { return(std::shared_ptr<SqrExpKMatrix>(new SqrExpKMatrix(*this))); } EddyCatch
/// Multiplication of K^{-1} with column vector
NEWMAT::ColumnVector iKy(const NEWMAT::ColumnVector& y) const;
/// Multiplication of K^{-1} with column vector
NEWMAT::ColumnVector iKy(const NEWMAT::ColumnVector& y);
/// Get prediction vector for the ith scan.
NEWMAT::RowVector PredVec(unsigned int i, bool excl) const;
/// Get prediction vector for the ith scan.
NEWMAT::RowVector PredVec(unsigned int i, bool excl);
/// Get variance of prediction for the ith scan
double PredVar(unsigned int i, bool excl);
/// Get error variance of the ith scan
double ErrVar(unsigned int i) const EddyTry { return(this->sqr(_hpar[2])); } EddyCatch
/// Returns K^{-1}
NEWMAT::SymmetricMatrix iK() const;
/// Returns K^{-1}
NEWMAT::SymmetricMatrix iK();
/// Reset (to state after construction by default constructor)
void Reset();
/// Set diffusion parameters
void SetDiffusionPar(const std::vector<DiffPara>& dpars) {} // Not diffusion
/// Return indicies for calculating means
std::vector<std::vector<unsigned int> > GetMeanIndicies() const { return(std::vector<std::vector<unsigned int> >()); } // No group means
/// Set hyperparameters
void SetHyperPar(const std::vector<double>& hpar);
/// Multiply error-variance hyperpar by factor > 1.0
void MulErrVarBy(double ff);
/// Pre-calculate inverse to allow for (thread safe) const version of PredVec
void CalculateInvK();
/// Get starting guess for hyperparameters based on data
std::vector<double> GetHyperParGuess(const std::vector<NEWMAT::ColumnVector>& data) const;
/// Get hyperparameters
std::vector<double> GetHyperPar() const;
/// Get No of Scans
unsigned int NoOfScans() const EddyTry { return(this->n_scans()); } EddyCatch
/// Get No of hyperparameters
unsigned int NoOfHyperPar() const EddyTry { return(this->n_par()); } EddyCatch
/// Check for validity (i.e. positive definitness)
bool IsValid() const EddyTry { return(_K_ok); } EddyCatch
/// Returns log of determinant of K
double LogDet() const;
/// Returns derivative w.r.t. ith hyperparameter.
NEWMAT::SymmetricMatrix GetDeriv(unsigned int i) const;
/// Returns derivatives w.r.t. all hyperparameters
void GetAllDerivs(std::vector<NEWMAT::SymmetricMatrix>& derivs) const;
/// Return K-matrix
NEWMAT::SymmetricMatrix AsNewmat() const EddyTry { return(_K); } EddyCatch
/// Writes useful debug info to the screen
void Print() const;
/// Writes useful (and more bountiful) debug info to a file
void Write(const std::string& basefname) const;
private:
std::vector<double> _hpar; // Hyperparameters
arma::mat _dist_mat; // Matrix of distances between scans
bool _K_ok; // True if K has been confirmed to be positive definite
arma::mat _K; // K
arma::mat _cK; // R from a Cholesky decomposition of K
bool _iK_ok; // True of K^{-1} has been calculated
arma::mat _iK; // K^{-1}
std::vector<arma::rowvec> _pv; // Array of prediction vectors for the case of exclusion.
std::vector<bool> _pv_ok; // Indicates which prediction vectors are valid at any time
unsigned int n_par() const { return(3); }
unsigned int n_scans() const { return(static_cast<unsigned int>(_dist_mat.n_rows)); }
const arma::mat& dist_mat() const { return(_dist_mat); }
void calculate_K_matrix(const std::vector<double>& hpar,
const arma::mat& dist_mat,
arma::mat& K) const;
void calculate_dK_matrix(// Input
const std::vector<double>& hpar,
const arma::mat& dist_mat,
unsigned int pindx,
// Output
arma::mat& dK) const;
arma::rowvec k_row(unsigned int indx,
bool excl,
const std::vector<double>& hpar,
const arma::mat& dist_mat) const;
void validate_K_matrix();
void calculate_iK();
arma::mat calculate_iK_index(unsigned int i) const;
arma::mat make_pred_vec_matrix(bool excl=false) const;
bool valid_hpars(const std::vector<double>& hpar) const;
double sqr(double a) const { return(a*a); }
};
} // End namespace EDDY
#endif // end #ifndef KMatrix_h
/*! \file LSResampler.cpp
\brief Contains definition of CPU implementation of a class for least-squares resampling of pairs of images
\author Jesper Andersson
\version 1.0b, August, 2013.
*/
//
// LSResampler.cpp
//
// Jesper Andersson, FMRIB Image Analysis Group
//
// Copyright (C) 2011 University of Oxford
//
#include <cstdlib>
#include <string>
#include <vector>
#include <cmath>
#include <time.h>
#include "armawrap/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"
#include "LSResampler.h"
namespace EDDY {
class LSResamplerImpl
{
public:
LSResamplerImpl(const EDDY::ECScan& s1,
const EDDY::ECScan& s2,
std::shared_ptr<const NEWIMAGE::volume<float> > hzfield,
double lambda,
const Utilities::NoOfThreads& nthr);
const NEWIMAGE::volume<float>& GetResampledVolume() const EddyTry { return(_rvol); } EddyCatch
const NEWIMAGE::volume<float>& GetMask() const EddyTry { return(_omask); } EddyCatch
private:
NEWIMAGE::volume<float> _rvol; // Resampled volume
NEWIMAGE::volume<float> _omask; // Mask indicating valid voxels in _rvol
// Convenience routine that resamples the slices given by 'slices'.
// It greatly facilitates a parallel implementation, which however
// is not currently used.
void resample_slices(// Input
const NEWIMAGE::volume<float>& ima1,
const NEWIMAGE::volume<float>& field1,
const NEWIMAGE::volume<float>& ima2,
const NEWIMAGE::volume<float>& field2,
const NEWIMAGE::volume<float>& mask,
double lambda,
int offset,
int stride,
bool pex,
// Output
NEWIMAGE::volume<float>& ovol,
NEWIMAGE::volume<float>& omask);
template<typename T>
T sqr(const T& v) const { return(v*v); }
};
LSResampler::LSResampler(const EDDY::ECScan& s1,
const EDDY::ECScan& s2,
std::shared_ptr<const NEWIMAGE::volume<float> > hzfield,
double lambda,
const Utilities::NoOfThreads& nthr) EddyTry
{
_pimpl = new LSResamplerImpl(s1,s2,hzfield,lambda,nthr);
} EddyCatch
LSResampler::~LSResampler()
{
delete _pimpl;
}
const NEWIMAGE::volume<float>& LSResampler::GetResampledVolume() const EddyTry
{
return(_pimpl->GetResampledVolume());
} EddyCatch
const NEWIMAGE::volume<float>& LSResampler::GetMask() const EddyTry
{
return(_pimpl->GetMask());
} EddyCatch
/****************************************************************//**
*
* Constructs an LSResamplerImpl object.
* All the work for resampling a pair of scans into a single volume
* is carried out inside this constructor. After the object has been
* constructed one can immediately obtain the resampled volume through
* a call to GetResampledVolume.
* \param s1 One of a pair of scans with parallel phase-encodings
* \param s2 The second of a pair of scans with parallel phase-encodings
* \param hzfield Field in Hz in model space
*
********************************************************************/
LSResamplerImpl::LSResamplerImpl(const EDDY::ECScan& s1,
const EDDY::ECScan& s2,
std::shared_ptr<const NEWIMAGE::volume<float> > hzfield,
double lambda,
const Utilities::NoOfThreads& nthr) EddyTry
{
// cout << "I'm in CPU version of LSResampler" << endl;
if (!EddyUtils::AreMatchingPair(s1,s2)) throw EddyException("LSResampler::LSResampler:: Mismatched pair");
// Resample both images using rigid body parameters
NEWIMAGE::volume<float> mask1, mask2;
NEWIMAGE::volume<float> ima1 = s1.GetMotionCorrectedIma(mask1);
NEWIMAGE::volume<float> ima2 = s2.GetMotionCorrectedIma(mask2);
NEWIMAGE::volume<float> mask = mask1*mask2;
_omask.reinitialize(mask.xsize(),mask.ysize(),mask.zsize());
_omask = 1.0;
_rvol.reinitialize(ima1.xsize(),ima1.ysize(),ima1.zsize());
NEWIMAGE::copybasicproperties(ima1,_rvol);
// Get fields
NEWIMAGE::volume4D<float> field1_4D = s1.FieldForScanToModelTransform(hzfield); // In mm
NEWIMAGE::volume4D<float> field2_4D = s2.FieldForScanToModelTransform(hzfield); // In mm
// Check what direction phase-encode is in
bool pex = (s1.GetAcqPara().PhaseEncodeVector()(1)) ? true : false;
// Get relevant parts of fields
NEWIMAGE::volume<float> field1, field2;
if (pex) { field1 = field1_4D[0]; field2 = field2_4D[0]; }
else { field1 = field1_4D[1]; field2 = field2_4D[1]; }
// Pre-calculate some stuff for convenience
if (nthr._n == 1) { // Single thread
std::vector<int> slices(ima1.zsize());
std::iota (slices.begin(),slices.end(),0); // Fill with 0, 1, ..., ima1.zsize()-1
this->resample_slices(ima1,field1,ima2,field2,mask,lambda,0,1,pex,_rvol,_omask);
}
else if (nthr._n > 1) {
std::vector<std::thread> threads(nthr._n-1);
for (int t=0; t<nthr._n-1; t++) {
threads[t] = std::thread(&LSResamplerImpl::resample_slices,this,std::ref(ima1),std::ref(field1),std::ref(ima2),
std::ref(field2),std::ref(mask),lambda,t,nthr._n,pex,std::ref(_rvol),std::ref(_omask));
}
this->resample_slices(ima1,field1,ima2,field2,mask,lambda,nthr._n-1,nthr._n,pex,_rvol,_omask);
std::for_each(threads.begin(),threads.end(),std::mem_fn(&std::thread::join));
}
return;
} EddyCatch
void LSResamplerImpl::resample_slices(// Input
const NEWIMAGE::volume<float>& ima1,
const NEWIMAGE::volume<float>& field1,
const NEWIMAGE::volume<float>& ima2,
const NEWIMAGE::volume<float>& field2,
const NEWIMAGE::volume<float>& mask,
double lambda,
int offset,
int stride,
bool pex,
// Output
NEWIMAGE::volume<float>& ovol,
NEWIMAGE::volume<float>& omask)
{
// Display vector class that does much of the work
unsigned int sz = (pex) ? field1.xsize() : field1.ysize();
TOPUP::DispVec dv1(sz), dv2(sz);
// Pre-calculate some stuff for convenience
NEWMAT::Matrix StS = dv1.GetS_Matrix(false);
StS = lambda*StS.t()*StS;
NEWMAT::ColumnVector zeros;
if (pex) zeros.ReSize(ima1.xsize()); else zeros.ReSize(ima1.ysize());
zeros = 0.0;
double sf1, sf2; // Scale factors mm->voxels
if (pex) { sf1 = 1.0/ima1.xdim(); sf2 = 1.0/ima2.xdim(); }
else { sf1 = 1.0/ima1.ydim(); sf2 = 1.0/ima2.ydim(); }
// Loop over all slices we are supposed to do
for (unsigned int k=offset; k<ima1.zsize(); k+=stride) {
// Loop over all colums/rows
for (int ij=0; ij<((pex) ? ima1.ysize() : ima1.xsize()); ij++) {
bool row_col_is_ok = true;
if (pex) {
if (!dv1.RowIsAlright(mask,k,ij)) row_col_is_ok = false;
else {
dv1.SetFromRow(field1,k,ij);
dv2.SetFromRow(field2,k,ij);
}
}
else {
if (!dv1.ColumnIsAlright(mask,k,ij)) row_col_is_ok = false;
else {
dv1.SetFromColumn(field1,k,ij);
dv2.SetFromColumn(field2,k,ij);
}
}
if (row_col_is_ok) {
NEWMAT::Matrix K = dv1.GetK_Matrix(sf1) & dv2.GetK_Matrix(sf2);
NEWMAT::Matrix KtK = K.t()*K + StS;
NEWMAT::ColumnVector y;
if (pex) y = ima1.ExtractRow(ij,k) & ima2.ExtractRow(ij,k);
else y = ima1.ExtractColumn(ij,k) & ima2.ExtractColumn(ij,k);
NEWMAT::ColumnVector Kty = K.t()*y;
NEWMAT::ColumnVector y_hat = KtK.i()*Kty;
if (pex) ovol.SetRow(ij,k,y_hat); else ovol.SetColumn(ij,k,y_hat);
}
else {
if (pex) omask.SetRow(ij,k,zeros); else omask.SetColumn(ij,k,zeros);
}
}
}
return;
}
} // End namespace EDDY
/*! \file LSResampler.h
\brief Contains declaration of class for least-squares resampling of pairs of images
\author Jesper Andersson
\version 1.0b, April, 2013.
*/
//
// LSResampler.h
//
// Jesper Andersson, FMRIB Image Analysis Group
//
// Copyright (C) 2011 University of Oxford
//
#ifndef LSResampler_h
#define LSResampler_h
#include <cstdlib>
#include <string>
#include <vector>
#include <cmath>
#include <memory>
#include <time.h>
#include "newimage/newimageall.h"
#include "ECScanClasses.h"
namespace EDDY {
class LSResamplerImpl;
/****************************************************************//**
*
* \brief Class used to perform Least-Squares resampling of a pair
* of images acquired with reversed PE-blips.
*
* Class used to perform Least-Squares resampling of a pair
* of images acquired with reversed PE-blips. It is implemented
* using the "Pimpl idiom" which means that this class only
* implements an interface whereas the actual work is being performed
* by the LSResamplerImpl class which is declared and defined in
* LSResampler.cpp or cuda/LSResampler.cu depending on what platform
* the code is compiled for.
*
* The way you would use it is by first constructing the object and
* then calling `GetResampledVolume()` to retrieve the resampled image
* and, possibly, `GetMask()` to get a binary mask indicating valid voxels.
*
* Example:
* ~~~{.c}
* LSResampler my_resampler(blipup,blipdown,field);
* NEWIMAGE::volume<float> my_hifi_image = my_resampler.GetResampledVolume();
* ~~~
********************************************************************/
class LSResampler
{
public:
/// Constructor. Performs the actual work so that when the object is created there is already a resampled image ready.
LSResampler(const EDDY::ECScan& s1,
const EDDY::ECScan& s2,
std::shared_ptr<const NEWIMAGE::volume<float> > hzfield,
double lambda,
const Utilities::NoOfThreads& nthr);
// Delegating constructors for convenience
LSResampler(const EDDY::ECScan& s1,
const EDDY::ECScan& s2,
std::shared_ptr<const NEWIMAGE::volume<float> > hzfield) EddyTry : LSResampler(s1,s2,hzfield,0.01,Utilities::NoOfThreads(1)) {} EddyCatch
LSResampler(const EDDY::ECScan& s1,
const EDDY::ECScan& s2,
std::shared_ptr<const NEWIMAGE::volume<float> > hzfield,
double lambda) EddyTry : LSResampler(s1,s2,hzfield,lambda,Utilities::NoOfThreads(1)) {} EddyCatch
LSResampler(const EDDY::ECScan& s1,
const EDDY::ECScan& s2,
std::shared_ptr<const NEWIMAGE::volume<float> > hzfield,
const Utilities::NoOfThreads& nthr) EddyTry : LSResampler(s1,s2,hzfield,0.01,nthr) {} EddyCatch
~LSResampler();
/// Returns the resampled image. N.B. one resampled image per pair of input images.
const NEWIMAGE::volume<float>& GetResampledVolume() const;
/// Returns a binary mask with one for the voxels where a valid resampling could be performed.
const NEWIMAGE::volume<float>& GetMask() const;
private:
LSResamplerImpl* _pimpl;
};
} // End namespace EDDY
#endif // End #ifndef LSResampler_h
// Declarations of classes that implements a hirearchy
// of models for long-time constant fields from eddy
// currents induced by diffusion gradients.
//
// LongECModels.h
//
// Jesper Andersson, FMRIB Image Analysis Group
//
// Copyright (C) 2022 University of Oxford
//
#include <cstdlib>
#include <string>
#include <vector>
#include <cmath>
#include <iostream>
#include <fstream>
#include <time.h>
#include "nlohmann/json.hpp"
#include "armawrap/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 "EddyHelperClasses.h"
#include "LongECModels.h"
#include "ECScanClasses.h"
using namespace EDDY;
// Initial value for eddy current time constant (half-life in seconds).
// small enough to result in little time dependence, but large enough to have a derivative
double const TC_INITIAL_VALUE = 0.1;
double const INTCPT_INITIAL_VALUE = 0.01;
//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
//
// Class EC_TimeAndIntercept2Weights
//
// This class translates an EC time constant and intercept to weights
//
//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
arma::colvec EC_TimeAndIntercept2Weights::GetWeightsForPrevious(double Thl,
double Intcpt) const EddyTry
{
arma::colvec weights(_mbg.NGroups());
for (int i=0; i<weights.n_rows; i++) {
weights(i) = Intcpt * std::exp(-(i)*(_TR/_mbg.NGroups())*std::log(2.0)/Thl);
}
return(weights);
} EddyCatch
arma::colvec EC_TimeAndIntercept2Weights::GetWeightsForCurrent(double Thl,
double Intcpt) const EddyTry
{
arma::colvec weights(_mbg.NGroups());
for (int i=0; i<weights.n_rows; i++) {
weights(i) = 1.0 - Intcpt * std::exp(-(i)*(_TR/_mbg.NGroups())*std::log(2.0)/Thl);
}
return(weights);
} EddyCatch
//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
//
// Class EC_TimeConst2Weights
//
// This class translates an EC time constant to weights
//
//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
arma::colvec EC_TimeConst2Weights::GetWeightsForPrevious(double Thl) const EddyTry
{
arma::colvec prev(_mbg.NGroups());
arma::colvec curr(_mbg.NGroups());
this->calculate_weights(Thl,prev,curr);
return(prev);
} EddyCatch
arma::colvec EC_TimeConst2Weights::GetWeightsForCurrent(double Thl) const EddyTry
{
arma::colvec prev(_mbg.NGroups());
arma::colvec curr(_mbg.NGroups());
this->calculate_weights(Thl,prev,curr);
return(curr);
} EddyCatch
void EC_TimeConst2Weights::calculate_weights(double Thl,
arma::colvec& prev,
arma::colvec& curr) const EddyTry
{
arma::colvec gtimes(_mbg.NGroups()); // Times at which diffusion gradients are played out
arma::colvec otimes(2*_mbg.NGroups()); // Times at which eddy currents are observed
arma::colvec all(2*_mbg.NGroups(),0.0); // The effects at all times in order [curr prev]
for (int i=0; i<gtimes.n_rows; i++) gtimes(i) = i*(_TR/_mbg.NGroups());
for (int i=0; i<otimes.n_rows; i++) otimes(i) = i*(_TR/_mbg.NGroups());
for (int i=0; i<otimes.n_rows; i++) {
for (int j=0; j<gtimes.n_rows; j++) {
if (gtimes(j)-otimes(i) > 1e-6) { // Only count contribution from gradients in the past
break;
}
all(i) += std::exp(-(otimes(i)-gtimes(j))*std::log(2.0)/Thl);
}
}
// Normalise the weights so that they are 1 for the end of current volume
auto nf = all(_mbg.NGroups()-1);
std::transform(all.begin(),all.end(),all.begin(),[nf](auto& c){return(c/nf);});
// Finally divide it up into prev and curr
curr = all.head_rows(_mbg.NGroups());
prev = all.tail_rows(_mbg.NGroups());
return;
} EddyCatch
//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
//
// Class IndividualWeightsModel
//
// This class models the weights directly on a volume-by-volume
// basis.
//
//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
IndividualWeightsModel::IndividualWeightsModel(ECScanManager& sm) EddyTry
: _nscans(sm.NScans()), _ngroups(sm.MultiBand().NGroups()),
_prev(sm.NScans(),arma::colvec(sm.MultiBand().NGroups(),arma::fill::zeros)),
_curr(sm.NScans(),arma::colvec(sm.MultiBand().NGroups(),arma::fill::ones)),
_ll(sm.NScans(),1e-6) // Start with very little regularisation
{
if (!sm.IsDiffusion()) throw EddyException("IndividualWeightsModel::IndividualWeightsModel: sm is not diffusion");
for (unsigned int s=0; s<sm.NScans(); s++) {
sm.Scan(s).set_previous(_prev[s]);
sm.Scan(s).set_current(_curr[s]);
}
} EddyCatch
double IndividualWeightsModel::GetDerivParam(const EDDY::ECScan& sc,
unsigned int i,
unsigned int s) const EddyTry
{
if (s >= _nscans) throw EddyException("IndividualWeightsModel::GetDerivParam: s is out of range");
if (i >= NDerivs()) throw EddyException("IndividualWeightsModel::GetDerivParam: i is out of range");
if (i < _ngroups) return(sc.GetWeightsForPrevious()(i));
else return(sc.GetWeightsForCurrent()(i-_ngroups));
} EddyCatch
double IndividualWeightsModel::GetDerivScale(unsigned int i) const EddyTry
{
if (i >= NDerivs()) throw EddyException("IndividualWeightsModel::GetDerivScale: i is out of range");
return(1e-5);
} EddyCatch
void IndividualWeightsModel::SetDerivParam(EDDY::ECScan& sc,
unsigned int i,
unsigned int s, // Not needed, but checked anyway
double val) const EddyTry
{
if (i >= NDerivs()) throw EddyException("IndividualWeightsModel::SetDerivParam: i is out of range");
if (s >= _nscans) throw EddyException("IndividualWeightsModel::SetDerivParam: s is out of range");
if (i < _ngroups) {
arma::colvec p = sc.GetWeightsForPrevious();
p(i) = val;
sc.set_previous(p);
}
else {
arma::colvec p = sc.GetWeightsForCurrent();
p(i-_ngroups) = val;
sc.set_current(p);
}
return;
} EddyCatch
double IndividualWeightsModel::GetLevenbergLambda(unsigned int s) const EddyTry {
if (s >= _nscans) throw EddyException("IndividualWeightsModel::GetLevenbergLambda: s is out of range");
return(_ll[s]);
} EddyCatch
void IndividualWeightsModel::SetLevenbergLambda(unsigned int s, double lambda) EddyTry {
if (s >= _nscans) throw EddyException("IndividualWeightsModel::SetLevenbergLambda: s is out of range");
_ll[s] = lambda;
return;
} EddyCatch
std::vector<arma::colvec> IndividualWeightsModel::GetParameters(const ECScanManager& sm) const EddyTry
{
// First verify that the parameters are consistent
for (unsigned int s=0; s<sm.NScans(); s++) {
if (!model_and_scan_agree(sm.Scan(s),s)) {
char message[256]; sprintf(message,"IndividualWeightsModel::GetParameters(sm): mismatch between model and scan %u",s);
throw EddyException(std::string(message));
}
}
std::vector<arma::colvec> rval(_nscans);
for (unsigned int s=0; s<_nscans; s++) rval[s] = arma::join_cols(_prev[s],_curr[s]);
return(rval);
} EddyCatch
arma::colvec IndividualWeightsModel::GetParameters(const ECScan& sc, unsigned int s) const EddyTry
{
// First verify that the parameters are consistent
if (s >= _nscans) throw EddyException("IndividualWeightsModel::GetParameters(sc,s): s out of bounds");
if (!model_and_scan_agree(sc,s)) {
char message[256]; sprintf(message,"IndividualWeightsModel::GetParameters(sc,s): mismatch between model and scan %u",s);
throw EddyException(std::string(message));
}
return(arma::join_cols(_prev[s],_curr[s]));
} EddyCatch
void IndividualWeightsModel::UpdateParameters(ECScanManager& sm, const std::vector<arma::colvec>& update) EddyTry
{
// First check agreement between model and update
if (update.size() != _nscans || update[0].n_rows != NDerivs()) {
throw EddyException("IndividualWeightsModel::UpdateParameters(sm,update): size mismatch between update and model");
}
// Next check agreement between model and scans
if (sm.NScans() != _nscans) throw EddyException("IndividualWeightsModel::UpdateParameters(sm,update): size mismatch between sm.NScans() and _nscans");
for (unsigned int s=0; s<sm.NScans(); s++) {
if (!model_and_scan_agree(sm.Scan(s),s)) {
char message[256]; sprintf(message,"IndividualWeightsModel::UpdateParameters(sm,update): mismatch between model and scan %u",s);
throw EddyException(std::string(message));
}
}
// Now update parameters
for (unsigned int s=0; s<sm.NScans(); s++) {
_prev[s] += update[s].head(_ngroups);
_curr[s] += update[s].tail(_ngroups);
sm.Scan(s).set_previous(_prev[s]);
sm.Scan(s).set_current(_curr[s]);
}
return;
} EddyCatch
void IndividualWeightsModel::UpdateParameters(ECScan& sc, unsigned int s, const arma::colvec& update) EddyTry
{
// First check agreement between model and update
if (update.n_rows != NDerivs()) {
throw EddyException("IndividualWeightsModel::UpdateParameters(sc,s,update): size mismatch between update and model");
}
if (s >= _nscans) throw EddyException("IndividualWeightsModel::UpdateParameters(sc,s,update): s out of bounds");
// Next check agreement between model and scans
if (!model_and_scan_agree(sc,s)) {
char message[256]; sprintf(message,"IndividualWeightsModel::UpdateParameters(sc,s,update): mismatch between model and scan %u",s);
throw EddyException(std::string(message));
}
// Now update parameters
_prev[s] += update.head(_ngroups);
_curr[s] += update.tail(_ngroups);
sc.set_previous(_prev[s]);
sc.set_current(_curr[s]);
return;
} EddyCatch
nlohmann::json IndividualWeightsModel::WriteReport(const ECScanManager& sm, const std::string& fname, bool write_old_style_file) const EddyTry
{
if (_nscans != sm.NScans()) throw EddyException("IndividualWeightsModel::WriteReport: Mismatch between _nscans and sm.NScans()");
nlohmann::json long_ec_json;
if (write_old_style_file) {
try {
std::ofstream fout;
fout.open(fname.c_str(),std::ios::out|std::ios::trunc);
fout << "Long time constant EC report for individual weights model. Two rows per volume." << std::endl;
fout << "The first row gives the weights for the EC-field from the previous diffusion gradient." << std::endl;
fout << "The second row gives the weights for the EC-field from the current diffusion gradient." << std::endl;
fout << "Each column represents the weight for one MB-group" << std::endl;
for (unsigned int s=0; s<_nscans; s++) {
arma::colvec par = this->GetParameters(sm.Scan(s),s);
arma::colvec previous = par.head_rows(_ngroups);
for (unsigned int c=0; c<_ngroups; c++) fout << previous(c) << " ";
fout << std::endl;
arma::colvec current = par.tail_rows(_ngroups);
for (unsigned int c=0; c<_ngroups; c++) fout << current(c) << " ";
fout << std::endl;
}
fout.close();
}
catch(...) { throw EddyException("IndividualWeightsModel::WriteReport: Error when attempting to write file " + fname); }
}
std::vector<nlohmann::json> volumes(_nscans);
for (unsigned int s=0; s<_nscans; s++) {
volumes[s]["Volume #"] = s;
arma::colvec par = this->GetParameters(sm.Scan(s),s);
volumes[s]["Previous"] = arma::conv_to<std::vector<double> >::from(par.head_rows(_ngroups));
volumes[s]["Current"] = arma::conv_to<std::vector<double> >::from(par.tail_rows(_ngroups));
}
long_ec_json["Model"] = "Individual weights";
long_ec_json["Format"] = "Long time-constant eddy currents was modelled as two sets of weights per volume,\nthe first is the weights of the EC field from the previous diffusion gradient\n and the second is the weights for the EC field for the current diffusion gradient.\nThe weights pertains to the MB-groups.";
long_ec_json["Volumes"] = volumes;
return(long_ec_json);
} EddyCatch
bool IndividualWeightsModel::model_and_scan_agree(const ECScan& sc, unsigned int s) const EddyTry
{
return(!(arma::any(sc.GetWeightsForPrevious() != _prev[s]) || arma::any(sc.GetWeightsForCurrent() != _curr[s])));
} EddyCatch
//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
//
// Class IndividualTimeConstantsModel
//
// This class models the time constants (half-life in seconds)
// on a volume-by-volume basis.
//
//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
IndividualTimeConstantsModel::IndividualTimeConstantsModel(ECScanManager& sm) EddyTry
: _nscans(sm.NScans()), _tai2w(sm.RepetitionTime(),sm.MultiBand()), _par(sm.NScans(),arma::colvec(4,arma::fill::none)),
_ll(sm.NScans(),0.1) // Start with a fair bit of regularisation
{
if (!sm.IsDiffusion()) throw EddyException("IndividualTimeConstantsModel::IndividualTimeConstantsModel: sm is not diffusion");
for (unsigned int s=0; s<_nscans; s++) {
_par[s](0) = TC_INITIAL_VALUE;
_par[s](1) = INTCPT_INITIAL_VALUE;
_par[s](2) = TC_INITIAL_VALUE;
_par[s](3) = INTCPT_INITIAL_VALUE;
}
arma::colvec prev = _tai2w.GetWeightsForPrevious(_par[0](0),_par[0](1));
arma::colvec curr = _tai2w.GetWeightsForCurrent(_par[0](2),_par[0](3));
std::cout << "IndividualTimeConstantsModel::IndividualTimeConstantsModel" << std::endl;
std::cout << "Previous:" << std::endl;
for (unsigned int i=0; i<prev.n_rows; i++) std::cout << prev(i) << std::endl;
std::cout << "Current:" << std::endl;
for (unsigned int i=0; i<prev.n_rows; i++) std::cout << curr(i) << std::endl;
for (unsigned int s=0; s<_nscans; s++) {
sm.Scan(s).set_previous(prev);
sm.Scan(s).set_current(curr);
}
} EddyCatch
double IndividualTimeConstantsModel::GetDerivParam(const EDDY::ECScan& sc, unsigned int i, unsigned int s) const EddyTry
{
if (i >= NDerivs()) throw EddyException("IndividualTimeConstantsModel::GetDerivParam: i out of bounds");
if (s >= _nscans) throw EddyException("IndividualTimeConstantsModel::GetDerivParam: s out of bounds");
return(_par[s](i));
} EddyCatch
double IndividualTimeConstantsModel::GetDerivScale(unsigned int i) const EddyTry
{
if (i >= NDerivs()) throw EddyException("IndividualTimeConstantsModel::GetDerivScale: i out of bounds");
if (i==0 || i==2) return(1e-3); // If it is half life
return(1e-4); // If it is intercept
// return(1e-3);
} EddyCatch
void IndividualTimeConstantsModel::SetDerivParam(ECScan& sc, unsigned int i, unsigned int s, double val) const
{
if (i >= NDerivs()) throw EddyException("IndividualTimeConstantsModel::SetDerivParam: i out of bounds");
if (s >= _nscans) throw EddyException("IndividualTimeConstantsModel::SetDerivParam: s out of bounds");
if (i<2) {
if (i==0) sc.set_previous(_tai2w.GetWeightsForPrevious(val,_par[s](1)));
else sc.set_previous(_tai2w.GetWeightsForPrevious(_par[s](0),val));
}
else {
if (i==2) sc.set_current(_tai2w.GetWeightsForCurrent(val,_par[s](3)));
else sc.set_current(_tai2w.GetWeightsForCurrent(_par[s](2),val));
}
}
double IndividualTimeConstantsModel::GetLevenbergLambda(unsigned int s) const EddyTry {
if (s >= _nscans) throw EddyException("IndividualTimeConstantsModel::GetLevenbergLambda: s is out of range");
return(_ll[s]);
} EddyCatch
void IndividualTimeConstantsModel::SetLevenbergLambda(unsigned int s, double lambda) EddyTry {
if (s >= _nscans) throw EddyException("IndividualTimeConstantsModel::SetLevenbergLambda: s is out of range");
_ll[s] = lambda;
return;
} EddyCatch
std::vector<arma::colvec> IndividualTimeConstantsModel::GetParameters(const ECScanManager& sm) const EddyTry
{
// First verify that the parameters are consistent
for (unsigned int s=0; s<sm.NScans(); s++) {
if (!model_and_scan_agree(sm.Scan(s),s)) {
char message[256]; sprintf(message,"IndividualTimeConstantsModel::GetParameters(sm): mismatch between model and scan %u",s);
throw EddyException(std::string(message));
}
}
return(_par);
} EddyCatch
arma::colvec IndividualTimeConstantsModel::GetParameters(const ECScan& sc, unsigned int s) const EddyTry
{
// First verify that the parameters are consistent
if (s >= _nscans) throw EddyException("IndividalTimeConstantsModel::GetParameters(sc,s): s out of bounds");
if (!model_and_scan_agree(sc,s)) {
char message[256]; sprintf(message,"IndividualTimeConstantsModel::GetParameters(sc,s): mismatch between model and scan %u",s);
throw EddyException(std::string(message));
}
return(_par[s]);
} EddyCatch
void IndividualTimeConstantsModel::UpdateParameters(ECScanManager& sm, const std::vector<arma::colvec>& update) EddyTry
{
// First check agreement between model and update
if (update.size() != _nscans || update[0].n_rows != NDerivs()) {
throw EddyException("IndividualTimeConstantsModel::UpdateParameters(sm,update): size mismatch between update and model");
}
// Next check agreement between model and scans
if (sm.NScans() != _nscans) throw EddyException("IndividualTimeConstantsModel::UpdateParameters(sm,update): size mismatch between sm.NScans() and _nscans");
for (unsigned int s=0; s<sm.NScans(); s++) {
if (!model_and_scan_agree(sm.Scan(s),s)) {
char message[256]; sprintf(message,"IndividalTimeConstantsModel::UpdateParameters(sm,update): mismatch between model and scan %u",s);
throw EddyException(std::string(message));
}
}
// Now update parameters
for (unsigned int s=0; s<sm.NScans(); s++) {
for (unsigned int i=0; i<NDerivs(); i++) _par[s](i) += update[s](i);
sm.Scan(s).set_previous(_tai2w.GetWeightsForPrevious(_par[s](0),_par[s](1)));
sm.Scan(s).set_current(_tai2w.GetWeightsForCurrent(_par[s](2),_par[s](3)));
}
return;
} EddyCatch
void IndividualTimeConstantsModel::UpdateParameters(ECScan& sc, unsigned int s, const arma::colvec& update) EddyTry
{
// First check agreement between model and update
if (update.n_rows != NDerivs()) {
throw EddyException("IndividalTimeConstantsModel::UpdateParameters(sc,s,update): size mismatch between update and model");
}
if (s >= _nscans) throw EddyException("IndividalTimeConstantsModel::UpdateParameters(sc,s,update): s out of bounds");
// Next check agreement between model and scans
if (!model_and_scan_agree(sc,s)) {
char message[256]; sprintf(message,"IndividalTimeConstantsModel::UpdateParameters(sc,s,update): mismatch between model and scan %u",s);
throw EddyException(std::string(message));
}
// Now update parameters
for (unsigned int i=0; i<NDerivs(); i++) _par[s](i) += update(i);
sc.set_previous(_tai2w.GetWeightsForPrevious(_par[s](0),_par[s](1)));
sc.set_current(_tai2w.GetWeightsForCurrent(_par[s](2),_par[s](3)));
return;
} EddyCatch
nlohmann::json IndividualTimeConstantsModel::WriteReport(const ECScanManager& sm, const std::string& fname, bool write_old_style_file) const EddyTry
{
if (_nscans != sm.NScans()) throw EddyException("IndividualTimeConstantsModel::WriteReport: Mismatch between _nscans and sm.NScans()");
if (write_old_style_file) {
try {
std::ofstream fout;
fout.open(fname.c_str(),std::ios::out|std::ios::trunc);
fout << "Long time constant EC report for individual time constants model. Three rows per volume." << std::endl;
fout << "The first row gives [TC Intercept] for previous, followed by [TC Intercept] for current." << std::endl;
fout << "The second row gives the weights for the EC-field from the previous diffusion gradient." << std::endl;
fout << "The third row gives the weights for the EC-field from the current diffusion gradient." << std::endl;
fout << "Each column of rows two and three represents the weight for one MB-group" << std::endl;
std::vector<arma::colvec> tc = this->GetParameters(sm);
for (unsigned int s=0; s<_nscans; s++) {
fout << tc[s](0) << " " << tc[s](1) << " " << tc[s](2) << " " << tc[s](3) << std::endl;
arma::colvec previous = sm.Scan(s).GetWeightsForPrevious();
for (unsigned int c=0; c<_tai2w.NGroups(); c++) fout << previous(c) << " ";
fout << std::endl;
arma::colvec current = sm.Scan(s).GetWeightsForCurrent();
for (unsigned int c=0; c<_tai2w.NGroups(); c++) fout << current(c) << " ";
fout << std::endl;
}
fout.close();
}
catch(...) { throw EddyException("IndividualTimeConstantsModel::WriteReport: Error when attempting to write file " + fname); }
}
nlohmann::json long_ec_json;
std::vector<nlohmann::json> volumes(_nscans);
for (unsigned int s=0; s<_nscans; s++) {
volumes[s]["Volume #"] = s;
volumes[s]["Half-life (s) Intercept for previous"] = arma::conv_to<std::vector<double> >::from(this->GetParameters(sm.Scan(s),s).rows(0,1));
volumes[s]["Half-life (s) Intercept for current"] = arma::conv_to<std::vector<double> >::from(this->GetParameters(sm.Scan(s),s).rows(2,3));
volumes[s]["Resulting weights for previous"] = arma::conv_to<std::vector<double> >::from(sm.Scan(s).GetWeightsForPrevious());
volumes[s]["Resulting weights for current"] = arma::conv_to<std::vector<double> >::from(sm.Scan(s).GetWeightsForCurrent());
}
long_ec_json["Model"] = "Individual time-constants";
long_ec_json["Format"] = "Long time-constant eddy currents was modelled as one time-constant (half-life in seconds) \nand one intercept for previous and current, resulting in four parameters per volume.\nThe resulting weights for the MB-groups are also given.";
long_ec_json["Volumes"] = volumes;
return(long_ec_json);
} EddyCatch
bool IndividualTimeConstantsModel::model_and_scan_agree(const ECScan& sc, unsigned int s) const EddyTry
{
return(!(arma::any(sc.GetWeightsForPrevious() != _tai2w.GetWeightsForPrevious(_par[s](0),_par[s](1))) ||
arma::any(sc.GetWeightsForCurrent() != _tai2w.GetWeightsForCurrent(_par[s](2),_par[s](3)))));
} EddyCatch
//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
//
// Class JointWeightsModel
//
// This class models the weights jointly across all volumes.
//
//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
JointWeightsModel::JointWeightsModel(ECScanManager& sm) EddyTry
: _nscans(sm.NScans()), _ngroups(sm.MultiBand().NGroups()),
_prev(arma::colvec(sm.MultiBand().NGroups(),arma::fill::zeros)), _curr(arma::colvec(sm.MultiBand().NGroups(),arma::fill::ones)),
_ll(1e-6) // Start with very little regularisation
{
if (!sm.IsDiffusion()) throw EddyException("JointWeightsModel::JointWeightsModel: sm is not diffusion");
for (unsigned int s=0; s<sm.NScans(); s++) {
sm.Scan(s).set_previous(_prev);
sm.Scan(s).set_current(_curr);
}
} EddyCatch
double JointWeightsModel::GetDerivParam(const EDDY::ECScan& sc,
unsigned int i,
unsigned int s) const EddyTry
{
if (s >= _nscans) throw EddyException("JointWeightsModel::GetDerivParam: s is out of range");
if (i >= NDerivs()) throw EddyException("JointWeightsModel::GetDerivParam: i is out of range");
if (i < _ngroups) return(sc.GetWeightsForPrevious()(i));
else return(sc.GetWeightsForCurrent()(i-_ngroups));
} EddyCatch
double JointWeightsModel::GetDerivScale(unsigned int i) const EddyTry
{
if (i >= NDerivs()) throw EddyException("JointWeightsModel::GetDerivScale: i is out of range");
return(1e-5);
} EddyCatch
void JointWeightsModel::SetDerivParam(EDDY::ECScan& sc,
unsigned int i,
unsigned int s, // Not needed, but checked anyway
double val) const EddyTry
{
if (i >= NDerivs()) throw EddyException("JointWeightsModel::GetDerivParam: i is out of range");
if (s >= _nscans) throw EddyException("JointWeightsModel::GetDerivParam: s is out of range");
if (i < _ngroups) {
arma::colvec p = sc.GetWeightsForPrevious();
p(i) = val;
sc.set_previous(p);
}
else {
arma::colvec p = sc.GetWeightsForCurrent();
p(i-_ngroups) = val;
sc.set_current(p);
}
return;
} EddyCatch
double JointWeightsModel::GetLevenbergLambda(unsigned int s) const EddyTry {
if (s >= _nscans) throw EddyException("JointWeightsModel::GetLevenbergLambda: s is out of range"); // Not needed, but do anyway
return(_ll);
} EddyCatch
void JointWeightsModel::SetLevenbergLambda(unsigned int s, double lambda) EddyTry {
if (s >= _nscans) throw EddyException("JointWeightsModel::SetLevenbergLambda: s is out of range"); // Not needed, but do anyway
_ll = lambda;
return;
} EddyCatch
std::vector<arma::colvec> JointWeightsModel::GetParameters(const ECScanManager& sm) const EddyTry
{
// First verify that the parameters are consistent
for (unsigned int s=0; s<sm.NScans(); s++) {
if (!model_and_scan_agree(sm.Scan(s),s)) {
char message[256]; sprintf(message,"JointWeightsModel::GetParameters(sm): mismatch between model and scan %u",s);
throw EddyException(std::string(message));
}
}
std::vector<arma::colvec> rval(1);
rval[0] = arma::join_cols(_prev,_curr);
return(rval);
} EddyCatch
arma::colvec JointWeightsModel::GetParameters(const ECScan& sc, unsigned int s) const EddyTry
{
// First verify that the parameters are consistent
if (s >= _nscans) throw EddyException("JointWeightsModel::GetParameters(sc,s): s out of bounds");
if (!model_and_scan_agree(sc,s)) {
char message[256]; sprintf(message,"JointWeightsModel::GetParameters(sc,s): mismatch between model and scan %u",s);
throw EddyException(std::string(message));
}
return(arma::join_cols(_prev,_curr));
} EddyCatch
void JointWeightsModel::UpdateParameters(ECScanManager& sm, const std::vector<arma::colvec>& update) EddyTry
{
// First check agreement between model and update
if (update.size() != 1 || update[0].n_rows != NDerivs()) {
throw EddyException("JointWeightsModel::UpdateParameters(sm,update): size mismatch between update and model");
}
// Next check agreement between model and scans
if (sm.NScans() != _nscans) throw EddyException("JointWeightsModel::UpdateParameters(sm,update): size mismatch between sm.NScans() and _nscans");
for (unsigned int s=0; s<sm.NScans(); s++) {
if (!model_and_scan_agree(sm.Scan(s),s)) {
char message[256]; sprintf(message,"JointWeightsModel::UpdateParameters(sm,update): mismatch between model and scan %u",s);
throw EddyException(std::string(message));
}
}
// Now update parameters
_prev += update[0].head(_ngroups);
_curr += update[0].tail(_ngroups);
for (unsigned int s=0; s<sm.NScans(); s++) {
sm.Scan(s).set_previous(_prev);
sm.Scan(s).set_current(_curr);
}
return;
} EddyCatch
void JointWeightsModel::UpdateParameters(ECScan& sc, unsigned int s, const arma::colvec& update) EddyTry
{
throw EddyException("JointWeightsModel::UpdateParameters(sc,s,update): not allowed to update individual scan");
return;
} EddyCatch
nlohmann::json JointWeightsModel::WriteReport(const ECScanManager& sm, const std::string& fname, bool write_old_style_file) const EddyTry
{
if (_nscans != sm.NScans()) throw EddyException("JointWeightsModel::WriteReport: Mismatch between _nscans and sm.NScans()");
if (write_old_style_file) {
try {
std::ofstream fout;
fout.open(fname.c_str(),std::ios::out|std::ios::trunc);
fout << "Long time constant EC report for joint weights model. Two rows." << std::endl;
fout << "The first row gives the weights for the EC-field from the previous diffusion gradient." << std::endl;
fout << "The second row gives the weights for the EC-field from the current diffusion gradient." << std::endl;
fout << "Each column represents the weight for one MB-group" << std::endl;
std::vector<arma::colvec> par = this->GetParameters(sm);
arma::colvec previous = par[0].head_rows(_ngroups);
for (unsigned int c=0; c<_ngroups; c++) fout << previous(c) << " ";
fout << std::endl;
arma::colvec current = par[0].tail_rows(_ngroups);
for (unsigned int c=0; c<_ngroups; c++) fout << current(c) << " ";
fout << std::endl;
fout.close();
}
catch(...) { throw EddyException("JointWeightsModel::WriteReport: Error when attempting to write file " + fname); }
}
nlohmann::json long_ec_json;
long_ec_json["Model"] = "Joint weights";
long_ec_json["Format"] = "Long time-constant eddy currents was modelled as two sets of weights,\nthe first is the weights of the EC field from the previous diffusion gradient\n and the second is the weights for the EC field for the current diffusion gradient.\nThe weights pertains to the MB-groups and are pooled across all volumes.";
std::vector<arma::colvec> par = this->GetParameters(sm);
long_ec_json["Previous"] = arma::conv_to<std::vector<double> >::from(par[0].head_rows(_ngroups));
long_ec_json["Current"] = arma::conv_to<std::vector<double> >::from(par[0].tail_rows(_ngroups));
return(long_ec_json);
} EddyCatch
bool JointWeightsModel::model_and_scan_agree(const ECScan& sc, unsigned int s) const EddyTry
{
return(!(arma::any(sc.GetWeightsForPrevious() != _prev) || arma::any(sc.GetWeightsForCurrent() != _curr)));
} EddyCatch
//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
//
// Class JointTimeConstantModel
//
// This class models the time-constant (half-life in seconds)
// jointly across all volumes.
//
//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
JointTimeConstantModel::JointTimeConstantModel(ECScanManager& sm) EddyTry
: _nscans(sm.NScans()), _tai2w(sm.RepetitionTime(),sm.MultiBand()), _par(arma::colvec(4,arma::fill::none)),
_ll(0.1) // Start with a fair bit of regularisation
{
if (!sm.IsDiffusion()) throw EddyException("JointTimeConstantsModel::JointTimeConstantsModel: sm is not diffusion");
_par(0) = TC_INITIAL_VALUE;
_par(1) = INTCPT_INITIAL_VALUE;
_par(2) = TC_INITIAL_VALUE;
_par(3) = INTCPT_INITIAL_VALUE;
arma::colvec prev = _tai2w.GetWeightsForPrevious(_par(0),_par(1));
arma::colvec curr = _tai2w.GetWeightsForCurrent(_par(2),_par(3));
for (unsigned int s=0; s<sm.NScans(); s++) {
sm.Scan(s).set_previous(prev);
sm.Scan(s).set_current(curr);
}
} EddyCatch
double JointTimeConstantModel::GetDerivParam(const EDDY::ECScan& sc, unsigned int i, unsigned int s) const EddyTry
{
if (i >= NDerivs()) throw EddyException("JointTimeConstantModel::GetDerivParam: i out of bounds");
if (s >= _nscans) throw EddyException("JointTimeConstantModel::GetDerivParam: s out of bounds");
return(_par(i));
} EddyCatch
double JointTimeConstantModel::GetDerivScale(unsigned int i) const EddyTry
{
if (i >= NDerivs()) throw EddyException("JointTimeConstantModel::GetDerivScale: i out of bounds");
return(1e-5);
} EddyCatch
void JointTimeConstantModel::SetDerivParam(ECScan& sc, unsigned int i, unsigned int s, double val) const
{
if (i >= NDerivs()) throw EddyException("JointTimeConstantModel::SetDerivParam: i out of bounds");
if (s >= _nscans) throw EddyException("JointTimeConstantModel::SetDerivParam: s out of bounds");
if (i < 2) {
if (i==0) sc.set_previous(_tai2w.GetWeightsForPrevious(val,_par(1)));
else sc.set_previous(_tai2w.GetWeightsForPrevious(_par(0),val));
}
else {
if (i==2) sc.set_current(_tai2w.GetWeightsForCurrent(val,_par(3)));
else sc.set_current(_tai2w.GetWeightsForCurrent(_par(2),val));
}
}
double JointTimeConstantModel::GetLevenbergLambda(unsigned int s) const EddyTry {
if (s >= _nscans) throw EddyException("JointTimeConstantModel::GetLevenbergLambda: s is out of range"); // Not needed, but do anyway
return(_ll);
} EddyCatch
void JointTimeConstantModel::SetLevenbergLambda(unsigned int s, double lambda) EddyTry {
if (s >= _nscans) throw EddyException("JointTimeConstantModel::SetLevenbergLambda: s is out of range"); // Not needed, but do anyway
_ll = lambda;
return;
} EddyCatch
std::vector<arma::colvec> JointTimeConstantModel::GetParameters(const ECScanManager& sm) const EddyTry
{
// First verify that the parameters are consistent
for (unsigned int s=0; s<sm.NScans(); s++) {
if (!model_and_scan_agree(sm.Scan(s),s)) {
char message[256]; sprintf(message,"JointTimeConstantModel::GetParameters(sm): mismatch between model and scan %u",s);
throw EddyException(std::string(message));
}
}
return(std::vector<arma::colvec>(1,_par));
} EddyCatch
arma::colvec JointTimeConstantModel::GetParameters(const ECScan& sc, unsigned int s) const EddyTry
{
// First verify that the parameters are consistent
if (s >= _nscans) throw EddyException("JointTimeConstantModel::GetParameters(sc,s): s out of bounds");
if (!model_and_scan_agree(sc,s)) {
char message[256]; sprintf(message,"JointTimeConstantModel::GetParameters(sc,s): mismatch between model and scan %u",s);
throw EddyException(std::string(message));
}
return(_par);
} EddyCatch
void JointTimeConstantModel::UpdateParameters(ECScanManager& sm, const std::vector<arma::colvec>& update) EddyTry
{
// First check agreement between model and update
if (update.size() != 1 || update[0].n_rows != NDerivs()) {
throw EddyException("JointTimeConstantModel::UpdateParameters(sm,update): size mismatch between update and model");
}
// Next check agreement between model and scans
if (sm.NScans() != _nscans) throw EddyException("JointTimeConstantModel::UpdateParameters(sm,update): size mismatch between sm.NScans() and _nscans");
for (unsigned int s=0; s<sm.NScans(); s++) {
if (!model_and_scan_agree(sm.Scan(s),s)) {
char message[256]; sprintf(message,"JointTimeConstantModel::UpdateParameters(sm,update): mismatch between model and scan %u",s);
throw EddyException(std::string(message));
}
}
// Now update parameters
for (unsigned int i=0; i<NDerivs(); i++) _par(i) += update[0](i);
for (unsigned int s=0; s<sm.NScans(); s++) {
sm.Scan(s).set_previous(_tai2w.GetWeightsForPrevious(_par(0),_par(1)));
sm.Scan(s).set_current(_tai2w.GetWeightsForCurrent(_par(2),_par(3)));
}
return;
} EddyCatch
void JointTimeConstantModel::UpdateParameters(ECScan& sc, unsigned int s, const arma::colvec& update) EddyTry
{
throw EddyException("JointTimeConstantModel::UpdateParameters(sc,s,update): not allowed to update individual scan");
return;
} EddyCatch
nlohmann::json JointTimeConstantModel::WriteReport(const ECScanManager& sm, const std::string& fname, bool write_old_style_file) const EddyTry
{
if (_nscans != sm.NScans()) throw EddyException("JointTimeConstantModel::WriteReport: Mismatch between _nscans and sm.NScans()");
if (write_old_style_file) {
try {
std::ofstream fout;
fout.open(fname.c_str(),std::ios::out|std::ios::trunc);
fout << "Long time constant EC report for joint time constant model. Three rows." << std::endl;
fout << "The first row gives [TC Intercept] for previous, followed by [TC Intercept] for current." << std::endl;
fout << "The second row gives the resulting weights for the EC-field from the previous diffusion gradient." << std::endl;
fout << "The third row gives the resulting weights for the EC-field from the current diffusion gradient." << std::endl;
fout << "Each column represents the weight for one MB-group" << std::endl;
std::vector<arma::colvec> tc = this->GetParameters(sm);
fout << tc[0](0) << " " << tc[0](1) << " " << tc[0](2) << " " << tc[0](3) << std::endl;
arma::colvec previous = sm.Scan(0).GetWeightsForPrevious();
for (unsigned int c=0; c<_tai2w.NGroups(); c++) fout << previous(c) << " ";
fout << std::endl;
arma::colvec current = sm.Scan(0).GetWeightsForCurrent();
for (unsigned int c=0; c<_tai2w.NGroups(); c++) fout << current(c) << " ";
fout << std::endl;
fout.close();
}
catch(...) { throw EddyException("JointTimeConstantModel::WriteReport: Error when attempting to write file " + fname); }
}
nlohmann::json long_ec_json;
long_ec_json["Model"] = "Joint time-constant";
long_ec_json["Format"] = "Long time-constant eddy currents was modelled as a single time-constant (half-life in seconds) across all volumes.\nThe resulting weights for the MB-groups are also given.";
long_ec_json["Half-life (s) Intercept for previous"] = arma::conv_to<std::vector<double> >::from(this->GetParameters(sm)[0].rows(0,1));
long_ec_json["Half-life (s) Intercept for current"] = arma::conv_to<std::vector<double> >::from(this->GetParameters(sm)[0].rows(2,3));
long_ec_json["Resulting weights for previous"] = arma::conv_to<std::vector<double> >::from(sm.Scan(0).GetWeightsForPrevious());
long_ec_json["Resulting weights for current"] = arma::conv_to<std::vector<double> >::from(sm.Scan(0).GetWeightsForCurrent());
return(long_ec_json);
} EddyCatch
bool JointTimeConstantModel::model_and_scan_agree(const ECScan& sc, unsigned int s) const EddyTry
{
return(!(arma::any(sc.GetWeightsForPrevious() != _tai2w.GetWeightsForPrevious(_par(0),_par(1))) ||
arma::any(sc.GetWeightsForCurrent() != _tai2w.GetWeightsForCurrent(_par(2),_par(3)))));
} EddyCatch
nlohmann::json NoLongECModel::WriteReport(const ECScanManager& sm, const std::string& fname, bool write_old_style_file) const EddyTry {
nlohmann::json long_ec_json;
long_ec_json["Model"] = "None";
long_ec_json["Format"] = "No modelling of long time-constant eddy currents was performed.";
return(long_ec_json);
} EddyCatch
/*! \file ECModels.h
\brief Contains declaration of classes that implements models for long time-constant eddy currents.
\author Jesper Andersson
\version 1.0b, Oct., 2022.
*/
// Declarations of classes that implements a hirearchy
// of models for long-time constant fields from eddy
// currents induced by diffusion gradients.
//
// LongECModels.h
//
// Jesper Andersson, FMRIB Image Analysis Group
//
// Copyright (C) 2022 University of Oxford
//
#ifndef LongECModels_h
#define LongECModels_h
#include <cstdlib>
#include <string>
#include <vector>
#include <cmath>
#include <memory>
#include "armawrap/newmat.h"
#include "newimage/newimageall.h"
#include "miscmaths/miscmaths.h"
#include "EddyHelperClasses.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
namespace EDDY {
/****************************************************************//**
*
* \brief Translates an EC time constant and intercept to weights
*
* Given TR, an EDDY::MultiBandGroup object and parameters
* consisting of time-constant (half-life in seconds), and
* scale factor/intercept it will return a vector of weights
* for the current and the following volume eddy current-induced
* field.
* This is a helper class for two of the models below.
*
********************************************************************/
class EC_TimeAndIntercept2Weights
{
public:
EC_TimeAndIntercept2Weights(double TR, const MultiBandGroups& mbg) EddyTry : _TR(TR), _mbg(mbg) { } EddyCatch
/// Get weights for EC-field from previous volume,
arma::colvec GetWeightsForPrevious(double Thl, // Half-life in seconds
double Intcpt) const; // Intercept
/// Get weights for EC field from current volume.
arma::colvec GetWeightsForCurrent(double Thl, // Half-life in seconds
double Intcpt) const; // Intercept
/// Return the number of MB-groups
unsigned int NGroups() const EddyTry { return(_mbg.NGroups()); } EddyCatch
private:
const double _TR; /// Repetition time
const MultiBandGroups _mbg; /// Multi-band information
};
/****************************************************************//**
*
* \brief This class translates an EC time constant to weights
*
* Given TR, an EDDY::MultiBandGroup object and a time-constant
* (half-life in seconds) of the eddy currents it will return
* a vector of weights for the previous and the current volume
* eddy current-induced field. This is a helper class for two of
* the models below.
*
* It is currently retired in favour of the model above.
*
********************************************************************/
class EC_TimeConst2Weights
{
public:
EC_TimeConst2Weights(double TR, const MultiBandGroups& mbg) EddyTry : _TR(TR), _mbg(mbg) { } EddyCatch
/// Get weights for EC field from previous volume. Thl: Half life in seconds
arma::colvec GetWeightsForPrevious(double Thl) const;
/// Get weights for EC field from current volume. Thl: Half life in seconds
arma::colvec GetWeightsForCurrent(double Thl) const;
/// Return the number of MB-groups
unsigned int NGroups() const EddyTry { return(_mbg.NGroups()); } EddyCatch
private:
const double _TR; /// Repetition time
const MultiBandGroups _mbg; /// Multi-band information
void calculate_weights(double Thl, // Half life in seconds
arma::colvec& prev, // Weights for EC from previous volume
arma::colvec& curr) const; // Weights for EC from current volume
};
/****************************************************************//**
*
* \brief This class manages the parameters for the long time-constant
* (time varying) eddy currents.
*
* This class manages the parameters for the long time-constant
* (time varying) eddy currents. I have chosen to _not_ use the
* strategy of a virtual base class, as I do with ECModel below,
* and instead have if-else blocks in the methods. This is because
* I want some of the methods to be friends of ECScan, and I don't
* want to have to friend declare every new subclass.
*
* All the Long EC models are based on the same assumption that
* the first few MB-groups of a volume experiences residual EC
* from the previous volume, and also that those same first few
* volumes does not yet experince the full EC-field from the
* diffusion gradient of the current volume.
* There are four concrete subclasses of the pure virtual
* parent class. They differ in if they model the weights directly
* or through a model with a time-constant. They also differ in
* if they model the different volumes individually, or if they
* assume that the effects are the same across all volumes.
*
********************************************************************/
class ECScan; // Forward declaration to allow us to declare ECScan as parameter to functions
class ECScanManager; // Forward declaration to allow us to declare ECScanManager as parameter to functions
class LongECModel
{
public:
LongECModel() EddyTry {} EddyCatch
~LongECModel() {}
/// Returns the model
virtual LongECModelType Model() const = 0;
/// Returns true for models that estimate individual parameters for each volume
virtual bool IsIndividual() const = 0;
/// Returns true for models that estimate joint parameters for all volumes
virtual bool IsJoint() const = 0;
/// Returns true if model estimates weights directly
virtual bool EstimatesWeights() const = 0;
/// Returns true if model estimates time constant/constants
virtual bool EstimatesTimeConst() const = 0;
/// Returns the number of MB-groups
virtual unsigned int NGroups() const = 0;
/// Return number of derivatives for a single scan
virtual int NDerivs() const = 0;
/// Gets the value of a single parameter (which has derivative) for scan sc.
/// It is intended for use on temporary copies of the scan sc, so this function
/// does not check for agreement between the scan and the model.
virtual double GetDerivParam(const EDDY::ECScan& sc, unsigned int i, unsigned int s) const = 0;
/// Gets suitable scale for numerical derivative for parameter i
virtual double GetDerivScale(unsigned int i) const = 0;
/// Sets the value of a single parameter (which has a derivative) for scan sc
/// It is intended for use on temporary copies of the scan sc, so this function
/// does not set the corresponding parameter in the model, only in the scan.
virtual void SetDerivParam(ECScan& sc, unsigned int i, unsigned int s, double val) const = 0;
/// Get weight for diagonal of Hessian
virtual double GetLevenbergLambda(unsigned int s) const = 0;
/// Set weight for diagonal of Hessian
virtual void SetLevenbergLambda(unsigned int s, double lambda) = 0;
/// Returns all the parameters of the model
virtual std::vector<arma::colvec> GetParameters(const ECScanManager& sm) const = 0;
/// Returns the parameters for a single volume
/// N.B. that the index i implicitly assumes ScanType st==ScanType::Any
virtual arma::colvec GetParameters(const ECScan& sc, unsigned int s) const = 0;
/// Updates all the parameters of the model. This is the method that should be used for setting.
virtual void UpdateParameters(ECScanManager& sm, const std::vector<arma::colvec>& update) = 0;
/// Updates the parameters for a single volume. This is the method to use when rolling back updates on individual volumes.
virtual void UpdateParameters(ECScan& sc, unsigned int s, const arma::colvec& update) = 0;
/// Return a json-struct report and (optionally) write a plain ascii text file
virtual nlohmann::json WriteReport(const ECScanManager& sm, const std::string& fname, bool write_old_style_file) const = 0;
};
//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
//
// Class IndividualWeightsModel
//
// This class models the weights directly on a volume-by-volume
// basis.
//
//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
class IndividualWeightsModel : public LongECModel
{
public:
IndividualWeightsModel(ECScanManager& sm);
~IndividualWeightsModel() {}
/// Returns the model
LongECModelType Model() const EddyTry { return(LongECModelType::Individual); } EddyCatch
/// Returns true for models that estimate individual parameters for each volume
bool IsIndividual() const EddyTry { return(true); } EddyCatch
/// Returns true for models that estimate joint parameters for all volumes
bool IsJoint() const EddyTry { return(!IsIndividual()); } EddyCatch
/// Returns true if model estimates weights directly
bool EstimatesWeights() const EddyTry { return(true); } EddyCatch
/// Returns true if model estimates time constant/constants
bool EstimatesTimeConst() const EddyTry { return(!EstimatesWeights()); } EddyCatch
/// Returns the number of MB-groups
unsigned int NGroups() const EddyTry { return(_ngroups); } EddyCatch
/// Return number of derivatives for a single scan
int NDerivs() const EddyTry { return(2*_ngroups); } EddyCatch
/// Gets the value of a single parameter (which has derivative) for scan sc.
/// It is intended for use on temporary copies of the scan sc, so this function
/// does not check for agreement between the scan and the model.
double GetDerivParam(const EDDY::ECScan& sc, unsigned int i, unsigned int s) const;
/// Gets suitable scale for numerical derivative for parameter i
double GetDerivScale(unsigned int i) const;
/// It is intended for use on temporary copies of the scan sc, so this function
/// does not set the corresponding parameter in the model, only in the scan.
/// Sets the value of a single parameter (which has a derivative) for scan sc
void SetDerivParam(ECScan& sc, unsigned int i, unsigned int s, double val) const;
/// Get weight for diagonal of Hessian
double GetLevenbergLambda(unsigned int s) const;
/// Set weight for diagonal of Hessian
void SetLevenbergLambda(unsigned int s, double lambda);
/// Returns all the parameters of the model
std::vector<arma::colvec> GetParameters(const ECScanManager& sm) const;
/// Returns the parameters for a single volume
/// N.B. that the index i implicitly assumes ScanType st==ScanType::Any
arma::colvec GetParameters(const ECScan& sc, unsigned int s) const;
/// Updates all the parameters of the model. N.B. that "update" means that the update is added to the current parameters.
void UpdateParameters(ECScanManager& sm, const std::vector<arma::colvec>& update);
/// Updates the parameters for a single volume. This is the method to use when rolling back updates on individual volumes.
/// N.B. that "update" means that the update is added to the current parameters.
/// N.B. that the index s implicitly assumes ScanType st==ScanType::Any
void UpdateParameters(ECScan& sc, unsigned int s, const arma::colvec& update);
/// Return a json-struct report and (optionally) write a plain ascii text file
nlohmann::json WriteReport(const ECScanManager& sm, const std::string& fname, bool write_old_style_file) const;
private:
unsigned int _nscans;
unsigned int _ngroups;
std::vector<arma::colvec> _prev;
std::vector<arma::colvec> _curr;
std::vector<double> _ll; // Levenberg lambda
bool model_and_scan_agree(const ECScan& sc, unsigned int s) const;
};
//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
//
// Class IndividualTimeConstantsModel
//
// This class models the time constants (half-life in seconds)
// and intercept on a volume-by-volume basis.
//
//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
class IndividualTimeConstantsModel : public LongECModel
{
public:
IndividualTimeConstantsModel(ECScanManager& sm);
~IndividualTimeConstantsModel() {}
/// Returns the model
LongECModelType Model() const EddyTry { return(LongECModelType::IndividualTimeConstant); } EddyCatch
/// Returns true for models that estimate individual parameters for each volume
bool IsIndividual() const EddyTry { return(true); } EddyCatch
/// Returns true for models that estimate joint parameters for all volumes
bool IsJoint() const EddyTry { return(!IsIndividual()); } EddyCatch
/// Returns true if model estimates weights directly
bool EstimatesWeights() const EddyTry { return(false); } EddyCatch
/// Returns true if model estimates time constant/constants
bool EstimatesTimeConst() const EddyTry { return(!EstimatesWeights()); } EddyCatch
/// Returns the number of MB-groups
unsigned int NGroups() const EddyTry { return(_tai2w.NGroups()); } EddyCatch
/// Return number of derivatives for a single scan
int NDerivs() const EddyTry { return(4); } EddyCatch
/// Gets the value of a single parameter (which has derivative) for scan sc.
/// It is intended for use on temporary copies of the scan sc, so this function
/// does not check for agreement between the scan and the model.
double GetDerivParam(const EDDY::ECScan& sc, unsigned int i, unsigned int s) const;
/// Gets suitable scale for numerical derivative for parameter i
double GetDerivScale(unsigned int i) const;
/// Sets the value of a single parameter (which has a derivative) for scan sc
/// It is intended for use on temporary copies of the scan sc, so this function
/// does not set the corresponding parameter in the model, only in the scan.
void SetDerivParam(ECScan& sc, unsigned int i, unsigned int s, double val) const;
/// Get weight for diagonal of Hessian
double GetLevenbergLambda(unsigned int s) const;
/// Set weight for diagonal of Hessian
void SetLevenbergLambda(unsigned int s, double lambda);
/// Returns all the parameters of the model
std::vector<arma::colvec> GetParameters(const ECScanManager& sm) const;
/// Returns the parameters for a single volume
/// N.B. that the index i implicitly assumes ScanType st==ScanType::Any
arma::colvec GetParameters(const ECScan& sc, unsigned int s) const;
/// Updates all the parameters of the model. N.B. that "update" means that the update is added to the current parameters.
void UpdateParameters(ECScanManager& sm, const std::vector<arma::colvec>& update);
/// Updates the parameters for a single volume. This is the method to use when rolling back updates on individual volumes.
/// N.B. that "update" means that the update is added to the current parameters.
/// N.B. that the index s implicitly assumes ScanType st==ScanType::Any
void UpdateParameters(ECScan& sc, unsigned int s, const arma::colvec& update);
/// Return a json-struct report and (optionally) write a plain ascii text file
nlohmann::json WriteReport(const ECScanManager& sm, const std::string& fname, bool write_old_style_file) const;
private:
unsigned int _nscans;
EC_TimeAndIntercept2Weights _tai2w;
std::vector<arma::colvec> _par; // Vector of [previous[TC Intercept] current[TC Intercept]]]
std::vector<double> _ll; // Levenberg lambda
bool model_and_scan_agree(const ECScan& sc, unsigned int s) const;
};
//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
//
// Class JointWeightsModel
//
// This class models the weights jointly across all volumes.
//
//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
class JointWeightsModel : public LongECModel
{
public:
JointWeightsModel(ECScanManager& sm);
~JointWeightsModel() {}
/// Returns the model
LongECModelType Model() const EddyTry { return(LongECModelType::Joint); } EddyCatch
/// Returns true for models that estimate individual parameters for each volume
bool IsIndividual() const EddyTry { return(false); } EddyCatch
/// Returns true for models that estimate joint parameters for all volumes
bool IsJoint() const EddyTry { return(!IsIndividual()); } EddyCatch
/// Returns true if model estimates weights directly
bool EstimatesWeights() const EddyTry { return(true); } EddyCatch
/// Returns true if model estimates time constant/constants
bool EstimatesTimeConst() const EddyTry { return(!EstimatesWeights()); } EddyCatch
/// Returns the number of MB-groups
unsigned int NGroups() const EddyTry { return(_ngroups); } EddyCatch
/// Return number of derivatives for a single scan
int NDerivs() const EddyTry { return(2*_ngroups); } EddyCatch
/// Gets the value of a single parameter (which has derivative) for scan sc.
/// It is intended for use on temporary copies of the scan sc, so this function
/// does not check for agreement between the scan and the model.
double GetDerivParam(const EDDY::ECScan& sc, unsigned int i, unsigned int s) const;
/// Gets suitable scale for numerical derivative for parameter i
double GetDerivScale(unsigned int i) const;
/// Sets the value of a single parameter (which has a derivative) for scan sc
/// It is intended for use on temporary copies of the scan sc, so this function
/// does not set the corresponding parameter in the model, only in the scan.
void SetDerivParam(ECScan& sc, unsigned int i, unsigned int s, double val) const;
/// Get weight for diagonal of Hessian
double GetLevenbergLambda(unsigned int s) const;
/// Set weight for diagonal of Hessian
void SetLevenbergLambda(unsigned int s, double lambda);
/// Returns all the parameters of the model
std::vector<arma::colvec> GetParameters(const ECScanManager& sm) const;
/// Returns the parameters for a single volume
/// N.B. that the index i implicitly assumes ScanType st==ScanType::Any
arma::colvec GetParameters(const ECScan& sc, unsigned int s) const;
/// Updates all the parameters of the model. N.B. that "update" means that the update is added to the current parameters.
void UpdateParameters(ECScanManager& sm, const std::vector<arma::colvec>& update);
/// Updates the parameters for a single volume. This is the method to use when rolling back updates on individual volumes.
/// N.B. that "update" means that the update is added to the current parameters.
/// N.B. that the index s implicitly assumes ScanType st==ScanType::Any
void UpdateParameters(ECScan& sc, unsigned int s, const arma::colvec& update);
/// Return a json-struct report and (optionally) write a plain ascii text file
nlohmann::json WriteReport(const ECScanManager& sm, const std::string& fname, bool write_old_style_file) const;
private:
unsigned int _nscans;
unsigned int _ngroups;
arma::colvec _prev;
arma::colvec _curr;
double _ll; // Levenberg lambda
bool model_and_scan_agree(const ECScan& sc, unsigned int s) const;
};
//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
//
// Class JointTimeConstantModel
//
// This class models the time-constant (half-life in seconds)
// jointly across all volumes.
//
//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
class JointTimeConstantModel : public LongECModel
{
public:
JointTimeConstantModel(ECScanManager& sm);
~JointTimeConstantModel() {}
/// Returns the model
LongECModelType Model() const EddyTry { return(LongECModelType::JointTimeConstant); } EddyCatch
/// Returns true for models that estimate individual parameters for each volume
bool IsIndividual() const EddyTry { return(false); } EddyCatch
/// Returns true for models that estimate joint parameters for all volumes
bool IsJoint() const EddyTry { return(!IsIndividual()); } EddyCatch
/// Returns true if model estimates weights directly
bool EstimatesWeights() const EddyTry { return(false); } EddyCatch
/// Returns true if model estimates time constant/constants
bool EstimatesTimeConst() const EddyTry { return(!EstimatesWeights()); } EddyCatch
/// Returns the number of MB-groups
unsigned int NGroups() const EddyTry { return(_tai2w.NGroups()); } EddyCatch
/// Return number of derivatives for a single scan
int NDerivs() const EddyTry { return(1); } EddyCatch
/// Gets the value of a single parameter (which has derivative) for scan sc.
/// It is intended for use on temporary copies of the scan sc, so this function
/// does not check for agreement between the scan and the model.
double GetDerivParam(const EDDY::ECScan& sc, unsigned int i, unsigned int s) const;
/// Gets suitable scale for numerical derivative for parameter i
double GetDerivScale(unsigned int i) const;
/// Sets the value of a single parameter (which has a derivative) for scan sc
/// It is intended for use on temporary copies of the scan sc, so this function
/// does not set the corresponding parameter in the model, only in the scan.
void SetDerivParam(ECScan& sc, unsigned int i, unsigned int s, double val) const;
/// Get weight for diagonal of Hessian
double GetLevenbergLambda(unsigned int s) const;
/// Set weight for diagonal of Hessian
void SetLevenbergLambda(unsigned int s, double lambda);
/// Returns all the parameters of the model
std::vector<arma::colvec> GetParameters(const ECScanManager& sm) const;
/// Returns the parameters for a single volume
/// N.B. that the index i implicitly assumes ScanType st==ScanType::Any
arma::colvec GetParameters(const ECScan& sc, unsigned int s) const;
/// Updates all the parameters of the model. N.B. that "update" means that the update is added to the current parameters.
void UpdateParameters(ECScanManager& sm, const std::vector<arma::colvec>& update);
/// Updates the parameters for a single volume. This is the method to use when rolling back updates on individual volumes.
/// N.B. that "update" means that the update is added to the current parameters.
/// N.B. that the index s implicitly assumes ScanType st==ScanType::Any
void UpdateParameters(ECScan& sc, unsigned int s, const arma::colvec& update);
/// Return a json-struct report and (optionally) write a plain ascii text file
nlohmann::json WriteReport(const ECScanManager& sm, const std::string& fname, bool write_old_style_file) const;
private:
unsigned int _nscans;
EC_TimeAndIntercept2Weights _tai2w;
arma::colvec _par; // previous[TC Intercept] current[TC Intercept]
double _ll; // Levenberg lambda
bool model_and_scan_agree(const ECScan& sc, unsigned int s) const;
};
//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
//
// Class NoLongECModel
//
// This is a "null-model" for the cases where we don't want to
// model long eddy currents.
//
//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
class NoLongECModel : public LongECModel
{
public:
NoLongECModel() {}
~NoLongECModel() {}
/// Returns the model
LongECModelType Model() const EddyTry { return(LongECModelType::None); } EddyCatch
/// Returns true for models that estimate individual parameters for each volume
bool IsIndividual() const EddyTry { return(false); } EddyCatch
/// Returns true for models that estimate joint parameters for all volumes
bool IsJoint() const EddyTry { return(false); } EddyCatch
/// Returns true if model estimates weights directly
bool EstimatesWeights() const EddyTry { return(false); } EddyCatch
/// Returns true if model estimates time constant/constants
bool EstimatesTimeConst() const EddyTry { return(false); } EddyCatch
/// Returns the number of MB-groups
unsigned int NGroups() const EddyTry {
throw EddyException("NoLongECModel::NGroups: unknown number of groups");
} EddyCatch
/// Return number of derivatives for a single scan
int NDerivs() const EddyTry { return(0); } EddyCatch
/// Gets the value of a single parameter (which has derivative) for scan sc.
/// It is intended for use on temporary copies of the scan sc, so this function
/// does not check for agreement between the scan and the model.
double GetDerivParam(const EDDY::ECScan& sc, unsigned int i, unsigned int s) const EddyTry {
throw EddyException("NoLongECModel::GetDerivParam: model has no parameters");
} EddyCatch
/// Gets suitable scale for numerical derivative for parameter i
double GetDerivScale(unsigned int i) const EddyTry {
throw EddyException("NoLongECModel::GetDerivScale: model has no parameters");
} EddyCatch
/// Sets the value of a single parameter (which has a derivative) for scan sc
/// It is intended for use on temporary copies of the scan sc, so this function
/// does not set the corresponding parameter in the model, only in the scan.
void SetDerivParam(ECScan& sc, unsigned int i, unsigned int s, double val) const EddyTry {
throw EddyException("NoLongECModel::SetDerivParam: model has no parameters");
} EddyCatch
/// Get weight for diagonal of Hessian
double GetLevenbergLambda(unsigned int s) const EddyTry {
throw EddyException("NoLongECModel::GetLevenbergLambda: model has no lambda");
} EddyCatch
/// Set weight for diagonal of Hessian
virtual void SetLevenbergLambda(unsigned int s, double lambda) EddyTry {
throw EddyException("NoLongECModel::SetLevenbergLambda: model has no lambda");
} EddyCatch
/// Returns all the parameters of the model
std::vector<arma::colvec> GetParameters(const ECScanManager& sm) const EddyTry {
throw EddyException("NoLongECModel::GetParameters(sm): model has no parameters");
} EddyCatch
/// Returns the parameters for a single volume
/// N.B. that the index i implicitly assumes ScanType st==ScanType::Any
arma::colvec GetParameters(const ECScan& sc, unsigned int s) const EddyTry {
throw EddyException("NoLongECModel::GetParameters(sc,s): model has no parameters");
} EddyCatch
/// Updates all the parameters of the model. N.B. that "update" means that the update is added to the current parameters.
void UpdateParameters(ECScanManager& sm, const std::vector<arma::colvec>& update) EddyTry {
throw EddyException("NoLongECModel::UpdateParameters(sm,update): model has no parameters");
} EddyCatch
/// Updates the parameters for a single volume. This is the method to use when rolling back updates on individual volumes.
/// N.B. that "update" means that the update is added to the current parameters.
/// N.B. that the index s implicitly assumes ScanType st==ScanType::Any
void UpdateParameters(ECScan& sc, unsigned int s, const arma::colvec& update) EddyTry {
throw EddyException("NoLongECModel::UpdateParameters(sc,s,update): model has no parameters");
} EddyCatch
nlohmann::json WriteReport(const ECScanManager& sm, const std::string& fname, bool write_old_style_file) const;
};
} // End namespace EDDY
#endif // End #ifndef LongECModels_h
#
# This is the makefile for the eddy project. It can be used
# to compile the following variants of eddy:
#
# - make cuda=1 (compiles a CUDA-capable version)
# - make cpu=1 (compiles a multi-threaded CPU version)
# - make (equivalent to make cpu=1)
#
# The resulting eddy binary executables will be named like so:
# - cpu: eddy_cpu
# - cuda: eddy_cudaX.Y (where X.Y is the CUDA version that
# the binary was compiled against)
#
# Both variants can be compiled in one invocation, e.g.:
#
# make cpu=1 cuda=1
#
# Notes regarding the cuda variant:
#
# - By default a fat binary with code for all the compute
# capabilities allowed for by a given CUDA version will
# be compiled. If one only need 3.0--3.5 (for testing)
# one can build with the fastbuild=1 option which saves
# a lot of compilation time, e.g.:
#
# make cuda=1 fastbuild=1
#
# - Alternately, to compile for a specific compute
# capabillity (or range thereof), you can specify the
# GENCODEFLAGS variable, e.g.:
#
# make cuda=1 GENCODEFLAGS="-gencode arch=compute_30,code=sm_30"
#
# - Or you can specify a specific compute capability with
# the COMPUTE_CAPABILITY vraiable, e.g.:
#
# make cuda=1 COMPUTE_CAPABILITY="30"
#
# - By default, all CUDA libraries (libcuda, libcudart, and those
# specified in the CUDALIBS variable below) will be dynamically
# linked in the resulting executable. The CUDA_STATIC variable
# can be specified to statically link these libraries instead,
# e.g.:
#
# make cuda=1 CUDA_STATIC=1
#
# - To compile against a specific version of the CUDA toolkit,
# just make sure that the relevant nvcc command is on your $PATH
# variable. For example:
#
# PATH=/usr/local/cuda10.2/bin:$PATH make cuda=1
#
# Alternately, you can set the $NVCC variable to refer to the
# specific nvcc executable, e.g.:
#
# NVCC=/usr/local/cuda11.3/bin/nvcc make cuda=1
#
# The CUDA_HOME, GENCODEFLAGS, and CUDA_STATIC variables are all
# handled in $FSLCONFDIR/buildSettings.mk.
include ${FSLCONFDIR}/default.mk
PROJNAME = eddy
SCRIPTS =
XFILES =
ifdef cpu
XFILES += eddy_cpu
SCRIPTS += eddy
endif
ifdef cuda
XFILES += eddy_cuda${CUDA_VER}
endif
# Default to cpu variant
# if no flags were provided
ifeq (${XFILES},)
XFILES = eddy_cpu
endif
# -rdynamic allows meaningful backtraces to
# be emitted on segmentation faults and
# other crashes (see fsl/utils/stack_dump.h).
# Understood by both clang++ and g++
USRLDFLAGS = -rdynamic
USRNVCCFLAGS = -DCOMPILE_GPU -Icuda
CUDALIBS = -L /datav/wkx/fsl/FSL-install-new/lib -lfsl-cudabasisfield_cuda11.8 \
-lhipblas -L/datav/wkx/fsl/FSL-install-new/lib -L/datav/wkx/fsl/FSL-install-new/lib \
-lfsl-topup -lfsl-warpfns -lfsl-meshclass -lfsl-basisfield -lfsl-newimage \
-lfsl-miscmaths -lfsl-cprob -lfsl-NewNifti -lfsl-znz -lfsl-utils \
-L /usr/local/lib/python3.10/dist-packages/ray/core -ljemalloc \
-llapack -lblas -lz -lm -fvisibility=default -fPIC
LIBS = -L /datav/wkx/fsl/FSL-install-new/lib -lfsl-topup -lfsl-warpfns -lfsl-meshclass -lfsl-basisfield \
-lfsl-newimage -lfsl-miscmaths -lfsl-cprob -lfsl-NewNifti \
-lfsl-znz -lfsl-utils
# Skip CUDA fat binary creation if fastbuild
# is set (overriding GENCODEFLAGS defined
# in FSLDIR/config/buildSettings.mk)
ifeq ($(fastbuild),1)
GENCODEFLAGS := -gencode arch=compute_30,code=sm_30
endif
# Build a specific compute capability if
# requested (overridding fastbuild and
# GENCODEFLAGS)
ifdef COMPUTE_CAPABILITY
GENCODEFLAGS := -gencode arch=compute_$(COMPUTE_CAPABILITY),code=sm_$(COMPUTE_CAPABILITY)
endif
# Compiled for all variants
OBJS := eddy.o b0Predictor.o BiasFieldEstimatorImpl.o \
CPUStackResampler.o DiffusionGP.o fmriPredictor.o ECModels.o \
LongECModels.o ECScanClasses.o EddyCommandLineOptions.o \
EddyHelperClasses.o EddyUtils.o HyParEstimator.o \
KMatrix.o MoveBySuscCF.o PostEddyAlignShellsFunctions.o
# Compiled for CPU variant
CPUOBJS := LSResampler.o PostEddyCF.o
# Compiled for cuda variant
CUDAOBJS := CudaVolume.o DerivativeCalculator.o DiffusionGP.o fmriPredictor.o \
EddyCudaHelperFunctions.o EddyGpuUtils.o EddyInternalGpuUtils.o \
EddyKernels.o EddyMatrixKernels.o GpuPredictorChunk.o \
LSResampler.o PostEddyCF.o StackResampler.o
# use separate build dirs for each variant. The
# BUILDDIR and CUDABUILDDIR variables are used by
# the depend.mk rule in $FSLDIR/config/rules.mk,
# which is used to automatically generate
# dependencies for each object file. Dependencies
# for CUDA object files are explicitly listed at
# the end of this Makefile.
CUDABUILDDIR = cudabuild/
BUILDDIR = cpubuild/
CPUOBJS := $(OBJS:%.o=cpubuild/%.o) $(CPUOBJS:%.o=cpubuild/%.o)
CUDAOBJS := $(OBJS:%.o=cudabuild/%.o) $(CUDAOBJS:%.o=cudabuild/cuda${CUDA_VER}/%.o)
all: ${XFILES}
clean:
@rm -f depend.mk eddy_cpu eddy_cuda*
@rm -rf cudabuild cpubuild
HIPCXXFLAGS = -DARMA_ALLOW_FAKE_GCC -std=c++17 -fPIC -g -O0 \
-I /opt/dtk/include -I ${FSLCONFDIR}/../include -I .
HIPLDFLAGS = -L /opt/dtk/lib -l hipblas -l hipblaslt -l galaxyhip -l amdhip64 -DARMA_ALLOW_FAKE_GCC -std=c++17 -fPIC -g -O0
# -DARMA_ALLOW_FAKE_GCC -std=c++17 -fPIC -g -O0
#################################
# CPU executable and object files
#################################
eddy_cpu: ${CPUOBJS}
@mkdir -p cpubuild
hipcc ${CXXFLAGS} -o $@ $^ ${LDFLAGS} -fvisibility=default -fPIC ${LIBS}
cpubuild/%.o: %.cpp
@mkdir -p cpubuild
hipcc $(CXXFLAGS) -c -o $@ $< -g -O0 -fvisibility=default -fPIC
#################################
# GPU executable and object files
#################################
# eddy_cuda${CUDA_VER}: ${CUDAOBJS}
# ${NVCC} ${NVCCFLAGS} -o $@ $^ ${NVCCLDFLAGS} -fvisibility=default -fPIC
eddy_cuda${CUDA_VER}: ${CUDAOBJS}
hipcc ${NVCCFLAGS} -o $@ $^ ${HIPLDFLAGS} ${CUDALIBS}
cudabuild/cuda${CUDA_VER}/%.o: cuda/%.cpp
@mkdir -p cudabuild/cuda${CUDA_VER}
hipcc $(NVCCFLAGS) -c -o $@ $< -g -O0 -fvisibility=default -fPIC
cudabuild/%.o: %.cpp
@mkdir -p cudabuild
hipcc $(CUDACXXFLAGS) -c -o $@ $< -g -O0 -g -fvisibility=default -fPIC
\ No newline at end of file
// Definitions of classes and functions that
// estimates the derivative fields for a
// movement-by-susceptibility model for the
// eddy project.
//
// This file contins all the code for both the
// CPU and the GPU implementations. The code
// generation is goverened #include:s and
// the COMPILE_GPU macro.
//
// MoveBySuscCF.cpp
//
// Jesper Andersson, FMRIB Image Analysis Group
//
// Copyright (C) 2016 University of Oxford
//
#include <cstdlib>
#include <string>
#include <vector>
#include <map>
#include <cmath>
#include <memory>
#include "armawrap/newmat.h"
#include "utils/FSLProfiler.h"
#include "basisfield/basisfield.h"
#include "basisfield/splinefield.h"
#include "EddyHelperClasses.h"
#include "DiffusionGP.h"
#include "b0Predictor.h"
#include "EddyUtils.h"
#include "ECScanClasses.h"
#include "MoveBySuscCF.h"
#include "eddy.h"
#ifdef COMPILE_GPU
#include "cuda/EddyGpuUtils.h"
#include "cudabasisfield/CBFSplineField.cuh"
#endif
using namespace std;
using namespace MISCMATHS;
namespace EDDY {
class MoveBySuscCFImpl
{
public:
MoveBySuscCFImpl(EDDY::ECScanManager& sm,
const EDDY::EddyCommandLineOptions& clo,
const std::vector<unsigned int>& b0s,
const std::vector<unsigned int>& dwis,
const std::vector<unsigned int>& mps,
unsigned int order,
bool ujm,
double ksp);
~MoveBySuscCFImpl() {}
double cf(const NEWMAT::ColumnVector& p);
NEWMAT::ReturnMatrix grad(const NEWMAT::ColumnVector& p);
std::shared_ptr<BFMatrix> hess(const NEWMAT::ColumnVector& p,
std::shared_ptr<BFMatrix> iptr=std::shared_ptr<BFMatrix>());
void SetLambda(double lambda) { _lmbd = lambda; }
unsigned int NPar() const EddyTry { return(this->total_no_of_parameters()); } EddyCatch
NEWMAT::ReturnMatrix Par() const EddyTry { return(this->get_field_parameters()); } EddyCatch
void WriteFirstOrderFields(const std::string& fname) const;
void WriteSecondOrderFields(const std::string& fname) const;
/// Used to force recalculation of sum images the next time cf, grad or hess is called
void ResetCache() { _utd = false; _m_utd = false; }
private:
EDDY::ECScanManager& _sm; ///< Ref to ScanManger. Means that external object will change.
EDDY::EddyCommandLineOptions _clo; ///< Internal copy of Command Line Options.
std::vector<unsigned int> _b0s; ///< Indicies for st=B0 of b0 volumes to use
std::vector<unsigned int> _dwis; ///< Indicies for st=DWI of dwi volumes to use
std::vector<unsigned int> _mps; ///< Zero-offset indicies to the movement parameters to model
unsigned int _order; ///< Order, should be 1 or 2
bool _ujm; ///< If true, Jacobian modulation is used for derivatives and Hessian
double _lmbd; ///< Lambda (weight of regularisation).
std::vector<unsigned int> _ksp; ///< Knot-spacing of spline-fields
#ifdef COMPILE_GPU
std::vector<std::shared_ptr<CBF::CBFSplineField> > _sp1; ///< Modelled first order fields
std::vector<std::vector<std::shared_ptr<CBF::CBFSplineField> > > _sp2; ///< Modelled 2nd order fields, organised as sub-diagonal matrix
#else
std::vector<std::shared_ptr<BASISFIELD::splinefield> > _sp1; ///< Modelled first order fields
std::vector<std::vector<std::shared_ptr<BASISFIELD::splinefield> > > _sp2; ///< Modelled 2nd order fields, organised as sub-diagonal matrix
#endif
NEWMAT::ColumnVector _cp; ///< Current p (parameters)
NEWMAT::ColumnVector _hypar; ///< Hyper-parameters for GP
NEWIMAGE::volume<float> _mask; ///< Latest calculated mask with data for all volumes
NEWIMAGE::volume<char> _cmask; ///< char representation of mask
unsigned int _nvox; ///< Number of non-zero voxels in mask
unsigned int _nthr; ///< Number of C++ threads to run calculaiont on
// Here I declare variables that are initialised
MISCMATHS::BFMatrixPrecisionType _hp = MISCMATHS::BFMatrixDoublePrecision; ///< Precision (double or float) of Hessian
bool _bno = true; ///< Brand New Object?
bool _utd = false; ///< Are all the sum images up-to-date?
bool _m_utd = false; ///< Is mask up to date or not?
bool _chgr = false; ///< Check Gradient, write gradient checking info
bool _chH = false; ///< Check Hessian, write Hessian checking info
unsigned int _grad_cnt = 0; ///< Counter to see how many times .grad() has been called
unsigned int _hess_cnt = 0; ///< Counter to see how many times .hess() has been called
// Here I declare sums-of-products of image volumes. It "looks like there would
// be a lot of these sum images, but for the "normal" case of a first-order model
// for pitch and roll with a single PE-orientation there is only one for .cf(),
// four for .grad() and six for .hess(). So the memory footprint is small.
// These two typedefs are used to make subsequent declarations a little saner
typedef std::vector<NEWIMAGE::volume<float> > ImageVec; ///< Vector of images
typedef std::vector<unsigned int> BpeVec; ///< Binarised PE-vector
// Used by .cf()
NEWIMAGE::volume<float> _sum_sqr_diff; ///< Sum of squared differences between prediction and observation
// Used by .grad()
ImageVec _sum_fo_deriv_diff; ///< First order Sum(deriv*diff)
std::map<BpeVec,ImageVec> _sum_fo_ima_diff; ///< First order Sum(ima*diff)
std::vector<ImageVec> _sum_so_deriv_diff; ///< Second order Sum(deriv*diff)
std::map<BpeVec,std::vector<ImageVec> > _sum_so_ima_diff; ///< Second order Sum(ima*diff)
// Used by .hess()
std::vector<ImageVec> _sum_fo_deriv_deriv;///< First order Sum(deriv*deriv)
std::map<BpeVec,std::vector<ImageVec> > _sum_fo_ima_ima; ///< First order Sum(ima*ima)
std::map<BpeVec,std::vector<ImageVec> > _sum_fo_deriv_ima; ///< First order Sum(deriv*ima)
std::vector<ImageVec> _sum_so_deriv_deriv;///< Second order Sum(deriv*deriv)
std::map<BpeVec,std::vector<ImageVec> > _sum_so_ima_ima; ///< Second order Sum(ima*ima)
std::map<BpeVec,std::vector<ImageVec> > _sum_so_deriv_ima; ///< Second order Sum(deriv*ima)
std::vector<ImageVec> _sum_cross_deriv_deriv; ///< Cross-term between 1st and 2nd order Sum(deriv*deriv)
std::map<BpeVec,std::vector<ImageVec> > _sum_cross_ima_ima; ///< Cross-term between 1st and 2nd order Sum(ima*ima)
std::map<BpeVec,std::vector<ImageVec> > _sum_cross_deriv_ima; ///< Cross-term between 1st and 2nd order Sum(deriv*ima)
// These private functions are all candidates for removal
unsigned int no_of_parameters_per_field() const EddyTry { return(_sp1[0]->CoefSz_x() * _sp1[0]->CoefSz_y() * _sp1[0]->CoefSz_z()); } EddyCatch
unsigned int no_of_first_order_fields() const EddyTry { return(_mps.size()); } EddyCatch
unsigned int no_of_second_order_fields() const EddyTry { return((_order == 1) ? 0 : _mps.size()*(_mps.size()+1)/2); } EddyCatch
unsigned int no_of_fields() const EddyTry { return(no_of_first_order_fields() + no_of_second_order_fields()); } EddyCatch
unsigned int total_no_of_parameters() const EddyTry { return(no_of_parameters_per_field()*no_of_fields()); } EddyCatch
unsigned int no_of_b0s() const EddyTry { return(_b0s.size()); } EddyCatch
unsigned int no_of_dwis() const EddyTry { return(_dwis.size()); } EddyCatch
unsigned int nvox() EddyTry { if (!_m_utd) recalculate_sum_images(); return(_nvox); } EddyCatch
double lambda() const { return(_lmbd); }
double pi() const { return(3.141592653589793); }
MISCMATHS::BFMatrixPrecisionType hessian_precision() const { return(_hp); }
NEWMAT::ColumnVector get_field_parameters() const EddyTry { return(_cp); } EddyCatch
void set_field_parameters(EDDY::ECScanManager& sm,
const NEWMAT::ColumnVector& p);
const NEWIMAGE::volume<float>& mask() EddyTry { if (!_m_utd) recalculate_sum_images(); return(_mask); } EddyCatch
const NEWIMAGE::volume<char>& cmask() EddyTry { if (!_m_utd) recalculate_sum_images(); return(_cmask); } EddyCatch
void resize_containers_for_sum_images_for_grad(const std::vector<std::vector<unsigned int> >& indicies,
const std::vector<EDDY::ScanType>& imtypes);
void resize_containers_for_sum_images_for_hess(const std::vector<std::vector<unsigned int> >& indicies,
const std::vector<EDDY::ScanType>& imtypes);
void set_sum_images_to_zero(const NEWIMAGE::volume<float>& ima);
void recalculate_sum_images();
void recalculate_sum_so_imas_for_hess(const NEWMAT::ColumnVector& p,
const BpeVec& bpe,
const NEWIMAGE::volume<float>& ima,
const NEWIMAGE::volume<float>& deriv);
void recalculate_sum_cross_imas_for_hess(const NEWMAT::ColumnVector& p,
const BpeVec& bpe,
const NEWIMAGE::volume<float>& ima,
const NEWIMAGE::volume<float>& deriv);
void calculate_first_order_subm(// Input
const std::vector<ImageVec>& sop_dfdf,
const std::map<BpeVec,std::vector<ImageVec> >& sop_ff,
const std::map<BpeVec,std::vector<ImageVec> >& sop_fdf,
const NEWIMAGE::volume<char>& lmask,
unsigned int nvox,
// Output
std::vector<std::vector<std::shared_ptr<MISCMATHS::BFMatrix> > >& subm) const;
void calculate_second_order_subm(// Input
const std::vector<ImageVec>& sop_dfdf,
const std::map<BpeVec,std::vector<ImageVec> >& sop_ff,
const std::map<BpeVec,std::vector<ImageVec> >& sop_fdf,
const NEWIMAGE::volume<char>& lmask,
unsigned int nvox,
// Output
std::vector<std::vector<std::shared_ptr<MISCMATHS::BFMatrix> > >& subm) const;
void calculate_cross_subm(// Input
const std::vector<ImageVec>& sop_dfdf,
const std::map<BpeVec,std::vector<ImageVec> >& sop_ff,
const std::map<BpeVec,std::vector<ImageVec> >& sop_fdf,
const NEWIMAGE::volume<char>& lmask,
unsigned int nvox,
// Output
std::vector<std::vector<std::shared_ptr<MISCMATHS::BFMatrix> > >& subm) const;
NEWMAT::ColumnVector stl2newmat(const std::vector<double>& stl) const;
NEWMAT::Matrix linspace(const NEWMAT::Matrix& inmat) const;
std::shared_ptr<MISCMATHS::BFMatrix> concatenate_subdiag_subm(const std::vector<std::vector<std::shared_ptr<MISCMATHS::BFMatrix> > >& subm,
unsigned int n) const;
std::shared_ptr<MISCMATHS::BFMatrix> concatenate_rect_subm(const std::vector<std::vector<std::shared_ptr<MISCMATHS::BFMatrix> > >& subm,
unsigned int m,
unsigned int n) const;
bool is_first_index_pair(unsigned int i,
unsigned int j,
const std::vector<unsigned int>& ivec,
const std::vector<std::vector<std::vector<unsigned int> > >& pmap) const;
std::pair<unsigned int,unsigned int> find_first_index_pair(const std::vector<unsigned int>& ivec,
const std::vector<std::vector<std::vector<unsigned int> > >& pmap) const;
std::vector<std::vector<std::vector<unsigned int> > > build_second_order_pmap() const;
std::vector<std::vector<std::vector<unsigned int> > > build_cross_pmap() const;
std::vector<unsigned int> make_index_vector(const std::pair<unsigned int,unsigned int>& p1,
const std::pair<unsigned int,unsigned int>& p2,
unsigned int np) const;
std::vector<unsigned int> make_index_vector(const std::pair<unsigned int,unsigned int>& p1,
unsigned int i,
unsigned int np) const;
std::pair<unsigned int,unsigned int> get_second_order_index_pair(unsigned int i,
unsigned int np) const;
};
MoveBySuscCF::MoveBySuscCF(EDDY::ECScanManager& sm,
const EDDY::EddyCommandLineOptions& clo,
const std::vector<unsigned int>& b0s,
const std::vector<unsigned int>& dwis,
const std::vector<unsigned int>& mps,
unsigned int order,
double ksp) EddyTry { _pimpl = new MoveBySuscCFImpl(sm,clo,b0s,dwis,mps,order,true,ksp); } EddyCatch
MoveBySuscCF::~MoveBySuscCF() { delete _pimpl; }
double MoveBySuscCF::cf(const NEWMAT::ColumnVector& p) const EddyTry { return(_pimpl->cf(p)); } EddyCatch
NEWMAT::ReturnMatrix MoveBySuscCF::grad(const NEWMAT::ColumnVector& p) const EddyTry { return(_pimpl->grad(p)); } EddyCatch
std::shared_ptr<BFMatrix> MoveBySuscCF::hess(const NEWMAT::ColumnVector& p,
std::shared_ptr<BFMatrix> iptr) const EddyTry { return(_pimpl->hess(p,iptr)); } EddyCatch
void MoveBySuscCF::SetLambda(double lambda) EddyTry { _pimpl->SetLambda(lambda); } EddyCatch
NEWMAT::ReturnMatrix MoveBySuscCF::Par() const EddyTry { return(_pimpl->Par()); } EddyCatch
unsigned int MoveBySuscCF::NPar() const EddyTry { return(_pimpl->NPar()); } EddyCatch
void MoveBySuscCF::WriteFirstOrderFields(const std::string& fname) const EddyTry { _pimpl->WriteFirstOrderFields(fname); } EddyCatch
void MoveBySuscCF::WriteSecondOrderFields(const std::string& fname) const EddyTry { _pimpl->WriteSecondOrderFields(fname); } EddyCatch
void MoveBySuscCF::ResetCache() EddyTry { _pimpl->ResetCache(); } EddyCatch
MoveBySuscCFImpl::MoveBySuscCFImpl(EDDY::ECScanManager& sm,
const EDDY::EddyCommandLineOptions& clo,
const std::vector<unsigned int>& b0s,
const std::vector<unsigned int>& dwis,
const std::vector<unsigned int>& mps,
unsigned int order,
bool ujm,
double ksp) EddyTry : _sm(sm), _clo(clo), _b0s(b0s), _dwis(dwis), _mps(mps), _order(order), _ujm(ujm), _lmbd(50)
{
static Utilities::FSLProfiler prof("_"+string(__FILE__)+"_"+string(__func__));
double total_key = prof.StartEntry("Total");
if (order != 1 && order != 2) throw EddyException("MoveBySuscCFImpl::MoveBySuscCFImpl: order must be 1 or 2");
std::vector<unsigned int> isz(3,0);
isz[0] = static_cast<unsigned int>(_sm.Scan(0,ScanType::Any).GetIma().xsize());
isz[1] = static_cast<unsigned int>(_sm.Scan(0,ScanType::Any).GetIma().ysize());
isz[2] = static_cast<unsigned int>(_sm.Scan(0,ScanType::Any).GetIma().zsize());
std::vector<double> vxs(3,0);
vxs[0] = static_cast<double>(_sm.Scan(0,ScanType::Any).GetIma().xdim());
vxs[1] = static_cast<double>(_sm.Scan(0,ScanType::Any).GetIma().ydim());
vxs[2] = static_cast<double>(_sm.Scan(0,ScanType::Any).GetIma().zdim());
_ksp.resize(3);
_ksp[0] = static_cast<unsigned int>((ksp / vxs[0]) + 0.5);
_ksp[1] = static_cast<unsigned int>((ksp / vxs[1]) + 0.5);
_ksp[2] = static_cast<unsigned int>((ksp / vxs[2]) + 0.5);
_nthr = clo.NumberOfThreads();
_sp1.resize(_mps.size(),nullptr);
for (unsigned int i=0; i<_mps.size(); i++) {
#ifdef COMPILE_GPU
_sp1[i] = std::shared_ptr<CBF::CBFSplineField>(new CBF::CBFSplineField(isz,vxs,_ksp));
#else
_sp1[i] = std::shared_ptr<BASISFIELD::splinefield>(new BASISFIELD::splinefield(isz,vxs,_ksp,3,Utilities::NoOfThreads(_nthr)));
#endif
}
if (order == 2) {
_sp2.resize(_mps.size());
for (unsigned int i=0; i<_mps.size(); i++) {
_sp2[i].resize(i+1,nullptr);
for (unsigned int j=0; j<(i+1); j++) {
#ifdef COMPILE_GPU
_sp2[i][j] = std::shared_ptr<CBF::CBFSplineField>(new CBF::CBFSplineField(isz,vxs,_ksp));
#else
_sp2[i][j] = std::shared_ptr<BASISFIELD::splinefield>(new BASISFIELD::splinefield(isz,vxs,_ksp,3,Utilities::NoOfThreads(_nthr)));
#endif
}
}
}
_cp.ReSize(this->total_no_of_parameters()); _cp = 0.0;
/*
char *ont = getenv("OMP_NUM_THREADS");
if (ont == nullptr) _omp_num_threads = 1;
else if (sscanf(ont,"%u",&_omp_num_threads) != 1) throw EddyException("MoveBySuscCFImpl::MoveBySuscCFImpl: problem reading environment variable OMP_NUM_THREADS");
*/
prof.EndEntry(total_key);
} EddyCatch
void MoveBySuscCFImpl::WriteFirstOrderFields(const std::string& fname) const EddyTry
{
const NEWIMAGE::volume<float>& tmp=_sm.Scan(0,ScanType::Any).GetIma();
NEWIMAGE::volume4D<float> ovol(tmp.xsize(),tmp.ysize(),tmp.zsize(),no_of_first_order_fields());
NEWIMAGE::copybasicproperties(tmp,ovol);
NEWIMAGE::volume<float> dfield = tmp;
for (unsigned int i=0; i<_mps.size(); i++) {
_sp1[i]->AsVolume(dfield);
if (_mps[i] > 2 && _mps[i] < 6) { // If it is a rotation
dfield *= static_cast<float>(this->pi() / 180.0);
}
ovol[i] = dfield;
}
NEWIMAGE::write_volume(ovol,fname);
} EddyCatch
void MoveBySuscCFImpl::WriteSecondOrderFields(const std::string& fname) const EddyTry
{
const NEWIMAGE::volume<float>& tmp=_sm.Scan(0,ScanType::Any).GetIma();
NEWIMAGE::volume4D<float> ovol(tmp.xsize(),tmp.ysize(),tmp.zsize(),no_of_second_order_fields());
NEWIMAGE::copybasicproperties(tmp,ovol);
NEWIMAGE::volume<float> dfield = tmp;
unsigned int cnt=0;
for (unsigned int i=0; i<_mps.size(); i++) {
for (unsigned int j=0; j<(i+1); j++) {
_sp2[i][j]->AsVolume(dfield);
ovol[cnt++] = dfield;
}
}
NEWIMAGE::write_volume(ovol,fname);
} EddyCatch
void MoveBySuscCFImpl::set_field_parameters(EDDY::ECScanManager& sm,
const NEWMAT::ColumnVector& p) EddyTry
{
if (static_cast<unsigned int>(p.Nrows()) != total_no_of_parameters()) throw EddyException("MoveBySuscCFImpl::set_field_parameters: mismatch between p and total no of parameters");
// Check if these parameters have been used before
if (static_cast<unsigned int>(_cp.Nrows()) == total_no_of_parameters() && _cp == p) return;
else {
_m_utd = false;
_utd = false;
_cp = p;
// Set first order fields
unsigned int pindx=1;
for (unsigned int i=0; i<_mps.size(); i++) {
_sp1[i]->SetCoef(p.Rows(pindx,pindx+no_of_parameters_per_field()-1));
NEWIMAGE::volume<float> dfield = sm.Scan(0,ScanType::Any).GetIma(); dfield=0.0;
_sp1[i]->AsVolume(dfield);
sm.SetDerivSuscField(_mps[i],dfield);
pindx += no_of_parameters_per_field();
}
// And second order fields if requested
if (_order == 2) {
for (unsigned int i=0; i<_mps.size(); i++) {
for (unsigned int j=0; j<(i+1); j++) {
_sp2[i][j]->SetCoef(p.Rows(pindx,pindx+no_of_parameters_per_field()-1));
NEWIMAGE::volume<float> dfield = sm.Scan(0,ScanType::Any).GetIma(); dfield=0.0;
_sp2[i][j]->AsVolume(dfield);
sm.Set2ndDerivSuscField(_mps[i],_mps[j],dfield);
pindx += no_of_parameters_per_field();
}
}
}
}
} EddyCatch
double MoveBySuscCFImpl::cf(const NEWMAT::ColumnVector& p) EddyTry
{
if (static_cast<unsigned int>(p.Nrows()) != total_no_of_parameters()) throw EddyException("MoveBySuscCFImpl::cf: mismatch between p and total no of parameters");
// Set derivative fields according to p
// cout << "Setting field parameters" << endl;
set_field_parameters(_sm,p);
recalculate_sum_images();
// Sum of squared differences
double ssd = _sum_sqr_diff.sum(mask()) / static_cast<double>(nvox());
// Add contribution from regularisation
double reg = 0;
for (unsigned int i=0; i<_mps.size(); i++) {
reg += lambda() * _sp1[i]->BendEnergy() / static_cast<double>(nvox());
}
if (_order == 2) {
for (unsigned int i=0; i<_mps.size(); i++) {
for (unsigned int j=0; j<(i+1); j++) {
reg += lambda() * _sp2[i][j]->BendEnergy() / static_cast<double>(nvox());
}
}
}
reg /= no_of_fields(); // Average over all fields.
ssd += reg;
return(ssd);
} EddyCatch
NEWMAT::ReturnMatrix MoveBySuscCFImpl::grad(const NEWMAT::ColumnVector& p) EddyTry
{
if (static_cast<unsigned int>(p.Nrows()) != total_no_of_parameters()) throw EddyException("MoveBySuscCFImpl::grad: mismatch between p and total no of parameters");
// Set derivative fields according to p
set_field_parameters(_sm,p);
recalculate_sum_images();
// Use the pre-calculated sum images to calculate the gradient
// cout << "Starting the actual calculation of the gradient" << endl;
NEWMAT::ColumnVector gradient(total_no_of_parameters()); gradient=0.0;
unsigned int fr = 1;
unsigned int lr = no_of_parameters_per_field();
NEWIMAGE::volume<float> ones = _sm.Scan(0,ScanType::Any).GetIma(); ones = 1.0;
for (unsigned int pi=0; pi<_mps.size(); pi++) {
// Contribution from voxel-translations
gradient.Rows(fr,lr) += (2.0/static_cast<double>(nvox())) * _sp1[pi]->Jte(_sum_fo_deriv_diff[pi],ones,&cmask());
// Contribution from Jacobian modulation
if (_ujm) {
for (const auto& elem : _sum_fo_ima_diff) { // Loop over all binarised PE-vectors in sum_fo_ima_diff
std::vector<unsigned int> dvec = elem.first;
gradient.Rows(fr,lr) += (2.0/static_cast<double>(nvox())) * _sp1[pi]->Jte(dvec,elem.second[pi],ones,&cmask());
}
}
fr += no_of_parameters_per_field();
lr += no_of_parameters_per_field();
}
if (_order == 2) {
for (unsigned int pi=0; pi<_mps.size(); pi++) {
for (unsigned int pj=0; pj<(pi+1); pj++) {
// Contribution from voxel-translations
gradient.Rows(fr,lr) += (2.0/static_cast<double>(nvox())) * _sp2[pi][pj]->Jte(_sum_so_deriv_diff[pi][pj],ones,&cmask());
// Contribution from Jacobian Modulation
if (_ujm) {
for (const auto& elem: _sum_so_ima_diff) {
std::vector<unsigned int> dvec = elem.first;
gradient.Rows(fr,lr) += (2.0/static_cast<double>(nvox())) * _sp2[pi][pj]->Jte(dvec,elem.second[pi][pj],ones,&cmask());
}
}
}
}
}
// Finally the contributions from regularisation
fr = 1;
lr = no_of_parameters_per_field();
for (unsigned int i=0; i<_mps.size(); i++) {
gradient.Rows(fr,lr) += (lambda()/static_cast<double>(nvox()*no_of_fields())) * _sp1[i]->BendEnergyGrad();
fr += no_of_parameters_per_field();
lr += no_of_parameters_per_field();
}
if (_order == 2) {
for (unsigned int i=0; i<_mps.size(); i++) {
for (unsigned int j=0; j<(i+1); j++) {
gradient.Rows(fr,lr) += (lambda()/static_cast<double>(nvox()*no_of_fields())) * _sp2[i][j]->BendEnergyGrad();
fr += no_of_parameters_per_field();
lr += no_of_parameters_per_field();
}
}
}
if (_chgr) { // If we are to check gradient
char fname[256]; sprintf(fname,"gradient_%02d.txt",_grad_cnt);
MISCMATHS::write_ascii_matrix(gradient,fname);
NEWMAT::ColumnVector tmp_p = p;
tmp_p(10000) += 0.0001;
double cf0 = this->cf(tmp_p);
tmp_p(10000) -= 0.0001;
cf0 = this->cf(tmp_p);
NEWMAT::ColumnVector numgrad(total_no_of_parameters()); numgrad=0.0;
unsigned int no_of_values = 20;
unsigned int step = total_no_of_parameters() / (no_of_values + 1);
double delta = 0.001;
for (unsigned int i=step/2; i<total_no_of_parameters(); i+=step) {
if (i<total_no_of_parameters()) {
tmp_p(i) += delta;
double cf1 = this->cf(tmp_p);
numgrad(i) = (cf1-cf0) / delta;
tmp_p(i) -= delta;
}
}
sprintf(fname,"numgrad_delta_001_%02d.txt",_grad_cnt);
MISCMATHS::write_ascii_matrix(numgrad,fname);
}
_grad_cnt++;
gradient.Release(); return(gradient);
} EddyCatch
/*!
* Calculates and returns the Hessian. It is rather complicated for the following reasons
* 1. Each sub-Hessian is a sum of four components. The direct translation and Jacobian components and
* the cross components (one is the transpose of the other).
* 2. The Hessian is a tiling of NxN sub-Hessians, where N is the total number of fields that are
* modelled. The fields divide into first and second order fields, and the Hessian into three
* blocks of sub-Hessian. The first is the first order fields and their interactions. The second
* is the second order field and their interactions. The third is the interactions between the
* first and second order fields. The two latter contain repetitions of the same sub-matrix, and
* this should be taken into account for efficiency. The first two are represented as sub-diagonal
* matrices of sub-matrices. The last is represented as a rectangular matrix of sub-matrices.
* \param[in] p The parameters that, when convolved with B-splines, define all modelled fields
* \param[in] iptr The previous Hessian. Allows us to explicitly free up the memory before allocating new.
* \return A boost shared ptr to a sparse matrix with the Gauss-Newton approximation to the Hessian.
*/
std::shared_ptr<MISCMATHS::BFMatrix> MoveBySuscCFImpl::hess(const NEWMAT::ColumnVector& p,
std::shared_ptr<BFMatrix> iptr) EddyTry
{
if (static_cast<unsigned int>(p.Nrows()) != total_no_of_parameters()) throw EddyException("MoveBySuscCFImpl::hess: mismatch between p and total no of parameters");
// Set derivative fields according to p
set_field_parameters(_sm,p);
recalculate_sum_images();
// Reclaim memory from old Hessian if there is one
if (iptr) iptr->Clear();
// Allocate matrices of pointers to sub-matrices
std::vector<std::vector<std::shared_ptr<MISCMATHS::BFMatrix> > > first_order_subm(this->no_of_first_order_fields());
std::vector<std::vector<std::shared_ptr<MISCMATHS::BFMatrix> > > second_order_subm(this->no_of_second_order_fields());
std::vector<std::vector<std::shared_ptr<MISCMATHS::BFMatrix> > > cross_subm(this->no_of_second_order_fields());
for (unsigned int i=0; i<no_of_first_order_fields(); i++) {
first_order_subm[i].resize(i+1);
}
if (_order == 2) { // Only allocate if actually used
for (unsigned int i=0; i<no_of_second_order_fields(); i++) {
second_order_subm[i].resize(i+1);
cross_subm[i].resize(this->no_of_first_order_fields());
}
}
// Then calculate the actual components (sub-matrices) of the Hessian
calculate_first_order_subm(_sum_fo_deriv_deriv,_sum_fo_ima_ima,_sum_fo_deriv_ima,cmask(),nvox(),first_order_subm);
if (_order == 2) {
calculate_second_order_subm(_sum_so_deriv_deriv,_sum_so_ima_ima,_sum_so_deriv_ima,cmask(),nvox(),second_order_subm);
calculate_cross_subm(_sum_cross_deriv_deriv,_sum_cross_ima_ima,_sum_cross_deriv_ima,cmask(),nvox(),cross_subm);
}
// Add regularisation to diagonal blocks
std::shared_ptr<MISCMATHS::BFMatrix> reg = _sp1[0]->BendEnergyHess(hessian_precision());
for (unsigned int i=0; i<this->no_of_first_order_fields(); i++) {
first_order_subm[i][i]->AddToMe(*reg,lambda()/static_cast<double>(nvox()*no_of_fields()));
}
for (unsigned int i=0; i<this->no_of_second_order_fields(); i++) {
second_order_subm[i][i]->AddToMe(*reg,lambda()/static_cast<double>(nvox()*no_of_fields()));
}
// Finally concatenate them together
std::shared_ptr<MISCMATHS::BFMatrix> first_order_subm_cat = concatenate_subdiag_subm(first_order_subm,no_of_first_order_fields());
first_order_subm.clear(); // Free up memory
std::shared_ptr<MISCMATHS::BFMatrix> rval = first_order_subm_cat;
// rval->Print("hessian.txt");
// exit(1);
if (_order == 2) {
std::shared_ptr<MISCMATHS::BFMatrix> second_order_subm_cat = concatenate_subdiag_subm(second_order_subm,no_of_second_order_fields());
second_order_subm.clear(); // Free up memory
std::shared_ptr<MISCMATHS::BFMatrix> cross_subm_cat = concatenate_rect_subm(cross_subm,no_of_second_order_fields(),no_of_first_order_fields());
cross_subm.clear(); // Free up memory
rval->HorConcat2MyRight(*(cross_subm_cat->Transpose()));
cross_subm_cat->HorConcat2MyRight(*second_order_subm_cat);
rval->VertConcatBelowMe(*cross_subm_cat);
}
if (_chH) { // If we are to check Hessian
bool old_chgr = false;
if (_chgr) { _chgr = false; old_chgr = true; }
char fname[256]; sprintf(fname,"hessian_%02d.txt",_hess_cnt);
rval->Print(fname);
NEWMAT::ColumnVector tmp_p = p;
NEWMAT::ColumnVector grad0 = this->grad(tmp_p);
unsigned int no_of_values = 20;
NEWMAT::Matrix numhess(no_of_values*total_no_of_parameters(),3); numhess=0.0;
unsigned int step = total_no_of_parameters() / (no_of_values + 1);
double delta = 0.01;
unsigned int ii = 0;
for (unsigned int i=step/2; i<total_no_of_parameters(); i+=step) {
if (ii<no_of_values) {
tmp_p(i) += delta;
NEWMAT::ColumnVector grad1 = this->grad(tmp_p);
unsigned int fr = (ii)*total_no_of_parameters()+1;
unsigned int lr = (ii+1)*total_no_of_parameters();
numhess.SubMatrix(fr,lr,3,3) = (grad1 - grad0) / delta;
numhess.SubMatrix(fr,lr,2,2) = i;
numhess.SubMatrix(fr,lr,1,1) = this->linspace(numhess.SubMatrix(fr,lr,1,1));
tmp_p(i) -= delta;
ii++;
}
}
sprintf(fname,"numhess_delta_01_%02d.txt",_hess_cnt);
MISCMATHS::write_ascii_matrix(numhess,fname);
_chgr = old_chgr;
}
_hess_cnt++;
return(rval);
} EddyCatch
std::shared_ptr<MISCMATHS::BFMatrix> MoveBySuscCFImpl::concatenate_subdiag_subm(const std::vector<std::vector<std::shared_ptr<MISCMATHS::BFMatrix> > >& subm,
unsigned int n) const EddyTry
{
// Concatenate each row of submatrices left->right
std::vector<std::shared_ptr<MISCMATHS::BFMatrix> > tmp(n);
for (unsigned int i=0; i<n; i++) {
for (unsigned int j=0; j<n; j++) {
if (!j) {
if (_hp == MISCMATHS::BFMatrixFloatPrecision) {
const SparseBFMatrix<float>& tmp2 = dynamic_cast<SparseBFMatrix<float>& >(*subm[i][0]);
tmp[i] = std::shared_ptr<MISCMATHS::BFMatrix>(new MISCMATHS::SparseBFMatrix<float>(tmp2));
}
else {
const SparseBFMatrix<double>& tmp2 = dynamic_cast<SparseBFMatrix<double>& >(*subm[i][0]);
tmp[i] = std::shared_ptr<MISCMATHS::BFMatrix>(new MISCMATHS::SparseBFMatrix<double>(tmp2));
}
}
else {
if (j <= i) tmp[i]->HorConcat2MyRight(*subm[i][j]);
else tmp[i]->HorConcat2MyRight(*(subm[j][i]->Transpose()));
}
}
}
// Concatenate the rows top->bottom
std::shared_ptr<MISCMATHS::BFMatrix> rval = tmp[0];
for (unsigned int i=1; i<n; i++) rval->VertConcatBelowMe(*tmp[i]);
return(rval);
} EddyCatch
std::shared_ptr<MISCMATHS::BFMatrix> MoveBySuscCFImpl::concatenate_rect_subm(const std::vector<std::vector<std::shared_ptr<MISCMATHS::BFMatrix> > >& subm,
unsigned int m,
unsigned int n) const EddyTry
{
// Concatenate each row of submatrices left->right
std::vector<std::shared_ptr<MISCMATHS::BFMatrix> > tmp(m);
for (unsigned int i=0; i<m; i++) {
for (unsigned int j=0; j<n; j++) {
if (!j) {
if (_hp == MISCMATHS::BFMatrixFloatPrecision) {
const SparseBFMatrix<float>& tmp2 = dynamic_cast<SparseBFMatrix<float>& >(*subm[i][0]);
tmp[i] = std::shared_ptr<MISCMATHS::BFMatrix>(new MISCMATHS::SparseBFMatrix<float>(tmp2));
}
else {
const SparseBFMatrix<double>& tmp2 = dynamic_cast<SparseBFMatrix<double>& >(*subm[i][0]);
tmp[i] = std::shared_ptr<MISCMATHS::BFMatrix>(new MISCMATHS::SparseBFMatrix<double>(tmp2));
}
}
else tmp[i]->HorConcat2MyRight(*subm[i][j]);
}
}
// Concatenate the rows top->bottom
std::shared_ptr<MISCMATHS::BFMatrix> rval = tmp[0];
for (unsigned int i=1; i<m; i++) rval->VertConcatBelowMe(*tmp[i]);
return(rval);
} EddyCatch
void MoveBySuscCFImpl::calculate_first_order_subm(// Input
const std::vector<ImageVec>& sop_dfdf,
const std::map<BpeVec,std::vector<ImageVec> >& sop_ff,
const std::map<BpeVec,std::vector<ImageVec> >& sop_fdf,
const NEWIMAGE::volume<char>& lmask,
unsigned int nvox,
// Output
std::vector<std::vector<std::shared_ptr<MISCMATHS::BFMatrix> > >& subm) const EddyTry
{
std::vector<unsigned int> noderiv(3,0); // No differentiation
NEWIMAGE::volume<float> ones = sop_dfdf[0][0]; ones = 1.0;
for (unsigned int i=0; i<no_of_first_order_fields(); i++) {
for (unsigned int j=0; j<(i+1); j++) {
subm[i][j] = _sp1[i]->JtJ(sop_dfdf[i][j],ones,&lmask,hessian_precision());
subm[i][j]->MulMeByScalar(2.0/static_cast<double>(nvox));
if (_ujm) {
for (const auto& elem : sop_ff) {
subm[i][j]->AddToMe(*_sp1[i]->JtJ(elem.first,elem.second[i][j],ones,&lmask,hessian_precision()),2.0/static_cast<double>(nvox));
}
for (const auto& elem: sop_fdf) {
std::shared_ptr<MISCMATHS::BFMatrix> tmp = _sp1[i]->JtJ(elem.first,elem.second[i][j],noderiv,ones,&lmask,hessian_precision());
subm[i][j]->AddToMe(*tmp,2.0/static_cast<double>(nvox));
subm[i][j]->AddToMe(*(tmp->Transpose()),2.0/static_cast<double>(nvox));
}
}
}
}
return;
} EddyCatch
void MoveBySuscCFImpl::calculate_second_order_subm(// Input
const std::vector<ImageVec>& sop_dfdf,
const std::map<BpeVec,std::vector<ImageVec> >& sop_ff,
const std::map<BpeVec,std::vector<ImageVec> >& sop_fdf,
const NEWIMAGE::volume<char>& lmask,
unsigned int nvox,
// Output
std::vector<std::vector<std::shared_ptr<MISCMATHS::BFMatrix> > >& subm) const EddyTry
{
if (_order == 2)
{
std::vector<std::vector<std::vector<unsigned int> > > pmap = build_second_order_pmap();
std::vector<unsigned int> noderiv(3,0); // No differentiation
NEWIMAGE::volume<float> ones = sop_dfdf[0][0]; ones = 1.0;
// First pass to calculate all unique sub-matrices
for (unsigned int i=0; i<no_of_second_order_fields(); i++) {
std::pair<unsigned int,unsigned int> first_pair = get_second_order_index_pair(i,no_of_second_order_fields());
for (unsigned int j=0; j<(i+1); j++) {
std::pair<unsigned int,unsigned int> second_pair = get_second_order_index_pair(j,no_of_second_order_fields());
std::vector<unsigned int> pindx = make_index_vector(first_pair,second_pair,no_of_first_order_fields());
if (is_first_index_pair(i,j,pindx,pmap)) { // If this is the first instance of this index combination
subm[i][j] = _sp2[i][j]->JtJ(sop_dfdf[i][j],ones,&lmask,hessian_precision());
subm[i][j]->MulMeByScalar(2.0/static_cast<double>(nvox));
if (_ujm) {
for (const auto& elem : sop_ff) {
subm[i][j]->AddToMe(*_sp2[i][j]->JtJ(elem.first,elem.second[i][j],ones,&lmask,hessian_precision()),(2.0/static_cast<double>(nvox)));
}
for (const auto& elem : sop_fdf) {
std::shared_ptr<MISCMATHS::BFMatrix> tmp = _sp2[i][j]->JtJ(elem.first,elem.second[i][j],noderiv,ones,&lmask,hessian_precision());
subm[i][j]->AddToMe(*tmp,(2.0/static_cast<double>(nvox)));
subm[i][j]->AddToMe(*(tmp->Transpose()),(2.0/static_cast<double>(nvox)));
}
}
}
}
}
// Second pass to copy pointers into non-unique locations
for (unsigned int i=0; i<no_of_second_order_fields(); i++) {
std::pair<unsigned int,unsigned int> first_pair = get_second_order_index_pair(i,no_of_second_order_fields());
for (unsigned int j=0; j<(i+1); j++) {
std::pair<unsigned int,unsigned int> second_pair = get_second_order_index_pair(j,no_of_second_order_fields());
std::vector<unsigned int> pindx = make_index_vector(first_pair,second_pair,no_of_first_order_fields());
if (!is_first_index_pair(i,j,pindx,pmap)) { // If this is NOT the first instance of this index combination
std::pair<unsigned int,unsigned int> iijj = find_first_index_pair(pindx,pmap);
if (i==j || iijj.first==iijj.second) { // Make deep copy if on diagonal
if (_hp == MISCMATHS::BFMatrixFloatPrecision) {
const SparseBFMatrix<float>& tmp = dynamic_cast<SparseBFMatrix<float>& >(*(subm[iijj.first][iijj.second]));
subm[i][j] = std::shared_ptr<MISCMATHS::BFMatrix>(new MISCMATHS::SparseBFMatrix<float>(tmp));
}
else {
const SparseBFMatrix<double>& tmp = dynamic_cast<SparseBFMatrix<double>& >(*(subm[iijj.first][iijj.second]));
subm[i][j] = std::shared_ptr<MISCMATHS::BFMatrix>(new MISCMATHS::SparseBFMatrix<double>(tmp));
}
}
else subm[i][j] = subm[iijj.first][iijj.second];
}
}
}
}
else throw EddyException("MoveBySuscCFImpl::calculate_second_order_subm: I should not be here.");
return;
} EddyCatch
void MoveBySuscCFImpl::calculate_cross_subm(// Input
const std::vector<ImageVec>& sop_dfdf,
const std::map<BpeVec,std::vector<ImageVec> >& sop_ff,
const std::map<BpeVec,std::vector<ImageVec> >& sop_fdf,
const NEWIMAGE::volume<char>& lmask,
unsigned int nvox,
// Output
std::vector<std::vector<std::shared_ptr<MISCMATHS::BFMatrix> > >& subm) const EddyTry
{
if (_order == 2)
{
std::vector<std::vector<std::vector<unsigned int> > > pmap = build_cross_pmap();
std::vector<unsigned int> noderiv(3,0); // No differentiation
NEWIMAGE::volume<float> ones = sop_dfdf[0][0]; ones = 1.0;
// First pass to calculate all unique sub-matrices
for (unsigned int i=0; i<no_of_second_order_fields(); i++) {
std::pair<unsigned int,unsigned int> first_pair = get_second_order_index_pair(i,no_of_second_order_fields());
for (unsigned int j=0; j<no_of_first_order_fields(); j++) {
std::vector<unsigned int> pindx = make_index_vector(first_pair,j,no_of_first_order_fields());
if (is_first_index_pair(i,j,pindx,pmap)) { // If this is the first instance of this index combination
subm[i][j] = _sp2[i][j]->JtJ(sop_dfdf[i][j],ones,&lmask,hessian_precision());
subm[i][j]->MulMeByScalar(2.0/static_cast<double>(nvox));
if (_ujm) {
for (const auto& elem : sop_ff) {
subm[i][j]->AddToMe(*_sp2[i][j]->JtJ(elem.first,elem.second[i][j],ones,&lmask,hessian_precision()),2.0/static_cast<double>(nvox));
}
for (const auto& elem : sop_fdf) {
std::shared_ptr<MISCMATHS::BFMatrix> tmp = _sp2[i][j]->JtJ(elem.first,elem.second[i][j],noderiv,ones,&lmask,hessian_precision());
subm[i][j]->AddToMe(*tmp,2.0/static_cast<double>(nvox));
subm[i][j]->AddToMe(*(tmp->Transpose()),2.0/static_cast<double>(nvox));
}
}
}
}
}
// Second pass to copy pointers into non-unique locations
for (unsigned int i=0; i<no_of_second_order_fields(); i++) {
std::pair<unsigned int,unsigned int> first_pair = get_second_order_index_pair(i,no_of_second_order_fields());
for (unsigned int j=0; j<no_of_first_order_fields(); j++) {
std::vector<unsigned int> pindx = make_index_vector(first_pair,j,no_of_first_order_fields());
if (!is_first_index_pair(i,j,pindx,pmap)) { // If this is NOT the first instance of this index combination
std::pair<unsigned int,unsigned int> iijj = find_first_index_pair(pindx,pmap);
subm[i][j] = subm[iijj.first][iijj.second];
}
}
}
}
else throw EddyException("MoveBySuscCFImpl::calculate_cross_subm: I should not be here.");
return;
} EddyCatch
bool MoveBySuscCFImpl::is_first_index_pair(unsigned int i,
unsigned int j,
const std::vector<unsigned int>& ivec,
const std::vector<std::vector<std::vector<unsigned int> > >& pmap) const EddyTry
{
if (pmap[i][j] != ivec) throw EddyException("MoveBySuscCFImpl::is_first_index_pair: ivec is not the i-j'th member of pmap.");
std::pair<unsigned int,unsigned int> first = find_first_index_pair(ivec,pmap);
if (first.first==i && first.second==j) return(true);
return(false);
} EddyCatch
std::pair<unsigned int,unsigned int> MoveBySuscCFImpl::find_first_index_pair(const std::vector<unsigned int>& ivec,
const std::vector<std::vector<std::vector<unsigned int> > >& pmap) const EddyTry
{
std::pair<unsigned int,unsigned int> first(0,0);
for ( ; first.first<pmap.size(); first.first++) {
for ( ; first.second<pmap[first.first].size(); first.second++) {
if (pmap[first.first][first.second] == ivec) return(first);
}
}
throw EddyException("MoveBySuscCFImpl::find_first_index_pair: ivec is not a member of pmap.");
} EddyCatch
std::vector<std::vector<std::vector<unsigned int> > > MoveBySuscCFImpl::build_second_order_pmap() const EddyTry
{
std::vector<std::vector<std::vector<unsigned int> > > pmap(no_of_second_order_fields());
for (unsigned int i=0; i<pmap.size(); i++) {
std::pair<unsigned int,unsigned int> first_pair = get_second_order_index_pair(i,no_of_second_order_fields());
pmap[i].resize(i+1);
for (unsigned j=0; j<(i+1); j++) {
std::pair<unsigned int,unsigned int> second_pair = get_second_order_index_pair(j,no_of_second_order_fields());
pmap[i][j] = make_index_vector(first_pair,second_pair,no_of_first_order_fields());
}
}
return(pmap);
} EddyCatch
std::vector<std::vector<std::vector<unsigned int> > > MoveBySuscCFImpl::build_cross_pmap() const EddyTry
{
std::vector<std::vector<std::vector<unsigned int> > > pmap(no_of_second_order_fields());
for (unsigned int i=0; i<pmap.size(); i++) {
std::pair<unsigned int,unsigned int> first_pair = get_second_order_index_pair(i,no_of_second_order_fields());
pmap[i].resize(no_of_first_order_fields());
for (unsigned j=0; j<no_of_first_order_fields(); j++) {
pmap[i][j] = make_index_vector(first_pair,j,no_of_first_order_fields());
}
}
return(pmap);
} EddyCatch
std::vector<unsigned int> MoveBySuscCFImpl::make_index_vector(const std::pair<unsigned int,unsigned int>& p1,
const std::pair<unsigned int,unsigned int>& p2,
unsigned int np) const EddyTry
{
std::vector<unsigned int> rval(np,0);
rval[p1.first]++; rval[p1.second]++; rval[p2.first]++; rval[p2.second]++;
return(rval);
} EddyCatch
std::vector<unsigned int> MoveBySuscCFImpl::make_index_vector(const std::pair<unsigned int,unsigned int>& p1,
unsigned int i,
unsigned int np) const EddyTry
{
std::vector<unsigned int> rval(np,0);
rval[p1.first]++; rval[p1.second]++; rval[i]++;
return(rval);
} EddyCatch
std::pair<unsigned int,unsigned int> MoveBySuscCFImpl::get_second_order_index_pair(unsigned int i,
unsigned int np) const EddyTry
{
std::pair<unsigned int,unsigned int> indx;
for (indx.first = 0; indx.first<np; indx.first++) {
for (indx.second = 0; indx.second<(indx.first+1); indx.second++) {
if (indx.first + indx.second == i) return(indx);
}
}
throw EddyException("MoveBySuscCFImpl::get_second_order_index_pair: I should not be here");
} EddyCatch
NEWMAT::ColumnVector MoveBySuscCFImpl::stl2newmat(const std::vector<double>& stl) const EddyTry
{
NEWMAT::ColumnVector nm(stl.size());
for (unsigned int i=0; i<stl.size(); i++) nm(i+1) = stl[i];
return(nm);
} EddyCatch
NEWMAT::Matrix MoveBySuscCFImpl::linspace(const NEWMAT::Matrix& inmat) const EddyTry
{
NEWMAT::Matrix rval(inmat.Nrows(),inmat.Ncols());
for (int r=1; r<=inmat.Nrows(); r++) rval(r,1) = r;
return(rval);
} EddyCatch
void MoveBySuscCFImpl::resize_containers_for_sum_images_for_grad(const std::vector<std::vector<unsigned int> >& indicies,
const std::vector<EDDY::ScanType>& imtypes) EddyTry
{
_sum_fo_deriv_diff.resize(_mps.size());
if (_order==2) {
_sum_so_deriv_diff.resize(_mps.size());
}
if (_ujm) {
for (unsigned int ti=0; ti<indicies.size(); ti++) {
for (unsigned int i=0; i<indicies[ti].size(); i++) {
NEWMAT::ColumnVector p = _sm.Scan(indicies[ti][i],imtypes[ti]).GetParams(EDDY::ParametersType::ZeroOrderMovement);
BpeVec bpe = _sm.Scan(indicies[ti][i],imtypes[ti]).GetAcqPara().BinarisedPhaseEncodeVector();
if (_sum_fo_ima_diff.find(bpe) == _sum_fo_ima_diff.end()) { // If this acqp has not been seen before
_sum_fo_ima_diff[bpe].resize(_mps.size());
if (_order==2) {
_sum_so_ima_diff[bpe].resize(_mps.size());
for (unsigned int pi=0; pi<_mps.size(); pi++) {
_sum_so_ima_diff[bpe][pi].resize(pi+1);
}
}
}
}
}
}
return;
} EddyCatch
void MoveBySuscCFImpl::resize_containers_for_sum_images_for_hess(const std::vector<std::vector<unsigned int> >& indicies,
const std::vector<EDDY::ScanType>& imtypes) EddyTry
{
// Start with the ones used for a first-order expansion
_sum_fo_deriv_deriv.resize(_mps.size());
for (unsigned int pi=0; pi<_mps.size(); pi++) _sum_fo_deriv_deriv[pi].resize(pi+1);
if (_ujm) {
for (unsigned int ti=0; ti<indicies.size(); ti++) {
for (unsigned int i=0; i<indicies[ti].size(); i++) {
NEWMAT::ColumnVector p = _sm.Scan(indicies[ti][i],imtypes[ti]).GetParams(EDDY::ParametersType::ZeroOrderMovement);
BpeVec bpe = _sm.Scan(indicies[ti][i],imtypes[ti]).GetAcqPara().BinarisedPhaseEncodeVector();
if (_sum_fo_ima_ima.find(bpe) == _sum_fo_ima_ima.end()) { // If this acqp has not been seen before
_sum_fo_ima_ima[bpe].resize(_mps.size());
_sum_fo_deriv_ima[bpe].resize(_mps.size());
for (unsigned int pi=0; pi<_mps.size(); pi++) {
_sum_fo_ima_ima[bpe][pi].resize(pi+1);
_sum_fo_deriv_ima[bpe][pi].resize(pi+1);
}
}
}
}
}
// Then do the ones used for a second-order expansion
if (_order == 2) {
_sum_so_deriv_deriv.resize(no_of_second_order_fields());
_sum_cross_deriv_deriv.resize(no_of_second_order_fields());
for (unsigned int i=0; i<no_of_second_order_fields(); i++) {
_sum_so_deriv_deriv[i].resize(i+1);
_sum_cross_deriv_deriv.resize(no_of_first_order_fields());
}
if (_ujm) {
for (unsigned int ti=0; ti<indicies.size(); ti++) {
for (unsigned int i=0; i<indicies[ti].size(); i++) {
NEWMAT::ColumnVector p = _sm.Scan(indicies[ti][i],imtypes[ti]).GetParams(EDDY::ParametersType::ZeroOrderMovement);
BpeVec bpe = _sm.Scan(indicies[ti][i],imtypes[ti]).GetAcqPara().BinarisedPhaseEncodeVector();
if (_sum_so_ima_ima.find(bpe) == _sum_so_ima_ima.end()) { // If this acqp has not been seen before
_sum_so_ima_ima[bpe].resize(no_of_second_order_fields());
_sum_so_deriv_ima[bpe].resize(no_of_second_order_fields());
_sum_cross_ima_ima[bpe].resize(no_of_second_order_fields());
_sum_cross_deriv_ima[bpe].resize(no_of_second_order_fields());
for (unsigned j=0; j<no_of_second_order_fields(); j++) {
_sum_so_ima_ima[bpe][j].resize(j+1);
_sum_so_deriv_ima[bpe][j].resize(j+1);
_sum_cross_ima_ima[bpe][j].resize(no_of_first_order_fields());
_sum_cross_deriv_ima[bpe][j].resize(no_of_first_order_fields());
}
}
}
}
}
}
return;
} EddyCatch
void MoveBySuscCFImpl::set_sum_images_to_zero(const NEWIMAGE::volume<float>& ima) EddyTry
{
NEWIMAGE::volume<float> zeros = ima; zeros = 0.0;
// Field for .cf()
_sum_sqr_diff = zeros;
// Fields for .grad()
for (unsigned int pi=0; pi<_mps.size(); pi++) {
_sum_fo_deriv_diff[pi] = zeros;
for (auto& elem : _sum_fo_ima_diff) elem.second[pi] = zeros;
if (_order == 2) {
for (unsigned int pj=0; pj<(pi+1); pj++) {
_sum_so_deriv_diff[pi][pj] = zeros;
for (auto& elem : _sum_so_ima_diff) elem.second[pi][pj] = zeros;
}
}
}
// Fields for .hess()
for (unsigned int pi=0; pi<_mps.size(); pi++) {
for (unsigned int pj=0; pj<(pi+1); pj++) {
_sum_fo_deriv_deriv[pi][pj] = zeros;
for (auto& elem : _sum_fo_ima_ima) elem.second[pi][pj] = zeros;
for (auto& elem : _sum_fo_deriv_ima) elem.second[pi][pj] = zeros;
}
}
if (_order == 2) {
for (unsigned int i=0; i<no_of_second_order_fields(); i++) {
for (unsigned int j=0; j<(i+1); j++) {
_sum_so_deriv_deriv[i][j] = zeros;
for (auto& elem : _sum_so_ima_ima) elem.second[i][j] = zeros;
for (auto& elem : _sum_so_deriv_ima) elem.second[i][j] = zeros;
}
for (unsigned int j=0; j<no_of_first_order_fields(); j++) {
_sum_cross_deriv_deriv[i][j] = zeros;
for (auto& elem : _sum_cross_ima_ima) elem.second[i][j] = zeros;
for (auto& elem : _sum_cross_deriv_ima) elem.second[i][j] = zeros;
}
}
}
return;
} EddyCatch
void MoveBySuscCFImpl::recalculate_sum_so_imas_for_hess(const NEWMAT::ColumnVector& p,
const BpeVec& bpe,
const NEWIMAGE::volume<float>& ima,
const NEWIMAGE::volume<float>& deriv) EddyTry
{
if (!_utd && _order==2) {
std::vector<std::vector<std::vector<unsigned int> > > pmap = build_second_order_pmap();
for (unsigned int i=0; i<no_of_second_order_fields(); i++) {
std::pair<unsigned int,unsigned int> first_pair = get_second_order_index_pair(i,no_of_second_order_fields());
for (unsigned int j=0; j<(i+1); j++) {
std::pair<unsigned int,unsigned int> second_pair = get_second_order_index_pair(j,no_of_second_order_fields());
std::vector<unsigned int> pindx = make_index_vector(first_pair,second_pair,no_of_first_order_fields());
if (is_first_index_pair(i,j,pindx,pmap)) { // If this is the first instance of this index combination
float factor=1.0;
for (unsigned int ii=0; ii<pindx.size(); ii++) {
for (unsigned int jj=0; jj<pindx[ii]; jj++) factor *= p(_mps[ii]+1);
}
// Voxel displacement term
_sum_so_deriv_deriv[i][j] += factor*deriv*deriv;
if (_ujm) {
// Jacobian modulation term
_sum_so_ima_ima[bpe][i][j] += factor*ima*ima;
// Cross term
_sum_so_deriv_ima[bpe][i][j] += factor*ima*deriv;
}
}
}
}
}
else throw EddyException("MoveBySuscCFImpl::recalculate_sum_so_imas_for_hess: I should not be here.");
return;
} EddyCatch
void MoveBySuscCFImpl::recalculate_sum_cross_imas_for_hess(const NEWMAT::ColumnVector& p,
const BpeVec& bpe,
const NEWIMAGE::volume<float>& ima,
const NEWIMAGE::volume<float>& deriv) EddyTry
{
if (!_utd && _order==2) {
std::vector<std::vector<std::vector<unsigned int> > > pmap = build_cross_pmap();
for (unsigned int i=0; i<no_of_second_order_fields(); i++) {
std::pair<unsigned int,unsigned int> first_pair = get_second_order_index_pair(i,no_of_second_order_fields());
for (unsigned int j=0; j<no_of_first_order_fields(); j++) {
std::vector<unsigned int> pindx = make_index_vector(first_pair,j,no_of_first_order_fields());
if (is_first_index_pair(i,j,pindx,pmap)) { // If this is the first instance of this index combination
float factor=1.0;
for (unsigned int ii=0; ii<pindx.size(); ii++) {
for (unsigned int jj=0; jj<pindx[ii]; jj++) factor *= p(_mps[ii]+1);
}
// Voxel displacement term
_sum_cross_deriv_deriv[i][j] += factor*deriv*deriv;
if (_ujm) {
// Jacobian modulation term
_sum_cross_ima_ima[bpe][i][j] += factor*ima*ima;
// Cross term
_sum_cross_deriv_ima[bpe][i][j] += factor*ima*deriv;
}
}
}
}
}
else throw EddyException("MoveBySuscCFImpl::recalculate_sum_cross_imas_for_hess: I should not be here.");
return;
} EddyCatch
void MoveBySuscCFImpl::recalculate_sum_images() EddyTry
{
if (!_utd || !_m_utd) {
// Make some temporary vectors to allow us to use the same code for B0 and DWI.
std::vector<std::vector<unsigned int> > indicies = {_b0s, _dwis};
std::vector<EDDY::ScanType> imtypes = {ScanType::b0, ScanType::DWI};
std::vector<NEWIMAGE::volume<float> > masks = {_sm.Scan(0,ScanType::Any).GetIma(), _sm.Scan(0,ScanType::Any).GetIma()};
std::vector<std::shared_ptr<EDDY::DWIPredictionMaker> > pmps(2,nullptr);
if (_bno) { // If Brand New Object
resize_containers_for_sum_images_for_grad(indicies,imtypes);
resize_containers_for_sum_images_for_hess(indicies,imtypes);
// Load up prediction maker and estimate (and save) hyper-parameters
if (_clo.NVoxHp() < 10000) _clo.SetNVoxHp(10000); // Make sure it is done well
#ifdef COMPILE_GPU // If it shall be compiled for GPU
if (indicies[0].size()) pmps[0] = EddyGpuUtils::LoadPredictionMaker(_clo,imtypes[0],_sm,0,0.0,masks[0]); // B0 predictor
if (indicies[1].size()) { // DWI predictor
pmps[1] = EddyGpuUtils::LoadPredictionMaker(_clo,imtypes[1],_sm,0,0.0,masks[1]);
_hypar = this->stl2newmat(pmps[1]->GetHyperPar());
}
#else
if (indicies[0].size()) pmps[0] = EDDY::LoadPredictionMaker(_clo,imtypes[0],_sm,0,0.0,masks[0]); // B0 predictor
if (indicies[1].size()) { // DWI predictor
pmps[1] = EDDY::LoadPredictionMaker(_clo,imtypes[1],_sm,0,0.0,masks[1]);
_hypar = this->stl2newmat(pmps[1]->GetHyperPar());
}
#endif
_bno = false;
}
else {
// Load up prediction maker and use saved hyper-parameters
_clo.SetHyperParValues(_hypar); // Clunk!
#ifdef COMPILE_GPU // If it shall be compiled for GPU
if (indicies[0].size()) pmps[0] = EddyGpuUtils::LoadPredictionMaker(_clo,imtypes[0],_sm,0,0.0,masks[0]); // B0 predictor
if (indicies[1].size()) pmps[1] = EddyGpuUtils::LoadPredictionMaker(_clo,imtypes[1],_sm,0,0.0,masks[1]); // DWI predictor
#else
if (indicies[0].size()) pmps[0] = EDDY::LoadPredictionMaker(_clo,imtypes[0],_sm,0,0.0,masks[0]); // B0 predictor
if (indicies[1].size()) pmps[1] = EDDY::LoadPredictionMaker(_clo,imtypes[1],_sm,0,0.0,masks[1]); // DWI predictor
#endif
}
// Make mask
_mask = masks[0] * masks[1];
_nvox = static_cast<unsigned int>(std::round(_mask.sum()));
_cmask.reinitialize(_mask.xsize(),_mask.ysize(),_mask.zsize());
NEWIMAGE::copybasicproperties(_mask,_cmask);
std::vector<int> tsz = {static_cast<int>(_mask.xsize()), static_cast<int>(_mask.ysize()), static_cast<int>(_mask.zsize())};
for (int k=0; k<tsz[2]; k++) for (int j=0; j<tsz[1]; j++) for (int i=0; i<tsz[0]; i++) _cmask(i,j,k) = (_mask(i,j,k) > 0.0) ? 1 : 0;
// This is where we start to generate the sum images
// First set them all to zero
set_sum_images_to_zero(_mask);
// And then start doing the actual summing
NEWIMAGE::volume4D<float> deriv(_mask.xsize(),_mask.ysize(),_mask.zsize(),3);
NEWIMAGE::copybasicproperties(_mask,deriv);
NEWIMAGE::volume<float> sderiv = _mask; // Scalar "derivative" image
std::vector<double> vxs = { _mask.xdim(), _mask.ydim(), _mask.zdim() };
for (unsigned int ti=0; ti<indicies.size(); ti++) { // Loop over b0 and DWI
for (unsigned int i=0; i<indicies[ti].size(); i++) { // Loop over all images
NEWMAT::ColumnVector p = _sm.Scan(indicies[ti][i],imtypes[ti]).GetParams(EDDY::ParametersType::ZeroOrderMovement);
NEWMAT::ColumnVector hz2mm = _sm.Scan(indicies[ti][i],imtypes[ti]).GetHz2mmVector();
std::vector<unsigned int> bpe = _sm.Scan(indicies[ti][i],imtypes[ti]).GetAcqPara().BinarisedPhaseEncodeVector();
#ifdef COMPILE_GPU // If it shall be compiled for GPU
NEWIMAGE::volume<float> vol = EddyGpuUtils::GetVolumetricUnwarpedScan(_sm.Scan(indicies[ti][i],imtypes[ti]),_sm.GetSuscHzOffResField(indicies[ti][i],imtypes[ti]),_sm.GetBiasField(),false,nullptr,&deriv);
#else
NEWIMAGE::volume<float> vol = _sm.Scan(indicies[ti][i],imtypes[ti]).GetVolumetricUnwarpedIma(_sm.GetSuscHzOffResField(indicies[ti][i],imtypes[ti]),deriv);
#endif
NEWIMAGE::volume<float> diff = vol - pmps[ti]->Predict(indicies[ti][i]);
_sum_sqr_diff += diff * diff;
sderiv = static_cast<float>(hz2mm(1)/vxs[0])*deriv[0] + static_cast<float>(hz2mm(2)/vxs[1])*deriv[1] + static_cast<float>(hz2mm(3)/vxs[2])*deriv[2];
if (_ujm) vol *= static_cast<float>(hz2mm(1)/vxs[0] + hz2mm(2)/vxs[1] + hz2mm(3)/vxs[2]); // Assumes only one of hz2mm is non-zero
// First sum the images used for .grad()
for (unsigned int pi=0; pi<_mps.size(); pi++) {
_sum_fo_deriv_diff[pi] += static_cast<float>(p(_mps[pi]+1))*sderiv*diff;
if (_ujm) _sum_fo_ima_diff[bpe][pi] += static_cast<float>(p(_mps[pi]+1))*vol*diff;
if (_order == 2) {
for (unsigned int pj=0; pj<(pi+1); pj++) {
_sum_so_deriv_diff[pi][pj] += static_cast<float>(p(_mps[pi]+1)*p(_mps[pj]+1))*sderiv*diff;
if (_ujm) _sum_so_ima_diff[bpe][pi][pj] += static_cast<float>(p(_mps[pi]+1)*p(_mps[pj]+1))*vol*diff;
}
}
}
// Then sum the images used for .hess()
for (unsigned int pi=0; pi<_mps.size(); pi++) {
for (unsigned int pj=0; pj<(pi+1); pj++) {
_sum_fo_deriv_deriv[pi][pj] += static_cast<float>(p(_mps[pi]+1)*p(_mps[pj]+1))*sderiv*sderiv;
if (_ujm) {
_sum_fo_ima_ima[bpe][pi][pj] += static_cast<float>(p(_mps[pi]+1)*p(_mps[pj]+1))*vol*vol;
_sum_fo_deriv_ima[bpe][pi][pj] += static_cast<float>(p(_mps[pi]+1)*p(_mps[pj]+1))*sderiv*vol;
}
}
}
if (_order == 2) { // Farm out 2nd order summation
recalculate_sum_so_imas_for_hess(p,bpe,vol,sderiv); // Second-order sub-matrix of total Hessian
recalculate_sum_cross_imas_for_hess(p,bpe,vol,sderiv); // Cross-term between first and second order sub-matrix
}
}
}
_m_utd = true;
_utd = true;
}
return;
} EddyCatch
} // End namespace EDDY
// Declarations of classes and functions that
// calculates the derivative fields for the
// movement-by-susceptibility modelling.
//
// MoveBySuscCF.h
//
// Jesper Andersson, FMRIB Image Analysis Group
//
// Copyright (C) 2016 University of Oxford
//
#ifndef MoveBySuscCF_h
#define MoveBySuscCF_h
#include <cstdlib>
#include <string>
#include <vector>
#include <cmath>
#include <memory>
#include "armawrap/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 "miscmaths/nonlin.h"
#include "EddyHelperClasses.h"
#include "ECModels.h"
#include "EddyCommandLineOptions.h"
namespace EDDY {
/****************************************************************//**
*
* \brief Class used to implement a cost-function for estimating
* the derivative fields of a movement-by-susceptibility model
* in the eddy project.
*
* Class used to implement a Mutual Information cost-
* function for post-hoc registration of shells in the eddy project.
* It is implemented using the "Pimpl idiom" which means that this class
* only implements an interface whereas the actual work is being performed
* by the MoveBySuscCFImpl class which is declared and defined in
* MoveBySuscCF.cpp or cuda/MoveBySuscCF.cu depending on what platform
* the code is compiled for.
*
********************************************************************/
class MoveBySuscCFImpl;
class MoveBySuscCF : public MISCMATHS::NonlinCF
{
public:
MoveBySuscCF(EDDY::ECScanManager& sm,
const EDDY::EddyCommandLineOptions& clo,
const std::vector<unsigned int>& b0s,
const std::vector<unsigned int>& dwis,
const std::vector<unsigned int>& mps,
unsigned int order,
double ksp);
~MoveBySuscCF();
double cf(const NEWMAT::ColumnVector& p) const;
NEWMAT::ReturnMatrix grad(const NEWMAT::ColumnVector& p) const;
std::shared_ptr<MISCMATHS::BFMatrix> hess(const NEWMAT::ColumnVector& p,
std::shared_ptr<MISCMATHS::BFMatrix> iptr=std::shared_ptr<MISCMATHS::BFMatrix>()) const;
void SetLambda(double lambda);
NEWMAT::ReturnMatrix Par() const;
unsigned int NPar() const;
void WriteFirstOrderFields(const std::string& fname) const;
void WriteSecondOrderFields(const std::string& fname) const;
void ResetCache();
private:
mutable MoveBySuscCFImpl *_pimpl;
};
} // End namespace EDDY
#endif // End #ifndef MoveBySuscCF_h
/*! \file PostEddyAlignShellsFunctions.cpp
\brief Contains some high level global functions used to
align shells to each other and to b0 after the "main eddy"
has completed.
*/
#include <iostream>
#include <cstdlib>
#include <string>
#include <vector>
#include <cmath>
#include <memory>
#include <thread>
#include "armawrap/newmat.h"
#include "topup/topup_file_io.h"
#include "EddyCommandLineOptions.h"
#include "PostEddyCF.h"
#include "PostEddyAlignShellsFunctions.h"
#ifdef COMPILE_GPU
#include "cuda/EddyGpuUtils.h"
#endif
using namespace std;
namespace EDDY {
void PEASUtils::PostEddyAlignShells(// Input
const EddyCommandLineOptions& clo,
bool upe, // Update parameter estimates or not
// Input/Output
ECScanManager& sm) EddyTry
{
std::vector<double> grpb;
std::vector<NEWMAT::ColumnVector> mi_mov_par;
std::vector<NEWMAT::ColumnVector> mi_mp_for_updates;
std::vector<std::vector<NEWMAT::ColumnVector> > mi_cmov_par;
std::vector<NEWMAT::ColumnVector> b0_mov_par;
std::vector<NEWMAT::ColumnVector> b0_mp_for_updates;
if (sm.B0sAreUsefulForPEAS()) {
if (clo.VeryVerbose()) cout << "Using interspersed b0's to estimate between shell movement" << endl;
PEASUtils::align_shells_using_interspersed_B0_scans(clo,sm,grpb,b0_mov_par,b0_mp_for_updates);
}
if (clo.VeryVerbose()) cout << "Using MI to estimate between shell movement" << endl;
PEASUtils::align_shells_using_MI(clo,false,sm,grpb,mi_mov_par,mi_cmov_par,mi_mp_for_updates); // Align based on MI between shell means
// Write report
PEASUtils::write_post_eddy_align_shells_report(mi_mov_par,mi_mp_for_updates,mi_cmov_par,b0_mov_par,b0_mp_for_updates,grpb,upe,clo);
// Update parameter estimates if requested
if (upe) {
std::vector<NEWMAT::ColumnVector> mp_for_updates;
if (clo.UseB0sToAlignShellsPostEddy()) mp_for_updates = b0_mp_for_updates;
else mp_for_updates = mi_mp_for_updates;
std::vector<std::vector<unsigned int> > dwi_indx = sm.GetShellIndicies(grpb);
for (unsigned int i=0; i<mp_for_updates.size(); i++) {
if (clo.VeryVerbose()) cout << "Parameters for shell " << grpb[i] << " to b0" << endl;
if (clo.VeryVerbose()) cout << "mp_for_updates = " << mp_for_updates[i] << endl;
}
if (clo.VeryVerbose()) cout << "Updating parameter estimates" << endl;
for (unsigned int i=0; i<dwi_indx.size(); i++) {
if (clo.VeryVerbose()) cout << "Updating parmeter estimates for shell " << i << endl;
PEASUtils::update_mov_par_estimates(mp_for_updates[i],dwi_indx[i],sm);
}
}
return;
} EddyCatch
void PEASUtils::PostEddyAlignShellsAlongPE(// Input
const EddyCommandLineOptions& clo,
bool upe, // Update parameter estimates or not
// Input/Output
ECScanManager& sm) EddyTry
{
// If there are scans with PE in x AND y there should be no ambiguity
if (sm.HasPEinXandY()) return;
std::vector<double> grpb;
std::vector<NEWMAT::ColumnVector> mi_mov_par;
std::vector<NEWMAT::ColumnVector> mi_mp_for_updates;
std::vector<std::vector<NEWMAT::ColumnVector> > mi_cmov_par;
if (clo.VeryVerbose()) cout << "Using MI to estimate between shell translation along PE" << endl;
PEASUtils::align_shells_using_MI(clo,true,sm,grpb,mi_mov_par,mi_cmov_par,mi_mp_for_updates); // Align based on MI between shell means
// Write report
PEASUtils::write_post_eddy_align_shells_along_PE_report(mi_mov_par,mi_mp_for_updates,mi_cmov_par,grpb,upe,clo);
// Update parameter estimates if requested
if (upe) {
std::vector<std::vector<unsigned int> > dwi_indx = sm.GetShellIndicies(grpb);
for (unsigned int i=0; i<mi_mp_for_updates.size(); i++) {
if (clo.VeryVerbose()) cout << "Parameters for shell " << grpb[i] << " to b0" << endl;
if (clo.VeryVerbose()) cout << "mp_for_updates = " << mi_mp_for_updates[i] << endl;
}
if (clo.VeryVerbose()) cout << "Updating parameter estimates" << endl;
for (unsigned int i=0; i<dwi_indx.size(); i++) {
if (clo.VeryVerbose()) cout << "Updating parmeter estimates for shell " << i << endl;
PEASUtils::update_mov_par_estimates(mi_mp_for_updates[i],dwi_indx[i],sm);
}
}
return;
} EddyCatch
void PEASUtils::WritePostEddyBetweenShellMIValues(// Input
const EddyCommandLineOptions& clo,
const ECScanManager& sm,
const std::vector<unsigned int>& n,
const std::vector<double>& first,
const std::vector<double>& last,
const std::string& bfname) EddyTry
{
// Get mean b0 volume
NEWIMAGE::volume<float> b0_mask = sm.Scan(0,ScanType::Any).GetIma(); b0_mask=1.0;
std::vector<unsigned int> b0_indx = sm.GetB0Indicies();
NEWIMAGE::volume<float> b0_mean = PEASUtils::get_mean_scan(clo,sm,b0_indx,b0_mask);
// NEWIMAGE::write_volume(b0_mean,"mean_b0_volume");
// Get mean volumes for all shells
if (clo.VeryVerbose()) cout << "Calculating shell means" << endl;
std::vector<NEWIMAGE::volume<float> > dwi_means;
std::vector<double> grpb;
NEWIMAGE::volume<float> mask = sm.Scan(0,ScanType::Any).GetIma(); mask=1.0;
if (sm.IsShelled()) { // Get shell means
std::vector<std::vector<unsigned int> > dwi_indx = sm.GetShellIndicies(grpb);
dwi_means.resize(dwi_indx.size());
NEWIMAGE::volume<float> tmpmask;
for (unsigned int i=0; i<dwi_indx.size(); i++) {
tmpmask = 1.0;
dwi_means[i] = PEASUtils::get_mean_scan(clo,sm,dwi_indx[i],tmpmask);
mask *= tmpmask;
}
}
else { // Get mean of all dwi's
}
mask *= b0_mask; // Mask valid for b0 and dwi
// Get and write MI values for all combinations of b0 and dwi shells
for (unsigned int i=0; i<dwi_means.size(); i++) {
char ofname[256]; sprintf(ofname,"%s_b0_b%d",bfname.c_str(),100*static_cast<int>(std::round(grpb[i]/100.0)));
write_between_shell_MI_values(b0_mean,dwi_means[i],mask,ofname,n,first,last);
}
} EddyCatch
/****************************************************************//**
*
* A static and private function in PEASUtils
* \param[in] clo Command line options
* \param[in] sm Collection of all scans.
* \param[in] indx List of indices that the mean should be based on.
* \param[out] mask A mask for which the mean is valid.
* \return A mean volume
*
********************************************************************/
NEWIMAGE::volume<float> PEASUtils::get_mean_scan(// Input
const EddyCommandLineOptions& clo,
const ECScanManager& sm,
const std::vector<unsigned int>& indx,
// Output
NEWIMAGE::volume<float>& mask) EddyTry
{
NEWIMAGE::volume<float> mean = sm.Scan(0,ScanType::Any).GetIma(); mean = 0.0;
mask = sm.Scan(0,ScanType::Any).GetIma(); EddyUtils::SetTrilinearInterp(mask); mask = 1.0;
if (clo.VeryVerbose()) cout << "Calculating shell means";
if (clo.VeryVerbose()) cout << endl << "Scan: " << endl;
#ifdef COMPILE_GPU
NEWIMAGE::volume<float> tmpmask = sm.Scan(0,ScanType::Any).GetIma();
EddyUtils::SetTrilinearInterp(tmpmask);
for (unsigned int i=0; i<indx.size(); i++) {
if (clo.VeryVerbose()) printf("%d ",indx[i]);
tmpmask = 1.0;
mean += EddyGpuUtils::GetUnwarpedScan(sm.Scan(indx[i],ScanType::Any),sm.GetSuscHzOffResField(indx[i],ScanType::Any),sm.GetBiasField(),false,&tmpmask);
mask *= tmpmask;
}
#else
if (clo.NumberOfThreads() == 1) { // If we are to run single thread
for (int i=0; i<int(indx.size()); i++) {
if (clo.VeryVerbose()) { cout << indx[i] << " "; cout.flush(); }
NEWIMAGE::volume<float> tmpmask = sm.Scan(0,ScanType::Any).GetIma();
EddyUtils::SetTrilinearInterp(tmpmask); tmpmask = 1.0;
mean += sm.GetUnwarpedScan(indx[i],tmpmask,ScanType::Any);
mask *= tmpmask;
}
}
else { // We are to run multi-threaded
// Decide number of volumes per thread
std::vector<unsigned int> nvols = EddyUtils::ScansPerThread(indx.size(),clo.NumberOfThreads());
// Next spawn threads to do the calculations
std::vector<std::thread> threads(clo.NumberOfThreads()-1); // + main thread makes clo.NumberOfThreads()
for (unsigned int i=0; i<clo.NumberOfThreads()-1; i++) {
threads[i] = std::thread(get_mean_scan_wrapper,nvols[i],nvols[i+1],std::ref(sm),std::ref(indx),
clo.VeryVerbose(),std::ref(mean),std::ref(mask));
}
get_mean_scan_wrapper(nvols[clo.NumberOfThreads()-1],nvols[clo.NumberOfThreads()],sm,indx,clo.VeryVerbose(),mean,mask);
std::for_each(threads.begin(),threads.end(),std::mem_fn(&std::thread::join));
}
#endif
if (clo.VeryVerbose()) printf("\n");
mean /= indx.size();
return(mean);
} EddyCatch
/****************************************************************//**
*
* A static and private function in PEASUtils. It only serves
* as a wrapper for "get_mean_scan" to facilitate using C++11
* threads for parallelisation.
* \param[in] sm Collection of all scans.
* \param[in] indx List of indices that the mean should be based on.
* \param[out] mask A mean volume
* \param[out] mask A mask for which the mean is valid.
* \return None
*
********************************************************************/
void PEASUtils::get_mean_scan_wrapper(unsigned int first_index,
unsigned int last_index,
const ECScanManager& sm,
const std::vector<unsigned int>& indx,
bool very_verbose,
// Output
NEWIMAGE::volume<float>& mean,
NEWIMAGE::volume<float>& mask) EddyTry
{
static std::mutex cout_mutex;
static std::mutex add_mutex;
static std::mutex mul_mutex;
for (unsigned int i=first_index; i<last_index; i++) {
if (very_verbose) {
cout_mutex.lock();
cout << indx[i] << " "; cout.flush();
cout_mutex.unlock();
}
NEWIMAGE::volume<float> tmpmask = sm.Scan(0,ScanType::Any).GetIma();
EddyUtils::SetTrilinearInterp(tmpmask); tmpmask = 1.0;
NEWIMAGE::volume<float> tmpima = sm.GetUnwarpedScan(indx[i],tmpmask,ScanType::Any);
add_mutex.lock();
mean += tmpima;
add_mutex.unlock();
mul_mutex.lock();
mask *= tmpmask;
mul_mutex.unlock();
}
} EddyCatch
NEWMAT::ColumnVector PEASUtils::register_volumes(// Input
const NEWIMAGE::volume<float>& ref,
const NEWIMAGE::volume<float>& ima,
const NEWIMAGE::volume<float>& mask,
// Output
NEWIMAGE::volume<float>& rima) EddyTry
{
EddyUtils::SetSplineInterp(ima);
EddyUtils::SetTrilinearInterp(mask);
MISCMATHS::NonlinParam nlpar(6,MISCMATHS::NL_NM);
// Set starting simplex to 1mm and 1 degree.
NEWMAT::ColumnVector ss(6); ss=1.0; for (int i=3; i<6; i++) ss(i+1) = 3.1415 / 180.0;
nlpar.SetStartAmoeba(ss);
// Make MI cost-function object with 256 bins
// 256 is MJ's recommendation, and also looks the smoothest in my testing.
PostEddyCF ecf(ref,ima,mask,256);
// Run optimisation
MISCMATHS::nonlin(nlpar,ecf);
rima = ecf.GetTransformedIma(nlpar.Par());
return(nlpar.Par());
} EddyCatch
NEWMAT::ColumnVector PEASUtils::register_volumes_along_PE(// Input
const NEWIMAGE::volume<float>& ref,
const NEWIMAGE::volume<float>& ima,
const NEWIMAGE::volume<float>& mask,
unsigned int pe_dir,
// Output
NEWIMAGE::volume<float>& rima) EddyTry
{
EddyUtils::SetSplineInterp(ima);
EddyUtils::SetTrilinearInterp(mask);
MISCMATHS::NonlinParam nlpar(1,MISCMATHS::NL_NM);
// Set starting simplex (actually just a line in this case) to 1mm
NEWMAT::ColumnVector ss(1); ss(1)=1.0;
nlpar.SetStartAmoeba(ss);
// Make MI cost-function object with 256 bins
// 256 is MJ's recommendation, and also looks the smoothest in my testing.
PostEddyCF ecf(ref,ima,mask,256,pe_dir);
// Run optimisation
MISCMATHS::nonlin(nlpar,ecf);
rima = ecf.GetTransformedIma(nlpar.Par());
return(nlpar.Par());
} EddyCatch
void PEASUtils::register_volumes_wrapper(// Input
unsigned int first_reg,
unsigned int last_reg,
const std::vector<std::pair<int,unsigned int> >& regs,
bool pe_only,
unsigned int pe_dir,
const NEWIMAGE::volume<float>& b0,
const std::vector<NEWIMAGE::volume<float> >& dwis,
const NEWIMAGE::volume<float>& mask,
const std::vector<double>& grpb,
bool vv,
// Output
std::vector<NEWMAT::ColumnVector>& mov_par,
std::vector<std::vector<NEWMAT::ColumnVector> >& cmov_par) EddyTry
{
static std::mutex cout_mutex;
NEWIMAGE::volume<float> rima;
for (unsigned int i=first_reg; i<last_reg; i++) {
if (pe_only) { // We are to register along PE only
if (regs[i].first < 0) { // We are to register to b0
mov_par[regs[i].second] = PEASUtils::register_volumes_along_PE(b0,dwis[regs[i].second],mask,pe_dir,rima);
if (vv) { // If very verbose
const std::lock_guard<std::mutex> lock(cout_mutex);
cout << "Registered shell " << grpb[regs[i].second] << " along PE to b0" << endl << std::flush;
cout << "PE-translation = " << PEASUtils::format_mp(mov_par[regs[i].second]) << endl << std::flush;
}
}
else { // If we are to register shells to each other
cmov_par[regs[i].first][regs[i].second] = PEASUtils::register_volumes_along_PE(dwis[regs[i].first],dwis[regs[i].second],mask,pe_dir,rima);
if (vv) { // If very verbose
const std::lock_guard<std::mutex> lock(cout_mutex);
cout << "Registered shell " << regs[i].second << " along PE to shell " << regs[i].first << endl << std::flush;
cout << "Registered shell " << grpb[regs[i].second] << " along PE to shell " << grpb[regs[i].first] << endl << std::flush;
cout << "PE-translation = " << PEASUtils::format_mp(cmov_par[regs[i].first][regs[i].second]) << endl << std::flush;
}
}
}
else { // We are to do a full rigid body registration
if (regs[i].first < 0) { // We are to register to b0
mov_par[regs[i].second] = PEASUtils::register_volumes(b0,dwis[regs[i].second],mask,rima);
if (vv) { // If very verbose
const std::lock_guard<std::mutex> lock(cout_mutex);
cout << "Registered shell " << grpb[regs[i].second] << " along PE to b0" << endl << std::flush;
cout << "Movement parameters: " << PEASUtils::format_mp(mov_par[regs[i].second]) << endl << std::flush;
}
}
else { // If we are to register shells to each other
cmov_par[regs[i].first][regs[i].second] = PEASUtils::register_volumes(dwis[regs[i].first],dwis[regs[i].second],mask,rima);
if (vv) { // If very verbose
const std::lock_guard<std::mutex> lock(cout_mutex);
cout << "Registered shell " << regs[i].second << " along PE to shell " << regs[i].first << endl << std::flush;
cout << "Registered shell " << grpb[regs[i].second] << " along PE to shell " << grpb[regs[i].first] << endl << std::flush;
cout << "Movement parameters: " << PEASUtils::format_mp(cmov_par[regs[i].first][regs[i].second]) << endl << std::flush;
}
}
}
}
} EddyCatch
std::string PEASUtils::format_mp(const NEWMAT::ColumnVector& mp) EddyTry
{
char str[256];
if (mp.Nrows() == 1) {
sprintf(str,"%5.2fmm",mp(1));
}
else if (mp.Nrows() == 6) {
sprintf(str,"xt = %5.2fmm, yt = %5.2fmm, zt = %5.2fmm, xr = %5.2fdeg, yr = %5.2fdeg, zr = %5.2fdeg",mp(1),mp(2),mp(3),180*mp(4)/M_PI,180*mp(5)/M_PI,180*mp(6)/M_PI);
}
else throw EddyException("EDDY::PEASUtils::format_mp: mp must have 1 or 6 elements");
return(std::string(str));
} EddyCatch
std::vector<NEWMAT::ColumnVector> PEASUtils::collate_mov_par_estimates_for_use(const std::vector<NEWMAT::ColumnVector>& mp,
const std::vector<std::vector<NEWMAT::ColumnVector> >& cmp,
const NEWIMAGE::volume<float>& ima) EddyTry
{
/*
// This bit of code combines estimates of lowest b-val shell to
// b0 with estimates from all other shells to lowest b-val
std::vector<NEWMAT::ColumnVector> omp = mp;
for (unsigned int i=1; i<omp.size(); i++) {
NEWMAT::Matrix A = TOPUP::MovePar2Matrix(mp[0],ima) * TOPUP::MovePar2Matrix(cmp[0][i],ima);
omp[i] = TOPUP::Matrix2MovePar(A,ima);
}
*/
// This bit of code uses the direct estimates of each shell to b0
std::vector<NEWMAT::ColumnVector> omp = mp;
/*
// This bit of code uses the direct estimates of the lowest shell to b0
// and then assumes that the shells are already aligned to each other.
std::vector<NEWMAT::ColumnVector> omp = mp;
for (unsigned int i=1; i<omp.size(); i++) omp[i] = omp[0];
*/
return(omp);
} EddyCatch
void PEASUtils::write_post_eddy_align_shells_report(const std::vector<NEWMAT::ColumnVector>& mi_dmp,
const std::vector<NEWMAT::ColumnVector>& mi_ump,
const std::vector<std::vector<NEWMAT::ColumnVector> >& mi_cmp,
const std::vector<NEWMAT::ColumnVector>& b0_dmp,
const std::vector<NEWMAT::ColumnVector>& b0_ump,
const std::vector<double>& grpb,
bool upe,
const EddyCommandLineOptions& clo) EddyTry
{
try {
std::ofstream file;
double pi = 3.14159;
file.open(clo.PeasReportFname().c_str(),ios::out|ios::trunc);
file << "These between shell parameters were calculated using MI between shell means" << endl;
file << setprecision(3) << "Movement parameters relative b0-shell from direct registration to mean b0" << endl;
for (unsigned int i=0; i<mi_dmp.size(); i++) {
file << "Shell " << grpb[i] << " to b0-shell" << endl;
file << setw(10) << "x-tr (mm)" << setw(10) << "y-tr (mm)" << setw(10) << "z-tr (mm)" << setw(12) << "x-rot (deg)" << setw(12) << "y-rot (deg)" << setw(12) << "z-rot (deg)" << endl;
file << setw(10) << mi_dmp[i](1) << setw(10) << mi_dmp[i](2) << setw(10) << mi_dmp[i](3) << setw(12) << mi_dmp[i](4)*180.0/pi << setw(12) << mi_dmp[i](5)*180.0/pi << setw(12) << mi_dmp[i](6)*180.0/pi << endl;
}
file << endl << "Relative movement parameters between the shells" << endl;
for (unsigned int i=1; i<mi_cmp[0].size(); i++) {
file << "Shell " << grpb[i] << " to shell " << grpb[0] << endl;
file << setw(10) << "x-tr (mm)" << setw(10) << "y-tr (mm)" << setw(10) << "z-tr (mm)" << setw(12) << "x-rot (deg)" << setw(12) << "y-rot (deg)" << setw(12) << "z-rot (deg)" << endl;
file << setw(10) << mi_cmp[0][i](1) << setw(10) << mi_cmp[0][i](2) << setw(10) << mi_cmp[0][i](3);
file << setw(12) << mi_cmp[0][i](4)*180.0/pi << setw(12) << mi_cmp[0][i](5)*180.0/pi << setw(12) << mi_cmp[0][i](6)*180.0/pi << endl;
}
file << endl << "Deduced movement parameters relative b0-shell" << endl;
for (unsigned int i=0; i<mi_ump.size(); i++) {
file << "Shell " << grpb[i] << " to b0-shell" << endl;
file << setw(10) << "x-tr (mm)" << setw(10) << "y-tr (mm)" << setw(10) << "z-tr (mm)" << setw(12) << "x-rot (deg)" << setw(12) << "y-rot (deg)" << setw(12) << "z-rot (deg)" << endl;
file << setw(10) << mi_ump[i](1) << setw(10) << mi_ump[i](2) << setw(10) << mi_ump[i](3) << setw(12) << mi_ump[i](4)*180.0/pi << setw(12) << mi_ump[i](5)*180.0/pi << setw(12) << mi_ump[i](6)*180.0/pi << endl;
}
file << endl << endl;
if (b0_dmp.size()) { // If parameters were also estimated using interspersed b0s
file << "These between shell parameters were calculated using interspersed b0-volumes" << endl;
file << setprecision(3) << "Movement parameters relative b0-shell" << endl;
for (unsigned int i=0; i<b0_dmp.size(); i++) {
file << "Shell " << grpb[i] << " to b0-shell" << endl;
file << setw(10) << "x-tr (mm)" << setw(10) << "y-tr (mm)" << setw(10) << "z-tr (mm)" << setw(12) << "x-rot (deg)" << setw(12) << "y-rot (deg)" << setw(12) << "z-rot (deg)" << endl;
file << setw(10) << b0_dmp[i](1) << setw(10) << b0_dmp[i](2) << setw(10) << b0_dmp[i](3) << setw(12) << b0_dmp[i](4)*180.0/pi << setw(12) << b0_dmp[i](5)*180.0/pi << setw(12) << b0_dmp[i](6)*180.0/pi << endl;
}
file << endl << endl;
}
if (!upe) file << "The movement parameters presented above have been calculated but not used";
else {
file << "These are the movement parameters that have been applied to the data" << endl << endl;
std::vector<NEWMAT::ColumnVector> ump;
if (clo.UseB0sToAlignShellsPostEddy()) ump = b0_ump;
else ump = mi_ump;
for (unsigned int i=0; i<ump.size(); i++) {
file << "Shell " << grpb[i] << " to b0-shell" << endl;
file << setw(10) << "x-tr (mm)" << setw(10) << "y-tr (mm)" << setw(10) << "z-tr (mm)" << setw(12) << "x-rot (deg)" << setw(12) << "y-rot (deg)" << setw(12) << "z-rot (deg)" << endl;
file << setw(10) << ump[i](1) << setw(10) << ump[i](2) << setw(10) << ump[i](3) << setw(12) << ump[i](4)*180.0/pi << setw(12) << ump[i](5)*180.0/pi << setw(12) << ump[i](6)*180.0/pi << endl;
}
}
file.close();
}
catch (...) {
throw EddyException("EDDY::PEASUtils::write_post_eddy_align_shells_report: Failed writing file.");
}
} EddyCatch
void PEASUtils::write_post_eddy_align_shells_along_PE_report(const std::vector<NEWMAT::ColumnVector>& mi_dmp,
const std::vector<NEWMAT::ColumnVector>& mi_ump,
const std::vector<std::vector<NEWMAT::ColumnVector> >& mi_cmp,
const std::vector<double>& grpb,
bool upe,
const EddyCommandLineOptions& clo) EddyTry
{
try {
std::ofstream file;
file.open(clo.PeasAlongPEReportFname().c_str(),ios::out|ios::trunc);
file << "These between shell PE-translations were calculated using MI between shell means" << endl;
file << setprecision(3) << "PE-translations (mm) relative b0-shell from direct registration to mean b0" << endl;
for (unsigned int i=0; i<mi_dmp.size(); i++) {
file << "Shell " << grpb[i] << " to b0-shell: PE-translation = " << mi_dmp[i](1) << " mm" << endl;
}
file << endl << "Relative PE-translations (mm) between the shells" << endl;
for (unsigned int i=1; i<mi_cmp[0].size(); i++) {
file << "Shell " << grpb[i] << " to shell " << grpb[0] << ": PE-translation = " << mi_cmp[0][i](1) << " mm" << endl;
}
file << endl << "Deduced PE-translations (mm) relative b0-shell" << endl;
for (unsigned int i=0; i<mi_ump.size(); i++) {
file << "Shell " << grpb[i] << " to b0-shell: PE-translation = " << mi_ump[i](1) << " mm" << endl;
}
file << endl << endl;
if (!upe) file << "The PE-translations presented above have been calculated but not used";
else {
file << "These are the PE-translations (mm) that have been applied to the data" << endl << endl;
for (unsigned int i=0; i<mi_ump.size(); i++) {
file << "Shell " << grpb[i] << " to b0-shell: PE-translation = " << mi_ump[i](1) << " mm" << endl;
}
}
file.close();
}
catch (...) {
throw EddyException("EDDY::WritePostEddyAlignShellsReport: Failed writing file.");
}
} EddyCatch
void PEASUtils::update_mov_par_estimates(// Input
const NEWMAT::ColumnVector& mp,
const std::vector<unsigned int>& indx,
// Input/output
ECScanManager& sm) EddyTry
{
NEWMAT::Matrix A;
if (mp.Nrows()==1) {
NEWMAT::ColumnVector tmp(6); tmp = 0.0;
if (sm.HasPEinX()) tmp(1) = mp(1); else tmp(2) = mp(1);
A = TOPUP::MovePar2Matrix(tmp,sm.Scan(0,ScanType::Any).GetIma());
}
else if (mp.Nrows()==6) A = TOPUP::MovePar2Matrix(mp,sm.Scan(0,ScanType::Any).GetIma());
else throw EddyException("EDDY::PostEddyCFImpl::GetTransformedIma: size of p must be 1 or 6");
for (unsigned int i=0; i<indx.size(); i++) {
NEWMAT::Matrix iM = sm.Scan(indx[i],ScanType::Any).InverseMovementMatrix();
iM = A*iM;
NEWMAT::ColumnVector nmp = TOPUP::Matrix2MovePar(iM.i(),sm.Scan(0,ScanType::Any).GetIma());
sm.Scan(indx[i],ScanType::Any).SetParams(nmp,EDDY::ParametersType::Movement);
}
return;
} EddyCatch
void PEASUtils::align_shells_using_MI(// Input
const EddyCommandLineOptions& clo,
bool pe_only,
// Input/Output
ECScanManager& sm,
// Output
std::vector<double>& grpb,
std::vector<NEWMAT::ColumnVector>& mov_par,
std::vector<std::vector<NEWMAT::ColumnVector> >& cmov_par,
std::vector<NEWMAT::ColumnVector>& mp_for_updates) EddyTry
{
std::vector<std::vector<unsigned int> > dwi_indx;
// Get mean b0 volume
NEWIMAGE::volume<float> b0_mask = sm.Scan(0,ScanType::Any).GetIma(); b0_mask=1.0;
std::vector<unsigned int> b0_indx = sm.GetB0Indicies();
NEWIMAGE::volume<float> b0_mean = PEASUtils::get_mean_scan(clo,sm,b0_indx,b0_mask);
// NEWIMAGE::write_volume(b0_mean,"mean_b0_volume");
// Get mean volumes for all shells
if (clo.VeryVerbose()) cout << "Calculating shell means" << endl;
std::vector<NEWIMAGE::volume<float> > dwi_means;
NEWIMAGE::volume<float> mask = sm.Scan(0,ScanType::Any).GetIma(); mask=1.0;
if (sm.IsShelled()) { // Get shell means
dwi_indx = sm.GetShellIndicies(grpb);
dwi_means.resize(dwi_indx.size());
NEWIMAGE::volume<float> tmpmask;
for (unsigned int i=0; i<dwi_indx.size(); i++) {
tmpmask = 1.0;
dwi_means[i] = PEASUtils::get_mean_scan(clo,sm,dwi_indx[i],tmpmask);
mask *= tmpmask;
}
}
else { // Get mean of all dwi's
}
mask *= b0_mask; // Mask valid for b0 and dwi
// NEWIMAGE::write_volume(dwi_means[0],"mean_dwi_volume");
// NEWIMAGE::write_volume(mask,"peas_mask");
// Register all volumes to b0
if (clo.VeryVerbose()) cout << "Registering shell means" << endl;
mov_par.resize(dwi_means.size());
cmov_par.resize(dwi_means.size());
// The logic below is _awful_, but was an unfortunate consequence of
// rewriting the multi-CPU branch using the C++11 thread library.
// It means that the code below gets executed if it was compiled
// for GPU _OR_ if it was compiled for CPU and we are to run single threaded.
#ifndef COMPILE_GPU
if (clo.NumberOfThreads() == 1 || dwi_means.size() == 1) { // If we are to run single threaded
#endif
NEWIMAGE::volume<float> rima;
for (unsigned int i=0; i<dwi_means.size(); i++) {
if (pe_only) {
if (clo.VeryVerbose()) cout << "Registering shell " << grpb[i] << " along PE to b0" << endl;
unsigned int pe_dir = sm.HasPEinX() ? 0 : 1;
mov_par[i] = PEASUtils::register_volumes_along_PE(b0_mean,dwi_means[i],mask,pe_dir,rima);
if (clo.VeryVerbose()) cout << "PE-translation = " << PEASUtils::format_mp(mov_par[i]) << endl;
}
else {
if (clo.VeryVerbose()) cout << "Registering shell " << grpb[i] << " to b0" << endl;
mov_par[i] = PEASUtils::register_volumes(b0_mean,dwi_means[i],mask,rima);
if (clo.VeryVerbose()) cout << "Movement parameters: " << PEASUtils::format_mp(mov_par[i]) << endl;
}
}
for (unsigned int i=0; i<dwi_means.size(); i++) {
cmov_par[i].resize(dwi_means.size());
for (unsigned int j=i+1; j<dwi_means.size(); j++) {
if (pe_only) {
if (clo.VeryVerbose()) cout << "Registering shell " << j << " along PE to shell " << i << endl; cout.flush();
if (clo.VeryVerbose()) cout << "Registering shell " << grpb[j] << " along PE to shell " << grpb[i] << endl; cout.flush();
unsigned int pe_dir = sm.HasPEinX() ? 0 : 1;
cmov_par[i][j] = PEASUtils::register_volumes_along_PE(dwi_means[i],dwi_means[j],mask,pe_dir,rima);
if (clo.VeryVerbose()) cout << "PE-translation = " << PEASUtils::format_mp(cmov_par[i][j]) << endl;
}
else {
if (clo.VeryVerbose()) cout << "Registering shell " << j << " to shell " << i << endl; cout.flush();
if (clo.VeryVerbose()) cout << "Registering shell " << grpb[j] << " to shell " << grpb[i] << endl; cout.flush();
cmov_par[i][j] = PEASUtils::register_volumes(dwi_means[i],dwi_means[j],mask,rima);
if (clo.VeryVerbose()) cout << "Movement parameters: " << PEASUtils::format_mp(cmov_par[i][j]) << endl;
}
}
}
#ifndef COMPILE_GPU
}
#endif
// The next part is what we should run if the code was _NOT_
// compiled for GPU and we are to run multi-threaded.
#ifndef COMPILE_GPU
if (clo.NumberOfThreads() > 1 && dwi_means.size() > 1) {
// Decide number of registrations and number of threads
unsigned int num_regs = dwi_means.size() + (dwi_means.size()-1)*dwi_means.size()/2u;
unsigned int num_threads = std::min(clo.NumberOfThreads(),num_regs);
// Specify all registrations, where the index -1 is used to indicate the b0
// as target for the registration. pairs are <target,input>
std::vector<std::pair<int,unsigned int> > regs(num_regs);
// Registrations to b0.
for (unsigned int i=0; i<dwi_means.size(); i++) {
regs[i] = std::pair<int,unsigned int>(-1,i);
cmov_par[i].resize(dwi_means.size());
}
// Registrations of dwi shells to each other.
unsigned int cntr = dwi_means.size();
for (unsigned int i=0; i<dwi_means.size(); i++) {
for (unsigned int j=i+1; j<dwi_means.size(); j++) regs[cntr++] = std::pair<int,unsigned int>(i,j);
}
// Decide number of registrations for each thread
std::vector<unsigned int> nregs_pt = EddyUtils::ScansPerThread(num_regs,num_threads);
// Next spawn threads to do the calculations
std::vector<std::thread> threads(num_threads-1);
unsigned int pe_dir = sm.HasPEinX() ? 0 : 1;
for (unsigned int i=0; i<num_threads-1; i++) {
threads[i] = std::thread(PEASUtils::register_volumes_wrapper,nregs_pt[i],nregs_pt[i+1],std::ref(regs),pe_only,pe_dir,std::ref(b0_mean),
std::ref(dwi_means),std::ref(mask),std::ref(grpb),clo.VeryVerbose(),std::ref(mov_par),std::ref(cmov_par));
}
register_volumes_wrapper(nregs_pt[num_threads-1],nregs_pt[num_threads],regs,pe_only,pe_dir,
b0_mean,dwi_means,mask,grpb,clo.VeryVerbose(),mov_par,cmov_par);
std::for_each(threads.begin(),threads.end(),std::mem_fn(&std::thread::join));
}
#endif
// Collate estimates to use
mp_for_updates = collate_mov_par_estimates_for_use(mov_par,cmov_par,sm.Scan(0,ScanType::Any).GetIma());
} EddyCatch
void PEASUtils::write_between_shell_MI_values(const NEWIMAGE::volume<float>& ref,
const NEWIMAGE::volume<float>& ima,
const NEWIMAGE::volume<float>& mask,
const std::string& fname,
const std::vector<unsigned int>& n,
const std::vector<double>& first,
const std::vector<double>& last) EddyTry
{
EddyUtils::SetSplineInterp(ima);
EddyUtils::SetTrilinearInterp(mask);
PostEddyCF ecf(ref,ima,mask,256);
std::ofstream file;
double pi = 3.14159;
file.open(fname,ios::out|ios::trunc);
file << setprecision(8);
NEWMAT::ColumnVector mp(6);
for (unsigned int xti=0; xti<n[0]; xti++) {
mp(1) = (n[0]>1) ? first[0] + xti * ((last[0]-first[0]) / (n[0]-1)) : first[0];
for (unsigned int yti=0; yti<n[1]; yti++) {
mp(2) = (n[1]>1) ? first[1] + yti * ((last[1]-first[1]) / (n[1]-1)) : first[1];
for (unsigned int zti=0; zti<n[2]; zti++) {
mp(3) = (n[2]>1) ? first[2] + zti * ((last[2]-first[2]) / (n[2]-1)) : first[2];
for (unsigned int xri=0; xri<n[3]; xri++) {
mp(4) = (n[3]>1) ? first[3] + xri * ((last[3]-first[3]) / (n[3]-1)) : first[3];
mp(4) = pi * mp(4) / 180.0;
for (unsigned int yri=0; yri<n[4]; yri++) {
mp(5) = (n[4]>1) ? first[4] + yri * ((last[4]-first[4]) / (n[4]-1)) : first[4];
mp(5) = pi * mp(5) / 180.0;
for (unsigned int zri=0; zri<n[5]; zri++) {
mp(6) = (n[5]>1) ? first[5] + zri * ((last[5]-first[5]) / (n[5]-1)) : first[5];
mp(6) = pi * mp(6) / 180.0;
file << setw(16) << mp(1) << setw(16) << mp(2) << setw(16) << mp(3);
file << setw(16) << 180.0*mp(4)/pi << setw(16) << 180.0*mp(5)/pi << setw(16) << 180.0*mp(6)/pi;
file << setw(16) << ecf.cf(mp) << endl;
}
}
}
}
}
}
return;
} EddyCatch
void PEASUtils::align_shells_using_interspersed_B0_scans(// Input
const EddyCommandLineOptions& clo,
// Input/Output
ECScanManager& sm,
// Output
std::vector<double>& grpb,
std::vector<NEWMAT::ColumnVector>& mov_par,
std::vector<NEWMAT::ColumnVector>& mp_for_updates) EddyTry
{
std::vector<DiffPara> dpv = sm.GetDiffParas();
std::vector<unsigned int> grpi; // A vector with a group index for each Scan in sm. Group 0 pertains to b0.
EddyUtils::GetGroups(dpv,grpi,grpb); // N.B. This version of grpb has not had the b0 removed
std::vector<unsigned int> b0_indx = sm.GetB0Indicies();
mov_par.resize(grpb.size()-1);
for (unsigned int i=0; i<grpb.size()-1; i++) mov_par[i].ReSize(6);
NEWMAT::ColumnVector delta(grpb.size()); // Delta values between b0 and shells
NEWMAT::ColumnVector n(grpb.size()); // Number of delta values per shell
for (unsigned int i=0; i<6; i++) { // Loop over movement parameters
delta=0.0; n=0.0;
for (unsigned int j=0; j<b0_indx.size(); j++) { // Calculate delta-values between b0s and surrounding dwis
if (b0_indx[j]==0) {
if (!sm.IsB0(b0_indx[j]+1)) {
NEWMAT::ColumnVector b0mp = sm.Scan(b0_indx[j]).GetParams(EDDY::ParametersType::ZeroOrderMovement);
NEWMAT::ColumnVector mp = sm.Scan(b0_indx[j]+1).GetParams(EDDY::ParametersType::ZeroOrderMovement);
delta(grpi[b0_indx[j]+1]) += mp(i+1)-b0mp(i+1);
n(grpi[b0_indx[j]+1]) += 1;
}
}
else if (b0_indx[j]==(sm.NScans()-1)) {
if (!sm.IsB0(b0_indx[j]-1)) {
NEWMAT::ColumnVector b0mp = sm.Scan(b0_indx[j]).GetParams(EDDY::ParametersType::ZeroOrderMovement);
NEWMAT::ColumnVector mp = sm.Scan(b0_indx[j]-1).GetParams(EDDY::ParametersType::ZeroOrderMovement);
delta(grpi[b0_indx[j]-1]) += mp(i+1)-b0mp(i+1);
n(grpi[b0_indx[j]-1]) += 1;
}
}
else {
NEWMAT::ColumnVector b0mp = sm.Scan(b0_indx[j]).GetParams(EDDY::ParametersType::ZeroOrderMovement);
if (!sm.IsB0(b0_indx[j]-1)) {
NEWMAT::ColumnVector mp = sm.Scan(b0_indx[j]-1).GetParams(EDDY::ParametersType::ZeroOrderMovement);
delta(grpi[b0_indx[j]-1]) += mp(i+1)-b0mp(i+1);
n(grpi[b0_indx[j]-1]) += 1;
}
if (!sm.IsB0(b0_indx[j]+1)) {
NEWMAT::ColumnVector mp = sm.Scan(b0_indx[j]+1).GetParams(EDDY::ParametersType::ZeroOrderMovement);
delta(grpi[b0_indx[j]+1]) += mp(i+1)-b0mp(i+1);
n(grpi[b0_indx[j]+1]) += 1;
}
}
}
for (unsigned int j=0; j<grpb.size()-1; j++) {
mov_par[j](i+1) = delta(j+1) / float(n(j+1));
}
}
mp_for_updates = mov_par;
} EddyCatch
} // End namespace EDDY
#ifndef PostEddyAlignShellsFunctions_h
#define PostEddyAlignShellsFunctions_h
#include "EddyHelperClasses.h"
#include "ECScanClasses.h"
#include "DiffusionGP.h"
#include "b0Predictor.h"
#include "EddyUtils.h"
#include "EddyCommandLineOptions.h"
namespace EDDY {
/****************************************************************//**
*
* \brief This class does Post Eddy Alignment of Shells (PEAS)
*
*
*
********************************************************************/
class PEASUtils
{
public:
/// Attempts to align the shells after eddy has done all within shell alignments
static void PostEddyAlignShells(// Input
const EddyCommandLineOptions& clo,
bool upe, // Update parameter estimates or not
// Input/Output
ECScanManager& sm);
/// Aligns shells through PE-direction translation _only_
static void PostEddyAlignShellsAlongPE(// Input
const EddyCommandLineOptions& clo,
bool upe, // Update parameter estimates or not
// Input/Output
ECScanManager& sm);
/// Generate text file with MI values for different movement parameters. For debugging purposes only.
static void WritePostEddyBetweenShellMIValues(// Input
const EddyCommandLineOptions& clo,
const ECScanManager& sm,
const std::vector<unsigned int>& n,
const std::vector<double>& first,
const std::vector<double>& last,
const std::string& bfname);
private:
static NEWIMAGE::volume<float> get_mean_scan(// Input
const EddyCommandLineOptions& clo,
const ECScanManager& sm,
const std::vector<unsigned int>& indx,
// Output
NEWIMAGE::volume<float>& mask);
static void get_mean_scan_wrapper(unsigned int first_index,
unsigned int last_index,
const ECScanManager& sm,
const std::vector<unsigned int>& indx,
bool very_verbose,
// Output
NEWIMAGE::volume<float>& mean,
NEWIMAGE::volume<float>& mask);
static NEWMAT::ColumnVector register_volumes(// Input
const NEWIMAGE::volume<float>& ref,
const NEWIMAGE::volume<float>& ima,
const NEWIMAGE::volume<float>& mask,
// Output
NEWIMAGE::volume<float>& rima);
static NEWMAT::ColumnVector register_volumes_along_PE(// Input
const NEWIMAGE::volume<float>& ref,
const NEWIMAGE::volume<float>& ima,
const NEWIMAGE::volume<float>& mask,
unsigned int pe_dir,
// Output
NEWIMAGE::volume<float>& rima);
static void register_volumes_wrapper(// Input
unsigned int first_reg,
unsigned int last_reg,
const std::vector<std::pair<int,unsigned int> >& regs,
bool pe_only,
unsigned int pe_dir,
const NEWIMAGE::volume<float>& b0,
const std::vector<NEWIMAGE::volume<float> >& dwis,
const NEWIMAGE::volume<float>& mask,
const std::vector<double>& grpb,
bool vv,
// Output
std::vector<NEWMAT::ColumnVector>& mov_par,
std::vector<std::vector<NEWMAT::ColumnVector> >& cmov_par);
static std::string format_mp(const NEWMAT::ColumnVector& mp);
static std::vector<NEWMAT::ColumnVector> collate_mov_par_estimates_for_use(const std::vector<NEWMAT::ColumnVector>& mp,
const std::vector<std::vector<NEWMAT::ColumnVector> >& cmp,
const NEWIMAGE::volume<float>& ima);
static void write_post_eddy_align_shells_report(const std::vector<NEWMAT::ColumnVector>& mi_dmp,
const std::vector<NEWMAT::ColumnVector>& mi_ump,
const std::vector<std::vector<NEWMAT::ColumnVector> >& mi_cmp,
const std::vector<NEWMAT::ColumnVector>& b0_dmp,
const std::vector<NEWMAT::ColumnVector>& b0_ump,
const std::vector<double>& grpb,
bool upe,
const EddyCommandLineOptions& clo);
static void write_post_eddy_align_shells_along_PE_report(const std::vector<NEWMAT::ColumnVector>& mi_dmp,
const std::vector<NEWMAT::ColumnVector>& mi_ump,
const std::vector<std::vector<NEWMAT::ColumnVector> >& mi_cmp,
const std::vector<double>& grpb,
bool upe,
const EddyCommandLineOptions& clo);
static void update_mov_par_estimates(// Input
const NEWMAT::ColumnVector& mp,
const std::vector<unsigned int>& indx,
// Input/output
ECScanManager& sm);
static void align_shells_using_MI(// Input
const EddyCommandLineOptions& clo,
bool pe_only,
// Input/Output
ECScanManager& sm,
// Output
std::vector<double>& grpb,
std::vector<NEWMAT::ColumnVector>& mov_par,
std::vector<std::vector<NEWMAT::ColumnVector> >& cmov_par,
std::vector<NEWMAT::ColumnVector>& mp_for_updates);
static void write_between_shell_MI_values(const NEWIMAGE::volume<float>& b0_mean,
const NEWIMAGE::volume<float>& dwi_means,
const NEWIMAGE::volume<float>& mask,
const std::string& fname,
const std::vector<unsigned int>& n,
const std::vector<double>& first,
const std::vector<double>& last);
static void align_shells_using_interspersed_B0_scans(// Input
const EddyCommandLineOptions& clo,
// Input/Output
ECScanManager& sm,
// Output
std::vector<double>& grpb,
std::vector<NEWMAT::ColumnVector>& mov_par,
std::vector<NEWMAT::ColumnVector>& mp_for_updates);
}; // End of class PEASUtils
} // End namespace EDDY
#endif // end #ifndef PostEddyAlignShellsFunctions_h
// Definitions of classes and functions that
// perform a post-hoc registration of the shells
// for the eddy project/.
//
// post_registration.h
//
// Jesper Andersson, FMRIB Image Analysis Group
//
// Copyright (C) 2011 University of Oxford
//
#include <cstdlib>
#include <string>
#include <vector>
#include <cmath>
#include "armawrap/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 "miscmaths/nonlin.h"
#include "warpfns/warpfns.h"
#include "topup/topup_file_io.h"
#include "EddyUtils.h"
#include "PostEddyCF.h"
namespace EDDY {
class PostEddyCFImpl
{
public:
PostEddyCFImpl(const NEWIMAGE::volume<float>& ref,
const NEWIMAGE::volume<float>& ima,
const NEWIMAGE::volume<float>& mask) EddyTry : _ref(ref), _ima(ima), _mask(mask) {} EddyCatch
~PostEddyCFImpl() {}
double cf(const NEWMAT::ColumnVector& p,
const EDDY::MutualInfoHelper& fwd_mih,
const EDDY::MutualInfoHelper& bwd_mih,
int pe_dir) const;
NEWIMAGE::volume<float> GetTransformedIma(const NEWMAT::ColumnVector& p,
int pe_dir) const;
private:
NEWIMAGE::volume<float> _ref;
NEWIMAGE::volume<float> _ima;
NEWIMAGE::volume<float> _mask;
};
PostEddyCF::PostEddyCF(const NEWIMAGE::volume<float>& ref,
const NEWIMAGE::volume<float>& ima,
const NEWIMAGE::volume<float>& mask,
unsigned int nbins) EddyTry
: _fwd_mih(nbins,ref.robustmin(),ref.robustmax(),ima.robustmin(),ima.robustmax()),
_bwd_mih(nbins,ima.robustmin(),ima.robustmax(),ref.robustmin(),ref.robustmax())
{
_pimpl = new PostEddyCFImpl(ref,ima,mask);
_pe_dir = -1; // Disallowed value
} EddyCatch
PostEddyCF::PostEddyCF(const NEWIMAGE::volume<float>& ref,
const NEWIMAGE::volume<float>& ima,
const NEWIMAGE::volume<float>& mask,
unsigned int nbins,
unsigned int pe_dir) EddyTry
: _fwd_mih(nbins,ref.robustmin(),ref.robustmax(),ima.robustmin(),ima.robustmax()),
_bwd_mih(nbins,ima.robustmin(),ima.robustmax(),ref.robustmin(),ref.robustmax())
{
_pimpl = new PostEddyCFImpl(ref,ima,mask);
if (pe_dir > 1) throw EddyException("EDDY::PostEddyCF::PostEddyCF: pe_dir must be 0 or 1");
else _pe_dir = static_cast<int>(pe_dir);
} EddyCatch
PostEddyCF::~PostEddyCF() { delete _pimpl; }
double PostEddyCF::cf(const NEWMAT::ColumnVector& p) const EddyTry { return(_pimpl->cf(p,_fwd_mih,_bwd_mih,_pe_dir)); } EddyCatch
NEWMAT::ReturnMatrix PostEddyCF::grad(const NEWMAT::ColumnVector& p) const EddyTry
{
NEWMAT::ColumnVector tp = p;
NEWMAT::ColumnVector gradv(p.Nrows());
static const double dscale[] = {1e-2, 1e-2, 1e-2, 1e-5, 1e-5, 1e-5}; // Works both for Nrows = 1 and Nrows = 6
double base = _pimpl->cf(tp,_fwd_mih,_bwd_mih,_pe_dir);
for (int i=0; i<p.Nrows(); i++) {
tp(i+1) += dscale[i];
gradv(i+1) = (_pimpl->cf(tp,_fwd_mih,_bwd_mih,_pe_dir) - base) / dscale[i];
tp(i+1) -= dscale[i];
}
gradv.Release();
return(gradv);
} EddyCatch
NEWIMAGE::volume<float> PostEddyCF::GetTransformedIma(const NEWMAT::ColumnVector& p) const EddyTry { return(_pimpl->GetTransformedIma(p,_pe_dir)); } EddyCatch
double PostEddyCFImpl::cf(const NEWMAT::ColumnVector& p,
const EDDY::MutualInfoHelper& fwd_mih,
const EDDY::MutualInfoHelper& bwd_mih,
int pe_dir) const EddyTry
{
NEWIMAGE::volume<float> rima = _ima; rima = 0.0;
NEWIMAGE::volume<char> mask1(_ima.xsize(),_ima.ysize(),_ima.zsize());
NEWIMAGE::copybasicproperties(_ima,mask1); mask1 = 1;
NEWIMAGE::volume<float> mask2 = _ima; mask2 = 0.0;
NEWMAT::Matrix A;
if (p.Nrows() == 1) {
if(pe_dir<0 || pe_dir>1) EddyException("EDDY::PostEddyCFImpl::cf: invalid pe_dir value");
NEWMAT::ColumnVector tmp(6); tmp = 0.0;
tmp(pe_dir + 1) = p(1);
A = TOPUP::MovePar2Matrix(tmp,_ima);
}
else if (p.Nrows() == 6) A = TOPUP::MovePar2Matrix(p,_ima);
else throw EddyException("EDDY::PostEddyCFImpl::cf: size of p must be 1 or 6");
// Transform image
NEWIMAGE::affine_transform(_ima,A,rima,mask1);
// Transform mask in same way
NEWIMAGE::affine_transform(_mask,A,mask2);
// mask2.binarise(0.99); // Value above (arbitrary) 0.99 implies valid voxels
// Combine masks
mask2 *= EddyUtils::ConvertMaskToFloat(mask1);
// double rval = - mih.MI(_ref,rima,mask2);
double rval = - fwd_mih.SoftMI(_ref,rima,mask2);
// Backward transform image
rima = 0.0; mask1 = 1.0; mask2 = 0.0;
NEWIMAGE::affine_transform(_ref,A.i(),rima,mask1);
// Backward transform mask
NEWIMAGE::affine_transform(_mask,A.i(),mask2);
// mask2.binarise(0.99);
mask2 *= EddyUtils::ConvertMaskToFloat(mask1);
// rval += - mih.MI(_ima,rima,mask2); rval /= 2.0;
rval += - bwd_mih.SoftMI(_ima,rima,mask2); rval /= 2.0;
//cout << "rval = " << rval << endl;
//cout << "p = " << p << endl;
return(rval);
} EddyCatch
NEWIMAGE::volume<float> PostEddyCFImpl::GetTransformedIma(const NEWMAT::ColumnVector& p,
int pe_dir) const EddyTry
{
NEWIMAGE::volume<float> rima = _ima; rima = 0.0;
NEWMAT::Matrix A;
if (p.Nrows() == 1) {
if(pe_dir<0 || pe_dir>1) EddyException("EDDY::PostEddyCFImpl::GetTransformedIma: invalid pe_dir value");
NEWMAT::ColumnVector tmp(6); tmp = 0.0;
tmp(pe_dir + 1) = p(1);
A = TOPUP::MovePar2Matrix(tmp,_ima);
}
else if (p.Nrows() == 6) A = TOPUP::MovePar2Matrix(p,_ima);
else throw EddyException("EDDY::PostEddyCFImpl::GetTransformedIma: size of p must be 1 or 6");
NEWIMAGE::affine_transform(_ima,A,rima);
return(rima);
} EddyCatch
} // End namespace EDDY
// Declarations of classes and functions that
// perform a post-hoc registration of the shells
// for the eddy project/.
//
// post_registration.h
//
// Jesper Andersson, FMRIB Image Analysis Group
//
// Copyright (C) 2011 University of Oxford
//
#ifndef PostEddyCF_h
#define PostEddyCF_h
#include <cstdlib>
#include <string>
#include <vector>
#include <cmath>
#include "armawrap/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 "miscmaths/nonlin.h"
#include "EddyHelperClasses.h"
#include "ECModels.h"
namespace EDDY {
/****************************************************************//**
*
* \brief Class used to implement a Mutual Information cost-
* function for post-hoc registration of shells in the eddy project.
*
* Class used to implement a Mutual Information cost-
* function for post-hoc registration of shells in the eddy project.
* It is implemented using the "Pimpl idiom" which means that this class
* only implements an interface whereas the actual work is being performed
* by the PostEddyCFImpl class which is declared and defined in
* PostEddyCF.cpp or cuda/PostEddyCF.cu depending on what platform
* the code is compiled for.
*
********************************************************************/
class PostEddyCFImpl;
class PostEddyCF : public MISCMATHS::NonlinCF
{
public:
PostEddyCF(const NEWIMAGE::volume<float>& ref,
const NEWIMAGE::volume<float>& ima,
const NEWIMAGE::volume<float>& mask,
unsigned int nbins);
PostEddyCF(const NEWIMAGE::volume<float>& ref,
const NEWIMAGE::volume<float>& ima,
const NEWIMAGE::volume<float>& mask,
unsigned int nbins,
unsigned int pe_dir);
~PostEddyCF();
NEWIMAGE::volume<float> GetTransformedIma(const NEWMAT::ColumnVector& p) const;
double cf(const NEWMAT::ColumnVector& p) const;
NEWMAT::ReturnMatrix grad(const NEWMAT::ColumnVector& p) const;
private:
int _pe_dir; // 0 for x, 1 for y
MutualInfoHelper _fwd_mih;
MutualInfoHelper _bwd_mih;
PostEddyCFImpl *_pimpl;
};
} // End namespace EDDY
#endif // End #ifndef PostEddyCF_h
// Definitions of class to make silly
// predictions about b0 scans.
//
// b0Predictor.cpp
//
// Jesper Andersson, FMRIB Image Analysis Group
//
// Copyright (C) 2011 University of Oxford
//
#include <cstdlib>
#include <string>
#include <vector>
#include <cmath>
#include "armawrap/newmat.h"
#include "newimage/newimageall.h"
#include "miscmaths/miscmaths.h"
#include "EddyHelperClasses.h"
#include "EddyUtils.h"
#include "b0Predictor.h"
using namespace EDDY;
//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
//
// Class b0Predictor
//
//
//
//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
std::vector<NEWIMAGE::volume<float> > b0Predictor::Predict(const std::vector<unsigned int>& indicies,
bool exclude) EddyTry
{
std::vector<NEWIMAGE::volume<float> > rval(indicies.size());
for (unsigned int i=0; i<indicies.size(); i++) rval[i] = predict();
return(rval);
} EddyCatch
NEWIMAGE::volume<float> b0Predictor::InputData(unsigned int indx) const EddyTry
{
if (!IsPopulated()) throw EddyException("b0Predictor::InputData: Not yet fully populated");
if (indx >= _sptrs.size()) throw EddyException("b0Predictor::InputData: indx out of range");
return(*(_sptrs[indx]));
} EddyCatch
std::vector<NEWIMAGE::volume<float> > b0Predictor::InputData(const std::vector<unsigned int>& indx) const EddyTry
{
if (!IsPopulated()) throw EddyException("b0Predictor::InputData: Not yet fully populated");
for (unsigned int i=0; i<indx.size(); i++) if (indx[i] >= _sptrs.size()) throw EddyException("b0Predictor::InputData: indx out of range");
std::vector<NEWIMAGE::volume<float> > rval(indx.size());
for (unsigned int i=0; i<indx.size(); i++) rval[i] = *(_sptrs[indx[i]]);
return(rval);
} EddyCatch
bool b0Predictor::IsPopulated() const EddyTry
{
if (_pop) return(true);
else {
_pop = true;
for (unsigned int i=0; i<_sptrs.size(); i++) {
if (!_sptrs[i]) { _pop = false; break; }
}
}
return(_pop);
} EddyCatch
void b0Predictor::SetNoOfScans(unsigned int n) EddyTry
{
if (n == _sptrs.size()) return; // No change
else if (n > _sptrs.size()) { // If increasing size
_sptrs.resize(n,std::shared_ptr<NEWIMAGE::volume<float> >()); // New elements populated by NULL
_pop = false;
_valid = false;
}
else { // If decreasing size
_sptrs.resize(n);
_valid = false;
if (_pop==false) { // _pop may potentially go from false to true
_pop = IsPopulated();
}
}
return;
} EddyCatch
void b0Predictor::SetScan(const NEWIMAGE::volume<float>& scan,
const DiffPara& dp,
unsigned int indx) EddyTry
{
if (int(indx) > (int(_sptrs.size())-1)) throw EddyException("b0Predictor::SetScan: Invalid image index");
if (_sptrs.size() && _sptrs[0] && !NEWIMAGE::samesize(*_sptrs[0],scan)) throw EddyException("b0Predictor::SetScan: Wrong image dimension");
_sptrs[indx] = std::shared_ptr<NEWIMAGE::volume<float> >(new NEWIMAGE::volume<float>(scan));
} EddyCatch
void b0Predictor::WriteImageData(const std::string& fname) const EddyTry
{
char ofname[256];
if (!IsPopulated()) throw EddyException("DiffusionGP::Write: Not yet fully populated");
// For practical reasons the volumes are written individually
for (unsigned int i=0; i<_sptrs.size(); i++) {
sprintf(ofname,"%s_%03d",fname.c_str(),i);
NEWIMAGE::write_volume(*(_sptrs[i]),ofname);
}
NEWIMAGE::write_volume(_mean,fname+std::string("_mean"));
NEWIMAGE::write_volume(_var,fname+std::string("_var"));
NEWIMAGE::write_volume(_mask,fname+std::string("_mask"));
} EddyCatch
/*! \file b0Predictor.h
\brief Contains declaration of class for making silly (mean) predictions about b=0 data.
\author Jesper Andersson
\version 1.0b, Sep., 2012.
*/
// Declarations of class to make silly
// predictions about b0 scans.
//
// b0Predictor.h
//
// Jesper Andersson, FMRIB Image Analysis Group
//
// Copyright (C) 2011 University of Oxford
//
#ifndef b0Predictor_h
#define b0Predictor_h
#include <cstdlib>
#include <string>
#include <vector>
#include <cmath>
#include <memory>
#include "armawrap/newmat.h"
#include "newimage/newimageall.h"
#include "miscmaths/miscmaths.h"
#include "EddyHelperClasses.h"
#include "DWIPredictionMaker.h"
namespace EDDY {
/****************************************************************//**
*
* \brief Class for making silly (mean) predictions about b=0 data.
*
* Will make predictions about b=0 scans. Since we don't expect any
* variability in these scans the prediction will simply be the mean
* of all the scans in the prediction maker. It is there mainly so
* that the same code can be used for registration of
* diffusion-weighted and b=0 scans.
*
********************************************************************/
class b0Predictor : public DWIPredictionMaker
{
public:
b0Predictor() EddyTry : _pop(true), _valid(false) {} EddyCatch
~b0Predictor() {}
virtual NEWIMAGE::volume<float> Predict(unsigned int indx, bool exclude=false) const EddyTry { return(predict()); } EddyCatch
virtual NEWIMAGE::volume<float> Predict(unsigned int indx, bool exclude=false) EddyTry { return(predict()); } EddyCatch
virtual NEWIMAGE::volume<float> PredictCPU(unsigned int indx, bool exclude=false) EddyTry { return(predict()); } EddyCatch
virtual std::vector<NEWIMAGE::volume<float> > Predict(const std::vector<unsigned int>& indicies, bool exclude=false);
/// Returns input data for point given by indx
virtual NEWIMAGE::volume<float> InputData(unsigned int indx) const;
/// Returns input data for points given by indicies
virtual std::vector<NEWIMAGE::volume<float> > InputData(const std::vector<unsigned int>& indicies) const;
/// Returns variance of prediction for point given by indx.
virtual double PredictionVariance(unsigned int indx, bool exclude=false) EddyTry { return(_mean.mean(_mask) / std::sqrt(double(_sptrs.size()))); } EddyCatch
/// Returns measurement-error variance for point given by indx.
virtual double ErrorVariance(unsigned int indx) const EddyTry { return(_mean.mean(_mask)); } EddyCatch
virtual bool IsPopulated() const; // Returns true if all data present
virtual bool IsValid() const { return(_valid); } // Returns true if ready to make predictions
virtual void SetNoOfScans(unsigned int n);
virtual void SetScan(const NEWIMAGE::volume<float>& scan, // _May_ be thread safe if used "sensibly"
const DiffPara& dp,
unsigned int indx,
unsigned int sess) { SetScan(scan,dp,indx); }
virtual void SetScan(const NEWIMAGE::volume<float>& scan, // _May_ be thread safe if used "sensibly"
const DiffPara& dp,
unsigned int indx);
virtual unsigned int NoOfHyperPar() const { return(0); }
virtual std::vector<double> GetHyperPar() const EddyTry { std::vector<double> rval; return(rval); } EddyCatch
virtual void EvaluateModel(const NEWIMAGE::volume<float>& mask, float fwhm, bool verbose=false) EddyTry
{
if (!IsPopulated()) throw EddyException("b0Predictor::EvaluateModel: Not ready to evaluate model");
if (!IsValid()) { _mask=mask; _mean=mean_vol(_mask); _var=variance_vol(_mean,_mask); }
} EddyCatch
virtual void EvaluateModel(const NEWIMAGE::volume<float>& mask, bool verbose=false) EddyTry { EvaluateModel(mask,0.0,verbose); } EddyCatch
virtual void WriteImageData(const std::string& fname) const;
virtual void WriteMetaData(const std::string& fname) const {}
virtual void Write(const std::string& fname) const EddyTry { WriteImageData(fname); WriteMetaData(fname); } EddyCatch
private:
std::vector<std::shared_ptr<NEWIMAGE::volume<float> > > _sptrs; // Pointers to the scans
NEWIMAGE::volume<float> _mean; // Mean image
NEWIMAGE::volume<float> _var; // Variance image
NEWIMAGE::volume<float> _mask; // Mask indicating valid voxels
mutable bool _pop; // Tells if all data is present
bool _valid; // Tells if it is ready to make predictions
const NEWIMAGE::volume<float>& predict() const EddyTry
{
if (!IsValid()) throw EddyException("b0Predictor::Predict: Not ready to make predictions");
return(_mean);
} EddyCatch
NEWIMAGE::volume<float> mean_vol(const NEWIMAGE::volume<float>& mask) EddyTry
{
NEWIMAGE::volume<float> mvol = *_sptrs[0];
mvol = 0.0;
for (int k=0; k<_sptrs[0]->zsize(); k++) {
for (int j=0; j<_sptrs[0]->ysize(); j++) {
for (int i=0; i<_sptrs[0]->xsize(); i++) {
if (1) {
// if (mask(i,j,k)) { I removed masking 13/5-2016. J.A.
float &v = mvol(i,j,k);
for (unsigned int s=0; s<_sptrs.size(); s++) {
v += (*_sptrs[s])(i,j,k);
}
v /= float(_sptrs.size());
}
}
}
}
_valid = true;
return(mvol);
} EddyCatch
NEWIMAGE::volume<float> variance_vol(const NEWIMAGE::volume<float>& mean,
const NEWIMAGE::volume<float>& mask) EddyTry
{
NEWIMAGE::volume<float> vvol = *_sptrs[0];
vvol = 0.0;
if (_sptrs.size() > 1) {
for (int k=0; k<_sptrs[0]->zsize(); k++) {
for (int j=0; j<_sptrs[0]->ysize(); j++) {
for (int i=0; i<_sptrs[0]->xsize(); i++) {
if (1) {
// if (mask(i,j,k)) { I removed masking 13/5-2016. J.A.
float &v = vvol(i,j,k);
float mv = mean(i,j,k);
for (unsigned int s=0; s<_sptrs.size(); s++) {
v += MISCMATHS::Sqr(((*_sptrs[s])(i,j,k) - mv));
}
v /= float(_sptrs.size()-1);
}
}
}
}
}
_valid = true;
return(vvol);
} EddyCatch
};
} // End namespace EDDY
#endif // End #ifndef b0Predictor_h
export CONDA_BUILD="1"
export PYTHONNOUSERSITE="1"
export CONDA_DEFAULT_ENV="/datav/wkx/fsl/FSL-install-new"
export ARCH="64"
export PREFIX="/datav/wkx/fsl/FSL-install-new"
export BUILD_PREFIX="/tmp/fsl_ci_config_130478/conda-bld/fsl-eddy_1756308990621/_build_env"
export SYS_PREFIX="/micromamba"
export SYS_PYTHON="/micromamba/bin/python"
export SUBDIR="linux-64"
export build_platform="linux-64"
export SRC_DIR="/tmp/fsl_ci_config_130478/conda-bld/fsl-eddy_1756308990621/work"
export ROOT="/micromamba"
export CONDA_PY="312"
export PY3K="1"
export PY_VER="3.12"
export STDLIB_DIR="/datav/wkx/fsl/FSL-install-new/lib/python3.13"
export SP_DIR="/datav/wkx/fsl/FSL-install-new/lib/python3.13/site-packages"
export NPY_VER="2.1"
export CONDA_NPY="21"
export NPY_DISTUTILS_APPEND_FLAGS="1"
export PERL_VER="5.26"
export CONDA_PERL="5.26"
export LUA_VER="5"
export CONDA_LUA="5"
export R_VER="3.5"
export CONDA_R="3.5"
export GIT_DESCRIBE_TAG="2401.2"
export GIT_DESCRIBE_NUMBER="0"
export GIT_DESCRIBE_HASH="gaed6682"
export GIT_DESCRIBE_TAG_PEP440="2401.2"
export GIT_FULL_HASH="aed668224b3311fce76740dfa9ad91224bcd6ac8"
export GIT_BUILD_STR="0_gaed6682"
export PKG_NAME="fsl-eddy"
export PKG_VERSION="2401.2"
export PKG_BUILDNUM="5"
export PKG_BUILD_STRING="placeholder"
export PKG_HASH="1234567"
export RECIPE_DIR="/builds/fsl/conda/fsl-eddy"
export CPU_COUNT="2"
export MAKEFLAGS="-j 2"
export SHLIB_EXT=".so"
export PATH="/tmp/fsl_ci_config_130478/conda-bld/fsl-eddy_1756308990621/_build_env/bin:/datav/wkx/fsl/FSL-install-new/bin:/tmp/fsl_ci_config_130478/ci-rules/bin:/micromamba/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
export HOME="/root"
export PKG_CONFIG_PATH="/datav/wkx/fsl/FSL-install-new/lib/pkgconfig"
export CMAKE_GENERATOR="Unix Makefiles"
export LD_RUN_PATH="/datav/wkx/fsl/FSL-install-new/lib"
export BUILD="x86_64-conda_cos6-linux-gnu"
export ignore_build_only_deps="{'python', 'numpy'}"
export pin_run_as_build="OrderedDict({'python': {'min_pin': 'x.x', 'max_pin': 'x.x'}, 'r-base': {'min_pin': 'x.x', 'max_pin': 'x.x'}, 'libboost_headers': {'max_pin': 'x.x'}, 'libboost_devel': {'max_pin': 'x.x'}, 'boost-cpp': {'max_pin': 'x.x.x'}, 'vtk-base': {'max_pin': 'x.x'}, 'zlib': {'max_pin': 'x.x'}})"
export extend_keys="{'ignore_version', 'pin_run_as_build', 'extend_keys', 'ignore_build_only_deps'}"
export boost_cpp="1.84.*"
export zlib="1.3.*"
export libboost_headers="1.86.0"
export fortran_compiler="gfortran"
export cran_mirror="https://cran.r-project.org"
export c_stdlib="sysroot"
export liblapack="3.9 *netlib"
export vtk_base="9.3.*"
export c_compiler="gcc"
export target_platform="linux-64"
export libboost_devel="1.86.0"
export cpu_optimization_target="nocona"
export r_base="3.5"
export libblas="3.9 *netlib"
export c_stdlib_version="2.17"
export cxx_compiler="gxx"
export CONDA_BUILD_STATE="BUILD"
export PIP_NO_BUILD_ISOLATION="False"
export PIP_NO_DEPENDENCIES="True"
export PIP_IGNORE_INSTALLED="True"
export PIP_CACHE_DIR="/tmp/fsl_ci_config_130478/conda-bld/fsl-eddy_1756308990621/pip_cache"
export PIP_NO_INDEX="True"
eval "$('/micromamba/bin/python' -m conda shell.bash hook)"
conda activate "/datav/wkx/fsl/FSL-install-new"
conda activate --stack "/tmp/fsl_ci_config_130478/conda-bld/fsl-eddy_1756308990621/_build_env"
make clean && make cpu=1 cuda=1 -j$(nproc) 2>&1 | tee log.log
\ No newline at end of file
if [ -z ${CONDA_BUILD+x} ]; then
source '/tmp/fsl_ci_config_130478/conda-bld/fsl-eddy_1756308990621/work/build_env_setup.sh'
fi
#!/usr/bin/env bash
set -e
export FSLDIR=$PREFIX
export FSLDEVDIR=$PREFIX
. $FSLDIR/etc/fslconf/fsl-devel.sh
make insertcopyright
mkdir -p $PREFIX/src/
cp -r $(pwd) $PREFIX/src/$PKG_NAME
# Only build/install CPU variant
# (see fsl/conda/fsl-eddy-cuda)
make cpu=1
make cpu=1 install
/*! \file CublasHandleManager.h
\brief Contains declaration and definition of a single class used to manage a cuBlas handle.
\author Jesper Andersson
\version 1.0b, Sep., 2012.
*/
// Declarations of classes that implements useful
// concepts for the eddy current project.
//
// CublasHandleManager.h
//
// Jesper Andersson, FMRIB Image Analysis Group
//
// Copyright (C) 2020 University of Oxford
//
// Because of a bug in cuda_fp16.hpp, that gets included by hipblas.h, it has to
// be included before any include files that set up anything related to the std-lib.
// If not, there will be an ambiguity in cuda_fp16.hpp about wether to use the
// old-style C isinf or the new (since C++11) std::isinf.
#if !defined(ROCM_SYMLINK_HIPBLAS_H)
#error hipblas.h must be included at the very top of any file including CublasHandleManager.h
#endif
#include <stdio.h>
#pragma push
#pragma diag_suppress = code_is_unreachable // Supress warnings from armawrap
#pragma diag_suppress = expr_has_no_effect // Supress warnings from boost
#include "armawrap/newmat.h"
#include "miscmaths/miscmaths.h"
#include "newimage/newimageall.h"
#pragma pop
#include "EddyHelperClasses.h"
#include <hip/hip_runtime.h>
#include <hip/hip_runtime.h>
class CublasHandleManager
{
public:
CublasHandleManager(const CublasHandleManager&) = delete;
CublasHandleManager& operator=(const CublasHandleManager&) = delete;
static hipblasHandle_t& GetHandle();
private:
CublasHandleManager() {
hipblasStatus_t status;
status = hipblasCreate(&_handle);
if (status != HIPBLAS_STATUS_SUCCESS) throw EDDY::EddyException("EDDY::CublasHandleManager::CublasHandleManager: cuBLAS initialization failed");
}
~CublasHandleManager() { hipblasDestroy(_handle); }
hipblasHandle_t _handle;
};
hipblasHandle_t& CublasHandleManager::GetHandle()
{
static CublasHandleManager instance;
return(instance._handle);
}
/*! \file CublasHandleManager.h
\brief Contains declaration and definition of a single class used to manage a cuBlas handle.
\author Jesper Andersson
\version 1.0b, Sep., 2012.
*/
// Declarations of classes that implements useful
// concepts for the eddy current project.
//
// CublasHandleManager.h
//
// Jesper Andersson, FMRIB Image Analysis Group
//
// Copyright (C) 2020 University of Oxford
//
// Because of a bug in cuda_fp16.hpp, that gets included by cublas_v2.h, it has to
// be included before any include files that set up anything related to the std-lib.
// If not, there will be an ambiguity in cuda_fp16.hpp about wether to use the
// old-style C isinf or the new (since C++11) std::isinf.
#if !defined(CUBLAS_V2_H_)
#error cublas_v2.h must be included at the very top of any file including CublasHandleManager.h
#endif
#include <stdio.h>
#pragma push
#pragma diag_suppress = code_is_unreachable // Supress warnings from armawrap
#pragma diag_suppress = expr_has_no_effect // Supress warnings from boost
#include "armawrap/newmat.h"
#include "miscmaths/miscmaths.h"
#include "newimage/newimageall.h"
#pragma pop
#include "EddyHelperClasses.h"
#include <cuda.h>
#include <cuda_runtime.h>
class CublasHandleManager
{
public:
CublasHandleManager(const CublasHandleManager&) = delete;
CublasHandleManager& operator=(const CublasHandleManager&) = delete;
static cublasHandle_t& GetHandle();
private:
CublasHandleManager() {
cublasStatus_t status;
status = cublasCreate(&_handle);
if (status != CUBLAS_STATUS_SUCCESS) throw EDDY::EddyException("EDDY::CublasHandleManager::CublasHandleManager: cuBLAS initialization failed");
}
~CublasHandleManager() { cublasDestroy(_handle); }
cublasHandle_t _handle;
};
cublasHandle_t& CublasHandleManager::GetHandle()
{
static CublasHandleManager instance;
return(instance._handle);
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment