def combine_trap_grad_xyz(gradients, system, dur): """Helper function that merges multiple gradients A list of gradients are combined into one set of 3 oblique gradients (Gx, Gy, Gz) with equivalent areas Note that the waveforms are not preserved : the outputs will always be trapezoidal gradients Parameters ---------- gradients : list List of gradients to be combined; there can be any number of x, y, or z gradients system : Opts Pulseq object that indicates system constraints for gradient parameters dur : float Duration of the output oblique gradients Returns ------- gtx, gty, gtz : Gradient Oblique pulseq gradients with equivalent areas to all input gradients combined """ gx_area, gy_area, gz_area = (0, 0, 0) for g in gradients: if g.channel == 'x': gx_area += g.area elif g.channel == 'y': gy_area += g.area elif g.channel == 'z': gz_area += g.area kwargs_for_gtx = {"channel": 'x', "system": system, "area": gx_area, "duration": dur} kwargs_for_gty = {"channel": 'y', "system": system, "area": gy_area, "duration": dur} kwargs_for_gtz = {"channel": 'z', "system": system, "area": gz_area, "duration": dur} gtx = make_trapezoid(kwargs_for_gtx) gty = make_trapezoid(kwargs_for_gty) gtz = make_trapezoid(kwargs_for_gtz) return gtx, gty, gtz
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
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
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
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
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