write_MPRAGE.py 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. from types import SimpleNamespace
  2. import numpy as np
  3. import pypulseq as pp
  4. def main(plot: bool, write_seq: bool, seq_filename: str = "mprage_pypulseq.seq"):
  5. # ======
  6. # SETUP
  7. # ======
  8. seq = pp.Sequence() # Create a new sequence object
  9. # Set system limits
  10. system = pp.Opts(
  11. max_grad=24,
  12. grad_unit="mT/m",
  13. max_slew=100,
  14. slew_unit="T/m/s",
  15. rf_ringdown_time=20e-6,
  16. rf_dead_time=100e-6,
  17. adc_dead_time=10e-6,
  18. )
  19. alpha = 7 # Flip angle
  20. ro_dur = 5017.6e-6
  21. ro_os = 1 # Readout oversampling
  22. ro_spoil = 3 # Additional k-max excursion for RO spoiling
  23. TI = 1.1
  24. TR_out = 2.5
  25. rf_spoiling_inc = 117
  26. rf_len = 100e-6
  27. ax = SimpleNamespace() # Encoding axes
  28. fov = np.array([192, 240, 256]) * 1e-3 # Define FOV and resolution
  29. N = [192, 240, 256]
  30. ax.d1 = "z" # Fastest dimension (readout)
  31. ax.d2 = "x" # Second-fastest dimension (inner phase-encoding loop)
  32. xyz = ["x", "y", "z"]
  33. ax.d3 = np.setdiff1d(xyz, [ax.d1, ax.d2])[0]
  34. ax.n1 = xyz.index(ax.d1)
  35. ax.n2 = xyz.index(ax.d2)
  36. ax.n3 = xyz.index(ax.d3)
  37. # Create alpha-degree hard pulse and gradient
  38. rf = pp.make_block_pulse(
  39. flip_angle=alpha * np.pi / 180, system=system, duration=rf_len
  40. )
  41. rf180 = pp.make_adiabatic_pulse(
  42. pulse_type="hypsec", system=system, duration=10.24e-3, dwell=1e-5
  43. )
  44. # Define other gradients and ADC events
  45. deltak = 1 / fov
  46. gro = pp.make_trapezoid(
  47. channel=ax.d1,
  48. amplitude=N[ax.n1] * deltak[ax.n1] / ro_dur,
  49. flat_time=np.ceil((ro_dur + system.adc_dead_time) / system.grad_raster_time)
  50. * system.grad_raster_time,
  51. system=system,
  52. )
  53. adc = pp.make_adc(
  54. num_samples=N[ax.n1] * ro_os,
  55. duration=ro_dur,
  56. delay=gro.rise_time,
  57. system=system,
  58. )
  59. # First 0.5 is necessary to account for the Siemens sampling in the center of the dwell periods
  60. gro_pre = pp.make_trapezoid(
  61. channel=ax.d1,
  62. area=-gro.amplitude
  63. * (adc.dwell * (adc.num_samples / 2 + 0.5) + 0.5 * gro.rise_time),
  64. system=system,
  65. )
  66. gpe1 = pp.make_trapezoid(
  67. channel=ax.d2, area=-deltak[ax.n2] * (N[ax.n2] / 2), system=system
  68. ) # Maximum PE1 gradient
  69. gpe2 = pp.make_trapezoid(
  70. channel=ax.d3, area=-deltak[ax.n3] * (N[ax.n3] / 2), system=system
  71. ) # Maximum PE2 gradient
  72. # Spoil with 4x cycles per voxel
  73. gsl_sp = pp.make_trapezoid(
  74. channel=ax.d3, area=np.max(deltak * N) * 4, duration=10e-3, system=system
  75. )
  76. # We cut the RO gradient into two parts for the optimal spoiler timing
  77. gro1, gro_Sp = pp.split_gradient_at(
  78. grad=gro, time_point=gro.rise_time + gro.flat_time
  79. )
  80. # Gradient spoiling
  81. if ro_spoil > 0:
  82. gro_Sp = pp.make_extended_trapezoid_area(
  83. channel=gro.channel,
  84. grad_start=gro.amplitude,
  85. grad_end=0,
  86. area=deltak[ax.n1] / 2 * N[ax.n1] * ro_spoil,
  87. system=system,
  88. )[0]
  89. # Calculate timing of the fast loop. We will have two blocks in the inner loop:
  90. # 1: spoilers/rewinders + RF
  91. # 2: prewinder, phase enconding + readout
  92. rf.delay = pp.calc_duration(gro_Sp, gpe1, gpe2)
  93. gro_pre, _, _ = pp.align(right=[gro_pre, gpe1, gpe2])
  94. gro1.delay = pp.calc_duration(gro_pre)
  95. adc.delay = gro1.delay + gro.rise_time
  96. gro1 = pp.add_gradients(grads=[gro1, gro_pre], system=system)
  97. TR_inner = pp.calc_duration(rf) + pp.calc_duration(gro1) # For TI delay
  98. # pe_steps -- control reordering
  99. pe1_steps = ((np.arange(N[ax.n2])) - N[ax.n2] / 2) / N[ax.n2] * 2
  100. pe2_steps = ((np.arange(N[ax.n3])) - N[ax.n3] / 2) / N[ax.n3] * 2
  101. # TI calc
  102. TI_delay = (
  103. np.round(
  104. (
  105. TI
  106. - (np.where(pe1_steps == 0)[0][0]) * TR_inner
  107. - (pp.calc_duration(rf180) - pp.calc_rf_center(rf180)[0] - rf180.delay)
  108. - rf.delay
  109. - pp.calc_rf_center(rf)[0]
  110. )
  111. / system.block_duration_raster
  112. )
  113. * system.block_duration_raster
  114. )
  115. TR_out_delay = TR_out - TR_inner * N[ax.n2] - TI_delay - pp.calc_duration(rf180)
  116. # All LABELS / counters an flags are automatically initialized to 0 in the beginning, no need to define initial 0's
  117. # so we will just increment LIN after the ADC event (e.g. during the spoiler)
  118. label_inc_lin = pp.make_label(type="INC", label="LIN", value=1)
  119. label_inc_par = pp.make_label(type="INC", label="PAR", value=1)
  120. label_reset_par = pp.make_label(type="SET", label="PAR", value=0)
  121. # Pre-register objects that do not change while looping
  122. result = seq.register_grad_event(gsl_sp)
  123. gsl_sp.id = result if isinstance(result, int) else result[0]
  124. result = seq.register_grad_event(gro_Sp)
  125. gro_Sp.id = result if isinstance(result, int) else result[0]
  126. result = seq.register_grad_event(gro1)
  127. gro1.id = result if isinstance(result, int) else result[0]
  128. # Phase of the RF object will change, therefore we only pre-register the shapes
  129. _, rf.shape_IDs = seq.register_rf_event(rf)
  130. rf180.id, rf180.shape_IDs = seq.register_rf_event(rf180)
  131. label_inc_par.id = seq.register_label_event(label_inc_par)
  132. # Sequence
  133. for j in range(N[ax.n3]):
  134. seq.add_block(rf180)
  135. seq.add_block(pp.make_delay(TI_delay), gsl_sp)
  136. rf_phase = 0
  137. rf_inc = 0
  138. # Pre-register PE events that repeat in the inner loop
  139. gpe2je = pp.scale_grad(grad=gpe2, scale=pe2_steps[j])
  140. gpe2je.id = seq.register_grad_event(gpe2je)
  141. gpe2jr = pp.scale_grad(grad=gpe2, scale=-pe2_steps[j])
  142. gpe2jr.id = seq.register_grad_event(gpe2jr)
  143. for i in range(N[ax.n2]):
  144. rf.phase_offset = rf_phase / 180 * np.pi
  145. adc.phase_offset = rf_phase / 180 * np.pi
  146. rf_inc = np.mod(rf_inc + rf_spoiling_inc, 360.0)
  147. rf_phase = np.mod(rf_phase + rf_inc, 360.0)
  148. if i == 0:
  149. seq.add_block(rf)
  150. else:
  151. seq.add_block(
  152. rf,
  153. gro_Sp,
  154. pp.scale_grad(grad=gpe1, scale=-pe1_steps[i - 1]),
  155. gpe2jr,
  156. label_inc_par,
  157. )
  158. seq.add_block(
  159. adc, gro1, pp.scale_grad(grad=gpe1, scale=pe1_steps[i]), gpe2je
  160. )
  161. seq.add_block(
  162. gro_Sp, pp.make_delay(TR_out_delay), label_reset_par, label_inc_lin
  163. )
  164. # ======
  165. # VISUALIZATION
  166. # ======
  167. if plot:
  168. seq.plot(time_range=[0, TR_out * 2], label="PAR")
  169. # =========
  170. # WRITE .SEQ
  171. # =========
  172. if write_seq:
  173. seq.write(seq_filename)
  174. if __name__ == "__main__":
  175. main(plot=True, write_seq=True)