readout_time = 6.4e-3 + 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 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) 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') 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)
# convert parameters to Pulseq units TR *= 1e-3 # [s] TE *= 1e-3 # [s] rf_dur *= 1e-3 # [s] slice_res *= 1e-3 # [m] if Nintl/redfac%1 != 0: raise ValueError('Number of interleaves is not multiple of reduction factor') if spiraltype!=1 and spiraltype!=4: ValueError('Right now only spiraltype 1 (spiral out) and 4 (ROI) possible.') #%% RF Pulse and slab/slice selection gradient # make rf pulse and calculate duration of excitation and rewinding rf, gz, gz_rew, rf_del = make_sinc_pulse(flip_angle=flip_angle*np.pi/180, system=system, duration=rf_dur, slice_thickness=slice_res, apodization=0.5, time_bw_product=tbp_exc, use='excitation', return_gz=True, return_delay=True) exc_to_rew = rf_del.delay - rf_dur/2 - rf.delay # time from middle of rf pulse to rewinder, rf_del.delay equals the block length rew_dur = calc_duration(gz_rew) # RF spoiling parameters rf_spoiling_inc = 50 # increment of RF spoiling [°] rf_phase = 0 rf_inc = 0 # Fat saturation if fatsat: if B0 > 4: fatsat_bw = 1000 # bandwidth of fatsat pulse [Hz] else: fatsat_bw = 300 fatsat_fa = 110 # flip angle [°]
#to avoid exceeding gradient limits gdfact = 1.0 tr_per_slice = tr / n_slices system = Opts(max_grad=32, grad_unit='mT/m', max_slew=130, slew_unit='T/m/s', rf_ringdown_time=30e-6, rf_dead_time=100e-6, adc_dead_time=20e-6) b0 = 2.89 sat_ppm = -3.45 sat_freq = sat_ppm * 1e-6 * b0 * system.gamma rf_fs, _, _ = make_gauss_pulse(flip_angle=110 * math.pi / 180, system=system, duration=8e-3, bandwidth=abs(sat_freq), freq_offset=sat_freq) gz_fs = make_trapezoid(channel='z', system=system, delay=calc_duration(rf_fs), area=1 / 1e-4) rf, gz, gz_reph = make_sinc_pulse(flip_angle=math.pi / 2, system=system, duration=3e-3, slice_thickness=slice_thickness, apodization=0.5, time_bw_product=4) rf180, gz180, _ = make_sinc_pulse(flip_angle=math.pi, system=system, duration=5e-3, slice_thickness=slice_thickness, apodization=0.5, time_bw_product=4) rf180.phase_offset = math.pi/2 gz_spoil = make_trapezoid(channel='z', system=system, area=2*gz.area, duration=3e-3) delta_k = 1 / fov k_width = Nx * delta_k readout_time = 1.05e-3 blip_dur = math.ceil(2 * math.sqrt(delta_k / system.max_slew) / seq.grad_raster_time / 2) * seq.grad_raster_time * 2 gy = make_trapezoid(channel='y', system=system, area=delta_k, duration=blip_dur) extra_area = blip_dur / 2 * blip_dur / 2 * system.max_slew
fov = 256e-3 Nx = 256 Ny = 256 slice_thickness = 5e-3 flip = 90 * pi / 180 kwargs_for_sinc = { "flip_angle": flip, "system": system, "duration": 2e-3, "slice_thickness": slice_thickness, "apodization": 0.5, "time_bw_product": 4 } rf, gz = make_sinc_pulse(kwargs_for_sinc, 2) # plt.plot(rf.t[0], rf.signal[0]) # plt.show() delta_k = 1 / fov kWidth = Nx * delta_k readoutTime = system.grad_raster_time * Nx kwargs_for_gx = { "channel": 'x', "system": system, "flat_area": kWidth, "flat_time": readoutTime } gx = make_trapezoid(kwargs_for_gx) kwargs_for_adc = { "num_samples": Nx,
from pypulseq.opts import Opts seq = Sequence() fov = 256e-3 Nx = 256 Ny = 256 alpha = 10 slice_thickness = 3e-3 TE = 7.38e-3 TR = 100e-3 rf_spoiling_inc = 117 sys = Opts(max_grad=28, grad_unit='mT/m', max_slew=150, slew_unit='T/m/s', rf_ringdown_time=20e-6, rf_dead_time=100e-6, adc_dead_time=10e-6) rf, gz, gzr = make_sinc_pulse(flip_angle=alpha * math.pi / 180, duration=4e-3, slice_thickness=slice_thickness, apodization=0.5, time_bw_product=4, system=sys) deltak = 1 / fov gx = make_trapezoid(channel='x', flat_area=Nx * deltak, flat_time=6.4e-3, system=sys) adc = make_adc(num_samples=Nx, duration=gx.flat_time, delay=gx.rise_time, system=sys) gx_pre = make_trapezoid(channel='x', area=-gx.area / 2, duration=2e-3, system=sys) gz_reph = make_trapezoid(channel='z', area=-gz.area / 2, duration=2e-3, system=sys) phase_areas = (np.arange(Ny) - Ny / 2) * deltak gx_spoil = make_trapezoid(channel='x', area=2 * Nx * deltak, system=sys) gz_spoil = make_trapezoid(channel='z', area=4 / slice_thickness, system=sys) delay_TE = math.ceil((TE - calc_duration(gx_pre) - gz.fall_time - gz.flat_time / 2 - calc_duration( gx) / 2) / seq.grad_raster_time) * seq.grad_raster_time delay_TR = math.ceil((TR - calc_duration(gx_pre) - calc_duration(gz) - calc_duration( gx) - delay_TE) / seq.grad_raster_time) * seq.grad_raster_time
# Define FOV and resolution fov = 220e-3 Nx = 64 Ny = 64 slice_thickness = 3e-3 # Slice thickness n_slices = 3 # Set system limits system = Opts(max_grad=32, grad_unit='mT/m', max_slew=130, slew_unit='T/m/s', rf_ringdown_time=30e-6, rf_dead_time=100e-6) # ====== # CREATE EVENTS # ====== # Create 90 degree slice selection pulse and gradient rf, gz, _ = make_sinc_pulse(flip_angle=np.pi / 2, system=system, duration=3e-3, slice_thickness=slice_thickness, apodization=0.5, time_bw_product=4, return_gz=True) # Define other gradients and ADC events delta_k = 1 / fov k_width = Nx * delta_k dwell_time = 4e-6 readout_time = Nx * dwell_time flat_time = math.ceil(readout_time * 1e5) * 1e-5 # round-up to the gradient raster gx = make_trapezoid(channel='x', system=system, amplitude=k_width / readout_time, flat_time=flat_time) adc = make_adc(num_samples=Nx, duration=readout_time, delay=gx.rise_time + flat_time / 2 - (readout_time - dwell_time) / 2) # Pre-phasing gradients pre_time = 8e-4 gx_pre = make_trapezoid(channel='x', system=system, area=-gx.area / 2, duration=pre_time) gz_reph = make_trapezoid(channel='z', system=system, area=-gz.area / 2, duration=pre_time)
fov = 220e-3 Nx = 256 Ny = 256 slice_thickness = 5e-3 flip = 15 * pi / 180 kwargs_for_sinc = { "flip_angle": flip, "system": system, "duration": 4e-3, "slice_thickness": slice_thickness, "apodization": 0.5, "time_bw_product": 4 } rf, gz = make_sinc_pulse(kwargs_for_sinc, 2) # plt.plot(rf.t[0], rf.signal[0]) # plt.show() delta_k = 1 / fov kWidth = Nx * delta_k readoutTime = 6.4e-3 kwargs_for_gx = { "channel": 'x', "system": system, "flat_area": kWidth, "flat_time": readoutTime } gx = make_trapezoid(kwargs_for_gx) kwargs_for_adc = { "num_samples": Nx,
def bssfp_readout(seq, system, fov=200e-3, Nstartup=11, Ny=128): """ Creates a Balanced steady-state free precession (bSSFP) sequence and adds to seq object Parameters ---------- seq: object Sequence object system : Opts System limits. fov : float, optional Field-of-view [m]. Default is 0.2 m. Nstartup : float, optional Number of start-up RF pulses. Default is 11 Ny : float, optional Number of phase encoding lines. Default is 128. Returns ------- seq : SimpleNamespace Seq object with bSSFP readout TR : float Repetition time Ny : float Final number of phase encoding lines. """ # Sequence Parameters enc = 'xyz' Nx = 128 thk = 6e-3 fa = 35 # [deg] Nramp = Nstartup # ADC duration (controls TR/TE) adc_dur = 2560 / 2 # [us] rf_dur = 490 # [us] rf_apo = 0.5 rf_bwt = 1.5 ############################################################################# # Create slice selection pulse and gradient rf, g_ss, __ = make_sinc_pulse( flip_angle=fa * pi / 180, system=system, duration=rf_dur * 1e-6, slice_thickness=thk, apodization=rf_apo, time_bw_product=rf_bwt ) # for next pyPulseq version add:, return_gz=True) g_ss.channel = enc[2] # Slice refocusing g_ss_reph = make_trapezoid(channel=enc[2], system=system, area=-g_ss.area / 2, duration=0.00017 * 2) ############################################################################# rf.delay = calc_duration(g_ss) - calc_duration(rf) + rf.delay ############################################################################# # Readout gradient and ADC delta_k = 1 / fov kWidth = Nx * delta_k # Readout and ADC g_ro = make_trapezoid(channel=enc[0], system=system, flat_area=kWidth, flat_time=(adc_dur) * 1e-6) adc = make_adc(num_samples=Nx, duration=g_ro.flat_time, delay=g_ro.rise_time) # Readout rewinder g_ro_pre = make_trapezoid(channel=enc[0], system=system, area=-g_ro.area / 2) phaseAreas_tmp = np.arange(0, Ny, 1).tolist() phaseAreas = np.dot(np.subtract(phaseAreas_tmp, Ny / 2), delta_k) gs8_times = [ 0, g_ro.fall_time, g_ro.fall_time + g_ro_pre.rise_time, g_ro.fall_time + g_ro_pre.rise_time + g_ro_pre.flat_time, g_ro.fall_time + g_ro_pre.rise_time + g_ro_pre.flat_time + g_ro_pre.fall_time ] gs8_amp = [g_ro.amplitude, 0, g_ro_pre.amplitude, g_ro_pre.amplitude, 0] gx_2 = make_extended_trapezoid(channel=enc[0], times=gs8_times, amplitudes=gs8_amp) # Calculate phase encoding gradient duration pe_dur = calc_duration(gx_2) gx_allExt_times = [ 0, g_ro_pre.rise_time, g_ro_pre.rise_time + g_ro_pre.flat_time, g_ro_pre.rise_time + g_ro_pre.flat_time + g_ro_pre.fall_time, g_ro_pre.rise_time + g_ro_pre.flat_time + g_ro_pre.fall_time + g_ro.rise_time, g_ro_pre.rise_time + g_ro_pre.flat_time + g_ro_pre.fall_time + g_ro.rise_time + g_ro.flat_time + 1e-5, g_ro_pre.rise_time + g_ro_pre.flat_time + g_ro_pre.fall_time + g_ro.rise_time + g_ro.flat_time + 1e-5 + g_ro.fall_time, g_ro_pre.rise_time + g_ro_pre.flat_time + g_ro_pre.fall_time + g_ro.rise_time + g_ro.flat_time + 1e-5 + g_ro.fall_time + g_ro_pre.rise_time, g_ro_pre.rise_time + g_ro_pre.flat_time + g_ro_pre.fall_time + g_ro.rise_time + g_ro.flat_time + 1e-5 + g_ro.fall_time + g_ro_pre.rise_time + g_ro_pre.flat_time, g_ro_pre.rise_time + g_ro_pre.flat_time + g_ro_pre.fall_time + g_ro.rise_time + g_ro.flat_time + 1e-5 + g_ro.fall_time + g_ro_pre.rise_time + g_ro_pre.flat_time + g_ro_pre.fall_time ] gx_allExt_amp = [ 0, g_ro_pre.amplitude, g_ro_pre.amplitude, 0, g_ro.amplitude, g_ro.amplitude, 0, g_ro_pre.amplitude, g_ro_pre.amplitude, 0 ] gx_all = make_extended_trapezoid(channel=enc[0], times=gx_allExt_times, amplitudes=gx_allExt_amp) ############################################################################# gzrep_times = [ 0, g_ss_reph.rise_time, g_ss_reph.rise_time + g_ss_reph.flat_time, g_ss_reph.rise_time + g_ss_reph.flat_time + g_ss_reph.fall_time, g_ss_reph.rise_time + g_ss_reph.flat_time + g_ss_reph.fall_time + calc_duration(g_ro) + 2 * calc_duration(g_ro_pre) - 2 * calc_duration(g_ss_reph) + 1e-5, g_ss_reph.rise_time + g_ss_reph.flat_time + g_ss_reph.fall_time + calc_duration(g_ro) + 2 * calc_duration(g_ro_pre) - 2 * calc_duration(g_ss_reph) + 1e-5 + g_ss_reph.rise_time, g_ss_reph.rise_time + g_ss_reph.flat_time + g_ss_reph.fall_time + calc_duration(g_ro) + 2 * calc_duration(g_ro_pre) - 2 * calc_duration(g_ss_reph) + 1e-5 + g_ss_reph.rise_time + g_ss_reph.flat_time, g_ss_reph.rise_time + g_ss_reph.flat_time + g_ss_reph.fall_time + calc_duration(g_ro) + 2 * calc_duration(g_ro_pre) - 2 * calc_duration(g_ss_reph) + 1e-5 + g_ss_reph.rise_time + g_ss_reph.flat_time + g_ss_reph.fall_time ] gzrep_amp = [ 0, g_ss_reph.amplitude, g_ss_reph.amplitude, 0, 0, g_ss_reph.amplitude, g_ss_reph.amplitude, 0 ] gzrep_all = make_extended_trapezoid(channel=enc[2], times=gzrep_times, amplitudes=gzrep_amp) adc.delay = g_ro_pre.rise_time + g_ro_pre.flat_time + g_ro_pre.fall_time + g_ro.rise_time + 0.5e-5 # finish timing calculation TR = calc_duration(g_ss) + calc_duration(gx_all) TE = TR / 2 ni_acqu_pattern = np.arange(1, Ny + 1, 1) Ny_aq = len(ni_acqu_pattern) print('Acquisition window is: %3.2f ms' % (TR * Ny_aq * 1e3)) rf05 = rf rf_waveform = rf.signal ############################################################################ # Start-up RF pulses # (ramp-up of Nramp) for nRamp in np.arange(1, Nramp + 1, 1): if np.mod(nRamp, 2): rf.phase_offset = 0 adc.phase_offset = 0 else: rf.phase_offset = -pi adc.phase_offset = -pi rf05.signal = np.divide(nRamp, Nramp) * rf_waveform gyPre_2 = make_trapezoid('y', area=phaseAreas[0], duration=pe_dur, system=system) gyPre_1 = make_trapezoid('y', area=-phaseAreas[0], duration=pe_dur, system=system) gyPre_times = [ 0, gyPre_2.rise_time, gyPre_2.rise_time + gyPre_2.flat_time, gyPre_2.rise_time + gyPre_2.flat_time + gyPre_2.fall_time, gyPre_2.rise_time + gyPre_2.flat_time + gyPre_2.fall_time + g_ro.flat_time + 1e-5, gyPre_2.rise_time + gyPre_2.flat_time + gyPre_2.fall_time + g_ro.flat_time + 1e-5 + gyPre_1.rise_time, gyPre_2.rise_time + gyPre_2.flat_time + gyPre_2.fall_time + g_ro.flat_time + 1e-5 + gyPre_1.rise_time + gyPre_1.flat_time, gyPre_2.rise_time + gyPre_2.flat_time + gyPre_2.fall_time + g_ro.flat_time + 1e-5 + gyPre_1.rise_time + gyPre_1.flat_time + gyPre_1.fall_time ] gyPre_amp = [ 0, gyPre_2.amplitude, gyPre_2.amplitude, 0, 0, gyPre_1.amplitude, gyPre_1.amplitude, 0 ] gyPre_all = make_extended_trapezoid(channel=enc[1], times=gyPre_times, amplitudes=gyPre_amp) if nRamp == 1: seq.add_block(rf05, g_ss) seq.add_block(gx_all, gyPre_all, gzrep_all) else: seq.add_block(rf05, g_ss) seq.add_block(gx_all, gyPre_all, gzrep_all) ############################################################################ ############################################################################ # Actual Readout iterate number of phase encoding steps Ny for i in np.arange(1, Ny + 1, 1): # ******************************* # Phase cycling if np.mod(i + Nramp, 2): rf.phase_offset = 0 adc.phase_offset = 0 else: rf.phase_offset = -pi adc.phase_offset = -pi # ******************************* # *************************************************************************************** # Create phase encoding gradients if np.mod(Nramp, 2): gyPre_2 = make_trapezoid('y', area=phaseAreas[i - 1], duration=pe_dur, system=system) if i > 1: gyPre_1 = make_trapezoid( 'y', area=-phaseAreas[np.mod(i + Ny - 2, Ny)], duration=pe_dur, system=system) else: gyPre_1 = make_trapezoid( 'y', area=-phaseAreas[np.mod(i + Ny - 1, Ny)], duration=pe_dur, system=system) else: gyPre_2 = make_trapezoid('y', area=phaseAreas[i], duration=pe_dur, system=system) if i > 1: gyPre_1 = make_trapezoid( 'y', area=-phaseAreas[np.mod(i + Ny - 2, Ny)], duration=pe_dur, system=system) else: gyPre_1 = make_trapezoid('y', area=phaseAreas[np.mod( i + Ny - 2, Ny)], duration=pe_dur, system=system) gyPre_times = [ 0, gyPre_2.rise_time, gyPre_2.rise_time + gyPre_2.flat_time, gyPre_2.rise_time + gyPre_2.flat_time + gyPre_2.fall_time, gyPre_2.rise_time + gyPre_2.flat_time + gyPre_2.fall_time + g_ro.flat_time + 1e-5, gyPre_2.rise_time + gyPre_2.flat_time + gyPre_2.fall_time + g_ro.flat_time + 1e-5 + gyPre_2.rise_time, gyPre_2.rise_time + gyPre_2.flat_time + gyPre_2.fall_time + g_ro.flat_time + 1e-5 + gyPre_2.rise_time + gyPre_2.flat_time, gyPre_2.rise_time + gyPre_2.flat_time + gyPre_2.fall_time + g_ro.flat_time + 1e-5 + gyPre_2.rise_time + gyPre_2.flat_time + gyPre_2.fall_time ] gyPre_amp = [ 0, gyPre_2.amplitude, gyPre_2.amplitude, 0, 0, -gyPre_2.amplitude, -gyPre_2.amplitude, 0 ] # Verify if phase encoding gradient amplitude is not zero try: gyPre_all = make_extended_trapezoid(channel=enc[1], times=gyPre_times, amplitudes=gyPre_amp) flag_zerogy = 0 except: gyPre_times = [ 0, pe_dur, pe_dur + g_ro.flat_time + 1e-5, pe_dur + g_ro.flat_time + 1e-5 + pe_dur ] gyPre_amp = [0, gyPre_2.amplitude, 0, 0] flag_zerogy = 1 # *************************************************************************************** # add RF and Slice selecitve gradient seq.add_block(rf, g_ss) # add Phase and frequency encoding gradients and ADC if flag_zerogy == 1: seq.add_block(gx_all, gzrep_all, adc) else: seq.add_block(gx_all, gyPre_all, gzrep_all, adc) return seq, TR, Ny
def write_irse_interleaved_split_gradient(n=256, fov=250e-3, thk=5e-3, fa=90, te=12e-3, tr=2000e-3, ti=150e-3, slice_locations=[0], enc='xyz'): """ 2D IRSE sequence with overlapping gradient ramps and interleaved slices Inputs ------ n : integer Matrix size (isotropic) fov : float Field-of-View in [meters] thk : float Slice thickness in [meters] fa : float Flip angle in [degrees] te : float Echo Time in [seconds] tr : float Repetition Time in [seconds] ti : float Inversion Time in [seconds] slice_locations : array_like Array of slice locations from isocenter in [meters] enc : str Spatial encoding directions; 1st - readout; 2nd - phase encoding; 3rd - slice select Use str with any permutation of x, y, and z to obtain orthogonal slices e.g. The default 'xyz' means axial(z) slice with readout in x and phase encoding in y Returns ------- seq : pypulseq.Sequence.sequence Sequence object Output sequence object. Can be saved with seq.write('file_name.seq') sl_order : numpy.ndarray Randomly generated slice order. Useful for reconstruction. """ # ========= # SYSTEM LIMITS # ========= # Set the hardware limits and initialize sequence object dG = 250e-6 # Fixed ramp time for all gradients system = Opts(max_grad=32, grad_unit='mT/m', max_slew=130, slew_unit='T/m/s', rf_ringdown_time=100e-6, rf_dead_time=100e-6, adc_dead_time=10e-6) seq = Sequence(system) # ========= # TIME CALCULATIONS # ========= readout_time = 6.4e-3 + 2 * system.adc_dead_time t_ex = 2.5e-3 t_exwd = t_ex + system.rf_ringdown_time + system.rf_dead_time t_ref = 2e-3 t_refwd = t_ref + system.rf_ringdown_time + system.rf_dead_time t_sp = 0.5 * (te - readout_time - t_refwd) t_spex = 0.5 * (te - t_exwd - t_refwd) fsp_r = 1 fsp_s = 0.5 # ========= # ENCODING DIRECTIONS # ========== ch_ro = enc[0] ch_pe = enc[1] ch_ss = enc[2] # ========= # RF AND GRADIENT SHAPES - BASED ON RESOLUTION REQUIREMENTS : kmax and Npe # ========= # RF Phases rf_ex_phase = np.pi / 2 rf_ref_phase = 0 # Excitation phase (90 deg) flip_ex = 90 * np.pi / 180 rf_ex, gz, _ = make_sinc_pulse(flip_angle=flip_ex, system=system, duration=t_ex, slice_thickness=thk, apodization=0.5, time_bw_product=4, phase_offset=rf_ex_phase, return_gz=True) gs_ex = make_trapezoid(channel=ch_ss, system=system, amplitude=gz.amplitude, flat_time=t_exwd, rise_time=dG) # Refocusing (same gradient & RF is used for initial inversion) flip_ref = fa * np.pi / 180 rf_ref, gz, _ = make_sinc_pulse(flip_angle=flip_ref, system=system, duration=t_ref, slice_thickness=thk, apodization=0.5, time_bw_product=4, phase_offset=rf_ref_phase, use='refocusing', return_gz=True) gs_ref = make_trapezoid(channel=ch_ss, system=system, amplitude=gs_ex.amplitude, flat_time=t_refwd, rise_time=dG) ags_ex = gs_ex.area / 2 gs_spr = make_trapezoid(channel=ch_ss, system=system, area=ags_ex * (1 + fsp_s), duration=t_sp, rise_time=dG) gs_spex = make_trapezoid(channel=ch_ss, system=system, area=ags_ex * fsp_s, duration=t_spex, rise_time=dG) delta_k = 1 / fov k_width = n * delta_k gr_acq = make_trapezoid(channel=ch_ro, system=system, flat_area=k_width, flat_time=readout_time, rise_time=dG) adc = make_adc(num_samples=n, duration=gr_acq.flat_time - 40e-6, delay=20e-6) gr_spr = make_trapezoid(channel=ch_ro, system=system, area=gr_acq.area * fsp_r, duration=t_sp, rise_time=dG) gr_spex = make_trapezoid(channel=ch_ro, system=system, area=gr_acq.area * (1 + fsp_r), duration=t_spex, rise_time=dG) agr_spr = gr_spr.area agr_preph = gr_acq.area / 2 + agr_spr gr_preph = make_trapezoid(channel=ch_ro, system=system, area=agr_preph, duration=t_spex, rise_time=dG) phase_areas = (np.arange(n) - n / 2) * delta_k # Split gradients and recombine into blocks gs1_times = [0, gs_ex.rise_time] gs1_amp = [0, gs_ex.amplitude] gs1 = make_extended_trapezoid(channel=ch_ss, times=gs1_times, amplitudes=gs1_amp) gs2_times = [0, gs_ex.flat_time] gs2_amp = [gs_ex.amplitude, gs_ex.amplitude] gs2 = make_extended_trapezoid(channel=ch_ss, times=gs2_times, amplitudes=gs2_amp) gs3_times = [ 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 = [ gs_ex.amplitude, gs_spex.amplitude, gs_spex.amplitude, gs_ref.amplitude ] gs3 = make_extended_trapezoid(channel=ch_ss, times=gs3_times, amplitudes=gs3_amp) gs4_times = [0, gs_ref.flat_time] gs4_amp = [gs_ref.amplitude, gs_ref.amplitude] gs4 = make_extended_trapezoid(channel=ch_ss, times=gs4_times, amplitudes=gs4_amp) gs5_times = [ 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 = [gs_ref.amplitude, gs_spr.amplitude, gs_spr.amplitude, 0] gs5 = make_extended_trapezoid(channel=ch_ss, times=gs5_times, amplitudes=gs5_amp) gs7_times = [ 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 = [0, gs_spr.amplitude, gs_spr.amplitude, gs_ref.amplitude] gs7 = make_extended_trapezoid(channel=ch_ss, times=gs7_times, amplitudes=gs7_amp) gr3 = gr_preph gr5_times = [ 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 = [0, gr_spr.amplitude, gr_spr.amplitude, gr_acq.amplitude] gr5 = make_extended_trapezoid(channel=ch_ro, times=gr5_times, amplitudes=gr5_amp) gr6_times = [0, readout_time] gr6_amp = [gr_acq.amplitude, gr_acq.amplitude] gr6 = make_extended_trapezoid(channel=ch_ro, times=gr6_times, amplitudes=gr6_amp) gr7_times = [ 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 = [gr_acq.amplitude, gr_spr.amplitude, gr_spr.amplitude, 0] gr7 = make_extended_trapezoid(channel=ch_ro, times=gr7_times, amplitudes=gr7_amp) t_ex = gs1.t[-1] + gs2.t[-1] + gs3.t[-1] t_ref = gs4.t[-1] + gs5.t[-1] + gs7.t[-1] + readout_time t_end = gs4.t[-1] + gs5.t[-1] # Calculate maximum number of slices that can fit in one TR # Without spoilers on each side TE_prime = 0.5 * calc_duration(gs_ref) + ti + te + 0.5 * readout_time + np.max( [calc_duration(gs7), calc_duration(gr7)]) + \ calc_duration(gs4) + calc_duration(gs5) ns_per_TR = np.floor(tr / TE_prime) print('Number of slices that can be accommodated = ' + str(ns_per_TR)) # Lengthen TR to accommodate slices if needed, and display message n_slices = len(slice_locations) if (ns_per_TR < n_slices): print( f'TR too short, adapted to include all slices to: {n_slices * TE_prime + 50e-6} s' ) TR = round(n_slices * TE_prime + 50e-6, ndigits=5) print('New TR = ' + str(TR)) ns_per_TR = np.floor(TR / TE_prime) if (n_slices < ns_per_TR): ns_per_TR = n_slices # randperm so that adjacent slices do not get excited one after the other sl_order = np.random.permutation(n_slices) print('Number of slices acquired per TR = ' + str(ns_per_TR)) # Delays TI_fill = ti - (0.5 * calc_duration(gs_ref) + calc_duration(gs1) + 0.5 * calc_duration(gs2)) delay_TI = make_delay(TI_fill) TR_fill = tr - ns_per_TR * TE_prime delay_TR = make_delay(TR_fill) for k_ex in range(n): phase_area = phase_areas[k_ex] gp_pre = make_trapezoid(channel=ch_pe, system=system, area=phase_area, duration=t_sp, rise_time=dG) gp_rew = make_trapezoid(channel=ch_pe, system=system, area=-phase_area, duration=t_sp, rise_time=dG) s_in_TR = 0 for s in range(len(sl_order)): # rf_ex.freq_offset = gs_ex.amplitude * slice_thickness * (sl_order[s] - (n_slices - 1) / 2) # rf_ref.freq_offset = gs_ref.amplitude * slice_thickness * (sl_order[s] - (n_slices - 1) / 2) rf_ex.freq_offset = gs_ex.amplitude * slice_locations[sl_order[s]] rf_ref.freq_offset = gs_ref.amplitude * slice_locations[ sl_order[s]] rf_ex.phase_offset = rf_ex_phase - 2 * np.pi * rf_ex.freq_offset * calc_rf_center( rf_ex)[0] rf_ref.phase_offset = rf_ref_phase - 2 * np.pi * rf_ref.freq_offset * calc_rf_center( rf_ref)[0] # Inversion using refocusing pulse seq.add_block(gs_ref, rf_ref) seq.add_block(delay_TI) # SE portion seq.add_block(gs1) seq.add_block(gs2, rf_ex) seq.add_block(gs3, gr3) seq.add_block(gs4, rf_ref) seq.add_block(gs5, gr5, gp_pre) seq.add_block(gr6, adc) seq.add_block(gs7, gr7, gp_rew) seq.add_block(gs4) seq.add_block(gs5) s_in_TR += 1 if (s_in_TR == ns_per_TR): seq.add_block(delay_TR) s_in_TR = 0 # Check timing to make sure sequence runs on scanner seq.check_timing() return seq, sl_order
def write_tse(n=256, fov=250e-3, thk=5e-3, fa_exc=90, fa_ref=180, te=50e-3, tr=2000e-3, slice_locations=[0], turbo_factor=4, enc='xyz'): """ 2D TSE sequence with interleaved slices and user-defined turbo factor Inputs ------ n : integer Matrix size (isotropic) fov : float Field-of-View in [meters] thk : float Slice thickness in [meters] fa_exc : float Initial excitation flip angle in [degrees] fa_ref : float All following flip angles for spin echo refocusing in [degrees] te : float Echo Time in [seconds] tr : float Repetition Time in [seconds] slice_locations : array_like Array of slice locations from isocenter in [meters] turbo_factor : integer Number of echoes per TR enc : str Spatial encoding directions; 1st - readout; 2nd - phase encoding; 3rd - slice select Use str with any permutation of x, y, and z to obtain orthogonal slices e.g. The default 'xyz' means axial(z) slice with readout in x and phase encoding in y Returns ------- seq : pypulseq.Sequence.sequence Sequence object Output sequence object. Can be saved with seq.write('file_name.seq') pe_order : numpy.ndarray (turbo_factor) x (number_of_excitations) matrix of phase encoding order This is required for phase sorting before ifft2 reconstruction. """ # Set system limits ramp_time = 250e-6 # Ramp up/down time for all gradients where this is specified system = Opts( max_grad=32, grad_unit='mT/m', max_slew=130, slew_unit='T/m/s', rf_ringdown_time=100e-6, # changed from 30e-6 rf_dead_time=100e-6, adc_dead_time=20e-6) # Initialize sequence seq = Sequence(system) # Spatial encoding directions ch_ro = enc[0] ch_pe = enc[1] ch_ss = enc[2] # Derived parameters Nf, Np = (n, n) delta_k = 1 / fov k_width = Nf * delta_k # Number of echoes per excitation (i.e. turbo factor) n_echo = turbo_factor # Readout duration readout_time = 6.4e-3 + 2 * system.adc_dead_time # Excitation pulse duration t_ex = 2.5e-3 t_exwd = t_ex + system.rf_ringdown_time + system.rf_dead_time # Refocusing pulse duration t_ref = 2e-3 t_refwd = t_ref + system.rf_ringdown_time + system.rf_dead_time # Time gaps for spoilers t_sp = 0.5 * (te - readout_time - t_refwd ) # time gap between pi-pulse and readout t_spex = 0.5 * (te - t_exwd - t_refwd ) # time gap between pi/2-pulse and pi-pulse # Spoiling factors fsp_r = 1 # in readout direction per refocusing fsp_s = 0.5 # in slice direction per refocusing # 3. Calculate sequence components ## Slice-selective RF pulses & gradient #* RF pulses with zero frequency shift are created. The frequency is then changed before adding the pulses to sequence blocks for each slice. # 90 deg pulse (+y') rf_ex_phase = np.pi / 2 flip_ex = fa_exc * np.pi / 180 rf_ex, g_ss, _ = make_sinc_pulse(flip_angle=flip_ex, system=system, duration=t_ex, slice_thickness=thk, apodization=0.5, time_bw_product=4, phase_offset=rf_ex_phase, return_gz=True) gs_ex = make_trapezoid(channel=ch_ss, system=system, amplitude=g_ss.amplitude, flat_time=t_exwd, rise_time=ramp_time) # 180 deg pulse (+x') rf_ref_phase = 0 flip_ref = fa_ref * np.pi / 180 rf_ref, gz, _ = make_sinc_pulse(flip_angle=flip_ref, system=system, duration=t_ref, slice_thickness=thk, apodization=0.5, time_bw_product=4, phase_offset=rf_ref_phase, use='refocusing', return_gz=True) gs_ref = make_trapezoid(channel=ch_ss, system=system, amplitude=gs_ex.amplitude, flat_time=t_refwd, rise_time=ramp_time) rf_ex, g_ss, _ = make_sinc_pulse(flip_angle=flip_ex, system=system, duration=t_ex, slice_thickness=thk, apodization=0.5, time_bw_product=4, phase_offset=rf_ex_phase, return_gz=True) ## Make gradients and ADC # gs_spex : slice direction spoiler between initial excitation and 1st 180 pulse # gs_spr : slice direction spoiler between 180 pulses # gr_spr : readout direction spoiler; area is (fsp_r) x (full readout area) # SS spoiling ags_ex = gs_ex.area / 2 gs_spr = make_trapezoid(channel=ch_ss, system=system, area=ags_ex * (1 + fsp_s), duration=t_sp, rise_time=ramp_time) gs_spex = make_trapezoid(channel=ch_ss, system=system, area=ags_ex * fsp_s, duration=t_spex, rise_time=ramp_time) # Readout gradient and ADC gr_acq = make_trapezoid(channel=ch_ro, system=system, flat_area=k_width, flat_time=readout_time, rise_time=ramp_time) # No need for risetime delay since it is set at beginning of flattime; delay is ADC deadtime adc = make_adc(num_samples=Nf, duration=gr_acq.flat_time - 40e-6, delay=20e-6) # RO spoiling gr_spr = make_trapezoid(channel=ch_ro, system=system, area=gr_acq.area * fsp_r, duration=t_sp, rise_time=ramp_time) # Following is not used anywhere # gr_spex = make_trapezoid(channel=ch_ro, system=system, area=gr_acq.area * (1 + fsp_r), duration=t_spex, rise_time=ramp_time) # Prephasing gradient in RO direction agr_preph = gr_acq.area / 2 + gr_spr.area gr_preph = make_trapezoid(channel=ch_ro, system=system, area=agr_preph, duration=t_spex, rise_time=ramp_time) # Phase encoding areas # Need to export the pe_order for reconsturuction # Number of readouts/echoes to be produced per TR n_ex = math.floor(Np / n_echo) pe_steps = np.arange(1, n_echo * n_ex + 1) - 0.5 * n_echo * n_ex - 1 if divmod(n_echo, 2)[1] == 0: # If there is an even number of echoes pe_steps = np.roll(pe_steps, -round(n_ex / 2)) pe_order = pe_steps.reshape((n_ex, n_echo), order='F').T savemat('pe_info.mat', {'order': pe_order, 'dims': ['n_echo', 'n_ex']}) phase_areas = pe_order * delta_k # Split gradients and recombine into blocks # gs1 : ramp up of gs_ex gs1_times = [0, gs_ex.rise_time] gs1_amp = [0, gs_ex.amplitude] gs1 = make_extended_trapezoid(channel=ch_ss, times=gs1_times, amplitudes=gs1_amp) # gs2 : flat part of gs_ex gs2_times = [0, gs_ex.flat_time] gs2_amp = [gs_ex.amplitude, gs_ex.amplitude] gs2 = make_extended_trapezoid(channel=ch_ss, times=gs2_times, amplitudes=gs2_amp) # gs3 : Bridged slice pre-spoiler gs3_times = [ 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 = [ gs_ex.amplitude, gs_spex.amplitude, gs_spex.amplitude, gs_ref.amplitude ] gs3 = make_extended_trapezoid(channel=ch_ss, times=gs3_times, amplitudes=gs3_amp) # gs4 : Flat slice selector for pi-pulse gs4_times = [0, gs_ref.flat_time] gs4_amp = [gs_ref.amplitude, gs_ref.amplitude] gs4 = make_extended_trapezoid(channel=ch_ss, times=gs4_times, amplitudes=gs4_amp) # gs5 : Bridged slice post-spoiler gs5_times = [ 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 = [gs_ref.amplitude, gs_spr.amplitude, gs_spr.amplitude, 0] gs5 = make_extended_trapezoid(channel=ch_ss, times=gs5_times, amplitudes=gs5_amp) # gs7 : The gs3 for next pi-pulse gs7_times = [ 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 = [0, gs_spr.amplitude, gs_spr.amplitude, gs_ref.amplitude] gs7 = make_extended_trapezoid(channel=ch_ss, times=gs7_times, amplitudes=gs7_amp) # gr3 : pre-readout gradient gr3 = gr_preph # gr5 : Readout post-spoiler gr5_times = [ 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 = [0, gr_spr.amplitude, gr_spr.amplitude, gr_acq.amplitude] gr5 = make_extended_trapezoid(channel=ch_ro, times=gr5_times, amplitudes=gr5_amp) # gr6 : Flat readout gradient gr6_times = [0, readout_time] gr6_amp = [gr_acq.amplitude, gr_acq.amplitude] gr6 = make_extended_trapezoid(channel=ch_ro, times=gr6_times, amplitudes=gr6_amp) # gr7 : the gr3 for next repeat gr7_times = [ 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 = [gr_acq.amplitude, gr_spr.amplitude, gr_spr.amplitude, 0] gr7 = make_extended_trapezoid(channel=ch_ro, times=gr7_times, amplitudes=gr7_amp) # Timing (delay) calculations # delay_TR : delay at the end of each TSE pulse train (i.e. each TR) t_ex = gs1.t[-1] + gs2.t[-1] + gs3.t[-1] t_ref = gs4.t[-1] + gs5.t[-1] + gs7.t[-1] + readout_time t_end = gs4.t[-1] + gs5.t[-1] TE_train = t_ex + n_echo * t_ref + t_end TR_fill = (tr - n_slices * TE_train) / n_slices TR_fill = system.grad_raster_time * round( TR_fill / system.grad_raster_time) if TR_fill < 0: TR_fill = 1e-3 print( 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) # Add building blocks to sequence for k_ex in range(n_ex + 1): # For each TR for s in range(n_slices): # For each slice (multislice) if slice_locations is None: rf_ex.freq_offset = gs_ex.amplitude * thk * ( s - (n_slices - 1) / 2) rf_ref.freq_offset = gs_ref.amplitude * thk * ( s - (n_slices - 1) / 2) rf_ex.phase_offset = rf_ex_phase - 2 * np.pi * rf_ex.freq_offset * calc_rf_center( rf_ex)[0] rf_ref.phase_offset = rf_ref_phase - 2 * np.pi * rf_ref.freq_offset * calc_rf_center( rf_ref)[0] else: rf_ex.freq_offset = gs_ex.amplitude * slice_locations[s] rf_ref.freq_offset = gs_ref.amplitude * slice_locations[s] rf_ex.phase_offset = rf_ex_phase - 2 * np.pi * rf_ex.freq_offset * calc_rf_center( rf_ex)[0] rf_ref.phase_offset = rf_ref_phase - 2 * np.pi * rf_ref.freq_offset * calc_rf_center( rf_ref)[0] seq.add_block(gs1) seq.add_block(gs2, rf_ex) # make sure gs2 has channel ch_ss seq.add_block(gs3, gr3) for k_echo in range(n_echo): # For each echo if k_ex > 0: phase_area = phase_areas[k_echo, k_ex - 1] else: # First TR is skipped so zero phase encoding is needed phase_area = 0.0 # 0.0 and not 0 because -phase_area should successfully result in negative zero gp_pre = make_trapezoid(channel=ch_pe, system=system, area=phase_area, duration=t_sp, rise_time=ramp_time) # print('gp_pre info: ', gp_pre) gp_rew = make_trapezoid(channel=ch_pe, system=system, area=-phase_area, duration=t_sp, rise_time=ramp_time) seq.add_block(gs4, rf_ref) seq.add_block(gs5, gr5, gp_pre) # Skipping first TR 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) # Check timing to make sure sequence runs on scanner seq.check_timing() return seq, pe_order
slew_unit='T/m/s', rf_ringdown_time=30e-6, rf_dead_time=100e-6, adc_dead_time=20e-6) # Sequence components FA = 10 # deg thk = 5e-3 #rf, gz, gz_reph = make_sinc_pulse(flip_angle=FA*np.pi/180, duration=1e-3, slice_thickness=thk, apodization=0.5, # time_bw_product=2, center_pos=1, system=system, return_gz=True) rf, gz, gz_reph = make_sinc_pulse(flip_angle=FA * np.pi / 180, duration=2e-3, slice_thickness=thk, apodization=0.5, time_bw_product=2, center_pos=0.5, system=system, return_gz=True) GAMMA_BAR = GAMMA / (2 * np.pi) rf_dt = rf.t[1] - rf.t[0] print(f'Slice bw : {thk*gz.amplitude} Hz') bwbw = 2 * thk * gz.amplitude signals, m = simulate_rf(bw_spins=bwbw, n_spins=200, pdt1t2=(1, 0, 0), flip_angle=90, dt=rf_dt, solver="RK45", pulse_type='custom', pulse_shape=rf.signal / GAMMA_BAR,
def make_code_sequence(FOV=250e-3, N=64, TR=100e-3, flip=15, enc_type='3D', rf_type='gauss', os_factor=1, save_seq=True): """ 3D or 2D (projection) CODE sequence (cite! ) Parameters ---------- FOV : float, default=0.25 Isotropic image field-of-view in [meters] N : int, default=64 Isotropic image matrix size. Also used for base readout sample number (2x that of Nyquist requirement) TR : float, default=0.1 Repetition time in [seconds] flip : float, default=15 Flip angle in [degrees] enc_type : str, default='3D' Dimensionality of sequence - '3D' or '2D' rf_type : str, default='gauss' RF pulse shape - 'gauss' or 'sinc' os_factor : float, default=1 Oversampling factor in readout The number of readout samples is the nearest integer from os_factor*N save_seq : bool, default=True Whether to save this sequence as a .seq file Returns ------- seq : Sequence PyPulseq CODE sequence object TE : float Echo time of generated sequence in [seconds] ktraj : np.ndarray 3D k-space trajectory [spoke, readout sample, 3] where the last dimension refers to spatial frequency coordinates (kx, ky, kz) For 2D projection version, disregard the last dimension. """ # System options (copied from : amri-sos service form) system = Opts(max_grad=32, grad_unit='mT/m', max_slew=130, slew_unit='T/m/s', rf_ringdown_time=30e-6, rf_dead_time=100e-6, adc_dead_time=20e-6) # Parameters # Radial sampling set-up dx = FOV / N if enc_type == '3D': Ns, Ntheta, Nphi = get_radk_params_3D(dx, FOV) # Radial encoding details thetas = np.linspace(0, np.pi, Ntheta, endpoint=False) elif enc_type == "2D": Nphi = get_radk_params_2D(N) Ntheta = 1 Ns = Nphi thetas = np.array([np.pi / 2]) # Zero z-gradient. phis = np.linspace(0, 2 * np.pi, Nphi, endpoint=False) print( f'{enc_type} acq.: using {Ntheta} thetas and {Nphi} phis - {Ns} spokes in total.' ) # Make sequence components # Slice-selective RF pulse: 100 us, 15 deg gauss pulse FA = flip * pi / 180 rf_dur = 100e-6 thk_slab = FOV if rf_type == 'gauss': rf, g_pre, __ = make_gauss_pulse(flip_angle=FA, duration=rf_dur, slice_thickness=thk_slab, system=system, return_gz=True) elif rf_type == 'sinc': rf, g_pre, __ = make_sinc_pulse(flip_angle=FA, duration=rf_dur, slice_thickness=thk_slab, apodization=0.5, time_bw_product=4, system=system, return_gz=True) else: raise ValueError("RF type can only be sinc or gauss") # Round off timing to system requirements rf.delay = system.grad_raster_time * round( rf.delay / system.grad_raster_time) g_pre.delay = system.grad_raster_time * round( g_pre.delay / system.grad_raster_time) # Readout gradient (5 ms readout time) #ro_time = 5e-3 #ro_rise_time = 20e-6 dr = FOV / N kmax = (1 / dr) / 2 Nro = np.round(os_factor * N) # Asymmetry! (0 - fully rewound; 1 - half-echo) ro_asymmetry = (kmax - g_pre.area / 2) / (kmax + g_pre.area / 2) s = np.round(ro_asymmetry * Nro / 2) / (Nro / 2) ro_duration = 2.5e-3 dkp = (1 / FOV) / (1 + s) ro_area = N * dkp g_ro = make_trapezoid(channel='x', flat_area=ro_area, flat_time=ro_duration, system=system) adc = make_adc(num_samples=Nro, duration=g_ro.flat_time, delay=g_ro.rise_time, system=system) # Readout gradient & ADC #adc_dwell = 10e-6 # 10 us sampling interval (100 KHz readout bandwidth) #g_ro = make_trapezoid(channel='x', system=system, amplitude=dk/adc_dwell, flat_time=adc_dwell*int(N/2), rise_time=ro_rise_time) #flat_delay = (0.5*g_pre.area - 0.5*ro_rise_time*g_ro.amplitude) / g_ro.amplitude #flat_delay = system.grad_raster_time * round(flat_delay / system.grad_raster_time) # Make sure the first ADC is at center of k-space. #adc = make_adc(system=system, num_samples=Nro, dwell = adc_dwell, delay = g_ro.rise_time+flat_delay) # Delay TRfill = TR - calc_duration(g_pre) - calc_duration(g_ro) delayTR = make_delay(TRfill) #TE = 0.5 * calc_duration(g_pre) + adc.delay TE = 0.5 * calc_duration(g_pre) + g_ro.rise_time + adc.dwell * Nro / 2 * ( 1 - s) print(f'TE obtained: {TE*1e3} ms') # Initiate storage of trajectory ktraj = np.zeros([Ns, int(adc.num_samples), 3]) extra_delay_time = 0 # Construct sequence # Initiate sequence seq = Sequence(system) u = 0 # For each direction for phi in phis: for theta in thetas: # Construct oblique gradient ug = get_3d_unit_grad(theta, phi) g_pre_x, g_pre_y, g_pre_z = make_oblique_gradients(gradient=g_pre, unit_grad=-1 * ug) g_ro_x, g_ro_y, g_ro_z = make_oblique_gradients(gradient=g_ro, unit_grad=ug) # Add components seq.add_block(rf, g_pre_x, g_pre_y, g_pre_z) seq.add_block(make_delay(extra_delay_time)) seq.add_block(g_ro_x, g_ro_y, g_ro_z, adc) seq.add_block(delayTR) # Store trajectory gpxh, gpyh, gpzh = make_oblique_gradients(gradient=g_pre, unit_grad=-0.5 * ug) ktraj[u, :, :] = get_ktraj_3d_rew_delay(g_ro_x, gpxh, g_ro_y, gpyh, g_ro_z, gpzh, adc) u += 1 # Display sequence plot #seq.plot(time_range=[-TR/2,1.5*TR]) # Test sequence validity #out_text = seq.test_report() #print(out_text) # Save sequence if save_seq: seq.write( f'seqs/code{enc_type}_{rf_type}_TR{TR*1e3:.0f}_TE{TE*1e3:.2f}_FA{flip}_N{N}_delay{extra_delay_time*1e3}ms.seq' ) savemat( f'seqs/ktraj_code{enc_type}_{rf_type}_TR{TR*1e3:.0f}_TE{TE*1e3:.2f}_FA{flip}_N{N}.mat', {'ktraj': ktraj}) return seq, TE, ktraj
def write_UTE_3D_rf_spoiled(N=64, FOV=250e-3, slab_thk=250e-3, FA=10, TR=10e-3, ro_asymmetry=0.97, os_factor=1, rf_type='sinc', rf_dur=1e-3, use_half_pulse=True, save_seq=True): """ Parameters ---------- N : int, default=64 Matrix size FOV : float, default=0.25 Field-of-view in [meters] slab_thk : float, default=0.25 Slab thickness in [meters] FA : float, default=10 Flip angle in [degrees] TR : float, default=0.01 Repetition time in [seconds] ro_asymmetry : float, default=0.97 The ratio A/B where a A/(A+B) portion of 2*Kmax is omitted and B/(A+B) is acquired. os_factor : float, default=1 Oversampling factor in readout The number of readout samples is the nearest integer from os_factor*N rf_type : str, default='sinc' RF pulse shape - 'sinc', 'gauss', or 'rect' rf_dur : float, default=0.001 RF pulse duration use_half_pulse : bool, default=True Whether to use half pulse excitation for shorter TE; This doubles both the number of excitations and acquisition time save_seq : bool, default=True Whether to save this sequence as a .seq file Returns ------- seq : Sequence PyPulseq 2D UTE sequence object TE : float Echo time of generated sequence in [seconds] ktraj : np.ndarray 3D k-space trajectory [spoke, readout sample, 3] where the last dimension refers to spatial frequency coordinates (kx, ky, kz) """ # Adapted from pypulseq demo write_ute.py (obtained mid-2021) system = Opts(max_grad=32, grad_unit='mT/m', max_slew=130, slew_unit='T/m/s', rf_ringdown_time=30e-6, rf_dead_time=100e-6, adc_dead_time=20e-6) seq = Sequence(system=system) # Derived parameters dx = FOV / N Ns, Ntheta, Nphi = get_radk_params_3D(dx, FOV) # Make this function print(f'Using {Ns} spokes, with {Ntheta} thetas and {Nphi} phis') # Spoke angles thetas = np.linspace(0, np.pi, Ntheta, endpoint=False) phis = np.linspace(0, 2 * np.pi, Nphi, endpoint=False) ro_duration = 2.5e-3 ro_os = os_factor # Oversampling factor rf_spoiling_inc = 117 # RF spoiling increment value. if use_half_pulse: cp = 1 else: cp = 0.5 tbw = 2 # Sequence components if rf_type == 'sinc': rf, gz, gz_reph = make_sinc_pulse(flip_angle=FA * np.pi / 180, duration=rf_dur, slice_thickness=slab_thk, apodization=0.5, time_bw_product=tbw, center_pos=cp, system=system, return_gz=True) gz_ramp_reph = make_trapezoid(channel='z', area=-gz.fall_time * gz.amplitude / 2, system=system) elif rf_type == 'rect': rf = make_block_pulse(flip_angle=FA * np.pi / 180, duration=rf_dur, slice_thickness=slab_thk, return_gz=False) elif rf_type == 'gauss': rf, gz, gz_reph = make_gauss_pulse(flip_angle=FA * np.pi / 180, duration=rf_dur, slice_thickness=slab_thk, system=system, return_gz=True) gz_ramp_reph = make_trapezoid(channel='z', area=-gz.fall_time * gz.amplitude / 2, system=system) # Asymmetry! (0 - fully rewound; 1 - hall-echo) Nro = np.round(ro_os * N) # Number of readout points s = np.round(ro_asymmetry * Nro / 2) / (Nro / 2) dk = (1 / FOV) / (1 + s) ro_area = N * dk gro = make_trapezoid(channel='x', flat_area=ro_area, flat_time=ro_duration, system=system) adc = make_adc(num_samples=Nro, duration=gro.flat_time, delay=gro.rise_time, system=system) gro_pre = make_trapezoid(channel='x', area=-(gro.area - ro_area) / 2 - (ro_area / 2) * (1 - s), system=system) # Spoilers gro_spoil = make_trapezoid(channel='x', area=0.2 * N * dk, system=system) # Calculate timing TE = gro.rise_time + adc.dwell * Nro / 2 * (1 - s) if rf_type == 'sinc' or rf_type == 'gauss': if use_half_pulse: TE += gz.fall_time + calc_duration(gro_pre) else: TE += calc_duration(gz) / 2 + calc_duration(gro_pre) delay_TR = np.ceil( (TR - calc_duration(gro_pre) - calc_duration(gz) - calc_duration(gro)) / seq.grad_raster_time) * seq.grad_raster_time elif rf_type == 'rect': TE += calc_duration(gro_pre) delay_TR = np.ceil((TR - calc_duration(gro_pre) - calc_duration(gro)) / seq.grad_raster_time) * seq.grad_raster_time assert np.all(delay_TR >= calc_duration(gro_spoil) ) # The TR delay starts at the same time as the spoilers! print(f'TE = {TE * 1e6:.0f} us') C = int(use_half_pulse) + 1 # Starting RF phase and increments rf_phase = 0 rf_inc = 0 Nline = Ntheta * Nphi ktraj = np.zeros([Nline, int(adc.num_samples), 3]) u = 0 for th in range(Ntheta): for ph in range(Nphi): unit_grad = np.zeros(3) unit_grad[0] = np.sin(thetas[th]) * np.cos(phis[ph]) unit_grad[1] = np.sin(thetas[th]) * np.sin(phis[ph]) unit_grad[2] = np.cos(thetas[th]) # Two repeats if using half pulse for c in range(C): # RF spoiling rf.phase_offset = (rf_phase / 180) * np.pi adc.phase_offset = (rf_phase / 180) * np.pi rf_inc = np.mod(rf_inc + rf_spoiling_inc, 360.0) rf_phase = np.mod(rf_phase + rf_inc, 360.0) # Rewinder and readout gradients, vectorized gpx, gpy, gpz = make_oblique_gradients(gro_pre, unit_grad) grx, gry, grz = make_oblique_gradients(gro, unit_grad) gsx, gsy, gsz = make_oblique_gradients(gro_spoil, unit_grad) if rf_type == 'sinc' or rf_type == 'gauss': if use_half_pulse: # Reverse slice select amplitude (always z = 0) modify_gradient(gz, scale=-1) modify_gradient(gz_ramp_reph, scale=-1) gpz_reph = copy.deepcopy(gpz) modify_gradient(gpz_reph, scale=(gpz.area + gz_ramp_reph.area) / gpz.area) else: gpz_reph = copy.deepcopy(gpz) modify_gradient(gpz_reph, scale=(gpz.area + gz_reph.area) / gpz.area) seq.add_block(rf, gz) seq.add_block(gpx, gpy, gpz_reph) elif rf_type == 'rect': seq.add_block(rf) seq.add_block(gpx, gpy, gpz) seq.add_block(grx, gry, grz, adc) seq.add_block(gsx, gsy, gsz, make_delay(delay_TR)) #print(f'Spokes: {u+1}/{Nline}') ktraj[u, :, :] = get_ktraj_3d(grx, gry, grz, adc, [gpx], [gpy], [gpz]) u += 1 ok, error_report = seq.check_timing( ) # Check whether the timing of the sequence is correct if ok: print('Timing check passed successfully') else: print('Timing check failed. Error listing follows:') [print(e) for e in error_report] if save_seq: seq.write( f'ute_3d_rf-{rf_type}_rw_s{s}_N{N}_FOV{FOV}_TR{TR}_TE{TE}_C={use_half_pulse+1}.seq' ) savemat( f'ktraj_ute_3d_rw_s{s}_N{N}_FOV{FOV}_TR{TR}_TE{TE}_C={use_half_pulse+1}.mat', {'ktraj': ktraj}) return seq, TE, ktraj
def gre_refscan(seq, meta_file=None, system=Opts(), params=None): # decrease slew rate a bit save_slew = system.max_slew system.max_slew = 100 * system.gamma if params is None: params = { "fov": 210e-3, "res": 3e-3, "flip_angle": 12, "rf_dur": 1e-3, "tbp": 2, "slices": 1, "slice_res": 2e-3, "dist_fac": 0, "readout_bw": 600 } # RF rf, gz, gz_reph, rf_del = make_sinc_pulse( flip_angle=params["flip_angle"] * math.pi / 180, duration=params["rf_dur"], slice_thickness=params["slice_res"], apodization=0.5, time_bw_product=params["tbp"], system=system, return_gz=True, return_delay=True) # Calculate readout gradient and ADC parameters delta_k = 1 / params["fov"] Nx = Ny = int(params["fov"] / params["res"] + 0.5) samples = 2 * Nx # 2x oversampling gx_flat_time_us = int(1e6 / params["readout_bw"]) # readout_bw is in Hz/Px dwelltime = ph.trunc_to_raster(1e-6 * gx_flat_time_us / samples, decimals=7) gx_flat_time = round(dwelltime * samples, 5) if (1e5 * gx_flat_time % 2 == 1): gx_flat_time += 10e-6 # even flat time diff_flat_adc = gx_flat_time - (dwelltime * samples) # Gradients gx_flat_area = Nx * delta_k * ( gx_flat_time / (dwelltime * samples)) # compensate for longer flat time than ADC gx = make_trapezoid(channel='x', flat_area=gx_flat_area, flat_time=gx_flat_time, system=system) gx_pre = make_trapezoid(channel='x', area=-gx.area / 2, duration=1.4e-3, system=system) phase_areas = (np.arange(Ny) - Ny / 2) * delta_k # reduce slew rate of spoilers to avoid stimulation gx_spoil = make_trapezoid(channel='x', area=2 * Nx * delta_k, system=system, max_slew=120 * system.gamma) gz_spoil = make_trapezoid(channel='z', area=4 / params["slice_res"], system=system, max_slew=120 * system.gamma) # take minimum TE rounded up to .1 ms min_TE = np.ceil( (gz.fall_time + gz.flat_time / 2 + calc_duration(gx_pre) + calc_duration(gx) / 2) / seq.grad_raster_time) * seq.grad_raster_time TE = ph.round_up_to_raster(min_TE, decimals=4) delay_TE = TE - min_TE # take minimum TR rounded up to .1 ms min_TR = calc_duration(gx_pre) + calc_duration(gz) + calc_duration( gx) + delay_TE + calc_duration(gx_spoil, gz_spoil) TR = ph.round_up_to_raster(min_TR, decimals=4) delay_TR = TR - min_TR # ADC with 2x oversampling adc = make_adc(num_samples=samples, dwell=dwelltime, delay=gx.rise_time + diff_flat_adc / 2, system=system) # RF spoiling rf_spoiling_inc = 117 rf_phase = 0 rf_inc = 0 # build sequence prepscans = 40 # number of dummy preparation scans if params["slices"] % 2 == 1: slc = 0 else: slc = 1 for s in range(params["slices"]): if s == int(params["slices"] / 2 + 0.5): if params["slices"] % 2 == 1: slc = 1 else: slc = 0 rf.freq_offset = gz.amplitude * params["slice_res"] * ( slc - (params["slices"] - 1) / 2) * (1 + params["dist_fac"] * 1e-2) # prepscans for d in range(prepscans): rf.phase_offset = rf_phase / 180 * np.pi adc.phase_offset = rf_phase / 180 * np.pi rf_inc = divmod(rf_inc + rf_spoiling_inc, 360.0)[1] rf_phase = divmod(rf_phase + rf_inc, 360.0)[1] seq.add_block(rf, gz, rf_del) gy_pre = make_trapezoid(channel='y', area=phase_areas[0], duration=1.4e-3, system=system) seq.add_block(gx_pre, gy_pre, gz_reph) seq.add_block(make_delay(delay_TE)) seq.add_block(gx) gy_pre.amplitude = -gy_pre.amplitude seq.add_block(make_delay(delay_TR), gx_spoil, gy_pre, gz_spoil) # imaging scans for i in range(Ny): rf.phase_offset = rf_phase / 180 * np.pi adc.phase_offset = rf_phase / 180 * np.pi rf_inc = divmod(rf_inc + rf_spoiling_inc, 360.0)[1] rf_phase = divmod(rf_phase + rf_inc, 360.0)[1] seq.add_block(rf, gz, rf_del) gy_pre = make_trapezoid(channel='y', area=phase_areas[i], duration=1.4e-3, system=system) seq.add_block(gx_pre, gy_pre, gz_reph) seq.add_block(make_delay(delay_TE)) seq.add_block(gx, adc) gy_pre.amplitude = -gy_pre.amplitude seq.add_block(make_delay(delay_TR), gx_spoil, gy_pre, gz_spoil) if meta_file is not None: acq = ismrmrd.Acquisition() acq.idx.kspace_encode_step_1 = i acq.idx.kspace_encode_step_2 = 0 # only 2D atm acq.idx.slice = slc # acq.idx.average = avg acq.setFlag(ismrmrd.ACQ_IS_PARALLEL_CALIBRATION) if i == Ny - 1: acq.setFlag(ismrmrd.ACQ_LAST_IN_SLICE) meta_file.append_acquisition(acq) slc += 2 # acquire every 2nd slice, afterwards fill slices inbetween delay_end = make_delay( d=2) # 5s delay after reference scan to allow for relaxation seq.add_block(delay_end) system.max_slew = save_slew
seq = Sequence() rf_dead_time = 100e-6 # lead time before rf can be applied rf_ringdown_time = 30e-6 # scanner specific - Siemens: coil hold time (20e-6) + frequency reset time (10e-6) system = Opts(max_grad=max_grad, grad_unit='mT/m', max_slew=max_slew, slew_unit='T/m/s', rf_dead_time=rf_dead_time, rf_ringdown_time=rf_ringdown_time) # RF rf, gz, gz_reph, rf_del = make_sinc_pulse(flip_angle=flip_angle * math.pi / 180, duration=rf_dur, slice_thickness=slice_res, apodization=0.5, time_bw_product=tbp, system=system, return_gz=True, return_delay=True) # Calculate readout gradient and ADC parameters delta_k = 1 / fov Nx = Ny = int(fov / res + 0.5) samples = 2 * Nx # 2x oversampling gx_flat_time_us = int(1e6 / readout_bw) # readout_bw is in Hz/Px dwelltime_us = gx_flat_time_us / samples gx_flat_time = round(1e-6 * dwelltime_us * samples, 5) # Gradients gx = make_trapezoid(channel='x',