Skip to content

Commit 73c0933

Browse files
committed
wip
1 parent 88d2fb2 commit 73c0933

File tree

1 file changed

+91
-5
lines changed

1 file changed

+91
-5
lines changed

src/mrinufft/io/pulseq.py

Lines changed: 91 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,20 @@
55
sequences. Requires the `pypulseq` package to be installed.
66
"""
77

8+
from types import SimpleNamespace
89
import numpy as np
910
import pypulseq as pp
11+
from numpy.typing import NDArray
12+
from ..trajectories.utils import convert_trajectory_to_gradients
1013

1114

12-
def read_pulseq_traj(filename):
15+
def read_pulseq_traj(filename, traj_delay=0.0, grad_offset=0.0):
1316
"""Extract k-space trajectory from a Pulseq sequence file.
1417
1518
The sequence should be a valid Pulseq `.seq` file, with arbitrary gradient
1619
waveforms, which all have the same length.
1720
18-
Unlike `Sequence.calculate_kspace`, this function returns the k-space
19-
trajectory segmented in shots ("blocks" from Pulseq), and works directly
20-
from the gradients waveforms stored in the `.seq` file.
21+
Note
2122
2223
Parameters
2324
----------
@@ -37,5 +38,90 @@ def read_pulseq_traj(filename):
3738
seq = pp.Sequence()
3839
seq.read(filename)
3940

41+
kspace_adc, kspace, t_exc, t_refocus, t_adc = seq.calculate_kspace(
42+
traj_delay, grad_offset
43+
)
4044

41-
gradient_waveforms = seq.waveforms()
45+
# split t_adc with t_exc and t_refocus, the index are then used to split kspace_adc
46+
47+
t_splits = sorted(np.concatenate([t_exc, t_refocus]))
48+
idx_prev = 0
49+
kspace_shots = []
50+
for t in t_splits:
51+
idx_next = np.searchsorted(t_adc, t, side="left")
52+
if idx_next == idx_prev:
53+
continue
54+
if idx_next == len(kspace_adc) and t > t_adc[-1]: # last useful point
55+
break
56+
kspace_shots.append(kspace_adc[idx_prev:idx_next, :])
57+
idx_prev= idx_next
58+
kspace_shots = np.asarray(kspace_shots)
59+
return kspace_shots
60+
61+
62+
def pulseq_grad_blocks(
63+
trajectory: NDArray, system: pp.Opts = pp.Opts.default
64+
) -> list[SimpleNamespace]:
65+
"""Create Pulseq gradient blocks from a k-space trajectory.
66+
67+
Parameters
68+
----------
69+
trajectory : np.ndarray
70+
The k-space trajectory as a numpy array of shape (n_shots, n_samples, 3),
71+
where the last dimension corresponds to the x, y, and z coordinates in k-space.
72+
73+
Returns
74+
-------
75+
list
76+
A list of Pulseq gradient blocks corresponding to the k-space trajectory.
77+
"""
78+
grads = convert_trajectory_to_gradients(trajectory)
79+
80+
# TODO add prewind/postwind stuff from #276
81+
# TODO ensure amplitudes are correct
82+
83+
# TODO deduplicate gradients (reduces the size of the file, speed up the sequence loading).
84+
# TODO Are rotation of trajectories supported in pulseq?
85+
86+
grads = convert_trajectory_to_gradients(trajectory)
87+
for g in grads:
88+
for i, c in enumerate(["x", "y", "z"]):
89+
pp_g = pp.make_arbitrary_grad("x", g[:, i])
90+
91+
92+
return blocks_pp
93+
94+
95+
def pulseq_gre_arb_grad(trajectory, pulse, system: pp.Opts = pp.Opts.default):
96+
"""Create a Pulseq GRE sequence with arbitrary gradient waveform.
97+
98+
Parameters
99+
----------
100+
trajectory : np.ndarray
101+
The k-space trajectory as a numpy array of shape (n_shots, n_samples, 3),
102+
where the last dimension corresponds to the x, y, and z coordinates in k-space.
103+
pulse : pypulseq.Pulse
104+
105+
system : pypulseq.Opts, optional
106+
The system options for the Pulseq sequence. Default is `pp.Opts.default`.
107+
108+
Returns
109+
-------
110+
pp.Sequence
111+
A Pulseq sequence object with the specified arbitrary gradient waveform.
112+
113+
114+
See Also
115+
--------
116+
117+
"""
118+
seq = pp.Sequence(system=system)
119+
grad_shot_ids, grad_shots = pulseq_grad_blocks(trajectory, system)
120+
121+
122+
# TODO
123+
for grad_shot_id in grad_shot_ids:
124+
pp.add_block() # RF pulse
125+
pp.add_block() # pause for tuning echo time
126+
pp.add_block() # Gradient block with ADC
127+
return seq

0 commit comments

Comments
 (0)