calc_ramp.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  1. from typing import Tuple
  2. import numpy as np
  3. from seqgen.pypulseq.opts import Opts
  4. def calc_ramp(
  5. k0: np.ndarray,
  6. k_end: np.ndarray,
  7. max_grad: np.ndarray = np.zeros(0),
  8. max_points: int = 500,
  9. max_slew: np.ndarray = np.zeros(0),
  10. system: Opts = Opts(),
  11. ) -> Tuple[np.ndarray, bool]:
  12. """
  13. Join the points `k0` and `k_end` in three-dimensional k-space in minimal time, observing the gradient and slew
  14. limits (`max_grad` and `max_slew` respectively), and the gradient strength `G0` before `k0[:, 1]` and `Gend` after
  15. `k_end[:, 1]`. In the context of a fixed gradient dwell time this is a discrete problem with an a priori unknown
  16. number of discretization steps. Therefore this method tries out the optimization with 0 steps, then 1 step, and so
  17. on, until all conditions can be fulfilled, thus yielding a short connection.
  18. Parameters
  19. ----------
  20. k0 : numpy.ndarray
  21. Two preceding points in k-space. Shape is `[3, 2]`. From these points, the starting gradient will be calculated.
  22. k_end : numpy.ndarray
  23. Two following points in k-space. Shape is `[3, 2]`. From these points, the target gradient will be calculated.
  24. max_grad : float or array_like, default=0
  25. Maximum total gradient strength. Either a single value or one value for each coordinate, of shape `[3, 1]`.
  26. max_points : int, default=500
  27. Maximum number of k-space points to be used in connecting `k0` and `k_end`.
  28. max_slew : float or array_like, default=0
  29. Maximum total slew rate. Either a single value or one value for each coordinate, of shape `[3, 1]`.
  30. system : Opts, default=Opts()
  31. System limits.
  32. Returns
  33. -------
  34. k_out : numpy.ndarray
  35. Connected k-space trajectory.
  36. success : bool
  37. Boolean flag indicating if `k0` and `k_end` were successfully joined.
  38. """
  39. def __inside_limits(grad, slew):
  40. if mode == 0:
  41. grad2 = np.sum(np.square(grad), axis=1)
  42. slew2 = np.sum(np.square(slew), axis=1)
  43. ok = np.all(np.max(grad2) <= np.square(max_grad)) and np.all(
  44. np.max(slew2) <= np.square(max_slew)
  45. )
  46. else:
  47. ok = (np.sum(np.max(np.abs(grad), axis=1) <= max_grad) == 3) and (
  48. np.sum(np.max(np.abs(slew), axis=1) <= max_slew) == 3
  49. )
  50. return ok
  51. def __joinleft0(k0, k_end, use_points, G0, G_end):
  52. if use_points == 0:
  53. G = np.stack((G0, (k_end - k0) / grad_raster, G_end)).T
  54. S = (G[:, 1:] - G[:, :-1]) / grad_raster
  55. k_out_left = np.zeros((3, 0))
  56. success = __inside_limits(G, S)
  57. return success, k_out_left
  58. dk = (k_end - k0) / (use_points + 1)
  59. kopt = k0 + dk
  60. Gopt = (kopt - k0) / grad_raster
  61. Sopt = (Gopt - G0) / grad_raster
  62. okGopt = np.sum(np.square(Gopt)) <= np.square(max_grad)
  63. okSopt = np.sum(np.square(Sopt)) <= np.square(max_slew)
  64. if okGopt and okSopt:
  65. k_left = kopt
  66. else:
  67. a = np.multiply(max_grad, grad_raster)
  68. b = np.multiply(max_slew, grad_raster**2)
  69. dkprol = G0 * grad_raster
  70. dkconn = dk - dkprol
  71. ksl = k0 + dkprol + dkconn / np.linalg.norm(dkconn) * b
  72. Gsl = (ksl - k0) / grad_raster
  73. okGsl = np.sum(np.square(Gsl)) <= np.square(max_grad)
  74. kgl = k0 + np.multiply(dk / np.linalg.norm(dk), a)
  75. Ggl = (kgl - k0) / grad_raster
  76. Sgl = (Ggl - G0) / grad_raster
  77. okSgl = np.sum(np.square(Sgl)) <= np.square(max_slew)
  78. if okGsl:
  79. k_left = ksl
  80. elif okSgl:
  81. k_left = kgl
  82. else:
  83. c = np.linalg.norm(dkprol)
  84. c1 = np.divide(np.square(a) - np.square(b) + np.square(c), (2 * c))
  85. h = np.sqrt(np.square(a) - np.square(c1))
  86. kglsl = k0 + np.multiply(c1, np.divide(dkprol, np.linalg.norm(dkprol)))
  87. projondkprol = (kgl * dkprol.T) * (dkprol / np.linalg.norm(dkprol))
  88. hdirection = kgl - projondkprol
  89. kglsl = kglsl + h * hdirection / np.linalg.norm(hdirection)
  90. k_left = kglsl
  91. success, k = __joinright0(
  92. k_left, k_end, (k_left - k0) / grad_raster, G_end, use_points - 1
  93. )
  94. if len(k) != 0:
  95. if len(k.shape) == 1:
  96. k = k.reshape((len(k), 1))
  97. if len(k_left.shape) == 1:
  98. k_left = k_left.reshape((len(k_left), 1))
  99. k_out_left = np.hstack((k_left, k))
  100. else:
  101. k_out_left = k_left
  102. return success, k_out_left
  103. def __joinleft1(k0, k_end, use_points, G0, G_end):
  104. if use_points == 0:
  105. G = np.stack((G0, (k_end - k0) / grad_raster, G_end))
  106. S = (G[:, 1:] - G[:, :-1]) / grad_raster
  107. k_out_left = np.zeros((3, 0))
  108. success = __inside_limits(G, S)
  109. return success, k_out_left
  110. k_left = np.zeros(3)
  111. dk = (k_end - k0) / (use_points + 1)
  112. kopt = k0 + dk
  113. Gopt = (kopt - k0) / grad_raster
  114. Sopt = (Gopt - G0) / grad_raster
  115. okGopt = np.abs(Gopt) <= max_grad
  116. okSopt = np.abs(Sopt) <= max_slew
  117. dkprol = G0 * grad_raster
  118. dkconn = dk - dkprol
  119. ksl = k0 + dkprol + np.multiply(np.sign(dkconn), max_slew) * grad_raster**2
  120. Gsl = (ksl - k0) / grad_raster
  121. okGsl = np.abs(Gsl) <= max_grad
  122. kgl = k0 + np.multiply(np.sign(dk), max_grad) * grad_raster**2
  123. Ggl = (kgl - k0) / grad_raster
  124. Sgl = (Ggl - G0) / grad_raster
  125. okSgl = np.abs(Sgl) <= max_slew
  126. for ii in range(3):
  127. if okGopt[ii] == 1 and okSopt[ii] == 1:
  128. k_left[ii] = kopt[ii]
  129. elif okGsl[ii] == 1:
  130. k_left[ii] = ksl[ii]
  131. elif okSgl[ii] == 1:
  132. k_left[ii] = kgl[ii]
  133. else:
  134. print("Unknown error")
  135. success, k = __joinright1(
  136. k0=k_left,
  137. k_end=k_end,
  138. use_points=use_points - 1,
  139. G0=(k_left - k0) / grad_raster,
  140. G_end=G_end,
  141. )
  142. if len(k) != 0:
  143. if len(k.shape) == 1:
  144. k = k.reshape((len(k), 1))
  145. if len(k_left.shape) == 1:
  146. k_left = k_left.reshape((len(k_left), 1))
  147. k_out_left = np.hstack((k_left, k))
  148. else:
  149. k_out_left = k_left
  150. return success, k_out_left
  151. def __joinright0(k0, k_end, use_points, G0, G_end):
  152. if use_points == 0:
  153. G = np.stack((G0, (k_end - k0) / grad_raster, G_end)).T
  154. S = (G[:, 1:] - G[:, :-1]) / grad_raster
  155. k_out_right = np.zeros((3, 0))
  156. success = __inside_limits(G, S)
  157. return success, k_out_right
  158. dk = (k0 - k_end) / (use_points + 1)
  159. kopt = k_end + dk
  160. Gopt = (k_end - kopt) / grad_raster
  161. Sopt = (G_end - Gopt) / grad_raster
  162. okGopt = np.sum(np.square(Gopt)) <= np.square(max_grad)
  163. okSopt = np.sum(np.square(Sopt)) <= np.square(max_slew)
  164. if okGopt and okSopt:
  165. k_right = kopt
  166. else:
  167. a = np.multiply(max_grad, grad_raster)
  168. b = np.multiply(max_slew, grad_raster**2)
  169. dkprol = -G_end * grad_raster
  170. dkconn = dk - dkprol
  171. ksl = k_end + dkprol + dkconn / np.linalg.norm(dkconn) * b
  172. Gsl = (k_end - ksl) / grad_raster
  173. okGsl = np.sum(np.square(Gsl)) <= np.square(max_grad)
  174. kgl = k_end + np.multiply(dk / np.linalg.norm(dk), a)
  175. Ggl = (k_end - kgl) / grad_raster
  176. Sgl = (G_end - Ggl) / grad_raster
  177. okSgl = np.sum(np.square(Sgl)) <= np.square(max_slew)
  178. if okGsl:
  179. k_right = ksl
  180. elif okSgl:
  181. k_right = kgl
  182. else:
  183. c = np.linalg.norm(dkprol)
  184. c1 = np.divide(np.square(a) - np.square(b) + np.square(c), (2 * c))
  185. h = np.sqrt(np.square(a) - np.square(c1))
  186. kglsl = k_end + np.multiply(
  187. c1, np.divide(dkprol, np.linalg.norm(dkprol))
  188. )
  189. projondkprol = (kgl * dkprol.T) * (dkprol / np.linalg.norm(dkprol))
  190. hdirection = kgl - projondkprol
  191. kglsl = kglsl + h * hdirection / np.linalg.norm(hdirection)
  192. k_right = kglsl
  193. success, k = __joinleft0(
  194. k0=k0,
  195. k_end=k_right,
  196. G0=G0,
  197. G_end=(k_end - k_right) / grad_raster,
  198. use_points=use_points - 1,
  199. )
  200. if len(k) != 0:
  201. if len(k.shape) == 1:
  202. k = k.reshape((len(k), 1))
  203. if len(k_right.shape) == 1:
  204. k_right = k_right.reshape((len(k_right), 1))
  205. k_out_right = np.hstack((k, k_right))
  206. else:
  207. k_out_right = k_right
  208. return success, k_out_right
  209. def __joinright1(k0, k_end, use_points, G0, G_end):
  210. if use_points == 0:
  211. G = np.stack((G0, (k_end - k0) / grad_raster, G_end))
  212. S = (G[:, 1:] - G[:, :-1]) / grad_raster
  213. k_out_right = np.zeros((3, 0))
  214. success = __inside_limits(G, S)
  215. return success, k_out_right
  216. k_right = np.zeros(3)
  217. dk = (k0 - k_end) / (use_points + 1)
  218. kopt = k_end + dk
  219. Gopt = (k_end - kopt) / grad_raster
  220. Sopt = (G_end - Gopt) / grad_raster
  221. okGopt = np.abs(Gopt) <= max_grad
  222. okSopt = np.abs(Sopt) <= max_slew
  223. dkprol = -G_end * grad_raster
  224. dkconn = dk - dkprol
  225. ksl = k_end + dkprol + np.multiply(np.sign(dkconn), max_slew) * grad_raster**2
  226. Gsl = (k_end - ksl) / grad_raster
  227. okGsl = np.abs(Gsl) <= max_grad
  228. kgl = k_end + np.multiply(np.sign(dk), max_grad) * grad_raster
  229. Ggl = (k_end - kgl) / grad_raster
  230. Sgl = (G_end - Ggl) / grad_raster
  231. okSgl = np.abs(Sgl) <= max_slew
  232. for ii in range(3):
  233. if okGopt[ii] == 1 and okSopt[ii] == 1:
  234. k_right[ii] = kopt[ii]
  235. elif okGsl[ii] == 1:
  236. k_right[ii] = ksl[ii]
  237. elif okSgl[ii] == 1:
  238. k_right[ii] = kgl[ii]
  239. else:
  240. print("Unknown error")
  241. success, k = __joinleft1(
  242. k0=k0,
  243. k_end=k_right,
  244. use_points=use_points - 1,
  245. G0=G0,
  246. G_end=(k_end - k_right) / grad_raster,
  247. )
  248. if len(k) != 0:
  249. if len(k.shape) == 1:
  250. k = k.reshape((len(k), 1))
  251. if len(k_right.shape) == 1:
  252. k_right = k_right.reshape((len(k_right), 1))
  253. k_out_right = np.hstack((k, k_right))
  254. else:
  255. k_out_right = k_right
  256. return success, k_out_right
  257. # =========
  258. # MAIN FUNCTION
  259. # =========
  260. if np.all(np.where(max_grad <= 0)):
  261. max_grad = [system.max_grad]
  262. if np.all(np.where(max_slew <= 0)):
  263. max_slew = [system.max_slew]
  264. grad_raster = system.grad_raster_time
  265. if len(max_grad) == 1 and len(max_slew) == 1:
  266. mode = 0
  267. elif len(max_grad) == 3 and len(max_slew) == 3:
  268. mode = 1
  269. else:
  270. raise ValueError("Input value max grad or max slew in invalid format.")
  271. G0 = (k0[:, 1] - k0[:, 0]) / grad_raster
  272. G_end = (k_end[:, 1] - k_end[:, 0]) / grad_raster
  273. k0 = k0[:, 1]
  274. k_end = k_end[:, 0]
  275. success = 0
  276. k_out = np.zeros((3, 0))
  277. use_points = 0
  278. while success == 0 and use_points <= max_points:
  279. if mode == 0:
  280. if np.linalg.norm(G0) > max_grad or np.linalg.norm(G_end) > max_grad:
  281. break
  282. success, k_out = __joinleft0(
  283. k0=k0, k_end=k_end, G0=G0, G_end=G_end, use_points=use_points
  284. )
  285. else:
  286. if np.abs(G0) > np.abs(max_grad) or np.abs(G_end) > np.abs(max_grad):
  287. break
  288. success, k_out = __joinleft1(
  289. k0=k0, k_end=k_end, use_points=use_points, G0=G0, G_end=G_end
  290. )
  291. use_points += 1
  292. return k_out, success