def test_get_ktraj(self): # Set up gx, gy, adc using known k-space traj - and compare output to it fov = 250e-3 # [m] dk = 1 / fov # [m^-1] n = 128 theta = np.random.rand() * 2 * np.pi gx = make_trapezoid(channel='x', system=Opts(), flat_area=n * dk * np.cos(theta), flat_time=5e-3, rise_time=0.5e-3) gy = make_trapezoid(channel='y', system=Opts(), flat_area=n * dk * np.sin(theta), flat_time=5e-3, rise_time=0.5e-3) adc = make_adc(num_samples=n, system=Opts(), duration=gx.flat_area, delay=gx.rise_time) ktraj_complex = get_ktraj(gx, gy, adc, display=False) # Check: shape, type np.testing.assert_equal(ktraj_complex.shape, (n, )) self.assertIsInstance(ktraj_complex, np.ndarray) self.assertIs(type(ktraj_complex[0]), np.complex128) # Check: values kx = np.cos(theta) * np.arange(0, n * dk, dk) + 0.5 * gx.rise_time * gx.amplitude ky = np.sin(theta) * np.arange(0, n * dk, dk) + 0.5 * gy.rise_time * gy.amplitude np.testing.assert_array_almost_equal(ktraj_complex, kx + 1j * ky)
def test_modify_gradient(self): g1 = make_trapezoid(channel='x', amplitude=5000, duration=1e-3, rise_time=1e-4) g1_area = g1.area g1_flat = g1.flat_area g2 = make_extended_trapezoid(channel='z', amplitudes=[4000, 5000, 5000, 6000], times=[0, 1e-3, 6e-3, 7e-3]) # Use a random scale sc = np.random.rand() # Modify a trapezoidal gradient modify_gradient(g1, scale=sc, channel='z') np.testing.assert_equal(g1.amplitude, 5000 * sc) np.testing.assert_equal(g1.channel, 'z') np.testing.assert_equal(g1.area, g1_area * sc) np.testing.assert_equal(g1.flat_area, g1_flat * sc) # Modify an arbitrary gradient modify_gradient(g2, scale=0, channel='y') np.testing.assert_equal(g2.channel, 'y') np.testing.assert_equal(np.sum(g2.waveform), 0) np.testing.assert_equal(g2.first, 0) np.testing.assert_equal(g2.last, 0)
def test_get_ktraj_3d_rew_delay(self): # Set up gx, gy, adc using known k-space traj - and compare output to it fov = 250e-3 # [m] dk = 1 / fov # [m^-1] n = 128 theta = np.random.rand() * np.pi phi = np.random.rand() * 2 * np.pi gx = make_trapezoid(channel='x', system=Opts(), flat_area=n * dk * np.sin(theta) * np.cos(phi), flat_time=5e-3, rise_time=0.5e-3) gy = make_trapezoid(channel='y', system=Opts(), flat_area=n * dk * np.sin(theta) * np.sin(phi), flat_time=5e-3, rise_time=0.5e-3) gz = make_trapezoid(channel='z', system=Opts(), flat_area=n * dk * np.cos(theta), flat_time=5e-3, rise_time=0.5e-3) gx_rew = make_trapezoid(channel='x', system=Opts(), area=-gx.area / 2, duration=2.5e-3) gy_rew = make_trapezoid(channel='y', system=Opts(), area=-gy.area / 2, duration=2.5e-3) gz_rew = make_trapezoid(channel='z', system=Opts(), area=-gz.area / 2, duration=2.5e-3) adc_add_delay = 1e-3 adc = make_adc(num_samples=n, system=Opts(), duration=gx.flat_area, delay=gx.rise_time + adc_add_delay) ktraj = get_ktraj_3d_rew_delay(gx, gx_rew, gy, gy_rew, gz, gz_rew, adc) # Check: shape, type self.assertIsInstance(ktraj, np.ndarray) np.testing.assert_equal(ktraj.shape, [n, 3]) # Check: values kx = np.sin(theta) * np.cos(phi) * np.arange(0, n * dk, dk) + \ 0.5 * gx.rise_time * gx.amplitude + gx_rew.area + adc_add_delay * gx.amplitude ky = np.sin(theta) * np.sin(phi) * np.arange(0, n * dk, dk) + \ 0.5 * gy.rise_time * gy.amplitude + gy_rew.area + adc_add_delay * gy.amplitude kz = np.cos(theta) * np.arange(0, n * dk, dk) + 0.5 * gz.rise_time * gz.amplitude + gz_rew.area + \ adc_add_delay * gz.amplitude np.testing.assert_array_almost_equal(ktraj[:, 0], kx) np.testing.assert_array_almost_equal(ktraj[:, 1], ky) np.testing.assert_array_almost_equal(ktraj[:, 2], kz)
def test_combine_oblique_radial_readout_2d(self): g = make_trapezoid(channel='x', amplitude=5000, duration=1e-3, rise_time=1e-4) # Generate random unit gradients and random theta theta1 = np.random.rand() * np.pi phi1 = np.random.rand() * 2 * np.pi ug1 = np.array([ np.cos(theta1) * np.cos(phi1), np.cos(theta1) * np.sin(phi1), np.sin(theta1) ]) theta2 = np.random.rand() * np.pi phi2 = np.random.rand() * 2 * np.pi ug2 = np.array([ np.cos(theta2) * np.cos(phi2), np.cos(theta2) * np.sin(phi2), np.sin(theta2) ]) ug2p = ug2 - ug1 * (np.dot(ug1, ug2) / (np.linalg.norm(ug1)**2)) ug2pn = ug2p / np.linalg.norm(ug2p) alpha = np.random.rand() * 2 * np.pi # Timing is checked within the function gx, gy, gz = combine_oblique_radial_readout_2d(g, ug1, ug2pn, alpha) # Check amplitude np.testing.assert_almost_equal( gx.amplitude, 5000 * (ug1[0] * np.cos(alpha) + ug2pn[0] * np.sin(alpha))) np.testing.assert_almost_equal( gy.amplitude, 5000 * (ug1[1] * np.cos(alpha) + ug2pn[1] * np.sin(alpha))) np.testing.assert_almost_equal( gz.amplitude, 5000 * (ug1[2] * np.cos(alpha) + ug2pn[2] * np.sin(alpha)))
def test_make_oblique_gradients(self): g = make_trapezoid(channel='x', amplitude=5000, duration=1e-3, rise_time=1e-4) theta = np.random.rand() * np.pi phi = np.random.rand() * 2 * np.pi ug = [ np.cos(theta) * np.cos(phi), np.cos(theta) * np.sin(phi), np.sin(theta) ] gx, gy, gz = make_oblique_gradients(g, ug) # Check channel np.testing.assert_equal(gx.channel, 'x') np.testing.assert_equal(gy.channel, 'y') np.testing.assert_equal(gz.channel, 'z') # Check amplitude np.testing.assert_almost_equal(gx.amplitude, 5000 * ug[0]) np.testing.assert_almost_equal(gy.amplitude, 5000 * ug[1]) np.testing.assert_almost_equal(gz.amplitude, 5000 * ug[2])
def make_arbitrary_rf(signal: np.ndarray, flip_angle: float, system: Opts = Opts(), freq_offset: float = 0, phase_offset: float = 0, time_bw_product: float = 0, bandwidth: float = 0, max_grad: float = 0, max_slew: float = 0, slice_thickness: float = 0, delay: float = 0, use: str = None) -> SimpleNamespace: """ Creates a radio-frequency pulse event with arbitrary pulse shape and optionally an accompanying slice select trapezoidal gradient event. Parameters ---------- signal : np.ndarray Arbitrary waveform. flip_angle : float Flip angle in radians. system : Opts System limits. Default is a system limits object initialised to default values. freq_offset : float Frequency offset in Hertz (Hz). Default is 0. phase_offset : float Phase offset in Hertz (Hz). Default is 0. time_bw_product : float Time-bandwidth product. Default is 4. bandwidth : float Bandwidth in Hertz (Hz). max_grad : float Maximum gradient strength of accompanying slice select trapezoidal event. Default is `system`'s `max_grad`. max_slew : float Maximum slew rate of accompanying slice select trapezoidal event. Default is `system`'s `max_slew`. slice_thickness : float Slice thickness of accompanying slice select trapezoidal event. The slice thickness determines the area of the slice select event. delay : float Delay in milliseconds (ms) of accompanying slice select trapezoidal event. use : str Use of arbitrary radio-frequency pulse event. Must be one of 'excitation', 'refocusing' or 'inversion'. Returns ------- rf : SimpleNamespace Radio-frequency pulse event with arbitrary pulse shape. gz : SimpleNamespace Slice select trapezoidal gradient event accompanying the arbitrary radio-frequency pulse event. """ valid_use_pulses = ['excitation', 'refocusing', 'inversion'] if use is not None and use not in valid_use_pulses: raise ValueError( f'Invalid use parameter. Must be one of excitation, refocusing or inversion. You passed: {use}') signal = np.squeeze(signal) if signal.ndim > 1: raise ValueError(f'signal should have ndim=1. You passed ndim={signal.ndim}') signal = signal / np.sum(signal * system.rf_raster_time) * flip_angle / (2 * np.pi) N = len(signal) duration = N * system.rf_raster_time t = np.arange(1, N + 1) * system.rf_raster_time rf = SimpleNamespace() rf.type = 'rf' rf.signal = signal rf.t = t rf.freq_offset = freq_offset rf.phase_offset = phase_offset rf.dead_time = system.rf_dead_time rf.ringdown_time = system.rf_ringdown_time rf.delay = delay if use is not None: rf.use = use if rf.dead_time > rf.delay: rf.delay = rf.dead_time try: if slice_thickness <= 0: raise ValueError('Slice thickness must be provided.') if bandwidth <= 0: raise ValueError('Bandwidth of pulse must be provided.') if max_grad > 0: system.max_grad = max_grad if max_slew > 0: system.max_slew = max_slew BW = bandwidth if time_bw_product > 0: BW = time_bw_product / duration amplitude = BW / slice_thickness area = amplitude * duration gz = make_trapezoid(channel='z', system=system, flat_time=duration, flat_area=area) if rf.delay > gz.rise_time: gz.delay = math.ceil((rf.delay - gz.rise_time) / system.grad_raster_time) * system.grad_raster_time if rf.delay < (gz.rise_time + gz.delay): rf.delay = gz.rise_time + gz.delay except: gz = None if rf.ringdown_time > 0: t_fill = np.arange(1, round(rf.ringdown_time / 1e-6) + 1) * 1e-6 rf.t = np.concatenate((rf.t, rf.t[-1] + t_fill)) rf.signal = np.concatenate((rf.signal, np.zeros(len(t_fill)))) return rf, gz
fsp_s = 0.5 rf_ex_phase = np.pi / 2 rf_ref_phase = 0 flip_ex = 90 * np.pi / 180 rf_ex, gz, _ = make_sinc_pulse(flip_angle=flip_ex, system=system, duration=t_ex, slice_thickness=slice_thickness, apodization=0.5, time_bw_product=4, phase_offset=rf_ex_phase) gs_ex = make_trapezoid(channel='z', system=system, amplitude=gz.amplitude, flat_time=t_exwd, rise_time=dG) flip_ref = rf_flip[0] * np.pi / 180 rf_ref, gz, _ = make_sinc_pulse(flip_angle=flip_ref, system=system, duration=t_ref, slice_thickness=slice_thickness, apodization=0.5, time_bw_product=4, phase_offset=rf_ref_phase, use='refocusing') gs_ref = make_trapezoid(channel='z', system=system, amplitude=gs_ex.amplitude,
gdir, nb0s = difunc.get_dirs(ndirs) #to avoid exceeding gradient limits gdfact = 1.0 tr_per_slice = tr / n_slices system = Opts(max_grad=32, grad_unit='mT/m', max_slew=130, slew_unit='T/m/s', rf_ringdown_time=30e-6, rf_dead_time=100e-6, adc_dead_time=20e-6) b0 = 2.89 sat_ppm = -3.45 sat_freq = sat_ppm * 1e-6 * b0 * system.gamma rf_fs, _, _ = make_gauss_pulse(flip_angle=110 * math.pi / 180, system=system, duration=8e-3, bandwidth=abs(sat_freq), freq_offset=sat_freq) gz_fs = make_trapezoid(channel='z', system=system, delay=calc_duration(rf_fs), area=1 / 1e-4) rf, gz, gz_reph = make_sinc_pulse(flip_angle=math.pi / 2, system=system, duration=3e-3, slice_thickness=slice_thickness, apodization=0.5, time_bw_product=4) rf180, gz180, _ = make_sinc_pulse(flip_angle=math.pi, system=system, duration=5e-3, slice_thickness=slice_thickness, apodization=0.5, time_bw_product=4) rf180.phase_offset = math.pi/2 gz_spoil = make_trapezoid(channel='z', system=system, area=2*gz.area, duration=3e-3) delta_k = 1 / fov k_width = Nx * delta_k readout_time = 1.05e-3 blip_dur = math.ceil(2 * math.sqrt(delta_k / system.max_slew) / seq.grad_raster_time / 2) * seq.grad_raster_time * 2
def make_gauss_pulse(flip_angle: float, apodization: float = 0, bandwidth: float = 0, center_pos: float = 0.5, delay: float = 0, duration: float = 0, freq_offset: float = 0, max_grad: float = 0, max_slew: float = 0, phase_offset: float = 0, return_gz: bool = False, slice_thickness: float = 0, system: Opts = Opts(), time_bw_product: float = 4, use: str = str()) -> Union[SimpleNamespace, Tuple[SimpleNamespace, SimpleNamespace, SimpleNamespace]]: """ Creates a radio-frequency gauss pulse event and optionally accompanying slice select and slice select rephasing trapezoidal gradient events. Parameters ---------- flip_angle : float Flip angle in radians. apodization : float, optional, default=0 Apodization. bandwidth : float, optional, default=0 Bandwidth in Hertz (Hz). center_pos : float, optional, default=0.5 Position of peak. delay : float, optional Delay in milliseconds (ms). duration : float, optional, default=0 Duration in milliseconds (ms). freq_offset : float, optional, default=0 Frequency offset in Hertz (Hz). max_grad : float, optional, default=0 Maximum gradient strength of accompanying slice select trapezoidal event. max_slew : float, optional, default=0 Maximum slew rate of accompanying slice select trapezoidal event. phase_offset : float, optional, default=0 Phase offset in Hertz (Hz). return_gz : bool, default=False Boolean flag indicating if slice-selective gradient has to be returned. slice_thickness : float, optional, default=0 Slice thickness of accompanying slice select trapezoidal event. The slice thickness determines the area of the slice select event. system : Opts, optional, default=Opts() System limits. time_bw_product : int, optional, default=4 Time-bandwidth product. use : str, optional, default=str() Use of radio-frequency gauss pulse event. Must be one of 'excitation', 'refocusing' or 'inversion'. Returns ------- rf : SimpleNamespace Radio-frequency gauss pulse event. gz : SimpleNamespace Accompanying slice select trapezoidal gradient event. gzr : SimpleNamespace Accompanying slice select rephasing trapezoidal gradient event. Raises ------ ValueError If invalid `use` is passed. Must be one of 'excitation', 'refocusing' or 'inversion'. If `return_gz=True` and `slice_thickness` was not passed. """ valid_use_pulses = ['excitation', 'refocusing', 'inversion'] if use != '' and use not in valid_use_pulses: raise ValueError( f"Invalid use parameter. Must be one of 'excitation', 'refocusing' or 'inversion'. Passed: {use}") if bandwidth == 0: BW = time_bw_product / duration else: BW = bandwidth alpha = apodization N = int(round(duration / 1e-6)) t = np.arange(1, N + 1) * system.rf_raster_time tt = t - (duration * center_pos) window = 1 - alpha + alpha * np.cos(2 * np.pi * tt / duration) signal = np.multiply(window, __gauss(BW * tt)) flip = np.sum(signal) * system.rf_raster_time * 2 * np.pi signal = signal * flip_angle / flip rf = SimpleNamespace() rf.type = 'rf' rf.signal = signal rf.t = t rf.freq_offset = freq_offset rf.phase_offset = phase_offset rf.dead_time = system.rf_dead_time rf.ringdown_time = system.rf_ringdown_time rf.delay = delay if use != '': rf.use = use if rf.dead_time > rf.delay: rf.delay = rf.dead_time if return_gz: if slice_thickness == 0: raise ValueError('Slice thickness must be provided') if max_grad > 0: system.max_grad = max_grad if max_slew > 0: system.max_slew = max_slew amplitude = BW / slice_thickness area = amplitude * duration gz = make_trapezoid(channel='z', system=system, flat_time=duration, flat_area=area) gzr = make_trapezoid(channel='z', system=system, area=-area * (1 - center_pos) - 0.5 * (gz.area - area)) if rf.delay > gz.rise_time: gz.delay = math.ceil((rf.delay - gz.rise_time) / system.grad_raster_time) * system.grad_raster_time if rf.delay < (gz.rise_time + gz.delay): rf.delay = gz.rise_time + gz.delay if rf.ringdown_time > 0: t_fill = np.arange(1, round(rf.ringdown_time / 1e-6) + 1) * 1e-6 rf.t = np.concatenate((rf.t, rf.t[-1] + t_fill)) rf.signal = np.concatenate((rf.signal, np.zeros(len(t_fill)))) # Following 2 lines of code are workarounds for numpy returning 3.14... for np.angle(-0.00...) negative_zero_indices = np.where(rf.signal == -0.0) rf.signal[negative_zero_indices] = 0 if return_gz: return rf, gz, gzr else: return rf
rf_dead_time=100e-6) # ====== # CREATE EVENTS # ====== # Create 90 degree slice selection pulse and gradient rf, gz, _ = make_sinc_pulse(flip_angle=np.pi / 2, system=system, duration=3e-3, slice_thickness=slice_thickness, apodization=0.5, time_bw_product=4, return_gz=True) # Define other gradients and ADC events delta_k = 1 / fov k_width = Nx * delta_k dwell_time = 4e-6 readout_time = Nx * dwell_time flat_time = math.ceil(readout_time * 1e5) * 1e-5 # round-up to the gradient raster gx = make_trapezoid(channel='x', system=system, amplitude=k_width / readout_time, flat_time=flat_time) adc = make_adc(num_samples=Nx, duration=readout_time, delay=gx.rise_time + flat_time / 2 - (readout_time - dwell_time) / 2) # Pre-phasing gradients pre_time = 8e-4 gx_pre = make_trapezoid(channel='x', system=system, area=-gx.area / 2, duration=pre_time) gz_reph = make_trapezoid(channel='z', system=system, area=-gz.area / 2, duration=pre_time) gy_pre = make_trapezoid(channel='y', system=system, area=-Ny / 2 * delta_k, duration=pre_time) # Phase blip in shortest possible time dur = math.ceil(2 * math.sqrt(delta_k / system.max_slew) / 10e-6) * 10e-6 gy = make_trapezoid(channel='y', system=system, area=delta_k, duration=dur) # ====== # CONSTRUCT SEQUENCE
def make_block_pulse(flip_angle, system=Opts(), duration=0, freq_offset=0, phase_offset=0, time_bw_product=0, bandwidth=0, max_grad=0, max_slew=0, slice_thickness=0, delay=0, use=''): """ Makes a Holder object for an rf pulse Event. Parameters ---------- kwargs : dict Key value mappings of rf Event parameters_params and values. nargout: int Number of output arguments to be returned. Default is 1, only rf Event is returned. Passing any number greater than 1 will return the Gz Event along with the rf Event. Returns ------- Tuple consisting of: rf : Holder rf Event configured based on supplied kwargs. gz : Holder Slice select trapezoidal gradient Event. """ valid_use_pulses = ['excitation', 'refocusing', 'inversion'] if use not in valid_use_pulses: raise Exception() if duration == 0: if time_bw_product > 0: duration = time_bw_product / bandwidth elif bandwidth > 0: duration = 1 / (4 * bandwidth) else: raise ValueError('Either bandwidth or duration must be defined') BW = 1 / (4 * duration) N = round(duration / 1e-6) t = np.arange(N) * system.rf_raster_time signal = flip_angle / (2 * np.pi) / duration * np.ones(len(t)) rf = SimpleNamespace() rf.type = 'rf' rf.signal = signal rf.t = t rf.freq_offset = freq_offset rf.phase_offset = phase_offset rf.dead_time = system.rf_dead_time rf.ring_down_time = system.rf_ring_down_time if use != '': rf.use = use if rf.dead_time > rf.delay: rf.delay = rf.dead_time try: if slice_thickness < 0: raise ValueError('Slice thickness must be provided') if max_grad > 0: system.max_grad = max_grad if max_slew > 0: system.max_slew = max_slew amplitude = BW / slice_thickness area = amplitude * duration kwargs_for_trap = { 'channel': 'z', 'system': system, 'flat_time': duration, 'flat_area': area } gz = make_trapezoid(kwargs_for_trap) if rf.delay > gz.rise_time: gz.delay = math.ceil( (rf.delay - gz.rise_time) / system.grad_raster_time) * system.grad_raster_time if rf.delay < (gz.rise_time + gz.delay): rf.delay = gz.rise_time + gz.delay except: gz = None if rf.ring_down_time > 0: t_fill = np.arange(round(rf.ring_down_time / 1e-6) + 1) * 1e-6 rf.t = np.concatenate((rf.t, (rf.t[-1] + t_fill))) rf.signal = np.concatenate((rf.signal, np.zeros(len(t_fill)))) return rf, gz
def make_arbitrary_rf(flip_angle, system=Opts(), signal=0, freq_offset=0, phase_offset=0, time_bw_product=0, bandwidth=0, max_grad=0, max_slew=0, slice_thickness=0, delay=0, use=None): """ Makes a Holder object for an arbitrary rf pulse Event. Parameters ---------- kwargs : dict Key value mappings of rf Event parameters_params and values. nargout: int Number of output arguments to be returned. Default is 1, only rf Event is returned. Passing any number greater than 1 will return the Gz Event along with the rf Event. Returns ------- rf : Holder rf Event configured based on supplied kwargs. gz : Holder Slice select trapezoidal gradient Event. """ signal = signal / np.sum( signal * system.rf_raster_time) * flip_angle / (2 * pi) N = len(signal) duration = N * system.rf_raster_time t = np.arange(1, N=1) * system.rf_raster_time rf = SimpleNamespace() rf.type = 'rf' rf.signal = signal rf.t = t rf.freq_offset = freq_offset rf.phase_offset = phase_offset rf.rf_dead_time = system.rf_dead_time rf.ringdown_time = system.rf_ringdown_time rf.delay = delay if use is not None: rf.use = use try: if slice_thickness <= 0: raise ValueError('Slice thickness must be provided') if bandwidth <= 0: raise ValueError('Bandwidth of pulse must be provided') if max_grad > 0: system.max_grad = max_grad if max_slew > 0: system.max_slew = max_slew BW = bandwidth if time_bw_product > 0: BW = time_bw_product / duration amplitude = BW / slice_thickness area = amplitude * duration gz = make_trapezoid(channel='z', system=system, flat_time=duration, flat_area=area) if rf.delay > gz.rise_time: gz.delay = math.ceil( (rf.delay - gz.rise_time) / system.grad_raster_time) * system.grad_raster_time if rf.delay < (gz.rise_time + gz.delay): rf.delay = gz.rise_time + gz.delay except: gz = None if rf.ring_down_time > 0: t_fill = np.arange(1, round(rf.ringdown_time / 1e-6) + 1) * 1e-6 rf.t = np.concatenate((rf.t, rf.t[-1] + t_fill)) rf.signal = np.concatenate((rf.signal, np.zeros(len(t_fill)))) return rf, gz
TR = 180e-3 # in s BW = 50e3 # Hz Slicethickness = 3e-3 dt = 4e-6 GradRasterTime = 1 / (2 * BW) # s flip = 90 * pi / 180 kwargs_for_sinc = {"flip_angle": flip, "system": system, "duration": 3e-3, "slice_thickness": Slicethickness, "apodization": 0.5, "time_bw_product": 4} rf, gz = make_sinc_pulse(kwargs_for_sinc, 2) delta_k = 1 / fov kWidth = Nx * delta_k readoutTime = GradRasterTime * Nx kwargs_for_gx = {"channel": 'x', "system": system, "flat_area": kWidth, "flat_time": readoutTime} gx = make_trapezoid(kwargs_for_gx) # kwargs_for_adc = {"num_samples": Nx, "system": system, "duration": gx.flat_time, # "delay": gx.rise_time} # adc = mr.makeAdc(Nx,lims,'Duration',gx.flatTime,'Delay',gx.riseTime) # CHANGE: Matched ADC parameters with Matlab code kwargs_for_adc = {"num_samples": Nx, "dwell": 4e-6} adc = make_adc(kwargs_for_adc) # kwargs_for_adc = {"num_samples": Nx, "Dwell": dt} # adc = makeadc(kwargs_for_adc) count = count + 1 # kwargs_for_gxPre = {"channel": 'x', "system": system, "area": -gx.area/2, "Duration": readoutTime/2} # gxPre = maketrapezoid(kwargs_for_gxPre)
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
# scanner limits sys = Opts(max_grad=40, grad_unit='mT/m', max_slew=130, slew_unit='T/m/s', rf_ringdown_time=30e-6, rf_dead_time=100e-6, rf_raster_time=1e-6) gamma_hz = 42.5764 # =========== # PREPARATION # =========== # spoiler 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()
def make_block_pulse(flip_angle: float, bandwidth: float = 0, delay: float = 0, duration: float = 0, freq_offset: float = 0, max_grad: float = 0, max_slew: float = 0, phase_offset: float = 0, return_gz: bool = False, system: Opts = Opts(), slice_thickness: float = 0, time_bw_product: float = 0, use: str = str()) -> Union[SimpleNamespace, Tuple[SimpleNamespace, SimpleNamespace]]: """ Creates a radio-frequency block pulse event and optionally an accompanying slice select trapezoidal gradient event. Parameters ---------- flip_angle : float Flip angle in radians. bandwidth : float, default=0 Bandwidth in Hertz (hz). delay : float, default=0 Delay in milliseconds (ms) of accompanying slice select trapezoidal event. duration : float, default=0 Duration in milliseconds (ms). freq_offset : float, default=0 Frequency offset in Hertz (Hz). max_grad : float, default=0 Maximum gradient strength of accompanying slice select trapezoidal event. max_slew : float, default=0 Maximum slew rate of accompanying slice select trapezoidal event. phase_offset : float, default=0 Phase offset Hertz (Hz). return_gz : bool, default=False Boolean flag to indicate if slice-selective gradient has to be returned. slice_thickness : float, default=0 Slice thickness of accompanying slice select trapezoidal event. The slice thickness determines the area of the slice select event. system : Opts, default=Opts() System limits. time_bw_product : float, default=0 Time-bandwidth product. use : str, default=str() Use of radio-frequency block pulse event. Must be one of 'excitation', 'refocusing' or 'inversion'. Returns ------- rf : SimpleNamespace Radio-frequency block pulse event. gz : SimpleNamespace Slice select trapezoidal gradient event accompanying the radio-frequency block pulse event. Raises ------ ValueError If invalid `use` parameter is passed. Must be one of 'excitation', 'refocusing' or 'inversion'. If neither `bandwidth` nor `duration` are passed. If `return_gz=True`, and `slice_thickness` is not passed. """ valid_use_pulses = ['excitation', 'refocusing', 'inversion'] if use != '' and use not in valid_use_pulses: raise ValueError( f"Invalid use parameter. Must be one of 'excitation', 'refocusing' or 'inversion'. Passed: {use}") if duration == 0: if time_bw_product > 0: duration = time_bw_product / bandwidth elif bandwidth > 0: duration = 1 / (4 * bandwidth) else: raise ValueError('Either bandwidth or duration must be defined') BW = 1 / (4 * duration) N = round(duration / 1e-6) t = np.arange(1, N + 1) * system.rf_raster_time signal = flip_angle / (2 * np.pi) / duration * np.ones(len(t)) rf = SimpleNamespace() rf.type = 'rf' rf.signal = signal rf.t = t rf.freq_offset = freq_offset rf.phase_offset = phase_offset rf.dead_time = system.rf_dead_time rf.ringdown_time = system.rf_ringdown_time rf.delay = delay if use != '': rf.use = use if rf.dead_time > rf.delay: rf.delay = rf.dead_time if return_gz: if slice_thickness < 0: raise ValueError('Slice thickness must be provided') if max_grad > 0: system.max_grad = max_grad if max_slew > 0: system.max_slew = max_slew amplitude = BW / slice_thickness area = amplitude * duration gz = make_trapezoid(channel='z', system=system, flat_time=duration, flat_area=area) if rf.delay > gz.rise_time: gz.delay = math.ceil((rf.delay - gz.rise_time) / system.grad_raster_time) * system.grad_raster_time if rf.delay < (gz.rise_time + gz.delay): rf.delay = gz.rise_time + gz.delay if rf.ringdown_time > 0: t_fill = np.arange(1, round(rf.ringdown_time / 1e-6) + 1) * 1e-6 rf.t = np.concatenate((rf.t, (rf.t[-1] + t_fill))) rf.signal = np.concatenate((rf.signal, np.zeros(len(t_fill)))) if return_gz: return rf, gz else: return rf
Nx = 256 Ny = 256 alpha = 10 slice_thickness = 3e-3 TE = 7.38e-3 TR = 100e-3 rf_spoiling_inc = 117 sys = Opts(max_grad=28, grad_unit='mT/m', max_slew=150, slew_unit='T/m/s', rf_ringdown_time=20e-6, rf_dead_time=100e-6, adc_dead_time=10e-6) rf, gz, gzr = make_sinc_pulse(flip_angle=alpha * math.pi / 180, duration=4e-3, slice_thickness=slice_thickness, apodization=0.5, time_bw_product=4, system=sys) deltak = 1 / fov gx = make_trapezoid(channel='x', flat_area=Nx * deltak, flat_time=6.4e-3, system=sys) adc = make_adc(num_samples=Nx, duration=gx.flat_time, delay=gx.rise_time, system=sys) gx_pre = make_trapezoid(channel='x', area=-gx.area / 2, duration=2e-3, system=sys) gz_reph = make_trapezoid(channel='z', area=-gz.area / 2, duration=2e-3, system=sys) phase_areas = (np.arange(Ny) - Ny / 2) * deltak gx_spoil = make_trapezoid(channel='x', area=2 * Nx * deltak, system=sys) gz_spoil = make_trapezoid(channel='z', area=4 / slice_thickness, system=sys) delay_TE = math.ceil((TE - calc_duration(gx_pre) - gz.fall_time - gz.flat_time / 2 - calc_duration( gx) / 2) / seq.grad_raster_time) * seq.grad_raster_time delay_TR = math.ceil((TR - calc_duration(gx_pre) - calc_duration(gz) - calc_duration( gx) - delay_TE) / seq.grad_raster_time) * seq.grad_raster_time if not np.all(delay_TR >= calc_duration(gx_spoil, gz_spoil)): raise Exception()
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_sinc_pulse_channel(channel: str, flip_angle: float, system: Opts = Opts(), duration: float = 0, freq_offset: float = 0, phase_offset: float = 0, time_bw_product: float = 4, apodization: float = 0, center_pos: float = 0.5, max_grad: float = 0, max_slew: float = 0, slice_thickness: float = 0, delay: float = 0, use: str = None): """ Creates a radio-frequency sinc pulse event and optionally accompanying slice select and slice select rephasing trapezoidal gradient events. Parameters ---------- flip_angle : float Flip angle in radians. system : Opts, optional System limits. Default is a system limits object initialised to default values. duration : float, optional Duration in milliseconds (ms). Default is 0. freq_offset : float, optional Frequency offset in Hertz (Hz). Default is 0. phase_offset : float, optional Phase offset in Hertz (Hz). Default is 0. time_bw_product : float, optional Time-bandwidth product. Default is 4. apodization : float, optional Apodization. Default is 0. center_pos : float, optional Position of peak. Default is 0.5 (midway). max_grad : float, optional Maximum gradient strength of accompanying slice select trapezoidal event. Default is 0. max_slew : float, optional Maximum slew rate of accompanying slice select trapezoidal event. Default is 0. slice_thickness : float, optional Slice thickness of accompanying slice select trapezoidal event. The slice thickness determines the area of the slice select event. Default is 0. delay : float, optional Delay in milliseconds (ms). Default is 0. use : str, optional Use of radio-frequency sinc pulse. Must be one of 'excitation', 'refocusing' or 'inversion'. Returns ------- rf : SimpleNamespace Radio-frequency sinc pulse event. gz : SimpleNamespace, optional Accompanying slice select trapezoidal gradient event. Returned only if `slice_thickness` is provided. gzr : SimpleNamespace, optional Accompanying slice select rephasing trapezoidal gradient event. Returned only if `slice_thickness` is provided. """ valid_use_pulses = ['excitation', 'refocusing', 'inversion'] if use is not None and use not in valid_use_pulses: raise ValueError( f'Invalid use parameter. Must be one of excitation, refocusing or inversion. You passed: {use}' ) if channel not in ['x', 'y', 'z']: raise ValueError() BW = time_bw_product / duration alpha = apodization N = int(round(duration / 1e-6)) t = np.arange(1, N + 1) * system.rf_raster_time tt = t - (duration * center_pos) window = 1 - alpha + alpha * np.cos(2 * np.pi * tt / duration) signal = np.multiply(window, np.sinc(BW * tt)) flip = np.sum(signal) * system.rf_raster_time * 2 * np.pi signal = signal * flip_angle / flip rf = SimpleNamespace() rf.type = 'rf' rf.signal = signal rf.t = t rf.freq_offset = freq_offset rf.phase_offset = phase_offset rf.dead_time = system.rf_dead_time rf.ringdown_time = system.rf_ringdown_time rf.delay = delay if use is not None: rf.use = use if rf.dead_time > rf.delay: rf.delay = rf.dead_time try: if slice_thickness == 0: raise ValueError('Slice thickness must be provided') if max_grad > 0: system.max_grad = max_grad if max_slew > 0: system.max_slew = max_slew amplitude = BW / slice_thickness area = amplitude * duration g = make_trapezoid(channel=channel, system=system, flat_time=duration, flat_area=area) gr = make_trapezoid(channel=channel, system=system, area=-area * (1 - center_pos) - 0.5 * (g.area - area)) if rf.delay > g.rise_time: g.delay = math.ceil( (rf.delay - g.rise_time) / system.grad_raster_time) * system.grad_raster_time if rf.delay < (g.rise_time + g.delay): rf.delay = g.rise_time + g.delay except: g = None gr = None if rf.ringdown_time > 0: t_fill = np.arange(1, round(rf.ringdown_time / 1e-6) + 1) * 1e-6 rf.t = np.concatenate((rf.t, rf.t[-1] + t_fill)) rf.signal = np.concatenate((rf.signal, np.zeros(len(t_fill)))) # Following 2 lines of code are workarounds for numpy returning 3.14... for np.angle(-0.00...) negative_zero_indices = np.where(rf.signal == -0.0) rf.signal[negative_zero_indices] = 0 return rf, g, gr
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
def opt_TE_bv_SE(bvalue_Dict, grads_times_Dict, seq_sys_Dict): """ Obtain optimal TE and diffusion-weighting gradient waveforms for twice-refocued spin echo (TRSE) DW sequence. Parameters ---------- bvalue_Dict : dictionary b-value related parameters needed in the TE optimization. grads_times_Dict : dictionary gradient times related parameters needed in the TE optimization. seq_sys_Dict : dictionary sequence and system related parameters needed in the TE optimization. Returns ------- n_TE : int echo time in integers units. bval : float b-value in s/mm2 gdiff : pypulseq gradient diffusion-weighting gradient waveform. n_delay_te1 : int delay between RF90 and RF180 in integers units. n_delay_te2 : int delay between RF180 and EPI readout in integers units. """ # Description # For S&T monopolar waveforms. # From an initial TE, check we satisfy all constraints -> otherwise increase TE. # Once all constraints are okay -> check b-value, if it is lower than the target one -> increase TE # Looks time-inefficient but it is fast enough to make it user-friendly. # We need to compute the exact time sequence. For the normal SE-MONO-EPI sequence micro second differences # are not important, however, if we wanna import external gradients the allocated time for them needs to # be the same, and thus exact timing is mandatory. With this in mind, we establish the following rounding rules: # Duration of RFs + spoiling, and EPI time to the center of the k-space is always math.ceil(). bvalue = bvalue_Dict["bvalue"] # Target b-value. nbvals = bvalue_Dict["nbvals"] # number of b-values. gscl = bvalue_Dict["gscl"] # gradient scaling. n_rf90r = grads_times_Dict[ "n_rf90r"] # Half right duration of RF90 (integer). n_rf180r = grads_times_Dict[ "n_rf180r"] # Half right duration of the RF180 (integer). n_rf180l = grads_times_Dict[ "n_rf180l"] # Half left duration of the RF180 (integer). gz_spoil = grads_times_Dict[ "gz_spoil"] # Spoil gradient to be placed around the RF180. gz180 = grads_times_Dict["gz180"] # RF180 simultaneous gradient. n_duration_center = grads_times_Dict[ "n_duration_center"] # Time needed to achieve the center of the k-space (integer). seq = seq_sys_Dict["seq"] # Sequence system = seq_sys_Dict["system"] # System i_raster_time = seq_sys_Dict[ "i_raster_time"] # Manually inputed inverse raster time. # Find minimum TE considering the readout times. n_TE = math.ceil(40e-3 / seq.grad_raster_time) n_delay_te2 = -1 while n_delay_te2 <= 0: n_TE = n_TE + 2 n_tINV = math.floor(n_TE / 2) n_delay_te2 = n_tINV - n_rf180r - n_duration_center # Find minimum TE for the target b-value bvalue_tmp = 0 while bvalue_tmp < np.max(bvalue): n_TE = n_TE + 2 n_tINV = math.floor(n_TE / 2) n_delay_te1 = n_tINV - n_rf90r - n_rf180l n_delay_te2 = n_tINV - n_rf180r - n_duration_center # Waveform Ramp time n_gdiff_rt = math.ceil(system.max_grad / system.max_slew / seq.grad_raster_time) # Select the shortest available time n_gdiff_delta = min(n_delay_te1, n_delay_te2) n_gdiff_Delta = n_delay_te1 + 2 * math.ceil( calc_duration(gz_spoil) / seq.grad_raster_time) + math.ceil( calc_duration(gz180) / seq.grad_raster_time) gdiff = make_trapezoid(channel='x', system=system, amplitude=system.max_grad, duration=n_gdiff_delta / i_raster_time) # delta only corresponds to the rectangle. n_gdiff_delta = n_gdiff_delta - 2 * n_gdiff_rt bv = calc_bval(system.max_grad, n_gdiff_delta / i_raster_time, n_gdiff_Delta / i_raster_time, n_gdiff_rt / i_raster_time) bval = bv * 1e-6 # Show final TE and b-values: print("TE:", round(n_TE / i_raster_time * 1e3, 2), "ms") for bv in range(1, nbvals + 1): print( round( calc_bval(system.max_grad * gscl[bv], n_gdiff_delta / i_raster_time, n_gdiff_Delta / i_raster_time, n_gdiff_rt / i_raster_time) * 1e-6, 2), "s/mm2") return n_TE, bval, gdiff, n_delay_te1, n_delay_te2
def sigpy_n_seq( flip_angle: float, apodization: float = 0, delay: float = 0, duration: float = 0, freq_offset: float = 0, center_pos: float = 0.5, max_grad: float = 0, max_slew: float = 0, phase_offset: float = 0, return_gz: bool = True, slice_thickness: float = 0, system: Opts = Opts(), time_bw_product: float = 4, pulse_cfg: pulse_opts = pulse_opts(), use: str = str() ) -> Union[SimpleNamespace, Tuple[SimpleNamespace, SimpleNamespace, SimpleNamespace]]: """ Creates a radio-frequency sinc pulse event using the sigpy rf pulse library and optionally accompanying slice select, slice select rephasing trapezoidal gradient events. Parameters ---------- flip_angle : float Flip angle in radians. apodization : float, optional, default=0 Apodization. center_pos : float, optional, default=0.5 Position of peak.5 (midway). delay : float, optional, default=0 Delay in milliseconds (ms). duration : float, optional, default=0 Duration in milliseconds (ms). freq_offset : float, optional, default=0 Frequency offset in Hertz (Hz). max_grad : float, optional, default=0 Maximum gradient strength of accompanying slice select trapezoidal event. max_slew : float, optional, default=0 Maximum slew rate of accompanying slice select trapezoidal event. phase_offset : float, optional, default=0 Phase offset in Hertz (Hz). return_gz:bool, default=False Boolean flag to indicate if slice-selective gradient has to be returned. slice_thickness : float, optional, default=0 Slice thickness of accompanying slice select trapezoidal event. The slice thickness determines the area of the slice select event. system : Opts, optional System limits. Default is a system limits object initialised to default values. time_bw_product : float, optional, default=4 Time-bandwidth product. use : str, optional, default=str() Use of radio-frequency sinc pulse. Must be one of 'excitation', 'refocusing' or 'inversion'. Returns ------- rf : SimpleNamespace Radio-frequency sinc pulse event. gz : SimpleNamespace, optional Accompanying slice select trapezoidal gradient event. Returned only if `slice_thickness` is provided. gzr : SimpleNamespace, optional Accompanying slice select rephasing trapezoidal gradient event. Returned only if `slice_thickness` is provided. Raises ------ ValueError If invalid `use` parameter was passed. Must be one of 'excitation', 'refocusing' or 'inversion'. If `return_gz=True` and `slice_thickness` was not provided. """ valid_use_pulses = ['excitation', 'refocusing', 'inversion'] if use != '' and use not in valid_use_pulses: raise ValueError( f"Invalid use parameter. Must be one of 'excitation', 'refocusing' or 'inversion'. Passed: {use}" ) if pulse_cfg.pulse_type == 'slr': [signal, t, pulse] = make_slr(flip_angle=flip_angle, time_bw_product=time_bw_product, duration=duration, system=system, pulse_cfg=pulse_cfg, disp=True) if pulse_cfg.pulse_type == 'sms': [signal, t, pulse] = make_sms(flip_angle=flip_angle, time_bw_product=time_bw_product, duration=duration, system=system, pulse_cfg=pulse_cfg, disp=True) rfp = SimpleNamespace() rfp.type = 'rf' rfp.signal = signal rfp.t = t rfp.freq_offset = freq_offset rfp.phase_offset = phase_offset rfp.dead_time = system.rf_dead_time rfp.ringdown_time = system.rf_ringdown_time rfp.delay = delay if use != '': rfp.use = use if rfp.dead_time > rfp.delay: rfp.delay = rfp.dead_time if return_gz: if slice_thickness == 0: raise ValueError('Slice thickness must be provided') if max_grad > 0: system.max_grad = max_grad if max_slew > 0: system.max_slew = max_slew BW = time_bw_product / duration amplitude = BW / slice_thickness area = amplitude * duration gz = make_trapezoid(channel='z', system=system, flat_time=duration, flat_area=area) gzr = make_trapezoid(channel='z', system=system, area=-area * (1 - center_pos) - 0.5 * (gz.area - area)) if rfp.delay > gz.rise_time: gz.delay = math.ceil( (rfp.delay - gz.rise_time) / system.grad_raster_time) * system.grad_raster_time if rfp.delay < (gz.rise_time + gz.delay): rfp.delay = gz.rise_time + gz.delay if rfp.ringdown_time > 0: t_fill = np.arange(1, round(rfp.ringdown_time / 1e-6) + 1) * 1e-6 rfp.t = np.concatenate((rfp.t, rfp.t[-1] + t_fill)) rfp.signal = np.concatenate((rfp.signal, np.zeros(len(t_fill)))) # Following 2 lines of code are workarounds for numpy returning 3.14... for np.angle(-0.00...) negative_zero_indices = np.where(rfp.signal == -0.0) rfp.signal[negative_zero_indices] = 0 if return_gz: return rfp, gz, gzr, pulse else: return rfp
def make_arbitrary_rf( signal: np.ndarray, flip_angle: float, bandwidth: float = 0, delay: float = 0, freq_offset: float = 0, max_grad: float = 0, max_slew: float = 0, phase_offset: float = 0, return_gz: bool = False, slice_thickness: float = 0, system: Opts = Opts(), time_bw_product: float = 0, use: str = str() ) -> Union[SimpleNamespace, Tuple[SimpleNamespace, SimpleNamespace]]: """ Creates a radio-frequency pulse event with arbitrary pulse shape and optionally an accompanying slice select trapezoidal gradient event. Parameters ---------- signal : numpy.ndarray Arbitrary waveform. flip_angle : float Flip angle in radians. bandwidth : float, default=0 Bandwidth in Hertz (Hz). delay : float, default=0 Delay in milliseconds (ms) of accompanying slice select trapezoidal event. freq_offset : float, default=0 Frequency offset in Hertz (Hz). max_grad : float, default=system.max_grad Maximum gradient strength of accompanying slice select trapezoidal event. max_slew : float, default=system.max_slew Maximum slew rate of accompanying slice select trapezoidal event. phase_offset : float, default=0 Phase offset in Hertz (Hz).a return_gz : bool, default=False Boolean flag to indicate if slice-selective gradient has to be returned. slice_thickness : float, default=0 Slice thickness of accompanying slice select trapezoidal event. The slice thickness determines the area of the slice select event. system : Opts, default=Opts() System limits. time_bw_product : float, default=4 Time-bandwidth product. use : str, default=str() Use of arbitrary radio-frequency pulse event. Must be one of 'excitation', 'refocusing' or 'inversion'. Returns ------- rf : SimpleNamespace Radio-frequency pulse event with arbitrary pulse shape. gz : SimpleNamespace, if return_gz=True Slice select trapezoidal gradient event accompanying the arbitrary radio-frequency pulse event. Raises ------ ValueError If invalid `use` parameter is passed. Must be one of 'excitation', 'refocusing' or 'inversion'. If `signal` with ndim > 1 is passed. If `return_gz=True`, and `slice_thickness` and `bandwith` are not passed. """ valid_use_pulses = ['excitation', 'refocusing', 'inversion'] if use != '' and use not in valid_use_pulses: raise ValueError( f"Invalid use parameter. Must be one of 'excitation', 'refocusing' or 'inversion'. Passed: {use}" ) signal = np.squeeze(signal) if signal.ndim > 1: raise ValueError( f'signal should have ndim=1. Passed ndim={signal.ndim}') signal = signal / np.sum( signal * system.rf_raster_time) * flip_angle / (2 * np.pi) N = len(signal) duration = N * system.rf_raster_time t = np.arange(1, N + 1) * system.rf_raster_time rf = SimpleNamespace() rf.type = 'rf' rf.signal = signal rf.t = t rf.freq_offset = freq_offset rf.phase_offset = phase_offset rf.dead_time = system.rf_dead_time rf.ringdown_time = system.rf_ringdown_time rf.delay = delay if use != '': rf.use = use if rf.dead_time > rf.delay: rf.delay = rf.dead_time if return_gz: if slice_thickness <= 0: raise ValueError('Slice thickness must be provided.') if bandwidth <= 0: raise ValueError('Bandwidth of pulse must be provided.') if max_grad > 0: system.max_grad = max_grad if max_slew > 0: system.max_slew = max_slew BW = bandwidth if time_bw_product > 0: BW = time_bw_product / duration amplitude = BW / slice_thickness area = amplitude * duration gz = make_trapezoid(channel='z', system=system, flat_time=duration, flat_area=area) if rf.delay > gz.rise_time: gz.delay = math.ceil( (rf.delay - gz.rise_time) / system.grad_raster_time) * system.grad_raster_time if rf.delay < (gz.rise_time + gz.delay): rf.delay = gz.rise_time + gz.delay if rf.ringdown_time > 0: t_fill = np.arange(1, round(rf.ringdown_time / 1e-6) + 1) * 1e-6 rf.t = np.concatenate((rf.t, rf.t[-1] + t_fill)) rf.signal = np.concatenate((rf.signal, np.zeros(len(t_fill)))) return rf, gz if return_gz else rf
rf_ringdown_time=20e-6, rf_dead_time=100e-6, adc_dead_time=10e-6) # Fat saturation if fatsat_enable: b0 = 1.494 sat_ppm = -3.45 sat_freq = sat_ppm * 1e-6 * b0 * system.gamma rf_fs, _, _ = make_gauss_pulse(flip_angle=110 * math.pi / 180, system=system, duration=8e-3, bandwidth=abs(sat_freq), freq_offset=sat_freq) gz_fs = make_trapezoid(channel='z', system=system, delay=calc_duration(rf_fs), area=1 / 1e-4) # Create 90 degree slice selection pulse and gradient rf, gz, _ = make_sinc_pulse(flip_angle=math.pi / 2, system=system, duration=3e-3, slice_thickness=slice_thickness, apodization=0.5, time_bw_product=4) # Refocusing pulse with spoiling gradients rf180, gz180, _ = make_sinc_pulse(flip_angle=math.pi, system=system, duration=5e-3, slice_thickness=slice_thickness,
"time_bw_product": 4 } rf, gz = make_sinc_pulse(kwargs_for_sinc, 2) # plt.plot(rf.t[0], rf.signal[0]) # plt.show() delta_k = 1 / fov kWidth = Nx * delta_k readoutTime = system.grad_raster_time * Nx kwargs_for_gx = { "channel": 'x', "system": system, "flat_area": kWidth, "flat_time": readoutTime } gx = make_trapezoid(kwargs_for_gx) kwargs_for_adc = { "num_samples": Nx, "system": system, "duration": gx.flat_time, "delay": gx.rise_time } adc = make_adc(kwargs_for_adc) kwargs_for_gxpre = { "channel": 'x', "system": system, "area": gx.area / 2, "duration": readoutTime / 2 } gx_pre = make_trapezoid(kwargs_for_gxpre)
def make_sinc_pulse(flip_angle, system=Opts(), duration=0, freq_offset=0, phase_offset=0, time_bw_product=4, bandwidth=0, apodization=0, center_pos=0.5, max_grad=0, max_slew=0, slice_thickness=0, delay=0, use=None): """ Makes a Holder object for an rf pulse Event. Parameters ---------- kwargs : dict Key value mappings of rf Event parameters_params and values. nargout: int Number of output arguments to be returned. Default is 1, only rf Event is returned. Passing any number greater than 1 will return the Gz Event along with the rf Event. Returns ------- rf : Holder rf Event configured based on supplied kwargs. gz : Holder Slice select trapezoidal gradient Event. """ if bandwidth == 0: BW = time_bw_product / duration else: BW = bandwidth alpha = apodization N = int(round(duration / 1e-6)) t = np.arange(1, N + 1) * system.rf_raster_time tt = t - (duration * center_pos) window = 1 - alpha + alpha * np.cos(2 * np.pi * tt / duration) signal = np.multiply(window, __gauss(BW * tt)) flip = np.sum(signal) * system.rf_raster_time * 2 * np.pi signal = signal * flip_angle / flip rf = SimpleNamespace() rf.type = 'rf' rf.signal = signal rf.t = t rf.freq_offset = freq_offset rf.phase_offset = phase_offset rf.dead_time = system.rf_dead_time rf.ring_down_time = system.rf_ringdown_time rf.delay = delay if use is not None: rf.use = use if rf.dead_time > rf.delay: rf.delay = rf.dead_time try: if slice_thickness == 0: raise ValueError('Slice thickness must be provided') if max_grad > 0: system.max_grad = max_grad if max_slew > 0: system.max_slew = max_slew amplitude = BW / slice_thickness area = amplitude * duration gz = make_trapezoid(channel='z', system=system, flat_time=duration, flat_area=area) gzr = make_trapezoid(channel='z', system=system, area=-area * (1 - center_pos) - 0.5 * (gz.area - area)) if rf.delay > gz.rise_time: gz.delay = math.ceil( (rf.delay - gz.rise_time) / system.grad_raster_time) * system.grad_raster_time if rf.delay < (gz.rise_time + gz.delay): rf.delay = gz.rise_time + gz.delay except: gz = None gzr = None if rf.ring_down_time > 0: t_fill = np.arange(1, round(rf.ring_down_time / 1e-6) + 1) * 1e-6 rf.t = np.concatenate((rf.t, rf.t[-1] + t_fill)) rf.signal = np.concatenate((rf.signal, np.zeros(len(t_fill)))) # Following 2 lines of code are workarounds for numpy returning 3.14... for np.angle(-0.00...) negative_zero_indices = np.where(rf.signal == -0.0) rf.signal[negative_zero_indices] = 0 return rf, gz, gzr
t_ex = 2.5e-3 t_ex_wd = t_ex + system.rf_ringdown_time + system.rf_dead_time t_ref = 2e-3 tf_ref_wd = t_ref + system.rf_ringdown_time + system.rf_dead_time t_sp = 0.5 * (TE - readout_time - tf_ref_wd) t_sp_ex = 0.5 * (TE - t_ex_wd - tf_ref_wd) fspR = 1.0 fspS = 0.5 rfex_phase = math.pi / 2 rfref_phase = 0 flipex = 90 * math.pi / 180 rfex, gz, _ = make_sinc_pulse(flip_angle=flipex, system=system, duration=t_ex, slice_thickness=slice_thickness, apodization=0.5, time_bw_product=4, phase_offset=rfex_phase) GS_ex = make_trapezoid(channel='z', system=system, amplitude=gz.amplitude, flat_time=t_ex_wd, rise_time=dG) flipref = rf_flip[0] * math.pi / 180 rfref, gz, _ = make_sinc_pulse(flip_angle=flipref, system=system, duration=t_ref, slice_thickness=slice_thickness, apodization=0.5, time_bw_product=4, phase_offset=rfref_phase, use='refocusing') GS_ref = make_trapezoid(channel='z', system=system, amplitude=GS_ex.amplitude, flat_time=tf_ref_wd, rise_time=dG) AGS_ex = GS_ex.area / 2 GS_spr = make_trapezoid(channel='z', system=system, area=AGS_ex * (1 + fspS), duration=t_sp, rise_time=dG) GS_spex = make_trapezoid(channel='z', system=system, area=AGS_ex * fspS, duration=t_sp_ex, rise_time=dG) delta_k = 1 / fov k_width = Nx * delta_k GR_acq = make_trapezoid(channel='x', system=system, flat_area=k_width, flat_time=readout_time, rise_time=dG) adc = make_adc(num_samples=Nx, duration=GR_acq.flat_time - 40e-6, delay=20e-6)
# Sequence Parameters FOV = 200e-3 # [m] Ny = 128 Nstartup = 11 # Calculate TR _, TR, Ny_aq = bssfp_readout(Sequence(system), system, fov=FOV, Nstartup=Nstartup, Ny=Ny) # Calculate Crushers gzSpoil_INV = make_trapezoid('z', area=-3e3, duration=9.5e-3, system=system) FlatArea_crusher = [0, 0.4e3, 0.32e3, 0.27e3, 0.32e3, 0, 0.53e3, 0.32e3] FlatTime_crusher = [0, 5e-4, 6.3e-4, 6.3e-4, 6.3e-4, 0, 9.3e-4, 6.3e-4] crushers_in_between = 1 # Create adiabatic inversion pulse rf_inv = make_hyperSec_pulse(system=system, duration=10.24 * 1e-3) InvDur = rf_inv.t[-1] # duration Tdelay_trig = 550e-3 # [s] trig = make_trigger(delay=100e-6, duration=Tdelay_trig - (Nstartup + 1) * TR, system=system) trig_BetweenInversion = make_trigger(delay=100e-6, duration=Tdelay_trig,
# unit to [Hz/m], make spiral gradients sp_x *= system.gamma sp_y *= system.gamma spirals[k]['spiral'][0] = make_arbitrary_grad(channel='x', waveform=sp_x, delay=system.adc_dead_time, system=system) spirals[k]['spiral'][1] = make_arbitrary_grad(channel='y', waveform=sp_y, delay=system.adc_dead_time, system=system) if spiraltype==1: # calculate rephaser area area_x = sp_x.sum()*system.grad_raster_time area_y = sp_y.sum()*system.grad_raster_time # calculate rephasers and make gradients amp_x, ftop_x, ramp_x = ph.trap_from_area(-area_x, system, slewrate = 100) # reduce slew rate to 100 T/m/s to avoid stimulation amp_y, ftop_y, ramp_y = ph.trap_from_area(-area_y, system, slewrate = 100) spirals[k]['reph'][0] = make_trapezoid(channel='x', system=system, amplitude=amp_x, flat_time=ftop_x, rise_time=ramp_x) spirals[k]['reph'][1] = make_trapezoid(channel='y', system=system, amplitude=amp_y, flat_time=ftop_y, rise_time=ramp_y) reph_dur.append(max(ftop_x+2*ramp_x, ftop_y+2*ramp_y)) # check for acoustic resonances (checks only spirals) freq_max = ph.check_resonances([spiral_x,spiral_y]) #%% Gradient Spoiler on slice axis spoiler_area = 1.5*gz.flat_area - gz.area/2 # 1.5x moment under excitation pulse amp_spoil, ftop_spoil, ramp_spoil = ph.trap_from_area(spoiler_area, system, slewrate=100) # reduce slew rate to 100 T/m/s to avoid stimulation spoiler_z = make_trapezoid(channel='z',system=system, amplitude=amp_spoil, flat_time=ftop_spoil, rise_time=ramp_spoil, delay=100e-6) spoiler_dur = calc_duration(spoiler_z) reph_delay = spoiler_dur - np.max(reph_dur)
def bssfp_readout(seq, system, fov=200e-3, Nstartup=11, Ny=128): """ Creates a Balanced steady-state free precession (bSSFP) sequence and adds to seq object Parameters ---------- seq: object Sequence object system : Opts System limits. fov : float, optional Field-of-view [m]. Default is 0.2 m. Nstartup : float, optional Number of start-up RF pulses. Default is 11 Ny : float, optional Number of phase encoding lines. Default is 128. Returns ------- seq : SimpleNamespace Seq object with bSSFP readout TR : float Repetition time Ny : float Final number of phase encoding lines. """ # Sequence Parameters enc = 'xyz' Nx = 128 thk = 6e-3 fa = 35 # [deg] Nramp = Nstartup # ADC duration (controls TR/TE) adc_dur = 2560 / 2 # [us] rf_dur = 490 # [us] rf_apo = 0.5 rf_bwt = 1.5 ############################################################################# # Create slice selection pulse and gradient rf, g_ss, __ = make_sinc_pulse( flip_angle=fa * pi / 180, system=system, duration=rf_dur * 1e-6, slice_thickness=thk, apodization=rf_apo, time_bw_product=rf_bwt ) # for next pyPulseq version add:, return_gz=True) g_ss.channel = enc[2] # Slice refocusing g_ss_reph = make_trapezoid(channel=enc[2], system=system, area=-g_ss.area / 2, duration=0.00017 * 2) ############################################################################# rf.delay = calc_duration(g_ss) - calc_duration(rf) + rf.delay ############################################################################# # Readout gradient and ADC delta_k = 1 / fov kWidth = Nx * delta_k # Readout and ADC g_ro = make_trapezoid(channel=enc[0], system=system, flat_area=kWidth, flat_time=(adc_dur) * 1e-6) adc = make_adc(num_samples=Nx, duration=g_ro.flat_time, delay=g_ro.rise_time) # Readout rewinder g_ro_pre = make_trapezoid(channel=enc[0], system=system, area=-g_ro.area / 2) phaseAreas_tmp = np.arange(0, Ny, 1).tolist() phaseAreas = np.dot(np.subtract(phaseAreas_tmp, Ny / 2), delta_k) gs8_times = [ 0, g_ro.fall_time, g_ro.fall_time + g_ro_pre.rise_time, g_ro.fall_time + g_ro_pre.rise_time + g_ro_pre.flat_time, g_ro.fall_time + g_ro_pre.rise_time + g_ro_pre.flat_time + g_ro_pre.fall_time ] gs8_amp = [g_ro.amplitude, 0, g_ro_pre.amplitude, g_ro_pre.amplitude, 0] gx_2 = make_extended_trapezoid(channel=enc[0], times=gs8_times, amplitudes=gs8_amp) # Calculate phase encoding gradient duration pe_dur = calc_duration(gx_2) gx_allExt_times = [ 0, g_ro_pre.rise_time, g_ro_pre.rise_time + g_ro_pre.flat_time, g_ro_pre.rise_time + g_ro_pre.flat_time + g_ro_pre.fall_time, g_ro_pre.rise_time + g_ro_pre.flat_time + g_ro_pre.fall_time + g_ro.rise_time, g_ro_pre.rise_time + g_ro_pre.flat_time + g_ro_pre.fall_time + g_ro.rise_time + g_ro.flat_time + 1e-5, g_ro_pre.rise_time + g_ro_pre.flat_time + g_ro_pre.fall_time + g_ro.rise_time + g_ro.flat_time + 1e-5 + g_ro.fall_time, g_ro_pre.rise_time + g_ro_pre.flat_time + g_ro_pre.fall_time + g_ro.rise_time + g_ro.flat_time + 1e-5 + g_ro.fall_time + g_ro_pre.rise_time, g_ro_pre.rise_time + g_ro_pre.flat_time + g_ro_pre.fall_time + g_ro.rise_time + g_ro.flat_time + 1e-5 + g_ro.fall_time + g_ro_pre.rise_time + g_ro_pre.flat_time, g_ro_pre.rise_time + g_ro_pre.flat_time + g_ro_pre.fall_time + g_ro.rise_time + g_ro.flat_time + 1e-5 + g_ro.fall_time + g_ro_pre.rise_time + g_ro_pre.flat_time + g_ro_pre.fall_time ] gx_allExt_amp = [ 0, g_ro_pre.amplitude, g_ro_pre.amplitude, 0, g_ro.amplitude, g_ro.amplitude, 0, g_ro_pre.amplitude, g_ro_pre.amplitude, 0 ] gx_all = make_extended_trapezoid(channel=enc[0], times=gx_allExt_times, amplitudes=gx_allExt_amp) ############################################################################# gzrep_times = [ 0, g_ss_reph.rise_time, g_ss_reph.rise_time + g_ss_reph.flat_time, g_ss_reph.rise_time + g_ss_reph.flat_time + g_ss_reph.fall_time, g_ss_reph.rise_time + g_ss_reph.flat_time + g_ss_reph.fall_time + calc_duration(g_ro) + 2 * calc_duration(g_ro_pre) - 2 * calc_duration(g_ss_reph) + 1e-5, g_ss_reph.rise_time + g_ss_reph.flat_time + g_ss_reph.fall_time + calc_duration(g_ro) + 2 * calc_duration(g_ro_pre) - 2 * calc_duration(g_ss_reph) + 1e-5 + g_ss_reph.rise_time, g_ss_reph.rise_time + g_ss_reph.flat_time + g_ss_reph.fall_time + calc_duration(g_ro) + 2 * calc_duration(g_ro_pre) - 2 * calc_duration(g_ss_reph) + 1e-5 + g_ss_reph.rise_time + g_ss_reph.flat_time, g_ss_reph.rise_time + g_ss_reph.flat_time + g_ss_reph.fall_time + calc_duration(g_ro) + 2 * calc_duration(g_ro_pre) - 2 * calc_duration(g_ss_reph) + 1e-5 + g_ss_reph.rise_time + g_ss_reph.flat_time + g_ss_reph.fall_time ] gzrep_amp = [ 0, g_ss_reph.amplitude, g_ss_reph.amplitude, 0, 0, g_ss_reph.amplitude, g_ss_reph.amplitude, 0 ] gzrep_all = make_extended_trapezoid(channel=enc[2], times=gzrep_times, amplitudes=gzrep_amp) adc.delay = g_ro_pre.rise_time + g_ro_pre.flat_time + g_ro_pre.fall_time + g_ro.rise_time + 0.5e-5 # finish timing calculation TR = calc_duration(g_ss) + calc_duration(gx_all) TE = TR / 2 ni_acqu_pattern = np.arange(1, Ny + 1, 1) Ny_aq = len(ni_acqu_pattern) print('Acquisition window is: %3.2f ms' % (TR * Ny_aq * 1e3)) rf05 = rf rf_waveform = rf.signal ############################################################################ # Start-up RF pulses # (ramp-up of Nramp) for nRamp in np.arange(1, Nramp + 1, 1): if np.mod(nRamp, 2): rf.phase_offset = 0 adc.phase_offset = 0 else: rf.phase_offset = -pi adc.phase_offset = -pi rf05.signal = np.divide(nRamp, Nramp) * rf_waveform gyPre_2 = make_trapezoid('y', area=phaseAreas[0], duration=pe_dur, system=system) gyPre_1 = make_trapezoid('y', area=-phaseAreas[0], duration=pe_dur, system=system) gyPre_times = [ 0, gyPre_2.rise_time, gyPre_2.rise_time + gyPre_2.flat_time, gyPre_2.rise_time + gyPre_2.flat_time + gyPre_2.fall_time, gyPre_2.rise_time + gyPre_2.flat_time + gyPre_2.fall_time + g_ro.flat_time + 1e-5, gyPre_2.rise_time + gyPre_2.flat_time + gyPre_2.fall_time + g_ro.flat_time + 1e-5 + gyPre_1.rise_time, gyPre_2.rise_time + gyPre_2.flat_time + gyPre_2.fall_time + g_ro.flat_time + 1e-5 + gyPre_1.rise_time + gyPre_1.flat_time, gyPre_2.rise_time + gyPre_2.flat_time + gyPre_2.fall_time + g_ro.flat_time + 1e-5 + gyPre_1.rise_time + gyPre_1.flat_time + gyPre_1.fall_time ] gyPre_amp = [ 0, gyPre_2.amplitude, gyPre_2.amplitude, 0, 0, gyPre_1.amplitude, gyPre_1.amplitude, 0 ] gyPre_all = make_extended_trapezoid(channel=enc[1], times=gyPre_times, amplitudes=gyPre_amp) if nRamp == 1: seq.add_block(rf05, g_ss) seq.add_block(gx_all, gyPre_all, gzrep_all) else: seq.add_block(rf05, g_ss) seq.add_block(gx_all, gyPre_all, gzrep_all) ############################################################################ ############################################################################ # Actual Readout iterate number of phase encoding steps Ny for i in np.arange(1, Ny + 1, 1): # ******************************* # Phase cycling if np.mod(i + Nramp, 2): rf.phase_offset = 0 adc.phase_offset = 0 else: rf.phase_offset = -pi adc.phase_offset = -pi # ******************************* # *************************************************************************************** # Create phase encoding gradients if np.mod(Nramp, 2): gyPre_2 = make_trapezoid('y', area=phaseAreas[i - 1], duration=pe_dur, system=system) if i > 1: gyPre_1 = make_trapezoid( 'y', area=-phaseAreas[np.mod(i + Ny - 2, Ny)], duration=pe_dur, system=system) else: gyPre_1 = make_trapezoid( 'y', area=-phaseAreas[np.mod(i + Ny - 1, Ny)], duration=pe_dur, system=system) else: gyPre_2 = make_trapezoid('y', area=phaseAreas[i], duration=pe_dur, system=system) if i > 1: gyPre_1 = make_trapezoid( 'y', area=-phaseAreas[np.mod(i + Ny - 2, Ny)], duration=pe_dur, system=system) else: gyPre_1 = make_trapezoid('y', area=phaseAreas[np.mod( i + Ny - 2, Ny)], duration=pe_dur, system=system) gyPre_times = [ 0, gyPre_2.rise_time, gyPre_2.rise_time + gyPre_2.flat_time, gyPre_2.rise_time + gyPre_2.flat_time + gyPre_2.fall_time, gyPre_2.rise_time + gyPre_2.flat_time + gyPre_2.fall_time + g_ro.flat_time + 1e-5, gyPre_2.rise_time + gyPre_2.flat_time + gyPre_2.fall_time + g_ro.flat_time + 1e-5 + gyPre_2.rise_time, gyPre_2.rise_time + gyPre_2.flat_time + gyPre_2.fall_time + g_ro.flat_time + 1e-5 + gyPre_2.rise_time + gyPre_2.flat_time, gyPre_2.rise_time + gyPre_2.flat_time + gyPre_2.fall_time + g_ro.flat_time + 1e-5 + gyPre_2.rise_time + gyPre_2.flat_time + gyPre_2.fall_time ] gyPre_amp = [ 0, gyPre_2.amplitude, gyPre_2.amplitude, 0, 0, -gyPre_2.amplitude, -gyPre_2.amplitude, 0 ] # Verify if phase encoding gradient amplitude is not zero try: gyPre_all = make_extended_trapezoid(channel=enc[1], times=gyPre_times, amplitudes=gyPre_amp) flag_zerogy = 0 except: gyPre_times = [ 0, pe_dur, pe_dur + g_ro.flat_time + 1e-5, pe_dur + g_ro.flat_time + 1e-5 + pe_dur ] gyPre_amp = [0, gyPre_2.amplitude, 0, 0] flag_zerogy = 1 # *************************************************************************************** # add RF and Slice selecitve gradient seq.add_block(rf, g_ss) # add Phase and frequency encoding gradients and ADC if flag_zerogy == 1: seq.add_block(gx_all, gzrep_all, adc) else: seq.add_block(gx_all, gyPre_all, gzrep_all, adc) return seq, TR, Ny