make_extended_trapezoid_area.py 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. import math
  2. from types import SimpleNamespace
  3. from typing import Tuple
  4. import numpy as np
  5. from scipy.optimize import minimize
  6. from seqgen.pypulseq.make_extended_trapezoid import make_extended_trapezoid
  7. from seqgen.pypulseq.opts import Opts
  8. def make_extended_trapezoid_area(
  9. area: float, channel: str, grad_end: float, grad_start: float, system: Opts
  10. ) -> Tuple[SimpleNamespace, np.array, np.array]:
  11. """
  12. Makes the shortest possible extended trapezoid with a given area which starts and ends (optionally) as non-zero
  13. gradient values.
  14. Parameters
  15. ----------
  16. channel : str
  17. Orientation of extended trapezoidal gradient event. Must be one of 'x', 'y' or 'z'.
  18. grad_start : float
  19. Starting non-zero gradient value.
  20. grad_end : float
  21. Ending non-zero gradient value.
  22. area : float
  23. Area of extended trapezoid.
  24. system: Opts
  25. System limits.
  26. Returns
  27. -------
  28. grad : SimpleNamespace
  29. Extended trapezoid event.
  30. times : numpy.ndarray
  31. amplitude : numpy.ndarray
  32. """
  33. SR = system.max_slew * 0.99
  34. Tp = 0
  35. obj1 = (
  36. lambda x: (
  37. area - __testGA(x, 0, SR, system.grad_raster_time, grad_start, grad_end)
  38. )
  39. ** 2
  40. )
  41. arr_res = [
  42. minimize(fun=obj1, x0=-system.max_grad, method="Nelder-Mead"),
  43. minimize(fun=obj1, x0=0, method="Nelder-Mead"),
  44. minimize(fun=obj1, x0=system.max_grad, method="Nelder-Mead"),
  45. ]
  46. arr_res = np.array([(*res.x, res.fun) for res in arr_res])
  47. Gp, obj1val = arr_res[:, 0], arr_res[:, 1]
  48. i_min = np.argmin(obj1val)
  49. Gp = Gp[i_min]
  50. obj1val = obj1val[i_min]
  51. if obj1val > 1e-3 or np.abs(Gp) > system.max_grad: # Search did not converge
  52. Gp = system.max_grad * np.sign(Gp)
  53. obj2 = (
  54. lambda x: (
  55. area
  56. - __testGA(Gp, x, SR, system.grad_raster_time, grad_start, grad_end)
  57. )
  58. ** 2
  59. )
  60. res2 = minimize(fun=obj2, x0=0, method="Nelder-Mead")
  61. T, obj2val = *res2.x, res2.fun
  62. assert obj2val < 1e-2
  63. Tp = np.ceil(T / system.grad_raster_time) * system.grad_raster_time
  64. # Fix the ramps
  65. Tru = (
  66. np.ceil(np.abs(Gp - grad_start) / SR / system.grad_raster_time)
  67. * system.grad_raster_time
  68. )
  69. Trd = (
  70. np.ceil(np.abs(Gp - grad_end) / SR / system.grad_raster_time)
  71. * system.grad_raster_time
  72. )
  73. obj3 = lambda x: (area - __testGA1(x, Tru, Tp, Trd, grad_start, grad_end)) ** 2
  74. res = minimize(fun=obj3, x0=Gp, method="Nelder-Mead")
  75. Gp, obj3val = *res.x, res.fun
  76. assert obj3val < 1e-3 # Did the final search converge?
  77. assert Tp >= 0
  78. if Tp > 0:
  79. times = np.cumsum([0, Tru, Tp, Trd])
  80. amplitudes = [grad_start, Gp, Gp, grad_end]
  81. else:
  82. Tru = (
  83. np.ceil(np.abs(Gp - grad_start) / SR / system.grad_raster_time)
  84. * system.grad_raster_time
  85. )
  86. Trd = (
  87. np.ceil(np.abs(Gp - grad_end) / SR / system.grad_raster_time)
  88. * system.grad_raster_time
  89. )
  90. if Trd > 0:
  91. if Tru > 0:
  92. times = np.cumsum([0, Tru, Trd])
  93. amplitudes = np.array([grad_start, Gp, grad_end])
  94. else:
  95. times = np.cumsum([0, Trd])
  96. amplitudes = np.array([grad_start, grad_end])
  97. else:
  98. times = np.cumsum([0, Tru])
  99. amplitudes = np.array([grad_start, grad_end])
  100. grad = make_extended_trapezoid(
  101. channel=channel, system=system, times=times, amplitudes=amplitudes
  102. )
  103. grad.area = __testGA1(Gp, Tru, Tp, Trd, grad_start, grad_end)
  104. assert np.abs(grad.area - area) < 1e-3
  105. return grad, times, amplitudes
  106. def __testGA(Gp, Tp, SR, dT, Gs, Ge):
  107. Tru = np.ceil(np.abs(Gp - Gs) / SR / dT) * dT
  108. Trd = np.ceil(np.abs(Gp - Ge) / SR / dT) * dT
  109. ga = __testGA1(Gp, Tru, Tp, Trd, Gs, Ge)
  110. return ga
  111. def __testGA1(Gp, Tru, Tp, Trd, Gs, Ge):
  112. return 0.5 * Tru * (Gp + Gs) + Gp * Tp + 0.5 * (Gp + Ge) * Trd