2D Spiral Excitation Pulses

2D spiral pulses can be designed for any flip angle using Fourier designs The flip angle is the Fourier transform of the k-space weighting. This was described in

“A k-space analysis of small-tip-angle excitation” J Pauly, D Nishimura, A Macovski Journal of Magnetic Resonance (1969) 81 (1), 43-56

“A linear class of large-tip-angle selective excitation pulses”, J Pauly, D Nishimura, A Macovski Journal of Magnetic Resonance (1969) 82 (3), 571-587

An example design is an eight turn pulse with 1 G/cm gradient, and 2 G/cm/ms slew rates (10 mT/m and 20 mT/m/s)

%use octave

try
    cd rf_tools_octave
catch
    try
        cd ../rf_tools_octave
    catch
        try
            cd ../rf_tools_octave
        catch
            cd ../../rf_tools_octave
        end
    end
end

pkg load signal
%use octave

[rf g] = dz2d(8,1,4,512,1,2);

re_rf = real(rf);
im_rf = imag(rf);
re_g = real(g);
im_g = imag(g);
Gradient duration is  8.205 ms

where the second argument is the spatial bandwidth in cycles/cm and the fourth argument is the space-bandwidth product (like TBW for 1D pulses).

The pulse ends up being 8.2 ms. We can plot the RF pulse and $G_x$ and $G_y$ gradients as

%use octave

t = [1:512]*8.205/512;
%use sos
%get t --from Octave
%get re_rf --from Octave
%get im_rf --from Octave
%get re_g --from Octave
%get im_g --from Octave

import matplotlib.pyplot as plt
import plotly.plotly as py
import plotly.graph_objs as go
import numpy as np
from plotly import __version__
from plotly.offline import download_plotlyjs, init_notebook_mode, plot, iplot
config={'showLink': False, 'displayModeBar': False}

init_notebook_mode(connected=True)

from IPython.core.display import display, HTML

sub1_1 = go.Scatter(
    x = t,
    y = re_rf,
    name = 'real',
    text = 'real',
    hoverinfo = 'x+y+text',
    line = dict(
        color = ('rgb(22, 96, 167)'),
        ),
    showlegend=False,
)

sub1_2 = go.Scatter(
    x = t,
    y = im_rf,
    name = 'imaginary',
    text = 'imaginary',
    hoverinfo = 'x+y+text',
    line = dict(
        color = ('rgb(205, 12, 24)'),
        ),
    showlegend=False,
)

sub2_1 = go.Scatter(
    x = t,
    y = re_g,
    name = 'real',
    text = 'real',
    hoverinfo = 'x+y+text',
    line = dict(
        color = ('rgb(22, 96, 167)'),
        ),
)

sub2_2 = go.Scatter(
    x = t,
    y = im_g,
    name = 'imaginary',
    text = 'imaginary',
    hoverinfo = 'x+y+text',
    line = dict(
        color = ('rgb(205, 12, 24)'),
        ),
)

from plotly import tools

fig = tools.make_subplots(rows=2, cols=1, print_grid=False)
fig.append_trace(sub1_1, 1, 1)
fig.append_trace(sub1_2, 1, 1)

fig.append_trace(sub2_1, 2, 1)
fig.append_trace(sub2_2, 2, 1)

layout_subplot = go.Layout(
    width=600,
    height=600,
    margin=go.layout.Margin(
        l=100,
        r=50,
        b=60,
        t=50,
    ),
    xaxis=dict(
        showgrid=False,
        linecolor='black',
        linewidth=2,
        domain=[0, 1]
    ),
    yaxis=dict(
        showgrid=False,
        linecolor='black',
        linewidth=2,
        domain=[0.58, 1],
    ),
    xaxis2=dict(
        showgrid=False,
        linecolor='black',
        linewidth=2,
        domain=[0, 1],
        anchor='y2'
    ),
    yaxis2=dict(
        showgrid=False,
        linecolor='black',
        linewidth=2,
        domain=[0, 0.42],
    ),
    annotations=[
        dict(
            x=0.5004254919715793,
            y=1.05,
            showarrow=False,
            text='RF Pulse',
            font=dict(
                family='Times New Roman',
                size=22
            ),
            xref='paper',
            yref='paper'
        ),
        dict(
            x=0.5004254919715793,
            y=0.53,
            showarrow=False,
            text='time, ms',
            font=dict(
                family='Times New Roman',
                size=22
            ),
            xref='paper',
            yref='paper'
        ),
        dict(
            x=0.5004254919715793,
            y=-0.08,
            showarrow=False,
            text='time, ms',
            font=dict(
                family='Times New Roman',
                size=22
            ),
            xref='paper',
            yref='paper'
        ),
        dict(
            x=0.5004254919715793,
            y=0.45,
            showarrow=False,
            text='Gx and Gy',
            font=dict(
                family='Times New Roman',
                size=22
            ),
            xref='paper',
            yref='paper'
        ),
        dict(
            x=-0.15,
            y=0.2,
            showarrow=False,
            text='G/cm',
            font=dict(
                family='Times New Roman',
                size=22
            ),
            textangle=-90,
            xref='paper',
            yref='paper'
        ),
    ],

    legend=dict(
        x=0.15,
        y=0.95,
        traceorder='normal',
        font=dict(
            family='Times New Roman',
            size=12,
            color='#000'
        ),
        bordercolor='#000000',
        borderwidth=2
    )
)

