Skip to content

Commit 025c392

Browse files
authored
Add arc length parameterization (#236)
* Add arc length parameterization * Clarify annular ring description, extend example trajectory display function
1 parent a4466d1 commit 025c392

File tree

7 files changed

+296
-52
lines changed

7 files changed

+296
-52
lines changed
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
"""
2+
======================
3+
Trajectory constraints
4+
======================
5+
6+
A collection of methods to make trajectories fit hardware constraints.
7+
8+
"""
9+
10+
# %%
11+
# Hereafter we illustrate different methods to reduce the gradient
12+
# strengths and slew rates required for the trajectory to match the
13+
# hardware constraints of MRI machines. A summary table is available
14+
# below.
15+
#
16+
17+
# %%
18+
# .. list-table:: Constraint fitting methods
19+
# :header-rows: 1
20+
#
21+
# * -
22+
# - Gradient strength
23+
# - Slew rate
24+
# - Path preserved
25+
# - Density preserved
26+
# * - Arc-length parameterization
27+
# - Yes
28+
# - No
29+
# - Yes
30+
# - No
31+
#
32+
33+
# Internal
34+
import mrinufft as mn
35+
from mrinufft.trajectories.utils import Acquisition, compute_gradients_and_slew_rates
36+
from utils import show_trajectory_full
37+
38+
# External
39+
import numpy as np
40+
41+
42+
# %%
43+
# Script options
44+
# ==============
45+
# These options are used in the examples below as default values for all trajectories.
46+
47+
# Acquisition parameters
48+
acq = Acquisition.default
49+
50+
# %%
51+
52+
# Trajectory parameters
53+
Nc = 16 # Number of shots
54+
Ns = 3000 # Number of samples per shot
55+
in_out = False # Choose between in-out or center-out trajectories
56+
nb_zigzags = 5 # Number of zigzags for base trajectories
57+
58+
# %%
59+
60+
# Display parameters
61+
figure_size = 10 # Figure size for trajectory plots
62+
subfigure_size = 6 # Figure size for subplots
63+
one_shot = 2 * Nc // 3 # Highlight one shot in particular
64+
sample_freq = 60 # Frequency of samples to display in the trajectory plots
65+
66+
# %%
67+
# We will be using a cone trajectory to showcase the different methods as
68+
# it switches several times between high gradients and slew rates.
69+
70+
original_trajectory = mn.initialize_2D_cones(
71+
Nc, Ns, in_out=in_out, nb_zigzags=nb_zigzags
72+
)
73+
74+
# %%
75+
# Arc-length parameterization
76+
# ===========================
77+
# Arc-length parameterization is the simplest method to reduce the gradient
78+
# strength as it resamples the trajectory to have a constant distance between
79+
# samples. This is technically the lowest gradient strength achievable while
80+
# preserving the path of the trajectory, but it does not preserve the k-space
81+
# density and can lead to high slew rates as shown below.
82+
83+
show_trajectory_full(original_trajectory, one_shot, subfigure_size, sample_freq)
84+
85+
grads, slews = compute_gradients_and_slew_rates(original_trajectory, acq)
86+
grad_max, slew_max = np.max(grads), np.max(slews)
87+
print(f"Max gradient: {grad_max:.3f} T/m, Max slew rate: {slew_max:.3f} T/m/ms")
88+
89+
# %%
90+
#
91+
92+
from mrinufft.trajectories.projection import parameterize_by_arc_length
93+
94+
projected_trajectory = parameterize_by_arc_length(original_trajectory)
95+
96+
# %%
97+
98+
show_trajectory_full(projected_trajectory, one_shot, subfigure_size, sample_freq)
99+
100+
grads, slews = compute_gradients_and_slew_rates(projected_trajectory, acq)
101+
grad_max, slew_max = np.max(grads), np.max(slews)
102+
print(f"Max gradient: {grad_max:.3f} T/m, Max slew rate: {slew_max:.3f} T/m/ms")

examples/trajectories/utils.py

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,12 @@
99
import matplotlib.pyplot as plt
1010

1111
# Internal imports
12-
from mrinufft import display_2D_trajectory, display_3D_trajectory, displayConfig
12+
from mrinufft import (
13+
display_2D_trajectory,
14+
display_3D_trajectory,
15+
displayConfig,
16+
display_gradients_simply,
17+
)
1318
from mrinufft.trajectories.utils import KMAX
1419

1520

@@ -33,6 +38,65 @@ def show_trajectory(trajectory, one_shot, figure_size):
3338
plt.show()
3439

3540

41+
def show_trajectory_full(trajectory, one_shot, figure_size, sample_freq=10):
42+
# General configuration
43+
nb_dim = trajectory.shape[-1]
44+
fig = plt.figure(figsize=(3 * figure_size, figure_size))
45+
subfigs = fig.subfigures(1, 3, wspace=0)
46+
47+
# Trajectory display
48+
subfigs[0].suptitle("Trajectory", fontsize=displayConfig.fontsize, x=0.5, y=0.98)
49+
if nb_dim == 2:
50+
ax = display_2D_trajectory(
51+
trajectory,
52+
figsize=figure_size,
53+
one_shot=one_shot,
54+
subfigure=subfigs[0],
55+
)
56+
else:
57+
ax = display_3D_trajectory(
58+
trajectory,
59+
figsize=figure_size,
60+
one_shot=one_shot,
61+
per_plane=False,
62+
subfigure=subfigs[0],
63+
)
64+
for i in range(trajectory.shape[0]):
65+
ax.scatter(*(trajectory[i, ::sample_freq].T), s=15)
66+
ax.set_aspect("equal")
67+
68+
# Gradient display
69+
subfigs[1].suptitle("Gradients", fontsize=displayConfig.fontsize, x=0.5, y=0.98)
70+
display_gradients_simply(
71+
trajectory,
72+
shot_ids=[one_shot],
73+
figsize=figure_size,
74+
subfigure=subfigs[1],
75+
uni_gradient="k",
76+
uni_signal="gray",
77+
)
78+
79+
# Slew rates display
80+
subfigs[2].suptitle("Slew rates", fontsize=displayConfig.fontsize, x=0.5, y=0.98)
81+
display_gradients_simply(
82+
np.diff(trajectory, axis=1),
83+
shot_ids=[one_shot],
84+
figsize=figure_size,
85+
subfigure=subfigs[2],
86+
uni_gradient="k",
87+
uni_signal="gray",
88+
)
89+
90+
subfigs[2].axes[0].set_ylabel("Sx")
91+
subfigs[2].axes[1].set_ylabel("Sy")
92+
if nb_dim == 2:
93+
subfigs[2].axes[2].set_ylabel("|S|")
94+
else:
95+
subfigs[2].axes[2].set_ylabel("Sz")
96+
subfigs[2].axes[3].set_ylabel("|S|")
97+
plt.show()
98+
99+
36100
def show_trajectories(
37101
function, arguments, one_shot, subfig_size, dim="3D", axes=(0, 1)
38102
):

src/mrinufft/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@
6565
displayConfig,
6666
display_2D_trajectory,
6767
display_3D_trajectory,
68+
display_gradients,
69+
display_gradients_simply,
6870
)
6971

