MRArbGrad 4.3.0__tar.gz → 4.4.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. {mrarbgrad-4.3.0 → mrarbgrad-4.4.1}/LICENSE +1 -1
  2. mrarbgrad-4.4.1/MANIFEST.in +1 -0
  3. {mrarbgrad-4.3.0 → mrarbgrad-4.4.1/MRArbGrad.egg-info}/PKG-INFO +1 -1
  4. {mrarbgrad-4.3.0 → mrarbgrad-4.4.1}/MRArbGrad.egg-info/SOURCES.txt +4 -4
  5. {mrarbgrad-4.3.0/MRArbGrad.egg-info → mrarbgrad-4.4.1}/PKG-INFO +1 -1
  6. {mrarbgrad-4.3.0 → mrarbgrad-4.4.1}/mrarbgrad_src/Function.py +184 -48
  7. {mrarbgrad-4.3.0 → mrarbgrad-4.4.1}/mrarbgrad_src/Utility.py +108 -27
  8. {mrarbgrad-4.3.0 → mrarbgrad-4.4.1}/mrarbgrad_src/__init__.py +1 -1
  9. mrarbgrad-4.4.1/mrarbgrad_src/__pycache__/Function.cpython-312.pyc +0 -0
  10. mrarbgrad-4.4.1/mrarbgrad_src/__pycache__/Utility.cpython-312.pyc +0 -0
  11. mrarbgrad-4.4.1/mrarbgrad_src/__pycache__/__init__.cpython-312.pyc +0 -0
  12. {mrarbgrad-4.3.0 → mrarbgrad-4.4.1}/mrarbgrad_src/ext/traj/MrTraj.h +2 -2
  13. mrarbgrad-4.4.1/mrarbgrad_src/ext.cpython-312-x86_64-linux-gnu.so +0 -0
  14. {mrarbgrad-4.3.0 → mrarbgrad-4.4.1}/pyproject.toml +1 -1
  15. mrarbgrad-4.3.0/MANIFEST.in +0 -1
  16. mrarbgrad-4.3.0/example/example_BuiltInTraj.py +0 -105
  17. mrarbgrad-4.3.0/example/example_ExternalFunction2D.py +0 -80
  18. mrarbgrad-4.3.0/example/example_ExternalSamples2D.py +0 -84
  19. mrarbgrad-4.3.0/example/example_ExternalSamples3D.py +0 -72
  20. {mrarbgrad-4.3.0 → mrarbgrad-4.4.1}/MRArbGrad.egg-info/dependency_links.txt +0 -0
  21. {mrarbgrad-4.3.0 → mrarbgrad-4.4.1}/MRArbGrad.egg-info/requires.txt +0 -0
  22. {mrarbgrad-4.3.0 → mrarbgrad-4.4.1}/MRArbGrad.egg-info/top_level.txt +0 -0
  23. {mrarbgrad-4.3.0 → mrarbgrad-4.4.1}/README.md +0 -0
  24. {mrarbgrad-4.3.0 → mrarbgrad-4.4.1}/mrarbgrad_src/ext/mag/Mag.cpp +0 -0
  25. {mrarbgrad-4.3.0 → mrarbgrad-4.4.1}/mrarbgrad_src/ext/mag/Mag.h +0 -0
  26. {mrarbgrad-4.3.0 → mrarbgrad-4.4.1}/mrarbgrad_src/ext/main.cpp +0 -0
  27. {mrarbgrad-4.3.0 → mrarbgrad-4.4.1}/mrarbgrad_src/ext/traj/Cones.h +0 -0
  28. {mrarbgrad-4.3.0 → mrarbgrad-4.4.1}/mrarbgrad_src/ext/traj/MrTraj_2D.h +0 -0
  29. {mrarbgrad-4.3.0 → mrarbgrad-4.4.1}/mrarbgrad_src/ext/traj/Rosette.h +0 -0
  30. {mrarbgrad-4.3.0 → mrarbgrad-4.4.1}/mrarbgrad_src/ext/traj/Seiffert.h +0 -0
  31. {mrarbgrad-4.3.0 → mrarbgrad-4.4.1}/mrarbgrad_src/ext/traj/Shell3d.h +0 -0
  32. {mrarbgrad-4.3.0 → mrarbgrad-4.4.1}/mrarbgrad_src/ext/traj/Spiral.h +0 -0
  33. {mrarbgrad-4.3.0 → mrarbgrad-4.4.1}/mrarbgrad_src/ext/traj/TrajFunc.h +0 -0
  34. {mrarbgrad-4.3.0 → mrarbgrad-4.4.1}/mrarbgrad_src/ext/traj/VDSpiral.h +0 -0
  35. {mrarbgrad-4.3.0 → mrarbgrad-4.4.1}/mrarbgrad_src/ext/traj/Yarnball.h +0 -0
  36. {mrarbgrad-4.3.0 → mrarbgrad-4.4.1}/mrarbgrad_src/ext/utility/Intp.h +0 -0
  37. {mrarbgrad-4.3.0 → mrarbgrad-4.4.1}/mrarbgrad_src/ext/utility/LinIntp.h +0 -0
  38. {mrarbgrad-4.3.0 → mrarbgrad-4.4.1}/mrarbgrad_src/ext/utility/SplineIntp.h +0 -0
  39. {mrarbgrad-4.3.0 → mrarbgrad-4.4.1}/mrarbgrad_src/ext/utility/global.cpp +0 -0
  40. {mrarbgrad-4.3.0 → mrarbgrad-4.4.1}/mrarbgrad_src/ext/utility/global.h +0 -0
  41. {mrarbgrad-4.3.0 → mrarbgrad-4.4.1}/mrarbgrad_src/ext/utility/v3.cpp +0 -0
  42. {mrarbgrad-4.3.0 → mrarbgrad-4.4.1}/mrarbgrad_src/ext/utility/v3.h +0 -0
  43. {mrarbgrad-4.3.0 → mrarbgrad-4.4.1}/mrarbgrad_src/trajfunc/__init__.py +0 -0
  44. {mrarbgrad-4.3.0 → mrarbgrad-4.4.1}/mrarbgrad_src/trajfunc/main.py +0 -0
  45. {mrarbgrad-4.3.0 → mrarbgrad-4.4.1}/setup.cfg +0 -0
  46. {mrarbgrad-4.3.0 → mrarbgrad-4.4.1}/setup.py +0 -0
@@ -1,4 +1,4 @@
1
- Copyright 2025 Rui Luo
1
+ Copyright 2026 Rui Luo
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
4
 
@@ -0,0 +1 @@
1
+ graft mrarbgrad_src
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: MRArbGrad
3
- Version: 4.3.0
3
+ Version: 4.4.1
4
4
  Summary: Gradient waveform design tool for arbitrary k-space trajectories.
5
5
  Author-email: Ryan <ryan_shanghaitech@proton.me>
6
6
  License-Expression: MIT
@@ -12,13 +12,13 @@ MRArbGrad.egg-info/SOURCES.txt
12
12
  MRArbGrad.egg-info/dependency_links.txt
