T2star decay with CMPG sequence#

In this notebook, the functionality of the T2star module is demonstrated by simulating a CMPG/Turbo-spin-echo sequence without spatial encoding, but the signal is acquired on every simulation step.

Imports#

[1]:
import os
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'

# Load TF and check for GPUs
import tensorflow as tf
gpus = tf.config.list_physical_devices('GPU')
for gpu in gpus:
    tf.config.experimental.set_memory_growth(gpu, True)
logical_gpus = tf.config.list_logical_devices('GPU')
print(len(gpus), "Physical GPUs,", len(logical_gpus), "Logical GPUs")


# 3rd Party dependencies
from pint import Quantity
import pyvista
import numpy as np
import matplotlib.pyplot as plt
from copy import deepcopy
%matplotlib inline

# Project library cmrsim
import sys
sys.path.append("../../")
sys.path.append("../../../cmrseq")
import cmrseq
import cmrsim
import cmrsim.utils.particle_properties as part_factory
1 Physical GPUs, 1 Logical GPUs

Define CPMG sequence with hard-pulses#

[2]:
# Define MR-system specifications
system_specs = cmrseq.SystemSpec(max_grad=Quantity(80, "mT/m"), max_slew=Quantity(200., "mT/m/ms"),
                                 grad_raster_time=Quantity(0.01, "ms"),
                                 rf_raster_time=Quantity(0.01, "ms"),
                                 adc_raster_time=Quantity(0.01,"ms"))

# Set up relevant sequence blocks

delay = cmrseq.bausteine.Delay(system_specs=system_specs, duration = Quantity(0.3,'ms'))
end_delay = cmrseq.bausteine.Delay(system_specs=system_specs, duration = Quantity(10,'ms'))

pulse90 = cmrseq.bausteine.SincRFPulse(system_specs=system_specs, flip_angle=Quantity(90,'degrees'),
                                      duration=Quantity(0.01,'ms'),
                                      time_bandwidth_product=0,
                                      frequency_offset=Quantity(0,'Hz'),
                                      center=0.5, delay=Quantity(0, "ms"),
                                      apodization=0., name="90")

pulse180 = cmrseq.bausteine.SincRFPulse(system_specs=system_specs, flip_angle=Quantity(180,'degrees'),
                                      duration=Quantity(0.01,'ms'),
                                      time_bandwidth_product=0,
                                      frequency_offset=Quantity(0,'Hz'),
                                      center=0.5, delay=Quantity(0, "ms"),
                                      apodization=0., name="90")

adc_block90 = cmrseq.bausteine.SymmetricADC(system_specs=system_specs, num_samples=100,
                                            duration=Quantity(25,'ms'),
                                            delay=Quantity(0,'ms'))

adc_block180 = cmrseq.bausteine.SymmetricADC(system_specs=system_specs, num_samples=100,
                                            duration=Quantity(50,'ms'),
                                            delay=Quantity(0,'ms'))


# Create sequence with 90-180-180-180...
# Delays are required around each RF object to prevent sampling during RF
seq = cmrseq.Sequence([delay],system_specs=system_specs)
seq.append(pulse90)
seq.append(delay)
seq.append(adc_block90)
seq.append(delay)

seq.append(pulse180)
seq.append(delay)
seq.append(adc_block180)
seq.append(delay)

seq.append(pulse180)
seq.append(delay)
seq.append(adc_block180)
seq.append(delay)

seq.append(pulse180)
seq.append(delay)
seq.append(adc_block180)
seq.append(delay)
seq.append(end_delay)

plt.close("all")
f, a = plt.subplots(1, 1,figsize=(16, 5))
cmrseq.plotting.plot_sequence(seq, axes=[a,a,a,a], format_axes=False, add_legend=True)
../../_images/example_gallery_bloch_simulation_static_CPMG_t2star_3_0.png

Set up Bloch operator#

[3]:
# Define T2* module
T2s = cmrsim.bloch.submodules.T2starDistributionModel()
submodules = [T2s]

# Define bloch simulator module
time, rf_grid, grad_grid, adc_on_grid = [np.stack(v) for v in cmrseq.utils.grid_sequence_list([seq], force_uniform_grid=False)]

bloch_module = cmrsim.bloch.GeneralBlochOperator(name="FID", gamma=system_specs.gamma_rad.m_as("rad/mT/ms"),
                                                 time_grid=time[0],
                                                 gradient_waveforms=grad_grid,
                                                 rf_waveforms=rf_grid,
                                                 adc_events=adc_on_grid,
                                                 device="GPU:0", submodules=submodules)

Set up particles and relaxation properties#

[ ]:
n_particles = 100000
T1 = 10000
T2 = 300
T2p = 30

properties = {"M0": part_factory.uniform(default_value=1.)(n_particles),
              "T1": part_factory.uniform(default_value=T1)(n_particles),
              "T2": part_factory.uniform(default_value=T2)(n_particles),
              "omega_t2s": part_factory.t2star_lorentzian(T2p = T2p,prctile_cutoff=0.002)(n_particles),
              "magnetization": part_factory.norm_magnetization()(n_particles),
              "initial_position": np.zeros([n_particles,3]).astype(np.float32)}

# Run simulation
m_init = properties.pop("magnetization")
initial_position = properties.pop("initial_position")
bloch_module.reset()

Run Bloch-simulation#

[ ]:
_,_ = bloch_module(initial_position=initial_position, magnetization=m_init, **properties)

Plot Signal decay#

[7]:
sig= np.abs(bloch_module.time_signal_acc[0].numpy())
sig = sig/sig[0]

t = time[adc_on_grid[:,:,0]==1]

T2s = 1/(1/T2+1/T2p)

sig = sig*np.exp(-t[0]/T2s)

et2 = np.exp(-t/T2)
et2s = np.exp(-t/T2s)

fig, axes = plt.subplots(1, 1, figsize=(14, 5))
axes.plot(t, sig, label='Simulated CPMG')
axes.plot(t, et2, label='Expected T2')
axes.plot(t, et2s, label='Expected T2s')
axes.legend()
axes.set_xlabel('Time (ms)'), axes.set_ylabel('Normalized Signal (a.u.)')

[7]:
(Text(0.5, 0, 'Time (ms)'), Text(0, 0.5, 'Normalized Signal (a.u.)'))
../../_images/example_gallery_bloch_simulation_static_CPMG_t2star_11_1.png