fig["layout"]=layout_subplot

config={'showLink': False, 'displayModeBar': False}

plot(fig, filename = '2ds_fig_1.html', config = config)

display(HTML('2ds_fig_1.html'))

(View plot code)

Next we want to simulate the excitation profile. At this point it is scaled to 1 radian. To increase it to $\pi/2$

%use octave

rf = rf*pi/2;

The abr() simulator will take 2D pulses. First define spatial vectors

%use octave 

x = [-32:32]/4;
y = [-32:32]/4;

mxy = ab2ex(abr(rf,g,x,y));

abs_mxy = abs(mxy);
re_mxy = real(mxy);
im_mxy = imag(mxy);
%use sos
%get x --from Octave
%get y --from Octave
%get abs_mxy --from Octave

XX, YY = np.meshgrid(x, y)
ZZ = np.asarray(abs_mxy)

lines = []
line_marker = dict(color='#0066FF', width=2)
for i, j, k in zip(XX, YY, ZZ):
    lines.append(go.Scatter3d(x=i, y=j, z=k, mode='lines', line=line_marker))

layout = go.Layout(
    showlegend=False
)

fig = go.Figure(data=lines, layout=layout)

config={'showLink': False, 'displayModeBar': False}

plot(fig, filename = '2ds_fig_2.html', config = config)

display(HTML('2ds_fig_2.html'))

(View plot code)

This RF pulse is well refocused at the end of the pulse, as we can see by plotting $M_x$ and $-M_y$,

%use sos
%get x --from Octave
%get y --from Octave
%get re_mxy --from Octave

XX, YY = np.meshgrid(x, y)
ZZ = np.asarray(re_mxy)

lines = []
line_marker = dict(color='#0066FF', width=2)
for i, j, k in zip(XX, YY, ZZ):
    lines.append(go.Scatter3d(x=i, y=j, z=k, mode='lines', line=line_marker))

layout = go.Layout(
    showlegend=False,
    annotations=[
        dict(
            x=0.5004254919715793,
            y=1.05,
            showarrow=False,
            text='Mx',
            font=dict(
                family='Times New Roman',
                size=22
            ),
            xref='paper',
            yref='paper'
        ),
    ],
    scene = dict(
        zaxis = dict(nticks=10),
    ),
)

fig = go.Figure(data=lines, layout=layout)

config={'showLink': False, 'displayModeBar': False}

plot(fig, filename = '2ds_fig_3.html', config = config)

display(HTML('2ds_fig_3.html'))

(View plot code)

%use sos
%get x --from Octave
%get y --from Octave
%get im_mxy --from Octave

XX, YY = np.meshgrid(x, y)
ZZ = np.asarray(-im_mxy)

lines = []
line_marker = dict(color='#0066FF', width=2)
for i, j, k in zip(XX, YY, ZZ):
    lines.append(go.Scatter3d(x=i, y=j, z=k, mode='lines', line=line_marker))

layout = go.Layout(
    showlegend=False,
    annotations=[
        dict(
            x=0.5004254919715793,
            y=1.05,
            showarrow=False,
            text='-My',
            font=dict(
                family='Times New Roman',
                size=22
            ),
            xref='paper',
            yref='paper'
        ),
    ],
    scene = dict(
        zaxis = dict(nticks=10),
    ),
)

fig = go.Figure(data=lines, layout=layout)

config={'showLink': False, 'displayModeBar': False}

