import math import warnings import numpy as np from pypulseq.Sequence.sequence import Sequence from pypulseq.calc_rf_center import calc_rf_center from pypulseq.make_adc import make_adc from pypulseq.make_delay import make_delay from pypulseq.make_extended_trapezoid import make_extended_trapezoid from pypulseq.make_sinc_pulse import make_sinc_pulse from pypulseq.make_trapezoid import make_trapezoid from pypulseq.opts import Opts def main(plot: bool, write_seq: bool, seq_filename: str = "haste_pypulseq.seq"): # ====== # SETUP # ====== dG = 250e-6 # Set system limits system = Opts( max_grad=30, grad_unit="mT/m", max_slew=170, slew_unit="T/m/s", rf_ringdown_time=100e-6, rf_dead_time=100e-6, adc_dead_time=10e-6, ) seq = Sequence(system=system) # Create a new sequence object fov = 256e-3 # Define FOV and resolution Ny_pre = 8 Nx, Ny = 128, 128 n_echo = int(Ny / 2 + Ny_pre) # Number of echoes n_slices = 1 rf_flip = 180 # Flip angle if isinstance(rf_flip, int): rf_flip = np.zeros(n_echo) + rf_flip slice_thickness = 5e-3 # Slice thickness TE = 12e-3 # Echo time TR = 2000e-3 # Repetition time sampling_time = 6.4e-3 readout_time = sampling_time + 2 * system.adc_dead_time t_ex = 2.5e-3 t_ex_wd = t_ex + system.rf_ringdown_time + system.rf_dead_time t_ref = 2e-3 tf_ref_wd = t_ref + system.rf_ringdown_time + system.rf_dead_time t_sp = 0.5 * (TE - readout_time - tf_ref_wd) t_sp_ex = 0.5 * (TE - t_ex_wd - tf_ref_wd) fspR = 1.0 fspS = 0.5 rfex_phase = math.pi / 2 rfref_phase = 0 # ====== # CREATE EVENTS # ====== # Create 90 degree slice selection pulse and gradient flipex = 90 * math.pi / 180 rfex, gz, _ = make_sinc_pulse( flip_angle=flipex, system=system, duration=t_ex, slice_thickness=slice_thickness, apodization=0.5, time_bw_product=4, phase_offset=rfex_phase, return_gz=True, ) GS_ex = make_trapezoid( channel="z", system=system, amplitude=gz.amplitude, flat_time=t_ex_wd, rise_time=dG, ) flipref = rf_flip[0] * math.pi / 180 rfref, gz, _ = make_sinc_pulse( flip_angle=flipref, system=system, duration=t_ref, slice_thickness=slice_thickness, apodization=0.5, time_bw_product=4, phase_offset=rfref_phase, use="refocusing", return_gz=True, ) GS_ref = make_trapezoid( channel="z", system=system, amplitude=GS_ex.amplitude, flat_time=tf_ref_wd, rise_time=dG, ) AGS_ex = GS_ex.area / 2 GS_spr = make_trapezoid( channel="z", system=system, area=AGS_ex * (1 + fspS), duration=t_sp, rise_time=dG, ) GS_spex = make_trapezoid( channel="z", system=system, area=AGS_ex * fspS, duration=t_sp_ex, rise_time=dG ) delta_k = 1 / fov k_width = Nx * delta_k GR_acq = make_trapezoid( channel="x", system=system, flat_area=k_width, flat_time=readout_time, rise_time=dG, ) adc = make_adc(num_samples=Nx, duration=sampling_time, delay=system.adc_dead_time) GR_spr = make_trapezoid( channel="x", system=system, area=GR_acq.area * fspR, duration=t_sp, rise_time=dG ) GR_spex = make_trapezoid( channel="x", system=system, area=GR_acq.area * (1 + fspR), duration=t_sp_ex, rise_time=dG, ) AGR_spr = GR_spr.area AGR_preph = GR_acq.area / 2 + AGR_spr GR_preph = make_trapezoid( channel="x", system=system, area=AGR_preph, duration=t_sp_ex, rise_time=dG ) n_ex = 1 PE_order = np.arange(-Ny_pre, Ny + 1).T phase_areas = PE_order * delta_k # Split gradients and recombine into blocks GS1_times = np.array([0, GS_ex.rise_time]) GS1_amp = np.array([0, GS_ex.amplitude]) GS1 = make_extended_trapezoid(channel="z", times=GS1_times, amplitudes=GS1_amp) GS2_times = np.array([0, GS_ex.flat_time]) GS2_amp = np.array([GS_ex.amplitude, GS_ex.amplitude]) GS2 = make_extended_trapezoid(channel="z", times=GS2_times, amplitudes=GS2_amp) GS3_times = np.array( [ 0, GS_spex.rise_time, GS_spex.rise_time + GS_spex.flat_time, GS_spex.rise_time + GS_spex.flat_time + GS_spex.fall_time, ] ) GS3_amp = np.array( [GS_ex.amplitude, GS_spex.amplitude, GS_spex.amplitude, GS_ref.amplitude] ) GS3 = make_extended_trapezoid(channel="z", times=GS3_times, amplitudes=GS3_amp) GS4_times = np.array([0, GS_ref.flat_time]) GS4_amp = np.array([GS_ref.amplitude, GS_ref.amplitude]) GS4 = make_extended_trapezoid(channel="z", times=GS4_times, amplitudes=GS4_amp) GS5_times = np.array( [ 0, GS_spr.rise_time, GS_spr.rise_time + GS_spr.flat_time, GS_spr.rise_time + GS_spr.flat_time + GS_spr.fall_time, ] ) GS5_amp = np.array([GS_ref.amplitude, GS_spr.amplitude, GS_spr.amplitude, 0]) GS5 = make_extended_trapezoid(channel="z", times=GS5_times, amplitudes=GS5_amp) GS7_times = np.array( [ 0, GS_spr.rise_time, GS_spr.rise_time + GS_spr.flat_time, GS_spr.rise_time + GS_spr.flat_time + GS_spr.fall_time, ] ) GS7_amp = np.array([0, GS_spr.amplitude, GS_spr.amplitude, GS_ref.amplitude]) GS7 = make_extended_trapezoid(channel="z", times=GS7_times, amplitudes=GS7_amp) # Readout gradient GR3 = GR_preph GR5_times = np.array( [ 0, GR_spr.rise_time, GR_spr.rise_time + GR_spr.flat_time, GR_spr.rise_time + GR_spr.flat_time + GR_spr.fall_time, ] ) GR5_amp = np.array([0, GR_spr.amplitude, GR_spr.amplitude, GR_acq.amplitude]) GR5 = make_extended_trapezoid(channel="x", times=GR5_times, amplitudes=GR5_amp) GR6_times = np.array([0, readout_time]) GR6_amp = np.array([GR_acq.amplitude, GR_acq.amplitude]) GR6 = make_extended_trapezoid(channel="x", times=GR6_times, amplitudes=GR6_amp) GR7_times = np.array( [ 0, GR_spr.rise_time, GR_spr.rise_time + GR_spr.flat_time, GR_spr.rise_time + GR_spr.flat_time + GR_spr.fall_time, ] ) GR7_amp = np.array([GR_acq.amplitude, GR_spr.amplitude, GR_spr.amplitude, 0]) GR7 = make_extended_trapezoid(channel="x", times=GR7_times, amplitudes=GR7_amp) # Fill-times tex = GS1.shape_dur + GS2.shape_dur + GS3.shape_dur tref = GS4.shape_dur + GS5.shape_dur + GS7.shape_dur + readout_time tend = GS4.shape_dur + GS5.shape_dur TE_train = tex + n_echo * tref + tend TR_fill = (TR - n_slices * TE_train) / n_slices # Round to gradient raster TR_fill = system.grad_raster_time * round(TR_fill / system.grad_raster_time) if TR_fill < 0: TR_fill = 1e-3 warnings.warn( f"TR too short, adapted to include all slices to: {1000 * n_slices * (TE_train + TR_fill)} ms" ) else: print(f"TR fill: {1000 * TR_fill} ms") delay_TR = make_delay(TR_fill) delay_end = make_delay(5) # ====== # CONSTRUCT SEQUENCE # ====== # Define sequence blocks for k_ex in range(n_ex): for s in range(n_slices): rfex.freq_offset = ( GS_ex.amplitude * slice_thickness * (s - (n_slices - 1) / 2) ) rfref.freq_offset = ( GS_ref.amplitude * slice_thickness * (s - (n_slices - 1) / 2) ) # Align the phase for off-center slices rfex.phase_offset = ( rfex_phase - 2 * math.pi * rfex.freq_offset * calc_rf_center(rfex)[0] ) rfref.phase_offset = ( rfref_phase - 2 * math.pi * rfref.freq_offset * calc_rf_center(rfref)[0] ) seq.add_block(GS1) seq.add_block(GS2, rfex) seq.add_block(GS3, GR3) for k_ech in range(n_echo): if k_ex >= 0: phase_area = phase_areas[k_ech] else: phase_area = 0 GP_pre = make_trapezoid( channel="y", system=system, area=phase_area, duration=t_sp, rise_time=dG, ) GP_rew = make_trapezoid( channel="y", system=system, area=-phase_area, duration=t_sp, rise_time=dG, ) seq.add_block(GS4, rfref) seq.add_block(GS5, GR5, GP_pre) if k_ex >= 0: seq.add_block(GR6, adc) else: seq.add_block(GR6) seq.add_block(GS7, GR7, GP_rew) seq.add_block(GS4) seq.add_block(GS5) seq.add_block(delay_TR) seq.add_block(delay_end) # Check whether the timing of the sequence is correct ok, error_report = seq.check_timing() if ok: print("Timing check passed successfully") else: print("Timing check failed. Error listing follows:") [print(e) for e in error_report] # ====== # VISUALIZATION # ====== if plot: seq.plot() # ========= # WRITE .SEQ # ========= if write_seq: seq.write(seq_filename) # SETUPeq") if __name__ == "__main__": main(plot=True, write_seq=True)