def test_NUFFT_3D(self):
     """Test the adjoint operator for the 3D non-Cartesian Fourier transform
     on GPU
     """
     for num_channels in self.num_channels:
         for platform in self.platforms:
             _mask = np.random.randint(2, size=(self.N, self.N, self.N))
             _samples = convert_mask_to_locations(_mask)
             fourier_op_dir = NonCartesianFFT(samples=_samples,
                                              shape=(self.N, self.N,
                                                     self.N),
                                              implementation=platform,
                                              n_coils=num_channels)
             Img = (np.random.randn(num_channels, self.N, self.N, self.N) +
                    1j * np.random.randn(num_channels, self.N, self.N,
                                         self.N))
             f = (np.random.randn(num_channels, _samples.shape[0]) +
                  1j * np.random.randn(num_channels, _samples.shape[0]))
             f_p = fourier_op_dir.op(Img)
             I_p = fourier_op_dir.adj_op(f)
             x_d = np.vdot(Img, I_p)
             x_ad = np.vdot(f_p, f)
             np.testing.assert_allclose(x_d, x_ad, rtol=1e-5)
             print("NFFT in 3D adjoint test passes on GPU with"
                   "num_channels = " + str(num_channels) + " on "
                   "platform " + platform)
Пример #2
0
 def test_NFFT_2D(self):
     """Test the adjoint operator for the 2D non-Cartesian Fourier
     transform, with density compensator set to 1, to vet the code
     path, the test is unchanged otherwise
     """
     for num_channels in self.num_channels:
         print("Testing with num_channels=" + str(num_channels))
         for i in range(self.max_iter):
             _mask = np.random.randint(2, size=(self.N, self.N))
             _samples = convert_mask_to_locations(_mask)
             print("Process NFFT in 2D test '{0}'...", i)
             fourier_op = NonCartesianFFT(samples=_samples,
                                          shape=(self.N, self.N),
                                          n_coils=num_channels,
                                          implementation='cpu',
                                          density_comp=np.ones(
                                              (num_channels,
                                               _samples.shape[0])))
             Img = np.random.randn(num_channels, self.N, self.N) + \
                 1j * np.random.randn(num_channels, self.N, self.N)
             f = np.random.randn(num_channels, _samples.shape[0]) + \
                 1j * np.random.randn(num_channels, _samples.shape[0])
             f_p = fourier_op.op(Img)
             I_p = fourier_op.adj_op(f)
             x_d = np.vdot(Img, I_p)
             x_ad = np.vdot(f_p, f)
             np.testing.assert_allclose(x_d, x_ad, rtol=1e-10)
     print(" NFFT in 2D adjoint test passes")
Пример #3
0
    def test_similarity_stack_3D_nfft(self):
        """Test the similarity of stacked implementation of Fourier transform
        to that of NFFT
        """
        for channel in self.num_channels:
            print("Testing with num_channels=" + str(channel))
            for N in [16, 32]:
                # Nz is the number of slices, this would check both N=Nz
                # and N!=Nz
                Nz = 16
                _mask = np.random.randint(2, size=(N, N))

                # Generate random mask along z
                sampling_z = np.random.randint(2, size=Nz)
                _mask3D = np.zeros((N, N, Nz))
                for idx, acq_z in enumerate(sampling_z):
                    _mask3D[:, :, idx] = _mask * acq_z
                _samples = convert_mask_to_locations(_mask3D)

                print("Process Stack-3D similarity with NFFT for N=" + str(N))
                fourier_op_stack = Stacked3DNFFT(kspace_loc=_samples,
                                                 shape=(N, N, Nz),
                                                 implementation='cpu',
                                                 n_coils=channel)
                fourier_op_nfft = NonCartesianFFT(samples=_samples,
                                                  shape=(N, N, Nz),
                                                  implementation='cpu',
                                                  n_coils=channel)
                Img = np.random.random((channel, N, N, Nz)) + \
                    1j * np.random.random((channel, N, N, Nz))
                f = np.random.random((channel, _samples.shape[0])) + \
                    1j * np.random.random((channel, _samples.shape[0]))
                start_time = time.time()
                stack_f_p = fourier_op_stack.op(Img)
                stack_I_p = fourier_op_stack.adj_op(f)
                stack_runtime = time.time() - start_time
                start_time = time.time()
                nfft_f_p = fourier_op_nfft.op(Img)
                nfft_I_p = fourier_op_nfft.adj_op(f)
                np.testing.assert_allclose(stack_f_p, nfft_f_p, rtol=1e-9)
                np.testing.assert_allclose(stack_I_p, nfft_I_p, rtol=1e-9)
                nfft_runtime = time.time() - start_time
                print("For N=" + str(N) + " Speedup = " +
                      str(nfft_runtime / stack_runtime))
        print("Stacked FFT in 3D adjoint test passes")
