예제 #1
0
kWidth = Nx * delta_k
readoutTime = system.grad_raster_time * Nx
kwargs_for_gx = {
    "channel": 'x',
    "system": system,
    "flat_area": kWidth,
    "flat_time": readoutTime
}
gx = make_trapezoid(kwargs_for_gx)
kwargs_for_adc = {
    "num_samples": Nx,
    "system": system,
    "duration": gx.flat_time,
    "delay": gx.rise_time
}
adc = make_adc(kwargs_for_adc)

kwargs_for_gxpre = {
    "channel": 'x',
    "system": system,
    "area": gx.area / 2,
    "duration": readoutTime / 2
}
gx_pre = make_trapezoid(kwargs_for_gxpre)
kwargs_for_gz_reph = {
    "channel": 'z',
    "system": system,
    "area": -gz.area / 2,
    "duration": 2e-3
}
gz_reph = make_trapezoid(kwargs_for_gz_reph)
예제 #2
0
# ======
# CREATE EVENTS
# ======
# Create 90 degree slice selection pulse and gradient
rf, gz, _ = make_sinc_pulse(flip_angle=np.pi / 2, system=system, duration=3e-3, slice_thickness=slice_thickness,
                            apodization=0.5, time_bw_product=4, return_gz=True)

# Define other gradients and ADC events
delta_k = 1 / fov
k_width = Nx * delta_k
dwell_time = 4e-6
readout_time = Nx * dwell_time
flat_time = math.ceil(readout_time * 1e5) * 1e-5  # round-up to the gradient raster
gx = make_trapezoid(channel='x', system=system, amplitude=k_width / readout_time, flat_time=flat_time)
adc = make_adc(num_samples=Nx, duration=readout_time,
               delay=gx.rise_time + flat_time / 2 - (readout_time - dwell_time) / 2)

# Pre-phasing gradients
pre_time = 8e-4
gx_pre = make_trapezoid(channel='x', system=system, area=-gx.area / 2, duration=pre_time)
gz_reph = make_trapezoid(channel='z', system=system, area=-gz.area / 2, duration=pre_time)
gy_pre = make_trapezoid(channel='y', system=system, area=-Ny / 2 * delta_k, duration=pre_time)

# Phase blip in shortest possible time
dur = math.ceil(2 * math.sqrt(delta_k / system.max_slew) / 10e-6) * 10e-6
gy = make_trapezoid(channel='y', system=system, area=delta_k, duration=dur)

# ======
# CONSTRUCT SEQUENCE
# ======
# Define sequence blocks
예제 #3
0
actual_area = gx.area - gx.amplitude / gx.rise_time * blip_dur / 2 * blip_dur / 2 / 2 - gx.amplitude / gx.fall_time * blip_dur / 2 * blip_dur / 2 / 2
gx.amplitude = gx.amplitude / actual_area * k_width
gx.area = gx.amplitude * (gx.flat_time + gx.rise_time / 2 + gx.fall_time / 2)
gx.flat_area = gx.amplitude * gx.flat_time

adc_dwell_nyquist = delta_k / gx.amplitude
adc_dwell = math.floor(adc_dwell_nyquist * 1e7) * 1e-7
#both adc_dwell and grad_time must fall on the same grid
adc_dwell = math.floor(adc_dwell / seq.grad_raster_time)*seq.grad_raster_time
#making sure the number of samples is even
adc_samples = math.floor(readout_time / adc_dwell / 4) * 4

if adc_samples == 0:
    print('error! The readout time needs to be longer!')

adc = make_adc(num_samples=adc_samples, dwell=adc_dwell, delay=blip_dur / 2)

time_to_center = system.adc_dead_time * (adc_samples - 1) / 2
adc.delay = round((gx.rise_time + gx.flat_time / 2 - time_to_center) / seq.grad_raster_time) * seq.grad_raster_time

gy_parts = split_gradient_at(grad=gy, time_point=blip_dur / 2, system=system)
gy_blipup, gy_blipdown, _ = align('right', gy_parts[0], 'left', gy_parts[1], gx)
gy_blipdownup = add_gradients([gy_blipdown, gy_blipup], system=system)

gy_blipup.waveform = gy_blipup.waveform * pe_enable
gy_blipdown.waveform = gy_blipdown.waveform * pe_enable
gy_blipdownup.waveform = gy_blipdownup.waveform * pe_enable

gx_pre = make_trapezoid(channel='x', system=system, area=-gx.area / 2)
gy_pre = make_trapezoid(channel='y', system=system, area= -(1-pF)*Ny * delta_k)
예제 #4
0
def bssfp_readout(seq, system, fov=200e-3, Nstartup=11, Ny=128):
    """
    Creates a Balanced steady-state free precession (bSSFP) sequence and adds
    to seq object

    Parameters
    ----------
    seq: object
        Sequence object
    system : Opts
        System limits.
    fov : float, optional
        Field-of-view [m]. Default is 0.2 m.
    Nstartup : float, optional
        Number of start-up RF pulses. Default is 11
    Ny : float, optional
        Number of phase encoding lines. Default is 128.
   Returns
    -------
    seq : SimpleNamespace
        Seq object with bSSFP readout
    TR : float
        Repetition time
    Ny : float
        Final number of phase encoding lines.
    """
    # Sequence Parameters
    enc = 'xyz'
    Nx = 128
    thk = 6e-3
    fa = 35  # [deg]

    Nramp = Nstartup

    # ADC duration (controls TR/TE)
    adc_dur = 2560 / 2  # [us]

    rf_dur = 490  # [us]
    rf_apo = 0.5
    rf_bwt = 1.5

    #############################################################################
    #                 Create slice selection pulse and gradient
    rf, g_ss, __ = make_sinc_pulse(
        flip_angle=fa * pi / 180,
        system=system,
        duration=rf_dur * 1e-6,
        slice_thickness=thk,
        apodization=rf_apo,
        time_bw_product=rf_bwt
    )  # for next pyPulseq version add:, return_gz=True)
    g_ss.channel = enc[2]

    # Slice refocusing
    g_ss_reph = make_trapezoid(channel=enc[2],
                               system=system,
                               area=-g_ss.area / 2,
                               duration=0.00017 * 2)
    #############################################################################

    rf.delay = calc_duration(g_ss) - calc_duration(rf) + rf.delay

    #############################################################################
    #                        Readout gradient and ADC
    delta_k = 1 / fov
    kWidth = Nx * delta_k

    # Readout and ADC
    g_ro = make_trapezoid(channel=enc[0],
                          system=system,
                          flat_area=kWidth,
                          flat_time=(adc_dur) * 1e-6)
    adc = make_adc(num_samples=Nx,
                   duration=g_ro.flat_time,
                   delay=g_ro.rise_time)

    # Readout rewinder
    g_ro_pre = make_trapezoid(channel=enc[0],
                              system=system,
                              area=-g_ro.area / 2)

    phaseAreas_tmp = np.arange(0, Ny, 1).tolist()
    phaseAreas = np.dot(np.subtract(phaseAreas_tmp, Ny / 2), delta_k)

    gs8_times = [
        0, g_ro.fall_time, g_ro.fall_time + g_ro_pre.rise_time,
        g_ro.fall_time + g_ro_pre.rise_time + g_ro_pre.flat_time,
        g_ro.fall_time + g_ro_pre.rise_time + g_ro_pre.flat_time +
        g_ro_pre.fall_time
    ]
    gs8_amp = [g_ro.amplitude, 0, g_ro_pre.amplitude, g_ro_pre.amplitude, 0]
    gx_2 = make_extended_trapezoid(channel=enc[0],
                                   times=gs8_times,
                                   amplitudes=gs8_amp)

    # Calculate phase encoding gradient duration
    pe_dur = calc_duration(gx_2)

    gx_allExt_times = [
        0, g_ro_pre.rise_time, g_ro_pre.rise_time + g_ro_pre.flat_time,
        g_ro_pre.rise_time + g_ro_pre.flat_time + g_ro_pre.fall_time,
        g_ro_pre.rise_time + g_ro_pre.flat_time + g_ro_pre.fall_time +
        g_ro.rise_time, g_ro_pre.rise_time + g_ro_pre.flat_time +
        g_ro_pre.fall_time + g_ro.rise_time + g_ro.flat_time + 1e-5,
        g_ro_pre.rise_time + g_ro_pre.flat_time + g_ro_pre.fall_time +
        g_ro.rise_time + g_ro.flat_time + 1e-5 + g_ro.fall_time,
        g_ro_pre.rise_time + g_ro_pre.flat_time + g_ro_pre.fall_time +
        g_ro.rise_time + g_ro.flat_time + 1e-5 + g_ro.fall_time +
        g_ro_pre.rise_time, g_ro_pre.rise_time + g_ro_pre.flat_time +
        g_ro_pre.fall_time + g_ro.rise_time + g_ro.flat_time + 1e-5 +
        g_ro.fall_time + g_ro_pre.rise_time + g_ro_pre.flat_time,
        g_ro_pre.rise_time + g_ro_pre.flat_time + g_ro_pre.fall_time +
        g_ro.rise_time + g_ro.flat_time + 1e-5 + g_ro.fall_time +
        g_ro_pre.rise_time + g_ro_pre.flat_time + g_ro_pre.fall_time
    ]
    gx_allExt_amp = [
        0, g_ro_pre.amplitude, g_ro_pre.amplitude, 0, g_ro.amplitude,
        g_ro.amplitude, 0, g_ro_pre.amplitude, g_ro_pre.amplitude, 0
    ]
    gx_all = make_extended_trapezoid(channel=enc[0],
                                     times=gx_allExt_times,
                                     amplitudes=gx_allExt_amp)
    #############################################################################

    gzrep_times = [
        0, g_ss_reph.rise_time, g_ss_reph.rise_time + g_ss_reph.flat_time,
        g_ss_reph.rise_time + g_ss_reph.flat_time + g_ss_reph.fall_time,
        g_ss_reph.rise_time + g_ss_reph.flat_time + g_ss_reph.fall_time +
        calc_duration(g_ro) + 2 * calc_duration(g_ro_pre) -
        2 * calc_duration(g_ss_reph) + 1e-5, g_ss_reph.rise_time +
        g_ss_reph.flat_time + g_ss_reph.fall_time + calc_duration(g_ro) +
        2 * calc_duration(g_ro_pre) - 2 * calc_duration(g_ss_reph) + 1e-5 +
        g_ss_reph.rise_time, g_ss_reph.rise_time + g_ss_reph.flat_time +
        g_ss_reph.fall_time + calc_duration(g_ro) +
        2 * calc_duration(g_ro_pre) - 2 * calc_duration(g_ss_reph) + 1e-5 +
        g_ss_reph.rise_time + g_ss_reph.flat_time, g_ss_reph.rise_time +
        g_ss_reph.flat_time + g_ss_reph.fall_time + calc_duration(g_ro) +
        2 * calc_duration(g_ro_pre) - 2 * calc_duration(g_ss_reph) + 1e-5 +
        g_ss_reph.rise_time + g_ss_reph.flat_time + g_ss_reph.fall_time
    ]
    gzrep_amp = [
        0, g_ss_reph.amplitude, g_ss_reph.amplitude, 0, 0, g_ss_reph.amplitude,
        g_ss_reph.amplitude, 0
    ]
    gzrep_all = make_extended_trapezoid(channel=enc[2],
                                        times=gzrep_times,
                                        amplitudes=gzrep_amp)

    adc.delay = g_ro_pre.rise_time + g_ro_pre.flat_time + g_ro_pre.fall_time + g_ro.rise_time + 0.5e-5

    # finish timing calculation
    TR = calc_duration(g_ss) + calc_duration(gx_all)
    TE = TR / 2

    ni_acqu_pattern = np.arange(1, Ny + 1, 1)
    Ny_aq = len(ni_acqu_pattern)
    print('Acquisition window is: %3.2f ms' % (TR * Ny_aq * 1e3))

    rf05 = rf
    rf_waveform = rf.signal
    ############################################################################
    #                          Start-up RF pulses
    #                          (ramp-up of Nramp)
    for nRamp in np.arange(1, Nramp + 1, 1):
        if np.mod(nRamp, 2):
            rf.phase_offset = 0
            adc.phase_offset = 0
        else:
            rf.phase_offset = -pi
            adc.phase_offset = -pi

        rf05.signal = np.divide(nRamp, Nramp) * rf_waveform

        gyPre_2 = make_trapezoid('y',
                                 area=phaseAreas[0],
                                 duration=pe_dur,
                                 system=system)
        gyPre_1 = make_trapezoid('y',
                                 area=-phaseAreas[0],
                                 duration=pe_dur,
                                 system=system)

        gyPre_times = [
            0, gyPre_2.rise_time, gyPre_2.rise_time + gyPre_2.flat_time,
            gyPre_2.rise_time + gyPre_2.flat_time + gyPre_2.fall_time,
            gyPre_2.rise_time + gyPre_2.flat_time + gyPre_2.fall_time +
            g_ro.flat_time + 1e-5, gyPre_2.rise_time + gyPre_2.flat_time +
            gyPre_2.fall_time + g_ro.flat_time + 1e-5 + gyPre_1.rise_time,
            gyPre_2.rise_time + gyPre_2.flat_time + gyPre_2.fall_time +
            g_ro.flat_time + 1e-5 + gyPre_1.rise_time + gyPre_1.flat_time,
            gyPre_2.rise_time + gyPre_2.flat_time + gyPre_2.fall_time +
            g_ro.flat_time + 1e-5 + gyPre_1.rise_time + gyPre_1.flat_time +
            gyPre_1.fall_time
        ]
        gyPre_amp = [
            0, gyPre_2.amplitude, gyPre_2.amplitude, 0, 0, gyPre_1.amplitude,
            gyPre_1.amplitude, 0
        ]
        gyPre_all = make_extended_trapezoid(channel=enc[1],
                                            times=gyPre_times,
                                            amplitudes=gyPre_amp)

        if nRamp == 1:
            seq.add_block(rf05, g_ss)
            seq.add_block(gx_all, gyPre_all, gzrep_all)
        else:
            seq.add_block(rf05, g_ss)
            seq.add_block(gx_all, gyPre_all, gzrep_all)
    ############################################################################

    ############################################################################
    #           Actual Readout iterate number of phase encoding steps Ny
    for i in np.arange(1, Ny + 1, 1):
        # *******************************
        # Phase cycling
        if np.mod(i + Nramp, 2):
            rf.phase_offset = 0
            adc.phase_offset = 0
        else:
            rf.phase_offset = -pi
            adc.phase_offset = -pi
        # *******************************

        # ***************************************************************************************
        #                     Create phase encoding gradients
        if np.mod(Nramp, 2):
            gyPre_2 = make_trapezoid('y',
                                     area=phaseAreas[i - 1],
                                     duration=pe_dur,
                                     system=system)
            if i > 1:
                gyPre_1 = make_trapezoid(
                    'y',
                    area=-phaseAreas[np.mod(i + Ny - 2, Ny)],
                    duration=pe_dur,
                    system=system)
            else:
                gyPre_1 = make_trapezoid(
                    'y',
                    area=-phaseAreas[np.mod(i + Ny - 1, Ny)],
                    duration=pe_dur,
                    system=system)
        else:
            gyPre_2 = make_trapezoid('y',
                                     area=phaseAreas[i],
                                     duration=pe_dur,
                                     system=system)
            if i > 1:
                gyPre_1 = make_trapezoid(
                    'y',
                    area=-phaseAreas[np.mod(i + Ny - 2, Ny)],
                    duration=pe_dur,
                    system=system)
            else:
                gyPre_1 = make_trapezoid('y',
                                         area=phaseAreas[np.mod(
                                             i + Ny - 2, Ny)],
                                         duration=pe_dur,
                                         system=system)

        gyPre_times = [
            0, gyPre_2.rise_time, gyPre_2.rise_time + gyPre_2.flat_time,
            gyPre_2.rise_time + gyPre_2.flat_time + gyPre_2.fall_time,
            gyPre_2.rise_time + gyPre_2.flat_time + gyPre_2.fall_time +
            g_ro.flat_time + 1e-5, gyPre_2.rise_time + gyPre_2.flat_time +
            gyPre_2.fall_time + g_ro.flat_time + 1e-5 + gyPre_2.rise_time,
            gyPre_2.rise_time + gyPre_2.flat_time + gyPre_2.fall_time +
            g_ro.flat_time + 1e-5 + gyPre_2.rise_time + gyPre_2.flat_time,
            gyPre_2.rise_time + gyPre_2.flat_time + gyPre_2.fall_time +
            g_ro.flat_time + 1e-5 + gyPre_2.rise_time + gyPre_2.flat_time +
            gyPre_2.fall_time
        ]
        gyPre_amp = [
            0, gyPre_2.amplitude, gyPre_2.amplitude, 0, 0, -gyPre_2.amplitude,
            -gyPre_2.amplitude, 0
        ]

        # Verify if phase encoding gradient amplitude is not zero
        try:
            gyPre_all = make_extended_trapezoid(channel=enc[1],
                                                times=gyPre_times,
                                                amplitudes=gyPre_amp)
            flag_zerogy = 0
        except:
            gyPre_times = [
                0, pe_dur, pe_dur + g_ro.flat_time + 1e-5,
                pe_dur + g_ro.flat_time + 1e-5 + pe_dur
            ]
            gyPre_amp = [0, gyPre_2.amplitude, 0, 0]
            flag_zerogy = 1
        # ***************************************************************************************

        # add RF and Slice selecitve gradient
        seq.add_block(rf, g_ss)

        # add Phase and frequency encoding gradients and ADC
        if flag_zerogy == 1:
            seq.add_block(gx_all, gzrep_all, adc)
        else:
            seq.add_block(gx_all, gyPre_all, gzrep_all, adc)

    return seq, TR, Ny