plot(fig, filename = '2ds_fig_4.html', config = config)

display(HTML('2ds_fig_4.html'))

(View plot code)

$M_x$ is only a couple of percent.

We can simulate over a broader spatial range to visualize the sidelobes which are inherent in this type of design

%use octave

x = [-32:32]/4;
y = [-32:32]/4;

x = 4*x;
y = 4*y;

mxy4 = ab2ex(abr(rf,g,x,y));

re_mxy4 = real(mxy4);
im_mxy4 = imag(mxy4);
%use sos
%get x --from Octave
%get y --from Octave
%get re_mxy4 --from Octave

XX, YY = np.meshgrid(x, y)
ZZ = np.asarray(re_mxy4)

lines = []
line_marker = dict(color='#0066FF', width=2)
for i, j, k in zip(XX, YY, ZZ):
    lines.append(go.Scatter3d(x=i, y=j, z=k, mode='lines', line=line_marker))

layout = go.Layout(
    showlegend=False,
    annotations=[
        dict(
            x=0.5004254919715793,
            y=1.05,
            showarrow=False,
            text='Mx, wide FOV',
            font=dict(
                family='Times New Roman',
                size=22
            ),
            xref='paper',
            yref='paper'
        ),
    ],
    scene = dict(
        zaxis = dict(nticks=10),
    ),
)

fig = go.Figure(data=lines, layout=layout)

config={'showLink': False, 'displayModeBar': False}

plot(fig, filename = '2ds_fig_5.html', config = config)

display(HTML('2ds_fig_5.html'))

(View plot code)

%use sos
%get x --from Octave
%get y --from Octave
%get im_mxy4 --from Octave

XX, YY = np.meshgrid(x, y)
ZZ = np.asarray(-im_mxy4)

lines = []
line_marker = dict(color='#0066FF', width=2)
for i, j, k in zip(XX, YY, ZZ):
    lines.append(go.Scatter3d(x=i, y=j, z=k, mode='lines', line=line_marker))

layout = go.Layout(
    showlegend=False,
    annotations=[
        dict(
            x=0.5004254919715793,
            y=1.05,
            showarrow=False,
            text='-My, wide FOV',
            font=dict(
                family='Times New Roman',
                size=22
            ),
            xref='paper',
            yref='paper'
        ),
    ],
    scene = dict(
        zaxis = dict(nticks=10),
    ),
)

fig = go.Figure(data=lines, layout=layout)

config={'showLink': False, 'displayModeBar': False}

plot(fig, filename = '2ds_fig_6.html', config = config)

display(HTML('2ds_fig_6.html'))

(View plot code)

Note that the main lobe is imaginary, the first sidelobe is real, and the second sidelobe is imaginary. Extra credit if you can explain this!

We can use the same pulse as a spin-echo pulse if we increase the flip angle to $\pi$, and make sure the gradients integrate to zero. Here we do this with a single sample, in fact you’d want to add an additional gradient lobe, or incorportate the area by adjusting one of the crusher gradients.

%use octave

x = [-32:32]/4;
y = [-32:32]/4;

rfse = rf*2;

rfse = [0; rf*2];
gse = [-sum(g); g];

mxyse = ab2se(abr(rfse,gse,x,y));

re_mxyse = real(mxyse);
%use sos
%get x --from Octave
%get y --from Octave
%get re_mxyse --from Octave

XX, YY = np.meshgrid(x, y)
ZZ = np.asarray(re_mxyse)

lines = []
line_marker = dict(color='#0066FF', width=2)
for i, j, k in zip(XX, YY, ZZ):
    lines.append(go.Scatter3d(x=i, y=j, z=k, mode='lines', line=line_marker))

layout = go.Layout(
    annotations=[
        dict(
            x=0.5004254919715793,
            y=1.15,
            showarrow=False,
            text='My, Spin-echo Profile',
            font=dict(
                family='Times New Roman',
                size=22
            ),
            xref='paper',
            yref='paper'
        ),
    ],
    showlegend=False
)

fig = go.Figure(data=lines, layout=layout)

plot(fig, filename = '2ds_fig_7.html', config = config)

display(HTML('2ds_fig_7.html'))

(View plot code)

We’ve just plotted $M_y$ here, $M_x$ is again very small.

Things to try:

  • Change the space-bandwidth product to 6 or 8
  • Change the number of turns
  • Do a two shot experiment, where g = -g for the second shot. Add Mxy’s.
  • Use modern gradient numbers