Esempio n. 1
0
def make_gauss_hanning(flip_angle: float,
                       pulse_duration: float,
                       system: Opts = Opts())\
        -> SimpleNamespace:
    """
    Creates a radio-frequency pulse event for a gauss pulse with hanning window.
    :param flip_angle: flip_angle of the rf pulse
    :param pulse_duration: rf pulse duration [s]
    :param system: system limits of the MR scanner
    """

    rf_pulse = make_gauss_pulse(flip_angle=flip_angle,
                                duration=pulse_duration,
                                system=system,
                                phase_offset=0)
    # n_signal = np.sum(np.abs(rf_pulse.signal) > 0)
    n_signal = rf_pulse.signal.size
    # hanning_shape = hanning(n_signal + 2)
    hanning_shape = hanning(n_signal)
    # rf_pulse.signal[:n_signal] = hanning_shape[1:-1] / np.trapz(rf_pulse.t[:n_signal], hanning_shape[1:-1]) * \
    #                              (flip_angle / (2 * np.pi))
    rf_pulse.signal = hanning_shape / np.trapz(
        hanning_shape, x=rf_pulse.t) * (flip_angle / (2 * np.pi))
    return rf_pulse
# RF spoiling parameters
rf_spoiling_inc = 50 # increment of RF spoiling [°]
rf_phase        = 0 
rf_inc          = 0

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

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

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

#%% Spiral Readout Gradients

# Parameters spiral trajectory:

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

# nitlv:      number of spiral interleaves        15
Esempio n. 3
0
#gradient scaling
gscl=np.sqrt(np.linspace(0., 1., nbvals+1))
gdir, nb0s = difunc.get_dirs(ndirs)

#to avoid exceeding gradient limits
gdfact = 1.0

tr_per_slice = tr / n_slices

system = Opts(max_grad=32, grad_unit='mT/m', max_slew=130, slew_unit='T/m/s', rf_ringdown_time=30e-6,
              rf_dead_time=100e-6, adc_dead_time=20e-6)

b0 = 2.89
sat_ppm = -3.45
sat_freq = sat_ppm * 1e-6 * b0 * system.gamma
rf_fs, _, _ = make_gauss_pulse(flip_angle=110 * math.pi / 180, system=system, duration=8e-3, bandwidth=abs(sat_freq),
                               freq_offset=sat_freq)
gz_fs = make_trapezoid(channel='z', system=system, delay=calc_duration(rf_fs), area=1 / 1e-4)

rf, gz, gz_reph = make_sinc_pulse(flip_angle=math.pi / 2, system=system, duration=3e-3, slice_thickness=slice_thickness,
                                  apodization=0.5, time_bw_product=4)

rf180, gz180, _ =  make_sinc_pulse(flip_angle=math.pi, system=system, duration=5e-3, slice_thickness=slice_thickness,
                            apodization=0.5, time_bw_product=4)
rf180.phase_offset = math.pi/2

gz_spoil = make_trapezoid(channel='z', system=system, area=2*gz.area, duration=3e-3)

delta_k = 1 / fov
k_width = Nx * delta_k
readout_time = 1.05e-3
Esempio n. 4
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
Esempio n. 5
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