13
13
  MRArbGrad.egg-info/requires.txt
14
14
  MRArbGrad.egg-info/top_level.txt
15
- example/example_BuiltInTraj.py
16
- example/example_ExternalFunction2D.py
17
- example/example_ExternalSamples2D.py
18
- example/example_ExternalSamples3D.py
19
15
  mrarbgrad_src/Function.py
20
16
  mrarbgrad_src/Utility.py
21
17
  mrarbgrad_src/__init__.py
18
+ mrarbgrad_src/ext.cpython-312-x86_64-linux-gnu.so
19
+ mrarbgrad_src/__pycache__/Function.cpython-312.pyc
20
+ mrarbgrad_src/__pycache__/Utility.cpython-312.pyc
21
+ mrarbgrad_src/__pycache__/__init__.cpython-312.pyc
22
22
  mrarbgrad_src/ext/main.cpp
23
23
  mrarbgrad_src/ext/mag/Mag.cpp
24
24
  mrarbgrad_src/ext/mag/Mag.h
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: MRArbGrad
3
- Version: 4.3.0
3
+ Version: 4.4.1
4
4
  Summary: Gradient waveform design tool for arbitrary k-space trajectories.
5
5
  Author-email: Ryan <ryan_shanghaitech@proton.me>
6
6
  License-Expression: MIT
@@ -23,10 +23,24 @@ def calGrad4ExFunc\
23
23
  p0:float64 = 0e0,
24
24
  p1:float64 = 1e0,
25
25
  ) -> tuple[NDArray, NDArray]:
26
- '''
27
- :return: gradient waveform, corresponding parameter
28
- :rtype: tuple[NDArray, NDArray]
29
- '''
26
+ """
27
+ Generate gradient waveforms for external functions.
28
+
29
+ Args:
30
+ fov (float64, optional): Field of view, unit: `m`.
31
+ nPix (int64, optional): Number of pixel, unit: `a.u.`.
32
+ sLim (float64, optional): l2 Slew limit, unit: `Hz/pix/s`.
33
+ gLim (float64, optional): l2 Gradient limit, unit: `Hz/pix`.
34
+ dt (float64, optional): Time step, unit: `s`.
35
+ getK (Callable | None, optional): Trajectory function.
36
+ getDkDp (Callable | None, optional): 1st order derivative to trajectory function.
37
+ getD2kDp2 (Callable | None, optional): 2nd order derivative to trajectory function.
38
+ p0 (float64, optional): Lower bound of the trajectory parameter.
39
+ p1 (float64, optional): Upper bound of the trajectory parameter.
40
+
41
+ Returns:
42
+ tuple[NDArray, NDArray]: Gradient waveform (in `Hz/pix`); Corresponding parameter
43
+ """
30
44
  return ext.calGrad4ExFunc\
31
45
  (
32
46
  float64(fov),
@@ -55,10 +69,20 @@ def calGrad4ExSamp\
55
69
 
56
70
  arrK: NDArray = np.empty((0,3)),
57
71
  ) -> tuple[NDArray, NDArray]:
58
- '''
59
- :return: gradient waveform, corresponding parameter
60
- :rtype: tuple[NDArray, NDArray]
61
- '''
72
+ """
73
+ Generate gradient waveforms for external trajectory samples.
74
+
75
+ Args:
76
+ fov (float64, optional): Field of view, unit: `m`.
77
+ nPix (int64, optional): Number of pixel, unit: `a.u.`.
78
+ sLim (float64, optional): l2 Slew limit, unit: `Hz/pix/s`.
79
+ gLim (float64, optional): l2 Gradient limit, unit: `Hz/pix`.
80
+ dt (float64, optional): Time step, unit: `s`.
81
+ arrK (NDArray, optional): External trajectory samples, shape: `[nK,nAx]`
82
+
83
+ Returns:
84
+ tuple[NDArray, NDArray]: Gradient waveform (in `Hz/pix`); Corresponding parameter
85
+ """
62
86
  return ext.calGrad4ExSamp\
63
87
  (
64
88
  float64(fov),
@@ -83,10 +107,21 @@ def getG_Spiral\
83
107
  nSlice: int64 = 1,
84
108
  kRhoPhi: float64 = 0.5 / (4 * pi)
85
109
  ) -> tuple[list[NDArray], list[NDArray]]:
86
- '''
87
- :return: list of trajectory start, list of gradient waveforms
88
- :rtype: tuple[list[NDArray], list[NDArray]]
89
- '''
110
+ """
111
+ Generate gradient waveforms for a standard spiral trajectory.
112
+
113
+ Args:
114
+ fov (float64, optional): Field of view, unit: `m`.
115
+ nPix (int64, optional): Number of pixel, unit: `a.u.`.
116
+ sLim (float64, optional): l2 Slew limit, unit: `Hz/pix/s`.
117
+ gLim (float64, optional): l2 Gradient limit, unit: `Hz/pix`.
118
+ dt (float64, optional): Time step, unit: `s`.
119
+ nSlice (int64, optional): Number of slices.
120
+ kRhoPhi (float64, optional): Shape parameter.
121
+
122
+ Returns:
123
+ tuple[list[NDArray], list[NDArray]]: List of trajectory start points (in `/pix`); List of gradient waveforms (in `Hz/pix`)
124
+ """
90
125
  return ext.getG_Spiral\
91
126
  (
92
127
  float64(fov),
@@ -113,10 +148,22 @@ def getG_VDSpiral\
113
148
  kRhoPhi0: float64 = 0.5 / (8 * pi),
114
149
  kRhoPhi1: float64 = 0.5 / (2 * pi),
115
150
  ) -> tuple[list[NDArray], list[NDArray]]:
116
- '''
117
- :return: list of trajectory start, list of gradient waveforms
118
- :rtype: tuple[list[NDArray], list[NDArray]]
119
- '''
151
+ """
152
+ Generate gradient waveforms for a variable density (VD) spiral trajectory.
153
+
154
+ Args:
155
+ fov (float64, optional): Field of view, unit: `m`.
156
+ nPix (int64, optional): Number of pixel, unit: `a.u.`.
157
+ sLim (float64, optional): l2 Slew limit, unit: `Hz/pix/s`.
158
+ gLim (float64, optional): l2 Gradient limit, unit: `Hz/pix`.
159
+ dt (float64, optional): Time step, unit: `s`.
160
+ nSlice (int64, optional): Number of slices.
161
+ kRhoPhi0 (float64, optional): Inner shape parameter.
162
+ kRhoPhi1 (float64, optional): Outer shape parameter.
163
+
164
+ Returns:
165
+ tuple[list[NDArray], list[NDArray]]: List of trajectory start points (in `/pix`); List of gradient waveforms (in `Hz/pix`)
166
+ """
120
167
  return ext.getG_VDSpiral\
