MRArbGrad 4.2.0__tar.gz → 4.4.0__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 (41) hide show
  1. {mrarbgrad-4.2.0 → mrarbgrad-4.4.0/MRArbGrad.egg-info}/PKG-INFO +1 -1
  2. {mrarbgrad-4.2.0/MRArbGrad.egg-info → mrarbgrad-4.4.0}/PKG-INFO +1 -1
  3. {mrarbgrad-4.2.0 → mrarbgrad-4.4.0}/mrarbgrad_src/Function.py +184 -48
  4. {mrarbgrad-4.2.0 → mrarbgrad-4.4.0}/mrarbgrad_src/Utility.py +118 -29
  5. {mrarbgrad-4.2.0 → mrarbgrad-4.4.0}/mrarbgrad_src/__init__.py +1 -1
  6. {mrarbgrad-4.2.0 → mrarbgrad-4.4.0}/mrarbgrad_src/ext/traj/MrTraj.h +2 -2
  7. {mrarbgrad-4.2.0 → mrarbgrad-4.4.0}/mrarbgrad_src/ext/traj/VDSpiral.h +13 -9
  8. {mrarbgrad-4.2.0 → mrarbgrad-4.4.0}/pyproject.toml +1 -1
  9. {mrarbgrad-4.2.0 → mrarbgrad-4.4.0}/LICENSE +0 -0
  10. {mrarbgrad-4.2.0 → mrarbgrad-4.4.0}/MANIFEST.in +0 -0
  11. {mrarbgrad-4.2.0 → mrarbgrad-4.4.0}/MRArbGrad.egg-info/SOURCES.txt +0 -0
  12. {mrarbgrad-4.2.0 → mrarbgrad-4.4.0}/MRArbGrad.egg-info/dependency_links.txt +0 -0
  13. {mrarbgrad-4.2.0 → mrarbgrad-4.4.0}/MRArbGrad.egg-info/requires.txt +0 -0
  14. {mrarbgrad-4.2.0 → mrarbgrad-4.4.0}/MRArbGrad.egg-info/top_level.txt +0 -0
  15. {mrarbgrad-4.2.0 → mrarbgrad-4.4.0}/README.md +0 -0
  16. {mrarbgrad-4.2.0 → mrarbgrad-4.4.0}/example/example_BuiltInTraj.py +0 -0
  17. {mrarbgrad-4.2.0 → mrarbgrad-4.4.0}/example/example_ExternalFunction2D.py +0 -0
  18. {mrarbgrad-4.2.0 → mrarbgrad-4.4.0}/example/example_ExternalSamples2D.py +0 -0
  19. {mrarbgrad-4.2.0 → mrarbgrad-4.4.0}/example/example_ExternalSamples3D.py +0 -0
  20. {mrarbgrad-4.2.0 → mrarbgrad-4.4.0}/mrarbgrad_src/ext/mag/Mag.cpp +0 -0
  21. {mrarbgrad-4.2.0 → mrarbgrad-4.4.0}/mrarbgrad_src/ext/mag/Mag.h +0 -0
  22. {mrarbgrad-4.2.0 → mrarbgrad-4.4.0}/mrarbgrad_src/ext/main.cpp +0 -0
  23. {mrarbgrad-4.2.0 → mrarbgrad-4.4.0}/mrarbgrad_src/ext/traj/Cones.h +0 -0
  24. {mrarbgrad-4.2.0 → mrarbgrad-4.4.0}/mrarbgrad_src/ext/traj/MrTraj_2D.h +0 -0
  25. {mrarbgrad-4.2.0 → mrarbgrad-4.4.0}/mrarbgrad_src/ext/traj/Rosette.h +0 -0
  26. {mrarbgrad-4.2.0 → mrarbgrad-4.4.0}/mrarbgrad_src/ext/traj/Seiffert.h +0 -0
  27. {mrarbgrad-4.2.0 → mrarbgrad-4.4.0}/mrarbgrad_src/ext/traj/Shell3d.h +0 -0
  28. {mrarbgrad-4.2.0 → mrarbgrad-4.4.0}/mrarbgrad_src/ext/traj/Spiral.h +0 -0
  29. {mrarbgrad-4.2.0 → mrarbgrad-4.4.0}/mrarbgrad_src/ext/traj/TrajFunc.h +0 -0
  30. {mrarbgrad-4.2.0 → mrarbgrad-4.4.0}/mrarbgrad_src/ext/traj/Yarnball.h +0 -0
  31. {mrarbgrad-4.2.0 → mrarbgrad-4.4.0}/mrarbgrad_src/ext/utility/Intp.h +0 -0
  32. {mrarbgrad-4.2.0 → mrarbgrad-4.4.0}/mrarbgrad_src/ext/utility/LinIntp.h +0 -0
  33. {mrarbgrad-4.2.0 → mrarbgrad-4.4.0}/mrarbgrad_src/ext/utility/SplineIntp.h +0 -0
  34. {mrarbgrad-4.2.0 → mrarbgrad-4.4.0}/mrarbgrad_src/ext/utility/global.cpp +0 -0
  35. {mrarbgrad-4.2.0 → mrarbgrad-4.4.0}/mrarbgrad_src/ext/utility/global.h +0 -0
  36. {mrarbgrad-4.2.0 → mrarbgrad-4.4.0}/mrarbgrad_src/ext/utility/v3.cpp +0 -0
  37. {mrarbgrad-4.2.0 → mrarbgrad-4.4.0}/mrarbgrad_src/ext/utility/v3.h +0 -0
  38. {mrarbgrad-4.2.0 → mrarbgrad-4.4.0}/mrarbgrad_src/trajfunc/__init__.py +0 -0
  39. {mrarbgrad-4.2.0 → mrarbgrad-4.4.0}/mrarbgrad_src/trajfunc/main.py +0 -0
  40. {mrarbgrad-4.2.0 → mrarbgrad-4.4.0}/setup.cfg +0 -0
  41. {mrarbgrad-4.2.0 → mrarbgrad-4.4.0}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: MRArbGrad