예제 #5
0
Ny = 256
alpha = 10
slice_thickness = 3e-3
TE = 7.38e-3
TR = 100e-3

rf_spoiling_inc = 117

sys = Opts(max_grad=28, grad_unit='mT/m', max_slew=150, slew_unit='T/m/s', rf_ringdown_time=20e-6, rf_dead_time=100e-6,
           adc_dead_time=10e-6)
rf, gz, gzr = make_sinc_pulse(flip_angle=alpha * math.pi / 180, duration=4e-3, slice_thickness=slice_thickness,
                              apodization=0.5, time_bw_product=4, system=sys)

deltak = 1 / fov
gx = make_trapezoid(channel='x', flat_area=Nx * deltak, flat_time=6.4e-3, system=sys)
adc = make_adc(num_samples=Nx, duration=gx.flat_time, delay=gx.rise_time, system=sys)
gx_pre = make_trapezoid(channel='x', area=-gx.area / 2, duration=2e-3, system=sys)
gz_reph = make_trapezoid(channel='z', area=-gz.area / 2, duration=2e-3, system=sys)
phase_areas = (np.arange(Ny) - Ny / 2) * deltak

gx_spoil = make_trapezoid(channel='x', area=2 * Nx * deltak, system=sys)
gz_spoil = make_trapezoid(channel='z', area=4 / slice_thickness, system=sys)

