2. iid Variable Density Sampling

2. iid Variable Density Sampling#

This notebook is intended to demonstrate the interest of using iid variable density sampling either from:

  • optimal distributions from the CS theory on orthonormal systems for Shannon wavelets derived in:

    • Chauffert et al, “Variable density compressed sensing in MRI. Theoretical vs heuristic sampling strategies”, Proc. 10th IEEE ISBI 2013: 298-301

    • Chauffert et al, “Variable Density Sampling with Continuous Trajectories” SIAM Imaging Sci, 2014;7(4):1992-1992)

  • or from handcrafted densities parameterized by the decay \(\eta\):

\[ p(k_x,k_y) =1/\left(k_x^2+k_y^2\right)^{\eta/2}, \quad \eta \simeq 3. \]
#DISPLAY BRAIN PHANTOM
%matplotlib inline

import os.path as op
import os
import math
import cmath
import sys

import numpy as np
import pywt as pw
import matplotlib.pyplot as plt

from skimage import data, io, filters
import brainweb_dl as bwdl

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

# get current working dir
#cwd = os.getcwd()
#dirimg_2d = op.join(cwd,"../data")
#img_size = 512   #256

#load data file corresponding to the target resolution
#filename = "BrainPhantom%s.png" % img_size
#mri_filename = op.join(dirimg_2d, filename)
#mri_img = io.imread(mri_filename)

mri_img = bwdl.get_mri(4, "T1")[70, ...].astype(np.float32)
#mri_img = bwdl.get_mri(4, "T2")[120, ...].astype(np.float32)
print(mri_img.shape)
img_size = mri_img.shape[0]
plt.figure()
plt.imshow(abs(mri_img))
#plt.imshow(abs(mri_2D),origin="lower")
#plt.imshow(abs(mri_2D),origin="upper")
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/9ce2bc73a6a72f1848d5804f5a3fa7d70aa1a64f3395bdcaa7bb10793c991abe.png
# Load target sampling distribution (precalculated in Matlab)
import numpy as np
from scipy.io import loadmat
import mrinufft.trajectories as mt

# Generate optimal sampoling density for a given wavelet transform, number of resolution levels and img size 
# see details Chauffert et al, IEEE ISBI 2013 for the computation of optimal sampling densities
#opt_density = mt.create_fast_chauffert_density((img_size,img_size),"sym10",3)
opt_density = mt.create_fast_chauffert_density((img_size,img_size),"db4",3)

# Generate Cartesian variable density mask
# change the value below if you want to change the final subsampling mask
threshold = 10. * opt_density.min()  # sys.float_info.epsilon \simeq 2e-16
kspace_mask = np.zeros((img_size, img_size), dtype="float64")
kspace_mask = np.where(opt_density > threshold, 1, kspace_mask)


fig, axs = plt.subplots(1, 2, figsize=(7, 4) )
axs[0].imshow(opt_density)
axs[0].set_title("Optimal variable density\nfor Shannon wavelets")
axs[1].imshow(kspace_mask)
axs[1].set_title("Variable density sampling mask")
Text(0.5, 1.0, 'Variable density sampling mask')
_images/1283e22274a63a178f31fc01c9a69df2d25e78a1c4552df921d189f220cd714e.png
#import numpy.fft as fft

norm = "ortho"
#norm = None

def fft(x):
    return np.fft.fft2(x, norm=norm)

def ifft(x):
    return np.fft.ifft2(x, norm=norm)

# Generate the kspace data: first Fourier transform the image
kspace_data = np.fft.fftshift(fft(mri_img))
#add Gaussian complex-valued random noise
signoise = 10
#kspace_data += np.random.randn(*mri_img.shape) * signoise * (1+1j)
# Mask data to perform subsampling
kspace_data *= kspace_mask

# Zero order solution
image_rec0 = ifft(np.fft.ifftshift(kspace_data))

