rise_time=rise_time) for c in ['x', 'y', 'z']
]

# RF pulses
flip_angle_sat = seq_defs['b1cwpe'] * gamma_hz * 2 * np.pi * seq_defs['tp']
sat_pulse, _ = make_block_pulse(flip_angle=flip_angle_sat,
                                duration=seq_defs['tp'],
                                system=sys)

# ADC events
pseudo_adc = make_adc(
    num_samples=1,
    duration=1e-3)  # (not played out; just used to split measurements)

# DELAYS
post_spoil_delay = make_delay(50e-6)
td_delay = make_delay(seq_defs['td'])
trec_delay = make_delay(seq_defs['trec'])
m0_delay = make_delay(seq_defs['trec_m0'])

# Sequence object
seq = Sequence()

# ===
# RUN
# ===

offsets_hz = seq_defs['offsets_ppm'] * gamma_hz * seq_defs[
    'b0']  # convert from ppm to Hz

for offset in offsets_hz:
def make_pulseq_epi_oblique(fov, n, thk, fa, tr, te, enc='xyz', slice_locs=None, echo_type="se", n_shots=1,
                            seg_type='blocked', write=False):
    """Makes an Echo Planar Imaging (EPI) sequence in any plane

        2D oblique multi-slice EPI pulse sequence with Cartesian encoding
        Oblique means that each of slice-selection, phase encoding, and frequency encoding
        can point in any specified direction

        Parameters
        ----------
        fov : array_like
            Isotropic field-of-view, or length-2 list [fov_readout, fov_phase], in meters
        n : array_like
            Isotropic matrix size, or length-2 list [n_readout, n_phase]
        thk : float
            Slice thickness in meters
        fa : float
            Flip angle in degrees
        tr : float
            Repetition time in seconds
        te : float
            Echo time in seconds
        enc : str or array_like, optional
            Spatial encoding directions
            1st - readout; 2nd - phase encoding; 3rd - slice select
            - Use str with any permutation of x, y, and z to obtain orthogonal slices
            e.g. The default 'xyz' means axial(z) slice with readout in x and phase encoding in y
            - Use list to indicate vectors in the encoding directions for oblique slices
            They should be perpendicular to each other, but not necessarily unit vectors
            e.g. [(2,1,0),(-1,2,0),(0,0,1)] rotates the two in-plane encoding directions for an axial slice
        slice_locs : array_like, optional
            Slice locations from isocenter in meters
            Default is None which means a single slice at the center
        echo_type : str, optional {'se','gre'}
            Type of echo generated
            se (default) - spin echo (an 180 deg pulse is used)
            gre - gradient echo
        n_shots : int, optional
            Number of shots used to encode each slicel; default is 1
        seg_type : str, optional {'blocked','interleaved'}
            Method to divide up k-space in the case of n_shots > 1; default is 'blocked'
            'blocked' - each shot covers a rectangle, with no overlap between shots
            'interleaved' - each shot samples the full k-space but with wider phase steps

        write : bool, optional
            Whether to write seq into file; default is False

        Returns
        -------
        seq : Sequence
            Pulse sequence as a Pulseq object
        ro_dirs : numpy.ndarray
            List of 0s and 1s indicating direction of readout
            0 - left to right
            1 - right to left (needs to be reversed at recon)
        ro_order : numpy.ndarray
            Order in which to re-arrange the readout lines
            It is [] for blocked acquisition (retain original order)

        """
    # Multi-slice, multi-shot (>=1)
    # TE is set to be where the trajectory crosses the center of k-space

    # System options
    kwargs_for_opts = {'max_grad': 32, 'grad_unit': 'mT/m',
                       'max_slew': 130, 'slew_unit': 'T/m/s', 'rf_ring_down_time': 30e-6,
                       'rf_dead_time': 100e-6, 'adc_dead_time': 20e-6}

    system = Opts(kwargs_for_opts)
    seq = Sequence(system)
    ug_fe, ug_pe, ug_ss = parse_enc(enc)

    # Sequence parameters
    Nf, Np = (n, n) if isinstance(n, int) else (n[0], n[1])
    delta_k_ro, delta_k_pe = (1 / fov, 1 / fov) if isinstance(fov, float) else (1 / fov[0], 1 / fov[1])
    kWidth_ro = Nf * delta_k_ro
    TE, TR = te, tr
    flip = fa * pi / 180

    # RF Pulse (first)
    kwargs_for_sinc = {"flip_angle": flip, "system": system, "duration": 2.5e-3, "slice_thickness": thk,
                       "apodization": 0.5, "time_bw_product": 4}
    rf, g_ss = make_sinc_pulse(kwargs_for_sinc, 2)
    g_ss_x, g_ss_y, g_ss_z = make_oblique_gradients(g_ss, ug_ss)

    # Readout gradients
    #    readoutTime = Nf * 4e-6
    dwell = 1e-5
    readoutTime = Nf * dwell
    kwargs_for_g_ro = {"channel": 'x', "system": system, "flat_area": kWidth_ro, "flat_time": readoutTime}
    g_ro_pos = make_trapezoid(kwargs_for_g_ro)
    g_ro_pos_x, g_ro_pos_y, g_ro_pos_z = make_oblique_gradients(g_ro_pos, ug_fe)
    g_ro_neg = copy.deepcopy(g_ro_pos)
    modify_gradient(g_ro_neg, scale=-1)
    g_ro_neg_x, g_ro_neg_y, g_ro_neg_z = make_oblique_gradients(g_ro_neg, ug_fe)

    kwargs_for_adc = {"num_samples": Nf, "system": system, "duration": g_ro_pos.flat_time,
                      "delay": g_ro_pos.rise_time + dwell / 2}
    adc = makeadc(kwargs_for_adc)

    pre_time = 8e-4
    # 180 deg pulse for SE
    if echo_type == "se":
        # RF Pulse (180 deg for SE)
        flip180 = 180 * pi / 180
        kwargs_for_sinc = {"flip_angle": flip180, "system": system, "duration": 2.5e-3, "slice_thickness": thk,
                           "apodization": 0.5, "time_bw_product": 4}
        rf180, g_ss180 = make_sinc_pulse(kwargs_for_sinc, 2)
        # Slice-select direction spoilers
        kwargs_for_g_ss_spoil = {"channel": 'z', "system": system, "area": g_ss.area * 2, "duration": 3 * pre_time}
        g_ss_spoil = make_trapezoid(kwargs_for_g_ss_spoil)
        ##
        modify_gradient(g_ss_spoil, 0)
        ##
        g_ss_spoil_x, g_ss_spoil_y, g_ss_spoil_z = make_oblique_gradients(g_ss_spoil, ug_ss)

    # Readout rewinder
    ro_pre_area = g_ro_neg.area / 2 if echo_type == 'gre' else g_ro_pos.area / 2
    kwargs_for_g_ro_pre = {"channel": 'x', "system": system, "area": ro_pre_area, "duration": pre_time}
    g_ro_pre = make_trapezoid(kwargs_for_g_ro_pre)
    g_ro_pre_x, g_ro_pre_y, g_ro_pre_z = make_oblique_gradients(g_ro_pre, ug_fe)

    # Slice-selective rephasing
    kwargs_for_g_ss_reph = {"channel": 'z', "system": system, "area": -g_ss.area / 2, "duration": pre_time}
    g_ss_reph = make_trapezoid(kwargs_for_g_ss_reph)
    g_ss_reph_x, g_ss_reph_y, g_ss_reph_z = make_oblique_gradients(g_ss_reph, ug_ss)

    # Phase encode rewinder
    if echo_type == 'gre':
        pe_max_area = (Np / 2) * delta_k_pe
    elif echo_type == 'se':
        pe_max_area = -(Np / 2) * delta_k_pe
    kwargs_for_g_pe_max = {"channel": 'y', "system": system, "area": pe_max_area, "duration": pre_time}
    g_pe_max = make_trapezoid(kwargs_for_g_pe_max)

    # Phase encoding blips
    dur = ceil(2 * sqrt(delta_k_pe / system.max_slew) / 10e-6) * 10e-6
    kwargs_for_g_blip = {"channel": 'y', "system": system, "area": delta_k_pe, "duration": dur}
    g_blip = make_trapezoid(kwargs_for_g_blip)

    # Delays
    duration_to_center = (Np / 2) * calc_duration(g_ro_pos) + (Np - 1) / 2 * calc_duration(g_blip)  # why?

    if echo_type == 'se':
        delayTE1 = TE / 2 - calc_duration(g_ss) / 2 - pre_time - calc_duration(g_ss_spoil) - calc_duration(rf180) / 2
        delayTE2 = TE / 2 - calc_duration(rf180) / 2 - calc_duration(g_ss_spoil) - duration_to_center
        delay1 = make_delay(delayTE1)
        delay2 = make_delay(delayTE2)
    elif echo_type == 'gre':
        delayTE = TE - calc_duration(g_ss) / 2 - pre_time - duration_to_center
        delay12 = make_delay(delayTE)

    delayTR = TR - TE - calc_duration(rf) / 2 - duration_to_center
    delay3 = make_delay(delayTR)  # This might be different for each rep though. Fix later

    #####################################################################################################
    # Multi-shot calculations
    ro_dirs = []
    ro_order = []

    # Find number of lines in each block

    if seg_type == 'blocked':

        # Number of lines in each full readout block
        nl = ceil(Np / n_shots)

        # Number of k-space lines per readout
        if Np % nl == 0:
            nlines_list = nl * np.ones(n_shots)
        else:
            nlines_list = nl * np.ones(n_shots - 1)
            nlines_list = np.append(nlines_list, Np % nl)

        pe_scales = 2 * np.append([0], np.cumsum(nlines_list)[:-1]) / Np - 1
        g_blip_x, g_blip_y, g_blip_z = make_oblique_gradients(g_blip, ug_pe)
        for nlines in nlines_list:
            ro_dirs = np.append(ro_dirs, ((-1) ** (np.arange(0, nlines) + 1) + 1) / 2)


    elif seg_type == 'interleaved':
        # Minimum number of lines per readout
        nb = floor(Np / n_shots)

        # Number of k-space lines per readout
        nlines_list = np.ones(n_shots) * nb
        nlines_list[:Np % n_shots] += 1

        # Phase encoding scales (starts from -1; i.e. bottom left combined with pre-readout)
        pe_scales = 2 * np.arange(0, (Np - n_shots) / Np, 1 / Np)[0:n_shots] - 1
        print(pe_scales)
        # Larger blips
        modify_gradient(g_blip, scale=n_shots)
        g_blip_x, g_blip_y, g_blip_z = make_oblique_gradients(g_blip, ug_pe)

        #        ro_order = np.reshape(np.reshape(np.arange(0,Np),(),order='F'),(0,Np))

        ro_order = np.zeros((nb + 1, n_shots))
        ro_inds = np.arange(Np)
        # Readout order for recon
        for k in range(n_shots):
            cs = int(nlines_list[k])
            ro_order[:cs, k] = ro_inds[:cs]
            ro_inds = np.delete(ro_inds, range(cs))
        ro_order = ro_order.flatten()[:Np].astype(int)

        print(ro_order)
        # Readout directions in original (interleaved) order
        for nlines in nlines_list:
            ro_dirs = np.append(ro_dirs, ((-1) ** (np.arange(0, nlines) + 1) + 1) / 2)

    #####################################################################################################

    # Add blocks

    for u in range(len(slice_locs)):  # For each slice
        # Offset rf
        rf.freq_offset = g_ss.amplitude * slice_locs[u]
        for v in range(n_shots):
            # Find init. phase encode
            g_pe = copy.deepcopy(g_pe_max)
            modify_gradient(g_pe, pe_scales[v])
            g_pe_x, g_pe_y, g_pe_z = make_oblique_gradients(g_pe, ug_pe)
            # First RF
            seq.add_block(rf, g_ss_x, g_ss_y, g_ss_z)
            # Pre-winder gradients
            pre_grads_list = [g_ro_pre_x, g_ro_pre_y, g_ro_pre_z,
                              g_pe_x, g_pe_y, g_pe_z,
                              g_ss_reph_x, g_ss_reph_y, g_ss_reph_z]
            gtx, gty, gtz = combine_trap_grad_xyz(pre_grads_list, system, pre_time)
            seq.add_block(gtx, gty, gtz)

            # 180 deg pulse and spoilers, only for Spin Echo
            if echo_type == 'se':
                # First delay
                seq.add_block(delay1)
                # Second RF : 180 deg with spoilers on both sides
                seq.add_block(g_ss_spoil_x, g_ss_spoil_y, g_ss_spoil_z)  # why?
                seq.add_block(rf180)
                seq.add_block(g_ss_spoil_x, g_ss_spoil_y, g_ss_spoil_z)
                # Delay between rf180 and beginning of readout
                seq.add_block(delay2)
            # For gradient echo it's just a delay
            elif echo_type == 'gre':
                seq.add_block(delay12)

            # EPI readout with blips
            for i in range(int(nlines_list[v])):
                if i % 2 == 0:
                    seq.add_block(g_ro_pos_x, g_ro_pos_y, g_ro_pos_z, adc)  # ro line in the positive direction
                else:
                    seq.add_block(g_ro_neg_x, g_ro_neg_y, g_ro_neg_z, adc)  # ro line backwards
                seq.add_block(g_blip_x, g_blip_y, g_blip_z)  # blip

            seq.add_block(delay3)

    # Display 1 TR
    # seq.plot(time_range=(0, TR))

    if write:
        seq.write("epi_{}_FOVf{:.0f}mm_FOVp{:.0f}mm_Nf{:d}_Np{:d}_TE{:.0f}ms_TR{:.0f}ms_{:d}shots.seq" \
                  .format(echo_type, fov[0] * 1000, fov[1] * 1000, Nf, Np, TE * 1000, TR * 1000, n_shots))

    print('EPI sequence (oblique) constructed')
    return seq, ro_dirs, ro_order
