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)
 def test_sensitivity_extraction_2D(self):
     """ This test ensures that the output of the non cartesian kspace
     extraction is same a that of mimicked cartesian extraction in 2D
     """
     _mask = np.ones((self.N, self.N))
     _samples = convert_mask_to_locations(_mask)
     fourier_op = NonCartesianFFT(samples=_samples, shape=(self.N, self.N))
     Img = (np.random.randn(self.num_channel, self.N, self.N) +
            1j * np.random.randn(self.num_channel, self.N, self.N))
     F_img = np.asarray(
         [fourier_op.op(Img[i]) for i in np.arange(self.num_channel)])
     Smaps_gridding, SOS_Smaps = get_Smaps(k_space=F_img,
                                           img_shape=(self.N, self.N),
                                           samples=_samples,
                                           thresh=(0.4, 0.4),
                                           mode='gridding',
                                           min_samples=(-0.5, -0.5),
                                           max_samples=(0.5, 0.5),
                                           n_cpu=1)
     Smaps_NFFT, SOS_Smaps = get_Smaps(k_space=F_img,
                                       img_shape=(self.N, self.N),
                                       thresh=(0.4, 0.4),
                                       samples=_samples,
                                       min_samples=(-0.5, -0.5),
                                       max_samples=(0.5, 0.5),
                                       mode='NFFT')
     np.testing.assert_allclose(Smaps_gridding, Smaps_NFFT)
Example #3
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")
Example #4
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")
Example #5
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)
Example #6
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")
def grad_L(**kwargs):
    # --
    # -- Compute gradient of L with respect to p
    # --
    # INPUTS:   pk: Point where we want to compute the gradient
    #           u0_mat: Ground_truth image
    #           y: kspace data associated to u0_mat
    #           param: list of lower and upper level parameters. Must contain:
    #                   - epsilon: weight of L2 norm in lower level reconstruction
    #                   - gamma: parameter for approximation of L1 norm
    #                   - c: weight of L for upper level cost function
    #                   - beta: weight of P for upper level cost function
    #               Remark: You should choose these parameters to have E(pk)~1 at the beginning (otherwire, L-BFGS-B may stop too early)
    #           fourier_op: Fourier operator for lower level reconstruction
    #           linear_op: Linear operator for lower level reconstruction
    #
    #           max_cgiter (optional): maximum number of Conjugate Gradient iterations (default: 3000)
    #           cgtol (optional): tolerance of Conjugate Gradient iterations (default: 1e-6)
    #           compute_conv (optional): plot convergence if True (default: False)
    #           verbose (optional)

    # -- Getting parameters
    max_cgiter = kwargs.get("max_cgiter", 4000)
    cgtol = kwargs.get("cgtol", 1e-6)
    compute_conv = kwargs.get("compute_conv", False)
    mask_type = kwargs.get("mask_type", "")
    learn_mask = kwargs.get("learn_mask", True)
    learn_alpha = kwargs.get("learn_alpha", True)

    u0_mat = kwargs.get("u0_mat", None)
    param = kwargs.get("param", None)
    y = kwargs.get("y", None)
    pk = kwargs.get("pk", None)

    samples = kwargs.get("samples", [])
    fourier_op = NonCartesianFFT(samples=samples,
                                 shape=u0_mat.shape,
                                 implementation='cpu')
    wavelet_name = kwargs.get("wavelet_name", "")
    wavelet_scale = kwargs.get("wavelet_scale", 1)
    linear_op = WaveletN(wavelet_name=wavelet_name,
                         nb_scale=wavelet_scale,
                         padding_mode="periodization")
    const = kwargs.get("const", {})

    verbose = kwargs.get("verbose", 0)
    cg_conv = []

    if u0_mat is None:
        raise ValueError("A ground truth image u0_mat is needed")
    if y is None: raise ValueError("kspace data y are needed")
    n = len(u0_mat)

    # -- Compute uk from pk with lower level solver if not given
    if verbose >= 0: print("\nStarting PDHG")
    uk, _ = pdhg(y,
                 pk,
                 mask_type=mask_type,
                 fourier_op=fourier_op,
                 linear_op=linear_op,
                 param=param,
                 maxit=50,
                 verbose=verbose,
                 const=const)

    # -- Defining linear operator from pk and uk
    def mv(w):
        w_complex = np.reshape(w[:n**2] + 1j * w[n**2:], (n, n))
        fx = np.reshape(
            Du2_Etot(uk,
                     pk,
                     w_complex,
                     eps=param["epsilon"],
                     fourier_op=fourier_op,
                     y=y,
                     linear_op=linear_op,
                     gamma=param["gamma"]), (n**2, ))
        return np.concatenate([np.real(fx), np.imag(fx)])

    lin = LinearOperator((2 * n**2, 2 * n**2), matvec=mv)

    if verbose >= 0: print("\nStarting Conjugate Gradient method")
    t1 = time.time()
    B = np.reshape(Du_L(uk, u0_mat, param["c"]), (n**2, ))
    B_real = np.concatenate([np.real(B), np.imag(B)])

    def cgcall(x):
        #CG callback function to plot convergence
        if compute_conv:
            cg_conv.append(
                np.linalg.norm(lin(x) - B_real) / np.linalg.norm(B_real))

    x_inter, _ = cg(lin,
                    B_real,
                    tol=cgtol,
                    maxiter=max_cgiter,
                    callback=cgcall)
    if verbose >= 0:
        print(
            f"Finished in {time.time()-t1}s - ||Ax-b||/||b||: {np.linalg.norm(lin(x_inter)-B_real)/np.linalg.norm(B_real)}"
        )

    # -- Plotting
    if compute_conv:
        plt.plot(cg_conv)
        plt.yscale("log")
        plt.title("Convergence of the conjugate gradient")
        plt.xlabel("Number of iterations")
        plt.ylabel("||Ax-b||/||b||")
        #plt.savefig("Upper Level/CG_conv.png")

    if np.linalg.norm(lin(x_inter) - B_real) / np.linalg.norm(B_real) > 1e-3:
        return np.zeros(pk.shape)
    else:
        return -Dpu_Etot(uk,
                         pk,
                         np.reshape(x_inter[:n**2] + 1j * x_inter[n**2:],
                                    (n, n)),
                         eps=param["epsilon"],
                         fourier_op=fourier_op,
                         y=y,
                         linear_op=linear_op,
                         gamma=param["gamma"],
                         learn_mask=learn_mask,
                         learn_alpha=learn_alpha)
