Example #1
0
def calc_SAR(file: Union[str, Path, Sequence]) -> None:
    """
    Compute Global SAR values on the `.seq` object for head and whole body over the specified time averages.

    Parameters
    ----------
    file : str, Path or Seuqence
        `.seq` file for which global SAR values will be computed. Can be path to `.seq` file as `str` or `Path`, or the
        `Sequence` object itself.

    Raises
    ------
    ValueError
        If `file` is a `str` or `Path` to the `.seq` file and this file does not exist on disk.
    """
    if isinstance(file, (str, Path)):
        if isinstance(file, str):
            file = Path(file)

        if file.exists() and file.is_file():
            seq_obj = Sequence()
            seq_obj.read(str(file))
            seq_obj = seq_obj
        else:
            raise ValueError('Seq file does not exist.')
    else:
        seq_obj = file

    Q_tmf, Q_hmf = _load_Q()
    SAR_wbg, SAR_hg, t = _SAR_from_seq(seq_obj, Q_tmf, Q_hmf)
    SARwbg_lim, tsec = _SAR_interp(SAR_wbg, t)
    SARhg_lim, tsec = _SAR_interp(SAR_hg, t)
    SAR_wbg_tensec, SAR_wbg_sixmin, SAR_hg_tensec, SAR_hg_sixmin, SAR_wbg_sixmin_peak, SAR_hg_sixmin_peak, \
    SAR_wbg_tensec_peak, SAR_hg_tensec_peak = _SAR_lims_check(SARwbg_lim, SARhg_lim, tsec)

    # Plot 10 sec average SAR
    if (tsec[-1] > 10):
        plt.plot(tsec, SAR_wbg_tensec, 'x-', label='Whole Body: 10sec')
        plt.plot(tsec, SAR_hg_tensec, '.-', label='Head only: 10sec')

        # plt.plot(t, SARwbg, label='Whole Body - instant')
        # plt.plot(t, SARhg, label='Whole Body - instant')

        plt.xlabel('Time (s)')
        plt.ylabel('SAR (W/kg)')
        plt.title('Global SAR  - Mass Normalized -  Whole body and head only')

        plt.legend()
        plt.grid(True)
        plt.show()
Example #2
0
def read_any_version(seq_file: Union[str, Path],
                     seq: Sequence = None) \
        -> Sequence:
    """
    Reads a sequence file (seq_file) independent of the (py)pulseq version.
    :param seq_file: path to the sequence file to read into the Sequence object
    :param seq: the sequence to read the seq file into. If not provided, a new Sequence object is instantiated
    :return seq: Sequence object
    """
    version = get_minor_version(seq_file)
    if not seq:
        seq = Sequence()
    if version in [2, 3]:
        seq.read(seq_file)
    else:
        raise ValueError('Version', version, 'can not be converted.')
    return seq
Example #3
0
def waveform_from_seqblock(seq_block):
    """
    extracts gradient waveform from Pypulseq sequence block
    """
    from pypulseq.Sequence.sequence import Sequence

    if seq_block.channel == 'x':
        axis = 0
    elif seq_block.channel == 'y':
        axis = 1
    elif seq_block.channel == 'z':
        axis = 2
    else:
        raise ValueError('No valid gradient waveform')
    dummy_seq = Sequence()  # helper dummy sequence
    dummy_seq.add_block(seq_block)
    return dummy_seq.gradient_waveforms()[
        axis, :-1]  # last value is a zero that does not belong to the waveform
def SARfromseq(fname, Qtmf, Qhmf):
    """
    This definition computes the global whole body and head only SAR values

    Parameters
    ----------
    fname : str
    Qtmf : numpy.ndarray
    Qhmf : numpy.ndarray

    Returns
    -------
    SARwbg_vec : numpy.ndarray
    SARhg_vec : numpy.ndarray
    t_vec : numpy.ndarray
        contains the Q-matrix, GSAR head and body for now
    """

    obj = Sequence()
    obj.read(str(SAR_PATH / 'assets' / fname))  # replaced by

    # Identify rf blocks and compute SAR - 10 seconds must be less than twice and 6 minutes must be less than 4 (WB) and 3.2 (head-20)
    blockEvents = obj.block_events
    numEvents = len(blockEvents)
    t_vec = np.zeros(numEvents)
    SARwbg_vec = np.zeros(t_vec.shape)
    SARhg_vec = np.zeros(t_vec.shape)
    t_prev = 0

    for iB in blockEvents:
        block = obj.get_block(iB)
        block_dur = calc_duration(block)
        t_vec[iB - 1] = t_prev + block_dur
        t_prev = t_vec[iB - 1]
        if ('rf' in block):  # has rf
            rf = block['rf']
            t = rf.t
            signal = rf.signal
            # This rf could be parallel transmit as well
            SARwbg_vec[iB] = calc_SAR(Qtmf, signal)
            SARhg_vec[iB] = calc_SAR(Qhmf, signal)

    return SARwbg_vec, SARhg_vec, t_vec
