SSFP simualtions¶

Author: Zimu Huo¶
Date: 02.2023¶

In bSSFP, the entire spin evolution can be modeled mathematically through bloch equations, which can be broken down into three components: excitation E, relaxation R, and precession P. These three components are matrix operators representing the sequence events. Immediately after the RF pulse R, the spin undergoes T1 and T2 relaxation E and off-resonance precession P. The bSSFP sequence achieves the steady state by repetitively executing these operations, while ensuring that the gradients are refocused within each TR.


References

[1] 
Author: Dr Neal K Bangerter
Title: Contrast enhancement and artifact reduction in steady state magnetic resonance imaging
Link: https://www.proquest.com/openview/41a8dcfb0f16a1289210b3bd4f9ea82b/1.pdf?cbl=18750&diss=y&pq-origsite=gscholar
InĀ [21]:
import sys
sys.path.insert(1, '../')
from matplotlib import pyplot as plt
import numpy as np
import sys
sys.path.insert(1, '../')
sys.path.insert(1, '../../')
from simulator import *
InĀ [22]:
def plot_bssfp(x, data, title):
    magnitude, phase = np.absolute(data), np.angle(data)
    plt.figure()
    plt.subplot(211)
    plt.ylabel('Magitude')
    plt.title(title)
    plt.grid(True)
    plt.plot(x, magnitude)
    plt.subplot(212)
    plt.xlabel('Off-Resonance (Hz)')
    plt.ylabel('Phase')
    plt.grid(True)
    plt.plot(x, phase)
    plt.show()
InĀ [23]:
def rotMat(alpha, phi):
    # RF rotation matrix 
    # Author Zimu Huo
    rotation = np.array([
    [np.cos(alpha)*np.sin(phi)**2 + np.cos(phi)**2,          
         (1-np.cos(alpha))*np.cos(phi)*np.sin(phi),         
     -np.sin(alpha)*np.sin(phi)],
        
    [    (1-np.cos(alpha))*np.cos(phi)*np.sin(phi),        
       np.cos(alpha)*np.cos(phi)**2+np.sin(phi)**2,         
                        np.sin(alpha)*np.cos(phi)],
        
    [                    np.sin(alpha)*np.sin(phi),                      
                        -np.sin(alpha)*np.cos(phi),                    
                                     np.cos(alpha)]
    ])
    return rotation
    
def iterative_SSFP(M0, alpha, phi, dphi, beta, TR, TE, T1, T2):
    # SSFP simulator
    # Author Zimu Huo
    signal = np.zeros([1,3])
    signal = np.asarray([0, 0, M0])
    Nr = len(TR)
    for i in range(Nr-1):
        signal = np.matmul(rotMat(alpha[i], phi[i]+np.sum(dphi[0:i])),signal) 
        signal[0] = signal[0]*np.exp(-TR[i]/T2)
        signal[1] = signal[1]*np.exp(-TR[i]/T2)
        signal[2] = M0+(signal[2]-M0)*np.exp(-TR[i]/T1)
        P = np.array([
            [ np.cos(beta[i]),  np.sin(beta[i]),   0],
            [-np.sin(beta[i]),  np.cos(beta[i]),   0],
            [            0,             0,   1]
            ])
        signal = np.matmul(P,signal)
        
    signal = np.matmul(rotMat(alpha[-1], phi[-1]+np.sum(dphi[:-1])),signal) 
    
    signal[0] = signal[0]*np.exp(-TE/T2)
    signal[1] = signal[1]*np.exp(-TE/T2)
    signal[2] = M0+(signal[2]-M0)*np.exp(-TE/T1) 
    P = np.array([
            [ np.cos(beta[-1]),  np.sin(beta[-1]),   0],
            [-np.sin(beta[-1]),  np.cos(beta[-1]),   0],
            [            0,             0,      1]
            ])
    signal = np.matmul(P,signal)
    return signal
def bssfp_bloch(b, size,  M0, alpha, phi, dphi, TR, TE, T1, T2):
    # SSFP simulator
    # Author Zimu Huo
    samples = np.zeros([size,3])
    Nr = len(TR)
    for index, beta in enumerate((b)):
        # some safty checks 
        betas = np.ones(Nr) * beta * TR[:Nr] / np.max(TR[:Nr])
        betas[-1] = beta * TE / np.max(TR[:Nr])
        ntime = int(Nr)
        mx = np.zeros(Nr)
        my = np.zeros(Nr)
        mz = np.zeros(Nr)
        alpha = np.asarray(alpha).astype("float64")[:Nr]
        beta = np.asarray(betas).astype("float64")[:Nr]
        TR = np.asarray(TR).astype("float64")[:Nr]
        phi = np.asarray(phi).astype("float64")[:Nr]
        dphi = np.asarray(dphi).astype("float64")[:Nr]
        
        samples[index,:] = iterative_SSFP(M0 = M0 , alpha = alpha, phi = phi, dphi = dphi, beta = betas, TR= TR, TE= TE, T1 = T1, T2 = T2)
    data = np.zeros([size,1], dtype = complex)
    data.real = samples[:,1].reshape(-1,1)
    data.imag = samples[:,0].reshape(-1,1)
    axis = b / (2*np.pi*TR[0]/1000)
    return axis, data
