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_make_scaled_extended_trapezoid(self): sc = np.random.rand() + 0.5 g0 = make_extended_trapezoid(channel='x', system=Opts(), times=[0, 1e-3, 6e-3, 7e-3], amplitudes=[4e3, 5e3, 5e3, 6e3]) g1 = make_scaled_extended_trapezoid( channel='x', system=Opts(), scale=1, times=np.array([0, 1e-3, 6e-3, 7e-3]), amplitudes=np.array([4e3, 5e3, 5e3, 6e3])) g2 = make_scaled_extended_trapezoid( channel='x', system=Opts(), scale=sc, times=np.array([0, 1e-3, 6e-3, 7e-3]), amplitudes=np.array([4e3, 5e3, 5e3, 6e3])) g3 = make_scaled_extended_trapezoid( channel='x', system=Opts(), scale=0, times=np.array([0, 1e-3, 6e-3, 7e-3]), amplitudes=np.array([4e3, 5e3, 5e3, 6e3])) np.testing.assert_equal(g0.channel, g1.channel) np.testing.assert_array_equal(g0.waveform, g1.waveform) np.testing.assert_equal(g0.type, g1.type) np.testing.assert_array_equal(g0.t, g1.t) np.testing.assert_array_almost_equal(g0.waveform * sc, g2.waveform) np.testing.assert_equal(g3.type, 'trap') np.testing.assert_equal(g3.amplitude, 0) np.testing.assert_equal(g3.area, 0)
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) GR_spr = make_trapezoid(channel='x', system=system, area=GR_acq.area * fspR, duration=t_sp, rise_time=dG) GR_spex = make_trapezoid(channel='x', system=system, area=GR_acq.area * (1 + fspR), duration=t_sp_ex, rise_time=dG) AGR_spr = GR_spr.area AGR_preph = GR_acq.area / 2 + AGR_spr GR_preph = make_trapezoid(channel='x', system=system, area=AGR_preph, duration=t_sp_ex, rise_time=dG) n_ex = 1 PE_order = np.arange(-Ny_pre, Ny + 1).T phase_areas = PE_order * delta_k GS1_times = [0, GS_ex.rise_time] GS1_amp = [0, GS_ex.amplitude] GS1 = make_extended_trapezoid(channel='z', 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='z', 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='z', 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='z', times=GS4_times, amplitudes=GS4_amp) GS5_times = [0, GS_spr.rise_time, GS_spr.rise_time + GS_spr.flat_time,
def split_gradient_at(grad: np.ndarray, time_point: float, system: Opts = Opts()): """ Split gradient waveform `grad` into two at time point `time_point`. Parameters ---------- grad : numpy.ndarray Gradient waveform to be split into two gradient waveforms. time_point : float, optional Time point at which `grad` will be split into two gradient waveforms. system : Opts, optional System limits. Default is a system limits object initialised to default values. Returns ------- grad1, grad2 : numpy.ndarray Gradient waveforms after splitting. """ grad_raster_time = system.grad_raster_time time_index = round(time_point / grad_raster_time) time_point = time_index * grad_raster_time time_index += 1 if grad.type == 'trap': ch = grad.channel grad.delay = round(grad.delay / grad_raster_time) * grad_raster_time grad.rise_time = round(grad.rise_time / grad_raster_time) * grad_raster_time grad.flat_time = round(grad.flat_time / grad_raster_time) * grad_raster_time grad.fall_time = round(grad.fall_time / grad_raster_time) * grad_raster_time if grad.flat_time == 0: times = [0, grad.rise_time, grad.rise_time + grad.fall_time] amplitudes = [0, grad.amplitude, 0] else: times = [0, grad.rise_time, grad.rise_time + grad.flat_time, grad.rise_time + grad.flat_time + grad.fall_time] amplitudes = [0, grad.amplitude, grad.amplitude, 0] if time_point < grad.delay: times = np.insert(grad.delay + times, 0, 0) amplitudes = [0, amplitudes] grad.delay = 0 amplitudes = np.array(amplitudes) times = np.array(times) amp_tp = np.interp(x=time_point, xp=times, fp=amplitudes) times1 = np.append(times[np.where(times < time_point)], time_point) amplitudes1 = np.append(amplitudes[np.where(times < time_point)], amp_tp) times2 = np.insert(times[times > time_point], 0, time_point) - time_point amplitudes2 = np.insert(amplitudes[times > time_point], 0, amp_tp) grad1 = make_extended_trapezoid(channel=ch, system=system, times=times1, amplitudes=amplitudes1, skip_check=True) grad1.delay = grad.delay grad2 = make_extended_trapezoid(channel=ch, system=system, times=times2, amplitudes=amplitudes2, skip_check=True) grad2.delay = time_point return grad1, grad2 elif grad.type == 'grad': if time_index == 1 or time_index >= len(grad.t): return grad else: grad1 = grad grad2 = grad grad1.last = grad.waveform[time_index] grad2.first = grad.waveform[time_index] grad2.delay = grad.delay + grad.t[time_index] grad1.t = grad.t[:time_index] grad1.waveform = grad.waveform[:time_index] grad2.t = grad.t[time_index:] grad2.waveform = grad.waveform[time_index:] return grad1, grad2 else: raise ValueError('Splitting of unsupported event.')
system=system, area=agr_preph, duration=t_spex, rise_time=dG) n_ex = math.floor(Ny / 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: pe_steps = np.roll(pe_steps, -round(n_ex / 2)) pe_order = pe_steps.reshape((n_ex, n_echo), order='F').T phase_areas = pe_order * 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='z', 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='z', 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='z', times=gs3_times, amplitudes=gs3_amp) gs4_times = [0, gs_ref.flat_time]
def make_extended_trapezoid_area(channel: str, Gs: float, Ge: float, A: float, system: Opts) -> Tuple[SimpleNamespace, np.array, np.array]: """ Makes shortest possible extended trapezoid with a given area. Parameters ---------- channel : str Orientation of extended trapezoidal gradient event. Must be one of 'x', 'y' or 'z'. Gs : float Starting non-zero gradient value. Ge : float Ending non-zero gradient value. A : float Area of extended trapezoid. system: Opts System limits. Returns ------- grad : SimpleNamespace Extended trapezoid event. times : numpy.ndarray amplitude : numpy.ndarray Raises ------ ValueError """ SR = system.max_slew * 0.99 Tp = 0 obj1 = lambda x: (A - __testGA(x, 0, SR, system.grad_raster_time, Gs, Ge)) ** 2 res = minimize(fun=obj1, x0=0, method='Nelder-Mead') Gp, obj1val = *res.x, res.fun if obj1val > 1e-3 or abs(Gp) > system.max_grad: # Search did not converge Gp = system.max_grad * np.sign(Gp) obj2 = lambda x: (A - __testGA(Gp, x, SR, system.grad_raster_time, Gs, Ge)) ** 2 res2 = minimize(fun=obj2, x0=0, method='Nelder-Mead') T, obj2val = *res2.x, res2.fun assert obj2val < 1e-2 Tp = math.ceil(T / system.grad_raster_time) * system.grad_raster_time # Fix the ramps Tru = math.ceil(abs(Gp - Gs) / SR / system.grad_raster_time) * system.grad_raster_time Trd = math.ceil(abs(Gp - Ge) / SR / system.grad_raster_time) * system.grad_raster_time obj3 = lambda x: (A - __testGA1(x, Tru, Tp, Trd, Gs, Ge)) ** 2 res = minimize(fun=obj3, x0=Gp, method='Nelder-Mead') Gp, obj3val = *res.x, res.fun assert obj3val < 1e-3 if Tp > 0: times = np.cumsum([0, Tru, Tp, Trd]) amplitudes = [Gs, Gp, Gp, Ge] else: Tru = math.ceil(abs(Gp - Gs) / SR / system.grad_raster_time) * system.grad_raster_time Trd = math.ceil(abs(Gp - Ge) / SR / system.grad_raster_time) * system.grad_raster_time if Trd > 0: if Tru > 0: times = np.cumsum([0, Tru, Trd]) amplitudes = np.array([Gs, Gp, Ge]) else: times = np.cumsum([0, Trd]) amplitudes = np.array([Gs, Ge]) else: times = np.cumsum([0, Tru]) amplitudes = np.array([Gs, Ge]) grad = make_extended_trapezoid(channel=channel, system=system, times=times, amplitudes=amplitudes) return grad, times, amplitudes
def split_gradient(grad: SimpleNamespace, system: Opts = Opts()) -> Tuple[SimpleNamespace, SimpleNamespace, SimpleNamespace]: """ Split gradient waveform `grad` into two gradient waveforms at the center. Parameters ---------- grad : array_like Gradient waveform to be split into two gradient waveforms. system : Opts, optional, default=Opts() System limits. Returns ------- grad1, grad2 : numpy.ndarray Split gradient waveforms. Raises ------ ValueError If arbitrary gradients are passed. If non-gradient event is passed. """ grad_raster_time = system.grad_raster_time total_length = calc_duration(grad) if grad.type == 'trap': ch = grad.channel grad.delay = round(grad.delay / grad_raster_time) * grad_raster_time grad.rise_time = round(grad.rise_time / grad_raster_time) * grad_raster_time grad.flat_time = round(grad.flat_time / grad_raster_time) * grad_raster_time grad.fall_time = round(grad.fall_time / grad_raster_time) * grad_raster_time times = [0, grad.rise_time] amplitudes = [0, grad.amplitude] ramp_up = make_extended_trapezoid(channel=ch, system=system, times=times, amplitudes=amplitudes, skip_check=True) ramp_up.delay = grad.delay times = [0, grad.fall_time] amplitudes = [grad.amplitude, 0] ramp_down = make_extended_trapezoid(channel=ch, system=system, times=times, amplitudes=amplitudes, skip_check=True) ramp_down.delay = total_length - grad.fall_time ramp_down.t = ramp_down.t * grad_raster_time flat_top = SimpleNamespace() flat_top.type = 'grad' flat_top.channel = ch flat_top.delay = grad.delay + grad.rise_time flat_top.t = np.arange(step=grad_raster_time, stop=ramp_down.delay - grad_raster_time - grad.delay - grad.rise_time) flat_top.waveform = grad.amplitude * np.ones(len(flat_top.t)) flat_top.first = grad.amplitude flat_top.last = grad.amplitude return ramp_up, flat_top, ramp_down elif grad.type == 'grad': raise ValueError('Splitting of arbitrary gradients is not implemented yet.') else: raise ValueError('Splitting of unsupported event.')
def bssfp_readout(seq, system, fov=200e-3, Nstartup=11, Ny=128): """ Creates a Balanced steady-state free precession (bSSFP) sequence and adds to seq object Parameters ---------- seq: object Sequence object system : Opts System limits. fov : float, optional Field-of-view [m]. Default is 0.2 m. Nstartup : float, optional Number of start-up RF pulses. Default is 11 Ny : float, optional Number of phase encoding lines. Default is 128. Returns ------- seq : SimpleNamespace Seq object with bSSFP readout TR : float Repetition time Ny : float Final number of phase encoding lines. """ # Sequence Parameters enc = 'xyz' Nx = 128 thk = 6e-3 fa = 35 # [deg] Nramp = Nstartup # ADC duration (controls TR/TE) adc_dur = 2560 / 2 # [us] rf_dur = 490 # [us] rf_apo = 0.5 rf_bwt = 1.5 ############################################################################# # Create slice selection pulse and gradient rf, g_ss, __ = make_sinc_pulse( flip_angle=fa * pi / 180, system=system, duration=rf_dur * 1e-6, slice_thickness=thk, apodization=rf_apo, time_bw_product=rf_bwt ) # for next pyPulseq version add:, return_gz=True) g_ss.channel = enc[2] # Slice refocusing g_ss_reph = make_trapezoid(channel=enc[2], system=system, area=-g_ss.area / 2, duration=0.00017 * 2) ############################################################################# rf.delay = calc_duration(g_ss) - calc_duration(rf) + rf.delay ############################################################################# # Readout gradient and ADC delta_k = 1 / fov kWidth = Nx * delta_k # Readout and ADC g_ro = make_trapezoid(channel=enc[0], system=system, flat_area=kWidth, flat_time=(adc_dur) * 1e-6) adc = make_adc(num_samples=Nx, duration=g_ro.flat_time, delay=g_ro.rise_time) # Readout rewinder g_ro_pre = make_trapezoid(channel=enc[0], system=system, area=-g_ro.area / 2) phaseAreas_tmp = np.arange(0, Ny, 1).tolist() phaseAreas = np.dot(np.subtract(phaseAreas_tmp, Ny / 2), delta_k) gs8_times = [ 0, g_ro.fall_time, g_ro.fall_time + g_ro_pre.rise_time, g_ro.fall_time + g_ro_pre.rise_time + g_ro_pre.flat_time, g_ro.fall_time + g_ro_pre.rise_time + g_ro_pre.flat_time + g_ro_pre.fall_time ] gs8_amp = [g_ro.amplitude, 0, g_ro_pre.amplitude, g_ro_pre.amplitude, 0] gx_2 = make_extended_trapezoid(channel=enc[0], times=gs8_times, amplitudes=gs8_amp) # Calculate phase encoding gradient duration pe_dur = calc_duration(gx_2) gx_allExt_times = [ 0, g_ro_pre.rise_time, g_ro_pre.rise_time + g_ro_pre.flat_time, g_ro_pre.rise_time + g_ro_pre.flat_time + g_ro_pre.fall_time, g_ro_pre.rise_time + g_ro_pre.flat_time + g_ro_pre.fall_time + g_ro.rise_time, g_ro_pre.rise_time + g_ro_pre.flat_time + g_ro_pre.fall_time + g_ro.rise_time + g_ro.flat_time + 1e-5, g_ro_pre.rise_time + g_ro_pre.flat_time + g_ro_pre.fall_time + g_ro.rise_time + g_ro.flat_time + 1e-5 + g_ro.fall_time, g_ro_pre.rise_time + g_ro_pre.flat_time + g_ro_pre.fall_time + g_ro.rise_time + g_ro.flat_time + 1e-5 + g_ro.fall_time + g_ro_pre.rise_time, g_ro_pre.rise_time + g_ro_pre.flat_time + g_ro_pre.fall_time + g_ro.rise_time + g_ro.flat_time + 1e-5 + g_ro.fall_time + g_ro_pre.rise_time + g_ro_pre.flat_time, g_ro_pre.rise_time + g_ro_pre.flat_time + g_ro_pre.fall_time + g_ro.rise_time + g_ro.flat_time + 1e-5 + g_ro.fall_time + g_ro_pre.rise_time + g_ro_pre.flat_time + g_ro_pre.fall_time ] gx_allExt_amp = [ 0, g_ro_pre.amplitude, g_ro_pre.amplitude, 0, g_ro.amplitude, g_ro.amplitude, 0, g_ro_pre.amplitude, g_ro_pre.amplitude, 0 ] gx_all = make_extended_trapezoid(channel=enc[0], times=gx_allExt_times, amplitudes=gx_allExt_amp) ############################################################################# gzrep_times = [ 0, g_ss_reph.rise_time, g_ss_reph.rise_time + g_ss_reph.flat_time, g_ss_reph.rise_time + g_ss_reph.flat_time + g_ss_reph.fall_time, g_ss_reph.rise_time + g_ss_reph.flat_time + g_ss_reph.fall_time + calc_duration(g_ro) + 2 * calc_duration(g_ro_pre) - 2 * calc_duration(g_ss_reph) + 1e-5, g_ss_reph.rise_time + g_ss_reph.flat_time + g_ss_reph.fall_time + calc_duration(g_ro) + 2 * calc_duration(g_ro_pre) - 2 * calc_duration(g_ss_reph) + 1e-5 + g_ss_reph.rise_time, g_ss_reph.rise_time + g_ss_reph.flat_time + g_ss_reph.fall_time + calc_duration(g_ro) + 2 * calc_duration(g_ro_pre) - 2 * calc_duration(g_ss_reph) + 1e-5 + g_ss_reph.rise_time + g_ss_reph.flat_time, g_ss_reph.rise_time + g_ss_reph.flat_time + g_ss_reph.fall_time + calc_duration(g_ro) + 2 * calc_duration(g_ro_pre) - 2 * calc_duration(g_ss_reph) + 1e-5 + g_ss_reph.rise_time + g_ss_reph.flat_time + g_ss_reph.fall_time ] gzrep_amp = [ 0, g_ss_reph.amplitude, g_ss_reph.amplitude, 0, 0, g_ss_reph.amplitude, g_ss_reph.amplitude, 0 ] gzrep_all = make_extended_trapezoid(channel=enc[2], times=gzrep_times, amplitudes=gzrep_amp) adc.delay = g_ro_pre.rise_time + g_ro_pre.flat_time + g_ro_pre.fall_time + g_ro.rise_time + 0.5e-5 # finish timing calculation TR = calc_duration(g_ss) + calc_duration(gx_all) TE = TR / 2 ni_acqu_pattern = np.arange(1, Ny + 1, 1) Ny_aq = len(ni_acqu_pattern) print('Acquisition window is: %3.2f ms' % (TR * Ny_aq * 1e3)) rf05 = rf rf_waveform = rf.signal ############################################################################ # Start-up RF pulses # (ramp-up of Nramp) for nRamp in np.arange(1, Nramp + 1, 1): if np.mod(nRamp, 2): rf.phase_offset = 0 adc.phase_offset = 0 else: rf.phase_offset = -pi adc.phase_offset = -pi rf05.signal = np.divide(nRamp, Nramp) * rf_waveform gyPre_2 = make_trapezoid('y', area=phaseAreas[0], duration=pe_dur, system=system) gyPre_1 = make_trapezoid('y', area=-phaseAreas[0], duration=pe_dur, system=system) gyPre_times = [ 0, gyPre_2.rise_time, gyPre_2.rise_time + gyPre_2.flat_time, gyPre_2.rise_time + gyPre_2.flat_time + gyPre_2.fall_time, gyPre_2.rise_time + gyPre_2.flat_time + gyPre_2.fall_time + g_ro.flat_time + 1e-5, gyPre_2.rise_time + gyPre_2.flat_time + gyPre_2.fall_time + g_ro.flat_time + 1e-5 + gyPre_1.rise_time, gyPre_2.rise_time + gyPre_2.flat_time + gyPre_2.fall_time + g_ro.flat_time + 1e-5 + gyPre_1.rise_time + gyPre_1.flat_time, gyPre_2.rise_time + gyPre_2.flat_time + gyPre_2.fall_time + g_ro.flat_time + 1e-5 + gyPre_1.rise_time + gyPre_1.flat_time + gyPre_1.fall_time ] gyPre_amp = [ 0, gyPre_2.amplitude, gyPre_2.amplitude, 0, 0, gyPre_1.amplitude, gyPre_1.amplitude, 0 ] gyPre_all = make_extended_trapezoid(channel=enc[1], times=gyPre_times, amplitudes=gyPre_amp) if nRamp == 1: seq.add_block(rf05, g_ss) seq.add_block(gx_all, gyPre_all, gzrep_all) else: seq.add_block(rf05, g_ss) seq.add_block(gx_all, gyPre_all, gzrep_all) ############################################################################ ############################################################################ # Actual Readout iterate number of phase encoding steps Ny for i in np.arange(1, Ny + 1, 1): # ******************************* # Phase cycling if np.mod(i + Nramp, 2): rf.phase_offset = 0 adc.phase_offset = 0 else: rf.phase_offset = -pi adc.phase_offset = -pi # ******************************* # *************************************************************************************** # Create phase encoding gradients if np.mod(Nramp, 2): gyPre_2 = make_trapezoid('y', area=phaseAreas[i - 1], duration=pe_dur, system=system) if i > 1: gyPre_1 = make_trapezoid( 'y', area=-phaseAreas[np.mod(i + Ny - 2, Ny)], duration=pe_dur, system=system) else: gyPre_1 = make_trapezoid( 'y', area=-phaseAreas[np.mod(i + Ny - 1, Ny)], duration=pe_dur, system=system) else: gyPre_2 = make_trapezoid('y', area=phaseAreas[i], duration=pe_dur, system=system) if i > 1: gyPre_1 = make_trapezoid( 'y', area=-phaseAreas[np.mod(i + Ny - 2, Ny)], duration=pe_dur, system=system) else: gyPre_1 = make_trapezoid('y', area=phaseAreas[np.mod( i + Ny - 2, Ny)], duration=pe_dur, system=system) gyPre_times = [ 0, gyPre_2.rise_time, gyPre_2.rise_time + gyPre_2.flat_time, gyPre_2.rise_time + gyPre_2.flat_time + gyPre_2.fall_time, gyPre_2.rise_time + gyPre_2.flat_time + gyPre_2.fall_time + g_ro.flat_time + 1e-5, gyPre_2.rise_time + gyPre_2.flat_time + gyPre_2.fall_time + g_ro.flat_time + 1e-5 + gyPre_2.rise_time, gyPre_2.rise_time + gyPre_2.flat_time + gyPre_2.fall_time + g_ro.flat_time + 1e-5 + gyPre_2.rise_time + gyPre_2.flat_time, gyPre_2.rise_time + gyPre_2.flat_time + gyPre_2.fall_time + g_ro.flat_time + 1e-5 + gyPre_2.rise_time + gyPre_2.flat_time + gyPre_2.fall_time ] gyPre_amp = [ 0, gyPre_2.amplitude, gyPre_2.amplitude, 0, 0, -gyPre_2.amplitude, -gyPre_2.amplitude, 0 ] # Verify if phase encoding gradient amplitude is not zero try: gyPre_all = make_extended_trapezoid(channel=enc[1], times=gyPre_times, amplitudes=gyPre_amp) flag_zerogy = 0 except: gyPre_times = [ 0, pe_dur, pe_dur + g_ro.flat_time + 1e-5, pe_dur + g_ro.flat_time + 1e-5 + pe_dur ] gyPre_amp = [0, gyPre_2.amplitude, 0, 0] flag_zerogy = 1 # *************************************************************************************** # add RF and Slice selecitve gradient seq.add_block(rf, g_ss) # add Phase and frequency encoding gradients and ADC if flag_zerogy == 1: seq.add_block(gx_all, gzrep_all, adc) else: seq.add_block(gx_all, gyPre_all, gzrep_all, adc) return seq, TR, Ny
def write_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 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 split_gradient_at( grad: SimpleNamespace, time_point: float, system: Opts = Opts() ) -> Union[SimpleNamespace, Tuple[SimpleNamespace, SimpleNamespace]]: """ Split gradient waveform `grad` into two at time point `time_point`. Parameters ---------- grad : SimpleNamespace Gradient event to be split into two gradient events. time_point : float Time point at which `grad` will be split into two gradient waveforms. system : Opts, optional, default=Opts() System limits. Returns ------- grad1, grad2 : SimpleNamespace Gradient waveforms after splitting. Raises ------ ValueError If non-gradient event is passed. """ grad_raster_time = system.grad_raster_time time_index = round(time_point / grad_raster_time) time_point = round(time_index * grad_raster_time, 6) # Work around floating-point arithmetic limitation time_index += 1 if grad.type == 'trap': ch = grad.channel grad.delay = round(grad.delay / grad_raster_time) * grad_raster_time grad.rise_time = round( grad.rise_time / grad_raster_time) * grad_raster_time grad.flat_time = round( grad.flat_time / grad_raster_time) * grad_raster_time grad.fall_time = round( grad.fall_time / grad_raster_time) * grad_raster_time if grad.flat_time == 0: times = [0, grad.rise_time, grad.rise_time + grad.fall_time] amplitudes = [0, grad.amplitude, 0] else: times = [ 0, grad.rise_time, grad.rise_time + grad.flat_time, grad.rise_time + grad.flat_time + grad.fall_time ] amplitudes = [0, grad.amplitude, grad.amplitude, 0] if time_point < grad.delay: times = np.insert(grad.delay + times, 0, 0) amplitudes = [0, amplitudes] grad.delay = 0 amplitudes = np.array(amplitudes) times = np.array(times).round( 6) # Work around floating-point arithmetic limitation amp_tp = np.interp(x=time_point, xp=times, fp=amplitudes) times1 = np.append(times[np.where(times < time_point)], time_point) amplitudes1 = np.append(amplitudes[np.where(times < time_point)], amp_tp) times2 = np.insert(times[times > time_point], 0, time_point) - time_point amplitudes2 = np.insert(amplitudes[times > time_point], 0, amp_tp) grad1 = make_extended_trapezoid(channel=ch, system=system, times=times1, amplitudes=amplitudes1, skip_check=True) grad1.delay = grad.delay grad2 = make_extended_trapezoid(channel=ch, system=system, times=times2, amplitudes=amplitudes2, skip_check=True) grad2.delay = time_point return grad1, grad2 elif grad.type == 'grad': if time_index == 1 or time_index >= len(grad.t): return grad else: grad1 = grad grad2 = grad grad1.last = 0.5 * (grad.waveform[time_index - 1] + grad.waveform[time_index]) grad2.first = grad1.last grad2.delay = grad.delay + grad.t[time_index] grad1.t = grad.t[:time_index] grad1.waveform = grad.waveform[:time_index] grad2.t = grad.t[time_index:] - time_point grad2.waveform = grad.waveform[time_index:] return grad1, grad2 else: raise ValueError('Splitting of unsupported event.')