Пример #4
0
    def generate_test_NFFT(self, shape, field_scale):
        """ Factorized code to test 2D and 3D wrapped NFFT with
        different homogeneous B0 field shifts at constant time.
        """
        for L, i, n_coils in product(self.L, range(self.max_iter),
                                     self.n_coils):
            mask = np.random.randint(2, size=shape)
            samples = convert_mask_to_locations(mask)
            samples = samples[:samples.shape[0]
                            - (samples.shape[0] % shape[0])]

            field_shift = field_scale * np.random.randint(-150, 150)
            field_map = field_shift * np.ones(shape)

            # Prepare reference and wrapper operators
            fourier_op = NonCartesianFFT(
                samples=samples,
                shape=shape,
                n_coils=n_coils,
                implementation='cpu',
                density_comp=np.ones((n_coils, samples.shape[0]))
            )
            wrapper_op = ORCFFTWrapper(fourier_op, field_map=field_map,
                                       time_vec=np.ones(shape[0]),
                                       mask=np.ones(shape),
                                       num_interpolators=L,
                                       n_bins=self.n_bins)

            # Forward operator
            img = np.squeeze(np.random.randn(n_coils, *shape) \
                      + 1j * np.random.randn(n_coils, *shape))
            ksp_fft = fourier_op.op(img)
            ksp_wra = wrapper_op.op(img * np.exp(-2j * np.pi * field_shift))
            np.testing.assert_allclose(ksp_fft, ksp_wra, rtol=self.rtol)

            # Adjoint operator
            ksp = np.squeeze(np.random.randn(n_coils, samples.shape[0]) \
                      + 1j * np.random.randn(n_coils, samples.shape[0]))
            img_fft = fourier_op.adj_op(ksp)
            img_wra = wrapper_op.adj_op(ksp * np.exp(2j * np.pi * field_shift))
            np.testing.assert_allclose(img_fft, img_wra, rtol=self.rtol)
Пример #5
0
 def test_NFFT_3D(self):
     """Test the adjoint operator for the 3D non-Cartesian Fourier transform
     """
     for num_channels in self.num_channels:
         print("Testing with num_channels=" + str(num_channels))
         for i in range(self.max_iter):
             _mask = np.random.randint(2, size=(self.N, self.N, self.N))
             _samples = convert_mask_to_locations(_mask)
             print("Process NFFT test in 3D '{0}'...", i)
             fourier_op = NonCartesianFFT(samples=_samples,
                                          shape=(self.N, self.N, self.N),
                                          n_coils=num_channels,
                                          implementation='cpu')
             Img = np.random.randn(num_channels, self.N, self.N, self.N) + \
                 1j * np.random.randn(num_channels, self.N, self.N, self.N)
             f = np.random.randn(num_channels, _samples.shape[0]) + \
                 1j * np.random.randn(num_channels, _samples.shape[0])
             f_p = fourier_op.op(Img)
             I_p = fourier_op.adj_op(f)
             x_d = np.vdot(Img, I_p)
             x_ad = np.vdot(f_p, f)
             np.testing.assert_allclose(x_d, x_ad, rtol=1e-10)
     print(" NFFT in 3D adjoint test passes")
