Note
Go to the end to download the full example code.
Single anatomical volume with 2D EPI with SNAKE-fMRI#
This examples walks through the elementary components of SNAKE.
Here we proceed step by step and use the Python interface. A more integrated
alternative is to use the CLI snake-main
# Imports
import numpy as np
from snake.core.simulation import SimConfig, default_hardware, GreConfig
from snake.core.phantom import Phantom
from snake.core.smaps import get_smaps
from snake.core.sampling import EPI3dAcquisitionSampler
Setting up the base simulation Config. This configuration holds all key parameters for the simulation, describing the scanner parameters.
sim_conf = SimConfig(
max_sim_time=6,
seq=GreConfig(TR=25, TE=30, FA=3),
hardware=default_hardware,
fov_mm=(181, 217, 181),
shape=(60, 71, 60),
)
sim_conf.hardware.n_coils = 8
sim_conf
Creating the base Phantom#
The simulation acquires the data describe in a phantom. A phantom consists of fuzzy segmentation of head tissue, and their MR intrinsic parameters (density, T1, T2, T2*, magnetic susceptibilities)
Here we use Brainweb reference mask and values for convenience.
phantom = Phantom.from_brainweb(sub_id=4, sim_conf=sim_conf)
# Here are the tissue availables and their parameters
phantom
Traceback (most recent call last):
File "/home/runner/work/snake-fmri/snake-fmri/examples/anatomical/example_EPI_2D.py", line 48, in <module>
phantom = Phantom.from_brainweb(sub_id=4, sim_conf=sim_conf)
File "/home/runner/work/snake-fmri/snake-fmri/src/snake/core/phantom/static.py", line 112, in from_brainweb
tissues_mask = get_mri(sub_id, contrast="fuzzy").astype(np.float32)
File "/opt/hostedtoolcache/Python/3.10.16/x64/lib/python3.10/site-packages/brainweb_dl/mri.py", line 173, in get_mri
data = _get_mri_sub20(
File "/opt/hostedtoolcache/Python/3.10.16/x64/lib/python3.10/site-packages/brainweb_dl/mri.py", line 91, in _get_mri_sub20
filename = get_brainweb20(
File "/opt/hostedtoolcache/Python/3.10.16/x64/lib/python3.10/site-packages/brainweb_dl/_brainweb.py", line 329, in get_brainweb20
Parallel(n_jobs=-1, backend="threading")(
File "/opt/hostedtoolcache/Python/3.10.16/x64/lib/python3.10/site-packages/joblib/parallel.py", line 2007, in __call__
return output if self.return_generator else list(output)
File "/opt/hostedtoolcache/Python/3.10.16/x64/lib/python3.10/site-packages/joblib/parallel.py", line 1650, in _get_outputs
yield from self._retrieve()
File "/opt/hostedtoolcache/Python/3.10.16/x64/lib/python3.10/site-packages/joblib/parallel.py", line 1754, in _retrieve
self._raise_error_fast()
File "/opt/hostedtoolcache/Python/3.10.16/x64/lib/python3.10/site-packages/joblib/parallel.py", line 1789, in _raise_error_fast
error_job.get_result(self.timeout)
File "/opt/hostedtoolcache/Python/3.10.16/x64/lib/python3.10/site-packages/joblib/parallel.py", line 745, in get_result
return self._return_or_raise()
File "/opt/hostedtoolcache/Python/3.10.16/x64/lib/python3.10/site-packages/joblib/parallel.py", line 763, in _return_or_raise
raise self._result
requests.exceptions.ConnectTimeout: HTTPConnectionPool(host='brainweb.bic.mni.mcgill.ca', port=80): Max retries exceeded with url: /cgi/brainweb1/?do_download_alias=subject04_bck&format_value=raw_short&zip_value=gnuzip&download_for_real=%5BStart+download%21%5D (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x7f2a3221f760>, 'Connection to brainweb.bic.mni.mcgill.ca timed out. (connect timeout=None)'))
Setting up Acquisition Pattern and Initializing Result file.#
# The next piece of simulation is the acquisition trajectory.
# Here nothing fancy, we are using a EPI (fully sampled), that samples a 3D
# k-space (this akin to the 3D EPI sequence of XXXX)
sampler = EPI3dAcquisitionSampler(accelz=1, acsz=0.1, orderz="top-down")
smaps = None
if sim_conf.hardware.n_coils > 1:
smaps = get_smaps(sim_conf.shape, n_coils=sim_conf.hardware.n_coils)
Acquisition with Cartesian Engine#
The generated file example_EPI.mrd
does not contains any k-space data for
now, only the sampling trajectory. letโs put some in. In order to do so, we
need to setup the acquisition engine that models the MR physics, and get
sampled at the specified k-space trajectory.
Here, we have a single frame to acquire with 60 slice (one EPI per slice), so a single worker will be enough.
from snake.core.engine import EPIAcquisitionEngine
engine = EPIAcquisitionEngine(model="T2s", slice_2d=True) # use the 2D epi.
engine(
"example_EPI2D.mrd",
sampler=sampler,
phantom=phantom,
sim_conf=sim_conf,
smaps=smaps,
# worker_chunk_size=20,
n_workers=1,
)
Simple reconstruction#
Getting k-space data is nice, but
SNAKE also provides rudimentary reconstruction tools to get images (and check
that we didnโt mess up the acquisition process).
This is available in the companion package snake.toolkit
.
Loading the .mrd
file to retrieve all information can be done using the
ismrmd
python package, but SNAKE provides convenient dataloaders, which are
more efficient, and take cares of managing underlying files access. As we are
showcasing the API, we will do things manually here, and use only core SNAKE.
from snake.mrd_utils import CartesianFrameDataLoader
with CartesianFrameDataLoader("example_EPI2D.mrd") as data_loader:
mask, kspace_data = data_loader.get_kspace_frame(0)
Reconstructing a Single Frame of fully sampled EPI boils down to performing a 3D IFFT:
from scipy.fft import ifftn, ifftshift, fftshift
axes = (
-2,
-1,
)
image_data = ifftshift(
ifftn(fftshift(kspace_data, axes=axes), axes=axes, norm="ortho"), axes=axes
)
# Take the square root sum of squares to get the magnitude image (SSOS)
image_data = np.sqrt(np.sum(np.abs(image_data) ** 2, axis=0))
image_data
import matplotlib.pyplot as plt
from snake.toolkit.plotting import axis3dcut
fig, ax = plt.subplots()
axis3dcut(image_data.squeeze().T, None, None, cbar=False, cuts=(40, 60, 40), ax=ax)
plt.show()
Total running time of the script: (2 minutes 14.921 seconds)