Advanced SNR#

Simulations of SNR efficiency, challenges to the fundamental assumptions of SNR (e.g. T2* decay during the readout)

SNR and \(T_2^*\) decay#

The SNR equation alone implies that the readout time should be as long as possible, as this leads to more total measurement time, but practically this is limited by the \(T_2^*\) decay rate, since at some point there will no longer be any signal to measure. To incorporate the effect of this decay, we need to account for the signal modulations during the readout by integrating the signal area, accounting for \(T_2^*\), which will offset signal gains from extending the readout

\[SNR \propto \frac{ \int_0^{T_{read}} \exp(-\tau / T_2^*) d\tau } { \sqrt{T_{read}} }\]

\(T_{read}\) is the readout duration, the numerator the signal acquired during this readout time, and the demoninator captures how the total noise scales with the square root of measurement time.

The above equation applieds for a one-sided decay, as in a gradient echo, while the following applies in the case of two-sided T2* decay, as in a spin echo:

\[SNR \propto \frac{\int_{-T_{read}/2}^{T_{read}/2} | \exp(-\tau / T_2^*) | d\tau }{\sqrt{T_{read}}}\]
import numpy as np
import matplotlib.pyplot as plt

Tread = np.linspace(0.01, 6)
T2s = 1

SNR_T2s_onesided = T2s * (1 - np.exp(-Tread / T2s)) / np.sqrt(Tread)
Imax_onesided = np.argmax(SNR_T2s_onesided)

SNR_T2s_twosided = T2s * 2 * (1 - np.exp(-Tread / 2 / T2s)) / np.sqrt(Tread)
Imax_twosided = np.argmax(SNR_T2s_twosided)

plt.plot(Tread, SNR_T2s_onesided)
plt.xlabel(r'$T_{read} / T_2^*$')
plt.ylabel('Relative SNR')
plt.title(r'One-sided $T_2^*$ decay (gradient-echo)')
plt.show()

print(
    f'Maximum for one-sided decay: '
    f'T_read / T_2* = {Tread[Imax_onesided]:.2f}'
)

plt.figure()
plt.plot(Tread, SNR_T2s_twosided)
plt.xlabel(r'$T_{read} / T_2^*$')
plt.ylabel('Relative SNR')
plt.title(r'Two-sided $T_2^*$ decay (spin-echo)')
plt.show()

print(
    f'Maximum for two-sided decay: '
    f'T_read / T_2* = {Tread[Imax_twosided]:.2f}'
)
_images/19126f8a39ee3d12ae3302d0bcf0171c4806ab9a9cbd449357486ddc2494e946.png
Maximum for one-sided decay: T_read / T_2* = 1.23
_images/d38b4993c50605398a9ec4a14851d0096c0aa686b30d6ea08925394e987ef61d.png
Maximum for two-sided decay: T_read / T_2* = 2.45

From this result, we can see that the optimal choice of \(T_{read}\) from a SNR point of view is approximately \( T_{read} / T_2^* = 1.3\) (one-sided) and \( T_{read} / T_2^* = 2.5\) (two-sided).

However, this relationship does not take into account the effects of resolution loss that will typically occur due to decaying \(T_2^*\) weighting in k-space. (See “MRI Signal Equation” for information on this effect).

SNR efficiency versus TR#

The SNR efficiency is useful to examine the choice of TR. It is especially interesting for spoiled gradient-echo sequences where when we can choose to use the optimal flip angle (“Ernst angle”) based on a \(T_1\) value of interest. For these sequences, the optimal flip angle is

\[\theta_{optimal} = \cos^{-1} \exp(-TR/T_1)\]

And \(f_{seq,GRE}\) is defined above.

Under this condition, we see the following intriguing result. Once the optimal flip angle begins to approach 90-degrees, the SNR efficiency begins to drop. But for the shorter TRs, the SNR efficiency is relatively stable for different TRs.

import numpy as np
import matplotlib.pyplot as plt

# SNR efficiency for spoiled gradient-echo pulse sequences
TRdT1 = np.linspace(0.01, 4)
T1 = np.array([400, 800, 1600])

# To remove TE or T2 effects
TE = 0
T2 = np.inf
M0 = 1

theta_optimal = np.zeros((len(T1), len(TRdT1)))
SNR_efficiency = np.zeros((len(T1), len(TRdT1)))

for IT1, T1_value in enumerate(T1):
    theta_optimal[IT1, :] = np.arccos(np.exp(-TRdT1))

    # Spoiled gradient-echo signal divided by sqrt(TR)
    signal = (
        M0
        * np.sin(theta_optimal[IT1, :])
        * (1 - np.exp(-TRdT1))
        / (1 - np.exp(-TRdT1) * np.cos(theta_optimal[IT1, :]))
    )
    SNR_efficiency[IT1, :] = signal / np.sqrt(TRdT1 * T1_value)

plt.figure()
for IT1, T1_value in enumerate(T1):
    plt.plot(
        TRdT1,
        np.degrees(theta_optimal[IT1, :]),
        label=f'{T1_value} ms',
    )
plt.xlabel('TR / T1')
plt.ylabel('Flip angle [degrees]')
plt.title('Optimal (Ernst) Angle for different T1 values')
plt.legend()

plt.figure()
for IT1, T1_value in enumerate(T1):
    plt.plot(TRdT1, SNR_efficiency[IT1, :], label=f'{T1_value} ms')
plt.ylim(0, 1.1 * np.max(SNR_efficiency))
plt.legend()
plt.xlabel('TR / T1')
plt.ylabel('SNR efficiency')
plt.title('Spoiled gradient-echo SNR efficiency for different T1 values')
plt.show()
_images/7208458da8e92e923b818789556c6a35447f83647a779cab0ed808c5089f3ce9.png _images/d4731f54b858953025a1ba45df39c76e46bf1555ebe43d7fdcd2a69167c14171.png