Example #8
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
radial_mask = get_sample_data("mri-radial-samples")
kspace_loc = radial_mask.data
density_comp = estimate_density_compensation(kspace_loc, image.shape)

#############################################################################
# Generate the kspace
# -------------------
#
# From the 2D brain slice and the acquisition mask, we retrospectively
# undersample the k-space using a radial acquisition mask
# We then reconstruct using adjoint with and without density compensation

# Get the locations of the kspace samples and the associated observations
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()
# View Input
# image.show()
# mask.show()

#############################################################################
# Generate the kspace
# -------------------
#
# From the 2D brain slice and the acquisition mask, we retrospectively
# undersample the k-space using a non cartesian acquisition mask
# We then grid the kspace to get the gridded solution as a baseline

# Get the kspace observation values for the kspace locations
fourier_op = NonCartesianFFT(samples=kspace_loc,
                             shape=image.shape,
                             n_coils=cartesian_ref_image.shape[0],
                             implementation='gpuNUFFT')
kspace_obs = fourier_op.op(cartesian_ref_image)

# Gridded solution
grid_space = np.linspace(-0.5, 0.5, num=image.shape[0])
grid2D = np.meshgrid(grid_space, grid_space)
grid_soln = np.asarray([
    gridded_inverse_fourier_transform_nd(kspace_loc, kspace_obs_ch,
                                         tuple(grid2D), 'linear')
    for kspace_obs_ch in kspace_obs
])
image_rec0 = pysap.Image(data=np.sqrt(np.sum(np.abs(grid_soln)**2, axis=0)))
# image_rec0.show()
base_ssim = ssim(image_rec0, image)
print('The Base SSIM is : ' + str(base_ssim))
Example #12
0
 def test_gridsearch_single_channel(self):
     """Test Gridsearch script in mri.scripts for
     single channel reconstruction this is a test of sanity
     and not if the reconstruction is right.
     """
     image = get_sample_data('2d-mri')
     mask = np.ones(image.shape)
     kspace_loc = convert_mask_to_locations(mask)
     fourier_op = NonCartesianFFT(samples=kspace_loc, shape=image.shape)
     kspace_data = fourier_op.op(image.data)
     # Define the keyword dictionaries based on convention
     metrics = {
         'ssim': {
             'metric': ssim,
             'mapping': {
                 'x_new': 'test',
                 'y_new': None
             },
             'cst_kwargs': {
                 'ref': image,
                 'mask': None
             },
             'early_stopping': True,
         },
     }
     linear_params = {
         'init_class': WaveletN,
         'kwargs': {
             'wavelet_name': 'sym8',
             'nb_scale': 4,
         }
     }
     regularizer_params = {
         'init_class': SparseThreshold,
         'kwargs': {
             'linear': Identity(),
             'weights': [0, 1e-5],
         }
     }
     optimizer_params = {
         # Just following convention
         'kwargs': {
             'optimization_alg': 'fista',
             'num_iterations': 10,
             'metrics': metrics,
         }
     }
     # Call the launch grid function and obtain results
     raw_results, test_cases, key_names, best_idx = launch_grid(
         kspace_data=kspace_data,
         fourier_op=fourier_op,
         linear_params=linear_params,
         regularizer_params=regularizer_params,
         optimizer_params=optimizer_params,
         reconstructor_kwargs={'gradient_formulation': 'synthesis'},
         reconstructor_class=SingleChannelReconstructor,
         compare_metric_details={'metric': 'ssim'},
         n_jobs=self.n_jobs,
         verbose=1,
     )
     # In this test we dont undersample the kspace so the
     # reconstruction is indeed with mu=0, ie best_idx=0
     np.testing.assert_equal(best_idx, 0)
     np.testing.assert_allclose(
         raw_results[best_idx][0],
         image,
         atol=1e-7,
     )