Example #3
0
kwargs_for_sinc = {
    "flip_angle": flip,
    "system": system,
    "duration": 2e-3,
    "slice_thickness": slice_thickness,
    "apodization": 0.5,
    "time_bw_product": 4
}
rf180, gz180 = make_sinc_pulse(kwargs_for_sinc, 2)

TE, TR = 100e-3, 4408e-3
delayTE1 = TE / 2 - calc_duration(gz_reph) - calc_duration(
    rf) - calc_duration(rf180) / 2
delayTE2 = TE / 2 - calc_duration(gx) / 2 - calc_duration(rf180) / 2
delayTE3 = TR - TE - calc_duration(gx)
delay1 = make_delay(delayTE1)
delay2 = make_delay(delayTE2)
delay3 = make_delay(delayTE3)

for i in range(2):
    seq.add_block(rf, gz)
    kwargs_for_gy_pre = {
        "channel": 'y',
        "system": system,
        "area": -(Ny / 2 - i) * delta_k,
        "duration": readoutTime / 2
    }
    gy_pre = make_trapezoid(kwargs_for_gy_pre)
    seq.add_block(gx_pre, gy_pre, gz_reph)
    seq.add_block(delay1)
    seq.add_block(rf180, gz180)
Example #4
0
GR7_amp = [GR_acq.amplitude, GR_spr.amplitude, GR_spr.amplitude, 0]
GR7 = make_extended_trapezoid(channel='x', times=GR7_times, amplitudes=GR7_amp)

tex = GS1.t[-1] + GS2.t[-1] + GS3.t[-1]
tref = GS4.t[-1] + GS5.t[-1] + GS7.t[-1] + readout_time
tend = GS4.t[-1] + GS5.t[-1]
TE_train = tex + n_echo * tref + tend
TR_fill = (TR - n_slices * TE_train) / n_slices

TR_fill = system.grad_raster_time * round(TR_fill / system.grad_raster_time)
if TR_fill < 0:
    TR_fill = 1e-3
    warnings.warn(f'TR too short, adapted to include all slices to: {1000 * n_slices * (TE_train + TR_fill)} ms')
else:
    print(f'TR fill: {1000 * TR_fill} ms')
delay_TR = make_delay(TR_fill)
delay_end = make_delay(5)

for k_ex in range(n_ex):
    for s in range(n_slices):
        rfex.freq_offset = GS_ex.amplitude * slice_thickness * (s - (n_slices - 1) / 2)
        rfref.freq_offset = GS_ref.amplitude * slice_thickness * (s - (n_slices - 1) / 2)
        rfex.phase_offset = rfex_phase - 2 * math.pi * rfex.freq_offset * calc_rf_center(rfex)[0]
        rfref.phase_offset = rfref_phase - 2 * math.pi * rfref.freq_offset * calc_rf_center(rfref)[0]

        seq.add_block(GS1)
        seq.add_block(GS2, rfex)
        seq.add_block(GS3, GR3)

        for k_ech in range(n_echo):
            if k_ex >= 0:
Example #5
0
    for nInv in np.arange(N_inversion[0]):
        print('----------- Inversion %3.0f -----------' % (nInv + 1))
        if nInv > 0:
            for nRec in np.arange(Nrecover_btwInv[nInv - 1]):
                seq.add_block(trig_BetweenInversion)

        # ***************************************************************************************************
        #                          Verify minimum possible TI
        #                   (nearest to TI_Vector[0], i.e. 100 ms)
        try:
            assert (all(TI_Vector[nInv] >= TI_min_attainable)
                    )  # verify if TI_vector value is possbile
            delayINV = TI_Vector[
                nInv] - TI_min_attainable  # calculate delay for TI
            TI_Vector_real[nInv] = TI_Vector[nInv]
            seq.addBlock(make_delay(delayINV))

        except:
            delayINV = 0
            if nInv == 0:
                TI_Vector_real[nInv] = TI_min_attainable
            else:
                TI_Vector_real[nInv] = TI_min_attainable + np.diff(
                    TI_Vector)  # add increment i.e. 80 ms

            print('TI changed from ', TI_Vector[nInv] * 1e3,
                  ' ms to %3.0f ms' % (TI_Vector_real[nInv] * 1e3))
        # ****************************************************************************************************

        trig_inv = make_trigger(
            delay=100e-6,
Example #6
0
delay_TE = math.ceil((TE - calc_duration(gx_pre) - gz.fall_time - gz.flat_time / 2 - calc_duration(
    gx) / 2) / seq.grad_raster_time) * seq.grad_raster_time
delay_TR = math.ceil((TR - calc_duration(gx_pre) - calc_duration(gz) - calc_duration(
    gx) - delay_TE) / seq.grad_raster_time) * seq.grad_raster_time

if not np.all(delay_TR >= calc_duration(gx_spoil, gz_spoil)):
    raise Exception()

rf_phase = 0
rf_inc = 0

for i in range(Ny):
    rf.phase_offset = rf_phase / 180 * np.pi
    adc.phase_offset = rf_phase / 180 * np.pi
    rf_inc = divmod(rf_inc + rf_spoiling_inc, 360.0)[1]
    rf_phase = divmod(rf_phase + rf_inc, 360.0)[1]

    seq.add_block(rf, gz)
    gy_pre = make_trapezoid(channel='y', area=phase_areas[i], duration=2e-3, system=sys)
    seq.add_block(gx_pre, gy_pre, gz_reph)
    seq.add_block(make_delay(delay_TE))
    seq.add_block(gx, adc)
    gy_pre.amplitude = -gy_pre.amplitude
    seq.add_block(make_delay(delay_TR), gx_spoil, gy_pre, gz_spoil)

report = seq.test_report()
print(report)
# seq.calculate_kspace()
# seq.write('/Users/sravan953/Documents/CU/Projects/PyPulseq/gre_pulseq.seq')
Example #7
0
def write_UTE_3D_rf_spoiled(N=64,
                            FOV=250e-3,
                            slab_thk=250e-3,
                            FA=10,
                            TR=10e-3,
                            ro_asymmetry=0.97,
                            os_factor=1,
                            rf_type='sinc',
                            rf_dur=1e-3,
                            use_half_pulse=True,
                            save_seq=True):
    """
    Parameters
    ----------
    N : int, default=64
        Matrix size
    FOV : float, default=0.25
        Field-of-view in [meters]
    slab_thk : float, default=0.25
        Slab thickness in [meters]
    FA : float, default=10
        Flip angle in [degrees]
    TR : float, default=0.01
        Repetition time in [seconds]
    ro_asymmetry : float, default=0.97
        The ratio A/B where a A/(A+B) portion of 2*Kmax is omitted and B/(A+B) is acquired.
    os_factor : float, default=1
        Oversampling factor in readout
        The number of readout samples is the nearest integer from os_factor*N
    rf_type : str, default='sinc'
        RF pulse shape - 'sinc', 'gauss', or 'rect'
    rf_dur : float, default=0.001
        RF pulse duration
    use_half_pulse : bool, default=True
        Whether to use half pulse excitation for shorter TE;
        This doubles both the number of excitations and acquisition time
    save_seq : bool, default=True
        Whether to save this sequence as a .seq file

    Returns
    -------
    seq : Sequence
        PyPulseq 2D UTE sequence object
    TE : float
        Echo time of generated sequence in [seconds]
    ktraj : np.ndarray
        3D k-space trajectory [spoke, readout sample, 3] where
        the last dimension refers to spatial frequency coordinates (kx, ky, kz)
    """

    # Adapted from pypulseq demo write_ute.py (obtained mid-2021)
    system = Opts(max_grad=32,
                  grad_unit='mT/m',
                  max_slew=130,
                  slew_unit='T/m/s',
                  rf_ringdown_time=30e-6,
                  rf_dead_time=100e-6,
                  adc_dead_time=20e-6)
    seq = Sequence(system=system)

    # Derived parameters
    dx = FOV / N
    Ns, Ntheta, Nphi = get_radk_params_3D(dx, FOV)  # Make this function
    print(f'Using {Ns} spokes, with {Ntheta} thetas and {Nphi} phis')

    # Spoke angles
    thetas = np.linspace(0, np.pi, Ntheta, endpoint=False)
    phis = np.linspace(0, 2 * np.pi, Nphi, endpoint=False)

    ro_duration = 2.5e-3
    ro_os = os_factor  # Oversampling factor
    rf_spoiling_inc = 117  # RF spoiling increment value.

    if use_half_pulse:
        cp = 1
    else:
        cp = 0.5

    tbw = 2

    # Sequence components
    if rf_type == 'sinc':
        rf, gz, gz_reph = make_sinc_pulse(flip_angle=FA * np.pi / 180,
                                          duration=rf_dur,
                                          slice_thickness=slab_thk,
                                          apodization=0.5,
                                          time_bw_product=tbw,
                                          center_pos=cp,
                                          system=system,
                                          return_gz=True)
        gz_ramp_reph = make_trapezoid(channel='z',
                                      area=-gz.fall_time * gz.amplitude / 2,
                                      system=system)

    elif rf_type == 'rect':
        rf = make_block_pulse(flip_angle=FA * np.pi / 180,
                              duration=rf_dur,
                              slice_thickness=slab_thk,
                              return_gz=False)
    elif rf_type == 'gauss':
        rf, gz, gz_reph = make_gauss_pulse(flip_angle=FA * np.pi / 180,
                                           duration=rf_dur,
                                           slice_thickness=slab_thk,
                                           system=system,
                                           return_gz=True)
        gz_ramp_reph = make_trapezoid(channel='z',
                                      area=-gz.fall_time * gz.amplitude / 2,
                                      system=system)

    # Asymmetry! (0 - fully rewound; 1 - hall-echo)
    Nro = np.round(ro_os * N)  # Number of readout points
    s = np.round(ro_asymmetry * Nro / 2) / (Nro / 2)
    dk = (1 / FOV) / (1 + s)
    ro_area = N * dk
    gro = make_trapezoid(channel='x',
                         flat_area=ro_area,
                         flat_time=ro_duration,
                         system=system)
    adc = make_adc(num_samples=Nro,
                   duration=gro.flat_time,
                   delay=gro.rise_time,
                   system=system)
    gro_pre = make_trapezoid(channel='x',
                             area=-(gro.area - ro_area) / 2 - (ro_area / 2) *
                             (1 - s),
                             system=system)

    # Spoilers
    gro_spoil = make_trapezoid(channel='x', area=0.2 * N * dk, system=system)

    # Calculate timing
    TE = gro.rise_time + adc.dwell * Nro / 2 * (1 - s)
    if rf_type == 'sinc' or rf_type == 'gauss':
        if use_half_pulse:
            TE += gz.fall_time + calc_duration(gro_pre)
        else:
            TE += calc_duration(gz) / 2 + calc_duration(gro_pre)

        delay_TR = np.ceil(
            (TR - calc_duration(gro_pre) - calc_duration(gz) -
             calc_duration(gro)) / seq.grad_raster_time) * seq.grad_raster_time
    elif rf_type == 'rect':
        TE += calc_duration(gro_pre)
        delay_TR = np.ceil((TR - calc_duration(gro_pre) - calc_duration(gro)) /
                           seq.grad_raster_time) * seq.grad_raster_time
    assert np.all(delay_TR >= calc_duration(gro_spoil)
                  )  # The TR delay starts at the same time as the spoilers!
    print(f'TE = {TE * 1e6:.0f} us')

    C = int(use_half_pulse) + 1

    # Starting RF phase and increments
    rf_phase = 0
    rf_inc = 0
    Nline = Ntheta * Nphi
    ktraj = np.zeros([Nline, int(adc.num_samples), 3])
    u = 0

    for th in range(Ntheta):
        for ph in range(Nphi):
            unit_grad = np.zeros(3)
            unit_grad[0] = np.sin(thetas[th]) * np.cos(phis[ph])
            unit_grad[1] = np.sin(thetas[th]) * np.sin(phis[ph])
            unit_grad[2] = np.cos(thetas[th])
            # Two repeats if using half pulse
            for c in range(C):
                # RF spoiling
                rf.phase_offset = (rf_phase / 180) * np.pi
                adc.phase_offset = (rf_phase / 180) * np.pi
                rf_inc = np.mod(rf_inc + rf_spoiling_inc, 360.0)
                rf_phase = np.mod(rf_phase + rf_inc, 360.0)

                # Rewinder and readout gradients, vectorized
                gpx, gpy, gpz = make_oblique_gradients(gro_pre, unit_grad)
                grx, gry, grz = make_oblique_gradients(gro, unit_grad)
                gsx, gsy, gsz = make_oblique_gradients(gro_spoil, unit_grad)

                if rf_type == 'sinc' or rf_type == 'gauss':
                    if use_half_pulse:
                        # Reverse slice select amplitude (always z = 0)
                        modify_gradient(gz, scale=-1)
                        modify_gradient(gz_ramp_reph, scale=-1)
                        gpz_reph = copy.deepcopy(gpz)
                        modify_gradient(gpz_reph,
                                        scale=(gpz.area + gz_ramp_reph.area) /
                                        gpz.area)
                    else:
                        gpz_reph = copy.deepcopy(gpz)
                        modify_gradient(gpz_reph,
                                        scale=(gpz.area + gz_reph.area) /
                                        gpz.area)

                    seq.add_block(rf, gz)
                    seq.add_block(gpx, gpy, gpz_reph)

                elif rf_type == 'rect':
                    seq.add_block(rf)
                    seq.add_block(gpx, gpy, gpz)

                seq.add_block(grx, gry, grz, adc)
                seq.add_block(gsx, gsy, gsz, make_delay(delay_TR))

            #print(f'Spokes: {u+1}/{Nline}')
            ktraj[u, :, :] = get_ktraj_3d(grx, gry, grz, adc, [gpx], [gpy],
                                          [gpz])
            u += 1

    ok, error_report = seq.check_timing(
    )  # Check whether the timing of the sequence is correct
    if ok:
        print('Timing check passed successfully')
    else:
        print('Timing check failed. Error listing follows:')
        [print(e) for e in error_report]

    if save_seq:
        seq.write(
            f'ute_3d_rf-{rf_type}_rw_s{s}_N{N}_FOV{FOV}_TR{TR}_TE{TE}_C={use_half_pulse+1}.seq'
        )
        savemat(
            f'ktraj_ute_3d_rw_s{s}_N{N}_FOV{FOV}_TR{TR}_TE{TE}_C={use_half_pulse+1}.mat',
            {'ktraj': ktraj})

    return seq, TE, ktraj
    n_gap_te2 = n_delay_te2 - n_delay_te1
    gap_te2 = n_gap_te2 / i_raster_time
    gap_te1 = 0
""" EPI calibration """
for s in range(n_slices):
    # Fat saturation
    if fatsat_enable:
        seq.add_block(rf_fs, gz_fs)

    # RF90
    rf.freq_offset = gz.amplitude * slice_thickness * (s - (n_slices - 1) / 2)
    seq.add_block(rf, gz)
    seq.add_block(gz_reph)

    # Delay for RF180
    seq.add_block(make_delay(delay_te1))

    # RF180
    seq.add_block(gz_spoil)
    rf180.freq_offset = gz180.amplitude * slice_thickness * (
        s - (n_slices - 1) / 2)
    seq.add_block(rf180, gz180)
    seq.add_block(gz_spoil)

    # Delay for EPI
    seq.add_block(make_delay(delay_te2))

    # Locate k-space - Only on the frequency encoding direction.
    seq.add_block(gx_pre)

    for i in range(Nyeff):
# Check TR delay time
assert tr_delay > 0, "Such parameter configuration needs longer TR."

""" EPI calibration """
for s in range(n_slices):
    # Fat saturation
    if fatsat_enable:
        seq.add_block(rf_fs, gz_fs)

    # RF90
    rf.freq_offset = gz.amplitude * slice_thickness * (s - (n_slices - 1) / 2)
    seq.add_block(rf, gz)
    seq.add_block(gz_reph)

    # Delay for first RF180
    seq.add_block(make_delay(d1))

    # First RF180
    seq.add_block(gz_spoil_1)
    rf180.freq_offset = gz180.amplitude * slice_thickness * (s - (n_slices - 1) / 2)
    seq.add_block(rf180, gz180)
    seq.add_block(gz_spoil_1)

    # Delay for second RF180
    seq.add_block(make_delay(d2 + d3))

    # Second RF180
    seq.add_block(gz_spoil_2)
    rf180.freq_offset = gz180.amplitude * slice_thickness * (s - (n_slices - 1) / 2)
    seq.add_block(rf180, gz180)
    seq.add_block(gz_spoil_2)
Example #10
0
spoil_amp = 0.8 * sys.max_grad  # Hz/m
rise_time = 1.0e-3  # spoiler rise time in seconds
spoil_dur = 6.5e-3  # complete spoiler duration in seconds

gx_spoil, gy_spoil, gz_spoil = [make_trapezoid(channel=c, system=sys, amplitude=spoil_amp, duration=spoil_dur,
                                               rise_time=rise_time) for c in ['x', 'y', 'z']]

# RF pulses
flip_angle_sat = seq_defs['b1cwpe'] * gamma_hz * 2 * np.pi * seq_defs['tp']
rf_pulse = make_block_pulse(flip_angle=flip_angle_sat, duration=seq_defs['tp'], system=sys)

# ADC events
pseudo_adc = make_adc(num_samples=1, duration=1e-3)  # (not played out; just used to split measurements)

# DELAYS
trec_delay = make_delay(seq_defs['trec'])
m0_delay = make_delay(seq_defs['trec_m0'])

# Sequence object
seq = Sequence()

# ===
# RUN
# ===

offsets_hz = seq_defs['offsets_ppm'] * gamma_hz * seq_defs['b0']  # convert from ppm to Hz

for m, offset in enumerate(offsets_hz):
    # print progress/offset
    print(f' {m + 1} / {len(offsets_hz)} : offset {offset}')
Example #11
0
def gre_refscan(seq, meta_file=None, system=Opts(), params=None):

    # decrease slew rate a bit
    save_slew = system.max_slew
    system.max_slew = 100 * system.gamma
    if params is None:
        params = {
            "fov": 210e-3,
            "res": 3e-3,
            "flip_angle": 12,
            "rf_dur": 1e-3,
            "tbp": 2,
            "slices": 1,
            "slice_res": 2e-3,
            "dist_fac": 0,
            "readout_bw": 600
        }

    # RF
    rf, gz, gz_reph, rf_del = make_sinc_pulse(
        flip_angle=params["flip_angle"] * math.pi / 180,
        duration=params["rf_dur"],
        slice_thickness=params["slice_res"],
        apodization=0.5,
        time_bw_product=params["tbp"],
        system=system,
        return_gz=True,
        return_delay=True)

    # Calculate readout gradient and ADC parameters
    delta_k = 1 / params["fov"]
    Nx = Ny = int(params["fov"] / params["res"] + 0.5)
    samples = 2 * Nx  # 2x oversampling
    gx_flat_time_us = int(1e6 / params["readout_bw"])  # readout_bw is in Hz/Px
    dwelltime = ph.trunc_to_raster(1e-6 * gx_flat_time_us / samples,
                                   decimals=7)
    gx_flat_time = round(dwelltime * samples, 5)
    if (1e5 * gx_flat_time % 2 == 1):
        gx_flat_time += 10e-6  # even flat time
    diff_flat_adc = gx_flat_time - (dwelltime * samples)

    # Gradients
    gx_flat_area = Nx * delta_k * (
        gx_flat_time /
        (dwelltime * samples))  # compensate for longer flat time than ADC
    gx = make_trapezoid(channel='x',
                        flat_area=gx_flat_area,
                        flat_time=gx_flat_time,
                        system=system)
    gx_pre = make_trapezoid(channel='x',
                            area=-gx.area / 2,
                            duration=1.4e-3,
                            system=system)
    phase_areas = (np.arange(Ny) - Ny / 2) * delta_k

    # reduce slew rate of spoilers to avoid stimulation
    gx_spoil = make_trapezoid(channel='x',
                              area=2 * Nx * delta_k,
                              system=system,
                              max_slew=120 * system.gamma)
    gz_spoil = make_trapezoid(channel='z',
                              area=4 / params["slice_res"],
                              system=system,
                              max_slew=120 * system.gamma)

    # take minimum TE rounded up to .1 ms
    min_TE = np.ceil(
        (gz.fall_time + gz.flat_time / 2 + calc_duration(gx_pre) +
         calc_duration(gx) / 2) / seq.grad_raster_time) * seq.grad_raster_time
    TE = ph.round_up_to_raster(min_TE, decimals=4)
    delay_TE = TE - min_TE

    # take minimum TR rounded up to .1 ms
    min_TR = calc_duration(gx_pre) + calc_duration(gz) + calc_duration(
        gx) + delay_TE + calc_duration(gx_spoil, gz_spoil)
    TR = ph.round_up_to_raster(min_TR, decimals=4)
    delay_TR = TR - min_TR

    # ADC with 2x oversampling
    adc = make_adc(num_samples=samples,
                   dwell=dwelltime,
                   delay=gx.rise_time + diff_flat_adc / 2,
                   system=system)

    # RF spoiling
    rf_spoiling_inc = 117
    rf_phase = 0
    rf_inc = 0

    # build sequence
    prepscans = 40  # number of dummy preparation scans

    if params["slices"] % 2 == 1:
        slc = 0
    else:
        slc = 1
    for s in range(params["slices"]):
        if s == int(params["slices"] / 2 + 0.5):
            if params["slices"] % 2 == 1:
                slc = 1
            else:
                slc = 0
        rf.freq_offset = gz.amplitude * params["slice_res"] * (
            slc - (params["slices"] - 1) / 2) * (1 + params["dist_fac"] * 1e-2)

        # prepscans
        for d in range(prepscans):
            rf.phase_offset = rf_phase / 180 * np.pi
            adc.phase_offset = rf_phase / 180 * np.pi
            rf_inc = divmod(rf_inc + rf_spoiling_inc, 360.0)[1]
            rf_phase = divmod(rf_phase + rf_inc, 360.0)[1]

            seq.add_block(rf, gz, rf_del)
            gy_pre = make_trapezoid(channel='y',
                                    area=phase_areas[0],
                                    duration=1.4e-3,
                                    system=system)
            seq.add_block(gx_pre, gy_pre, gz_reph)
            seq.add_block(make_delay(delay_TE))
            seq.add_block(gx)
            gy_pre.amplitude = -gy_pre.amplitude
            seq.add_block(make_delay(delay_TR), gx_spoil, gy_pre, gz_spoil)

        # imaging scans
        for i in range(Ny):
            rf.phase_offset = rf_phase / 180 * np.pi
            adc.phase_offset = rf_phase / 180 * np.pi
            rf_inc = divmod(rf_inc + rf_spoiling_inc, 360.0)[1]
            rf_phase = divmod(rf_phase + rf_inc, 360.0)[1]

            seq.add_block(rf, gz, rf_del)
            gy_pre = make_trapezoid(channel='y',
                                    area=phase_areas[i],
                                    duration=1.4e-3,
                                    system=system)
            seq.add_block(gx_pre, gy_pre, gz_reph)
            seq.add_block(make_delay(delay_TE))
            seq.add_block(gx, adc)
            gy_pre.amplitude = -gy_pre.amplitude
            seq.add_block(make_delay(delay_TR), gx_spoil, gy_pre, gz_spoil)

            if meta_file is not None:
                acq = ismrmrd.Acquisition()
                acq.idx.kspace_encode_step_1 = i
                acq.idx.kspace_encode_step_2 = 0  # only 2D atm
                acq.idx.slice = slc
                # acq.idx.average = avg
                acq.setFlag(ismrmrd.ACQ_IS_PARALLEL_CALIBRATION)
                if i == Ny - 1:
                    acq.setFlag(ismrmrd.ACQ_LAST_IN_SLICE)
                meta_file.append_acquisition(acq)

        slc += 2  # acquire every 2nd slice, afterwards fill slices inbetween

    delay_end = make_delay(
        d=2)  # 5s delay after reference scan to allow for relaxation
    seq.add_block(delay_end)
    system.max_slew = save_slew
Example #12
0
          2 * calc_duration(gz_spoil) + calc_duration(gz180) + delay_te2 +
          2 * gdiff_dur + calc_duration(gx_pre, gy_pre) +
          calc_duration(gx) * Nyeff + calc_duration(gy) *
          (Nyeff - 1) + calc_duration(gx_crush, gz_crush))) /
        seq.grad_raster_time) * seq.grad_raster_time

