55sequences. Requires the `pypulseq` package to be installed.
66"""
77
8+ from types import SimpleNamespace
89import numpy as np
910import 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