InĀ [24]:
# bSSFP
InĀ [25]:
size = 200
Nr = 2000
alpha = np.ones(Nr) * np.pi/6
T1 = 1000
T2 = 200
M0 = 1
TR = np.ones(Nr) * 10
TE = 10/2
M0 = 2
phi = np.ones(Nr) * 0
dphi = np.ones(Nr) * 0
f = 4
b = np.linspace(-f*np.pi, f*np.pi, size)
x, data = bssfp_bloch(b, size, M0 = M0 , alpha = alpha, phi = phi, dphi = dphi, TR= TR, TE= TE, T1 = T1, T2 = T2)
plot_bssfp(x, data, "bSSFP")
No description has been provided for this image
InĀ [26]:
size = 200
Nr = 2000

T1 = 1000
T2 = 200
M0 = 1
TR = np.ones(Nr) * 10
TE = 10/2
M0 = 2
phi = np.ones(Nr) * 0
dphi = np.ones(Nr) * 0
f = 2
b = np.linspace(-f*np.pi, f*np.pi, size)

signal = np.zeros([size, 4], dtype = complex)
for idx, flip_angle in enumerate (np.asarray([np.pi/ 9, np.pi / 6, np.pi / 4, np.pi / 2])):
    alpha = np.ones(Nr) * flip_angle
    x, data = bssfp_bloch(b, size, M0 = M0 , alpha = alpha, phi = phi, dphi = dphi, TR= TR, TE= TE, T1 = T1, T2 = T2)
    signal[:,idx] = data.flatten()
InĀ [27]:
plt.figure()
plt.ylabel('Magitude')
plt.title("bSSFP profile vs flip angle")
plt.xlabel('Off-Resonance (Hz)')
plt.grid(True)
plt.plot(x, np.abs(signal))
plt.legend([20, 30, 45, 60])
plt.show()
No description has been provided for this image
InĀ [28]:
## Different TR
InĀ [29]:
size = 200
Nr = 2000

T1 = 1000
T2 = 200

M0 = 1
TE = 10/2
M0 = 2
phi = np.ones(Nr) * 0
dphi = np.ones(Nr) * 0
f = 2
b = np.linspace(-f*np.pi, f*np.pi, size)

signal = np.zeros([size, 4], dtype = complex)
xs = np.zeros([size, 4], dtype = complex)
for idx, TR in enumerate (np.asarray([2, 4, 6, 8])):
    TE = TR/2
    TR = np.ones(Nr) * TR
    x, data = bssfp_bloch(b, size, M0 = M0 , alpha = alpha, phi = phi, dphi = dphi, TR= TR, TE= TE, T1 = T1, T2 = T2)
    signal[:,idx] = data.flatten()
    xs[:,idx] = x.flatten()
InĀ [30]:
plt.figure()
plt.ylabel('Magitude')
plt.title("bSSFP profile vs TR")
plt.grid(True)
plt.plot(xs, np.abs(signal))
plt.xlabel('Off-Resonance (Hz)')
plt.legend([2, 4, 6, 8])
plt.show()
No description has been provided for this image
InĀ [31]:
num_phasecyle = 2
size = 200
Nr = size
alpha = np.ones(Nr) * np.pi/6
T1 = 1000
T2 = 100
M0 = 10
TR = np.ones(Nr) * 10
TE = 10/2
M0 = 2
phi = np.ones(Nr) * 0
dphi = np.ones(Nr) * 0
f = 2
b = np.linspace(-np.pi*f, np.pi*f, Nr)
signal = []
for dphi in np.linspace(0, 2*np.pi, num_phasecyle , endpoint=False):
    dphi = np.ones(Nr) * dphi
    x, data = bssfp_bloch(b, size, M0 = M0 , alpha = alpha, phi = phi, dphi = dphi, TR= TR, TE= TE, T1 = T1, T2 = T2)
    signal.append(data)
InĀ [32]:
plt.figure()
plt.ylabel('Magitude')
plt.title("phase cycled bSSFP profile")
plt.xlabel('Off-Resonance (Hz)')
plt.grid(True)
plt.plot(x, np.abs(np.squeeze(signal)).T)
plt.show()
No description has been provided for this image