Пример #6
0
def get_Smaps(k_space,
              img_shape,
              samples,
              thresh,
              min_samples,
              max_samples,
              mode='gridding',
              method='linear',
              window_fun=None,
              density_comp=None,
              n_cpu=1,
              fourier_op_kwargs=None):
    r"""
    Get Smaps for from pMRI sample data.

    Estimate the sensitivity maps information from parallel mri
    acquisition and for variable density sampling scheme where the k-space
    center had been heavily sampled.

    Reference : Self-Calibrating Nonlinear Reconstruction Algorithms for
    Variable Density Sampling and Parallel Reception MRI
    https://ieeexplore.ieee.org/abstract/document/8448776

    Parameters
    ----------
    k_space: np.ndarray
        The acquired kspace of shape (M,L), where M is the number of samples
        acquired and L is the number of coils used
    img_shape: tuple
        The final output shape of Sensitivity Maps.
    samples: np.ndarray
        The sample locations where the above k_space data was acquired
    thresh: tuple
        The value of threshold in kspace for thresholding k-space center
    min_samples: tuple
        The minimum values in k-space where gridding must be done
    max_samples: tuple
        The maximum values in k-space where gridding must be done
    mode: string 'FFT' | 'NFFT' | 'gridding' | 'Stack', default='gridding'
        Defines the mode in which we would want to interpolate,
        NOTE: FFT should be considered only if the input has
        been sampled on the grid
    method: string 'linear' | 'cubic' | 'nearest', default='linear'
        For gridding mode, it defines the way interpolation must be done
    window_fun: "Hann", "Hanning", "Hamming", or a callable, default None.
        The window function to apply to the selected data. It is computed with
        the center locations selected. Only works with circular mask.
        If window_fun is a callable, it takes as input the n_samples x n_dims
        of samples position and would return an array of n_samples weight to be
        applied to the selected k-space values, before the smaps estimation.
    density_comp: np.ndarray default None
        The density compensation for kspace data in case it exists and we
        use density compensated adjoint for Smap estimation
    n_cpu: int default=1
        Number of parallel jobs in case of parallel MRI
    fourier_op_kwargs: dict, default None
        The keyword arguments given to fourier_op initialization if
        mode == 'NFFT'. If None, we choose implementation of fourier op to
        'gpuNUFFT' if library is installed.

    Returns
    -------
    Smaps: np.ndarray
        the estimated sensitivity maps of shape (img_shape, L) with L the
        number of channels
    SOS: np.ndarray
        The sum of Square used to extract the sensitivity maps

    Notes
    -----

    The Hann (or Hanning) and Hamming window  of width :math:`2\theta` are defined as:
    .. math::

    w(x,y) = a_0 - (1-a_0) * \cos(\pi * \sqrt{x^2+y^2}/\theta),
    \sqrt{x^2+y^2} \le \theta

    In the case of Hann window :math:`a_0=0.5`.
    For Hamming window we consider the optimal value in the equiripple sens:
    :math:`a_0=0.53836`.
    .. Wikipedia:: https://en.wikipedia.org/wiki/Window_function#Hann_and_Hamming_windows


    """
    if len(min_samples) != len(img_shape) \
            or len(max_samples) != len(img_shape):
        raise NameError('The img_shape, max_samples, and '
                        'min_samples must be of same length')
    k_space, samples, *density_comp = \
        extract_k_space_center_and_locations(
            data_values=k_space,
            samples_locations=samples,
            thr=thresh,
            img_shape=img_shape,
            is_fft=mode == 'FFT',
            window_fun=window_fun,
            density_comp=density_comp,
        )
    if density_comp:
        density_comp = density_comp[0]
    else:
        density_comp = None
    if samples is None:
        mode = 'FFT'
    L, M = k_space.shape
    Smaps_shape = (L, *img_shape)
    Smaps = np.zeros(Smaps_shape).astype('complex128')
    if mode == 'FFT':
        if not M == img_shape[0] * img_shape[1]:
            raise ValueError([
                'The number of samples in the k-space must be',
                'equal to the (image size, the number of coils)'
            ])
        k_space = k_space.reshape(Smaps_shape)
        for l in range(Smaps_shape[2]):
            Smaps[l] = pfft.ifftshift(pfft.ifft2(pfft.fftshift(k_space[l])))
    elif mode == 'NFFT':
        if fourier_op_kwargs is None:
            if gpunufft_available:
                fourier_op_kwargs = {'implementation': 'gpuNUFFT'}
            else:
                fourier_op_kwargs = {}
        fourier_op = NonCartesianFFT(
            samples=samples,
            shape=img_shape,
            density_comp=density_comp,
            n_coils=L,
            **fourier_op_kwargs,
        )
        Smaps = fourier_op.adj_op(np.ascontiguousarray(k_space))
    elif mode == 'Stack':
        grid_space = [
            np.linspace(min_samples[i],
                        max_samples[i],
                        num=img_shape[i],
                        endpoint=False)
            for i in np.arange(np.size(img_shape) - 1)
        ]
        grid = np.meshgrid(*grid_space)
        kspace_plane_loc, _, sort_pos, idx_mask_z = \
            get_stacks_fourier(samples, img_shape)
        Smaps = Parallel(n_jobs=n_cpu, mmap_mode='r+')(
            delayed(gridded_inverse_fourier_transform_stack)(
                kspace_data_sorted=k_space[l, sort_pos],
                kspace_plane_loc=kspace_plane_loc,
                idx_mask_z=idx_mask_z,
                grid=tuple(grid),
                volume_shape=img_shape,
                method=method) for l in range(L))
        Smaps = np.asarray(Smaps)
    elif mode == 'gridding':
        grid_space = [
            np.linspace(min_samples[i],
                        max_samples[i],
                        num=img_shape[i],
                        endpoint=False) for i in np.arange(np.size(img_shape))
        ]
        grid = np.meshgrid(*grid_space)
        Smaps = \
            Parallel(n_jobs=n_cpu, verbose=1, mmap_mode='r+')(
                delayed(gridded_inverse_fourier_transform_nd)(
                    kspace_loc=samples,
                    kspace_data=k_space[l],
                    grid=tuple(grid),
                    method=method
                )
                for l in range(L)
            )
        Smaps = np.asarray(Smaps)
    else:
        raise ValueError('Bad smap_extract_mode chosen! '
                         'Please choose between : '
                         '`FFT` | `NFFT` | `gridding` | `Stack`')
    SOS = np.sqrt(np.sum(np.abs(Smaps)**2, axis=0))
    for r in range(L):
        Smaps[r] /= SOS
    return Smaps, SOS