121
168
  (
122
169
  float64(fov),
@@ -143,10 +190,21 @@ def getG_VDSpiral_RT\
143
190
  kRhoPhi0: float64 = 0.5 / (8 * pi),
144
191
  kRhoPhi1: float64 = 0.5 / (2 * pi),
145
192
  ) -> tuple[list[NDArray], list[NDArray]]:
146
- '''
147
- :return: list of trajectory start, list of gradient waveforms
148
- :rtype: tuple[list[NDArray], list[NDArray]]
149
- '''
193
+ """
194
+ Generate gradient waveforms for a real-time variable density spiral trajectory.
195
+
196
+ Args:
197
+ fov (float64, optional): Field of view, unit: `m`.
198
+ nPix (int64, optional): Number of pixel, unit: `a.u.`.
199
+ sLim (float64, optional): l2 Slew limit, unit: `Hz/pix/s`.
200
+ gLim (float64, optional): l2 Gradient limit, unit: `Hz/pix`.
201
+ dt (float64, optional): Time step, unit: `s`.
202
+ kRhoPhi0 (float64, optional): Inner shape parameter.
203
+ kRhoPhi1 (float64, optional): Outer shape parameter.
204
+
205
+ Returns:
206
+ tuple[list[NDArray], list[NDArray]]: List of trajectory start points (in `/pix`); List of gradient waveforms (in `Hz/pix`)
207
+ """
150
208
  return ext.getG_VDSpiral_RT\
151
209
  (
152
210
  float64(fov),
@@ -174,10 +232,23 @@ def getG_Rosette\
174
232
  om2: float64 = 3*pi,
175
233
  tMax: float64 = 1e0,
176
234
  ) -> tuple[list[NDArray], list[NDArray]]:
177
- '''
178
- :return: list of trajectory start, list of gradient waveforms
179
- :rtype: tuple[list[NDArray], list[NDArray]]
180
- '''
235
+ """
236
+ Generate gradient waveforms for a rosette trajectory.
237
+
238
+ Args:
239
+ fov (float64, optional): Field of view, unit: `m`.
240
+ nPix (int64, optional): Number of pixel, unit: `a.u.`.
241
+ sLim (float64, optional): l2 Slew limit, unit: `Hz/pix/s`.
242
+ gLim (float64, optional): l2 Gradient limit, unit: `Hz/pix`.
243
+ dt (float64, optional): Time step, unit: `s`.
244
+ nSlice (int64, optional): Number of slices.
245
+ om1 (float64, optional): Shape parameter.
246
+ om2 (float64, optional): Shape parameter.
247
+ tMax (float64, optional): Shape parameter.
248
+
249
+ Returns:
250
+ tuple[list[NDArray], list[NDArray]]: List of trajectory start points (in `/pix`); List of gradient waveforms (in `Hz/pix`)
251
+ """
181
252
  return ext.getG_Rosette\
182
253
  (
183
254
  float64(fov),
@@ -208,10 +279,24 @@ def getG_Rosette_Trad\
208
279
  tMax: float64 = 1e0,
209
280
  tAcq: float64 = 2.523e-3,
210
281
  ) -> tuple[list[NDArray], list[NDArray]]:
211
- '''
212
- :return: list of trajectory start, list of gradient waveforms
213
- :rtype: tuple[list[NDArray], list[NDArray]]
214
- '''
282
+ """
283
+ Generate gradient waveforms for a traditional rosette trajectory.
284
+
285
+ Args:
286
+ fov (float64, optional): Field of view, unit: `m`.
287
+ nPix (int64, optional): Number of pixel, unit: `a.u.`.
288
+ sLim (float64, optional): l2 Slew limit, unit: `Hz/pix/s`.
289
+ gLim (float64, optional): l2 Gradient limit, unit: `Hz/pix`.
290
+ dt (float64, optional): Time step, unit: `s`.
291
+ nSlice (int64, optional): Number of slices.
292
+ om1 (float64, optional): Shape parameter.
293
+ om2 (float64, optional): Shape parameter.
294
+ tMax (float64, optional): Shape parameter.
295
+ tAcq (float64, optional): Acquisition duration.
296
+
297
+ Returns:
298
+ tuple[list[NDArray], list[NDArray]]: List of trajectory start points (in `/pix`); List of gradient waveforms (in `Hz/pix`)
299
+ """
215
300
  return ext.getG_Rosette_Trad\
216
301
  (
217
302
  float64(fov),
@@ -239,10 +324,20 @@ def getG_Shell3d\
239
324
 
240
325
  kRhoTht: float64 = 0.5 / (2 * pi),
241
326
  ) -> tuple[list[NDArray], list[NDArray]]:
242
- '''
243
- :return: list of trajectory start, list of gradient waveforms
244
- :rtype: tuple[list[NDArray], list[NDArray]]
245
- '''
327
+ """
328
+ Generate gradient waveforms for a 3D shell trajectory.
329
+
330
+ Args:
331
+ fov (float64, optional): Field of view, unit: `m`.
332
+ nPix (int64, optional): Number of pixel, unit: `a.u.`.
333
+ sLim (float64, optional): l2 Slew limit, unit: `Hz/pix/s`.
334
+ gLim (float64, optional): l2 Gradient limit, unit: `Hz/pix`.
335
+ dt (float64, optional): Time step, unit: `s`.
336
+ kRhoTht (float64, optional): Shape parameter.
337
+
338
+ Returns:
339
+ tuple[list[NDArray], list[NDArray]]: List of trajectory start points (in `/pix`); List of gradient waveforms (in `Hz/pix`)
340
+ """
246
341
  return ext.getG_Shell3d\
247
342
  (
248
343
  float64(fov),
@@ -266,10 +361,20 @@ def getG_Yarnball\
266
361
 
267
362
  kRhoPhi: float64 = 0.5 / (2 * pi),
268
363
  ) -> tuple[list[NDArray], list[NDArray]]:
269
- '''
270
- :return: list of trajectory start, list of gradient waveforms
271
- :rtype: tuple[list[NDArray], list[NDArray]]
272
- '''
364
+ """
365
+ Generate gradient waveforms for a yarnball trajectory.
366
+
367
+ Args:
368
+ fov (float64, optional): Field of view, unit: `m`.
369
+ nPix (int64, optional): Number of pixel, unit: `a.u.`.
370
+ sLim (float64, optional): l2 Slew limit, unit: `Hz/pix/s`.
371
+ gLim (float64, optional): l2 Gradient limit, unit: `Hz/pix`.
372
+ dt (float64, optional): Time step, unit: `s`.
373
+ kRhoPhi (float64, optional): Shape parameter.
374
+
375
+ Returns:
376
+ tuple[list[NDArray], list[NDArray]]: List of trajectory start points (in `/pix`); List of gradient waveforms (in `Hz/pix`)
377
+ """
273
378
  return ext.getG_Yarnball\
274
379
  (
275
380
  float64(fov),
@@ -293,10 +398,20 @@ def getG_Yarnball_RT\
293
398
 
294
399
  kRhoPhi: float64 = 0.5 / (2 * pi)
295
400
  ) -> tuple[list[NDArray], list[NDArray]]:
296
- '''
297
- :return: list of trajectory start, list of gradient waveforms
298
- :rtype: tuple[list[NDArray], list[NDArray]]
299
- '''
401
+ """
402
+ Generate gradient waveforms for a real-time yarnball trajectory.
403
+
404
+ Args:
405
+ fov (float64, optional): Field of view, unit: `m`.
406
+ nPix (int64, optional): Number of pixel, unit: `a.u.`.
407
+ sLim (float64, optional): l2 Slew limit, unit: `Hz/pix/s`.
408
+ gLim (float64, optional): l2 Gradient limit, unit: `Hz/pix`.
409
+ dt (float64, optional): Time step, unit: `s`.
410
+ kRhoPhi (float64, optional): Shape parameter.
411
+
412
+ Returns:
413
+ tuple[list[NDArray], list[NDArray]]: List of trajectory start points (in `/pix`); List of gradient waveforms (in `Hz/pix`)
414
+ """
300
415
  return ext.getG_Yarnball_RT\
301
416
  (
302
417
  float64(fov),
@@ -321,10 +436,21 @@ def getG_Seiffert\
321
436
  m: float64 = 0.07,
322
437
  uMax: float64 = 20.0,
323
438
  ) -> tuple[list[NDArray], list[NDArray]]:
324
- '''
325
- :return: list of trajectory start, list of gradient waveforms
326
- :rtype: tuple[list[NDArray], list[NDArray]]
327
- '''
439
+ """
440
+ Generate gradient waveforms for a Seiffert spiral trajectory.
441
+
442
+ Args:
443
+ fov (float64, optional): Field of view, unit: `m`.
444
+ nPix (int64, optional): Number of pixel, unit: `a.u.`.
445
+ sLim (float64, optional): l2 Slew limit, unit: `Hz/pix/s`.
446
+ gLim (float64, optional): l2 Gradient limit, unit: `Hz/pix`.
447
+ dt (float64, optional): Time step, unit: `s`.
448
+ m (float64, optional): Shape parameter.
449
+ uMax (float64, optional): Shape parameter.
450
+
451
+ Returns:
452
+ tuple[list[NDArray], list[NDArray]]: List of trajectory start points (in `/pix`); List of gradient waveforms (in `Hz/pix`)
453
+ """
328
454
  return ext.getG_Seiffert\
329
455
  (
330
456
  float64(fov),
@@ -349,10 +475,20 @@ def getG_Cones\
349
475
 
350
476
  kRhoPhi: float64 = 0.5 / (4 * pi),
351
477
  ) -> tuple[list[NDArray], list[NDArray]]:
352
- '''
353
- :return: list of trajectory start, list of gradient waveforms
354
- :rtype: tuple[list[NDArray], list[NDArray]]
355
- '''
478
+ """
479
+ Generate gradient waveforms for a 3D cones trajectory.
480
+
481
+ Args:
482
+ fov (float64, optional): Field of view, unit: `m`.
483
+ nPix (int64, optional): Number of pixel, unit: `a.u.`.
484
+ sLim (float64, optional): l2 Slew limit, unit: `Hz/pix/s`.
485
+ gLim (float64, optional): l2 Gradient limit, unit: `Hz/pix`.
486
+ dt (float64, optional): Time step, unit: `s`.
487
+ kRhoPhi (float64, optional): Shape parameter.
488
+
489
+ Returns:
490
+ tuple[list[NDArray], list[NDArray]]: List of trajectory start points (in `/pix`); List of gradient waveforms (in `Hz/pix`)
491
+ """
356
492
  return ext.getG_Cones\