Example #5
0
def calc_SAR(seq):
    """
    Compute Global SAR values on the `seq` object for head and whole body over the specified time averages.

    Parameters
    ----------
    seq : Sequence
        pypulseq `Sequence` object for which global SAR values will be computed.
    """

    if isinstance(seq, str):
        seq_obj = Sequence()
        seq_obj.read(seq)
        seq = seq_obj

    Qtmf, Qhmf = __loadQ()
    SARwbg, SARhg, t_vec = __SAR_from_seq(seq, Qtmf, Qhmf)
    SARwbg_lim, tsec = __SAR_interp(SARwbg, t_vec)
    SARhg_lim, tsec = __SAR_interp(SARhg, t_vec)
    SAR_wbg_tensec, SAR_wbg_sixmin, SAR_hg_tensec, SAR_hg_sixmin, SAR_wbg_sixmin_peak, SAR_hg_sixmin_peak, SAR_wbg_tensec_peak, SAR_hg_tensec_peak = __SAR_lims_check(
        SARwbg_lim, SARhg_lim, tsec)

    # Plot 10 sec average SAR
    if (tsec[-1] > 10):
        plt.plot(tsec, SAR_wbg_tensec, label='Whole Body: 10sec')
        plt.plot(tsec, SAR_hg_tensec, label='Head only: 10sec')

        # plt.plot(t_vec, SARwbg, label='Whole Body - instant')
        # plt.plot(t_vec, SARhg, label='Whole Body - instant')

        plt.xlabel('Time (s)')
        plt.ylabel('SAR (W/kg)')
        plt.title('Global SAR  - Mass Normalized -  Whole body and head only')
        ax = plt.subplot(111)

        # ax.legend()
        plt.grid(True)
        plt.show()
def make_pulseq_epi_oblique(fov, n, thk, fa, tr, te, enc='xyz', slice_locs=None, echo_type="se", n_shots=1,
                            seg_type='blocked', write=False):
    """Makes an Echo Planar Imaging (EPI) sequence in any plane

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    # Find number of lines in each block

    if seg_type == 'blocked':

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

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

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


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

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

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

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

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

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

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

    # Add blocks

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

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

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

            seq.add_block(delay3)

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

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

    print('EPI sequence (oblique) constructed')
    return seq, ro_dirs, ro_order
def make_pulseq_se_oblique(fov, n, thk, fa, tr, te, enc='xyz', slice_locs=None, write=False):
    """Makes a Spin Echo (SE) sequence in any plane

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

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

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

        """

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    print('Spin echo sequence (oblique) constructed')
    return seq
def make_pulseq_se(fov, n, thk, fa, tr, te, enc='xyz', slice_locs=None, write=False):
    """Makes a Spin Echo (SE) sequence

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

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

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

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

    TE, TR = te, tr

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

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

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

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

    g_ro_pre = make_trapezoid(kwargs_for_g_ro_pre)

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

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

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

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

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

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

    print('Spin echo sequence constructed')
    return seq
Example #9
0
import numpy as np

from pypulseq.Sequence.sequence import Sequence
from pypulseq.calc_rf_center import calc_rf_center
from pypulseq.make_adc import make_adc
from pypulseq.make_delay import make_delay
from pypulseq.make_extended_trapezoid import make_extended_trapezoid
from pypulseq.make_sinc_pulse import make_sinc_pulse
from pypulseq.make_trap_pulse import make_trapezoid
from pypulseq.opts import Opts

dG = 250e-6
system = Opts(max_grad=30, grad_unit='mT/m', max_slew=170, slew_unit='T/m/s', rf_ringdown_time=100e-6,
              rf_dead_time=100e-6, adc_dead_time=10e-6)

seq = Sequence(system=system)

fov = 256e-3
Ny_pre = 8
Nx, Ny = 128, 128
n_echo = int(Ny / 2 + Ny_pre)
n_slices = 1
rf_flip = 180
if isinstance(rf_flip, int):
    rf_flip = np.zeros(n_echo) + rf_flip
