Source code for etomo.operators.fourier.fourier

"""
These operators are not necessary as the reconstruction is faster and more
direct with Radon operator, but they can be used to compare CS with ML
approaches as it is easier to find an implementation of fourier transform in
tensorflow than an implementation of the Radon transform
gpuNUFFT is taken directly from pysap-mri
"""
import warnings
import numpy as np

PYNUFFT_AVAILABLE = True
GPUNUFFT_AVAILABLE = True
try:
    import pynufft
except ImportError:
    PYNUFFT_AVAILABLE = False
    warnings.warn('pynufft not installed')
try:
    from gpuNUFFT import NUFFTOp
except ImportError:
    GPUNUFFT_AVAILABLE = False
    warnings.warn('gpuNUFFT not installed')


[docs]class FourierBase: """ Base Fourier transform operator class"""
[docs] def op(self, img): """ This method calculates a Fourier transform""" raise NotImplementedError("'op' is an abstract method.")
[docs] def adj_op(self, x): """ This method calculates the adjoint Fourier transform of a real or complex sequence""" raise NotImplementedError("'adj_op' is an abstract method.")
[docs]class NUFFT2(FourierBase): """ Standard 2D non catesian Fast Fourrier Transform class Attributes ---------- samples: numpy.ndarray((m' * n, 2)) samples coordinates in the Fourier domain. shape: tuple(int) shape of the final reconstructed image (m, n) (not necessarily a square matrix). normalized: bool, default False tells if operator is normalized or not. """ def __init__(self, samples, shape, normalized=True): """ Initialize the 'NUFFT2' class. Parameters ---------- samples: numpy.ndarray((m' * n', 3)) samples coordinates in the Fourier domain. shape: tuple(int) shape of the final reconstructed image (m, n) (not necessarily a square matrix). normalized: bool, default False tells if operator is normalized or not. """ self.dtype = float if not PYNUFFT_AVAILABLE: raise ValueError('pynufft is not installed. Please install it of ' 'use pynfft instead.') self.n_coils = 1 self.dim = samples.shape[1] self.plan = pynufft.NUFFT() shape_fourier = [] for dim_size in shape: shape_fourier.append(int(2 * dim_size)) self.Jd = (6, 6) if self.dim == 2 else (5, 5, 5) self.plan.plan(samples, tuple(shape), tuple(shape_fourier), self.Jd) self.shape = shape self.samples = samples self.normalized = normalized self.norm_const = np.sqrt(samples.shape[0]) if normalized else 1
[docs] def op(self, img): """ This method calculates the masked non-cartesian Fourier transform of a 2-D image. Parameters ---------- img: numpy.ndarray((m, n)) input 2D array with the same shape as the mask. Returns ------- x: numpy.ndarray((m * n)) masked Fourier transform of the input image. """ return np.real(self.plan.forward(img)) / self.norm_const
[docs] def adj_op(self, x): """ This method calculates the adjoint non-cartesian Fourier transform of a 2-D coefficients array. Parameters ---------- x: numpy.ndarray((m' * n')) masked non-cartesian Fourier transform 2D data. Returns ------- img: numpy.ndarray((m, n)) adjoint 2D discrete Fourier transform of the input coefficients. """ return np.real(self.plan.adjoint(x)) * np.prod(self.plan.Kd) / \ self.norm_const
[docs]class gpuNUFFT(FourierBase): """ GPU implementation of N-D non uniform Fast Fourrier Transform class. Attributes ---------- samples: numpy.ndarray the normalized kspace location values in the Fourier domain. shape: tuple(int) shape of the image operator: NUFFTOp object to carry out operation n_coils: int, default 1 Number of coils used to acquire the signal in case of multiarray receiver coils acquisition. If n_coils > 1, please organize data as n_coils X data_per_coil """ def __init__(self, samples, shape, density_comp=None, n_coils=1, kernel_width=3, sector_width=8, osf=2, balance_workload=True, smaps=None): """ Initilize the 'NUFFT' class. Parameters ---------- samples: numpy.ndarray the kspace sample locations in the Fourier domain, normalized between -0.5 and 0.5 shape: tuple(int) shape of the image density_comp: numpy.ndarray, default None. k-space weighting, density compensation, if not specified equal weightage is given. kernel_width: int, default 3 interpolation kernel width (usually 3 to 7) sector_width: int, default 8 sector width to use osf: int, default 2 oversampling factor (usually between 1 and 2) balance_workload: bool, default True whether the workloads need to be balanced """ self.dtype = "complex128" if not GPUNUFFT_AVAILABLE: raise ValueError('gpuNUFFT library is not installed, ' 'please refer to README') self.shape = shape if density_comp is None: density_comp = np.ones(samples.shape[0]) if smaps is None: self.uses_sense = False else: smaps = np.asarray( [np.reshape(smap_ch.T, smap_ch.size) for smap_ch in smaps] ).T self.uses_sense = True self.operator = NUFFTOp( np.reshape(samples, samples.shape[::-1], order='F'), shape, n_coils, smaps, density_comp, kernel_width, sector_width, osf, balance_workload ) self.samples = samples
[docs] def op(self, image, interpolate_data=False): """ This method calculates the masked non-cartesian Fourier transform of a 2D / 3D image. Parameters ---------- image: numpy.ndarray input array with the same shape as shape. interpolate_data: bool, default False if set to True, the image is just apodized and interpolated to kspace locations. This is used for density estimation. Returns ------- numpy.ndarray Non Uniform Fourier transform of the input image. """ coeff = self.operator.op( np.reshape(image.T, image.size), interpolate_data ) # Data is always returned as num_channels X coeff_array, # so for single channel, we extract single array if not self.uses_sense: coeff = coeff[0] return coeff
[docs] def adj_op(self, coeff, grid_data=False): """ This method calculates adjoint of non-uniform Fourier transform of a 1-D coefficients array. Parameters ---------- coeff: numpy.ndarray masked non-uniform Fourier transform 1D data. grid_data: bool, default False if True, the kspace data is gridded and returned, this is used for density compensation Returns ------- numpy.ndarray adjoint operator of Non Uniform Fourier transform of the input coefficients. """ image = self.operator.adj_op(coeff, grid_data) image = np.squeeze(image).T # The received data from gpuNUFFT is num_channels x Nx x Ny x Nz, # hence we use squeeze return np.squeeze(image)