delay_TE = math.ceil((TE - calc_duration(gx_pre) - gz.fall_time - gz.flat_time / 2 - calc_duration(
    gx) / 2) / seq.grad_raster_time) * seq.grad_raster_time
delay_TR = math.ceil((TR - calc_duration(gx_pre) - calc_duration(gz) - calc_duration(
    gx) - delay_TE) / seq.grad_raster_time) * seq.grad_raster_time

if not np.all(delay_TR >= calc_duration(gx_spoil, gz_spoil)):
    raise Exception()
예제 #6
0
    except:
        readout_time = math.ceil(readout_time / seq.grad_raster_time +
                                 1) / i_raster_time

# Actual Area
actual_area = gx.area - gx.amplitude / gx.rise_time * dur / 2 * dur / 2 / 2 - gx.amplitude / gx.fall_time * dur / 2 * dur / 2 / 2
gx.amplitude = gx.amplitude / actual_area * k_width
gx.area = gx.amplitude * (gx.flat_time + gx.rise_time / 2 + gx.fall_time / 2)
gx.flat_area = gx.amplitude * gx.flat_time

# Update number of samples and ADC sampling rate (increase receiver bandwidth) to maintain the FOV.
adc_samples = math.ceil(
    readout_time / dwell_time /
    4) * 4  # On Siemens the number of samples needs to be divisible by 4.
