Non-Cartesian sampling: SPARKLING imaging

Non-Cartesian sampling: SPARKLING imaging#

We explore the performance of SPARKLING (Spreading projection Algorithm for Rapid K-space sampLING) as non-Cartesian imaging technique. We do not actually provide the code of this algorithm but instead upload result files containing trajectories generated from the previous radial in-out initialization. For details, see the recently published paper: Lazarus et al, “SPARKLING: variable‐density k‐space filling curves for accelerated \(T_2^*\)‐weighted MRI”, Magn Reson Med 2019; 81:3643:3661.

  • Author: Philippe Ciuciu (philippe.ciuciu@cea.fr)

  • Date: 06/24/2022

  • Update: 01/29/2024 (dependence on MRI-NUFFT for density compensation)

  • Update: 01/30/2024: add 2D cones

  • Target: IEEE EMBS-SPS Summer School on Novel acquisition and image reconstruction strategies in accelerated Magnetic Resonance Imaging

#DISPLAY T2* MR IMAGE
%matplotlib inline

import numpy as np
import os.path as op
import os
import math
import cmath
import matplotlib.pyplot as plt
import sys

import brainweb_dl as bwdl

import mrinufft as mn
from mrinufft import get_density, displayConfig
from mrinufft.trajectories import display
from mrinufft.density import voronoi
from mrinufft.trajectories import initialize_2D_spiral
from mrinufft.trajectories.display import display_2D_trajectory


plt.rcParams["image.origin"]="lower"
plt.rcParams["image.cmap"]='Greys_r'

mri_img = bwdl.get_mri(4, "T1")[80,:,:].astype(np.float32)
print(mri_img.shape)
img_size = mri_img.shape[0]
plt.figure()
plt.imshow(abs(mri_img))
plt.title("Original brain image")
plt.show()
/volatile/github-ci-mind-inria/gpu_mind_runner/_work/mri-acq-recon-book/mri-acq-recon-book/venv/lib/python3.10/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html
  from .autonotebook import tqdm as notebook_tqdm
(256, 256)
_images/8033343ab6201e82c67067e1964a9c01ba190be160d4300c4fa7acdff76b3b8a.png
from scipy.io import loadmat

cwd = os.getcwd()
dirimg_2d = op.join(cwd,"..","data")

k_spark = loadmat(op.join(cwd, "..", "data","samples_SPARKLING_N512_nc34x3073_OS1.mat"))
rfactor = img_size/34
print(np.floor(rfactor))
k_spark_vec = k_spark['samples']

Kmax = np.amax(k_spark_vec)
#print(Kmax)

k_spark_vec = k_spark_vec*1/(2*np.pi*Kmax)

#save in npz format in the outdir directory
outdir = op.join(cwd,"..","output")
filename_traj = "sparkling_radial_N"  + str(img_size) + ".npz"
outfile = op.join(outdir, filename_traj)
np.savez(outfile, k_spark_vec)

k_spark = plt.figure(figsize=(7,7))
plt.scatter(k_spark_vec[:,0],k_spark_vec[:,1], marker = '.', s=0.1)
plt.grid()
#Figure layout

unit = 1/4
tick = np.arange(-0.5, 0.5 + unit, unit)

label = [r"$-\frac{1}{2}$", r"$-\frac{1}{4}$", r"$0$", r"$+\frac{1}{4}$",  r"$+\frac{1}{2}$"]

plt.xticks(tick/np.pi,labels = label, fontsize = 16) ; plt.yticks(tick/np.pi,labels = label, fontsize = 16)
plt.xlabel(r"$k_x$", fontsize = 22) ; plt.ylabel(r"$k_y$", fontsize = 22)
plt.title("K-space sampling, spiral in-out initialization",fontsize = 18)
plt.show()
7.0
---------------------------------------------------------------------------
FileNotFoundError                         Traceback (most recent call last)
Cell In[2], line 20
     18 filename_traj = "sparkling_radial_N"  + str(img_size) + ".npz"
     19 outfile = op.join(outdir, filename_traj)
---> 20 np.savez(outfile, k_spark_vec)
     22 k_spark = plt.figure(figsize=(7,7))
     23 plt.scatter(k_spark_vec[:,0],k_spark_vec[:,1], marker = '.', s=0.1)

File ~/gpu_mind_runner/_work/mri-acq-recon-book/mri-acq-recon-book/venv/lib/python3.10/site-packages/numpy/lib/npyio.py:639, in savez(file, *args, **kwds)
    555 @array_function_dispatch(_savez_dispatcher)
    556 def savez(file, *args, **kwds):
    557     """Save several arrays into a single file in uncompressed ``.npz`` format.
    558 
    559     Provide arrays as keyword arguments to store them under the
   (...)
    637 
    638     """
