write_haste.py 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  1. import math
  2. import warnings
  3. import numpy as np
  4. from pypulseq.Sequence.sequence import Sequence
  5. from pypulseq.calc_rf_center import calc_rf_center
  6. from pypulseq.make_adc import make_adc
  7. from pypulseq.make_delay import make_delay
  8. from pypulseq.make_extended_trapezoid import make_extended_trapezoid
  9. from pypulseq.make_sinc_pulse import make_sinc_pulse
  10. from pypulseq.make_trapezoid import make_trapezoid
  11. from pypulseq.opts import Opts
  12. def main(plot: bool, write_seq: bool, seq_filename: str = "haste_pypulseq.seq"):
  13. # ======
  14. # SETUP
  15. # ======
  16. dG = 250e-6
  17. # Set system limits
  18. system = Opts(
  19. max_grad=30,
  20. grad_unit="mT/m",
  21. max_slew=170,
  22. slew_unit="T/m/s",
  23. rf_ringdown_time=100e-6,
  24. rf_dead_time=100e-6,
  25. adc_dead_time=10e-6,
  26. )
  27. seq = Sequence(system=system) # Create a new sequence object
  28. fov = 256e-3 # Define FOV and resolution
  29. Ny_pre = 8
  30. Nx, Ny = 128, 128
  31. n_echo = int(Ny / 2 + Ny_pre) # Number of echoes
  32. n_slices = 1
  33. rf_flip = 180 # Flip angle
  34. if isinstance(rf_flip, int):
  35. rf_flip = np.zeros(n_echo) + rf_flip
  36. slice_thickness = 5e-3 # Slice thickness
  37. TE = 12e-3 # Echo time
  38. TR = 2000e-3 # Repetition time
  39. sampling_time = 6.4e-3
  40. readout_time = sampling_time + 2 * system.adc_dead_time
  41. t_ex = 2.5e-3
  42. t_ex_wd = t_ex + system.rf_ringdown_time + system.rf_dead_time
  43. t_ref = 2e-3
  44. tf_ref_wd = t_ref + system.rf_ringdown_time + system.rf_dead_time
  45. t_sp = 0.5 * (TE - readout_time - tf_ref_wd)
  46. t_sp_ex = 0.5 * (TE - t_ex_wd - tf_ref_wd)
  47. fspR = 1.0
  48. fspS = 0.5
  49. rfex_phase = math.pi / 2
  50. rfref_phase = 0
  51. # ======
  52. # CREATE EVENTS
  53. # ======
  54. # Create 90 degree slice selection pulse and gradient
  55. flipex = 90 * math.pi / 180
  56. rfex, gz, _ = make_sinc_pulse(
  57. flip_angle=flipex,
  58. system=system,
  59. duration=t_ex,
  60. slice_thickness=slice_thickness,
  61. apodization=0.5,
  62. time_bw_product=4,
  63. phase_offset=rfex_phase,
  64. return_gz=True,
  65. )
  66. GS_ex = make_trapezoid(
  67. channel="z",
  68. system=system,
  69. amplitude=gz.amplitude,
  70. flat_time=t_ex_wd,
  71. rise_time=dG,
  72. )
  73. flipref = rf_flip[0] * math.pi / 180
  74. rfref, gz, _ = make_sinc_pulse(
  75. flip_angle=flipref,
  76. system=system,
  77. duration=t_ref,
  78. slice_thickness=slice_thickness,
  79. apodization=0.5,
  80. time_bw_product=4,
  81. phase_offset=rfref_phase,
  82. use="refocusing",
  83. return_gz=True,
  84. )
  85. GS_ref = make_trapezoid(
  86. channel="z",
  87. system=system,
  88. amplitude=GS_ex.amplitude,
  89. flat_time=tf_ref_wd,
  90. rise_time=dG,
  91. )
  92. AGS_ex = GS_ex.area / 2
  93. GS_spr = make_trapezoid(
  94. channel="z",
  95. system=system,
  96. area=AGS_ex * (1 + fspS),
  97. duration=t_sp,
  98. rise_time=dG,
  99. )
  100. GS_spex = make_trapezoid(
  101. channel="z", system=system, area=AGS_ex * fspS, duration=t_sp_ex, rise_time=dG
  102. )
  103. delta_k = 1 / fov
  104. k_width = Nx * delta_k
  105. GR_acq = make_trapezoid(
  106. channel="x",
  107. system=system,
  108. flat_area=k_width,
  109. flat_time=readout_time,
  110. rise_time=dG,
  111. )
  112. adc = make_adc(num_samples=Nx, duration=sampling_time, delay=system.adc_dead_time)
  113. GR_spr = make_trapezoid(
  114. channel="x", system=system, area=GR_acq.area * fspR, duration=t_sp, rise_time=dG
  115. )
  116. GR_spex = make_trapezoid(
  117. channel="x",
  118. system=system,
  119. area=GR_acq.area * (1 + fspR),
  120. duration=t_sp_ex,
  121. rise_time=dG,
  122. )
  123. AGR_spr = GR_spr.area
  124. AGR_preph = GR_acq.area / 2 + AGR_spr
  125. GR_preph = make_trapezoid(
  126. channel="x", system=system, area=AGR_preph, duration=t_sp_ex, rise_time=dG
  127. )
  128. n_ex = 1
  129. PE_order = np.arange(-Ny_pre, Ny + 1).T
  130. phase_areas = PE_order * delta_k
  131. # Split gradients and recombine into blocks
  132. GS1_times = np.array([0, GS_ex.rise_time])
  133. GS1_amp = np.array([0, GS_ex.amplitude])
  134. GS1 = make_extended_trapezoid(channel="z", times=GS1_times, amplitudes=GS1_amp)
  135. GS2_times = np.array([0, GS_ex.flat_time])
  136. GS2_amp = np.array([GS_ex.amplitude, GS_ex.amplitude])
  137. GS2 = make_extended_trapezoid(channel="z", times=GS2_times, amplitudes=GS2_amp)
  138. GS3_times = np.array(
  139. [
  140. 0,
  141. GS_spex.rise_time,
  142. GS_spex.rise_time + GS_spex.flat_time,
  143. GS_spex.rise_time + GS_spex.flat_time + GS_spex.fall_time,
  144. ]
  145. )
  146. GS3_amp = np.array(
  147. [GS_ex.amplitude, GS_spex.amplitude, GS_spex.amplitude, GS_ref.amplitude]
  148. )
  149. GS3 = make_extended_trapezoid(channel="z", times=GS3_times, amplitudes=GS3_amp)
  150. GS4_times = np.array([0, GS_ref.flat_time])
  151. GS4_amp = np.array([GS_ref.amplitude, GS_ref.amplitude])
  152. GS4 = make_extended_trapezoid(channel="z", times=GS4_times, amplitudes=GS4_amp)
  153. GS5_times = np.array(
  154. [
  155. 0,
  156. GS_spr.rise_time,
  157. GS_spr.rise_time + GS_spr.flat_time,
  158. GS_spr.rise_time + GS_spr.flat_time + GS_spr.fall_time,
  159. ]
  160. )
  161. GS5_amp = np.array([GS_ref.amplitude, GS_spr.amplitude, GS_spr.amplitude, 0])
  162. GS5 = make_extended_trapezoid(channel="z", times=GS5_times, amplitudes=GS5_amp)
  163. GS7_times = np.array(
  164. [
  165. 0,
  166. GS_spr.rise_time,
  167. GS_spr.rise_time + GS_spr.flat_time,
  168. GS_spr.rise_time + GS_spr.flat_time + GS_spr.fall_time,
  169. ]
  170. )
  171. GS7_amp = np.array([0, GS_spr.amplitude, GS_spr.amplitude, GS_ref.amplitude])
  172. GS7 = make_extended_trapezoid(channel="z", times=GS7_times, amplitudes=GS7_amp)
  173. # Readout gradient
  174. GR3 = GR_preph
  175. GR5_times = np.array(
  176. [
  177. 0,
  178. GR_spr.rise_time,
  179. GR_spr.rise_time + GR_spr.flat_time,
  180. GR_spr.rise_time + GR_spr.flat_time + GR_spr.fall_time,
  181. ]
  182. )
  183. GR5_amp = np.array([0, GR_spr.amplitude, GR_spr.amplitude, GR_acq.amplitude])
  184. GR5 = make_extended_trapezoid(channel="x", times=GR5_times, amplitudes=GR5_amp)
  185. GR6_times = np.array([0, readout_time])
  186. GR6_amp = np.array([GR_acq.amplitude, GR_acq.amplitude])
  187. GR6 = make_extended_trapezoid(channel="x", times=GR6_times, amplitudes=GR6_amp)
  188. GR7_times = np.array(
  189. [
  190. 0,
  191. GR_spr.rise_time,
  192. GR_spr.rise_time + GR_spr.flat_time,
  193. GR_spr.rise_time + GR_spr.flat_time + GR_spr.fall_time,
  194. ]
  195. )
  196. GR7_amp = np.array([GR_acq.amplitude, GR_spr.amplitude, GR_spr.amplitude, 0])
  197. GR7 = make_extended_trapezoid(channel="x", times=GR7_times, amplitudes=GR7_amp)
  198. # Fill-times
  199. tex = GS1.shape_dur + GS2.shape_dur + GS3.shape_dur
  200. tref = GS4.shape_dur + GS5.shape_dur + GS7.shape_dur + readout_time
  201. tend = GS4.shape_dur + GS5.shape_dur
  202. TE_train = tex + n_echo * tref + tend
  203. TR_fill = (TR - n_slices * TE_train) / n_slices # Round to gradient raster
  204. TR_fill = system.grad_raster_time * round(TR_fill / system.grad_raster_time)
  205. if TR_fill < 0:
  206. TR_fill = 1e-3
  207. warnings.warn(
  208. f"TR too short, adapted to include all slices to: {1000 * n_slices * (TE_train + TR_fill)} ms"
  209. )
  210. else:
  211. print(f"TR fill: {1000 * TR_fill} ms")
  212. delay_TR = make_delay(TR_fill)
  213. delay_end = make_delay(5)
  214. # ======
  215. # CONSTRUCT SEQUENCE
  216. # ======
  217. # Define sequence blocks
  218. for k_ex in range(n_ex):
  219. for s in range(n_slices):
  220. rfex.freq_offset = (
  221. GS_ex.amplitude * slice_thickness * (s - (n_slices - 1) / 2)
  222. )
  223. rfref.freq_offset = (
  224. GS_ref.amplitude * slice_thickness * (s - (n_slices - 1) / 2)
  225. )
  226. # Align the phase for off-center slices
  227. rfex.phase_offset = (
  228. rfex_phase - 2 * math.pi * rfex.freq_offset * calc_rf_center(rfex)[0]
  229. )
  230. rfref.phase_offset = (
  231. rfref_phase - 2 * math.pi * rfref.freq_offset * calc_rf_center(rfref)[0]
  232. )
  233. seq.add_block(GS1)
  234. seq.add_block(GS2, rfex)
  235. seq.add_block(GS3, GR3)
  236. for k_ech in range(n_echo):
  237. if k_ex >= 0:
  238. phase_area = phase_areas[k_ech]
  239. else:
  240. phase_area = 0
  241. GP_pre = make_trapezoid(
  242. channel="y",
  243. system=system,
  244. area=phase_area,
  245. duration=t_sp,
  246. rise_time=dG,
  247. )
  248. GP_rew = make_trapezoid(
  249. channel="y",
  250. system=system,
  251. area=-phase_area,
  252. duration=t_sp,
  253. rise_time=dG,
  254. )
  255. seq.add_block(GS4, rfref)
  256. seq.add_block(GS5, GR5, GP_pre)
  257. if k_ex >= 0:
  258. seq.add_block(GR6, adc)
  259. else:
  260. seq.add_block(GR6)
  261. seq.add_block(GS7, GR7, GP_rew)
  262. seq.add_block(GS4)
  263. seq.add_block(GS5)
  264. seq.add_block(delay_TR)
  265. seq.add_block(delay_end)
  266. # Check whether the timing of the sequence is correct
  267. ok, error_report = seq.check_timing()
  268. if ok:
  269. print("Timing check passed successfully")
  270. else:
  271. print("Timing check failed. Error listing follows:")
  272. [print(e) for e in error_report]
  273. # ======
  274. # VISUALIZATION
  275. # ======
  276. if plot:
  277. seq.plot()
  278. # =========
  279. # WRITE .SEQ
  280. # =========
  281. if write_seq:
  282. seq.write(seq_filename)
  283. # SETUPeq")
  284. if __name__ == "__main__":
  285. main(plot=True, write_seq=True)