fig, axs = plt.subplots(2, 2, figsize=(7, 7) )
axs[0, 0].imshow(mri_img)
axs[0, 0].set_title("True image")
axs[0, 1].imshow(kspace_mask)
axs[0, 1].set_title("Sampling mask")
axs[1,0].imshow(np.abs(kspace_data), vmax=0.01*np.abs(kspace_data).max())
axs[1, 0].set_title("k-space noisy data")
axs[1, 1].imshow(np.abs(image_rec0))
axs[1, 1].set_title("Zero-order recon")
plt.show()
_images/cc29d7e61d9b3e883eda32c5b345a75e26692282a8c8415057d99298a00648f1.png
# Now construct by hands a variable sampling distribution
# You can change the decay to modify the decreasing behavior in the center of k-space
# the larger the decay, the faster the decrease from low to high-frequencies
decay = 2
x = np.linspace(-1. / (2. * np.pi), 1. / (2. * np.pi), img_size)
X, Y = np.meshgrid(x, x)
r = np.sqrt(X ** 2 + Y ** 2)
print(r)
p_decay = np.power(r,-decay)
p_decay = p_decay/np.sum(p_decay)

#print(p_decay.max())
#print(p_decay.min())

# change the value below if you want to change the final subsampling mask
threshold = 2* opt_density.min()  # sys.float_info.epsilon \simeq 2e-16
kspace_mask = np.zeros((img_size,img_size), dtype="float64")
kspace_mask = np.where(p_decay > threshold, 1, kspace_mask)

fig, axs = plt.subplots(1, 2, figsize=(7, 4))
axs[0].imshow(p_decay, cmap='Greys_r', vmax=0.001 * np.abs(p_decay).max())
axs[0].set_title("Handcrafted VD")
axs[1].imshow(kspace_mask, cmap='Greys_r')
axs[1].set_title("VD sampling mask")
[[0.22507908 0.22419815 0.22332073 ... 0.22332073 0.22419815 0.22507908]
 [0.22419815 0.22331375 0.22243284 ... 0.22243284 0.22331375 0.22419815]
 [0.22332073 0.22243284 0.22154843 ... 0.22154843 0.22243284 0.22332073]
 ...
 [0.22332073 0.22243284 0.22154843 ... 0.22154843 0.22243284 0.22332073]
 [0.22419815 0.22331375 0.22243284 ... 0.22243284 0.22331375 0.22419815]
 [0.22507908 0.22419815 0.22332073 ... 0.22332073 0.22419815 0.22507908]]
Text(0.5, 1.0, 'VD sampling mask')
_images/24424c04540114032a44b0d9bdb0fabfcad25ac3dc35879f9bd61fed89bac16c.png
# Generate the kspace data: first Fourier transform the image
kspace_data = np.fft.fftshift(fft(mri_img))
#add Gaussian complex-valued random noise
signoise = 10
# Simulate independent noise realization on the real & imag parts
kspace_data += (np.random.randn(*mri_img.shape) + 1j * np.random.randn(*mri_img.shape)) * signoise
# Mask data to perform subsampling
kspace_data *= kspace_mask

# Zero order solution
image_rec0 = ifft(np.fft.ifftshift(kspace_data))

fig, axs = plt.subplots(2, 2, figsize=(7, 7))
axs[0, 0].imshow(mri_img, cmap='Greys_r')
axs[0, 0].set_title("True image")
axs[0, 1].imshow(kspace_mask, cmap='Greys_r')
axs[0, 1].set_title("Sampling mask")
axs[1, 0].imshow(np.abs(kspace_data),  cmap='gray', vmax=0.01*np.abs(kspace_data).max())
#axs[1].imshow(np.abs(np.fft.ifftshift(kspace_data)), cmap='Greys_r')
axs[1, 0].set_title("k-space noisy data")
axs[1, 1].imshow(np.abs(image_rec0), cmap='Greys_r')
axs[1, 1].set_title("Zero-order recon")
plt.show()
_images/08abdb6e050eee0eeee442a9bd5a52b56314d90583db7edd4d86bd9b3e1e165c.png