#EPI calibration
for s in range(n_slices):
    print(s - (n_slices - 1) / 2)
    if fatsat_enable:
        seq.add_block(rf_fs, gz_fs)
    rf.freq_offset = gz.amplitude * slice_thickness * (s - (n_slices - 1) / 2)
    seq.add_block(rf, gz)
    seq.add_block(gz_reph)

    seq.add_block(make_delay(gdiff_dur + delay_te1))

    seq.add_block(gz_spoil)
    rf180.freq_offset = gz180.amplitude * slice_thickness * (
        s - (n_slices - 1) / 2)
    seq.add_block(rf180, gz180)
    seq.add_block(gz_spoil)

    seq.add_block(make_delay(delay_te2 + gdiff_dur))

    seq.add_block(gx_pre)

    for i in range(1, Nyeff + 1):
        seq.add_block(gx, adc)
        seq.add_block(make_delay(calc_duration(gy)))
        gx.amplitude = -gx.amplitude
Example #13
0
# Time between the gradient and the RF180. This time might be zero some times, although it is not normal.
n_gap_te1 = n_delay_te1 - n_delay_te2
gap_te1 = n_gap_te1 / i_raster_time
""" EPI calibration """
for s in range(n_slices):
    # Fat saturation
    if fatsat_enable:
        seq.add_block(rf_fs, gz_fs)

    # RF90
    rf.freq_offset = gz.amplitude * slice_thickness * (s - (n_slices - 1) / 2)
    seq.add_block(rf, gz)
    seq.add_block(gz_reph)

    # Delay for RF180
    seq.add_block(make_delay(delay_te1))

    # RF180
    seq.add_block(gz_spoil)
    rf180.freq_offset = gz180.amplitude * slice_thickness * (
        s - (n_slices - 1) / 2)
    seq.add_block(rf180, gz180)
    seq.add_block(gz_spoil)

    # Delay for EPI
    seq.add_block(make_delay(delay_te2))

    # Locate k-space - Only on the frequency encoding direction.
    seq.add_block(gx_pre)

    for i in range(Nyeff):
