make_sinc_pulse.py 5.6 KB

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