write_epi_label.py 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. """
  2. Demo low-performance EPI sequence without ramp-sampling.
  3. In addition, it demonstrates how the LABEL extension can be used to set data header values, which can be used either in
  4. combination with integrated image reconstruction or to guide the off-line reconstruction tools.
  5. """
  6. import numpy as np
  7. import pypulseq as pp
  8. from pypulseq import calc_rf_center
  9. def main(plot: bool, write_seq: bool, seq_filename: str = "epi_lable_pypulseq.seq"):
  10. # ======
  11. # SETUP
  12. # ======
  13. seq = pp.Sequence() # Create a new sequence object
  14. fov = 220e-3 # Define FOV and resolution
  15. Nx = 96
  16. Ny = 96
  17. slice_thickness = 3e-3 # Slice thickness
  18. n_slices = 7
  19. n_reps = 4
  20. navigator = 3
  21. # Set system limits
  22. system = pp.Opts(
  23. max_grad=32,
  24. grad_unit="mT/m",
  25. max_slew=130,
  26. slew_unit="T/m/s",
  27. rf_ringdown_time=30e-6,
  28. rf_dead_time=100e-6,
  29. )
  30. # ======
  31. # CREATE EVENTS
  32. # ======
  33. # Create 90 degree slice selection pulse and gradient
  34. rf, gz, _ = pp.make_sinc_pulse(
  35. flip_angle=np.pi / 2,
  36. system=system,
  37. duration=3e-3,
  38. slice_thickness=slice_thickness,
  39. apodization=0.5,
  40. time_bw_product=4,
  41. return_gz=True,
  42. )
  43. # Define trigger
  44. trig = pp.make_trigger(channel="physio1", duration=2000e-6)
  45. # Define other gradients and ADC events
  46. delta_k = 1 / fov
  47. k_width = Nx * delta_k
  48. dwell_time = 4e-6
  49. readout_time = Nx * dwell_time
  50. flat_time = np.ceil(readout_time * 1e5) * 1e-5 # Round-up to the gradient raster
  51. gx = pp.make_trapezoid(
  52. channel="x",
  53. system=system,
  54. amplitude=k_width / readout_time,
  55. flat_time=flat_time,
  56. )
  57. adc = pp.make_adc(
  58. num_samples=Nx,
  59. duration=readout_time,
  60. delay=gx.rise_time + flat_time / 2 - (readout_time - dwell_time) / 2,
  61. )
  62. # Pre-phasing gradients
  63. pre_time = 8e-4
  64. gx_pre = pp.make_trapezoid(
  65. channel="x", system=system, area=-gx.area / 2, duration=pre_time
  66. )
  67. gz_reph = pp.make_trapezoid(
  68. channel="z", system=system, area=-gz.area / 2, duration=pre_time
  69. )
  70. gy_pre = pp.make_trapezoid(
  71. channel="y", system=system, area=Ny / 2 * delta_k, duration=pre_time
  72. )
  73. # Phase blip in the shortest possible time
  74. dur = np.ceil(2 * np.sqrt(delta_k / system.max_slew) / 10e-6) * 10e-6
  75. gy = pp.make_trapezoid(channel="y", system=system, area=-delta_k, duration=dur)
  76. gz_spoil = pp.make_trapezoid(channel="z", system=system, area=delta_k * Nx * 4)
  77. # ======
  78. # CONSTRUCT SEQUENCE
  79. # ======
  80. # Define sequence blocks
  81. for r in range(n_reps):
  82. seq.add_block(trig, pp.make_label(type="SET", label="SLC", value=0))
  83. for s in range(n_slices):
  84. rf.freq_offset = gz.amplitude * slice_thickness * (s - (n_slices - 1) / 2)
  85. # Compensate for the slide-offset induced phase
  86. rf.phase_offset = -rf.freq_offset * calc_rf_center(rf)[0]
  87. seq.add_block(rf, gz)
  88. seq.add_block(
  89. gx_pre,
  90. gz_reph,
  91. pp.make_label(type="SET", label="NAV", value=1),
  92. pp.make_label(type="SET", label="LIN", value=np.round(Ny / 2)),
  93. )
  94. for n in range(navigator):
  95. seq.add_block(
  96. gx,
  97. adc,
  98. pp.make_label(type="SET", label="REV", value=gx.amplitude < 0),
  99. pp.make_label(type="SET", label="SEG", value=gx.amplitude < 0),
  100. pp.make_label(type="SET", label="AVG", value=n + 1 == 3),
  101. )
  102. if n + 1 != navigator:
  103. # Dummy blip pulse to maintain identical RO gradient timing and the corresponding eddy currents
  104. seq.add_block(pp.make_delay(pp.calc_duration(gy)))
  105. gx.amplitude = -gx.amplitude # Reverse polarity of read gradient
  106. # Reset lin/nav/avg
  107. seq.add_block(
  108. gy_pre,
  109. pp.make_label(type="SET", label="LIN", value=0),
  110. pp.make_label(type="SET", label="NAV", value=0),
  111. pp.make_label(type="SET", label="AVG", value=0),
  112. )
  113. for i in range(Ny):
  114. seq.add_block(
  115. pp.make_label(type="SET", label="REV", value=gx.amplitude < 0),
  116. pp.make_label(type="SET", label="SEG", value=gx.amplitude < 0),
  117. )
  118. seq.add_block(gx, adc) # Read one line of k-space
  119. # Phase blip
  120. seq.add_block(gy, pp.make_label(type="INC", label="LIN", value=1))
  121. gx.amplitude = -gx.amplitude # Reverse polarity of read gradient
  122. seq.add_block(
  123. gz_spoil,
  124. pp.make_delay(0.1),
  125. pp.make_label(type="INC", label="SLC", value=1),
  126. )
  127. if np.remainder(navigator + Ny, 2) != 0:
  128. gx.amplitude = -gx.amplitude
  129. seq.add_block(pp.make_label(type="INC", label="REP", value=1))
  130. ok, error_report = seq.check_timing()
  131. if ok:
  132. print("Timing check passed successfully")
  133. else:
  134. print("Timing check failed! Error listing follows:")
  135. print(error_report)
  136. # ======
  137. # VISUALIZATION
  138. # ======
  139. if plot:
  140. seq.plot(
  141. time_range=(0, 0.1), time_disp="ms", label="SEG, LIN, SLC"
  142. ) # Plot sequence waveforms
  143. # =========
  144. # WRITE .SEQ
  145. # =========
  146. if write_seq:
  147. # Prepare sequence report
  148. seq.set_definition(key="FOV", value=[fov, fov, slice_thickness * n_slices])
  149. seq.set_definition(key="Name", value="epi_lbl")
  150. seq.write(seq_filename)
  151. if __name__ == "__main__":
  152. main(plot=True, write_seq=True)