--> 639     _savez(file, args, kwds, False)

File ~/gpu_mind_runner/_work/mri-acq-recon-book/mri-acq-recon-book/venv/lib/python3.10/site-packages/numpy/lib/npyio.py:736, in _savez(file, args, kwds, compress, allow_pickle, pickle_kwargs)
    733 else:
    734     compression = zipfile.ZIP_STORED
--> 736 zipf = zipfile_factory(file, mode="w", compression=compression)
    738 for key, val in namedict.items():
    739     fname = key + '.npy'

File ~/gpu_mind_runner/_work/mri-acq-recon-book/mri-acq-recon-book/venv/lib/python3.10/site-packages/numpy/lib/npyio.py:103, in zipfile_factory(file, *args, **kwargs)
    101 import zipfile
    102 kwargs['allowZip64'] = True
--> 103 return zipfile.ZipFile(file, *args, **kwargs)

File ~/gpu_mind_runner/_work/_tool/Python/3.10.16/x64/lib/python3.10/zipfile.py:1254, in ZipFile.__init__(self, file, mode, compression, allowZip64, compresslevel, strict_timestamps)
   1252 while True:
   1253     try:
-> 1254         self.fp = io.open(file, filemode)
   1255     except OSError:
   1256         if filemode in modeDict:

FileNotFoundError: [Errno 2] No such file or directory: '/volatile/github-ci-mind-inria/gpu_mind_runner/_work/mri-acq-recon-book/mri-acq-recon-book/src/../output/sparkling_radial_N256.npz'
# Display parameters
figure_size = 5.5  # Figure size for trajectory plots
subfigure_size = 3  # Figure size for subplots
one_shot = 0  # Highlight one shot in particular

def show_density(density, figure_size, *, log_scale=False):
    density = density.T[::-1]

    plt.figure(figsize=(figure_size, figure_size))
    if log_scale:
        plt.imshow(density, cmap="jet", norm=colors.LogNorm())
    else:
        plt.imshow(density, cmap="jet")

    ax = plt.gca()
    ax.set_xlabel("kx", fontsize=displayConfig.fontsize)
    ax.set_ylabel("ky", fontsize=displayConfig.fontsize)
    ax.set_aspect("equal")

    plt.axis(False)
    plt.colorbar()
    plt.show()


def show_densities(function, arguments, subfig_size, *, log_scale=False):
    # Initialize k-space densities with varying option
    densities = [function(arg).T[::-1] for arg in arguments]

    # Plot the trajectories side by side
    fig, axes = plt.subplots(
        1,
        len(densities),
        figsize=(len(densities) * subfig_size, subfig_size),
        constrained_layout=True,
    )

    for ax, arg, density in zip(axes, arguments, densities):
        ax.set_title(str(arg), fontsize=displayConfig.fontsize)
        ax.set_xlabel("kx", fontsize=displayConfig.fontsize)
        ax.set_ylabel("ky", fontsize=displayConfig.fontsize)
        ax.set_aspect("equal")
        if log_scale:
            ax.imshow(density, cmap="jet", norm=colors.LogNorm())
        else:
            ax.imshow(density, cmap="jet")
        ax.axis(False)
    plt.show()


def show_locations(function, arguments, subfig_size, *, log_scale=False):
    # Initialize k-space locations with varying option
    locations = [function(arg) for arg in arguments]

    # Plot the trajectories side by side
    fig, axes = plt.subplots(
        1,
        len(locations),
        figsize=(len(locations) * subfig_size, subfig_size),
        constrained_layout=True,
    )

    for ax, arg, location in zip(axes, arguments, locations):
        ax.set_title(str(arg), fontsize=displayConfig.fontsize)
        ax.set_xlim(-1.05 * KMAX, 1.05 * KMAX)
        ax.set_ylim(-1.05 * KMAX, 1.05 * KMAX)
        ax.set_xlabel("kx", fontsize=displayConfig.fontsize)
        ax.set_ylabel("ky", fontsize=displayConfig.fontsize)
        ax.set_aspect("equal")
        ax.scatter(location[..., 0], location[..., 1], s=3)
    plt.show()
#Cutoff/decay density
cutoff_density = mn.create_cutoff_decay_density(shape=shape_2d, cutoff=0.2, decay=2)
show_density(cutoff_density, figure_size=figure_size)