7072
from .density import voronoi, cell_count, pipe, get_density
@@ -130,6 +132,8 @@
130132
"displayConfig",
131133
"display_2D_trajectory",
132134
"display_3D_trajectory",
135+
"display_gradients",
136+
"display_gradients_simply",
133137
]
134138

135139
from importlib.metadata import version, PackageNotFoundError

src/mrinufft/trajectories/__init__.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,13 @@
33
See also the trajectories examples: :ref:`sphx_glr_generated_autoexamples_trajectories`
44
"""
55

6-
from .display import display_2D_trajectory, display_3D_trajectory, displayConfig
6+
from .display import (
7+
display_2D_trajectory,
8+
display_3D_trajectory,
9+
displayConfig,
10+
display_gradients,
11+
display_gradients_simply,
12+
)
713
from .gradients import patch_center_anomaly
814
from .inits import (
915
initialize_2D_eccentric,
@@ -13,6 +19,7 @@
1319
initialize_3D_random_walk,
1420
initialize_3D_travelling_salesman,
1521
)
22+
from .projection import parameterize_by_arc_length
1623
from .sampling import (
1724
create_chauffert_density,
1825
create_cutoff_decay_density,
@@ -122,4 +129,8 @@
122129
"displayConfig",
123130
"display_2D_trajectory",
124131
"display_3D_trajectory",
132+
"display_gradients",
133+
"display_gradients_simply",
134+
# projection
135+
"parameterize_by_arc_length",
125136
]

0 commit comments

Comments
 (0)