3
- Version: 4.2.0
3
+ Version: 4.4.0
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: MRArbGrad
3
- Version: 4.2.0
3
+ Version: 4.4.0
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,12 +1,93 @@
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)
7
8
 
8
- getGoldrat = lambda: goldrat
9
- getGoldang = lambda: goldang
9
+ def getGoldrat()->float: return goldrat
10
+ def getGoldang()->float: return goldang
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
10
91
 
11
92
  def gradClip(lstArrGrad:list[NDArray]|NDArray, dt:float, sLim:float, gLim:float) -> list[NDArray]|NDArray:
12
93
  """
@@ -55,71 +136,79 @@ def rand3d(i:int|NDArray, nAx:int=3, kx=sqrt(2), ky=sqrt(3), kz=sqrt(7)) -> NDAr
55
136
  (i**2 * 1/(1+ky))%1,
56
137
  (i**3 * 1/(1+kz))%1
57
138
  ][:nAx]).T
139
+
140
+ def genPermTab(n:int) -> list[int]:
141
+ inc = around(n*(goldrat-1)).astype(int64)
142
+ while gcd(inc,n)!=1: inc-=1
143
+ lstIdx = []
144
+ for i in range(n):
145
+ lstIdx.append(i*inc%n)
146
+ return lstIdx
58
147
 
59
- 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]:
60
149
  """
61
150
  # description:
62
151
  interpolate gradient waveform and calculate trajectory
63
152
 
64
153
  # parameter