#cutoff (float)
arguments = [0, 0.1, 0.2, 0.3]
function = lambda x: mn.create_cutoff_decay_density(
    shape=shape_2d,
    cutoff=x,
    decay=2,
    resolution=x,
)

show_densities(
    function,
    arguments,
    subfig_size=subfigure_size,
)

#decay (float)
arguments = [-1, 0, 0.5, 2]
# Density parameters
shape_2d = (img_size, img_size)

function = lambda x: mn.create_cutoff_decay_density(
    shape=shape_2d,
    cutoff=0.2,
    decay=x,
)
show_densities(
    function,
    arguments,
    subfig_size=subfigure_size,
)

arguments = [None, (1, 1), (1, 2), (1e-3, 0.5e-3)]

#dataset = bwdl.get_mri(4, "T1")[:, ::2, ::2]
#energy_density = mn.create_energy_density(dataset=dataset)
#show_density(energy_density, figure_size=figure_size, log_scale=True)


chauffert_density = mn.create_chauffert_density(
    shape=shape_2d,
    wavelet_basis="haar",
    nb_wavelet_scales=3,
)
show_density(chauffert_density, figure_size=figure_size)

arguments = ["haar", "rbio2.2", "coif4", "sym8"]
function = lambda x: mn.create_chauffert_density(
    shape=shape_2d,
    wavelet_basis=x,
    nb_wavelet_scales=3,
)
show_densities(
    function,
    arguments,
    subfig_size=subfigure_size,
)

arguments = [1, 2, 3, 4]
function = lambda x: mn.create_chauffert_density(
    shape=shape_2d,
    wavelet_basis="haar",
    nb_wavelet_scales=x,
)
show_densities(
    function,
    arguments,
    subfig_size=subfigure_size,
)

# Linear gradient
density = np.tile(np.linspace(0, 1, shape_2d[1])[:, None], (1, shape_2d[0]))
# Square center
density[
    3 * shape_2d[0] // 8 : 5 * shape_2d[0] // 8,
    3 * shape_2d[1] // 8 : 5 * shape_2d[1] // 8,
] = 2
# Outer circle
density = np.where(
    np.linalg.norm(np.indices(shape_2d) - shape_2d[0] / 2, axis=0) < shape_2d[0] / 2,
    density,
    0,
)
# Normalization
custom_density = density / np.sum(density)

show_density(custom_density, figure_size=figure_size)
_images/7e8efb3bf5adeabe10d6367aaa3c8330f13104c883e0504a5cc6993bb2ff4370.png _images/621018ee5f0d99a91a753756bdec313bee63a0ca3bb51bd202f8aff33f085941.png _images/68eae74b11bc951ff3385eafec76981e6f420be94bbeed35f04a3058c439a612.png _images/8bc117b18a1ac6cb50fa8d7f2a147e819519f00c937660abb75a37eeec466c14.png _images/25a4187595d7bb67d31a525de9f1ef7204bd47cd9329aaeec3340baa6f6adc32.png _images/4db4b215c58990dd2cc3729e6be07318a40f388ed8fcb0bc95804fccd4a0b97e.png
densities = {
    "Cutoff/Decay": cutoff_density,
    "Energy": energy_density,
    "Chauffert": chauffert_density,
    "Custom": custom_density,
}

arguments = densities.keys()
function = lambda x: densities[x]
show_densities(function, arguments, subfig_size=subfigure_size)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[24], line 4
      1 densities = {
      2     "Cutoff/Decay": cutoff_density,
      3     "Energy": energy_density,
----> 4     "Chauffert": chauffert_density,
      5     "Custom": custom_density,
      6 }
      8 arguments = densities.keys()
      9 function = lambda x: densities[x]

NameError: name 'chauffert_density' is not defined
rfactor = 4
nb_shots = math.ceil(img_size/rfactor)
print("number of shots: {}".format(nb_shots))

# define the regularly spaced samples on a single shot
num_samples = img_size

# Density parameters
shape_2d = (img_size, img_size)
cutoff_density = mn.create_cutoff_decay_density(shape=shape_2d, cutoff=0.2, decay=2)
#show_density(cutoff_density, figure_size=figure_size)

function = lambda x: mn.sample_from_density(nb_shots * num_samples, densities[x], method="lloyd")

arguments = densities.keys()
show_locations(function, arguments, subfig_size=subfigure_size)
number of shots: 64
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[8], line 24
     19 Nc = 10  # Number of shots
     20 Ns = 50  # Number of samples per shot
