make_gauss_pulse.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. import math
  2. from types import SimpleNamespace
  3. from typing import Tuple, Union
  4. import numpy as np
  5. from seqgen.pypulseq.calc_duration import calc_duration
  6. from seqgen.pypulseq.make_delay import make_delay
  7. from seqgen.pypulseq.make_trapezoid import make_trapezoid
  8. from seqgen.pypulseq.opts import Opts
  9. def make_gauss_pulse(
  10. flip_angle: float,
  11. apodization: float = 0,
  12. bandwidth: float = 0,
  13. center_pos: float = 0.5,
  14. delay: float = 0,
  15. dwell: float = 0,
  16. duration: float = 4e-3,
  17. freq_offset: float = 0,
  18. max_grad: float = 0,
  19. max_slew: float = 0,
  20. phase_offset: float = 0,
  21. return_gz: bool = False,
  22. return_delay: bool = False,
  23. slice_thickness: float = 0,
  24. system: Opts = Opts(),
  25. time_bw_product: float = 4,
  26. use: str = str(),
  27. ) -> Union[
  28. SimpleNamespace,
  29. Tuple[SimpleNamespace, SimpleNamespace, SimpleNamespace],
  30. Tuple[SimpleNamespace, SimpleNamespace, SimpleNamespace, SimpleNamespace],
  31. ]:
  32. """
  33. Create a [optionally slice selective] Gauss pulse.
  34. See also `pypulseq.Sequence.sequence.Sequence.add_block()`.
  35. Parameters
  36. ----------
  37. flip_angle : float
  38. Flip angle in radians.
  39. apodization : float, default=0
  40. Apodization.
  41. bandwidth : float, default=0
  42. Bandwidth in Hertz (Hz).
  43. center_pos : float, default=0.5
  44. Position of peak.
  45. delay : float, default=0
  46. Delay in seconds (s).
  47. dwell : float, default=0
  48. duration : float, default=4e-3
  49. Duration in seconds (s).
  50. freq_offset : float, default=0
  51. Frequency offset in Hertz (Hz).
  52. max_grad : float, default=0
  53. Maximum gradient strength of accompanying slice select trapezoidal event.
  54. max_slew : float, default=0
  55. Maximum slew rate of accompanying slice select trapezoidal event.
  56. phase_offset : float, default=0
  57. Phase offset in Hertz (Hz).
  58. return_delay : bool, default=False
  59. Boolean flag to indicate if the delay event has to be returned.
  60. return_gz : bool, default=False
  61. Boolean flag to indicate if the slice-selective gradient has to be returned.
  62. slice_thickness : float, default=0
  63. Slice thickness of accompanying slice select trapezoidal event. The slice thickness determines the area of the
  64. slice select event.
  65. system : Opts, default=Opts()
  66. System limits.
  67. time_bw_product : int, default=4
  68. Time-bandwidth product.
  69. use : str, default=str()
  70. Use of radio-frequency gauss pulse event. Must be one of 'excitation', 'refocusing' or 'inversion'.
  71. Returns
  72. -------
  73. rf : SimpleNamespace
  74. Radio-frequency gauss pulse event.
  75. gz : SimpleNamespace, optional
  76. Accompanying slice select trapezoidal gradient event.
  77. gzr : SimpleNamespace, optional
  78. Accompanying slice select rephasing trapezoidal gradient event.
  79. delay : SimpleNamespace, optional
  80. Delay event.
  81. Raises
  82. ------
  83. ValueError
  84. If invalid `use` is passed. Must be one of 'excitation', 'refocusing' or 'inversion'.
  85. If `return_gz=True` and `slice_thickness` was not passed.
  86. """
  87. valid_use_pulses = ["excitation", "refocusing", "inversion"]
  88. if use != "" and use not in valid_use_pulses:
  89. raise ValueError(
  90. f"Invalid use parameter. Must be one of 'excitation', 'refocusing' or 'inversion'. Passed: {use}"
  91. )
  92. if dwell == 0:
  93. dwell = system.rf_raster_time
  94. if bandwidth == 0:
  95. BW = time_bw_product / duration
  96. else:
  97. BW = bandwidth
  98. alpha = apodization
  99. N = int(np.round(duration / dwell))
  100. t = (np.arange(1, N + 1) - 0.5) * dwell
  101. tt = t - (duration * center_pos)
  102. window = 1 - alpha + alpha * np.cos(2 * np.pi * tt / duration)
  103. signal = window * __gauss(BW * tt)
  104. flip = np.sum(signal) * dwell * 2 * np.pi
  105. signal = signal * flip_angle / flip
  106. rf = SimpleNamespace()
  107. rf.type = "rf"
  108. rf.signal = signal
  109. rf.t = t
  110. rf.shape_dur = N * dwell
  111. rf.freq_offset = freq_offset
  112. rf.phase_offset = phase_offset
  113. rf.dead_time = system.rf_dead_time
  114. rf.ringdown_time = system.rf_ringdown_time
  115. rf.delay = delay
  116. if use != "":
  117. rf.use = use
  118. if rf.dead_time > rf.delay:
  119. rf.delay = rf.dead_time
  120. if return_gz:
  121. if slice_thickness == 0:
  122. raise ValueError("Slice thickness must be provided")
  123. if max_grad > 0:
  124. system.max_grad = max_grad
  125. if max_slew > 0:
  126. system.max_slew = max_slew
  127. amplitude = BW / slice_thickness
  128. area = amplitude * duration
  129. gz = make_trapezoid(
  130. channel="z", system=system, flat_time=duration, flat_area=area
  131. )
  132. gzr = make_trapezoid(
  133. channel="z",
  134. system=system,
  135. area=-area * (1 - center_pos) - 0.5 * (gz.area - area),
  136. )
  137. if rf.delay > gz.rise_time:
  138. gz.delay = (
  139. np.ceil((rf.delay - gz.rise_time) / system.grad_raster_time)
  140. * system.grad_raster_time
  141. )
  142. if rf.delay < (gz.rise_time + gz.delay):
  143. rf.delay = gz.rise_time + gz.delay
  144. if rf.ringdown_time > 0 and return_delay:
  145. delay = make_delay(calc_duration(rf) + rf.ringdown_time)
  146. # Following 2 lines of code are workarounds for numpy returning 3.14... for np.angle(-0.00...)
  147. negative_zero_indices = np.where(rf.signal == -0.0)
  148. rf.signal[negative_zero_indices] = 0
  149. if return_gz and return_delay:
  150. return rf, gz, gzr, delay
  151. elif return_gz:
  152. return rf, gz, gzr
  153. else:
  154. return rf
  155. def __gauss(x: np.ndarray) -> np.ndarray:
  156. return np.exp(-np.pi * np.square(x))