Example #14
0
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
Example #15
0
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
Example #16
0
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
Example #17
0
dur = ceil(2 * sqrt(delta_k / system.max_slew) / 10e-6) * 10e-6
kwargs_for_gy = {"channel": 'y', "system": system, "area": delta_k, "duration": dur}
gy = make_trapezoid(kwargs_for_gy)

flip = 180 * pi / 180
kwargs_for_sinc = {"flip_angle": flip, "system": system, "duration": 2.5e-3}
rf180 = make_block_pulse(kwargs_for_sinc)
kwargs_for_gz_spoil = {"channel": 'z', "system": system, "area": gz.area * 2, "duration": 3 * pre_time}
gz_spoil = make_trapezoid(kwargs_for_gz_spoil)

TE, TR = 200e-3, 1000e-3
duration_to_center = (Nx / 2 + 0.5) * calc_duration(gx) + Ny / 2 * calc_duration(gy)
delayTE1 = TE / 2 - calc_duration(gz) / 2 - pre_time - calc_duration(gz_spoil) - calc_duration(rf180) / 2
delayTE2 = TE / 2 - calc_duration(rf180) / 2 - calc_duration(gz_spoil) - duration_to_center
delay1 = make_delay(delayTE1)
delay2 = make_delay(delayTE2)

seq.add_block(rf, gz)
seq.add_block(gx_pre, gy_pre, gz_reph)
seq.add_block(delay1)
seq.add_block(gz_spoil)
seq.add_block(rf180)
seq.add_block(gz_spoil)
seq.add_block(delay2)
for i in range(Ny):
    seq.add_block(gx, adc)
    seq.add_block(gy)
    gx.amplitude = -gx.amplitude
seq.add_block(make_delay(1))
Example #18
0
gx_pre = make_trapezoid(kwargs_for_gxpre)
kwargs_for_gz_reph = {
    "channel": 'z',
    "system": system,
    "area": -gz.area / 2,
    "duration": 2e-3
}
gz_reph = make_trapezoid(kwargs_for_gz_reph)
phase_areas = (np.arange(Ny) - (Ny / 2)) * delta_k

TE, TR = 10e-3, 200e-3
delayTE = TE - calc_duration(
    gx_pre) - calc_duration(gz) / 2 - calc_duration(gx) / 2
delayTR = TR - calc_duration(gx_pre) - calc_duration(gz) - calc_duration(
    gx) - delayTE
delay1 = make_delay(delayTE)
delay2 = make_delay(delayTR)

for i in range(Ny):
    seq.add_block(rf, gz)
    kwargsForGyPre = {
        "channel": 'y',
        "system": system,
        "area": phase_areas[i],
        "duration": 2e-3
    }
    gyPre = make_trapezoid(kwargsForGyPre)
    seq.add_block(gx_pre, gyPre, gz_reph)
    seq.add_block(delay1)
    seq.add_block(gx, adc)
    seq.add_block(delay2)