---> 24 arguments = densities.keys()
     25 function = lambda x: mn.sample_from_density(Nc * Ns, densities[x], method="lloyd")
     26 show_locations(function, arguments, subfig_size=subfigure_size)

NameError: name 'densities' is not defined
data=convert_locations_to_mask(k_spark_vec, mri_img.shape)
#fourier_op = NonCartesianFFT(samples=k_spark_vec, shape=mri_img.shape,
#                             implementation='cpu')
#kspace_obs = fourier_op.op(mri_img.data)

NufftOperator = mrinufft.get_operator("finufft")
# For better image quality we use a density compensation
#density = "voronoi"
density = "cell_count"
#density = None
# And create the associated operator.
if density == None:
    nufft = NufftOperator(
        k_spark_vec.reshape(-1, 2), shape=mri_img.shape, density=None, n_coils=1
    )
elif density == "voronoi":
    voronoi_weights = get_density("voronoi", k_spark_vec.reshape(-1, 2))
    nufft = NufftOperator(
        k_spark_vec.reshape(-1, 2), shape=mri_img.shape, density=voronoi_weights, n_coils=1
    )
else:
    cell_count_weights = get_density("cell_count", k_spark_vec.reshape(-1, 2), shape=mri_img.shape, osf=2.0)
    nufft = NufftOperator(
        k_spark_vec.reshape(-1, 2), shape=mri_img.shape, density=cell_count_weights, n_coils=1, upsampfac=2.0
    )

kspace_obs = nufft.op(mri_img.data)  # Image -> Kspace
image2 = nufft.adj_op(kspace_obs)  # Kspace -> Image
grid_space = np.linspace(-0.5, 0.5, num=mri_img.shape[0])
grid2D = np.meshgrid(grid_space, grid_space)
#grid_soln = gridded_inverse_fourier_trans#form_nd(k_spark_vec, kspace_obs,
#                                                 tuple(grid2D), 'linear')
#plt.imshow(np.abs(grid_soln), cmap='gray'
plt.imshow(np.abs(image2), cmap='gray')
# Calculate SSIM
base_ssim = ssim(image2, mri_img)
plt.title('Gridded Solution\nSSIM = ' + str(base_ssim))
plt.show()
from scipy.spatial import Voronoi, voronoi_plot_2d

vor = Voronoi(k_spark_vec.reshape(-1, 2))
fig = voronoi_plot_2d(vor)

fig = voronoi_plot_2d(vor, show_vertices=False, line_colors='orange',
                      line_width=2, line_alpha=0.6, point_size=2)
plt.show()
import brainweb_dl as bwdl
from mrinufft.trajectories import initialize_2D_cones
from mrinufft.trajectories.display import display_2D_trajectory

mri_2D = bwdl.get_mri(4, "T1")[80, ...].astype(np.float32)
print(mri_2D.shape)
plt.figure()
plt.imshow(abs(mri_2D))
plt.show()
from mrinufft import get_density, get_operator, check_backend

traj = initialize_2D_cones(256, 256).astype(np.float32)

nufft = get_operator("finufft")(traj, mri_2D.shape, density=False)
kspace = nufft.op(mri_2D)
adjoint = nufft.adj_op(kspace)

fig, axs = plt.subplots(1, 3, figsize=(15, 5))
axs[0].imshow(abs(mri_2D))
display_2D_trajectory(traj, subfigure=axs[1])
axs[2].imshow(abs(adjoint))

voronoi_weights = get_density("voronoi", traj)
nufft_voronoi = get_operator("finufft")(
    traj, shape=mri_2D.shape, density=voronoi_weights
)
adjoint_voronoi = nufft_voronoi.adj_op(kspace)
fig, axs = plt.subplots(1, 3, figsize=(15, 5))
axs[0].imshow(abs(mri_2D))
display_2D_trajectory(traj, subfigure=axs[1])
axs[2].imshow(abs(adjoint_voronoi))

flat_traj = traj.reshape(-1, 2)
weights = np.sqrt(np.sum(flat_traj**2, axis=1))
nufft = get_operator("finufft")(traj, shape=mri_2D.shape, density=weights)
adjoint_manual = nufft.adj_op(kspace)
fig, axs = plt.subplots(1, 3, figsize=(15, 5))
axs[0].imshow(abs(mri_2D))
axs[0].set_title("Ground Truth")
axs[1].imshow(abs(adjoint))
axs[1].set_title("no density compensation")
axs[2].imshow(abs(adjoint_manual))
axs[2].set_title("manual density compensation")