adc = make_adc(num_samples=adc_samples,
               system=system,
               dwell=dwell_time,
               delay=dur / 2)

# Center the sampling where needed - This is needed due to the new sampling rate
# to align odd and even readouts. However, on the real hardware this misalignment is
# much stronger due to the gradient delays -> thus we can not achieve perfect matching.
time_to_center = dwell_time * (adc_samples - 1) / 2
adc.delay = np.floor(
    (gx.rise_time + gx.flat_time / 2 - time_to_center) *
    1e6) / 1e6  # It has to be multiple of the rf_raster_time....

# Split Gy to put half on each consecutive Gx and produce a combined synthetic gradient
gy_parts = split_gradient_at(grad=gy, time_point=dur / 2, system=system)
gy_blipup, gy_blipdown, _ = align('right', gy_parts[0], 'left', gy_parts[1],
                                  gx)
gy_blipup.delay = math.ceil(gy_blipup.delay / system.grad_raster_time -
예제 #7
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
예제 #8
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
    sys_gen['grad_raster_time'])  #gx_ro_wav.T
gy_ro_wav_orig = makeSystemlength(
    np.vstack((0 * gpe.reshape(-1, 1), np.zeros(
        (2, 1)), gy_ro_wav, 0 * gpe.reshape(-1, 1))),
    sys_gen['grad_raster_time'])  #gy_ro_wav.T
