make_arbitrary_rf.py 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. from types import SimpleNamespace
  2. from typing import Tuple, Union
  3. import numpy as np
  4. from seqgen.pypulseq import make_delay, calc_duration
  5. from seqgen.pypulseq.make_trapezoid import make_trapezoid
  6. from seqgen.pypulseq.make_delay import make_delay
  7. from seqgen.pypulseq.calc_duration import calc_duration
  8. from seqgen.pypulseq.opts import Opts
  9. from seqgen.pypulseq.supported_labels_rf_use import get_supported_rf_uses
  10. def make_arbitrary_rf(
  11. signal: np.ndarray,
  12. flip_angle: float,
  13. bandwidth: float = 0,
  14. delay: float = 0,
  15. dwell: float = 0,
  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 = 0,
  25. use: str = str(),
  26. ) -> Union[SimpleNamespace, Tuple[SimpleNamespace, SimpleNamespace]]:
  27. """
  28. Create an RF pulse with the given pulse shape.
  29. Parameters
  30. ----------
  31. signal : numpy.ndarray
  32. Arbitrary waveform.
  33. flip_angle : float
  34. Flip angle in radians.
  35. bandwidth : float, default=0
  36. Bandwidth in Hertz (Hz).
  37. delay : float, default=0
  38. Delay in seconds (s) of accompanying slice select trapezoidal event.
  39. freq_offset : float, default=0
  40. Frequency offset in Hertz (Hz).
  41. max_grad : float, default=system.max_grad
  42. Maximum gradient strength of accompanying slice select trapezoidal event.
  43. max_slew : float, default=system.max_slew
  44. Maximum slew rate of accompanying slice select trapezoidal event.
  45. phase_offset : float, default=0
  46. Phase offset in Hertz (Hz).a
  47. return_delay : bool, default=False
  48. Boolean flag to indicate if delay has to be returned.
  49. return_gz : bool, default=False
  50. Boolean flag to indicate if slice-selective gradient has to be returned.
  51. slice_thickness : float, default=0
  52. Slice thickness of accompanying slice select trapezoidal event. The slice thickness determines the area of the
  53. slice select event.
  54. system : Opts, default=Opts()
  55. System limits.
  56. time_bw_product : float, default=4
  57. Time-bandwidth product.
  58. use : str, default=str()
  59. Use of arbitrary radio-frequency pulse event. Must be one of 'excitation', 'refocusing' or 'inversion'.
  60. Returns
  61. -------
  62. rf : SimpleNamespace
  63. Radio-frequency pulse event with arbitrary pulse shape.
  64. gz : SimpleNamespace, optional
  65. Slice select trapezoidal gradient event accompanying the arbitrary radio-frequency pulse event.
  66. Raises
  67. ------
  68. ValueError
  69. If invalid `use` parameter is passed. Must be one of 'excitation', 'refocusing' or 'inversion'.
  70. If `signal` with ndim > 1 is passed.
  71. If `return_gz=True`, and `slice_thickness` and `bandwidth` are not passed.
  72. """
  73. valid_use_pulses = get_supported_rf_uses()
  74. if use != "" and use not in valid_use_pulses:
  75. raise ValueError(
  76. f"Invalid use parameter. Must be one of 'excitation', 'refocusing' or 'inversion'. Passed: {use}"
  77. )
  78. if dwell == 0:
  79. dwell = system.rf_raster_time
  80. signal = np.squeeze(signal)
  81. if signal.ndim > 1:
  82. raise ValueError(f"signal should have ndim=1. Passed ndim={signal.ndim}")
  83. signal = signal / np.abs(np.sum(signal * dwell)) * flip_angle / (2 * np.pi)
  84. N = len(signal)
  85. duration = N * dwell
  86. t = (np.arange(1, N + 1) - 0.5) * dwell
  87. rf = SimpleNamespace()
  88. rf.type = "rf"
  89. rf.signal = signal
  90. rf.t = t
  91. rf.shape_dur = duration
  92. rf.freq_offset = freq_offset
  93. rf.phase_offset = phase_offset
  94. rf.dead_time = system.rf_dead_time
  95. rf.ringdown_time = system.rf_ringdown_time
  96. rf.delay = delay
  97. if use != "":
  98. rf.use = use
  99. if rf.dead_time > rf.delay:
  100. rf.delay = rf.dead_time
  101. if return_gz:
  102. if slice_thickness <= 0:
  103. raise ValueError("Slice thickness must be provided.")
  104. if bandwidth <= 0:
  105. raise ValueError("Bandwidth of pulse must be provided.")
  106. if max_grad > 0:
  107. system.max_grad = max_grad
  108. if max_slew > 0:
  109. system.max_slew = max_slew
  110. BW = bandwidth
  111. if time_bw_product > 0:
  112. BW = time_bw_product / duration
  113. amplitude = BW / slice_thickness
  114. area = amplitude * duration
  115. gz = make_trapezoid(
  116. channel="z", system=system, flat_time=duration, flat_area=area
  117. )
  118. if rf.delay > gz.rise_time:
  119. # Round-up to gradient raster
  120. gz.delay = (
  121. np.ceil((rf.delay - gz.rise_time) / system.grad_raster_time)
  122. * system.grad_raster_time
  123. )
  124. if rf.delay < (gz.rise_time + gz.delay):
  125. rf.delay = gz.rise_time + gz.delay
  126. if rf.ringdown_time > 0 and return_delay:
  127. delay = make_delay(calc_duration(rf) + rf.ringdown_time)
  128. if return_gz and return_delay:
  129. return rf, gz, delay
  130. elif return_gz:
  131. return rf, gz
  132. else:
  133. return rf