Skip to content
Snippets Groups Projects
SpMatMatrices.cpp 8.55 KiB
//
//  Declarations for functions generating specialised 
//  sparse matrices of type SpMat
//
//  SpMatMatrices.h
//
//  Declares global functions for generating specialised
//  sparse matrices of type SpMat
//
// Jesper Andersson, FMRIB Image Analysis Group
//
// Copyright (C) 2019 University of Oxford
//
/*  CCOPYRIGHT  */

#include <vector>
#include "newmat.h"
#include "SpMat.h"
#include "SpMatMatrices.h"

namespace MISCMATHS {

/*!
 * Global function that creates and returns a symmetric
 * Toeplitz matrix with dimensions col.Nrows() x col.Nrows() and where the 
 * first column is given by col and all subsequent columns are translated
 * and shifted versions of that column.
 * \return A sparse symmetric Toeplitz matrix
 * \param[in] col First column of matrix
 */
MISCMATHS::SpMat<float> SparseSymmetricToeplitz(const NEWMAT::ColumnVector& col)
{
  unsigned int mn = static_cast<unsigned int>(col.Nrows());
  unsigned int nnz = 0; // No of non-zeros per column
  for (unsigned int i=0; i<mn; i++) nnz += (col(i+1) == 0) ? 0 : 1;
  std::vector<unsigned int> indx(nnz);
  std::vector<float> val(nnz);
  {
    unsigned int i = 0; unsigned int j = 0;
    for (i=0, j=0; i<mn; i++) if (col(i+1) != 0) { indx[j] = i; val[j++] = static_cast<float>(col(i+1)); }
  }
  unsigned int *irp = new unsigned int[nnz*mn];
  unsigned int *jcp = new unsigned int[mn+1];
  double *sp = new double[nnz*mn];
  unsigned int irp_cntr = 0;
  for (unsigned int col=0; col<mn; col++) {
    jcp[col] = irp_cntr;
    for (unsigned int r=0; r<nnz; r++) {
      irp[irp_cntr] = indx[r];
      sp[irp_cntr++] = val[r];
      indx[r] = (indx[r] == mn-1) ? 0 : indx[r]+1;      
    }
  }
  jcp[mn] = irp_cntr;
  MISCMATHS::SpMat<float> tpmat(mn,mn,irp,jcp,sp);
  delete [] irp; delete [] jcp; delete [] sp;
  return(tpmat);
}

/*!
 * Global function that creates and returns a symmetric matrix with dimensions 
 * prod(isz) x prod(isz) and which represent an approximate Hessian for
 * Bending energy. It is approximate because it only considers the straight
 * second derivatives.
 * \return A sparse symmetric Hessian of Bending Energy
 * \param[in] isz 3 element vector specifying matrix size of image
 * \param[in] vxs 3 element vector with voxel size in mm
 * \param[in] bc Boundary condition (PERIODIC or MIRROR)
 */
MISCMATHS::SpMat<float> Sparse3DBendingEnergyHessian(const std::vector<unsigned int>& isz, 
						     const std::vector<double>&       vxs,
						     MISCMATHS::BoundaryCondition     bc) 
{
  unsigned int mn = isz[0]*isz[1]*isz[2];
  unsigned int *irp = new unsigned int[3*mn]; // Worst case, might be slightly smaller
  unsigned int *jcp = new unsigned int[mn+1];
  double *sp = new double[3*mn];
  // x-direction
  unsigned int irp_cntr = 0;
  double sf = 1 / vxs[0]; // Let us scale all directions to mm^{-1}
  for (unsigned int k=0; k<isz[2]; k++) {
    for (unsigned int j=0; j<isz[1]; j++) {
      for (unsigned int i=0; i<isz[0]; i++) {
	jcp[k*isz[0]*isz[1] + j*isz[0] + i] = irp_cntr;
	if (bc == MISCMATHS::PERIODIC) {
	  if (i==0) {
	    irp[irp_cntr] = k*isz[0]*isz[1] + j*isz[0];
	    sp[irp_cntr++] = 2.0 * sf;
	    irp[irp_cntr] = k*isz[0]*isz[1] + j*isz[0] + 1;
	    sp[irp_cntr++] = -1.0 * sf;
	    irp[irp_cntr] = k*isz[0]*isz[1] + j*isz[0] + isz[0]-1;
	    sp[irp_cntr++] = -1.0 * sf;
	  }
	  else if (i==isz[0]-1) {
	    irp[irp_cntr] = k*isz[0]*isz[1] + j*isz[0];
	    sp[irp_cntr++] = -1.0 * sf;
	    irp[irp_cntr] = k*isz[0]*isz[1] + j*isz[0] + i - 1;
	    sp[irp_cntr++] = -1.0 * sf;
	    irp[irp_cntr] = k*isz[0]*isz[1] + j*isz[0] + i;
	    sp[irp_cntr++] = 2.0 * sf;	    
	  }
	  else {
	    irp[irp_cntr] = k*isz[0]*isz[1] + j*isz[0] + i - 1;
	    sp[irp_cntr++] = -1.0 * sf;
	    irp[irp_cntr] = k*isz[0]*isz[1] + j*isz[0] + i;
	    sp[irp_cntr++] = 2.0 * sf;	    
	    irp[irp_cntr] = k*isz[0]*isz[1] + j*isz[0] + i + 1;
	    sp[irp_cntr++] = -1.0 * sf;
	  }
	}
	else if (bc == MISCMATHS::MIRROR) {
	  if (i==0) {
	    irp[irp_cntr] = k*isz[0]*isz[1] + j*isz[0];
	    sp[irp_cntr++] = 2.0 * sf;
	    irp[irp_cntr] = k*isz[0]*isz[1] + j*isz[0] + 1;
	    sp[irp_cntr++] = -2.0 * sf;
	  }
	  else if (i==isz[0]-1) {
	    irp[irp_cntr] = k*isz[0]*isz[1] + j*isz[0] + i - 1;
	    sp[irp_cntr++] = -2.0 * sf;
	    irp[irp_cntr] = k*isz[0]*isz[1] + j*isz[0] + i;
	    sp[irp_cntr++] = 2.0 * sf;	    
	  }
	  else {
	    irp[irp_cntr] = k*isz[0]*isz[1] + j*isz[0] + i - 1;
	    sp[irp_cntr++] = -1.0 * sf;
	    irp[irp_cntr] = k*isz[0]*isz[1] + j*isz[0] + i;
	    sp[irp_cntr++] = 2.0 * sf;	    
	    irp[irp_cntr] = k*isz[0]*isz[1] + j*isz[0] + i + 1;
	    sp[irp_cntr++] = -1.0 * sf;
	  }
	}
      }
    }
  }
  jcp[mn] = irp_cntr;
  MISCMATHS::SpMat<float> At(mn,mn,irp,jcp,sp);
  MISCMATHS::SpMat<float> AtA = At * At.t();

  // y-direction
  irp_cntr = 0;
  sf = 1 / vxs[1]; // Let us scale all directions to mm^{-1}
  for (unsigned int k=0; k<isz[2]; k++) {
    for (unsigned int j=0; j<isz[1]; j++) {
      for (unsigned int i=0; i<isz[0]; i++) {
	jcp[k*isz[0]*isz[1] + j*isz[0] + i] = irp_cntr;
	if (bc == MISCMATHS::PERIODIC) {
	  if (j==0) {
	    irp[irp_cntr] = k*isz[0]*isz[1] + i;
	    sp[irp_cntr++] = 2.0 * sf;
	    irp[irp_cntr] = k*isz[0]*isz[1] + isz[0] + i;
	    sp[irp_cntr++] = -1.0;
	    irp[irp_cntr] = k*isz[0]*isz[1] + (isz[1]-1)*isz[0] + i;
	    sp[irp_cntr++] = -1.0;
	  }
	  else if (j==isz[1]-1) {
	    irp[irp_cntr] = k*isz[0]*isz[1] + i;
	    sp[irp_cntr++] = -1.0;
	    irp[irp_cntr] = k*isz[0]*isz[1] + (j-1)*isz[0] + i;
	    sp[irp_cntr++] = -1.0;
	    irp[irp_cntr] = k*isz[0]*isz[1] + j*isz[0] + i;
	    sp[irp_cntr++] = 2.0;	    
	  }
	  else {
	    irp[irp_cntr] = k*isz[0]*isz[1] + (j-1)*isz[0] + i;
	    sp[irp_cntr++] = -1.0 * sf;
	    irp[irp_cntr] = k*isz[0]*isz[1] + j*isz[0] + i;
	    sp[irp_cntr++] = 2.0 * sf;	    
	    irp[irp_cntr] = k*isz[0]*isz[1] + (j+1)*isz[0] + i;
	    sp[irp_cntr++] = -1.0 * sf;
	  }
	}
	else if (bc == MISCMATHS::MIRROR) {
	  if (j==0) {
	    irp[irp_cntr] = k*isz[0]*isz[1] + i;
	    sp[irp_cntr++] = 2.0 * sf;
	    irp[irp_cntr] = k*isz[0]*isz[1] + isz[0] + i;
	    sp[irp_cntr++] = -2.0;
	  }
	  else if (j==isz[1]-1) {
	    irp[irp_cntr] = k*isz[0]*isz[1] + (j-1)*isz[0] + i;
	    sp[irp_cntr++] = -2.0;
	    irp[irp_cntr] = k*isz[0]*isz[1] + j*isz[0] + i;
	    sp[irp_cntr++] = 2.0;	    
	  }
	  else {
	    irp[irp_cntr] = k*isz[0]*isz[1] + (j-1)*isz[0] + i;
	    sp[irp_cntr++] = -1.0 * sf;
	    irp[irp_cntr] = k*isz[0]*isz[1] + j*isz[0] + i;
	    sp[irp_cntr++] = 2.0 * sf;	    
	    irp[irp_cntr] = k*isz[0]*isz[1] + (j+1)*isz[0] + i;
	    sp[irp_cntr++] = -1.0 * sf;
	  }
	}
      }
    }
  }
  jcp[mn] = irp_cntr;
  At = MISCMATHS::SpMat<float>(mn,mn,irp,jcp,sp);
  AtA += At * At.t();

  // z-direction
  irp_cntr = 0;
  sf = 1 / vxs[2]; // Let us scale all directions to mm^{-1}
  for (unsigned int k=0; k<isz[2]; k++) {
    for (unsigned int j=0; j<isz[1]; j++) {
      for (unsigned int i=0; i<isz[0]; i++) {
	jcp[k*isz[0]*isz[1] + j*isz[0] + i] = irp_cntr;
	if (bc == MISCMATHS::PERIODIC) {
	  if (k==0) {
	    irp[irp_cntr] = j*isz[0] + i;
	    sp[irp_cntr++] = 2.0 * sf;
	    irp[irp_cntr] = isz[0]*isz[1] + j*isz[0] + i;
	    sp[irp_cntr++] = -1.0 * sf;
	    irp[irp_cntr] = (isz[2]-1)*isz[0]*isz[1] + j*isz[0] + i;
	    sp[irp_cntr++] = -1.0 * sf;
	  }
	  else if (k==isz[2]-1) {
	    irp[irp_cntr] = j*isz[0] + i;
	    sp[irp_cntr++] = -1.0 * sf;
	    irp[irp_cntr] = (k-1)*isz[0]*isz[1] + j*isz[0] + i;
	    sp[irp_cntr++] = -1.0 * sf;
	    irp[irp_cntr] = k*isz[0]*isz[1] + j*isz[0] + i;
	    sp[irp_cntr++] = 2.0 * sf;	    
	  }
	  else {
	    irp[irp_cntr] = (k-1)*isz[0]*isz[1] + j*isz[0] + i;
	    sp[irp_cntr++] = -1.0 * sf;
	    irp[irp_cntr] = k*isz[0]*isz[1] + j*isz[0] + i;
	    sp[irp_cntr++] = 2.0 * sf;	    
	    irp[irp_cntr] = (k+1)*isz[0]*isz[1] + j*isz[0] + i;
	    sp[irp_cntr++] = -1.0 * sf;
	  }
	}
	else if (bc == MISCMATHS::MIRROR) {
	  if (k==0) {
	    irp[irp_cntr] = j*isz[0] + i;
	    sp[irp_cntr++] = 2.0 * sf;
	    irp[irp_cntr] = isz[0]*isz[1] + j*isz[0] + i;
	    sp[irp_cntr++] = -2.0 * sf;
	  }
	  else if (k==isz[2]-1) {
	    irp[irp_cntr] = (k-1)*isz[0]*isz[1] + j*isz[0] + i;
	    sp[irp_cntr++] = -2.0 * sf;
	    irp[irp_cntr] = k*isz[0]*isz[1] + j*isz[0] + i;
	    sp[irp_cntr++] = 2.0 * sf;	    
	  }
	  else {
	    irp[irp_cntr] = (k-1)*isz[0]*isz[1] + j*isz[0] + i;
	    sp[irp_cntr++] = -1.0 * sf;
	    irp[irp_cntr] = k*isz[0]*isz[1] + j*isz[0] + i;
	    sp[irp_cntr++] = 2.0 * sf;	    
	    irp[irp_cntr] = (k+1)*isz[0]*isz[1] + j*isz[0] + i;
	    sp[irp_cntr++] = -1.0 * sf;
	  }
	}
      }
    }
  }
  jcp[mn] = irp_cntr;
  At = MISCMATHS::SpMat<float>(mn,mn,irp,jcp,sp);
  AtA += At * At.t();
  delete [] irp; delete [] jcp; delete [] sp;
  return(AtA);
}

} // End namespace MISCMATHS