def make_pulseq_gre_oblique(fov, n, thk, fa, tr, te, enc='xyz', slice_locs=None, write=False):
    """Makes a gradient-echo sequence in any plane

        2D oblique multi-slice gradient-echo pulse sequence with Cartesian encoding
        Oblique means that each of slice-selection, phase encoding, and frequency encoding
        can point in any specified direction

        Parameters
        ----------
        fov : array_like
            Isotropic field-of-view, or length-2 list [fov_readout, fov_phase], in meters
        n : array_like
            Isotropic matrix size, or length-2 list [n_readout, n_phase]
        thk : float
            Slice thickness in meters
        fa : float
            Flip angle in degrees
        tr : float
            Repetition time in seconds
        te : float
            Echo time in seconds
        enc : str or array_like, optional
            Spatial encoding directions
            1st - readout; 2nd - phase encoding; 3rd - slice select
            - Use str with any permutation of x, y, and z to obtain orthogonal slices
            e.g. The default 'xyz' means axial(z) slice with readout in x and phase encoding in y
            - Use list to indicate vectors in the encoding directions for oblique slices
            They should be perpendicular to each other, but not necessarily unit vectors
            e.g. [(2,1,0),(-1,2,0),(0,0,1)] rotates the two in-plane encoding directions for an axial slice
        slice_locs : array_like, optional
            Slice locations from isocenter in meters
            Default is None which means a single slice at the center
        write : bool, optional
            Whether to write seq into file; default is False

        Returns
        -------
        seq : Sequence
            Pulse sequence as a Pulseq object

        """

    # System options
    # kwargs_for_opts = {"rf_ring_down_time": 0, "rf_dead_time": 0}
    kwargs_for_opts = {'max_grad': 32, 'grad_unit': 'mT/m',
                       'max_slew': 130, 'slew_unit': 'T/m/s', 'rf_ring_down_time': 30e-6,
                       'rf_dead_time': 100e-6, 'adc_dead_time': 20e-6}
    system = Opts(kwargs_for_opts)
    seq = Sequence(system)

    # Calculate unit gradients for ss, fe, pe
    ug_fe, ug_pe, ug_ss = parse_enc(enc)

    # Sequence parameters
    Nf, Np = (n, n) if isinstance(n, int) else (n[0], n[1])
    delta_k_ro, delta_k_pe = (1 / fov, 1 / fov) if isinstance(fov, float) else (1 / fov[0], 1 / fov[1])
    kWidth_ro = Nf * delta_k_ro
    flip = fa * pi / 180

    # Slice select: RF and gradient
    kwargs_for_sinc = {"flip_angle": flip, "system": system, "duration": 4e-3, "slice_thickness": thk,
                       "apodization": 0.5, "time_bw_product": 4}
    rf, g_ss = make_sinc_pulse(kwargs_for_sinc, 2)
    g_ss_x, g_ss_y, g_ss_z = make_oblique_gradients(g_ss, ug_ss)

    # Readout and ADC
    readoutTime = 6.4e-3
    kwargs_for_g_ro = {"channel": 'x', "system": system, "flat_area": kWidth_ro, "flat_time": readoutTime}
    g_ro = make_trapezoid(kwargs_for_g_ro)
    g_ro_x, g_ro_y, g_ro_z = make_oblique_gradients(g_ro, ug_fe)  #
    kwargs_for_adc = {"num_samples": Nf, "duration": g_ro.flat_time, "delay": g_ro.rise_time}
    adc = makeadc(kwargs_for_adc)

    # Readout rewinder
    kwargs_for_g_ro_pre = {"channel": 'x', "system": system, "area": -g_ro.area / 2, "duration": 2e-3}
    g_ro_pre = make_trapezoid(kwargs_for_g_ro_pre)
    g_ro_pre_x, g_ro_pre_y, g_ro_pre_z = make_oblique_gradients(g_ro_pre, ug_fe)  #

    # Slice refocusing
    kwargs_for_g_ss_reph = {"channel": 'z', "system": system, "area": -g_ss.area / 2, "duration": 2e-3}
    g_ss_reph = make_trapezoid(kwargs_for_g_ss_reph)
    g_ss_reph_x, g_ss_reph_y, g_ss_reph_z = make_oblique_gradients(g_ss_reph, ug_ss)

    # Prepare phase areas
    phase_areas = (np.arange(Np) - (Np / 2)) * delta_k_pe

    TE, TR = te, tr
    delayTE = TE - calc_duration(g_ro_pre) - calc_duration(g_ss) / 2 - calc_duration(g_ro) / 2
    delayTR = TR - calc_duration(g_ro_pre) - calc_duration(g_ss) - calc_duration(g_ro) - delayTE
    delay1 = make_delay(delayTE)
    delay2 = make_delay(delayTR)

    if slice_locs is None:
        locs = [0]
    else:
        locs = slice_locs

    # Construct sequence!
    for u in range(len(locs)):
        # add frequency offset
        rf.freq_offset = g_ss.amplitude * locs[u]
        for i in range(Np):
            seq.add_block(rf, g_ss_x, g_ss_y, g_ss_z)
            kwargs_for_g_pe = {"channel": 'y', "system": system, "area": phase_areas[i], "duration": 2e-3}
            g_pe = make_trapezoid(kwargs_for_g_pe)

            g_pe_x, g_pe_y, g_pe_z = make_oblique_gradients(g_pe, ug_pe)

            pre_grads_list = [g_ro_pre_x, g_ro_pre_y, g_ro_pre_z,
                              g_ss_reph_x, g_ss_reph_y, g_ss_reph_z,
                              g_pe_x, g_pe_y, g_pe_z]

            gtx, gty, gtz = combine_trap_grad_xyz(gradients=pre_grads_list, system=system, dur=2e-3)

            seq.add_block(gtx, gty, gtz)
            seq.add_block(delay1)
            seq.add_block(g_ro_x, g_ro_y, g_ro_z, adc)
            seq.add_block(delay2)

    if write:
        seq.write(
            "gre_fov{:.0f}mm_Nf{:d}_Np{:d}_TE{:.0f}ms_TR{:.0f}ms_FA{:.0f}deg.seq".format(fov * 1000, Nf, Np, TE * 1000,
                                                                                         TR * 1000, flip * 180 / pi))
    print('GRE sequence constructed')
    return seq
Example #20
0
flip = 180 * pi / 180
kwargs_for_sinc = {
    "flip_angle": flip,
    "system": system,
    "duration": 2e-3,
    "slice_thickness": slice_thickness,
    "apodization": 0.5,
    "time_bw_product": 4
}
rf180, gz180 = make_sinc_pulse(kwargs_for_sinc, 2)

delayTE1 = TE / 2 - calc_duration(gz_reph) - calc_duration(
    rf) - calc_duration(rf180) / 2
delayTE2 = TE / 2 - calc_duration(gx) / 2 - calc_duration(rf180) / 2
delayTE3 = TR - TE - calc_duration(gx)
delay1 = make_delay(delayTE1)
delay2 = make_delay(delayTE2)
delay3 = make_delay(delayTE3)