def get_Smaps(k_space, img_shape, samples, thresh,
              min_samples, max_samples, mode='Gridding',
              method='linear', n_cpu=1):
    """
    This method estimate the sensitivity maps information from parallel mri
    acquisition and for variable density sampling scheme where teh k-space
    center had been heavily sampled.
    Reference : Self-Calibrating Nonlinear Reconstruction Algorithms for
    Variable Density Sampling and Parallel Reception MRI
    https://ieeexplore.ieee.org/abstract/document/8448776

    Parameters
    ----------
    k_space: np.ndarray
        The acquired kspace of shape (M,L), where M is the number of samples
        acquired and L is the number of coils used
    img_shape: tuple
        The final output shape of Sensitivity Maps.
    samples: np.ndarray
        The sample locations where the above k_space data was acquired
    thresh: tuple
        The value of threshold in kspace for thresholding k-space center
    min_samples: tuple
        The minimum values in k-space where gridding must be done
    max_samples: tuple
        The maximum values in k-space where gridding must be done
    mode: string 'FFT' | 'NFFT' | 'gridding', default='gridding'
        Defines the mode in which we would want to interpolate,
        NOTE: FFT should be considered only if the input has
        been sampled on the grid
    method: string 'linear' | 'cubic' | 'nearest', default='linear'
        For gridding mode, it defines the way interpolation must be done
    n_cpu: intm default=1
        Number of parallel jobs in case of parallel MRI

    Returns
    -------
    Smaps: np.ndarray
        the estimated sensitivity maps of shape (img_shape, L) with L the
        number of channels
    ISOS: np.ndarray
        The sum of Square used to extract the sensitivity maps
    """
    if len(min_samples) != len(img_shape) \
            or len(max_samples) != len(img_shape) \
            or len(thresh) != len(img_shape):
        raise NameError('The img_shape, max_samples, '
                        'min_samples and thresh must be of same length')
    k_space, samples = \
        extract_k_space_center_and_locations(
            data_values=k_space,
            samples_locations=samples,
            thr=thresh,
            img_shape=img_shape)
    if samples is None:
        mode = 'FFT'
    L, M = k_space.shape
    Smaps_shape = (L, *img_shape)
    Smaps = np.zeros(Smaps_shape).astype('complex128')
    if mode == 'FFT':
        if not M == img_shape[0]*img_shape[1]:
            raise ValueError(['The number of samples in the k-space must be',
                              'equal to the (image size, the number of coils)'
                              ])
        k_space = k_space.reshape(Smaps_shape)
        for l in range(Smaps_shape[2]):
            Smaps[l] = pfft.ifftshift(pfft.ifft2(pfft.fftshift(k_space[l])))
    elif mode == 'NFFT':
        fourier_op = NonCartesianFFT(samples=samples, shape=img_shape,
                                     implementation='cpu')
        Smaps = np.asarray([fourier_op.adj_op(k_space[l]) for l in range(L)])
    elif mode == 'Stack':
        grid_space = [np.linspace(min_samples[i],
                                  max_samples[i],
                                  num=img_shape[i],
                                  endpoint=False)
                      for i in np.arange(np.size(img_shape)-1)]
        grid = np.meshgrid(*grid_space)
        kspace_plane_loc, z_sample_loc, sort_pos = \
            get_stacks_fourier(samples)
        Smaps = Parallel(n_jobs=n_cpu)(
            delayed(gridded_inverse_fourier_transform_stack)
            (kspace_plane_loc=kspace_plane_loc,
             z_samples=z_sample_loc,
             kspace_data=k_space[l, sort_pos],
             grid=tuple(grid),
             method=method) for l in range(L))
        Smaps = np.asarray(Smaps)
    else:
        grid_space = [np.linspace(min_samples[i],
                                  max_samples[i],
                                  num=img_shape[i],
                                  endpoint=False)
                      for i in np.arange(np.size(img_shape))]
        grid = np.meshgrid(*grid_space)
        Smaps = \
            Parallel(n_jobs=n_cpu)(delayed(
                gridded_inverse_fourier_transform_nd)
                                   (kspace_loc=samples,
                                    kspace_data=k_space[l],
                                    grid=tuple(grid),
                                    method=method) for l in range(L))
        Smaps = np.asarray(Smaps)
    SOS = np.sqrt(np.sum(np.abs(Smaps)**2, axis=0))
    for r in range(L):
        Smaps[r] /= SOS
    return Smaps, SOS