Example #13
0
img_size = [min(real_img.shape)] * 2
square_mask[
    real_img_size[0] // 2 - img_size[0] // 2 : real_img_size[0] // 2 + img_size[0] // 2,
    real_img_size[1] // 2 - img_size[1] // 2 : real_img_size[1] // 2 + img_size[1] // 2,
] = 1
trajectories = sp.io.loadmat("data/NCTrajectories.mat")

#%% md

# Offline reconstruction

#%%
print(trajectories)
sp = trajectories["sparkling"]
plt.figure()
for idx in range(20):
    plt.scatter(
        sp[idx * 1536 * 2 : (idx + 1) * 1536 * 2 - 1, 0],
        sp[idx * 1536 * 2 : (idx + 1) * 1536 * 2 - 1, 1],
        s=1,
    )
plt.show()

fourier_sparkling = NonCartesianFFT(
    trajectories["sparkling"],
    shape=full_k.shape[1:],
    n_coils=full_k.shape[0],
    implementation="cpu",
    density_comp=trajectories["sparkling_w"],
)
Example #14
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)
def E(**kwargs):
    # --
    # -- Computes the cost of a given mask or parametrisation of mask --
    # --
    # INPUTS:   images: list of all images used to evaluate the reconstruction
    #           kspace_data: list of noised kspace data associated to these images
    #           param: list of lower and upper level parameters. Must contain:
    #                   - epsilon: weight of L2 norm in lower level reconstruction
    #                   - gamma: parameter for approximation of L1 norm
    #                   - c: weight of L for upper level cost function
    #                   - beta: weight of P for upper level cost function
    #               Remark: You should choose these parameters to have E(pk)~1 at the beginning (otherwire, L-BFGS-B may stop too early)
    #           fourier_op: Fourier operator for lower level reconstruction
    #           linear_op: Linear operator for lower level reconstruction
    #           mask_type (optional): learn cartesian mask if mask_type="cartesian", otherwise each point is independant.
    #                                   Other parametrisations may be implemented later.
    #           pk: initial mask. Mandatory if mask_type!="cartesian"
    #           lk: initial mask parametrisation. Mandatory if mask_type="cartesian"
    #           verbose (optional)

    # Getting parameters
    images = kwargs.get("images", None)
    kspace_data = kwargs.get("kspace_data", None)
    param = kwargs.get("param", None)

    samples = kwargs.get("samples", [])
    fourier_op = NonCartesianFFT(samples=samples,
                                 shape=images[0].shape,
                                 implementation='cpu')
    wavelet_name = kwargs.get("wavelet_name", "")
    wavelet_scale = kwargs.get("wavelet_scale", 1)
    linear_op = WaveletN(wavelet_name=wavelet_name,
                         nb_scale=wavelet_scale,
                         padding_mode="periodization")

    parallel = kwargs.get("parallel", False)
    mask_type = kwargs.get("mask_type", "")
    lk = kwargs.get("lk", None)
    pk = kwargs.get("pk", None)

    verbose = kwargs.get("verbose", 0)
    if verbose >= 0: print("\n\nEVALUATING E(p)")

    # Checking inputs errors
    if images is None or len(images) < 1:
        raise ValueError("At least one image is needed")
    if param is None: raise ValueError("Lower level parameters must be given")
    if len(images) != len(kspace_data):
        raise ValueError("Need as many images and kspace data")

    #Compute P(pk/lk)
    Ep = 0
    if mask_type == "cartesian":
        pk = pcart(lk)
        Ep = P(lk, param["beta"])
    elif mask_type == "radial_CO":
        n_rad = kwargs.get("n_rad")
        pk = pradCO(lk, n_rad)
        Ep = P(lk, param["beta"])
    else:
        Ep = P(pk, param["beta"])

    #Compute L(pk/lk)
    Nimages = len(images)
    if parallel:
        uk_list = Parallel(n_jobs=-1, verbose=0)(
            delayed(pdhg)(kspace_data[i],
                          pk,
                          samples=samples,
                          shape=images[0].shape,
                          wavelet_name=wavelet_name,
                          wavelet_scale=wavelet_scale,
                          param=param,
                          mask_type=mask_type,
                          const=kwargs.get("const", {}),
                          verbose=-1) for i in range(Nimages))
        Ep += np.sum(
            [L(uk_list[i][0], images[i], param["c"])
             for i in range(Nimages)]) / Nimages
    else:
        for i in range(Nimages):
            if verbose >= 0: print(f"\nImage {i+1}:")
            u0_mat, y = images[i], kspace_data[i]

            if verbose > 0: print("\nStarting PDHG")
            uk, _ = pdhg(y,
                         pk,
                         maxit=50,
                         fourier_op=fourier_op,
                         linear_op=linear_op,
                         **kwargs)

            Ep += L(uk, u0_mat, param["c"]) / Nimages

    return Ep
