rise_time=rise_time) for c in ['x', 'y', 'z'] ] # RF pulses flip_angle_sat = seq_defs['b1cwpe'] * gamma_hz * 2 * np.pi * seq_defs['tp'] sat_pulse, _ = make_block_pulse(flip_angle=flip_angle_sat, duration=seq_defs['tp'], system=sys) # ADC events pseudo_adc = make_adc( num_samples=1, duration=1e-3) # (not played out; just used to split measurements) # DELAYS post_spoil_delay = make_delay(50e-6) td_delay = make_delay(seq_defs['td']) trec_delay = make_delay(seq_defs['trec']) m0_delay = make_delay(seq_defs['trec_m0']) # Sequence object seq = Sequence() # === # RUN # === offsets_hz = seq_defs['offsets_ppm'] * gamma_hz * seq_defs[ 'b0'] # convert from ppm to Hz for offset in offsets_hz:
def make_pulseq_epi_oblique(fov, n, thk, fa, tr, te, enc='xyz', slice_locs=None, echo_type="se", n_shots=1, seg_type='blocked', write=False): """Makes an Echo Planar Imaging (EPI) sequence in any plane 2D oblique multi-slice EPI pulse sequence with Cartesian encoding Oblique means that each of slice-selection, phase encoding, and frequency encoding can point in any specified direction Parameters ---------- fov : array_like Isotropic field-of-view, or length-2 list [fov_readout, fov_phase], in meters n : array_like Isotropic matrix size, or length-2 list [n_readout, n_phase] thk : float Slice thickness in meters fa : float Flip angle in degrees tr : float Repetition time in seconds te : float Echo time in seconds enc : str or array_like, optional 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 - Use list to indicate vectors in the encoding directions for oblique slices They should be perpendicular to each other, but not necessarily unit vectors e.g. [(2,1,0),(-1,2,0),(0,0,1)] rotates the two in-plane encoding directions for an axial slice slice_locs : array_like, optional Slice locations from isocenter in meters Default is None which means a single slice at the center echo_type : str, optional {'se','gre'} Type of echo generated se (default) - spin echo (an 180 deg pulse is used) gre - gradient echo n_shots : int, optional Number of shots used to encode each slicel; default is 1 seg_type : str, optional {'blocked','interleaved'} Method to divide up k-space in the case of n_shots > 1; default is 'blocked' 'blocked' - each shot covers a rectangle, with no overlap between shots 'interleaved' - each shot samples the full k-space but with wider phase steps write : bool, optional Whether to write seq into file; default is False Returns ------- seq : Sequence Pulse sequence as a Pulseq object ro_dirs : numpy.ndarray List of 0s and 1s indicating direction of readout 0 - left to right 1 - right to left (needs to be reversed at recon) ro_order : numpy.ndarray Order in which to re-arrange the readout lines It is [] for blocked acquisition (retain original order) """ # Multi-slice, multi-shot (>=1) # TE is set to be where the trajectory crosses the center of k-space # System options kwargs_for_opts = {'max_grad': 32, 'grad_unit': 'mT/m', 'max_slew': 130, 'slew_unit': 'T/m/s', 'rf_ring_down_time': 30e-6, 'rf_dead_time': 100e-6, 'adc_dead_time': 20e-6} system = Opts(kwargs_for_opts) seq = Sequence(system) ug_fe, ug_pe, ug_ss = parse_enc(enc) # Sequence parameters Nf, Np = (n, n) if isinstance(n, int) else (n[0], n[1]) delta_k_ro, delta_k_pe = (1 / fov, 1 / fov) if isinstance(fov, float) else (1 / fov[0], 1 / fov[1]) kWidth_ro = Nf * delta_k_ro TE, TR = te, tr flip = fa * pi / 180 # RF Pulse (first) kwargs_for_sinc = {"flip_angle": flip, "system": system, "duration": 2.5e-3, "slice_thickness": thk, "apodization": 0.5, "time_bw_product": 4} rf, g_ss = make_sinc_pulse(kwargs_for_sinc, 2) g_ss_x, g_ss_y, g_ss_z = make_oblique_gradients(g_ss, ug_ss) # Readout gradients # readoutTime = Nf * 4e-6 dwell = 1e-5 readoutTime = Nf * dwell kwargs_for_g_ro = {"channel": 'x', "system": system, "flat_area": kWidth_ro, "flat_time": readoutTime} g_ro_pos = make_trapezoid(kwargs_for_g_ro) g_ro_pos_x, g_ro_pos_y, g_ro_pos_z = make_oblique_gradients(g_ro_pos, ug_fe) g_ro_neg = copy.deepcopy(g_ro_pos) modify_gradient(g_ro_neg, scale=-1) g_ro_neg_x, g_ro_neg_y, g_ro_neg_z = make_oblique_gradients(g_ro_neg, ug_fe) kwargs_for_adc = {"num_samples": Nf, "system": system, "duration": g_ro_pos.flat_time, "delay": g_ro_pos.rise_time + dwell / 2} adc = makeadc(kwargs_for_adc) pre_time = 8e-4 # 180 deg pulse for SE if echo_type == "se": # RF Pulse (180 deg for SE) flip180 = 180 * pi / 180 kwargs_for_sinc = {"flip_angle": flip180, "system": system, "duration": 2.5e-3, "slice_thickness": thk, "apodization": 0.5, "time_bw_product": 4} rf180, g_ss180 = make_sinc_pulse(kwargs_for_sinc, 2) # Slice-select direction spoilers kwargs_for_g_ss_spoil = {"channel": 'z', "system": system, "area": g_ss.area * 2, "duration": 3 * pre_time} g_ss_spoil = make_trapezoid(kwargs_for_g_ss_spoil) ## modify_gradient(g_ss_spoil, 0) ## g_ss_spoil_x, g_ss_spoil_y, g_ss_spoil_z = make_oblique_gradients(g_ss_spoil, ug_ss) # Readout rewinder ro_pre_area = g_ro_neg.area / 2 if echo_type == 'gre' else g_ro_pos.area / 2 kwargs_for_g_ro_pre = {"channel": 'x', "system": system, "area": ro_pre_area, "duration": pre_time} g_ro_pre = make_trapezoid(kwargs_for_g_ro_pre) g_ro_pre_x, g_ro_pre_y, g_ro_pre_z = make_oblique_gradients(g_ro_pre, ug_fe) # Slice-selective rephasing kwargs_for_g_ss_reph = {"channel": 'z', "system": system, "area": -g_ss.area / 2, "duration": pre_time} g_ss_reph = make_trapezoid(kwargs_for_g_ss_reph) g_ss_reph_x, g_ss_reph_y, g_ss_reph_z = make_oblique_gradients(g_ss_reph, ug_ss) # Phase encode rewinder if echo_type == 'gre': pe_max_area = (Np / 2) * delta_k_pe elif echo_type == 'se': pe_max_area = -(Np / 2) * delta_k_pe kwargs_for_g_pe_max = {"channel": 'y', "system": system, "area": pe_max_area, "duration": pre_time} g_pe_max = make_trapezoid(kwargs_for_g_pe_max) # Phase encoding blips dur = ceil(2 * sqrt(delta_k_pe / system.max_slew) / 10e-6) * 10e-6 kwargs_for_g_blip = {"channel": 'y', "system": system, "area": delta_k_pe, "duration": dur} g_blip = make_trapezoid(kwargs_for_g_blip) # Delays duration_to_center = (Np / 2) * calc_duration(g_ro_pos) + (Np - 1) / 2 * calc_duration(g_blip) # why? if echo_type == 'se': delayTE1 = TE / 2 - calc_duration(g_ss) / 2 - pre_time - calc_duration(g_ss_spoil) - calc_duration(rf180) / 2 delayTE2 = TE / 2 - calc_duration(rf180) / 2 - calc_duration(g_ss_spoil) - duration_to_center delay1 = make_delay(delayTE1) delay2 = make_delay(delayTE2) elif echo_type == 'gre': delayTE = TE - calc_duration(g_ss) / 2 - pre_time - duration_to_center delay12 = make_delay(delayTE) delayTR = TR - TE - calc_duration(rf) / 2 - duration_to_center delay3 = make_delay(delayTR) # This might be different for each rep though. Fix later ##################################################################################################### # Multi-shot calculations ro_dirs = [] ro_order = [] # Find number of lines in each block if seg_type == 'blocked': # Number of lines in each full readout block nl = ceil(Np / n_shots) # Number of k-space lines per readout if Np % nl == 0: nlines_list = nl * np.ones(n_shots) else: nlines_list = nl * np.ones(n_shots - 1) nlines_list = np.append(nlines_list, Np % nl) pe_scales = 2 * np.append([0], np.cumsum(nlines_list)[:-1]) / Np - 1 g_blip_x, g_blip_y, g_blip_z = make_oblique_gradients(g_blip, ug_pe) for nlines in nlines_list: ro_dirs = np.append(ro_dirs, ((-1) ** (np.arange(0, nlines) + 1) + 1) / 2) elif seg_type == 'interleaved': # Minimum number of lines per readout nb = floor(Np / n_shots) # Number of k-space lines per readout nlines_list = np.ones(n_shots) * nb nlines_list[:Np % n_shots] += 1 # Phase encoding scales (starts from -1; i.e. bottom left combined with pre-readout) pe_scales = 2 * np.arange(0, (Np - n_shots) / Np, 1 / Np)[0:n_shots] - 1 print(pe_scales) # Larger blips modify_gradient(g_blip, scale=n_shots) g_blip_x, g_blip_y, g_blip_z = make_oblique_gradients(g_blip, ug_pe) # ro_order = np.reshape(np.reshape(np.arange(0,Np),(),order='F'),(0,Np)) ro_order = np.zeros((nb + 1, n_shots)) ro_inds = np.arange(Np) # Readout order for recon for k in range(n_shots): cs = int(nlines_list[k]) ro_order[:cs, k] = ro_inds[:cs] ro_inds = np.delete(ro_inds, range(cs)) ro_order = ro_order.flatten()[:Np].astype(int) print(ro_order) # Readout directions in original (interleaved) order for nlines in nlines_list: ro_dirs = np.append(ro_dirs, ((-1) ** (np.arange(0, nlines) + 1) + 1) / 2) ##################################################################################################### # Add blocks for u in range(len(slice_locs)): # For each slice # Offset rf rf.freq_offset = g_ss.amplitude * slice_locs[u] for v in range(n_shots): # Find init. phase encode g_pe = copy.deepcopy(g_pe_max) modify_gradient(g_pe, pe_scales[v]) g_pe_x, g_pe_y, g_pe_z = make_oblique_gradients(g_pe, ug_pe) # First RF seq.add_block(rf, g_ss_x, g_ss_y, g_ss_z) # Pre-winder gradients pre_grads_list = [g_ro_pre_x, g_ro_pre_y, g_ro_pre_z, g_pe_x, g_pe_y, g_pe_z, g_ss_reph_x, g_ss_reph_y, g_ss_reph_z] gtx, gty, gtz = combine_trap_grad_xyz(pre_grads_list, system, pre_time) seq.add_block(gtx, gty, gtz) # 180 deg pulse and spoilers, only for Spin Echo if echo_type == 'se': # First delay seq.add_block(delay1) # Second RF : 180 deg with spoilers on both sides seq.add_block(g_ss_spoil_x, g_ss_spoil_y, g_ss_spoil_z) # why? seq.add_block(rf180) seq.add_block(g_ss_spoil_x, g_ss_spoil_y, g_ss_spoil_z) # Delay between rf180 and beginning of readout seq.add_block(delay2) # For gradient echo it's just a delay elif echo_type == 'gre': seq.add_block(delay12) # EPI readout with blips for i in range(int(nlines_list[v])): if i % 2 == 0: seq.add_block(g_ro_pos_x, g_ro_pos_y, g_ro_pos_z, adc) # ro line in the positive direction else: seq.add_block(g_ro_neg_x, g_ro_neg_y, g_ro_neg_z, adc) # ro line backwards seq.add_block(g_blip_x, g_blip_y, g_blip_z) # blip seq.add_block(delay3) # Display 1 TR # seq.plot(time_range=(0, TR)) if write: seq.write("epi_{}_FOVf{:.0f}mm_FOVp{:.0f}mm_Nf{:d}_Np{:d}_TE{:.0f}ms_TR{:.0f}ms_{:d}shots.seq" \ .format(echo_type, fov[0] * 1000, fov[1] * 1000, Nf, Np, TE * 1000, TR * 1000, n_shots)) print('EPI sequence (oblique) constructed') return seq, ro_dirs, ro_order
kwargs_for_sinc = { "flip_angle": flip, "system": system, "duration": 2e-3, "slice_thickness": slice_thickness, "apodization": 0.5, "time_bw_product": 4 } rf180, gz180 = make_sinc_pulse(kwargs_for_sinc, 2) TE, TR = 100e-3, 4408e-3 delayTE1 = TE / 2 - calc_duration(gz_reph) - calc_duration( rf) - calc_duration(rf180) / 2 delayTE2 = TE / 2 - calc_duration(gx) / 2 - calc_duration(rf180) / 2 delayTE3 = TR - TE - calc_duration(gx) delay1 = make_delay(delayTE1) delay2 = make_delay(delayTE2) delay3 = make_delay(delayTE3) for i in range(2): seq.add_block(rf, gz) kwargs_for_gy_pre = { "channel": 'y', "system": system, "area": -(Ny / 2 - i) * delta_k, "duration": readoutTime / 2 } gy_pre = make_trapezoid(kwargs_for_gy_pre) seq.add_block(gx_pre, gy_pre, gz_reph) seq.add_block(delay1) seq.add_block(rf180, gz180)
GR7_amp = [GR_acq.amplitude, GR_spr.amplitude, GR_spr.amplitude, 0] GR7 = make_extended_trapezoid(channel='x', times=GR7_times, amplitudes=GR7_amp) tex = GS1.t[-1] + GS2.t[-1] + GS3.t[-1] tref = GS4.t[-1] + GS5.t[-1] + GS7.t[-1] + readout_time tend = GS4.t[-1] + GS5.t[-1] TE_train = tex + n_echo * tref + tend 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 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) 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) 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:
for nInv in np.arange(N_inversion[0]): print('----------- Inversion %3.0f -----------' % (nInv + 1)) if nInv > 0: for nRec in np.arange(Nrecover_btwInv[nInv - 1]): seq.add_block(trig_BetweenInversion) # *************************************************************************************************** # Verify minimum possible TI # (nearest to TI_Vector[0], i.e. 100 ms) try: assert (all(TI_Vector[nInv] >= TI_min_attainable) ) # verify if TI_vector value is possbile delayINV = TI_Vector[ nInv] - TI_min_attainable # calculate delay for TI TI_Vector_real[nInv] = TI_Vector[nInv] seq.addBlock(make_delay(delayINV)) except: delayINV = 0 if nInv == 0: TI_Vector_real[nInv] = TI_min_attainable else: TI_Vector_real[nInv] = TI_min_attainable + np.diff( TI_Vector) # add increment i.e. 80 ms print('TI changed from ', TI_Vector[nInv] * 1e3, ' ms to %3.0f ms' % (TI_Vector_real[nInv] * 1e3)) # **************************************************************************************************** trig_inv = make_trigger( delay=100e-6,
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 if not np.all(delay_TR >= calc_duration(gx_spoil, gz_spoil)): raise Exception() rf_phase = 0 rf_inc = 0 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) gy_pre = make_trapezoid(channel='y', area=phase_areas[i], duration=2e-3, system=sys) 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) report = seq.test_report() print(report) # seq.calculate_kspace() # seq.write('/Users/sravan953/Documents/CU/Projects/PyPulseq/gre_pulseq.seq')
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
n_gap_te2 = n_delay_te2 - n_delay_te1 gap_te2 = n_gap_te2 / i_raster_time gap_te1 = 0 """ EPI calibration """ for s in range(n_slices): # Fat saturation if fatsat_enable: seq.add_block(rf_fs, gz_fs) # RF90 rf.freq_offset = gz.amplitude * slice_thickness * (s - (n_slices - 1) / 2) seq.add_block(rf, gz) seq.add_block(gz_reph) # Delay for RF180 seq.add_block(make_delay(delay_te1)) # RF180 seq.add_block(gz_spoil) rf180.freq_offset = gz180.amplitude * slice_thickness * ( s - (n_slices - 1) / 2) seq.add_block(rf180, gz180) seq.add_block(gz_spoil) # Delay for EPI seq.add_block(make_delay(delay_te2)) # Locate k-space - Only on the frequency encoding direction. seq.add_block(gx_pre) for i in range(Nyeff):
# Check TR delay time assert tr_delay > 0, "Such parameter configuration needs longer TR." """ EPI calibration """ for s in range(n_slices): # Fat saturation if fatsat_enable: seq.add_block(rf_fs, gz_fs) # RF90 rf.freq_offset = gz.amplitude * slice_thickness * (s - (n_slices - 1) / 2) seq.add_block(rf, gz) seq.add_block(gz_reph) # Delay for first RF180 seq.add_block(make_delay(d1)) # First RF180 seq.add_block(gz_spoil_1) rf180.freq_offset = gz180.amplitude * slice_thickness * (s - (n_slices - 1) / 2) seq.add_block(rf180, gz180) seq.add_block(gz_spoil_1) # Delay for second RF180 seq.add_block(make_delay(d2 + d3)) # Second RF180 seq.add_block(gz_spoil_2) rf180.freq_offset = gz180.amplitude * slice_thickness * (s - (n_slices - 1) / 2) seq.add_block(rf180, gz180) seq.add_block(gz_spoil_2)
spoil_amp = 0.8 * sys.max_grad # Hz/m rise_time = 1.0e-3 # spoiler rise time in seconds spoil_dur = 6.5e-3 # complete spoiler duration in seconds gx_spoil, gy_spoil, gz_spoil = [make_trapezoid(channel=c, system=sys, amplitude=spoil_amp, duration=spoil_dur, rise_time=rise_time) for c in ['x', 'y', 'z']] # RF pulses flip_angle_sat = seq_defs['b1cwpe'] * gamma_hz * 2 * np.pi * seq_defs['tp'] rf_pulse = make_block_pulse(flip_angle=flip_angle_sat, duration=seq_defs['tp'], system=sys) # ADC events pseudo_adc = make_adc(num_samples=1, duration=1e-3) # (not played out; just used to split measurements) # DELAYS trec_delay = make_delay(seq_defs['trec']) m0_delay = make_delay(seq_defs['trec_m0']) # Sequence object seq = Sequence() # === # RUN # === offsets_hz = seq_defs['offsets_ppm'] * gamma_hz * seq_defs['b0'] # convert from ppm to Hz for m, offset in enumerate(offsets_hz): # print progress/offset print(f' {m + 1} / {len(offsets_hz)} : offset {offset}')
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
2 * calc_duration(gz_spoil) + calc_duration(gz180) + delay_te2 + 2 * gdiff_dur + calc_duration(gx_pre, gy_pre) + calc_duration(gx) * Nyeff + calc_duration(gy) * (Nyeff - 1) + calc_duration(gx_crush, gz_crush))) / seq.grad_raster_time) * seq.grad_raster_time #EPI calibration for s in range(n_slices): print(s - (n_slices - 1) / 2) if fatsat_enable: seq.add_block(rf_fs, gz_fs) rf.freq_offset = gz.amplitude * slice_thickness * (s - (n_slices - 1) / 2) seq.add_block(rf, gz) seq.add_block(gz_reph) seq.add_block(make_delay(gdiff_dur + delay_te1)) seq.add_block(gz_spoil) rf180.freq_offset = gz180.amplitude * slice_thickness * ( s - (n_slices - 1) / 2) seq.add_block(rf180, gz180) seq.add_block(gz_spoil) seq.add_block(make_delay(delay_te2 + gdiff_dur)) seq.add_block(gx_pre) for i in range(1, Nyeff + 1): seq.add_block(gx, adc) seq.add_block(make_delay(calc_duration(gy))) gx.amplitude = -gx.amplitude
# Time between the gradient and the RF180. This time might be zero some times, although it is not normal. n_gap_te1 = n_delay_te1 - n_delay_te2 gap_te1 = n_gap_te1 / i_raster_time """ EPI calibration """ for s in range(n_slices): # Fat saturation if fatsat_enable: seq.add_block(rf_fs, gz_fs) # RF90 rf.freq_offset = gz.amplitude * slice_thickness * (s - (n_slices - 1) / 2) seq.add_block(rf, gz) seq.add_block(gz_reph) # Delay for RF180 seq.add_block(make_delay(delay_te1)) # RF180 seq.add_block(gz_spoil) rf180.freq_offset = gz180.amplitude * slice_thickness * ( s - (n_slices - 1) / 2) seq.add_block(rf180, gz180) seq.add_block(gz_spoil) # Delay for EPI seq.add_block(make_delay(delay_te2)) # Locate k-space - Only on the frequency encoding direction. seq.add_block(gx_pre) for i in range(Nyeff):
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 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_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
dur = ceil(2 * sqrt(delta_k / system.max_slew) / 10e-6) * 10e-6 kwargs_for_gy = {"channel": 'y', "system": system, "area": delta_k, "duration": dur} gy = make_trapezoid(kwargs_for_gy) flip = 180 * pi / 180 kwargs_for_sinc = {"flip_angle": flip, "system": system, "duration": 2.5e-3} rf180 = make_block_pulse(kwargs_for_sinc) kwargs_for_gz_spoil = {"channel": 'z', "system": system, "area": gz.area * 2, "duration": 3 * pre_time} gz_spoil = make_trapezoid(kwargs_for_gz_spoil) TE, TR = 200e-3, 1000e-3 duration_to_center = (Nx / 2 + 0.5) * calc_duration(gx) + Ny / 2 * calc_duration(gy) delayTE1 = TE / 2 - calc_duration(gz) / 2 - pre_time - calc_duration(gz_spoil) - calc_duration(rf180) / 2 delayTE2 = TE / 2 - calc_duration(rf180) / 2 - calc_duration(gz_spoil) - duration_to_center delay1 = make_delay(delayTE1) delay2 = make_delay(delayTE2) seq.add_block(rf, gz) seq.add_block(gx_pre, gy_pre, gz_reph) seq.add_block(delay1) seq.add_block(gz_spoil) seq.add_block(rf180) seq.add_block(gz_spoil) seq.add_block(delay2) for i in range(Ny): seq.add_block(gx, adc) seq.add_block(gy) gx.amplitude = -gx.amplitude seq.add_block(make_delay(1))
gx_pre = make_trapezoid(kwargs_for_gxpre) kwargs_for_gz_reph = { "channel": 'z', "system": system, "area": -gz.area / 2, "duration": 2e-3 } gz_reph = make_trapezoid(kwargs_for_gz_reph) phase_areas = (np.arange(Ny) - (Ny / 2)) * delta_k TE, TR = 10e-3, 200e-3 delayTE = TE - calc_duration( gx_pre) - calc_duration(gz) / 2 - calc_duration(gx) / 2 delayTR = TR - calc_duration(gx_pre) - calc_duration(gz) - calc_duration( gx) - delayTE delay1 = make_delay(delayTE) delay2 = make_delay(delayTR) for i in range(Ny): seq.add_block(rf, gz) kwargsForGyPre = { "channel": 'y', "system": system, "area": phase_areas[i], "duration": 2e-3 } gyPre = make_trapezoid(kwargsForGyPre) seq.add_block(gx_pre, gyPre, gz_reph) seq.add_block(delay1) seq.add_block(gx, adc) seq.add_block(delay2)
def make_pulseq_gre_oblique(fov, n, thk, fa, tr, te, enc='xyz', slice_locs=None, write=False): """Makes a gradient-echo sequence in any plane 2D oblique multi-slice gradient-echo pulse sequence with Cartesian encoding Oblique means that each of slice-selection, phase encoding, and frequency encoding can point in any specified direction Parameters ---------- fov : array_like Isotropic field-of-view, or length-2 list [fov_readout, fov_phase], in meters n : array_like Isotropic matrix size, or length-2 list [n_readout, n_phase] thk : float Slice thickness in meters fa : float Flip angle in degrees tr : float Repetition time in seconds te : float Echo time in seconds enc : str or array_like, optional 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 - Use list to indicate vectors in the encoding directions for oblique slices They should be perpendicular to each other, but not necessarily unit vectors e.g. [(2,1,0),(-1,2,0),(0,0,1)] rotates the two in-plane encoding directions for an axial slice slice_locs : array_like, optional Slice locations from isocenter in meters Default is None which means a single slice at the center write : bool, optional Whether to write seq into file; default is False Returns ------- seq : Sequence Pulse sequence as a Pulseq object """ # System options # kwargs_for_opts = {"rf_ring_down_time": 0, "rf_dead_time": 0} kwargs_for_opts = {'max_grad': 32, 'grad_unit': 'mT/m', 'max_slew': 130, 'slew_unit': 'T/m/s', 'rf_ring_down_time': 30e-6, 'rf_dead_time': 100e-6, 'adc_dead_time': 20e-6} system = Opts(kwargs_for_opts) seq = Sequence(system) # Calculate unit gradients for ss, fe, pe ug_fe, ug_pe, ug_ss = parse_enc(enc) # Sequence parameters Nf, Np = (n, n) if isinstance(n, int) else (n[0], n[1]) delta_k_ro, delta_k_pe = (1 / fov, 1 / fov) if isinstance(fov, float) else (1 / fov[0], 1 / fov[1]) kWidth_ro = Nf * delta_k_ro flip = fa * pi / 180 # Slice select: RF and gradient kwargs_for_sinc = {"flip_angle": flip, "system": system, "duration": 4e-3, "slice_thickness": thk, "apodization": 0.5, "time_bw_product": 4} rf, g_ss = make_sinc_pulse(kwargs_for_sinc, 2) g_ss_x, g_ss_y, g_ss_z = make_oblique_gradients(g_ss, ug_ss) # Readout and ADC readoutTime = 6.4e-3 kwargs_for_g_ro = {"channel": 'x', "system": system, "flat_area": kWidth_ro, "flat_time": readoutTime} g_ro = make_trapezoid(kwargs_for_g_ro) g_ro_x, g_ro_y, g_ro_z = make_oblique_gradients(g_ro, ug_fe) # kwargs_for_adc = {"num_samples": Nf, "duration": g_ro.flat_time, "delay": g_ro.rise_time} adc = makeadc(kwargs_for_adc) # Readout rewinder kwargs_for_g_ro_pre = {"channel": 'x', "system": system, "area": -g_ro.area / 2, "duration": 2e-3} g_ro_pre = make_trapezoid(kwargs_for_g_ro_pre) g_ro_pre_x, g_ro_pre_y, g_ro_pre_z = make_oblique_gradients(g_ro_pre, ug_fe) # # Slice refocusing kwargs_for_g_ss_reph = {"channel": 'z', "system": system, "area": -g_ss.area / 2, "duration": 2e-3} g_ss_reph = make_trapezoid(kwargs_for_g_ss_reph) g_ss_reph_x, g_ss_reph_y, g_ss_reph_z = make_oblique_gradients(g_ss_reph, ug_ss) # Prepare phase areas phase_areas = (np.arange(Np) - (Np / 2)) * delta_k_pe TE, TR = te, tr delayTE = TE - calc_duration(g_ro_pre) - calc_duration(g_ss) / 2 - calc_duration(g_ro) / 2 delayTR = TR - calc_duration(g_ro_pre) - calc_duration(g_ss) - calc_duration(g_ro) - delayTE delay1 = make_delay(delayTE) delay2 = make_delay(delayTR) if slice_locs is None: locs = [0] else: locs = slice_locs # Construct sequence! for u in range(len(locs)): # add frequency offset rf.freq_offset = g_ss.amplitude * locs[u] for i in range(Np): seq.add_block(rf, g_ss_x, g_ss_y, g_ss_z) kwargs_for_g_pe = {"channel": 'y', "system": system, "area": phase_areas[i], "duration": 2e-3} g_pe = make_trapezoid(kwargs_for_g_pe) g_pe_x, g_pe_y, g_pe_z = make_oblique_gradients(g_pe, ug_pe) pre_grads_list = [g_ro_pre_x, g_ro_pre_y, g_ro_pre_z, g_ss_reph_x, g_ss_reph_y, g_ss_reph_z, g_pe_x, g_pe_y, g_pe_z] gtx, gty, gtz = combine_trap_grad_xyz(gradients=pre_grads_list, system=system, dur=2e-3) seq.add_block(gtx, gty, gtz) seq.add_block(delay1) seq.add_block(g_ro_x, g_ro_y, g_ro_z, adc) seq.add_block(delay2) if write: seq.write( "gre_fov{:.0f}mm_Nf{:d}_Np{:d}_TE{:.0f}ms_TR{:.0f}ms_FA{:.0f}deg.seq".format(fov * 1000, Nf, Np, TE * 1000, TR * 1000, flip * 180 / pi)) print('GRE sequence constructed') return seq
flip = 180 * pi / 180 kwargs_for_sinc = { "flip_angle": flip, "system": system, "duration": 2e-3, "slice_thickness": slice_thickness, "apodization": 0.5, "time_bw_product": 4 } rf180, gz180 = make_sinc_pulse(kwargs_for_sinc, 2) delayTE1 = TE / 2 - calc_duration(gz_reph) - calc_duration( rf) - calc_duration(rf180) / 2 delayTE2 = TE / 2 - calc_duration(gx) / 2 - calc_duration(rf180) / 2 delayTE3 = TR - TE - calc_duration(gx) delay1 = make_delay(delayTE1) delay2 = make_delay(delayTE2) delay3 = make_delay(delayTE3) for inv in range(len(TI)): seq.add_block( rf180 ) # Non-selective at the moment, could be extended to make this selective/adiabatic seq.add_block(make_delay(TI[inv])) for i in range(Ny): seq.add_block(rf, gz) kwargs_for_gy_pre = { "channel": 'y', "system": system, "area": -(Ny / 2 - i) * delta_k, "duration": readoutTime / 2
def make_pulseq_gre(fov, n, thk, fa, tr, te, enc='xyz', slice_locs=None, write=False): """Makes a gradient-echo sequence 2D orthogonal multi-slice gradient-echo pulse sequence with Cartesian encoding Orthogonal means that each of slice-selection, phase encoding, and frequency encoding aligns with the x, y, or z directions Parameters ---------- fov : float Field-of-view in meters (isotropic) n : int Matrix size (isotropic) thk : float Slice thickness in meters fa : float Flip angle in degrees tr : float Repetition time in seconds te : float Echo time in seconds enc : str, optional Spatial encoding directions 1st - readout; 2nd - phase encoding; 3rd - slice select Default 'xyz' means axial(z) slice with readout in x and phase encoding in y slice_locs : array_like, optional Slice locations from isocenter in meters Default is None which means a single slice at the center write : bool, optional Whether to write seq into file; default is False Returns ------- seq : Sequence Pulse sequence as a Pulseq object """ kwargs_for_opts = {"rf_ring_down_time": 0, "rf_dead_time": 0} system = Opts(kwargs_for_opts) seq = Sequence(system) Nf = n Np = n flip = fa * pi / 180 kwargs_for_sinc = {"flip_angle": flip, "system": system, "duration": 4e-3, "slice_thickness": thk, "apodization": 0.5, "time_bw_product": 4} rf, g_ss = make_sinc_pulse(kwargs_for_sinc, 2) g_ss.channel = enc[2] delta_k = 1 / fov kWidth = Nf * delta_k # Readout and ADC readoutTime = 6.4e-3 kwargs_for_g_ro = {"channel": enc[0], "system": system, "flat_area": kWidth, "flat_time": readoutTime} g_ro = make_trapezoid(kwargs_for_g_ro) kwargs_for_adc = {"num_samples": Nf, "duration": g_ro.flat_time, "delay": g_ro.rise_time} adc = makeadc(kwargs_for_adc) # Readout rewinder kwargs_for_g_ro_pre = {"channel": enc[0], "system": system, "area": -g_ro.area / 2, "duration": 2e-3} g_ro_pre = make_trapezoid(kwargs_for_g_ro_pre) # Slice refocusing kwargs_for_g_ss_reph = {"channel": enc[2], "system": system, "area": -g_ss.area / 2, "duration": 2e-3} g_ss_reph = make_trapezoid(kwargs_for_g_ss_reph) phase_areas = (np.arange(Np) - (Np / 2)) * delta_k # TE, TR = 10e-3, 1000e-3 TE, TR = te, tr delayTE = TE - calc_duration(g_ro_pre) - calc_duration(g_ss) / 2 - calc_duration(g_ro) / 2 delayTR = TR - calc_duration(g_ro_pre) - calc_duration(g_ss) - calc_duration(g_ro) - delayTE delay1 = make_delay(delayTE) delay2 = make_delay(delayTR) if slice_locs is None: locs = [0] else: locs = slice_locs for u in range(len(locs)): # add frequency offset rf.freq_offset = g_ss.amplitude * locs[u] for i in range(Np): seq.add_block(rf, g_ss) kwargs_for_g_pe = {"channel": enc[1], "system": system, "area": phase_areas[i], "duration": 2e-3} g_pe = make_trapezoid(kwargs_for_g_pe) seq.add_block(g_ro_pre, g_pe, g_ss_reph) seq.add_block(delay1) seq.add_block(g_ro, adc) seq.add_block(delay2) if write: seq.write( "gre_fov{:.0f}mm_Nf{:d}_Np{:d}_TE{:.0f}ms_TR{:.0f}ms_FA{:.0f}deg.seq".format(fov * 1000, Nf, Np, TE * 1000, TR * 1000, flip * 180 / pi)) print('GRE sequence constructed') return seq
assert np.all(tr >= min_tr) tr_delay = round((tr_per_slice - (calc_duration(rf_fs) + calc_duration(gz_fs) + calc_duration(rf) + calc_duration(gz_reph) + 2*calc_duration(gz_spoil) + calc_duration(rf180) + calc_duration(gx_pre,gy_pre) + calc_duration(gx)*Ny*pF + calc_duration(gx_crush,gz_crush)))/seq.grad_raster_time) * seq.grad_raster_time #EPI calibration for s in range(n_slices): print(s - (n_slices - 1) / 2) seq.add_block(rf_fs, gz_fs) rf.freq_offset = gz.amplitude * slice_thickness * (s - (n_slices - 1) / 2) seq.add_block(rf, gz) seq.add_block(gz_reph) seq.add_block(make_delay(gdiff_dur + delay_te1)) seq.add_block(gz_spoil) seq.add_block(rf180, gz180) seq.add_block(gz_spoil) seq.add_block(make_delay(gdiff_dur)) seq.add_block(make_delay(delay_te2 + gdiff_dur)) seq.add_block(gx_pre) for i in range(1, Nyeff + 1): seq.add_block(gx, adc) gx.amplitude = -gx.amplitude
def make_pulseq_se(fov, n, thk, fa, tr, te, enc='xyz', slice_locs=None, write=False): """Makes a Spin Echo (SE) sequence 2D orthogonal multi-slice Spin-Echo pulse sequence with Cartesian encoding Orthogonal means that each of slice-selection, phase encoding, and frequency encoding aligns with the x, y, or z directions Parameters ---------- fov : float Field-of-view in meters (isotropic) n : int Matrix size (isotropic) thk : float Slice thickness in meters fa : float Flip angle in degrees tr : float Repetition time in seconds te : float Echo time in seconds enc : str, optional Spatial encoding directions 1st - readout; 2nd - phase encoding; 3rd - slice select Default 'xyz' means axial(z) slice with readout in x and phase encoding in y slice_locs : array_like, optional Slice locations from isocenter in meters Default is None which means a single slice at the center write : bool, optional Whether to write seq into file; default is False Returns ------- seq : Sequence Pulse sequence as a Pulseq object """ kwargs_for_opts = {"max_grad": 33, "grad_unit": "mT/m", "max_slew": 100, "slew_unit": "T/m/s", "rf_dead_time": 10e-6, "adc_dead_time": 10e-6} system = Opts(kwargs_for_opts) seq = Sequence(system) # Parameters Nf = n Np = n delta_k = 1 / fov kWidth = Nf * delta_k TE, TR = te, tr # Non-180 pulse flip1 = fa * pi / 180 kwargs_for_sinc = {"flip_angle": flip1, "system": system, "duration": 2e-3, "slice_thickness": thk, "apodization": 0.5, "time_bw_product": 4} rf, g_ss = make_sinc_pulse(kwargs_for_sinc, 2) g_ss.channel = enc[2] # 180 pulse flip2 = 180 * pi / 180 kwargs_for_sinc = {"flip_angle": flip2, "system": system, "duration": 2e-3, "slice_thickness": thk, "apodization": 0.5, "time_bw_product": 4} rf180, g_ss180 = make_sinc_pulse(kwargs_for_sinc, 2) g_ss180.channel = enc[2] # Readout gradient & ADC # readoutTime = system.grad_raster_time * Nf readoutTime = 6.4e-3 kwargs_for_g_ro = {"channel": enc[0], "system": system, "flat_area": kWidth, "flat_time": readoutTime} g_ro = make_trapezoid(kwargs_for_g_ro) kwargs_for_adc = {"num_samples": Nf, "system": system, "duration": g_ro.flat_time, "delay": g_ro.rise_time} adc = makeadc(kwargs_for_adc) # RO rewinder gradient kwargs_for_g_ro_pre = {"channel": enc[0], "system": system, "area": g_ro.area / 2, "duration": 2e-3} # "duration": g_ro.rise_time + g_ro.fall_time + readoutTime / 2} g_ro_pre = make_trapezoid(kwargs_for_g_ro_pre) # Slice refocusing gradient kwargs_for_g_ss_reph = {"channel": enc[2], "system": system, "area": -g_ss.area / 2, "duration": 2e-3} g_ss_reph = make_trapezoid(kwargs_for_g_ss_reph) # Delays delayTE1 = (TE - 2 * max(calc_duration(g_ss_reph), calc_duration(g_ro_pre)) - calc_duration(g_ss) - calc_duration( g_ss180)) / 2 # delayTE2 = TE / 2 - calc_duration(g_ro) / 2 - calc_duration(g_ss180) / 2 delayTE2 = (TE - calc_duration(g_ro) - calc_duration(g_ss180)) / 2 delayTE3 = TR - TE - (calc_duration(g_ss) + calc_duration(g_ro)) / 2 delay1 = make_delay(delayTE1) delay2 = make_delay(delayTE2) delay3 = make_delay(delayTE3) # Construct sequence if slice_locs is None: locs = [0] else: locs = slice_locs for u in range(len(locs)): rf180.freq_offset = g_ss180.amplitude * locs[u] rf.freq_offset = g_ss.amplitude * locs[u] for i in range(Np): seq.add_block(rf, g_ss) # 90-deg pulse kwargs_for_g_pe_pre = {"channel": enc[1], "system": system, "area": -(Np / 2 - i) * delta_k, "duration": 2e-3} # "duration": g_ro.rise_time + g_ro.fall_time + readoutTime / 2} g_pe_pre = make_trapezoid(kwargs_for_g_pe_pre) # Phase encoding gradient seq.add_block(g_ro_pre, g_pe_pre, g_ss_reph) # Add a combination of ro rewinder, phase encoding, and slice refocusing seq.add_block(delay1) # Delay 1: until 180-deg pulse seq.add_block(rf180, g_ss180) # 180 deg pulse for SE seq.add_block(delay2) # Delay 2: until readout seq.add_block(g_ro, adc) # Readout! seq.add_block(delay3) # Delay 3: until next inversion pulse if write: seq.write( "se_fov{:.0f}mm_Nf{:d}_Np{:d}_TE{:.0f}ms_TR{:.0f}ms.seq".format(fov * 1000, Nf, Np, TE * 1000, TR * 1000)) print('Spin echo sequence constructed') return seq
gr7 = make_extended_trapezoid(channel='x', 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] 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 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) for k_ex in range(n_ex + 1): for s in range(n_slices): rf_ex.freq_offset = gs_ex.amplitude * slice_thickness * ( s - (n_slices - 1) / 2) rf_ref.freq_offset = gs_ref.amplitude * slice_thickness * ( 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] seq.add_block(gs1) seq.add_block(gs2, rf_ex) seq.add_block(gs3, gr3)
def make_pulseq_se_oblique(fov, n, thk, fa, tr, te, enc='xyz', slice_locs=None, write=False): """Makes a Spin Echo (SE) sequence in any plane 2D oblique multi-slice Spin-Echo pulse sequence with Cartesian encoding Oblique means that each of slice-selection, phase encoding, and frequency encoding can point in any specified direction Parameters ---------- fov : array_like Isotropic field-of-view, or length-2 list [fov_readout, fov_phase], in meters n : array_like Isotropic matrix size, or length-2 list [n_readout, n_phase] thk : float Slice thickness in meters fa : float Flip angle in degrees tr : float Repetition time in seconds te : float Echo time in seconds enc : str or array_like, optional 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 - Use list to indicate vectors in the encoding directions for oblique slices They should be perpendicular to each other, but not necessarily unit vectors e.g. [(2,1,0),(-1,2,0),(0,0,1)] rotates the two in-plane encoding directions for an axial slice slice_locs : array_like, optional Slice locations from isocenter in meters Default is None which means a single slice at the center write : bool, optional Whether to write seq into file; default is False Returns ------- seq : Sequence Pulse sequence as a Pulseq object """ # System options kwargs_for_opts = {'max_grad': 32, 'grad_unit': 'mT/m', 'max_slew': 130, 'slew_unit': 'T/m/s', 'rf_ring_down_time': 30e-6, 'rf_dead_time': 100e-6, 'adc_dead_time': 20e-6} system = Opts(kwargs_for_opts) seq = Sequence(system) # Sequence parameters ug_fe, ug_pe, ug_ss = parse_enc(enc) Nf, Np = (n, n) if isinstance(n, int) else (n[0], n[1]) delta_k_ro, delta_k_pe = (1 / fov, 1 / fov) if isinstance(fov, float) else (1 / fov[0], 1 / fov[1]) kWidth_ro = Nf * delta_k_ro TE, TR = te, tr # Non-180 pulse flip1 = fa * pi / 180 kwargs_for_sinc = {"flip_angle": flip1, "system": system, "duration": 2e-3, "slice_thickness": thk, "apodization": 0.5, "time_bw_product": 4} rf, g_ss = make_sinc_pulse(kwargs_for_sinc, 2) g_ss_x, g_ss_y, g_ss_z = make_oblique_gradients(g_ss, ug_ss) # 180 pulse flip2 = 180 * pi / 180 kwargs_for_sinc = {"flip_angle": flip2, "system": system, "duration": 2e-3, "slice_thickness": thk, "apodization": 0.5, "time_bw_product": 4} rf180, g_ss180 = make_sinc_pulse(kwargs_for_sinc, 2) g_ss180_x, g_ss180_y, g_ss180_z = make_oblique_gradients(g_ss180, ug_ss) # Readout gradient & ADC readoutTime = 6.4e-3 kwargs_for_g_ro = {"channel": 'x', "system": system, "flat_area": kWidth_ro, "flat_time": readoutTime} g_ro = make_trapezoid(kwargs_for_g_ro) g_ro_x, g_ro_y, g_ro_z = make_oblique_gradients(g_ro, ug_fe) kwargs_for_adc = {"num_samples": Nf, "system": system, "duration": g_ro.flat_time, "delay": g_ro.rise_time} adc = makeadc(kwargs_for_adc) # RO rewinder gradient kwargs_for_g_ro_pre = {"channel": 'x', "system": system, "area": g_ro.area / 2, "duration": 2e-3} g_ro_pre = make_trapezoid(kwargs_for_g_ro_pre) g_ro_pre_x, g_ro_pre_y, g_ro_pre_z = make_oblique_gradients(g_ro_pre, ug_fe) # Slice refocusing gradient kwargs_for_g_ss_reph = {"channel": 'z', "system": system, "area": -g_ss.area / 2, "duration": 2e-3} g_ss_reph = make_trapezoid(kwargs_for_g_ss_reph) g_ss_reph_x, g_ss_reph_y, g_ss_reph_z = make_oblique_gradients(g_ss_reph, ug_ss) # Delays delayTE1 = (TE - 2 * max(calc_duration(g_ss_reph), calc_duration(g_ro_pre)) - calc_duration(g_ss) - calc_duration( g_ss180)) / 2 delayTE2 = (TE - calc_duration(g_ro) - calc_duration(g_ss180)) / 2 delayTE3 = TR - TE - (calc_duration(g_ss) + calc_duration(g_ro)) / 2 delay1 = make_delay(delayTE1) delay2 = make_delay(delayTE2) delay3 = make_delay(delayTE3) # Construct sequence if slice_locs is None: locs = [0] else: locs = slice_locs for u in range(len(locs)): rf180.freq_offset = g_ss180.amplitude * locs[u] rf.freq_offset = g_ss.amplitude * locs[u] for i in range(Np): seq.add_block(rf, g_ss_x, g_ss_y, g_ss_z) # 90-deg pulse kwargs_for_g_pe = {"channel": 'y', "system": system, "area": -(Np / 2 - i) * delta_k_pe, "duration": 2e-3} g_pe = make_trapezoid(kwargs_for_g_pe) # Phase encoding gradient g_pe_x, g_pe_y, g_pe_z = make_oblique_gradients(g_pe, ug_pe) pre_grads_list = [g_ro_pre_x, g_ro_pre_y, g_ro_pre_z, g_ss_reph_x, g_ss_reph_y, g_ss_reph_z, g_pe_x, g_pe_y, g_pe_z] gtx, gty, gtz = combine_trap_grad_xyz(pre_grads_list, system, 2e-3) seq.add_block(gtx, gty, gtz) # Add a combination of ro rewinder, phase encoding, and slice refocusing seq.add_block(delay1) # Delay 1: until 180-deg pulse seq.add_block(rf180, g_ss180_x, g_ss180_y, g_ss180_z) # 180 deg pulse for SE seq.add_block(delay2) # Delay 2: until readout seq.add_block(g_ro_x, g_ro_y, g_ro_z, adc) # Readout! seq.add_block(delay3) # Delay 3: until next inversion pulse if write: seq.write( "se_fov{:.0f}mm_Nf{:d}_Np{:d}_TE{:.0f}ms_TR{:.0f}ms.seq".format(fov * 1000, Nf, Np, TE * 1000, TR * 1000)) print('Spin echo sequence (oblique) constructed') return seq
# Fat saturation if fatsat: if B0 > 4: fatsat_bw = 1000 # bandwidth of fatsat pulse [Hz] else: fatsat_bw = 300 fatsat_fa = 110 # flip angle [°] fatsat_dur = ph.round_up_to_raster(fatsat_tbp/fatsat_bw, decimals=5) rf_fatsat, fatsat_del = make_gauss_pulse(flip_angle=fatsat_fa*np.pi/180, duration=fatsat_dur, bandwidth=fatsat_bw, freq_offset=B0*ph.fw_shift, system=system, return_delay=True) # echo time delay min_te = exc_to_rew + rew_dur + system.adc_dead_time if min_te > TE: raise ValueError('Minimum TE is {} ms.'.format(min_te*1e3)) te_delay = make_delay(d = TE - min_te) #%% Spiral Readout Gradients # Parameters spiral trajectory: # parameter description default value # --------- ------------- -------------- # nitlv: number of spiral interleaves 15 # res: resolution 1 mm # fov: target field of view 192 mm # max_amp: maximum gradient amplitude 42 mT/m # min_rise: minimum gradient risetime 5 us/(mT/m) # spiraltype: 1: spiral out # 2: spiral in
"ncontrast": len(TE) } create_hdr(hdr, params_hdr) #%% Set up sequence seq.set_definition( "Name", seq_name) # metadata file name is saved in Siemens header for reco seq.set_definition("FOV", [fov, fov, slice_res]) seq.set_definition("Slice_Thickness", "%f" % (slice_res * slices)) # Noise scans noisescans = 16 noise_samples = 256 noise_adc = make_adc(system=system, num_samples=256, dwell=1e-6 * dwelltime_us) noise_delay = make_delay( d=ph.round_up_to_raster(noise_adc.duration + 1e-3, decimals=5)) for k in range(noisescans): seq.add_block(noise_adc, noise_delay) acq = ismrmrd.Acquisition() acq.setFlag(ismrmrd.ACQ_IS_NOISE_MEASUREMENT) meta_file.append_acquisition(acq) if slices % 2 == 1: slc = 0 else: slc = 1 for s in range(slices): if s == int(slices / 2 + 0.5): if slices % 2 == 1: slc = 1 else: