write_tse.py 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322
  1. import math
  2. import warnings
  3. import numpy as np
  4. import pypulseq as pp
  5. def main(plot: bool, write_seq: bool, seq_filename: str = "tse_pypulseq.seq"):
  6. # ======
  7. # SETUP
  8. # ======
  9. dG = 250e-6
  10. # Set system limits
  11. system = pp.Opts(
  12. max_grad=32,
  13. grad_unit="mT/m",
  14. max_slew=130,
  15. slew_unit="T/m/s",
  16. rf_ringdown_time=100e-6,
  17. rf_dead_time=100e-6,
  18. adc_dead_time=10e-6,
  19. )
  20. seq = pp.Sequence(system) # Create a new sequence object
  21. fov = 256e-3 # Define FOV and resolution
  22. Nx, Ny = 128, 128
  23. n_echo = 16 # Number of echoes
  24. n_slices = 1
  25. rf_flip = 180 # Flip angle
  26. if isinstance(rf_flip, int):
  27. rf_flip = np.zeros(n_echo) + rf_flip
  28. slice_thickness = 5e-3
  29. TE = 12e-3 # Echo time
  30. TR = 2000e-3 # Repetition time
  31. sampling_time = 6.4e-3
  32. readout_time = sampling_time + 2 * system.adc_dead_time
  33. t_ex = 2.5e-3
  34. t_exwd = t_ex + system.rf_ringdown_time + system.rf_dead_time
  35. t_ref = 2e-3
  36. t_refwd = t_ref + system.rf_ringdown_time + system.rf_dead_time
  37. t_sp = 0.5 * (TE - readout_time - t_refwd)
  38. t_spex = 0.5 * (TE - t_exwd - t_refwd)
  39. fsp_r = 1
  40. fsp_s = 0.5
  41. rf_ex_phase = np.pi / 2
  42. rf_ref_phase = 0
  43. # ======
  44. # CREATE EVENTS
  45. # ======
  46. flip_ex = 90 * np.pi / 180
  47. rf_ex, gz, _ = pp.make_sinc_pulse(
  48. flip_angle=flip_ex,
  49. system=system,
  50. duration=t_ex,
  51. slice_thickness=slice_thickness,
  52. apodization=0.5,
  53. time_bw_product=4,
  54. phase_offset=rf_ex_phase,
  55. return_gz=True,
  56. )
  57. gs_ex = pp.make_trapezoid(
  58. channel="z",
  59. system=system,
  60. amplitude=gz.amplitude,
  61. flat_time=t_exwd,
  62. rise_time=dG,
  63. )
  64. flip_ref = rf_flip[0] * np.pi / 180
  65. rf_ref, gz, _ = pp.make_sinc_pulse(
  66. flip_angle=flip_ref,
  67. system=system,
  68. duration=t_ref,
  69. slice_thickness=slice_thickness,
  70. apodization=0.5,
  71. time_bw_product=4,
  72. phase_offset=rf_ref_phase,
  73. use="refocusing",
  74. return_gz=True,
  75. )
  76. gs_ref = pp.make_trapezoid(
  77. channel="z",
  78. system=system,
  79. amplitude=gs_ex.amplitude,
  80. flat_time=t_refwd,
  81. rise_time=dG,
  82. )
  83. ags_ex = gs_ex.area / 2
  84. gs_spr = pp.make_trapezoid(
  85. channel="z",
  86. system=system,
  87. area=ags_ex * (1 + fsp_s),
  88. duration=t_sp,
  89. rise_time=dG,
  90. )
  91. gs_spex = pp.make_trapezoid(
  92. channel="z", system=system, area=ags_ex * fsp_s, duration=t_spex, rise_time=dG
  93. )
  94. delta_k = 1 / fov
  95. k_width = Nx * delta_k
  96. gr_acq = pp.make_trapezoid(
  97. channel="x",
  98. system=system,
  99. flat_area=k_width,
  100. flat_time=readout_time,
  101. rise_time=dG,
  102. )
  103. adc = pp.make_adc(
  104. num_samples=Nx, duration=sampling_time, delay=system.adc_dead_time
  105. )
  106. gr_spr = pp.make_trapezoid(
  107. channel="x",
  108. system=system,
  109. area=gr_acq.area * fsp_r,
  110. duration=t_sp,
  111. rise_time=dG,
  112. )
  113. agr_spr = gr_spr.area
  114. agr_preph = gr_acq.area / 2 + agr_spr
  115. gr_preph = pp.make_trapezoid(
  116. channel="x", system=system, area=agr_preph, duration=t_spex, rise_time=dG
  117. )
  118. # Phase-encoding
  119. n_ex = math.floor(Ny / n_echo)
  120. pe_steps = np.arange(1, n_echo * n_ex + 1) - 0.5 * n_echo * n_ex - 1
  121. if divmod(n_echo, 2)[1] == 0:
  122. pe_steps = np.roll(pe_steps, [0, int(-np.round(n_ex / 2))])
  123. pe_order = pe_steps.reshape((n_ex, n_echo), order="F").T
  124. phase_areas = pe_order * delta_k
  125. # Split gradients and recombine into blocks
  126. gs1_times = np.array([0, gs_ex.rise_time])
  127. gs1_amp = np.array([0, gs_ex.amplitude])
  128. gs1 = pp.make_extended_trapezoid(channel="z", times=gs1_times, amplitudes=gs1_amp)
  129. gs2_times = np.array([0, gs_ex.flat_time])
  130. gs2_amp = np.array([gs_ex.amplitude, gs_ex.amplitude])
  131. gs2 = pp.make_extended_trapezoid(channel="z", times=gs2_times, amplitudes=gs2_amp)
  132. gs3_times = np.array(
  133. [
  134. 0,
  135. gs_spex.rise_time,
  136. gs_spex.rise_time + gs_spex.flat_time,
  137. gs_spex.rise_time + gs_spex.flat_time + gs_spex.fall_time,
  138. ]
  139. )
  140. gs3_amp = np.array(
  141. [gs_ex.amplitude, gs_spex.amplitude, gs_spex.amplitude, gs_ref.amplitude]
  142. )
  143. gs3 = pp.make_extended_trapezoid(channel="z", times=gs3_times, amplitudes=gs3_amp)
  144. gs4_times = np.array([0, gs_ref.flat_time])
  145. gs4_amp = np.array([gs_ref.amplitude, gs_ref.amplitude])
  146. gs4 = pp.make_extended_trapezoid(channel="z", times=gs4_times, amplitudes=gs4_amp)
  147. gs5_times = np.array(
  148. [
  149. 0,
  150. gs_spr.rise_time,
  151. gs_spr.rise_time + gs_spr.flat_time,
  152. gs_spr.rise_time + gs_spr.flat_time + gs_spr.fall_time,
  153. ]
  154. )
  155. gs5_amp = np.array([gs_ref.amplitude, gs_spr.amplitude, gs_spr.amplitude, 0])
  156. gs5 = pp.make_extended_trapezoid(channel="z", times=gs5_times, amplitudes=gs5_amp)
  157. gs7_times = np.array(
  158. [
  159. 0,
  160. gs_spr.rise_time,
  161. gs_spr.rise_time + gs_spr.flat_time,
  162. gs_spr.rise_time + gs_spr.flat_time + gs_spr.fall_time,
  163. ]
  164. )
  165. gs7_amp = np.array([0, gs_spr.amplitude, gs_spr.amplitude, gs_ref.amplitude])
  166. gs7 = pp.make_extended_trapezoid(channel="z", times=gs7_times, amplitudes=gs7_amp)
  167. # Readout gradient
  168. gr3 = gr_preph
  169. gr5_times = np.array(
  170. [
  171. 0,
  172. gr_spr.rise_time,
  173. gr_spr.rise_time + gr_spr.flat_time,
  174. gr_spr.rise_time + gr_spr.flat_time + gr_spr.fall_time,
  175. ]
  176. )
  177. gr5_amp = np.array([0, gr_spr.amplitude, gr_spr.amplitude, gr_acq.amplitude])
  178. gr5 = pp.make_extended_trapezoid(channel="x", times=gr5_times, amplitudes=gr5_amp)
  179. gr6_times = np.array([0, readout_time])
  180. gr6_amp = np.array([gr_acq.amplitude, gr_acq.amplitude])
  181. gr6 = pp.make_extended_trapezoid(channel="x", times=gr6_times, amplitudes=gr6_amp)
  182. gr7_times = np.array(
  183. [
  184. 0,
  185. gr_spr.rise_time,
  186. gr_spr.rise_time + gr_spr.flat_time,
  187. gr_spr.rise_time + gr_spr.flat_time + gr_spr.fall_time,
  188. ]
  189. )
  190. gr7_amp = np.array([gr_acq.amplitude, gr_spr.amplitude, gr_spr.amplitude, 0])
  191. gr7 = pp.make_extended_trapezoid(channel="x", times=gr7_times, amplitudes=gr7_amp)
  192. # Fill-times
  193. t_ex = pp.calc_duration(gs1) + pp.calc_duration(gs2) + pp.calc_duration(gs3)
  194. t_ref = (
  195. pp.calc_duration(gs4)
  196. + pp.calc_duration(gs5)
  197. + pp.calc_duration(gs7)
  198. + readout_time
  199. )
  200. t_end = pp.calc_duration(gs4) + pp.calc_duration(gs5)
  201. TE_train = t_ex + n_echo * t_ref + t_end
  202. TR_fill = (TR - n_slices * TE_train) / n_slices
  203. # Round to gradient raster
  204. TR_fill = system.grad_raster_time * np.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 = pp.make_delay(TR_fill)
  213. # ======
  214. # CONSTRUCT SEQUENCE
  215. # ======
  216. for k_ex in range(n_ex + 1):
  217. for s in range(n_slices):
  218. rf_ex.freq_offset = (
  219. gs_ex.amplitude * slice_thickness * (s - (n_slices - 1) / 2)
  220. )
  221. rf_ref.freq_offset = (
  222. gs_ref.amplitude * slice_thickness * (s - (n_slices - 1) / 2)
  223. )
  224. rf_ex.phase_offset = (
  225. rf_ex_phase
  226. - 2 * np.pi * rf_ex.freq_offset * pp.calc_rf_center(rf_ex)[0]
  227. )
  228. rf_ref.phase_offset = (
  229. rf_ref_phase
  230. - 2 * np.pi * rf_ref.freq_offset * pp.calc_rf_center(rf_ref)[0]
  231. )
  232. seq.add_block(gs1)
  233. seq.add_block(gs2, rf_ex)
  234. seq.add_block(gs3, gr3)
  235. for k_echo in range(n_echo):
  236. if k_ex > 0:
  237. phase_area = phase_areas[k_echo, k_ex - 1]
  238. else:
  239. phase_area = 0.0 # 0.0 and not 0 because -phase_area should successfully result in negative zero
  240. gp_pre = pp.make_trapezoid(
  241. channel="y",
  242. system=system,
  243. area=phase_area,
  244. duration=t_sp,
  245. rise_time=dG,
  246. )
  247. gp_rew = pp.make_trapezoid(
  248. channel="y",
  249. system=system,
  250. area=-phase_area,
  251. duration=t_sp,
  252. rise_time=dG,
  253. )
  254. seq.add_block(gs4, rf_ref)
  255. seq.add_block(gs5, gr5, gp_pre)
  256. if k_ex > 0:
  257. seq.add_block(gr6, adc)
  258. else:
  259. seq.add_block(gr6)
  260. seq.add_block(gs7, gr7, gp_rew)
  261. seq.add_block(gs4)
  262. seq.add_block(gs5)
  263. seq.add_block(delay_TR)
  264. (
  265. ok,
  266. error_report,
  267. ) = seq.check_timing() # Check whether the timing of the sequence is correct
  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. if __name__ == "__main__":
  284. main(plot=True, write_seq=True)