357
493
  (
358
494
  float64(fov),
@@ -1,6 +1,7 @@
1
1
  from numpy import *
2
2
  from numpy.linalg import norm
3
3
  from numpy.typing import *
4
+ from itertools import product
4
5
 
5
6
  goldrat = (1+sqrt(5))/2
6
7
  goldang = (2*pi)/(1+goldrat)
@@ -8,6 +9,86 @@ goldang = (2*pi)/(1+goldrat)
8
9
  def getGoldrat()->float: return goldrat
9
10
  def getGoldang()->float: return goldang
10
11
 
12
+ def unitcvt(x:float, fov:float, nPix:int, dir:int, gamma:float=42.5756e6) -> float:
13
+ """
14
+ This package uses `Hz/pix` for gradient unit, and `Hz/pix/s` for slew unit, and `pix` for length unit. This unit system has several good properties for theory derivation. You may need this helper function for unit conversion in case you are not familiar with this unit system.
15
+
16
+ You may need `dir=1,2` to convert common units (like `mT/m` for gradient and `T/m/s` for slew) to units supported by this package (like `Hz/pix` for gradient and `Hz/pix/s` for slew), before you call any gradient generation functions, or `dir=-1` when converting gradients generated by this package in `Hz/pix` back to `mT/m`.
17
+
18
+ Args:
19
+ x (float): Input value.
20
+ fov (float): Field of view.
21
+ nPix (int): Number of pixels.
22
+ dir (int):
23
+ `1`: from `mT/m` to `Hz/pix`.
24
+ `2`: from `T/m/s` to `Hz/pix/s`.
25
+ `-1`: from `Hz/pix` to `mT/m`.
26
+ `-2`: from `Hz/pix/s` to `T/m/s`.
27
+ gamma (float): Gyromagnetic ratio `Hz/T`.
28
+
29
+ Returns:
30
+ float: Converted value.
31
+ """
32
+ if dir==1:
33
+ return x * (1e-3*gamma*fov/nPix)
34
+ if dir==2:
35
+ return x * (gamma*fov/nPix)
36
+ if dir==-1:
37
+ return x / (1e-3*gamma*fov/nPix)
38
+ if dir==-2:
39
+ return x / (gamma*fov/nPix)
40
+ raise RuntimeError("dir")
41
+
42
+ def getK_Cartesian(nPix:int, nAx:int) -> list[NDArray]:
43
+ """
44
+ Generates a Cartesian sampling pattern.
45
+
46
+ Args:
47
+ nPix (int): Number of pixels.
48
+ nAx (int): Number of dimensions.
49
+
50
+ Returns:
51
+ list[NDArray]: List of trajectories in `[nK,nAx]`.
52
+ """
53
+ arrK1D = linspace(-0.5, 0.5, nPix, endpoint=0)
54
+ lstArrK:list[NDArray] = []
55
+
56
+ for arrKyKz in product(arrK1D, repeat=nAx-1):
57
+ arrK = empty((nPix,nAx), dtype=float32)
58
+ arrK[:,0] = arrK1D
59
+ arrK[:,1:] = arrKyKz
60
+ lstArrK.append(arrK)
61
+
62
+ return lstArrK
63
+
64
+
65
+ def getK_Radial(nPix:int, nAx:int, nSpoke:int, enGoldAng:bool) -> list:
66
+ """
67
+ Generates a Radial sampling pattern.
68
+
69
+ Args:
70
+ nPix (int): Number of pixels.
71
+ nAx (int): Number of dimensions.
72
+ nSpoke (int): Number of radial spokes.
73
+ enGoldAng (bool): If True, uses the golden angle for spoke spacing.
74
+
75
+ Returns:
76
+ list: A list of numpy arrays, each of shape [nPix, nAx], representing radial spokes.
77
+ """
78
+ if nAx!=2: raise NotImplementedError("nAx!=2")
79
+ lstArrK = []
80
+ arrK1D = linspace(-0.5, 0.5, nPix, endpoint=0)
81
+
82
+ for iSpoke in range(nSpoke):
83
+ if enGoldAng: tht = iSpoke * goldang
84
+ else: tht = iSpoke * (pi / nSpoke)
85
+ arrK = empty((nPix,nAx), dtype=float32)
86
+ arrK[:,0] = arrK1D * cos(tht)
87
+ arrK[:,1] = arrK1D * sin(tht)
88
+ lstArrK.append(arrK)
89
+
90
+ return lstArrK
91
+
11
92
  def gradClip(lstArrGrad:list[NDArray]|NDArray, dt:float, sLim:float, gLim:float) -> list[NDArray]|NDArray:
12
93
  """
13
94
  Clip the slewrate and gradient amlitude of a list of gradient waveforms
@@ -64,70 +145,70 @@ def genPermTab(n:int) -> list[int]:
64
145
  lstIdx.append(i*inc%n)
65
146
  return lstIdx
66
147
 
67
- def cvtGrad2Traj(arrG:NDArray, dtGrad:int|float, dtADC:int|float, nShift:int|float=1.0) -> tuple[NDArray, NDArray]:
148
+ def cvtGrad2Traj(arrGrad:NDArray, dtGrad:int|float, dtAdc:int|float, nShift:int|float=1.0) -> tuple[NDArray, NDArray]:
68
149
  """
69
150
  # description:
70
151
  interpolate gradient waveform and calculate trajectory
71
152
 
72
153
  # parameter
73
- `arrG`: array of gradient waveform
74
- `dtGrad`, `dtADC`: temporal resolution of gradient system and ADC
154
+ `arrGrad`: array of gradient waveform
155
+ `dtGrad`, `dtAdc`: temporal resolution of gradient system and ADC
75
156
 
76
157
  # return:
77
158
  interpolated trajectory and gradient
78
159
  """
79
- dtShift = nShift*dtADC
80
- nGrad, nDim = arrG.shape
81
- nADC = int(dtGrad/dtADC)*(nGrad-1)
82
- arrG_Resamp = zeros([nADC,nDim], dtype=float64)
83
- for iDim in range(nDim):
84
- arrG_Resamp[:,iDim] = interp(dtADC*arange(nADC)+dtShift, dtGrad*arange(nGrad), arrG[:,iDim])
85
- arrDk = zeros_like(arrG_Resamp)
86
- arrDk[0,:] = (arrG[0,:] + arrG_Resamp[0,:])*dtShift/2
87
- arrDk[1:,:] = (arrG_Resamp[:-1] + arrG_Resamp[1:])*dtADC/2
160
+ dtShift = nShift*dtAdc
161
+ nGrad, nAx = arrGrad.shape
162
+ nAdc = int(dtGrad/dtAdc)*(nGrad-1)
163
+ arrGrad_Resamp = zeros([nAdc,nAx], dtype=float64)
164
+ for iDim in range(nAx):
165
+ arrGrad_Resamp[:,iDim] = interp(dtAdc*arange(nAdc)+dtShift, dtGrad*arange(nGrad), arrGrad[:,iDim])
166
+ arrDk = zeros_like(arrGrad_Resamp)
167
+ arrDk[0,:] = (arrGrad[0,:] + arrGrad_Resamp[0,:])*dtShift/2
168
+ arrDk[1:,:] = (arrGrad_Resamp[:-1] + arrGrad_Resamp[1:])*dtAdc/2
88
169
  arrK = cumsum(arrDk,axis=0)
89
- return arrK, arrG_Resamp
170
+ return arrK, arrGrad_Resamp
90
171
 
91
- def delGrad(arrG:NDArray, tau:int|float) -> NDArray:
172
+ def delGrad(arrGrad:NDArray, tau:int|float) -> NDArray:
92
173
  """
93
174
  # description:
94
175
  delay the input gradient waveform by time constant tau
95
176
 
96
177
  # parameter
97
- `arrG`: array of single gradient waveform
178
+ `arrGrad`: array of single gradient waveform
98
179
  `tau`: time constant in RL circuit transfer function
99
180
 
100
181
  # return:
101
182
  delayed gradient waveform
102
183
  """
103
- assert arrG.ndim == 2, "only single gradient waveform is supported."
104
- if tau == 0: return arrG.copy() # avoid divided-by-0 later
105
- nPt, nAx = arrG.shape
184
+ assert arrGrad.ndim == 2, "only single gradient waveform is supported."
185
+ if tau == 0: return arrGrad.copy() # avoid divided-by-0 later
186
+ nPt, nAx = arrGrad.shape
106
187
 
107
188
  # perform oversample to get better impluse response profile
108
189
  ov = clip(10/tau, 1, 1e3).astype(int64) # the smaller the ov, the bigger oversampling is needed
109
- arrG_ov = zeros([nPt*ov,nAx], dtype=arrG.dtype)
190
+ arrGrad_ov = zeros([nPt*ov,nAx], dtype=arrGrad.dtype)
110
191
  for iAx in range(nAx):
111
- arrG_ov[:,iAx] = interp(linspace(0,nPt,nPt*ov,0), linspace(0,nPt,nPt,0), arrG[:,iAx]) # oversample
192
+ arrGrad_ov[:,iAx] = interp(linspace(0,nPt,nPt*ov,0), linspace(0,nPt,nPt,0), arrGrad[:,iAx]) # oversample
112
193
  nPt *= ov
113
194
  tau *= ov
114
195
 
115
196
  # derive impluse response of RL circuit
116
- arrG_Pad = zeros_like(arrG_ov)
117
- arrG_Pad[:nPt//2,:] = arrG_ov[-1:,:]
118
- arrG_Pad[nPt//2:,:] = arrG_ov[:1,:]
119
- arrG_ov = concatenate([arrG_ov, arrG_Pad], axis=0)
197
+ arrGrad_Pad = zeros_like(arrGrad_ov)
198
+ arrGrad_Pad[:nPt//2,:] = arrGrad_ov[-1:,:]
199
+ arrGrad_Pad[nPt//2:,:] = arrGrad_ov[:1,:]
200
+ arrGrad_ov = concatenate([arrGrad_ov, arrGrad_Pad], axis=0)
120
201
  arrT = linspace(0,2*nPt,2*nPt,0) + 0.5
121
202
  arrImpResRL = (1/tau)*exp(-arrT/tau)
122
203
  if abs(arrImpResRL.sum() - 1) > 1e-2: raise ValueError(f"arrImpResRL.sum() = {arrImpResRL.sum():.2f} (supposed to be 1) (tau too small or too large)")
123
204
 
124
205
  # perform convolution between input waveform and impulse response
125
- arrG_ov = fft.ifft(fft.fft(arrG_ov,axis=0)*fft.fft(arrImpResRL)[:,newaxis], axis=0).real
206
+ arrGrad_ov = fft.ifft(fft.fft(arrGrad_ov,axis=0)*fft.fft(arrImpResRL)[:,newaxis], axis=0).real
126
207
 
127
208
  # de-oversample
128
- arrG = arrG_ov[:nPt:ov,:]
209
+ arrGrad = arrGrad_ov[:nPt:ov,:]
129
210
 
130
- return arrG
211
+ return arrGrad
131
212
 
132
213
  def rotate(arr:NDArray, ang:float64, axis:int64) -> NDArray:
133
214
  if axis==0: # x
@@ -1,6 +1,6 @@
1
1
  from .Function import calGrad4ExFunc, calGrad4ExSamp
2
2
  from .Function import getG_Cones, getG_Rosette, getG_Rosette_Trad, getG_Seiffert, getG_Shell3d, getG_Spiral, getG_VDSpiral, getG_VDSpiral_RT, getG_Yarnball, getG_Yarnball_RT
3
3
  from .Function import setSolverMtg, setTrajRev, setGoldAng, setShuf, setMaxG0, setMaxG1, setMagGradSamp, setMagTrajSamp, setMagOverSamp, setMagSFS, setMagGradRep, setMagTrajRep, setDbgPrint, saveF64, loadF64, saveF32, loadF32
4
- from .Utility import _calDiaphony, rotate, _calJacElip, _calCompElipInt, _calSphFibPt, cvtGrad2Traj, getGoldang, getGoldrat, rand3d, gradClip, genPermTab
4
+ from .Utility import _calDiaphony, rotate, _calJacElip, _calCompElipInt, _calSphFibPt, cvtGrad2Traj, getGoldang, getGoldrat, rand3d, gradClip, genPermTab, unitcvt, getK_Cartesian, getK_Radial
5
5
 
6
6
  from . import trajfunc
@@ -13,7 +13,7 @@
13
13
  bool gMrTraj_enMtg = false; // whether to use Lustig's minTimeGrad solver
14
14
  f64 gMrTraj_g0Norm = 0e0; // initial gradient amplitude
15
15
  f64 gMrTraj_g1Norm = 0e0; // final gradient amplitude
16
- i64 gMrTraj_nGradSampRsv = 10000; // buffer size of mag solver
16
+ i64 gMrTraj_nGradSampRsv = 10000; // buffer size of MAG solver
17
17
  i64 gMrTraj_nTrajSampRsv = 10000; // num. of samp. when doing Traj. Rep.
18
18
 
19
19
  /*
@@ -204,7 +204,7 @@ protected:
204
204
  minRotang = std::min(minRotang, rotang);
205
205
  }
206
206
 
207
- // ensure the rot. Num. is a integer
207
+ // ensure the rot. Num. is an integer
208
208
  return (i64)std::ceil(2e0*M_PI/minRotang);
209
209
  }
210
210
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "MRArbGrad"
7
- version = "4.3.0"
7
+ version = "4.4.1"
8
8
  dependencies = ["numpy"]
9
9
 
10
10
  description = "Gradient waveform design tool for arbitrary k-space trajectories."
@@ -1 +0,0 @@
1
- global-include *.py *.c *.cpp *.h
@@ -1,105 +0,0 @@
1
- import mrarbgrad as mag
2
- from numpy import *
3
- from matplotlib.pyplot import *
4
- from numpy.linalg import norm
5
-
6
- gamma = 42.5756e6
7
- fov = 0.256
8
- nPix = 256
9
- dtGrad = 10e-6
10
- dtADC = 2.5e-6
11
- sLim = 50 * gamma * fov/nPix
12
- gLim = 20e-3 * gamma * fov/nPix
13
- # gLim = 1/nPix/dtADC
14
- argCom = dict(fov=fov, nPix=nPix, sLim=sLim, gLim=gLim, dt=dtGrad)
15
-
16
- mag.setSolverMtg(0) # set solver, 0 for proposed method, 1 for baseline method, baseline method may be removed due to copyright reason
17
- mag.setTrajRev(0) # reverse the trajectory
18
- mag.setGoldAng(1) # enable golden-angle interleaving (only for 2D)
19
- mag.setShuf(0) # enable TR-shuffling
20
- mag.setMaxG0(0) # use maximum possible initial gradient amplitude
21
- mag.setMaxG1(0) # use maximum possible final gradient amplitude
22
- mag.setMagOverSamp(8) # set oversampling ratio (default: 8, reduce only if hardware performance is too low to support real-time)
23
- mag.setMagSFS(0) # disable DTFBS (experimental)
24
- mag.setMagGradRep(1) # enable gradient reparameterization (experimental)
25
- mag.setMagTrajRep(1) # enable trajectory reparameterization (experimental)
26
- mag.setDbgPrint(1) # enable debug info (for benchmark purpose)
27
-
28
- # calculate gradient
29
- # lstArrK0, lstArrGrad = mag.getG_Spiral(**argCom); nAx = 2
30
- # lstArrK0, lstArrGrad = mag.getG_VDSpiral(**argCom); nAx = 2
31
- # lstArrK0, lstArrGrad = mag.getG_VDSpiral_RT(**argCom); nAx = 2
32
- # lstArrK0, lstArrGrad = mag.getG_Rosette(**argCom); nAx = 2
33
- # lstArrK0, lstArrGrad = mag.getG_Rosette_Trad(**argCom); nAx = 2
34
- # lstArrK0, lstArrGrad = mag.getG_Shell3d(**argCom); nAx = 3
35
- lstArrK0, lstArrGrad = mag.getG_Yarnball(**argCom); nAx = 3
36
- # lstArrK0, lstArrGrad = mag.getG_Seiffert(**argCom); nAx = 3
37
- # lstArrK0, lstArrGrad = mag.getG_Cones(**argCom); nAx = 3
38
-
39
- # lstArrGrad = mag.gradClip(lstArrGrad, dtGrad, sLim, gLim) # clip slew/grad amp with hardware constraint
40
-
41
- print("")
42
- print(f"Intlea Num.: {len(lstArrGrad)}")
43
- nRO_Max = amax([arrG.shape[0] for arrG in lstArrGrad])
44
- print(f"Tacq: {nRO_Max*dtGrad*1e3:.3f} ms")
45
- tTR = (nRO_Max*dtGrad + 2e-3)
46
- print(f"TR: {tTR*1e3:.3f} ms")
47
- tScan = tTR*len(lstArrGrad)
48
- print(f"Tscan: {tScan:.3e} s")
49
-
50
- # derive shape parameter
51
- if nAx==2:
52
- lstArrK0 = [arrK0[:2] for arrK0 in lstArrK0]
53
- lstArrGrad = [arrG[:,:2] for arrG in lstArrGrad]
54
- nRO, nAx = lstArrGrad[0].shape
55
-
56
- # derive slewrate
57
- lstArrSlew = [diff(arrG, axis=0)/dtGrad for arrG in lstArrGrad]
58
- sMax = max(norm(concatenate(lstArrSlew)/gamma*nPix/fov, axis=-1))
59
- gMax = max(norm(concatenate(lstArrGrad)/gamma*nPix/fov, axis=-1))
60
-
61
- # derive trajectory
62
- lstArrK = []
63
- for arrK0, arrGrad in zip(lstArrK0, lstArrGrad):
64
- arrK, _ = mag.cvtGrad2Traj(arrGrad, dtGrad, dtADC)
65
- arrK += arrK0
66
- lstArrK.append(arrK)
67
-
68
- iArrK = len(lstArrK)*2//3
69
-
70
- # k-space and g-space
71
- figure(figsize=(18,9), dpi=120)
72
-
73
- subplot(221, projection="3d" if nAx==3 else None)
74
- plot(*lstArrK[iArrK].T, ".-")
75
- axis("equal")
76
- grid("on")
77
- title(f"kspace {1}/{len(lstArrGrad)}")
78
-
79
- subplot(223, projection="3d" if nAx==3 else None)
80
- plot(*lstArrGrad[iArrK].T, ".-")
81
- axis("equal")
82
- grid("on")
83
- title("gspace")
84
-
85
- # gradient and slewrate
86
- subplot(222)
87
- for iAx in range(nAx):
88
- plot(lstArrGrad[iArrK][:,iAx]/gamma*nPix/fov, ".-")
89
- grid("on")
90
- title(f"Gradient")
91
-
92
- subplot(224)
93
- plot(norm(lstArrSlew[iArrK],axis=-1)/gamma*nPix/fov, ".-", c="tab:blue")
94
- ylim(sLim/gamma*nPix/fov*0.9, sLim/gamma*nPix/fov*1.1)
95
- grid("on")
96
-
97
- twinx()
98
- plot(norm(lstArrGrad[iArrK],axis=-1)/gamma*nPix/fov*1e3, ".-", c="tab:orange")
99
- ylim(gLim/gamma*nPix/fov*0.9*1e3, gLim/gamma*nPix/fov*1.1*1e3)
100
- grid("on")
101
- title(f"Grad & Slew amp., max grad:{gMax*1e3:.3f}, max slew:{sMax:.3f}")
102
-
103
- subplots_adjust(0.05,0.1,0.95,0.9, 0.2, 0.2)
104
-
105
- show()
@@ -1,80 +0,0 @@
1
- from numpy import *
2
- from numpy.linalg import norm
3
- from matplotlib.pyplot import *
4
- import mrarbgrad as mag
5
-
6
- gamma = 42.5756e6
7
- fov = 0.256
8
- nPix = 256
9
- dtGrad = 10e-6
10
- dtADC = 1e-6
11
- sLim = 50 * gamma * fov/nPix
12
- gLim = 20e-3 * gamma * fov/nPix
13
- # gLim = 1/nPix/dtADC
14
-
15
- # Rosette
16
- nAx = 2
17
- om1 = 19*pi
18
- om2 = 17*pi
19
- def Rosette(t):
20
- rho = 0.5*sin(om1*t)
21
- return array\
22
- ([
23
- rho*cos(om2*t),
24
- rho*sin(om2*t),
25
- 0*t
26
- ])
27
-
28
- pLim = [0,1]
29
-
30
- # derive slew-rate constrained trajectory
31
- mag.setMaxG0(0)
32
- mag.setMaxG1(0)
33
- arrGrad = mag.calGrad4ExFunc(fov, nPix, sLim, gLim, dtGrad, Rosette, None, None, pLim[0], pLim[1])[0]
34
- # arrGrad = mag.gradClip(arrGrad, dtGrad, sLim, gLim) # clip slew/grad amp with hardware constraint
35
- nRO = arrGrad.shape[0]
36
-
37
- arrSlew = diff(arrGrad, axis=0)/dtGrad
38
- print(f"sMax: {max(norm(arrSlew,axis=-1))/(42.58e6)*(nPix/fov)}")
39
-
40
- arrK = mag.cvtGrad2Traj(arrGrad, dtGrad, dtADC, 0.5)[0]
41
-
42
- # derive reference trajectory
43
- arrP_Ref = linspace(pLim[0], pLim[1], int(1e4))
44
- arrK_Ref = Rosette(arrP_Ref).T
45
-
46
- # plot
47
- figure(figsize=(20,10), dpi=120)
48
-
49
- subplot(221, projection=None if nAx==2 else "3d")
50
- if nAx==2: plot(arrK_Ref[:,0], arrK_Ref[:,1], "--", label="K_Ref")
51
- if nAx==3: plot(arrK_Ref[:,0], arrK_Ref[:,1], arrK_Ref[:,2], "--", label="K_Ref")
52
- if nAx==2: plot(arrK[:,0], arrK[:,1], ".-", label="K_Imp")
53
- if nAx==3: plot(arrK[:,0], arrK[:,1], arrK[:,2], ".-", label="K_Imp")
54
- xlim(-0.5,0.5)
55
- ylim(-0.5,0.5)
56
- axis("equal")
57
- grid("on")
58
- legend()
59
- title("k-Space")
60
-
61
- subplot(222)
62
- for iAx in range(nAx):
63
- plot(arrGrad[:,iAx]/(42.58e6)*(nPix/fov), ".-")
64
- grid("on")
65
- title("Gradient")
66
-
67
- subplot(223, projection=None if nAx==2 else "3d")
68
- if nAx==2: plot(arrGrad[:,0], arrGrad[:,1], ".-")
69
- if nAx==3: plot(arrGrad[:,0], arrGrad[:,1], arrGrad[:,2], ".-")
70
- axis("equal")
71
- grid("on")
72
- title("g-Space")
73
-
74
- subplot(224)
75
- plot(norm(arrSlew,axis=-1)/(42.58e6)*(nPix/fov), ".-")
76
- ylim(sLim/(42.58e6)*(nPix/fov)*0.9, sLim/(42.58e6)*(nPix/fov)*1.1)
77
- grid("on")
78
- title(f"Slewrate, max:{max(norm(arrSlew,axis=-1))/(42.58e6)*(nPix/fov):.3f}")
79
-
80
- show()
@@ -1,84 +0,0 @@
1
- from numpy import *
2
- from numpy.linalg import norm
3
- from matplotlib.pyplot import *
4
- import mrarbgrad as mag
5
-
6
- gamma = 42.5756e6
7
- fov = 0.256
8
- nPix = 256
9
- dtGrad = 10e-6
10
- dtADC = 2.5e-6
11
- sLim = 50 * gamma * fov/nPix
12
- gLim = 20e-3 * gamma * fov/nPix
13
- # gLim = 1/nPix/dtADC
14
-
15
- # Rosette
16
- om1 = 5*pi
17
- om2 = 3*pi
18
- pLim = [0,1]
19
- def TrajFunc(t):
20
- rho = 0.5*sin(om1*t)
21
- return array\
22
- ([
23
- rho*cos(om2*t),
24
- rho*sin(om2*t),
25
- zeros_like(t)
26
- ])
27
-
28
- # sample
29
- arrP = linspace(pLim[0], pLim[1], 1000)
30
- arrK = TrajFunc(arrP).T
31
- nAx = 2
32
-
33
- # derive slew-rate constrained trajectory
34
- for i in range(1):
35
- arrGrad = mag.calGrad4ExSamp(fov, nPix, sLim, gLim, dtGrad, arrK)[0]
36
- # arrGrad = mag.gradClip(arrGrad, dtGrad, sLim, gLim) # clip slew/grad amp with hardware constraint
37
- nRO = arrGrad.shape[0]
38
-
39
- arrSlew = diff(arrGrad, axis=0)/dtGrad
40
- print(f"sMax: {max(norm(arrSlew,axis=-1))/(42.58e6)*(nPix/fov)}")
41
-
42
- arrK, _ = mag.cvtGrad2Traj(arrGrad, dtGrad, dtADC, 0.5)
43
- arrK += TrajFunc(pLim[0])
44
- print(f"Err: {norm(arrK[-1,:]-TrajFunc(pLim[1])):.1e}")
45
-
46
- # derive reference trajectory
47
- arrP_Ref = linspace(pLim[0], pLim[1], int(1e4))
48
- arrK_Ref = TrajFunc(arrP_Ref).T
49
-
50
- # plot
51
- figure(figsize=(20,10), dpi=120)
52
-
53
- subplot(221, projection=None if nAx==2 else "3d")
54
- if nAx==2: plot(arrK_Ref[:,0], arrK_Ref[:,1], "--", label="K_Ref")
55
- if nAx==3: plot(arrK_Ref[:,0], arrK_Ref[:,1], arrK_Ref[:,2], "--", label="K_Ref")
56
- if nAx==2: plot(arrK[:,0], arrK[:,1], ".-", label="K_Imp")
57
- if nAx==3: plot(arrK[:,0], arrK[:,1], arrK[:,2], ".-", label="K_Imp")
58
- xlim(-0.5,0.5)
59
- ylim(-0.5,0.5)
60
- axis("equal")
61
- grid("on")
62
- legend()
63
- title("k-Space")
64
-
65
- subplot(222)
66
- for iAx in range(nAx):
67
- plot(arrGrad[:,iAx]/(42.58e6)*(nPix/fov), ".-")
68
- grid("on")
69
- title("Gradient")
70
-
71
- subplot(223, projection=None if nAx==2 else "3d")
72
- if nAx==2: plot(arrGrad[:,0], arrGrad[:,1], ".-")
73
- if nAx==3: plot(arrGrad[:,0], arrGrad[:,1], arrGrad[:,2], ".-")
74
- axis("equal")
75
- grid("on")
76
- title("g-Space")
77
-
78
- subplot(224)
79
- plot(norm(arrSlew,axis=-1)/(42.58e6)*(nPix/fov), ".-")
80
- ylim(sLim/(42.58e6)*(nPix/fov)*0.9, sLim/(42.58e6)*(nPix/fov)*1.1)
81
- grid("on")
82
- title(f"Slewrate, max:{max(norm(arrSlew,axis=-1))/(42.58e6)*(nPix/fov):.3f}")
83
-
84
- show()
@@ -1,72 +0,0 @@
1
- from numpy import *
2
- from numpy.linalg import norm
3
- from matplotlib.pyplot import *
4
- import mrarbgrad as mag
5
-
6
- gamma = 42.5756e6
7
- fov = 0.256
8
- nPix = 256
9
- dtGrad = 10e-6
10
- dtADC = 2.5e-6
11
- sLim = 50 * gamma * fov/nPix
12
- gLim = 20e-3 * gamma * fov/nPix
13
- # gLim = 1/nPix/dtADC
14
-
15
- # Yarnball
16
- nAx = 3
17
-
18
- kRhoPhi = 0.5/(2*pi)
19
- Yarnball = lambda sqrtTht: mag.trajfunc.Yarnball(sqrtTht, kRhoPhi)
20
- pLim = mag.trajfunc.prange_Yarnball(kRhoPhi)
21
-
22
- arrP = linspace(pLim[0], pLim[1], 1000)
23
- arrK = Yarnball(arrP)
24
-
25
- # derive slew-rate constrained trajectory
26
- mag.setDbgPrint(1)
27
- arrGrad = mag.calGrad4ExSamp(fov, nPix, sLim, gLim, dtGrad, arrK)[0]
28
- # arrGrad = mag.gradClip(arrGrad, dtGrad, sLim, gLim) # clip slew/grad amp with hardware constraint
29
- nRO = arrGrad.shape[0]
30
-
31
- arrSlew = diff(arrGrad, axis=0)/dtGrad
32
- print(f"sMax: {max(norm(arrSlew,axis=-1))/(42.58e6)*(nPix/fov)}")
33
-
34
- arrK, _ = mag.cvtGrad2Traj(arrGrad, dtGrad, dtADC, 0.5)
35
-
36
- # derive reference trajectory
37
- arrP_Ref = linspace(pLim[0], pLim[1], int(1e4))
38
- arrK_Ref = Yarnball(arrP_Ref)
39
-
40
- # plot
41
- figure(figsize=(20,10), dpi=120)
42
-
43
- subplot(221, projection="3d")
44
- plot(arrK_Ref[:,0], arrK_Ref[:,1], arrK_Ref[:,2], "--", label="K_Ref")
45
- plot(arrK[:,0], arrK[:,1], arrK[:,2], ".-", label="K_Imp")
46
- xlim(-0.5,0.5)
47
- ylim(-0.5,0.5)
48
- axis("equal")
49
- grid("on")
50
- legend()
51
- title("k-Space")
52
-
53
- subplot(222)
54
- for iAx in range(nAx):
55
- plot(arrGrad[:,iAx]/(42.58e6)*(nPix/fov), ".-")
56
- grid("on")
57
- title("Gradient")
58
-
59
- subplot(223, projection=None if nAx==2 else "3d")
60
- if nAx==2: plot(arrGrad[:,0], arrGrad[:,1], ".-")
61
- if nAx==3: plot(arrGrad[:,0], arrGrad[:,1], arrGrad[:,2], ".-")
62
- axis("equal")
63
- grid("on")
64
- title("g-Space")
65
-
66
- subplot(224)
67
- plot(norm(arrSlew,axis=-1)/(42.58e6)*(nPix/fov), ".-")
68
- ylim(sLim/(42.58e6)*(nPix/fov)*0.9, sLim/(42.58e6)*(nPix/fov)*1.1)
69
- grid("on")
70
- title(f"Slewrate, max:{max(norm(arrSlew,axis=-1))/(42.58e6)*(nPix/fov):.3f}")
71
-
72
- show()
File without changes
File without changes
File without changes