Example #16
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
Example #17
0
    def test_sensitivity_extraction_2D(self):
        """ This test ensures that the output of the non cartesian kspace
        extraction is same a that of mimicked cartesian extraction in 2D
        """
        mask = np.ones((self.N, self.N))
        samples = convert_mask_to_locations(mask)
        fourier_op = NonCartesianFFT(samples=samples, shape=(self.N, self.N))
        Img = (np.random.randn(self.num_channel, self.N, self.N) +
               1j * np.random.randn(self.num_channel, self.N, self.N))
        F_img = np.asarray(
            [fourier_op.op(Img[i]) for i in np.arange(self.num_channel)])
        Smaps_gridding, SOS_Smaps = get_Smaps(k_space=F_img,
                                              img_shape=(self.N, self.N),
                                              samples=samples,
                                              thresh=(0.4, 0.4),
                                              mode='gridding',
                                              min_samples=(-0.5, -0.5),
                                              max_samples=(0.5, 0.5),
                                              n_cpu=1)
        Smaps_NFFT_dc, SOS_Smaps_dc = get_Smaps(k_space=F_img,
                                                img_shape=(self.N, self.N),
                                                thresh=(0.4, 0.4),
                                                samples=samples,
                                                min_samples=(-0.5, -0.5),
                                                max_samples=(0.5, 0.5),
                                                mode='NFFT',
                                                density_comp=np.ones(
                                                    samples.shape[0]))
        Smaps_NFFT, SOS_Smaps = get_Smaps(
            k_space=F_img,
            img_shape=(self.N, self.N),
            thresh=(0.4, 0.4),
            samples=samples,
            min_samples=(-0.5, -0.5),
            max_samples=(0.5, 0.5),
            mode='NFFT',
        )
        Smaps_hann_NFFT, SOS_Smaps = get_Smaps(
            k_space=F_img,
            img_shape=(self.N, self.N),
            thresh=0.4,
            samples=samples,
            min_samples=(-0.5, -0.5),
            max_samples=(0.5, 0.5),
            window_fun="Hann",
            mode='NFFT',
        )
        Smaps_hann_gridding, SOS_Smaps = get_Smaps(
            k_space=F_img,
            img_shape=(self.N, self.N),
            thresh=0.4,
            samples=samples,
            min_samples=(-0.5, -0.5),
            max_samples=(0.5, 0.5),
            window_fun="Hann",
            mode='gridding',
        )

        Smaps_hamming_NFFT, SOS_Smaps = get_Smaps(
            k_space=F_img,
            img_shape=(self.N, self.N),
            thresh=0.4,
            samples=samples,
            min_samples=(-0.5, -0.5),
            max_samples=(0.5, 0.5),
            window_fun="Hamming",
            mode='NFFT',
        )
        Smaps_hamming_gridding, SOS_Smaps = get_Smaps(
            k_space=F_img,
            img_shape=(self.N, self.N),
            thresh=0.4,
            samples=samples,
            min_samples=(-0.5, -0.5),
            max_samples=(0.5, 0.5),
            window_fun="Hamming",
            mode='gridding',
        )
        Smaps_call_gridding, SOS_Smaps = get_Smaps(
            k_space=F_img,
            img_shape=(self.N, self.N),
            thresh=0.4,
            samples=samples,
            min_samples=(-0.5, -0.5),
            max_samples=(0.5, 0.5),
            window_fun=lambda x: 1,
            mode='gridding',
        )
        Smaps_call_NFFT, SOS_Smaps = get_Smaps(
            k_space=F_img,
            img_shape=(self.N, self.N),
            thresh=0.4,
            samples=samples,
            min_samples=(-0.5, -0.5),
            max_samples=(0.5, 0.5),
            window_fun=lambda x: 1,
            mode='NFFT',
        )

        np.testing.assert_allclose(Smaps_gridding, Smaps_NFFT_dc)
        np.testing.assert_allclose(Smaps_gridding, Smaps_NFFT)
        np.testing.assert_allclose(Smaps_hann_gridding, Smaps_hann_NFFT)
        np.testing.assert_allclose(Smaps_hamming_gridding, Smaps_hamming_NFFT)
        np.testing.assert_allclose(Smaps_call_gridding, Smaps_call_NFFT)
        # Test that we raise assert for bad mode
        np.testing.assert_raises(ValueError,
                                 get_Smaps,
                                 k_space=F_img,
                                 img_shape=(self.N, self.N),
                                 thresh=(0.4, 0.4),
                                 samples=samples,
                                 min_samples=(-0.5, -0.5),
                                 max_samples=(0.5, 0.5),
                                 mode='test')
        # Test that we raise assert for bad window
        np.testing.assert_raises(
            ValueError,
            get_Smaps,
            k_space=F_img,
            img_shape=(self.N, self.N),
            thresh=(0.4, 0.4),
            samples=samples,
            min_samples=(-0.5, -0.5),
            max_samples=(0.5, 0.5),
            window_fun='test',
            mode='gridding',
        )
Example #18
0
# View Input
# image.show()
# mask.show()

#############################################################################
# Generate the kspace
# -------------------
#
# From the 2D brain slice and the acquisition mask, we retrospectively
# undersample the k-space using a radial acquisition mask
# We then reconstruct the zero order solution as a baseline

# Get the locations of the kspace samples and the associated observations
fourier_op = NonCartesianFFT(samples=kspace_loc,
                             shape=image.shape,
                             implementation='cpu')
kspace_obs = fourier_op.op(image.data)

# Gridded solution
grid_space = np.linspace(-0.5, 0.5, num=image.shape[0])
grid2D = np.meshgrid(grid_space, grid_space)
grid_soln = gridded_inverse_fourier_transform_nd(kspace_loc, kspace_obs,
                                                 tuple(grid2D), 'linear')
image_rec0 = pysap.Image(data=grid_soln)
# image_rec0.show()
base_ssim = ssim(image_rec0, image)
print('The Base SSIM is : ' + str(base_ssim))

#############################################################################
# FISTA optimization