Skip to content

Commit 20d7364

Browse files
authored
Add support for v5.0 trajectories (#188)
1 parent 616b63a commit 20d7364

File tree

4 files changed

+50
-6
lines changed

4 files changed

+50
-6
lines changed

docs/trajectory_gradspec.rst

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ The binary file format is specified as follows:
1010
+----------------+-------+---------+---------+------------------------------------------------------------------------+
1111
| Name | Type | Size | Unit | Description |
1212
+================+=======+=========+=========+========================================================================+
13-
| Version | FLOAT | 1 | n.a. | file version this new version would be “4.0” |
13+
| Version | FLOAT | 1 | n.a. | file version this new version would be “5.0” |
1414
+----------------+-------+---------+---------+------------------------------------------------------------------------+
1515
| Dimension | FLOAT | 1 | n.a. | 2 -> 2D , 3 -> 3D |
1616
+----------------+-------+---------+---------+------------------------------------------------------------------------+
@@ -36,6 +36,8 @@ The binary file format is specified as follows:
3636
+----------------+-------+---------+---------+------------------------------------------------------------------------+
3737
| kStarts | FLOAT | D*Nc | 1/m | K-space location start |
3838
+----------------+-------+---------+---------+------------------------------------------------------------------------+
39+
| kEnds | FLOAT | D*Nc | 1/m | K-space location end points. Only in version>5.0 |
40+
+----------------+-------+---------+---------+------------------------------------------------------------------------+
3941
| Gradient array | FLOAT | D*Nc*Ns | unitary | Gradient trajectory expressed in the range [-1; 1] relative to MaxGrad |
4042
+----------------+-------+---------+---------+------------------------------------------------------------------------+
4143

src/mrinufft/io/nsp.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ def write_gradients(
3636
recon_tag: float = 1.1,
3737
timestamp: float | None = None,
3838
keep_txt_file: bool = False,
39+
final_positions: np.ndarray | None = None,
3940
):
4041
"""Create gradient file from gradients and initial positions.
4142
@@ -66,6 +67,8 @@ def write_gradients(
6667
keep_txt_file : bool, optional
6768
Whether to keep the text file used temporarily which holds data pushed to
6869
binary file, by default False
70+
final_positions : np.ndarray, optional
71+
Final positions. Shape (num_shots, dimension), by default None
6972
7073
"""
7174
num_shots = gradients.shape[0]
@@ -125,6 +128,20 @@ def write_gradients(
125128
)
126129
+ "\n"
127130
)
131+
if version >= 5:
132+
if final_positions is None:
133+
warnings.warn(
134+
"Final positions not provided for version >= 5,"
135+
"calculating final positions from gradients"
136+
)
137+
final_positions = initial_positions + np.sum(gradients, axis=1)
138+
file.write(
139+
"\n".join(
140+
" ".join([f"{iter2:5.4f}" for iter2 in iter1])
141+
for iter1 in final_positions
142+
)
143+
+ "\n"
144+
)
128145
if version < 4.1:
129146
# Write the maximum Gradient
130147
file.write(str(max_grad) + "\n")
@@ -185,6 +202,7 @@ def write_trajectory(
185202
check_constraints: bool = True,
186203
gmax: float = DEFAULT_GMAX,
187204
smax: float = DEFAULT_SMAX,
205+
version: float = 5,
188206
**kwargs,
189207
):
190208
"""Calculate gradients from k-space points and write to file.
@@ -212,17 +230,20 @@ def write_trajectory(
212230
Maximum gradient magnitude in T/m, by default 0.04
213231
smax : float, optional
214232
Maximum slew rate in T/m/ms, by default 0.1
233+
version: float, optional
234+
Trajectory versioning, by default 5
215235
kwargs : dict, optional
216236
Additional arguments for writing the gradient file.
217237
These are arguments passed to write_gradients function above.
218238
"""
219239
# Convert normalized trajectory to gradients
220-
gradients, initial_positions = convert_trajectory_to_gradients(
240+
gradients, initial_positions, final_positions = convert_trajectory_to_gradients(
221241
trajectory,
222242
norm_factor=norm_factor,
223243
resolution=np.asarray(FOV) / np.asarray(img_size),
224244
raster_time=raster_time,
225245
gamma=gamma,
246+
get_final_positions=True,
226247
)
227248

228249
# Check constraints if requested
@@ -245,10 +266,12 @@ def write_trajectory(
245266
write_gradients(
246267
gradients=gradients,
247268
initial_positions=initial_positions,
269+
final_positions=final_positions,
248270
grad_filename=grad_filename,
249271
img_size=img_size,
250272
FOV=FOV,
251273
gamma=gamma,
274+
version=version,
252275
**kwargs,
253276
)
254277

@@ -326,6 +349,9 @@ def read_trajectory(
326349
_, data = _pop_elements(data, left_over)
327350
initial_positions, data = _pop_elements(data, dimension * num_shots)
328351
initial_positions = np.reshape(initial_positions, (num_shots, dimension))
352+
if version >= 5:
353+
final_positions, data = _pop_elements(data, dimension * num_shots)
354+
final_positions = np.reshape(final_positions, (num_shots, dimension))
329355
dwell_time_ns = dwell_time * 1e6
330356
gradient_raster_time_ns = raster_time * 1e6
331357
if version < 4.1:

src/mrinufft/trajectories/utils.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,7 @@ def convert_trajectory_to_gradients(
207207
resolution=DEFAULT_RESOLUTION,
208208
raster_time=DEFAULT_RASTER_TIME,
209209
gamma=Gammas.HYDROGEN,
210+
get_final_positions=False,
210211
):
211212
"""Derive a normalized trajectory over time to provide gradients.
212213
@@ -227,6 +228,9 @@ def convert_trajectory_to_gradients(
227228
gamma : float, optional
228229
Gyromagnetic ratio of the selected nucleus in kHz/T
229230
The default is Gammas.HYDROGEN.
231+
get_final_positions : bool, optional
232+
If `True`, return the final positions in k-space.
233+
The default is `False`.
230234
231235
Returns
232236
-------
@@ -239,6 +243,8 @@ def convert_trajectory_to_gradients(
239243
# Compute gradients and starting positions
240244
gradients = np.diff(trajectory, axis=1) / gamma / raster_time
241245
initial_positions = trajectory[:, 0, :]
246+
if get_final_positions:
247+
return gradients, initial_positions, trajectory[:, -1, :]
242248
return gradients, initial_positions
243249

244250

tests/test_io.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from mrinufft.io.utils import add_phase_to_kspace_with_shifts
66
from mrinufft.trajectories.trajectory2D import initialize_2D_radial
77
from mrinufft.trajectories.trajectory3D import initialize_3D_cones
8-
from pytest_cases import parametrize_with_cases
8+
from pytest_cases import parametrize, parametrize_with_cases
99
from case_trajectories import CasesTrajectories
1010

1111

@@ -40,8 +40,18 @@ def case_trajectory_3D(self):
4040
"name, trajectory, FOV, img_size, in_out, min_osf, gamma, recon_tag",
4141
cases=CasesIO,
4242
)
43+
@parametrize("version", [4.2, 5.0])
4344
def test_write_n_read(
44-
name, trajectory, FOV, img_size, in_out, min_osf, gamma, recon_tag, tmp_path
45+
name,
46+
trajectory,
47+
FOV,
48+
img_size,
49+
in_out,
50+
min_osf,
51+
gamma,
52+
recon_tag,
53+
tmp_path,
54+
version,
4555
):
4656
"""Test function which writes the trajectory and reads it back."""
4757
write_trajectory(
@@ -51,15 +61,15 @@ def test_write_n_read(
5161
check_constraints=True,
5262
grad_filename=str(tmp_path / name),
5363
in_out=in_out,
54-
version=4.2,
64+
version=version,
5565
min_osf=min_osf,
5666
recon_tag=recon_tag,
5767
gamma=gamma,
5868
)
5969
read_traj, params = read_trajectory(
6070
str((tmp_path / name).with_suffix(".bin")), gamma=gamma, read_shots=True
6171
)
62-
assert params["version"] == 4.2
72+
assert params["version"] == version
6373
assert params["num_shots"] == trajectory.shape[0]
6474
assert params["num_samples_per_shot"] == trajectory.shape[1] - 1
6575
assert params["TE"] == (0.5 if in_out else 0)

0 commit comments

Comments
 (0)