fourier_op = NonCartesianFFT(
    samples=kspace_loc,
    shape=image.shape,
    implementation='gpuNUFFT',
)
fourier_op_density_comp = NonCartesianFFT(
    samples=kspace_loc,
    shape=image.shape,
    implementation='gpuNUFFT',
    density_comp=density_comp
)
# Get the kspace data retrospectively. Note that this can be done with
# `fourier_op_density_comp` as the forward operator is the same
kspace_obs = fourier_op.op(image.data)

# Simple adjoint
image_rec0 = pysap.Image(data=np.abs(fourier_op.adj_op(kspace_obs)))
# image_rec0.show()
base_ssim = ssim(image_rec0, image)
print('The SSIM from Adjoint is : ' + str(base_ssim))

# Density Compensation adjoint:
# This preconditions k-space giving a result closer to inverse
image_rec1 = pysap.Image(data=np.abs(
    fourier_op_density_comp.adj_op(kspace_obs))
)
# image_rec1.show()
new_ssim = ssim(image_rec1, image)
print('The SSIM from Density '
      'compensated Adjoint is : ' + str(new_ssim))
Пример #9
0
def pdhg(data, p, **kwargs):
    # --
    # -- MAIN LOWER LEVEL FUNCTION
    # --
    # INPUTS: - data: kspace measurements
    #         - p: p[:-1]=subsampling mask S(p), p[-1]=regularisation parameter alpha(p)
    #                   So len(p)=len(data)+1
    #         - fourier_op: fourier operator from a full mask of same shape as the final image.
    #         - linear_op: linear operator used in regularisation functions
    #                      For the moment, only use waveletN.
    #         - param: lower level energy parameters
    #                  Must contain parameters keys "epsilon" and "gamma".
    #           mask_type (optional): type of mask used ("cartesian", "radial"). Assume a cartesian mask if not given.
    # --
    # OPTIONAL INPUTS:
    #         - const: algorithm constants if we already know the values we want to use for tau and sigma
    #                  If not given, will compute them according to what is said in the article.
    #         - compute_energy: bool, we compute and return energy over iterations if True (default: False)
    #         - ground_truth: matrix representing the true image the data come from (default: None). If not None, we compute the ssim over iterations.
    #         - maxit,tol: We stop the algorithm when the norm of the difference between two steps
    #                      is smaller than tol or after maxit iterations (default: 200, 1e-4)
    # --
    # OUTPUTS: - uk: final image
    #          - norms(, energy, ssims): evolution of stopping criterion (and energy if compute_energy is True / ssims if ground_truth not None)

    fourier_op = kwargs.get("fourier_op", None)
    linear_op = kwargs.get("linear_op", None)
    param = kwargs.get("param", None)

    # Create fourier_op and linear_op if not given for multithreading
    if fourier_op is None:
        samples = kwargs.get("samples", [])
        shape = kwargs.get("shape", ())
        if samples is not None:
            fourier_op = NonCartesianFFT(samples=samples,
                                         shape=shape,
                                         implementation='cpu')
    if fourier_op is None:
        raise ValueError("A fourier operator fourier_op must be given")
    if linear_op is None:
        wavelet_name = kwargs.get("wavelet_name", "")
        wavelet_scale = kwargs.get("wavelet_scale", 1)
        if wavelet_name != "":
            linear_op = WaveletN(wavelet_name=wavelet_name,
                                 nb_scale=wavelet_scale,
                                 padding_mode="periodization")
    if linear_op is None:
        raise ValueError("A linear operator linear_op must be given")

    if param is None: raise ValueError("Lower level parameters must be given")
    mask_type = kwargs.get("mask_type", "")

    const = kwargs.get("const", {})
    compute_energy = kwargs.get("compute_energy", False)
    ground_truth = kwargs.get("ground_truth", None)
    maxit = kwargs.get("maxit", 200)
    tol = kwargs.get("tol", 1e-6)
    verbose = kwargs.get("verbose", 1)

    #Global parameters
    p, pn1 = p[:-1], p[-1]
    epsilon = param["epsilon"]
    gamma = param["gamma"]
    n_iter = 0
    #Algorithm constants
    const = compute_constants(param, const, p)
    if verbose >= 0: print("Sigma:", const["sigma"], "\nTau:", const["tau"])

    #Initializing
    uk = fourier_op.adj_op(p * data)
    vk = np.copy(uk)
    wk = linear_op.op(uk)
    uk_bar = np.copy(uk)
    norm = 2 * tol

    #For plots
    if compute_energy:
        energy = []
    if ground_truth is not None:
        ssims = []
    norms = []

    #Main loop
    t1 = time.time()
    while n_iter < maxit and norm > tol:
        uk, vk, wk, uk_bar, norm = step(uk, vk, wk, uk_bar, const, p, pn1,
                                        data, param, linear_op, fourier_op,
                                        mask_type)
        n_iter += 1

        #Saving informations
        norms.append(norm)
        if compute_energy:
            energy.append(
                energy_wavelet(uk, p, pn1, data, gamma, epsilon, linear_op,
                               fourier_op))
        if ground_truth is not None:
            ssims.append(ssim(uk, ground_truth))

        #Printing
        if n_iter % 10 == 0 and verbose > 0:
            if compute_energy:
                print(n_iter, " iterations:\nCost:", energy[-1], "\nNorm:",
                      norm, "\n")
            else:
                print(n_iter, " iterations:\nNorm:", norm, "\n")
    if verbose >= 0:
        print("Finished in", time.time() - t1, "seconds.")

    #Return
    if compute_energy and ground_truth is not None:
        return uk, norms, energy, ssims
    elif ground_truth is not None:
        return uk, norms, ssims
    elif compute_energy:
        return uk, norms, energy
    else:
        return uk, norms
Пример #10
0
import numpy as np
from mri.operators import NonCartesianFFT

traj = np.load('/volatile/temp_traj.npy')

fourier = NonCartesianFFT(traj, (384, 384, 208),
                          'gpuNUFFT',
                          n_coils=20,
                          smaps=np.ones((20, 384, 384, 208)),
                          osf=2)

for i in range(10):
    print(i)
    K = fourier.op(np.zeros((384, 384, 208)))
    I = fourier.adj_op(K)