Tools

The packages AbstractNFFT.jl and NFFT.jl are on purpose kept small and focussed. Additional tooling that relates to the NFFT is offloaded into a package NFFTTools.jl.

Sampling Density

The first tool that NFFTTools.jl offers is the computation of the sampling density. To motivate this let us have a look at the normalized variant of the DFT matrix $\bm{F}$. $\bm{F}$ is unitary, which means that

\[ \bm{F}^{\mathsf{H}} \bm{F} = \bm{I} \]

where $\bm{I}$ is the identity matrix. In other words, $\bm{F}^{\mathsf{H}}$ is the inverse of $\bm{F}^{\mathsf{H}}$.

Now let's switch to the NFFT. Have you already wondered why we don't call the adjoint the inverse? Well it's because in general we have

\[ \bm{A}^{\mathsf{H}} \bm{A} \neq \bm{I} \]

In fact, the inverse of the NFFT is a much more complicated subject since the linear system $\bm{A} \bm{f} = \hat{\bm{f}}$ can have one, no or many solutions because $\bm{A}$can be under- or over-determined.

The good news is that in most cases, with $J \approx N$ and no complete clustering of the sampling nodes, one can find a diagonal weighting matrix $\bm{W} = \left( w_j \right)_{j=1}^{J}$such that

\[ \bm{A}^{\mathsf{H}} \bm{W} \bm{A} \approx \bm{I} \]

The weights $w_j$ can be considered to we quadrature weights that account for the area covered by the node $\bm{k}_j$.

NFFTTools.jl provides the function sdc that takes an existing plan and calculated suitable density weights:

weights = sdc(p, iters = 10)

The function implements the method proposed in Pipe & Menon, 1999. Mag Reson Med, 186, 179.

Toeplitz Kernel

The aforementioned matrix $\bm{A}^{\mathsf{H}} \bm{W} \bm{A}$ arises when solving linear system of the form

\[\bm{A} \bm{f} = \hat{\bm{f}}\]

which can be done via the normal equation

\[\bm{A}^{\mathsf{H}} \bm{W} \bm{A} \bm{f} = \bm{A}^{\mathsf{H}} \bm{W} \hat{\bm{f}}\]

The normal or Gram matrix $\bm{A}^{\mathsf{H}} \bm{W} \bm{A}$ has a Toeplitz structure. For multi-dimensional NFFT is is a block Toeplitz matrix with Toeplitz blocks. A Toeplitz matrix (and its block variants) can be embedded into a circulant matrix of twice the size in each dimension. Circulant matrices are known to be diagonalizable by ordinary FFTs. This means we can multiply with $\bm{A}^{\mathsf{H}} \bm{W} \bm{A}$ by just two FFTs of size $2\bm{N}$, which is basically the same amount of FFTs as are required for an NFFT-based calculation of matrix-vector products with $\bm{A}^{\mathsf{H}} \bm{W} \bm{A}$. But the important difference is that no convolution step is required for the Toeplitz-based approach. This can often lead to speedups, which are in particular important when using the Gram matrix in iterative solvers (see Fessler et al., IEEE Trans. Sig. Proc., 53, 9 for the mathematical background).

With NFFTTools.jl one can calculate the kernel required to exploit the Toeplitz structure with the function calculateToeplitzKernel. Multiplications with the Gram matrix can then be done using the function convolveToeplitzKernel!. The following outlines a complete example for the usage of both functions:

using NFFT, NFFTTools, FFTW

N = (32, 32)                            # signal size
Ñ = 2 .* N                              # oversampled signal size

k = Float32.(rand(2, 1000) .- 0.5)      # 2D sampling nodes
p = plan_nfft(k, Ñ)                     # 2D NFFT plan
fftplan = plan_fft(zeros(ComplexF32, Ñ));

λ = Array{ComplexF32}(undef, Ñ)         # pre-allocate Toeplitz kernel 
calculateToeplitzKernel!(λ, p, k,fftplan)       # calculate Toeplitz kernel 

y = randn(ComplexF32, Ñ)
convolveToeplitzKernel!(y, λ)           # multiply with Gram matrix