for inv in range(len(TI)):
    seq.add_block(
        rf180
    )  # Non-selective at the moment, could be extended to make this selective/adiabatic
    seq.add_block(make_delay(TI[inv]))
    for i in range(Ny):
        seq.add_block(rf, gz)
        kwargs_for_gy_pre = {
            "channel": 'y',
            "system": system,
            "area": -(Ny / 2 - i) * delta_k,
            "duration": readoutTime / 2
def make_pulseq_gre(fov, n, thk, fa, tr, te, enc='xyz', slice_locs=None, write=False):
    """Makes a gradient-echo sequence

    2D orthogonal multi-slice gradient-echo pulse sequence with Cartesian encoding
    Orthogonal means that each of slice-selection, phase encoding, and frequency encoding
    aligns with the x, y, or z directions

    Parameters
    ----------
    fov : float
        Field-of-view in meters (isotropic)
    n : int
        Matrix size (isotropic)
    thk : float
        Slice thickness in meters
    fa : float
        Flip angle in degrees
    tr : float
        Repetition time in seconds
    te : float
        Echo time in seconds
    enc : str, optional
        Spatial encoding directions
        1st - readout; 2nd - phase encoding; 3rd - slice select
        Default 'xyz' means axial(z) slice with readout in x and phase encoding in y
    slice_locs : array_like, optional
        Slice locations from isocenter in meters
        Default is None which means a single slice at the center
    write : bool, optional
        Whether to write seq into file; default is False

    Returns
    -------
    seq : Sequence
        Pulse sequence as a Pulseq object

    """
    kwargs_for_opts = {"rf_ring_down_time": 0, "rf_dead_time": 0}
    system = Opts(kwargs_for_opts)
    seq = Sequence(system)

    Nf = n
    Np = n
    flip = fa * pi / 180
    kwargs_for_sinc = {"flip_angle": flip, "system": system, "duration": 4e-3, "slice_thickness": thk,
                       "apodization": 0.5, "time_bw_product": 4}
    rf, g_ss = make_sinc_pulse(kwargs_for_sinc, 2)
    g_ss.channel = enc[2]

    delta_k = 1 / fov
    kWidth = Nf * delta_k

    # Readout and ADC
    readoutTime = 6.4e-3
    kwargs_for_g_ro = {"channel": enc[0], "system": system, "flat_area": kWidth, "flat_time": readoutTime}
    g_ro = make_trapezoid(kwargs_for_g_ro)
    kwargs_for_adc = {"num_samples": Nf, "duration": g_ro.flat_time, "delay": g_ro.rise_time}
    adc = makeadc(kwargs_for_adc)
    # Readout rewinder
    kwargs_for_g_ro_pre = {"channel": enc[0], "system": system, "area": -g_ro.area / 2, "duration": 2e-3}
    g_ro_pre = make_trapezoid(kwargs_for_g_ro_pre)
    # Slice refocusing
    kwargs_for_g_ss_reph = {"channel": enc[2], "system": system, "area": -g_ss.area / 2, "duration": 2e-3}
    g_ss_reph = make_trapezoid(kwargs_for_g_ss_reph)
    phase_areas = (np.arange(Np) - (Np / 2)) * delta_k

    # TE, TR = 10e-3, 1000e-3
    TE, TR = te, tr
    delayTE = TE - calc_duration(g_ro_pre) - calc_duration(g_ss) / 2 - calc_duration(g_ro) / 2
    delayTR = TR - calc_duration(g_ro_pre) - calc_duration(g_ss) - calc_duration(g_ro) - delayTE
    delay1 = make_delay(delayTE)
    delay2 = make_delay(delayTR)

    if slice_locs is None:
        locs = [0]
    else:
        locs = slice_locs

    for u in range(len(locs)):
        # add frequency offset
        rf.freq_offset = g_ss.amplitude * locs[u]
        for i in range(Np):
            seq.add_block(rf, g_ss)
            kwargs_for_g_pe = {"channel": enc[1], "system": system, "area": phase_areas[i], "duration": 2e-3}
            g_pe = make_trapezoid(kwargs_for_g_pe)
            seq.add_block(g_ro_pre, g_pe, g_ss_reph)
            seq.add_block(delay1)
            seq.add_block(g_ro, adc)
            seq.add_block(delay2)

    if write:
        seq.write(
            "gre_fov{:.0f}mm_Nf{:d}_Np{:d}_TE{:.0f}ms_TR{:.0f}ms_FA{:.0f}deg.seq".format(fov * 1000, Nf, Np, TE * 1000,
                                                                                         TR * 1000, flip * 180 / pi))
    print('GRE sequence constructed')
    return seq
assert np.all(tr >= min_tr)

tr_delay = round((tr_per_slice - (calc_duration(rf_fs) + calc_duration(gz_fs) + calc_duration(rf) + calc_duration(gz_reph) + 2*calc_duration(gz_spoil) + calc_duration(rf180) + calc_duration(gx_pre,gy_pre)
        + calc_duration(gx)*Ny*pF + calc_duration(gx_crush,gz_crush)))/seq.grad_raster_time) * seq.grad_raster_time


#EPI calibration
for s in range(n_slices):
    print(s - (n_slices - 1) / 2)
    seq.add_block(rf_fs, gz_fs)
    rf.freq_offset = gz.amplitude * slice_thickness * (s - (n_slices - 1) / 2)
    seq.add_block(rf, gz)
    seq.add_block(gz_reph)

    seq.add_block(make_delay(gdiff_dur + delay_te1))

    seq.add_block(gz_spoil)
    seq.add_block(rf180, gz180)
    seq.add_block(gz_spoil)

    seq.add_block(make_delay(gdiff_dur))

    seq.add_block(make_delay(delay_te2 + gdiff_dur))

    seq.add_block(gx_pre)

    for i in range(1, Nyeff + 1):
        seq.add_block(gx, adc)
        gx.amplitude = -gx.amplitude
def make_pulseq_se(fov, n, thk, fa, tr, te, enc='xyz', slice_locs=None, write=False):
    """Makes a Spin Echo (SE) sequence

    2D orthogonal multi-slice Spin-Echo pulse sequence with Cartesian encoding
    Orthogonal means that each of slice-selection, phase encoding, and frequency encoding
    aligns with the x, y, or z directions

    Parameters
    ----------
    fov : float
        Field-of-view in meters (isotropic)
    n : int
        Matrix size (isotropic)
    thk : float
        Slice thickness in meters
    fa : float
        Flip angle in degrees
    tr : float
        Repetition time in seconds
    te : float
        Echo time in seconds
    enc : str, optional
        Spatial encoding directions
        1st - readout; 2nd - phase encoding; 3rd - slice select
        Default 'xyz' means axial(z) slice with readout in x and phase encoding in y
    slice_locs : array_like, optional
        Slice locations from isocenter in meters
        Default is None which means a single slice at the center
    write : bool, optional
        Whether to write seq into file; default is False

    Returns
    -------
    seq : Sequence
        Pulse sequence as a Pulseq object

    """
    kwargs_for_opts = {"max_grad": 33, "grad_unit": "mT/m", "max_slew": 100, "slew_unit": "T/m/s",
                       "rf_dead_time": 10e-6,
                       "adc_dead_time": 10e-6}
    system = Opts(kwargs_for_opts)
    seq = Sequence(system)
    # Parameters
    Nf = n
    Np = n
    delta_k = 1 / fov
    kWidth = Nf * delta_k

    TE, TR = te, tr

    # Non-180 pulse
    flip1 = fa * pi / 180
    kwargs_for_sinc = {"flip_angle": flip1, "system": system, "duration": 2e-3, "slice_thickness": thk,
                       "apodization": 0.5, "time_bw_product": 4}
    rf, g_ss = make_sinc_pulse(kwargs_for_sinc, 2)
    g_ss.channel = enc[2]

    # 180 pulse
    flip2 = 180 * pi / 180
    kwargs_for_sinc = {"flip_angle": flip2, "system": system, "duration": 2e-3, "slice_thickness": thk,
                       "apodization": 0.5, "time_bw_product": 4}
    rf180, g_ss180 = make_sinc_pulse(kwargs_for_sinc, 2)
    g_ss180.channel = enc[2]

    # Readout gradient & ADC
    #    readoutTime = system.grad_raster_time * Nf
    readoutTime = 6.4e-3
    kwargs_for_g_ro = {"channel": enc[0], "system": system, "flat_area": kWidth, "flat_time": readoutTime}
    g_ro = make_trapezoid(kwargs_for_g_ro)
    kwargs_for_adc = {"num_samples": Nf, "system": system, "duration": g_ro.flat_time, "delay": g_ro.rise_time}
    adc = makeadc(kwargs_for_adc)

    # RO rewinder gradient
    kwargs_for_g_ro_pre = {"channel": enc[0], "system": system, "area": g_ro.area / 2,
                           "duration": 2e-3}
    #                        "duration": g_ro.rise_time + g_ro.fall_time + readoutTime / 2}

    g_ro_pre = make_trapezoid(kwargs_for_g_ro_pre)

    # Slice refocusing gradient
    kwargs_for_g_ss_reph = {"channel": enc[2], "system": system, "area": -g_ss.area / 2, "duration": 2e-3}
    g_ss_reph = make_trapezoid(kwargs_for_g_ss_reph)

    # Delays
    delayTE1 = (TE - 2 * max(calc_duration(g_ss_reph), calc_duration(g_ro_pre)) - calc_duration(g_ss) - calc_duration(
        g_ss180)) / 2
    # delayTE2 = TE / 2 - calc_duration(g_ro) / 2 - calc_duration(g_ss180) / 2
    delayTE2 = (TE - calc_duration(g_ro) - calc_duration(g_ss180)) / 2
    delayTE3 = TR - TE - (calc_duration(g_ss) + calc_duration(g_ro)) / 2

    delay1 = make_delay(delayTE1)
    delay2 = make_delay(delayTE2)
    delay3 = make_delay(delayTE3)

    # Construct sequence
    if slice_locs is None:
        locs = [0]
    else:
        locs = slice_locs

    for u in range(len(locs)):
        rf180.freq_offset = g_ss180.amplitude * locs[u]
        rf.freq_offset = g_ss.amplitude * locs[u]
        for i in range(Np):
            seq.add_block(rf, g_ss)  # 90-deg pulse
            kwargs_for_g_pe_pre = {"channel": enc[1], "system": system, "area": -(Np / 2 - i) * delta_k,
                                   "duration": 2e-3}
            # "duration": g_ro.rise_time + g_ro.fall_time + readoutTime / 2}
            g_pe_pre = make_trapezoid(kwargs_for_g_pe_pre)  # Phase encoding gradient
            seq.add_block(g_ro_pre, g_pe_pre,
                          g_ss_reph)  # Add a combination of ro rewinder, phase encoding, and slice refocusing
            seq.add_block(delay1)  # Delay 1: until 180-deg pulse
            seq.add_block(rf180, g_ss180)  # 180 deg pulse for SE
            seq.add_block(delay2)  # Delay 2: until readout
            seq.add_block(g_ro, adc)  # Readout!
            seq.add_block(delay3)  # Delay 3: until next inversion pulse

    if write:
        seq.write(
            "se_fov{:.0f}mm_Nf{:d}_Np{:d}_TE{:.0f}ms_TR{:.0f}ms.seq".format(fov * 1000, Nf, Np, TE * 1000, TR * 1000))

    print('Spin echo sequence constructed')
    return seq
Example #24
0
gr7 = make_extended_trapezoid(channel='x', times=gr7_times, amplitudes=gr7_amp)

t_ex = gs1.t[-1] + gs2.t[-1] + gs3.t[-1]
t_ref = gs4.t[-1] + gs5.t[-1] + gs7.t[-1] + readout_time
t_end = gs4.t[-1] + gs5.t[-1]
TE_train = t_ex + n_echo * t_ref + t_end
TR_fill = (TR - n_slices * TE_train) / n_slices
TR_fill = system.grad_raster_time * round(TR_fill / system.grad_raster_time)
if TR_fill < 0:
    TR_fill = 1e-3
    warnings.warn(
        f'TR too short, adapted to include all slices to: {1000 * n_slices * (TE_train + TR_fill)} ms'
    )
else:
    print(f'TR fill: {1000 * TR_fill} ms')
delay_TR = make_delay(TR_fill)

for k_ex in range(n_ex + 1):
    for s in range(n_slices):
        rf_ex.freq_offset = gs_ex.amplitude * slice_thickness * (
            s - (n_slices - 1) / 2)
        rf_ref.freq_offset = gs_ref.amplitude * slice_thickness * (
            s - (n_slices - 1) / 2)
        rf_ex.phase_offset = rf_ex_phase - 2 * np.pi * rf_ex.freq_offset * calc_rf_center(
            rf_ex)[0]
        rf_ref.phase_offset = rf_ref_phase - 2 * np.pi * rf_ref.freq_offset * calc_rf_center(
            rf_ref)[0]

        seq.add_block(gs1)
        seq.add_block(gs2, rf_ex)
        seq.add_block(gs3, gr3)
def make_pulseq_se_oblique(fov, n, thk, fa, tr, te, enc='xyz', slice_locs=None, write=False):
    """Makes a Spin Echo (SE) sequence in any plane

        2D oblique multi-slice Spin-Echo pulse sequence with Cartesian encoding
        Oblique means that each of slice-selection, phase encoding, and frequency encoding
        can point in any specified direction

        Parameters
        ----------
        fov : array_like
            Isotropic field-of-view, or length-2 list [fov_readout, fov_phase], in meters
        n : array_like
            Isotropic matrix size, or length-2 list [n_readout, n_phase]
        thk : float
            Slice thickness in meters
        fa : float
            Flip angle in degrees
        tr : float
            Repetition time in seconds
        te : float
            Echo time in seconds
        enc : str or array_like, optional
            Spatial encoding directions
            1st - readout; 2nd - phase encoding; 3rd - slice select
            - Use str with any permutation of x, y, and z to obtain orthogonal slices
            e.g. The default 'xyz' means axial(z) slice with readout in x and phase encoding in y
            - Use list to indicate vectors in the encoding directions for oblique slices
            They should be perpendicular to each other, but not necessarily unit vectors
            e.g. [(2,1,0),(-1,2,0),(0,0,1)] rotates the two in-plane encoding directions for an axial slice
        slice_locs : array_like, optional
            Slice locations from isocenter in meters
            Default is None which means a single slice at the center
        write : bool, optional
            Whether to write seq into file; default is False

        Returns
        -------
        seq : Sequence
            Pulse sequence as a Pulseq object

        """

    # System options
    kwargs_for_opts = {'max_grad': 32, 'grad_unit': 'mT/m',
                       'max_slew': 130, 'slew_unit': 'T/m/s', 'rf_ring_down_time': 30e-6,
                       'rf_dead_time': 100e-6, 'adc_dead_time': 20e-6}
    system = Opts(kwargs_for_opts)
    seq = Sequence(system)

    # Sequence parameters
    ug_fe, ug_pe, ug_ss = parse_enc(enc)
    Nf, Np = (n, n) if isinstance(n, int) else (n[0], n[1])
    delta_k_ro, delta_k_pe = (1 / fov, 1 / fov) if isinstance(fov, float) else (1 / fov[0], 1 / fov[1])
    kWidth_ro = Nf * delta_k_ro
    TE, TR = te, tr

    # Non-180 pulse
    flip1 = fa * pi / 180
    kwargs_for_sinc = {"flip_angle": flip1, "system": system, "duration": 2e-3, "slice_thickness": thk,
                       "apodization": 0.5, "time_bw_product": 4}
    rf, g_ss = make_sinc_pulse(kwargs_for_sinc, 2)
    g_ss_x, g_ss_y, g_ss_z = make_oblique_gradients(g_ss, ug_ss)

    # 180 pulse
    flip2 = 180 * pi / 180
    kwargs_for_sinc = {"flip_angle": flip2, "system": system, "duration": 2e-3, "slice_thickness": thk,
                       "apodization": 0.5, "time_bw_product": 4}
    rf180, g_ss180 = make_sinc_pulse(kwargs_for_sinc, 2)
    g_ss180_x, g_ss180_y, g_ss180_z = make_oblique_gradients(g_ss180, ug_ss)

    # Readout gradient & ADC
    readoutTime = 6.4e-3
    kwargs_for_g_ro = {"channel": 'x', "system": system, "flat_area": kWidth_ro, "flat_time": readoutTime}
    g_ro = make_trapezoid(kwargs_for_g_ro)
    g_ro_x, g_ro_y, g_ro_z = make_oblique_gradients(g_ro, ug_fe)
    kwargs_for_adc = {"num_samples": Nf, "system": system, "duration": g_ro.flat_time, "delay": g_ro.rise_time}
    adc = makeadc(kwargs_for_adc)

    # RO rewinder gradient
    kwargs_for_g_ro_pre = {"channel": 'x', "system": system, "area": g_ro.area / 2,
                           "duration": 2e-3}
    g_ro_pre = make_trapezoid(kwargs_for_g_ro_pre)
    g_ro_pre_x, g_ro_pre_y, g_ro_pre_z = make_oblique_gradients(g_ro_pre, ug_fe)

    # Slice refocusing gradient
    kwargs_for_g_ss_reph = {"channel": 'z', "system": system, "area": -g_ss.area / 2, "duration": 2e-3}
    g_ss_reph = make_trapezoid(kwargs_for_g_ss_reph)
    g_ss_reph_x, g_ss_reph_y, g_ss_reph_z = make_oblique_gradients(g_ss_reph, ug_ss)

    # Delays
    delayTE1 = (TE - 2 * max(calc_duration(g_ss_reph), calc_duration(g_ro_pre)) - calc_duration(g_ss) - calc_duration(
        g_ss180)) / 2
    delayTE2 = (TE - calc_duration(g_ro) - calc_duration(g_ss180)) / 2
    delayTE3 = TR - TE - (calc_duration(g_ss) + calc_duration(g_ro)) / 2

    delay1 = make_delay(delayTE1)
    delay2 = make_delay(delayTE2)
    delay3 = make_delay(delayTE3)

    # Construct sequence
    if slice_locs is None:
        locs = [0]
    else:
        locs = slice_locs

    for u in range(len(locs)):
        rf180.freq_offset = g_ss180.amplitude * locs[u]
        rf.freq_offset = g_ss.amplitude * locs[u]
        for i in range(Np):
            seq.add_block(rf, g_ss_x, g_ss_y, g_ss_z)  # 90-deg pulse
            kwargs_for_g_pe = {"channel": 'y', "system": system, "area": -(Np / 2 - i) * delta_k_pe, "duration": 2e-3}
            g_pe = make_trapezoid(kwargs_for_g_pe)  # Phase encoding gradient
            g_pe_x, g_pe_y, g_pe_z = make_oblique_gradients(g_pe, ug_pe)

            pre_grads_list = [g_ro_pre_x, g_ro_pre_y, g_ro_pre_z,
                              g_ss_reph_x, g_ss_reph_y, g_ss_reph_z,
                              g_pe_x, g_pe_y, g_pe_z]
            gtx, gty, gtz = combine_trap_grad_xyz(pre_grads_list, system, 2e-3)

            seq.add_block(gtx, gty, gtz)  # Add a combination of ro rewinder, phase encoding, and slice refocusing
            seq.add_block(delay1)  # Delay 1: until 180-deg pulse
            seq.add_block(rf180, g_ss180_x, g_ss180_y, g_ss180_z)  # 180 deg pulse for SE
            seq.add_block(delay2)  # Delay 2: until readout
            seq.add_block(g_ro_x, g_ro_y, g_ro_z, adc)  # Readout!
            seq.add_block(delay3)  # Delay 3: until next inversion pulse

    if write:
        seq.write(
            "se_fov{:.0f}mm_Nf{:d}_Np{:d}_TE{:.0f}ms_TR{:.0f}ms.seq".format(fov * 1000, Nf, Np, TE * 1000, TR * 1000))

    print('Spin echo sequence (oblique) constructed')
    return seq
# Fat saturation
if fatsat:
    if B0 > 4:
        fatsat_bw = 1000 # bandwidth of fatsat pulse [Hz]
    else:
        fatsat_bw = 300
    fatsat_fa = 110 # flip angle [°]
    fatsat_dur = ph.round_up_to_raster(fatsat_tbp/fatsat_bw, decimals=5)

    rf_fatsat, fatsat_del = make_gauss_pulse(flip_angle=fatsat_fa*np.pi/180, duration=fatsat_dur, bandwidth=fatsat_bw, freq_offset=B0*ph.fw_shift, system=system, return_delay=True)

# echo time delay
min_te = exc_to_rew + rew_dur + system.adc_dead_time
if min_te > TE:
    raise ValueError('Minimum TE is {} ms.'.format(min_te*1e3))
te_delay = make_delay(d = TE - min_te)

#%% Spiral Readout Gradients

# Parameters spiral trajectory:

# parameter         description               default value
# ---------        -------------              --------------

# nitlv:      number of spiral interleaves        15
# res:        resolution                          1 mm
# fov:        target field of view                192 mm
# max_amp:    maximum gradient amplitude          42 mT/m
# min_rise:   minimum gradient risetime           5 us/(mT/m)
# spiraltype: 1: spiral out                   
#             2: spiral in                        
    "ncontrast": len(TE)
}
create_hdr(hdr, params_hdr)

#%% Set up sequence

seq.set_definition(
    "Name", seq_name)  # metadata file name is saved in Siemens header for reco
seq.set_definition("FOV", [fov, fov, slice_res])
seq.set_definition("Slice_Thickness", "%f" % (slice_res * slices))

# Noise scans
noisescans = 16
noise_samples = 256
noise_adc = make_adc(system=system, num_samples=256, dwell=1e-6 * dwelltime_us)
noise_delay = make_delay(
    d=ph.round_up_to_raster(noise_adc.duration + 1e-3, decimals=5))
for k in range(noisescans):
    seq.add_block(noise_adc, noise_delay)
    acq = ismrmrd.Acquisition()
    acq.setFlag(ismrmrd.ACQ_IS_NOISE_MEASUREMENT)
    meta_file.append_acquisition(acq)

if slices % 2 == 1:
    slc = 0
else:
    slc = 1
for s in range(slices):
    if s == int(slices / 2 + 0.5):
        if slices % 2 == 1:
            slc = 1
        else: