Skip to content

Operators & Solvers

Operator Convention

All encoding operators in forge follow a unified interface:

Expression Meaning
G * x Forward transform: image → k-space
G / y Adjoint transform: k-space → image

Both operators accept and return arma::Col<std::complex<T1>> vectors at the Armadillo interface boundary. Internally, hot paths use forgeCol/forgeMat for GPU dispatch (see forge Types).

Encoding Operators

Class Description
Gdft<T1> Direct non-uniform DFT with optional off-resonance correction
GdftR2<T1> Gdft extended with R2* field map gradient support
Gnufft<T1> Fast NUFFT via Kaiser-Bessel gridding (CPU + GPU)
Gfft<T1> Uniform Cartesian FFT (wraps FFTW / cuFFT)
SENSE<T1, Tobj> Multi-coil sensitivity encoding wrapping any encoding operator
pcSENSE<T1> Phase-corrected SENSE with per-shot phase maps
pcSenseTimeSeg<T1> pcSENSE combined with time-segmented field correction
TimeSegmentation<T1, Tobj> Off-resonance correction via time-segmented interpolation

Gnufft Pipeline

The NUFFT forward and adjoint transforms are multi-step pipelines:

graph LR
    subgraph "Forward (image → k-space)"
        A[Image] --> B[Deapodize]
        B --> C[Zero-pad]
        C --> D[Oversampled FFT]
        D --> E[KB Interpolation]
        E --> F[k-space samples]
    end
graph RL
    subgraph "Adjoint (k-space → image)"
        G[k-space samples] --> H[KB Gridding]
        H --> I[Inverse FFT]
        I --> J[Crop]
        J --> K[Deapodize]
        K --> L[Image]
    end

Density Compensation

Non-uniform k-space sampling requires density compensation weights (DCF) to correct for variable sampling density before adjoint gridding. forge uses the Pipe iterative algorithm:

\[W_{n+1} = \frac{W_n}{G^H G \, W_n}\]

where \(G^H G\) is the forward-then-adjoint gridding operation. This converges in ~15-20 iterations. Implemented in calc3DDensityCompensation().

Regularization

All regularization operators inherit from Robject<T1>:

Class Description
Robject<T1> Abstract base class for penalty functions
QuadPenalty<T1> Quadratic (Tikhonov) penalty: \(R(\mathbf{x}) = \frac{\beta}{2}\|C\mathbf{x}\|^2\)
TVPenalty<T1> Smooth TV (Charbonnier) penalty: \(\psi(d) = \delta^2(\sqrt{1+(d/\delta)^2}-1)\)

Solvers

Function Description
solve_pwls_pcg Preconditioned weighted least-squares via conjugate gradient (Polak-Ribiere-Polyak)
solve_grad_desc Gradient descent solver
reconSolve High-level reconstruction entry point

The primary solver minimizes:

\[\hat{\mathbf{x}} = \arg\min_{\mathbf{x}} \|\mathbf{W}^{1/2}(\mathbf{y} - A\mathbf{x})\|^2 + R(\mathbf{x})\]

Quick Example

#include "forge/Operators/Gdft.h"
#include "forge/Penalties/QuadPenalty.h"
#include "forge/Solvers/solve_pwls_pcg.hpp"
using namespace arma;

// Build a Cartesian Gdft operator (8x8 image, 64 k-space samples)
uword Nx = 8, Ny = 8, n1 = Nx*Ny, n2 = Nx*Ny;
Col<float> kx(n2), ky(n2), kz(n2, fill::zeros);
Col<float> ix(n1), iy(n1), iz(n1, fill::zeros);
// ... fill kx/ky with Cartesian grid, ix/iy with pixel positions ...
Col<float> FM(n1, fill::zeros), t(n2, fill::zeros);
Gdft<float> G(n1, n2, kx, ky, kz, ix, iy, iz, FM, t);

// Forward transform
Col<cx_float> image(n1, fill::ones);
Col<cx_float> kspace = G * image;

// Adjoint transform
Col<cx_float> back = G / kspace;

// Iterative reconstruction via PCG
Col<float> W(n2, fill::ones);
QuadPenalty<float> R(Nx, Ny, 1, 0.01f);
Col<cx_float> x0(n1, fill::zeros);
Col<cx_float> xhat = solve_pwls_pcg<float>(x0, G, W, kspace, R, 50);