gz_ro_wav_orig = makeSystemlength(
    np.vstack((gpe.reshape(-1, 1), np.zeros(
        (2, 1)), 0 * gx_ro_wav, -gpe.reshape(-1, 1))),
    sys_gen['grad_raster_time'])

gx_ro_wav_orig = grad_interpolate(gx_ro_wav_orig)
gy_ro_wav_orig = grad_interpolate(gy_ro_wav_orig)
gz_ro_wav_orig = grad_interpolate(gz_ro_wav_orig)

# ADC
adc = make_adc(num_samples=max(gx_ro_wav_orig.shape),
               dwell=system.grad_raster_time)

###
# 3. Sequence loop
###
rfphs = 0  # rad
rf_spoil_seed_cnt = 0

ktraj_full = []
for iframe in range(1, fmri['nframes'] + 1):
    print(str(iframe) + ' of ' + str(fmri['nframes']))

    # set kz sampling pattern for this frame
    kz1 = fmri['kzFull']  # numel(kz) = nz

    ktraj_frame = []
예제 #10
0
# ===========

# spoiler
spoil_amp = 0.8 * sys.max_grad  # Hz/m
rise_time = 1.0e-3  # spoiler rise time in seconds
spoil_dur = 6.5e-3  # complete spoiler duration in seconds

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

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

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

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

# Sequence object
seq = Sequence()

# ===
# RUN
# ===

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

for m, offset in enumerate(offsets_hz):
예제 #11
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
예제 #12
0
flipref = rf_flip[0] * math.pi / 180
rfref, gz, _ = make_sinc_pulse(flip_angle=flipref, system=system, duration=t_ref, slice_thickness=slice_thickness,
                               apodization=0.5, time_bw_product=4, phase_offset=rfref_phase, use='refocusing',
                               return_gz=True)
GS_ref = make_trapezoid(channel='z', system=system, amplitude=GS_ex.amplitude, flat_time=tf_ref_wd, rise_time=dG)

AGS_ex = GS_ex.area / 2
GS_spr = make_trapezoid(channel='z', system=system, area=AGS_ex * (1 + fspS), duration=t_sp, rise_time=dG)
GS_spex = make_trapezoid(channel='z', system=system, area=AGS_ex * fspS, duration=t_sp_ex, rise_time=dG)

delta_k = 1 / fov
k_width = Nx * delta_k

GR_acq = make_trapezoid(channel='x', system=system, flat_area=k_width, flat_time=readout_time, rise_time=dG)
adc = make_adc(num_samples=Nx, duration=GR_acq.flat_time - 2 * system.adc_dead_time, delay=20e-6)
GR_spr = make_trapezoid(channel='x', system=system, area=GR_acq.area * fspR, duration=t_sp, rise_time=dG)
GR_spex = make_trapezoid(channel='x', system=system, area=GR_acq.area * (1 + fspR), duration=t_sp_ex, rise_time=dG)

AGR_spr = GR_spr.area
AGR_preph = GR_acq.area / 2 + AGR_spr
GR_preph = make_trapezoid(channel='x', system=system, area=AGR_preph, duration=t_sp_ex, rise_time=dG)

n_ex = 1
PE_order = np.arange(-Ny_pre, Ny + 1).T
phase_areas = PE_order * delta_k

# Split gradients and recombine into blocks
GS1_times = [0, GS_ex.rise_time]
GS1_amp = [0, GS_ex.amplitude]
GS1 = make_extended_trapezoid(channel='z', times=GS1_times, amplitudes=GS1_amp)
예제 #13
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
예제 #14
0
def gre_refscan(seq, meta_file=None, system=Opts(), params=None):

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    delay_end = make_delay(
        d=2)  # 5s delay after reference scan to allow for relaxation
    seq.add_block(delay_end)
    system.max_slew = save_slew