slice_thickness = 5e-3
TE = 12e-3
TR = 2000e-3
TE_eff = 60e-3
k0 = round(TE_eff / TE)
PE_type = 'linear'
from pypulseq.Sequence.sequence import Sequence
import matplotlib.pyplot as plt
import h5py

# Load both files

q1 = 3
q2 = 6

seq_orig = Sequence()
seq_orig.read('gre_jemris.seq')
print('Seq original')
print(seq_orig.get_block(q1).gx)

seq_proc = Sequence()
seq_proc.read('gre_jemris_seq2xml_jemris.seq')

#print("Seq processed:")
#print(seq_proc.get_block(q2).gx)

#seq_orig.plot(time_range=[0,10])

#seq_proc.plot(time_range=[0,10])

sd = h5py.File('gre_jemris_seq2xml_jemris.h5', 'r')
sd = sd['seqdiag']
plt.figure(1)
plt.subplot(411)
plt.title("Twice converted JEMRIS sequence diagram")

plt.plot(sd['T'][()], sd['TXM'][()])
Example #11
0
    return rf_inv


# Press the green button in the gutter to run the script.
if __name__ == '__main__':
    # System limits
    system = Opts(max_grad=28,
                  grad_unit='mT/m',
                  max_slew=125,
                  slew_unit='T/m/s',
                  rf_ringdown_time=20e-6,
                  rf_dead_time=100e-6,
                  adc_dead_time=10e-6)

    seq = Sequence(system)

    # Sequence Parameters
    FOV = 200e-3  # [m]
    Ny = 128
    Nstartup = 11

    # Calculate TR
    _, TR, Ny_aq = bssfp_readout(Sequence(system),
                                 system,
                                 fov=FOV,
                                 Nstartup=Nstartup,
                                 Ny=Ny)

    # Calculate Crushers
    gzSpoil_INV = make_trapezoid('z',
Example #12
0
# ======
# SETUP
# ======
dG = 250e-6

# Set system limits
system = Opts(max_grad=30,
              grad_unit='mT/m',
              max_slew=170,
              slew_unit='T/m/s',
              rf_ringdown_time=100e-6,
              rf_dead_time=100e-6,
              adc_dead_time=10e-6)

seq = Sequence(system=system)  # Create a new sequence object
# Define FOV and resolution
fov = 256e-3
Ny_pre = 8
Nx, Ny = 128, 128
n_echo = int(Ny / 2 + Ny_pre)
n_slices = 1
rf_flip = 180
if isinstance(rf_flip, int):
    rf_flip = np.zeros(n_echo) + rf_flip
slice_thickness = 5e-3  # Slice thickness
TE = 12e-3
TR = 2000e-3
TE_eff = 60e-3
k0 = round(TE_eff / TE)
PE_type = 'linear'
Example #13
0
from pypulseq.Sequence.sequence import Sequence
from pypulseq.opts import Opts
from pypulseq.make_arbitrary_grad import make_arbitrary_grad
import numpy as np

seq = Sequence(system=Opts())

gx = make_arbitrary_grad(channel='x',
                         waveform=np.array([1, 2, 3, 4, 5, 4, 3, 2, 1]))
seq.add_block(gx)

seq.write("hiseq.seq")

seq2 = Sequence()
seq2.read('hiseq.seq')
print(seq2.get_block(1).gx)
Example #14
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 #15
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
Example #16
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 #17
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
def tbt_seq2xml(n):
    seq = Sequence()
    seq_path = f'sim/ismrm_abstract/spgr_var_N/spgr_gspoil_N{n}_Ns1_TE10ms_TR50ms_FA30deg_acq_112020.seq'
    seq.read(seq_path)
    seq2xml(seq, seq_name=f'spgr{n}',out_folder=f'sim/ismrm_abstract/spgr_var_N/spgr{n}')
Example #19
0
"""
import math

import matplotlib.pyplot as plt
import numpy as np

from pypulseq.Sequence.sequence import Sequence
from pypulseq.make_adc import make_adc
from pypulseq.make_sinc_pulse import make_sinc_pulse
from pypulseq.make_trap_pulse import make_trapezoid
from pypulseq.opts import Opts

# ======
# SETUP
# ======
seq = Sequence()  # Create a new sequence object
# Define FOV and resolution
fov = 220e-3
Nx = 64
Ny = 64
slice_thickness = 3e-3  # Slice thickness
n_slices = 3

# Set system limits
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)

# ======
# CREATE EVENTS
# ======
# Create 90 degree slice selection pulse and gradient
def simulate_pulseq_jemris(
        seq_path,
        phantom_info,
        coil_fov,
        tx='uniform',
        rx='uniform',  # TODO add input that includes sequence info for
        # TODO      dimensioning the RO points into kspace
    tx_maps=None,
        rx_maps=None,
        sim_name=None,
        env_option="local"):
    """Runs simulation using an already-made .seq file

    Inputs
    ------
    seq_path : str
        Path to seq file
    phantom_info : dict
        Information used to create phantom; input to create_and_save_phantom()
    coil_fov : float
        Field-of-view of coil in mm
    tx : str, optional
        Type of tx coil; default is 'uniform'; the only other option is 'custom'
    rx : str, optional
        Type of rx coil; default is 'uniform'; the only other option is 'custom'
    tx_maps : list, optional
        List of np.ndarray (dtype='complex') maps for all tx channels
        Required for 'custom' type tx
    rx_maps : list, optional
        List of np.ndarray (dtype='complex') maps for all rx channels
        Required for 'custom' type rx
    sim_name : str, optional
        Used as folder name inside sim folder
        Default is None, in which case sim_name will be set to the current timestamp

    Returns
    -------
    sim_output :
        Delivers output from sim_jemris

    """
    if sim_name is None:
        sim_name = time.strftime("%Y%m%d%H%M%S")

    if env_option == 'local':
        target_path = 'sim\\' + sim_name
    elif env_option == 'colab':
        target_path = 'sim\\' + sim_name

    # Make target folder
    dir_str = f'sim\\{sim_name}'
    if not os.path.isdir(dir_str):
        os.system(f'mkdir {dir_str}')

    # Convert .seq to .xml
    seq = Sequence()
    seq.read(seq_path)
    print(seq.get_block(1))
    seq_name = seq_path[seq_path.rfind('/') + 1:seq_path.rfind('.seq')]
    seq2xml(seq, seq_name=seq_name, out_folder=str(target_path))

    # Make phantom and save as .h5 file
    pht_name = create_and_save_phantom(phantom_info, out_folder=target_path)

    # Make sure we have the tx/rx files
    tx_filename = tx + '.xml'
    rx_filename = rx + '.xml'

    # Save Tx as xml
    if tx == 'uniform':
        cp_command = f'copy {os.getcwd()}\\sim\\{tx}.xml {os.getcwd()}\\{str(target_path)}\\{tx}.xml'
        print(cp_command)
        a = os.system(cp_command)
        print(a)
    elif tx == 'custom' and tx_maps is not None:
        coil2xml(b1maps=tx_maps,
                 fov=coil_fov,
                 name='custom_tx',
                 out_folder=target_path)
        tx_filename = 'custom_tx.xml'
    else:
        raise ValueError('Tx coil type not found')

    # save Rx as xml
    if rx == 'uniform':
        os.system(f'copy sim\\{rx}.xml sim\\{str(target_path)}')
    elif rx == 'custom' and rx_maps is not None:
        coil2xml(b1maps=rx_maps,
                 fov=coil_fov,
                 name='custom_rx',
                 out_folder=target_path)
        rx_filename = 'custom_rx.xml'
    else:
        raise ValueError('Rx coil type not found')

    # Run simuluation in target folder
    list_sim_files = {
        'seq_xml': seq_name + '.xml',
        'pht_h5': pht_name + '.h5',
        'tx_xml': tx_filename,
        'rx_xml': rx_filename
    }
    sim_output = sim_jemris(list_sim_files=list_sim_files,
                            working_folder=target_path)

    return sim_output
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
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
meta_filename = meta_folder+seq_name+'.h5'
if os.path.isfile(meta_filename):
    os.remove(meta_filename)
meta_file = ismrmrd.Dataset(meta_filename)

hdr = ismrmrd.xsd.ismrmrdHeader()
t_min = TE + dwelltime/2 # save trajectory starting point for B0-correction
params_hdr = {"fov": fov, "res": res, "slices": slices, "slice_res": slice_res, "nintl":int(Nintl/redfac), "avg": averages,
                "nsegments": num_segments, "dwelltime": dwelltime, "t_min": t_min, "trajtype": "spiral"}
create_hdr(hdr, params_hdr)
meta_file.write_xml_header(hdr.toXML('utf-8'))

#%% Add sequence blocks to sequence & write acquisitions to metadata

# Set up the sequence
seq = Sequence()

# Definitions section in seq file
seq.set_definition("Name", seq_name) # metadata file name is saved in Siemens header for reco
seq.set_definition("FOV", [1e-3*fov, 1e-3*fov, slice_res]) # for FOV positioning
seq.set_definition("Slice_Thickness", "%f" % (slice_res*(1+dist_fac*1e-2)*(slices-1)+slice_res)) # we misuse this to show the total covered head area in the GUI
if num_segments > 1:
    seq.set_definition("MaxAdcSegmentLength", "%d" % int(num_samples/num_segments+0.5)) # for automatic ADC segment length setting

# Noise scans
noise_samples = 256
noise_dwelltime = 2e-6
noise_adc = make_adc(system=system, num_samples=256, dwell=noise_dwelltime, delay=system.adc_dead_time)
noise_delay = make_delay(d=ph.round_up_to_raster(calc_duration(noise_adc)+1e-3,decimals=5)) # add some more time to the ADC delay to be safe
for k in range(noisescans):
    seq.add_block(noise_adc, noise_delay)
Example #24
0
def write_gapped_swift(N,
                       BW,
                       R=128,
                       TR=100e-3,
                       flip=90,
                       L_over=1,
                       enc_type='3D',
                       n_adc=2,
                       spoke_os=1,
                       fm_type='full',
                       output=False):
    """
    Parameters
    ----------
    N : int
        Isotropic matrix size
    R : float
        Time-bandwidth product
    TR : float
        Repetition time [seconds]
    flip : float
        Flip angle [degrees] as calculated with equation ? in ?
    L_over : float, default=1
        RF oversampling factor
    enc_type : str, default='3D'
        Type of sequence encoding: '2D' or '3D'
    spoke_os : int, default=1
        Oversampling factor for theta
    fm_type : str, default='full'
        The way frequency modulation is handled
        'none' - each pulse segment has a fixed phase
        'linear' - each pulse segment has a fixed frequency corresponding to FM
        'full' - each pulse segment has complete FM based on the ideal pulse
    output : str, default=False
        Whether the sequence is saved as a seq file


    Returns
    -------
    seq : Sequence
        PyPulseq gapped SWIFT sequence object
    seq_info : dict
        Information for reconstruction
        'thetas' - all polar angles
        'phis' - all azimuthal angles
        'rf_complex' - Complex RF waveform
        'ktraj': 3D k-space trajectory
    """

    # Let's do SWIFT
    # System
    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
    FOV = 250e-3  # Field of view
    #N_sample = int(N / 2) # Number of points sampled on a radial spoke / number of segments 1 pulse is broken into
    dx = FOV / N  # Spatial resolution (isotropic)

    # Number of spokes, number of altitude angles, number of azimuthal angles
    if enc_type == '3D':
        Ns, Ntheta, Nphi = get_radk_params_3D(dx, FOV)
        N_line = Ntheta * Nphi
        # Radial encoding details
        thetas = np.linspace(0, np.pi, Ntheta, endpoint=False)
    elif enc_type == "2D":
        Nphi = get_radk_params_2D(N)
        thetas = np.array([np.pi / 2])  # Zero z-gradient.
        N_line = Nphi

    phis = np.linspace(0, 2 * np.pi, Nphi * spoke_os, endpoint=False)
    N_line = N_line * spoke_os

    # Pulse parameters
    FA = flip * pi / 180
    beta = 5  # so that F1 is ~0.01 at tau = +- 1
    Tp = R / BW  # Total pulse duration
    N_seg = int(L_over * R)
    dw = Tp / N_seg  # sampling dt

    dc = 0.5  # pulse duty cycle
    T_seg = dw * dc  # Duration of 1 segment
    gap = dw - T_seg  # time interval where RF is off in each segment
    adc_factor = 0.5  # Where during the gap lies the sample

    #g_amp = dk / dw  # Net gradient amplitude

    g_amp = BW / FOV
    RF_amp_max = FA / (2 * pi * np.power(beta, -1 / 2) * np.sqrt(R) / BW
                       )  # From gapped pulse paper; units: [Hz]

    # Gradient & ADC
    ramp_time = 100e-6
    delay_TR = TR - N_seg * dw
    g_const = make_extended_trapezoid(channel='x',
                                      times=np.array([0, dw]),
                                      amplitudes=np.array([g_amp, g_amp]),
                                      system=system)
    g_delay = make_extended_trapezoid(channel='x',
                                      times=np.array([0, delay_TR]),
                                      amplitudes=np.array([g_amp, g_amp]),
                                      system=system)
    #dwell = ((1 - adc_factor) * gap - 200e-6) / 2 # This worked for 3D N=16 but not 2D N=128
    #dwell = ((1 - adc_factor) * gap - system.adc_dead_time) / 2
    #dwell = ((1 - adc_factor) * gap) / 2
    dwell = 10e-6

    adc = make_adc(num_samples=n_adc,
                   delay=T_seg + adc_factor * gap,
                   dwell=dwell)

    # Find RF modulations
    AM, FM = make_HS_pulse(beta=beta, b1=RF_amp_max, bw=BW)
    list_RFs = extract_chopped_pulses(AM,
                                      FM,
                                      Tp,
                                      N_seg,
                                      T_seg,
                                      fm_type=fm_type)
    # Save information
    rf_complex = np.zeros((1, len(list_RFs)), dtype=complex)
    for u in range(len(list_RFs)):
        rf_complex[0, u] = list_RFs[u].signal[0] * np.exp(
            1j * list_RFs[u].phase_offset)

    amp_gx, amp_gy, amp_gz = 0, 0, 0
    # Construct the sequence
    seq = Sequence(system)
    q = 0
    y = 0

    ktraj = np.zeros((N_seg, N_line, 3))
    nline = 0
    for theta in thetas:  # For each altitude angle
        for phi in phis:  # For each azimuth angle
            print(f'Adding {q} of {len(thetas)*len(phis)} directions')
            # Calculate gradients
            unit_grad = get_3d_unit_grad(theta, phi)
            g_const_x, g_const_y, g_const_z = make_oblique_gradients(
                g_const, unit_grad)
            # Ramps from last encode to this oen
            g_ramp_x = make_extended_trapezoid_check_zero(
                channel='x',
                times=np.array([0, ramp_time]),
                amplitudes=[amp_gx, g_const_x.first],
                system=system)
            g_ramp_y = make_extended_trapezoid_check_zero(
                channel='y',
                times=np.array([0, ramp_time]),
                amplitudes=[amp_gy, g_const_y.first],
                system=system)
            g_ramp_z = make_extended_trapezoid_check_zero(
                channel='z',
                times=np.array([0, ramp_time]),
                amplitudes=[amp_gz, g_const_z.first],
                system=system)
            amp_gx, amp_gy, amp_gz = g_const_x.first, g_const_y.first, g_const_z.first
            seq.add_block(g_ramp_x, g_ramp_y, g_ramp_z)
            for n in range(N_seg):  # For each RF segment
                # Make ramp-up from the last gradient & this constant gradient
                # Add RF, ADC with delay, and split gradient together
                seq.add_block(list_RFs[n], g_const_x, g_const_y, g_const_z,
                              adc)
                y = y + 1

            dk_3d = adc.delay * np.array([
                g_const_x.waveform[0], g_const_y.waveform[0],
                g_const_z.waveform[0]
            ])
            ktraj[:, nline, 0] = dk_3d[0] * np.arange(1, N_seg + 1)
            ktraj[:, nline, 1] = dk_3d[1] * np.arange(1, N_seg + 1)
            ktraj[:, nline, 2] = dk_3d[2] * np.arange(1, N_seg + 1)
            nline += 1

            g_delay_x, g_delay_y, g_delay_z = make_oblique_gradients(
                g_delay, unit_grad)
            seq.add_block(g_delay_x, g_delay_y, g_delay_z)

            q += 1

    today = date.today()
    todayf = today.strftime("%m%d%y")
    seq_info = {
        'thetas': thetas,
        'phis': phis,
        'rf_complex': rf_complex,
        'ktraj': ktraj
    }
    savemat(
        f'swiftV2_info_bw{BW}_nadc{n_adc}_FOV{FOV*1e3}_FA{flip}_N{N}_{enc_type}_L-over{L_over}_{todayf}_sos{spoke_os}.mat',
        seq_info)
    if output:
        seq.write(
            f'swiftV2_bw{BW}_nadc{n_adc}_FOV{FOV*1e3}_FA{flip}_N{N}_{enc_type}_L-over{L_over}_{todayf}_sos{spoke_os}.seq'
        )

    print(f'Gradient amplitude is {g_amp} Hz/m')

    return seq, seq_info