65
- `arrG`: array of gradient waveform
66
- `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
67
156
 
68
157
  # return:
69
158
  interpolated trajectory and gradient
70
159
  """
71
- dtShift = nShift*dtADC
72
- nGrad, nDim = arrG.shape
73
- nADC = int(dtGrad/dtADC)*(nGrad-1)
74
- arrG_Resamp = zeros([nADC,nDim], dtype=float64)
75
- for iDim in range(nDim):
76
- arrG_Resamp[:,iDim] = interp(dtADC*arange(nADC)+dtShift, dtGrad*arange(nGrad), arrG[:,iDim])
77
- arrDk = zeros_like(arrG_Resamp)
78
- arrDk[0,:] = (arrG[0,:] + arrG_Resamp[0,:])*dtShift/2
79
- 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
80
169
  arrK = cumsum(arrDk,axis=0)
81
- return arrK, arrG_Resamp
170
+ return arrK, arrGrad_Resamp
82
171
 
83
- def delGrad(arrG:NDArray, tau:int|float) -> NDArray:
172
+ def delGrad(arrGrad:NDArray, tau:int|float) -> NDArray:
84
173
  """
85
174
  # description:
86
175
  delay the input gradient waveform by time constant tau
87
176
 
88
177
  # parameter
89
- `arrG`: array of single gradient waveform
178
+ `arrGrad`: array of single gradient waveform
90
179
  `tau`: time constant in RL circuit transfer function
91
180
 
92
181
  # return:
93
182
  delayed gradient waveform
94
183
  """
95
- assert arrG.ndim == 2, "only single gradient waveform is supported."
96
- if tau == 0: return arrG.copy() # avoid divided-by-0 later
97
- 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
98
187
 
99
188
  # perform oversample to get better impluse response profile
100
189
  ov = clip(10/tau, 1, 1e3).astype(int64) # the smaller the ov, the bigger oversampling is needed
101
- arrG_ov = zeros([nPt*ov,nAx], dtype=arrG.dtype)
190
+ arrGrad_ov = zeros([nPt*ov,nAx], dtype=arrGrad.dtype)
102
191
  for iAx in range(nAx):
103
- 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
104
193
  nPt *= ov
105
194
  tau *= ov
106
195
 
107
196
  # derive impluse response of RL circuit
108
- arrG_Pad = zeros_like(arrG_ov)
109
- arrG_Pad[:nPt//2,:] = arrG_ov[-1:,:]
110
- arrG_Pad[nPt//2:,:] = arrG_ov[:1,:]
111
- 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)
112
201
  arrT = linspace(0,2*nPt,2*nPt,0) + 0.5
113
202
  arrImpResRL = (1/tau)*exp(-arrT/tau)
114
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)")
115
204
 
116
205
  # perform convolution between input waveform and impulse response
117
- 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
118
207
 
119
208
  # de-oversample
120
- arrG = arrG_ov[:nPt:ov,:]
209
+ arrGrad = arrGrad_ov[:nPt:ov,:]
121
210
 
122
- return arrG
211
+ return arrGrad
123
212
 
124
213
  def rotate(arr:NDArray, ang:float64, axis:int64) -> NDArray:
125
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
4
+ from .Utility import _calDiaphony, rotate, _calJacElip, _calCompElipInt, _calSphFibPt, cvtGrad2Traj, getGoldang, getGoldrat, rand3d, gradClip, genPermTab
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
 
@@ -14,7 +14,10 @@ public:
14
14
  m_phi0 = phi0;
15
15
 
16
16
  m_p0 = 0e0;
17
- m_p1 = (std::log(m_kRhoPhi1)-std::log(m_kRhoPhi0)) / (2e0*(m_kRhoPhi1-m_kRhoPhi0));
17
+ if (m_kRhoPhi0!=m_kRhoPhi1)
18
+ { m_p1 = (std::log(kRhoPhi1)-std::log(kRhoPhi0)) / (2e0*(kRhoPhi1-kRhoPhi0)); }
19
+ else
20
+ { m_p1 = 0.5e0/kRhoPhi0; }
18
21
  }
19
22
 
20
23
  virtual bool getK(v3* k, f64 p)
@@ -22,7 +25,11 @@ public:
22
25
  if (k==NULL) return false;
23
26
 
24
27
  f64& phi = p;
25
- f64 rho = m_kRhoPhi0*(std::exp(2e0*(m_kRhoPhi1 - m_kRhoPhi0)*phi) - 1e0) / (2e0*(m_kRhoPhi1 - m_kRhoPhi0));
28
+ f64 rho;
29
+ if (m_kRhoPhi0!=m_kRhoPhi1)
30
+ { rho = m_kRhoPhi0*(std::exp(2e0*(m_kRhoPhi1 - m_kRhoPhi0)*phi) - 1e0) / (2e0*(m_kRhoPhi1 - m_kRhoPhi0)); }
31
+ else
32
+ { rho = m_kRhoPhi0*phi; }
26
33
  k->x = rho * std::cos(phi + m_phi0);
27
34
  k->y = rho * std::sin(phi + m_phi0);
28
35
  k->z = 0e0;
@@ -39,19 +46,16 @@ protected:
39
46
  class VDSpiral: public MrTraj_2D
40
47
  {
41
48
  public:
42
- VDSpiral(const GeoPara& objGeoPara, const GradPara& objGradPara, i64 nStack, f64 kRhoPhi0, f64 dRhoPhi1):
49
+ VDSpiral(const GeoPara& objGeoPara, const GradPara& objGradPara, i64 nStack, f64 kRhoPhi0, f64 kRhoPhi1):
43
50
  MrTraj_2D(objGeoPara,objGradPara,0,0,0,0,v3(),vv3())
44
51
  {
45
- if (kRhoPhi0==dRhoPhi1) throw std::invalid_argument("kRhoPhi0==dRhoPhi1");
46
-
47
- m_ptfBaseTraj = new VDSpiral_TrajFunc(kRhoPhi0, dRhoPhi1);
52
+ m_ptfBaseTraj = new VDSpiral_TrajFunc(kRhoPhi0, kRhoPhi1);
48
53
  ASSERT(m_ptfBaseTraj!=NULL);
49
54
  m_nStack = nStack;
50
55
 
51
- i64 nRot = calNRot(std::max(kRhoPhi0, dRhoPhi1), m_objGeoPara.nPix);
56
+ i64 nRot = calNRot(std::max(kRhoPhi0, kRhoPhi1), m_objGeoPara.nPix);
52
57
  m_rotang = calRotAng(nRot);
53
58
  m_nAcq = nRot*m_nStack;
54
- PRINT(m_nAcq) // TEST
55
59
 
56
60
  calGrad(&m_v3BaseM0PE, &m_vv3BaseGRO, NULL, *m_ptfBaseTraj, m_objGradPara);
57
61
  m_nSampMax = m_vv3BaseGRO.size();
@@ -78,7 +82,7 @@ public:
78
82
  {
79
83
  m_kRhoPhi0 = kRhoPhi0;
80
84
  m_kRhoPhi1 = kRhoPhi1;
81
- m_nAcq = calNRot(kRhoPhi1, objGeoPara.nPix);
85
+ m_nAcq = calNRot(std::max(kRhoPhi0, kRhoPhi1), objGeoPara.nPix);
82
86
  m_dRotAng = 2e0*M_PI/m_nAcq;
83
87
  genPermTab(&m_vi64PermTab, m_nAcq);
84
88
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "MRArbGrad"
7
- version = "4.2.0"
7
+ version = "4.4.0"
8
8
  dependencies = ["numpy"]
9
9
 
10
10
  description = "Gradient waveform design tool for arbitrary k-space trajectories."
File without changes
File without changes
File without changes
File without changes
File without changes