예제 #15
0
GS_ex = make_trapezoid(channel='z', system=system, amplitude=gz.amplitude, flat_time=t_ex_wd, rise_time=dG)

flipref = rf_flip[0] * math.pi / 180
rfref, gz, _ = make_sinc_pulse(flip_angle=flipref, system=system, duration=t_ref, slice_thickness=slice_thickness,
                               apodization=0.5, time_bw_product=4, phase_offset=rfref_phase, use='refocusing')
GS_ref = make_trapezoid(channel='z', system=system, amplitude=GS_ex.amplitude, flat_time=tf_ref_wd, rise_time=dG)

AGS_ex = GS_ex.area / 2
GS_spr = make_trapezoid(channel='z', system=system, area=AGS_ex * (1 + fspS), duration=t_sp, rise_time=dG)
GS_spex = make_trapezoid(channel='z', system=system, area=AGS_ex * fspS, duration=t_sp_ex, rise_time=dG)

delta_k = 1 / fov
k_width = Nx * delta_k

GR_acq = make_trapezoid(channel='x', system=system, flat_area=k_width, flat_time=readout_time, rise_time=dG)
adc = make_adc(num_samples=Nx, duration=GR_acq.flat_time - 40e-6, delay=20e-6)
GR_spr = make_trapezoid(channel='x', system=system, area=GR_acq.area * fspR, duration=t_sp, rise_time=dG)
GR_spex = make_trapezoid(channel='x', system=system, area=GR_acq.area * (1 + fspR), duration=t_sp_ex, rise_time=dG)

AGR_spr = GR_spr.area
AGR_preph = GR_acq.area / 2 + AGR_spr
GR_preph = make_trapezoid(channel='x', system=system, area=AGR_preph, duration=t_sp_ex, rise_time=dG)

n_ex = 1
PE_order = np.arange(-Ny_pre, Ny + 1).T
phase_areas = PE_order * delta_k

GS1_times = [0, GS_ex.rise_time]
GS1_amp = [0, GS_ex.amplitude]
GS1 = make_extended_trapezoid(channel='z', times=GS1_times, amplitudes=GS1_amp)
예제 #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
            num_segments += 1
            while (num_samples/num_segments % 2 != 0):
                num_samples += 2
        else:
            num_samples += 2*num_segments
        segm_dur = 1e5 * dwelltime * num_samples/num_segments 
    print('ADC has to be segmented!! Number of ADCs: {}. Per segment: {}. Segments: {}.'.format(num_samples,num_samples/num_segments,num_segments))

    # self check
    if (num_samples/num_segments % 2 != 0 or num_samples % 2 != 0 or not round(segm_dur,ndigits=5).is_integer()):
        raise ValueError("Check if number of samples and number of samples per segment are even. Check if segment duration is on gradient raster time.")

if num_samples > 65535: # max of uint16 used by ISMRMRD
    raise ValueError("Too many samples for ISMRMRD format - lower the oversampling factor or take more interleaves")

adc = make_adc(system=system, num_samples=num_samples, dwell=dwelltime, delay=system.adc_dead_time)
adc_dur = num_samples * dwelltime
adc_delay = ph.round_up_to_raster(adc_dur+200e-6, decimals=5) # add small delay after readout for ADC frequency reset event and to avoid stimulation by rephaser
adc_delay = make_delay(d=adc_delay)

#%% Set up metadata file for reco and write header

meta_folder = '../../dependency/metadata/'
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,
     calc_duration(gx_pre, max_gy_pre, gz_reph) + calc_duration(gx) / 2) /
    seq.grad_raster_time) * seq.grad_raster_time
delay_TE1 = TE[0] - min_TE
delay_TE2 = TE[1] - TE[0] - calc_duration(gx) - calc_duration(gx_mid)
if delay_TE1 < 0:
    raise ValueError(
        f"TE 1 too small by {1e3*abs(delay_TE1)} ms. Increase readout bandwidth."
    )
if delay_TE2 < 0:
    raise ValueError(
        f"TE 2 too small by {1e3*abs(delay_TE2)} ms. Increase readout bandwidth."
    )

# ADC
adc = make_adc(num_samples=samples,
               dwell=1e-6 * dwelltime_us,
               delay=gx.rise_time,
               system=system)

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

#%% Set up metadata

meta_folder = '../../dependency/metadata/'
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()