Ejemplo n.º 1
0
    def test_get_ktraj(self):
        # Set up gx, gy, adc using known k-space traj - and compare output to it
        fov = 250e-3  # [m]
        dk = 1 / fov  # [m^-1]
        n = 128
        theta = np.random.rand() * 2 * np.pi
        gx = make_trapezoid(channel='x',
                            system=Opts(),
                            flat_area=n * dk * np.cos(theta),
                            flat_time=5e-3,
                            rise_time=0.5e-3)
        gy = make_trapezoid(channel='y',
                            system=Opts(),
                            flat_area=n * dk * np.sin(theta),
                            flat_time=5e-3,
                            rise_time=0.5e-3)
        adc = make_adc(num_samples=n,
                       system=Opts(),
                       duration=gx.flat_area,
                       delay=gx.rise_time)
        ktraj_complex = get_ktraj(gx, gy, adc, display=False)
        # Check: shape, type
        np.testing.assert_equal(ktraj_complex.shape, (n, ))
        self.assertIsInstance(ktraj_complex, np.ndarray)
        self.assertIs(type(ktraj_complex[0]), np.complex128)
        # Check: values

        kx = np.cos(theta) * np.arange(0, n * dk,
                                       dk) + 0.5 * gx.rise_time * gx.amplitude
        ky = np.sin(theta) * np.arange(0, n * dk,
                                       dk) + 0.5 * gy.rise_time * gy.amplitude
        np.testing.assert_array_almost_equal(ktraj_complex, kx + 1j * ky)
Ejemplo n.º 2
0
    def test_modify_gradient(self):
        g1 = make_trapezoid(channel='x',
                            amplitude=5000,
                            duration=1e-3,
                            rise_time=1e-4)
        g1_area = g1.area
        g1_flat = g1.flat_area
        g2 = make_extended_trapezoid(channel='z',
                                     amplitudes=[4000, 5000, 5000, 6000],
                                     times=[0, 1e-3, 6e-3, 7e-3])

        # Use a random scale
        sc = np.random.rand()

        # Modify a trapezoidal gradient
        modify_gradient(g1, scale=sc, channel='z')
        np.testing.assert_equal(g1.amplitude, 5000 * sc)
        np.testing.assert_equal(g1.channel, 'z')
        np.testing.assert_equal(g1.area, g1_area * sc)
        np.testing.assert_equal(g1.flat_area, g1_flat * sc)

        # Modify an arbitrary gradient
        modify_gradient(g2, scale=0, channel='y')
        np.testing.assert_equal(g2.channel, 'y')
        np.testing.assert_equal(np.sum(g2.waveform), 0)
        np.testing.assert_equal(g2.first, 0)
        np.testing.assert_equal(g2.last, 0)
Ejemplo n.º 3
0
    def test_get_ktraj_3d_rew_delay(self):
        # Set up gx, gy, adc using known k-space traj - and compare output to it
        fov = 250e-3  # [m]
        dk = 1 / fov  # [m^-1]
        n = 128
        theta = np.random.rand() * np.pi
        phi = np.random.rand() * 2 * np.pi

        gx = make_trapezoid(channel='x',
                            system=Opts(),
                            flat_area=n * dk * np.sin(theta) * np.cos(phi),
                            flat_time=5e-3,
                            rise_time=0.5e-3)
        gy = make_trapezoid(channel='y',
                            system=Opts(),
                            flat_area=n * dk * np.sin(theta) * np.sin(phi),
                            flat_time=5e-3,
                            rise_time=0.5e-3)
        gz = make_trapezoid(channel='z',
                            system=Opts(),
                            flat_area=n * dk * np.cos(theta),
                            flat_time=5e-3,
                            rise_time=0.5e-3)

        gx_rew = make_trapezoid(channel='x',
                                system=Opts(),
                                area=-gx.area / 2,
                                duration=2.5e-3)
        gy_rew = make_trapezoid(channel='y',
                                system=Opts(),
                                area=-gy.area / 2,
                                duration=2.5e-3)
        gz_rew = make_trapezoid(channel='z',
                                system=Opts(),
                                area=-gz.area / 2,
                                duration=2.5e-3)

        adc_add_delay = 1e-3
        adc = make_adc(num_samples=n,
                       system=Opts(),
                       duration=gx.flat_area,
                       delay=gx.rise_time + adc_add_delay)
        ktraj = get_ktraj_3d_rew_delay(gx, gx_rew, gy, gy_rew, gz, gz_rew, adc)

        # Check: shape, type
        self.assertIsInstance(ktraj, np.ndarray)
        np.testing.assert_equal(ktraj.shape, [n, 3])

        # Check: values
        kx = np.sin(theta) * np.cos(phi) * np.arange(0, n * dk, dk) + \
             0.5 * gx.rise_time * gx.amplitude + gx_rew.area + adc_add_delay * gx.amplitude
        ky = np.sin(theta) * np.sin(phi) * np.arange(0, n * dk, dk) + \
             0.5 * gy.rise_time * gy.amplitude + gy_rew.area + adc_add_delay * gy.amplitude
        kz = np.cos(theta) * np.arange(0, n * dk, dk) + 0.5 * gz.rise_time * gz.amplitude + gz_rew.area + \
             adc_add_delay * gz.amplitude

        np.testing.assert_array_almost_equal(ktraj[:, 0], kx)
        np.testing.assert_array_almost_equal(ktraj[:, 1], ky)
        np.testing.assert_array_almost_equal(ktraj[:, 2], kz)
Ejemplo n.º 4
0
    def test_combine_oblique_radial_readout_2d(self):
        g = make_trapezoid(channel='x',
                           amplitude=5000,
                           duration=1e-3,
                           rise_time=1e-4)
        # Generate random unit gradients and random theta

        theta1 = np.random.rand() * np.pi
        phi1 = np.random.rand() * 2 * np.pi
        ug1 = np.array([
            np.cos(theta1) * np.cos(phi1),
            np.cos(theta1) * np.sin(phi1),
            np.sin(theta1)
        ])

        theta2 = np.random.rand() * np.pi
        phi2 = np.random.rand() * 2 * np.pi
        ug2 = np.array([
            np.cos(theta2) * np.cos(phi2),
            np.cos(theta2) * np.sin(phi2),
            np.sin(theta2)
        ])

        ug2p = ug2 - ug1 * (np.dot(ug1, ug2) / (np.linalg.norm(ug1)**2))
        ug2pn = ug2p / np.linalg.norm(ug2p)

        alpha = np.random.rand() * 2 * np.pi

        # Timing is checked within the function
        gx, gy, gz = combine_oblique_radial_readout_2d(g, ug1, ug2pn, alpha)

        # Check amplitude
        np.testing.assert_almost_equal(
            gx.amplitude,
            5000 * (ug1[0] * np.cos(alpha) + ug2pn[0] * np.sin(alpha)))
        np.testing.assert_almost_equal(
            gy.amplitude,
            5000 * (ug1[1] * np.cos(alpha) + ug2pn[1] * np.sin(alpha)))
        np.testing.assert_almost_equal(
            gz.amplitude,
            5000 * (ug1[2] * np.cos(alpha) + ug2pn[2] * np.sin(alpha)))
Ejemplo n.º 5
0
 def test_make_oblique_gradients(self):
     g = make_trapezoid(channel='x',
                        amplitude=5000,
                        duration=1e-3,
                        rise_time=1e-4)
     theta = np.random.rand() * np.pi
     phi = np.random.rand() * 2 * np.pi
     ug = [
         np.cos(theta) * np.cos(phi),
         np.cos(theta) * np.sin(phi),
         np.sin(theta)
     ]
     gx, gy, gz = make_oblique_gradients(g, ug)
     # Check channel
     np.testing.assert_equal(gx.channel, 'x')
     np.testing.assert_equal(gy.channel, 'y')
     np.testing.assert_equal(gz.channel, 'z')
     # Check amplitude
     np.testing.assert_almost_equal(gx.amplitude, 5000 * ug[0])
     np.testing.assert_almost_equal(gy.amplitude, 5000 * ug[1])
     np.testing.assert_almost_equal(gz.amplitude, 5000 * ug[2])
Ejemplo n.º 6
0
def make_arbitrary_rf(signal: np.ndarray, flip_angle: float, system: Opts = Opts(), freq_offset: float = 0,
                      phase_offset: float = 0, time_bw_product: float = 0, bandwidth: float = 0, max_grad: float = 0,
                      max_slew: float = 0, slice_thickness: float = 0, delay: float = 0,
                      use: str = None) -> SimpleNamespace:
    """
    Creates a radio-frequency pulse event with arbitrary pulse shape and optionally an accompanying slice select
    trapezoidal gradient event.

    Parameters
    ----------
    signal : np.ndarray
        Arbitrary waveform.
    flip_angle : float
        Flip angle in radians.
    system : Opts
        System limits. Default is a system limits object initialised to default values.
    freq_offset : float
        Frequency offset in Hertz (Hz). Default is 0.
    phase_offset : float
        Phase offset in Hertz (Hz). Default is 0.
    time_bw_product : float
        Time-bandwidth product. Default is 4.
    bandwidth : float
        Bandwidth in Hertz (Hz).
     max_grad : float
        Maximum gradient strength of accompanying slice select trapezoidal event. Default is `system`'s `max_grad`.
     max_slew : float
        Maximum slew rate of accompanying slice select trapezoidal event. Default is `system`'s `max_slew`.
    slice_thickness : float
        Slice thickness of accompanying slice select trapezoidal event. The slice thickness determines the area of the
        slice select event.
    delay : float
        Delay in milliseconds (ms) of accompanying slice select trapezoidal event.
    use : str
        Use of arbitrary radio-frequency pulse event. Must be one of 'excitation', 'refocusing' or 'inversion'.

    Returns
    -------
    rf : SimpleNamespace
        Radio-frequency pulse event with arbitrary pulse shape.
    gz : SimpleNamespace
        Slice select trapezoidal gradient event accompanying the arbitrary radio-frequency pulse event.
    """
    valid_use_pulses = ['excitation', 'refocusing', 'inversion']
    if use is not None and use not in valid_use_pulses:
        raise ValueError(
            f'Invalid use parameter. Must be one of excitation, refocusing or inversion. You passed: {use}')

    signal = np.squeeze(signal)
    if signal.ndim > 1:
        raise ValueError(f'signal should have ndim=1. You passed ndim={signal.ndim}')
    signal = signal / np.sum(signal * system.rf_raster_time) * flip_angle / (2 * np.pi)

    N = len(signal)
    duration = N * system.rf_raster_time
    t = np.arange(1, N + 1) * system.rf_raster_time

    rf = SimpleNamespace()
    rf.type = 'rf'
    rf.signal = signal
    rf.t = t
    rf.freq_offset = freq_offset
    rf.phase_offset = phase_offset
    rf.dead_time = system.rf_dead_time
    rf.ringdown_time = system.rf_ringdown_time
    rf.delay = delay

    if use is not None:
        rf.use = use

    if rf.dead_time > rf.delay:
        rf.delay = rf.dead_time

    try:
        if slice_thickness <= 0:
            raise ValueError('Slice thickness must be provided.')
        if bandwidth <= 0:
            raise ValueError('Bandwidth of pulse must be provided.')

        if max_grad > 0:
            system.max_grad = max_grad
        if max_slew > 0:
            system.max_slew = max_slew

        BW = bandwidth
        if time_bw_product > 0:
            BW = time_bw_product / duration

        amplitude = BW / slice_thickness
        area = amplitude * duration
        gz = make_trapezoid(channel='z', system=system, flat_time=duration, flat_area=area)

        if rf.delay > gz.rise_time:
            gz.delay = math.ceil((rf.delay - gz.rise_time) / system.grad_raster_time) * system.grad_raster_time

        if rf.delay < (gz.rise_time + gz.delay):
            rf.delay = gz.rise_time + gz.delay
    except:
        gz = None

    if rf.ringdown_time > 0:
        t_fill = np.arange(1, round(rf.ringdown_time / 1e-6) + 1) * 1e-6
        rf.t = np.concatenate((rf.t, rf.t[-1] + t_fill))
        rf.signal = np.concatenate((rf.signal, np.zeros(len(t_fill))))

    return rf, gz
Ejemplo n.º 7
0
fsp_s = 0.5

rf_ex_phase = np.pi / 2
rf_ref_phase = 0

flip_ex = 90 * np.pi / 180
rf_ex, gz, _ = make_sinc_pulse(flip_angle=flip_ex,
                               system=system,
                               duration=t_ex,
                               slice_thickness=slice_thickness,
                               apodization=0.5,
                               time_bw_product=4,
                               phase_offset=rf_ex_phase)
gs_ex = make_trapezoid(channel='z',
                       system=system,
                       amplitude=gz.amplitude,
                       flat_time=t_exwd,
                       rise_time=dG)

flip_ref = rf_flip[0] * np.pi / 180
rf_ref, gz, _ = make_sinc_pulse(flip_angle=flip_ref,
                                system=system,
                                duration=t_ref,
                                slice_thickness=slice_thickness,
                                apodization=0.5,
                                time_bw_product=4,
                                phase_offset=rf_ref_phase,
                                use='refocusing')
gs_ref = make_trapezoid(channel='z',
                        system=system,
                        amplitude=gs_ex.amplitude,
Ejemplo n.º 8
0
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

blip_dur = math.ceil(2 * math.sqrt(delta_k / system.max_slew) / seq.grad_raster_time / 2) * seq.grad_raster_time * 2
Ejemplo n.º 9
0
def make_gauss_pulse(flip_angle: float, apodization: float = 0, bandwidth: float = 0, center_pos: float = 0.5,
                     delay: float = 0, duration: float = 0, freq_offset: float = 0, max_grad: float = 0,
                     max_slew: float = 0, phase_offset: float = 0, return_gz: bool = False, slice_thickness: float = 0,
                     system: Opts = Opts(), time_bw_product: float = 4,
                     use: str = str()) -> Union[SimpleNamespace,
                                                Tuple[SimpleNamespace, SimpleNamespace, SimpleNamespace]]:
    """
    Creates a radio-frequency gauss pulse event and optionally accompanying slice select and slice select rephasing
    trapezoidal gradient events.

    Parameters
    ----------
    flip_angle : float
        Flip angle in radians.
    apodization : float, optional, default=0
        Apodization.
    bandwidth : float, optional, default=0
        Bandwidth in Hertz (Hz).
    center_pos : float, optional, default=0.5
        Position of peak.
    delay : float, optional
        Delay in milliseconds (ms).
    duration : float, optional, default=0
        Duration in milliseconds (ms).
    freq_offset : float, optional, default=0
        Frequency offset in Hertz (Hz).
    max_grad : float, optional, default=0
        Maximum gradient strength of accompanying slice select trapezoidal event.
    max_slew : float, optional, default=0
        Maximum slew rate of accompanying slice select trapezoidal event.
    phase_offset : float, optional, default=0
        Phase offset in Hertz (Hz).
    return_gz : bool, default=False
        Boolean flag indicating if slice-selective gradient has to be returned.
    slice_thickness : float, optional, default=0
        Slice thickness of accompanying slice select trapezoidal event. The slice thickness determines the area of the
        slice select event.
    system : Opts, optional, default=Opts()
        System limits.
    time_bw_product : int, optional, default=4
        Time-bandwidth product.
    use : str, optional, default=str()
        Use of radio-frequency gauss pulse event. Must be one of 'excitation', 'refocusing' or 'inversion'.

    Returns
    -------
    rf : SimpleNamespace
        Radio-frequency gauss pulse event.
    gz : SimpleNamespace
        Accompanying slice select trapezoidal gradient event.
    gzr : SimpleNamespace
        Accompanying slice select rephasing trapezoidal gradient event.

    Raises
    ------
    ValueError
        If invalid `use` is passed. Must be one of 'excitation', 'refocusing' or 'inversion'.
        If `return_gz=True` and `slice_thickness` was not passed.
    """
    valid_use_pulses = ['excitation', 'refocusing', 'inversion']
    if use != '' and use not in valid_use_pulses:
        raise ValueError(
            f"Invalid use parameter. Must be one of 'excitation', 'refocusing' or 'inversion'. Passed: {use}")

    if bandwidth == 0:
        BW = time_bw_product / duration
    else:
        BW = bandwidth
    alpha = apodization
    N = int(round(duration / 1e-6))
    t = np.arange(1, N + 1) * system.rf_raster_time
    tt = t - (duration * center_pos)
    window = 1 - alpha + alpha * np.cos(2 * np.pi * tt / duration)
    signal = np.multiply(window, __gauss(BW * tt))
    flip = np.sum(signal) * system.rf_raster_time * 2 * np.pi
    signal = signal * flip_angle / flip

    rf = SimpleNamespace()
    rf.type = 'rf'
    rf.signal = signal
    rf.t = t
    rf.freq_offset = freq_offset
    rf.phase_offset = phase_offset
    rf.dead_time = system.rf_dead_time
    rf.ringdown_time = system.rf_ringdown_time
    rf.delay = delay
    if use != '':
        rf.use = use

    if rf.dead_time > rf.delay:
        rf.delay = rf.dead_time

    if return_gz:
        if slice_thickness == 0:
            raise ValueError('Slice thickness must be provided')

        if max_grad > 0:
            system.max_grad = max_grad

        if max_slew > 0:
            system.max_slew = max_slew

        amplitude = BW / slice_thickness
        area = amplitude * duration
        gz = make_trapezoid(channel='z', system=system, flat_time=duration, flat_area=area)
        gzr = make_trapezoid(channel='z', system=system, area=-area * (1 - center_pos) - 0.5 * (gz.area - area))

        if rf.delay > gz.rise_time:
            gz.delay = math.ceil((rf.delay - gz.rise_time) / system.grad_raster_time) * system.grad_raster_time

        if rf.delay < (gz.rise_time + gz.delay):
            rf.delay = gz.rise_time + gz.delay

    if rf.ringdown_time > 0:
        t_fill = np.arange(1, round(rf.ringdown_time / 1e-6) + 1) * 1e-6
        rf.t = np.concatenate((rf.t, rf.t[-1] + t_fill))
        rf.signal = np.concatenate((rf.signal, np.zeros(len(t_fill))))

    # Following 2 lines of code are workarounds for numpy returning 3.14... for np.angle(-0.00...)
    negative_zero_indices = np.where(rf.signal == -0.0)
    rf.signal[negative_zero_indices] = 0

    if return_gz:
        return rf, gz, gzr
    else:
        return rf
Ejemplo n.º 10
0
              rf_dead_time=100e-6)

# ======
# 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
Ejemplo n.º 11
0
def make_block_pulse(flip_angle,
                     system=Opts(),
                     duration=0,
                     freq_offset=0,
                     phase_offset=0,
                     time_bw_product=0,
                     bandwidth=0,
                     max_grad=0,
                     max_slew=0,
                     slice_thickness=0,
                     delay=0,
                     use=''):
    """
    Makes a Holder object for an rf pulse Event.

    Parameters
    ----------
    kwargs : dict
        Key value mappings of rf Event parameters_params and values.
    nargout: int
        Number of output arguments to be returned. Default is 1, only rf Event is returned. Passing any number greater
        than 1 will return the Gz Event along with the rf Event.

    Returns
    -------
    Tuple consisting of:
    rf : Holder
        rf Event configured based on supplied kwargs.
    gz : Holder
        Slice select trapezoidal gradient Event.
    """

    valid_use_pulses = ['excitation', 'refocusing', 'inversion']
    if use not in valid_use_pulses:
        raise Exception()

    if duration == 0:
        if time_bw_product > 0:
            duration = time_bw_product / bandwidth
        elif bandwidth > 0:
            duration = 1 / (4 * bandwidth)
        else:
            raise ValueError('Either bandwidth or duration must be defined')

    BW = 1 / (4 * duration)
    N = round(duration / 1e-6)
    t = np.arange(N) * system.rf_raster_time
    signal = flip_angle / (2 * np.pi) / duration * np.ones(len(t))

    rf = SimpleNamespace()
    rf.type = 'rf'
    rf.signal = signal
    rf.t = t
    rf.freq_offset = freq_offset
    rf.phase_offset = phase_offset
    rf.dead_time = system.rf_dead_time
    rf.ring_down_time = system.rf_ring_down_time

    if use != '':
        rf.use = use

    if rf.dead_time > rf.delay:
        rf.delay = rf.dead_time

    try:
        if slice_thickness < 0:
            raise ValueError('Slice thickness must be provided')

        if max_grad > 0:
            system.max_grad = max_grad
        if max_slew > 0:
            system.max_slew = max_slew

        amplitude = BW / slice_thickness
        area = amplitude * duration
        kwargs_for_trap = {
            'channel': 'z',
            'system': system,
            'flat_time': duration,
            'flat_area': area
        }
        gz = make_trapezoid(kwargs_for_trap)

        if rf.delay > gz.rise_time:
            gz.delay = math.ceil(
                (rf.delay - gz.rise_time) /
                system.grad_raster_time) * system.grad_raster_time

        if rf.delay < (gz.rise_time + gz.delay):
            rf.delay = gz.rise_time + gz.delay
    except:
        gz = None

    if rf.ring_down_time > 0:
        t_fill = np.arange(round(rf.ring_down_time / 1e-6) + 1) * 1e-6
        rf.t = np.concatenate((rf.t, (rf.t[-1] + t_fill)))
        rf.signal = np.concatenate((rf.signal, np.zeros(len(t_fill))))

    return rf, gz
Ejemplo n.º 12
0
def make_arbitrary_rf(flip_angle,
                      system=Opts(),
                      signal=0,
                      freq_offset=0,
                      phase_offset=0,
                      time_bw_product=0,
                      bandwidth=0,
                      max_grad=0,
                      max_slew=0,
                      slice_thickness=0,
                      delay=0,
                      use=None):
    """
    Makes a Holder object for an arbitrary rf pulse Event.

    Parameters
    ----------
    kwargs : dict
        Key value mappings of rf Event parameters_params and values.
    nargout: int
        Number of output arguments to be returned. Default is 1, only rf Event is returned. Passing any number greater
        than 1 will return the Gz Event along with the rf Event.

    Returns
    -------
    rf : Holder
        rf Event configured based on supplied kwargs.
    gz : Holder
        Slice select trapezoidal gradient Event.
    """

    signal = signal / np.sum(
        signal * system.rf_raster_time) * flip_angle / (2 * pi)
    N = len(signal)
    duration = N * system.rf_raster_time
    t = np.arange(1, N=1) * system.rf_raster_time

    rf = SimpleNamespace()
    rf.type = 'rf'
    rf.signal = signal
    rf.t = t
    rf.freq_offset = freq_offset
    rf.phase_offset = phase_offset
    rf.rf_dead_time = system.rf_dead_time
    rf.ringdown_time = system.rf_ringdown_time
    rf.delay = delay
    if use is not None:
        rf.use = use

    try:
        if slice_thickness <= 0:
            raise ValueError('Slice thickness must be provided')
        if bandwidth <= 0:
            raise ValueError('Bandwidth of pulse must be provided')

        if max_grad > 0:
            system.max_grad = max_grad
        if max_slew > 0:
            system.max_slew = max_slew

        BW = bandwidth
        if time_bw_product > 0:
            BW = time_bw_product / duration

        amplitude = BW / slice_thickness
        area = amplitude * duration
        gz = make_trapezoid(channel='z',
                            system=system,
                            flat_time=duration,
                            flat_area=area)

        if rf.delay > gz.rise_time:
            gz.delay = math.ceil(
                (rf.delay - gz.rise_time) /
                system.grad_raster_time) * system.grad_raster_time

        if rf.delay < (gz.rise_time + gz.delay):
            rf.delay = gz.rise_time + gz.delay
    except:
        gz = None

    if rf.ring_down_time > 0:
        t_fill = np.arange(1, round(rf.ringdown_time / 1e-6) + 1) * 1e-6
        rf.t = np.concatenate((rf.t, rf.t[-1] + t_fill))
        rf.signal = np.concatenate((rf.signal, np.zeros(len(t_fill))))

    return rf, gz
Ejemplo n.º 13
0
TR = 180e-3  # in s
BW = 50e3  # Hz
Slicethickness = 3e-3
dt = 4e-6
GradRasterTime = 1 / (2 * BW)  # s

flip = 90 * pi / 180
kwargs_for_sinc = {"flip_angle": flip, "system": system, "duration": 3e-3, "slice_thickness": Slicethickness,
                   "apodization": 0.5, "time_bw_product": 4}
rf, gz = make_sinc_pulse(kwargs_for_sinc, 2)

delta_k = 1 / fov
kWidth = Nx * delta_k
readoutTime = GradRasterTime * 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 = mr.makeAdc(Nx,lims,'Duration',gx.flatTime,'Delay',gx.riseTime)
# CHANGE: Matched ADC parameters with Matlab code
kwargs_for_adc = {"num_samples": Nx, "dwell": 4e-6}
adc = make_adc(kwargs_for_adc)
# kwargs_for_adc = {"num_samples": Nx, "Dwell": dt}
# adc = makeadc(kwargs_for_adc)


count = count + 1
# kwargs_for_gxPre = {"channel": 'x', "system": system, "area": -gx.area/2, "Duration": readoutTime/2}
# gxPre = maketrapezoid(kwargs_for_gxPre)

Ejemplo n.º 14
0
def make_code_sequence(FOV=250e-3,
                       N=64,
                       TR=100e-3,
                       flip=15,
                       enc_type='3D',
                       rf_type='gauss',
                       os_factor=1,
                       save_seq=True):
    """
    3D or 2D (projection) CODE sequence
    (cite! )

    Parameters
    ----------
    FOV : float, default=0.25
        Isotropic image field-of-view in [meters]
    N : int, default=64
        Isotropic image matrix size.
        Also used for base readout sample number (2x that of Nyquist requirement)
    TR : float, default=0.1
        Repetition time in [seconds]
    flip : float, default=15
        Flip angle in [degrees]
    enc_type : str, default='3D'
        Dimensionality of sequence - '3D' or '2D'
    rf_type : str, default='gauss'
        RF pulse shape - 'gauss' or 'sinc'
    os_factor : float, default=1
        Oversampling factor in readout
        The number of readout samples is the nearest integer from os_factor*N
    save_seq : bool, default=True
        Whether to save this sequence as a .seq file

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

    """
    # System options (copied from : amri-sos service form)
    system = Opts(max_grad=32,
                  grad_unit='mT/m',
                  max_slew=130,
                  slew_unit='T/m/s',
                  rf_ringdown_time=30e-6,
                  rf_dead_time=100e-6,
                  adc_dead_time=20e-6)
    # Parameters
    # Radial sampling set-up
    dx = FOV / N
    if enc_type == '3D':
        Ns, Ntheta, Nphi = get_radk_params_3D(dx, FOV)
        # Radial encoding details
        thetas = np.linspace(0, np.pi, Ntheta, endpoint=False)
    elif enc_type == "2D":
        Nphi = get_radk_params_2D(N)
        Ntheta = 1
        Ns = Nphi
        thetas = np.array([np.pi / 2])  # Zero z-gradient.

    phis = np.linspace(0, 2 * np.pi, Nphi, endpoint=False)
    print(
        f'{enc_type} acq.: using {Ntheta} thetas and {Nphi} phis - {Ns} spokes in total.'
    )

    # Make sequence components
    # Slice-selective RF pulse: 100 us, 15 deg gauss pulse
    FA = flip * pi / 180

    rf_dur = 100e-6
    thk_slab = FOV

    if rf_type == 'gauss':
        rf, g_pre, __ = make_gauss_pulse(flip_angle=FA,
                                         duration=rf_dur,
                                         slice_thickness=thk_slab,
                                         system=system,
                                         return_gz=True)
    elif rf_type == 'sinc':
        rf, g_pre, __ = make_sinc_pulse(flip_angle=FA,
                                        duration=rf_dur,
                                        slice_thickness=thk_slab,
                                        apodization=0.5,
                                        time_bw_product=4,
                                        system=system,
                                        return_gz=True)
    else:
        raise ValueError("RF type can only be sinc or gauss")

    # Round off timing to system requirements
    rf.delay = system.grad_raster_time * round(
        rf.delay / system.grad_raster_time)
    g_pre.delay = system.grad_raster_time * round(
        g_pre.delay / system.grad_raster_time)

    # Readout gradient (5 ms readout time)
    #ro_time = 5e-3
    #ro_rise_time = 20e-6

    dr = FOV / N
    kmax = (1 / dr) / 2
    Nro = np.round(os_factor * N)

    # Asymmetry! (0 - fully rewound; 1 - half-echo)
    ro_asymmetry = (kmax - g_pre.area / 2) / (kmax + g_pre.area / 2)
    s = np.round(ro_asymmetry * Nro / 2) / (Nro / 2)
    ro_duration = 2.5e-3
    dkp = (1 / FOV) / (1 + s)
    ro_area = N * dkp
    g_ro = make_trapezoid(channel='x',
                          flat_area=ro_area,
                          flat_time=ro_duration,
                          system=system)
    adc = make_adc(num_samples=Nro,
                   duration=g_ro.flat_time,
                   delay=g_ro.rise_time,
                   system=system)

    # Readout gradient & ADC
    #adc_dwell = 10e-6 # 10 us sampling interval (100 KHz readout bandwidth)
    #g_ro = make_trapezoid(channel='x', system=system, amplitude=dk/adc_dwell, flat_time=adc_dwell*int(N/2), rise_time=ro_rise_time)
    #flat_delay = (0.5*g_pre.area - 0.5*ro_rise_time*g_ro.amplitude) / g_ro.amplitude
    #flat_delay = system.grad_raster_time * round(flat_delay / system.grad_raster_time)
    # Make sure the first ADC is at center of k-space.
    #adc = make_adc(system=system, num_samples=Nro, dwell = adc_dwell, delay = g_ro.rise_time+flat_delay)

    # Delay
    TRfill = TR - calc_duration(g_pre) - calc_duration(g_ro)
    delayTR = make_delay(TRfill)

    #TE = 0.5 * calc_duration(g_pre) + adc.delay
    TE = 0.5 * calc_duration(g_pre) + g_ro.rise_time + adc.dwell * Nro / 2 * (
        1 - s)
    print(f'TE obtained: {TE*1e3} ms')

    # Initiate storage of trajectory
    ktraj = np.zeros([Ns, int(adc.num_samples), 3])

    extra_delay_time = 0

    # Construct sequence
    # Initiate sequence
    seq = Sequence(system)
    u = 0
    # For each direction
    for phi in phis:
        for theta in thetas:
            # Construct oblique gradient
            ug = get_3d_unit_grad(theta, phi)
            g_pre_x, g_pre_y, g_pre_z = make_oblique_gradients(gradient=g_pre,
                                                               unit_grad=-1 *
                                                               ug)
            g_ro_x, g_ro_y, g_ro_z = make_oblique_gradients(gradient=g_ro,
                                                            unit_grad=ug)
            # Add components
            seq.add_block(rf, g_pre_x, g_pre_y, g_pre_z)
            seq.add_block(make_delay(extra_delay_time))
            seq.add_block(g_ro_x, g_ro_y, g_ro_z, adc)
            seq.add_block(delayTR)

            # Store trajectory
            gpxh, gpyh, gpzh = make_oblique_gradients(gradient=g_pre,
                                                      unit_grad=-0.5 * ug)
            ktraj[u, :, :] = get_ktraj_3d_rew_delay(g_ro_x, gpxh, g_ro_y, gpyh,
                                                    g_ro_z, gpzh, adc)
            u += 1

    # Display sequence plot
    #seq.plot(time_range=[-TR/2,1.5*TR])
    # Test sequence validity
    #out_text = seq.test_report()
    #print(out_text)
    # Save sequence
    if save_seq:
        seq.write(
            f'seqs/code{enc_type}_{rf_type}_TR{TR*1e3:.0f}_TE{TE*1e3:.2f}_FA{flip}_N{N}_delay{extra_delay_time*1e3}ms.seq'
        )
        savemat(
            f'seqs/ktraj_code{enc_type}_{rf_type}_TR{TR*1e3:.0f}_TE{TE*1e3:.2f}_FA{flip}_N{N}.mat',
            {'ktraj': ktraj})

    return seq, TE, ktraj
Ejemplo n.º 15
0
# scanner limits
sys = Opts(max_grad=40, grad_unit='mT/m', max_slew=130, slew_unit='T/m/s',
           rf_ringdown_time=30e-6, rf_dead_time=100e-6, rf_raster_time=1e-6)

gamma_hz = 42.5764

# ===========
# PREPARATION
# ===========

# 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()
Ejemplo n.º 16
0
def make_block_pulse(flip_angle: float, bandwidth: float = 0, delay: float = 0, duration: float = 0,
                     freq_offset: float = 0, max_grad: float = 0, max_slew: float = 0, phase_offset: float = 0,
                     return_gz: bool = False, system: Opts = Opts(), slice_thickness: float = 0,
                     time_bw_product: float = 0,
                     use: str = str()) -> Union[SimpleNamespace, Tuple[SimpleNamespace, SimpleNamespace]]:
    """
    Creates a radio-frequency block pulse event and optionally an accompanying slice select trapezoidal gradient event.

    Parameters
    ----------
    flip_angle : float
        Flip angle in radians.
    bandwidth : float, default=0
        Bandwidth in Hertz (hz).
    delay : float, default=0
        Delay in milliseconds (ms) of accompanying slice select trapezoidal event.
    duration : float, default=0
        Duration in milliseconds (ms).
    freq_offset : float, default=0
        Frequency offset in Hertz (Hz).
    max_grad : float, default=0
        Maximum gradient strength of accompanying slice select trapezoidal event.
    max_slew : float, default=0
        Maximum slew rate of accompanying slice select trapezoidal event.
    phase_offset : float, default=0
        Phase offset Hertz (Hz).
    return_gz : bool, default=False
        Boolean flag to indicate if slice-selective gradient has to be returned.
    slice_thickness : float, default=0
        Slice thickness of accompanying slice select trapezoidal event. The slice thickness determines the area of the
        slice select event.
    system : Opts, default=Opts()
        System limits.
    time_bw_product : float, default=0
        Time-bandwidth product.
    use : str, default=str()
        Use of radio-frequency block pulse event. Must be one of 'excitation', 'refocusing' or 'inversion'.

    Returns
    -------
    rf : SimpleNamespace
        Radio-frequency block pulse event.
    gz : SimpleNamespace
        Slice select trapezoidal gradient event accompanying the radio-frequency block pulse event.

    Raises
    ------
    ValueError
        If invalid `use` parameter is passed. Must be one of 'excitation', 'refocusing' or 'inversion'.
        If neither `bandwidth` nor `duration` are passed.
        If `return_gz=True`, and `slice_thickness` is not passed.
    """
    valid_use_pulses = ['excitation', 'refocusing', 'inversion']
    if use != '' and use not in valid_use_pulses:
        raise ValueError(
            f"Invalid use parameter. Must be one of 'excitation', 'refocusing' or 'inversion'. Passed: {use}")

    if duration == 0:
        if time_bw_product > 0:
            duration = time_bw_product / bandwidth
        elif bandwidth > 0:
            duration = 1 / (4 * bandwidth)
        else:
            raise ValueError('Either bandwidth or duration must be defined')

    BW = 1 / (4 * duration)
    N = round(duration / 1e-6)
    t = np.arange(1, N + 1) * system.rf_raster_time
    signal = flip_angle / (2 * np.pi) / duration * np.ones(len(t))

    rf = SimpleNamespace()
    rf.type = 'rf'
    rf.signal = signal
    rf.t = t
    rf.freq_offset = freq_offset
    rf.phase_offset = phase_offset
    rf.dead_time = system.rf_dead_time
    rf.ringdown_time = system.rf_ringdown_time
    rf.delay = delay

    if use != '':
        rf.use = use

    if rf.dead_time > rf.delay:
        rf.delay = rf.dead_time

    if return_gz:
        if slice_thickness < 0:
            raise ValueError('Slice thickness must be provided')

        if max_grad > 0:
            system.max_grad = max_grad
        if max_slew > 0:
            system.max_slew = max_slew

        amplitude = BW / slice_thickness
        area = amplitude * duration
        gz = make_trapezoid(channel='z', system=system, flat_time=duration, flat_area=area)

        if rf.delay > gz.rise_time:
            gz.delay = math.ceil((rf.delay - gz.rise_time) / system.grad_raster_time) * system.grad_raster_time

        if rf.delay < (gz.rise_time + gz.delay):
            rf.delay = gz.rise_time + gz.delay

    if rf.ringdown_time > 0:
        t_fill = np.arange(1, round(rf.ringdown_time / 1e-6) + 1) * 1e-6
        rf.t = np.concatenate((rf.t, (rf.t[-1] + t_fill)))
        rf.signal = np.concatenate((rf.signal, np.zeros(len(t_fill))))

    if return_gz:
        return rf, gz
    else:
        return rf
Ejemplo n.º 17
0
Nx = 256
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()
Ejemplo n.º 18
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
Ejemplo n.º 19
0
def make_sinc_pulse_channel(channel: str,
                            flip_angle: float,
                            system: Opts = Opts(),
                            duration: float = 0,
                            freq_offset: float = 0,
                            phase_offset: float = 0,
                            time_bw_product: float = 4,
                            apodization: float = 0,
                            center_pos: float = 0.5,
                            max_grad: float = 0,
                            max_slew: float = 0,
                            slice_thickness: float = 0,
                            delay: float = 0,
                            use: str = None):
    """
    Creates a radio-frequency sinc pulse event and optionally accompanying slice select and slice select rephasing
    trapezoidal gradient events.

    Parameters
    ----------
    flip_angle : float
        Flip angle in radians.
    system : Opts, optional
        System limits. Default is a system limits object initialised to default values.
    duration : float, optional
        Duration in milliseconds (ms). Default is 0.
    freq_offset : float, optional
        Frequency offset in Hertz (Hz). Default is 0.
    phase_offset : float, optional
        Phase offset in Hertz (Hz). Default is 0.
    time_bw_product : float, optional
        Time-bandwidth product. Default is 4.
    apodization : float, optional
        Apodization. Default is 0.
    center_pos : float, optional
        Position of peak. Default is 0.5 (midway).
     max_grad : float, optional
        Maximum gradient strength of accompanying slice select trapezoidal event. Default is 0.
     max_slew : float, optional
        Maximum slew rate of accompanying slice select trapezoidal event. Default is 0.
    slice_thickness : float, optional
        Slice thickness of accompanying slice select trapezoidal event. The slice thickness determines the area of the
        slice select event. Default is 0.
    delay : float, optional
        Delay in milliseconds (ms). Default is 0.
    use : str, optional
        Use of radio-frequency sinc pulse. Must be one of 'excitation', 'refocusing' or 'inversion'.

    Returns
    -------
    rf : SimpleNamespace
        Radio-frequency sinc pulse event.
    gz : SimpleNamespace, optional
        Accompanying slice select trapezoidal gradient event. Returned only if `slice_thickness` is provided.
    gzr : SimpleNamespace, optional
        Accompanying slice select rephasing trapezoidal gradient event. Returned only if `slice_thickness` is provided.
    """
    valid_use_pulses = ['excitation', 'refocusing', 'inversion']
    if use is not None and use not in valid_use_pulses:
        raise ValueError(
            f'Invalid use parameter. Must be one of excitation, refocusing or inversion. You passed: {use}'
        )

    if channel not in ['x', 'y', 'z']:
        raise ValueError()

    BW = time_bw_product / duration
    alpha = apodization
    N = int(round(duration / 1e-6))
    t = np.arange(1, N + 1) * system.rf_raster_time
    tt = t - (duration * center_pos)
    window = 1 - alpha + alpha * np.cos(2 * np.pi * tt / duration)
    signal = np.multiply(window, np.sinc(BW * tt))
    flip = np.sum(signal) * system.rf_raster_time * 2 * np.pi
    signal = signal * flip_angle / flip

    rf = SimpleNamespace()
    rf.type = 'rf'
    rf.signal = signal
    rf.t = t
    rf.freq_offset = freq_offset
    rf.phase_offset = phase_offset
    rf.dead_time = system.rf_dead_time
    rf.ringdown_time = system.rf_ringdown_time
    rf.delay = delay
    if use is not None:
        rf.use = use

    if rf.dead_time > rf.delay:
        rf.delay = rf.dead_time

    try:
        if slice_thickness == 0:
            raise ValueError('Slice thickness must be provided')

        if max_grad > 0:
            system.max_grad = max_grad

        if max_slew > 0:
            system.max_slew = max_slew

        amplitude = BW / slice_thickness
        area = amplitude * duration
        g = make_trapezoid(channel=channel,
                           system=system,
                           flat_time=duration,
                           flat_area=area)
        gr = make_trapezoid(channel=channel,
                            system=system,
                            area=-area * (1 - center_pos) - 0.5 *
                            (g.area - area))

        if rf.delay > g.rise_time:
            g.delay = math.ceil(
                (rf.delay - g.rise_time) /
                system.grad_raster_time) * system.grad_raster_time

        if rf.delay < (g.rise_time + g.delay):
            rf.delay = g.rise_time + g.delay
    except:
        g = None
        gr = None

    if rf.ringdown_time > 0:
        t_fill = np.arange(1, round(rf.ringdown_time / 1e-6) + 1) * 1e-6
        rf.t = np.concatenate((rf.t, rf.t[-1] + t_fill))
        rf.signal = np.concatenate((rf.signal, np.zeros(len(t_fill))))

    # Following 2 lines of code are workarounds for numpy returning 3.14... for np.angle(-0.00...)
    negative_zero_indices = np.where(rf.signal == -0.0)
    rf.signal[negative_zero_indices] = 0

    return rf, g, gr
Ejemplo n.º 20
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
Ejemplo n.º 21
0
def opt_TE_bv_SE(bvalue_Dict, grads_times_Dict, seq_sys_Dict):
    """
    Obtain optimal TE and diffusion-weighting gradient waveforms for twice-refocued spin echo (TRSE) DW sequence.

    Parameters
    ----------
    bvalue_Dict : dictionary
                  b-value related parameters needed in the TE optimization.

    grads_times_Dict : dictionary
                       gradient times related parameters needed in the TE optimization.

    seq_sys_Dict : dictionary
                   sequence and system related parameters needed in the TE optimization.

    Returns
    -------
    n_TE : int
	   echo time in integers units.

    bval : float
           b-value in s/mm2

    gdiff : pypulseq gradient
	    diffusion-weighting gradient waveform.

    n_delay_te1 : int
	          delay between RF90 and RF180 in integers units.

    n_delay_te2 : int
	          delay between RF180 and EPI readout in integers units.
    """

    # Description
    # For S&T monopolar waveforms.
    # From an initial TE, check we satisfy all constraints -> otherwise increase TE.
    # Once all constraints are okay -> check b-value, if it is lower than the target one -> increase TE
    # Looks time-inefficient but it is fast enough to make it user-friendly.

    # We need to compute the exact time sequence. For the normal SE-MONO-EPI sequence micro second differences
    # are not important, however, if we wanna import external gradients the allocated time for them needs to
    # be the same, and thus exact timing is mandatory. With this in mind, we establish the following rounding rules:
    # Duration of RFs + spoiling, and EPI time to the center of the k-space is always math.ceil().

    bvalue = bvalue_Dict["bvalue"]  # Target b-value.
    nbvals = bvalue_Dict["nbvals"]  # number of b-values.
    gscl = bvalue_Dict["gscl"]  # gradient scaling.

    n_rf90r = grads_times_Dict[
        "n_rf90r"]  # Half right duration of RF90 (integer).
    n_rf180r = grads_times_Dict[
        "n_rf180r"]  # Half right duration of the RF180 (integer).
    n_rf180l = grads_times_Dict[
        "n_rf180l"]  # Half left duration of the RF180 (integer).
    gz_spoil = grads_times_Dict[
        "gz_spoil"]  # Spoil gradient to be placed around the RF180.
    gz180 = grads_times_Dict["gz180"]  # RF180 simultaneous gradient.
    n_duration_center = grads_times_Dict[
        "n_duration_center"]  # Time needed to achieve the center of the k-space (integer).

    seq = seq_sys_Dict["seq"]  # Sequence
    system = seq_sys_Dict["system"]  # System
    i_raster_time = seq_sys_Dict[
        "i_raster_time"]  # Manually inputed inverse raster time.

    # Find minimum TE considering the readout times.
    n_TE = math.ceil(40e-3 / seq.grad_raster_time)
    n_delay_te2 = -1
    while n_delay_te2 <= 0:
        n_TE = n_TE + 2

        n_tINV = math.floor(n_TE / 2)
        n_delay_te2 = n_tINV - n_rf180r - n_duration_center

    # Find minimum TE for the target b-value
    bvalue_tmp = 0
    while bvalue_tmp < np.max(bvalue):
        n_TE = n_TE + 2

        n_tINV = math.floor(n_TE / 2)
        n_delay_te1 = n_tINV - n_rf90r - n_rf180l
        n_delay_te2 = n_tINV - n_rf180r - n_duration_center

        # Waveform Ramp time
        n_gdiff_rt = math.ceil(system.max_grad / system.max_slew /
                               seq.grad_raster_time)

        # Select the shortest available time
        n_gdiff_delta = min(n_delay_te1, n_delay_te2)
        n_gdiff_Delta = n_delay_te1 + 2 * math.ceil(
            calc_duration(gz_spoil) / seq.grad_raster_time) + math.ceil(
                calc_duration(gz180) / seq.grad_raster_time)

        gdiff = make_trapezoid(channel='x',
                               system=system,
                               amplitude=system.max_grad,
                               duration=n_gdiff_delta / i_raster_time)

        # delta only corresponds to the rectangle.
        n_gdiff_delta = n_gdiff_delta - 2 * n_gdiff_rt

        bv = calc_bval(system.max_grad, n_gdiff_delta / i_raster_time,
                       n_gdiff_Delta / i_raster_time,
                       n_gdiff_rt / i_raster_time)
        bval = bv * 1e-6

    # Show final TE and b-values:
    print("TE:", round(n_TE / i_raster_time * 1e3, 2), "ms")
    for bv in range(1, nbvals + 1):
        print(
            round(
                calc_bval(system.max_grad * gscl[bv], n_gdiff_delta /
                          i_raster_time, n_gdiff_Delta / i_raster_time,
                          n_gdiff_rt / i_raster_time) * 1e-6, 2), "s/mm2")

    return n_TE, bval, gdiff, n_delay_te1, n_delay_te2
Ejemplo n.º 22
0
def sigpy_n_seq(
    flip_angle: float,
    apodization: float = 0,
    delay: float = 0,
    duration: float = 0,
    freq_offset: float = 0,
    center_pos: float = 0.5,
    max_grad: float = 0,
    max_slew: float = 0,
    phase_offset: float = 0,
    return_gz: bool = True,
    slice_thickness: float = 0,
    system: Opts = Opts(),
    time_bw_product: float = 4,
    pulse_cfg: pulse_opts = pulse_opts(),
    use: str = str()
) -> Union[SimpleNamespace, Tuple[SimpleNamespace, SimpleNamespace,
                                  SimpleNamespace]]:
    """
    Creates a radio-frequency sinc pulse event using the sigpy rf pulse library and optionally accompanying slice select, slice select rephasing
    trapezoidal gradient events.

    Parameters
    ----------
    flip_angle : float
        Flip angle in radians.
    apodization : float, optional, default=0
        Apodization.
    center_pos : float, optional, default=0.5
        Position of peak.5 (midway).
    delay : float, optional, default=0
        Delay in milliseconds (ms).
    duration : float, optional, default=0
        Duration in milliseconds (ms).
    freq_offset : float, optional, default=0
        Frequency offset in Hertz (Hz).
    max_grad : float, optional, default=0
        Maximum gradient strength of accompanying slice select trapezoidal event.
    max_slew : float, optional, default=0
        Maximum slew rate of accompanying slice select trapezoidal event.
    phase_offset : float, optional, default=0
        Phase offset in Hertz (Hz).
    return_gz:bool, default=False
        Boolean flag to indicate if slice-selective gradient has to be returned.
    slice_thickness : float, optional, default=0
        Slice thickness of accompanying slice select trapezoidal event. The slice thickness determines the area of the
        slice select event.
    system : Opts, optional
        System limits. Default is a system limits object initialised to default values.
    time_bw_product : float, optional, default=4
        Time-bandwidth product.
    use : str, optional, default=str()
        Use of radio-frequency sinc pulse. Must be one of 'excitation', 'refocusing' or 'inversion'.

    Returns
    -------
    rf : SimpleNamespace
        Radio-frequency sinc pulse event.
    gz : SimpleNamespace, optional
        Accompanying slice select trapezoidal gradient event. Returned only if `slice_thickness` is provided.
    gzr : SimpleNamespace, optional
        Accompanying slice select rephasing trapezoidal gradient event. Returned only if `slice_thickness` is provided.

    Raises
    ------
    ValueError
        If invalid `use` parameter was passed. Must be one of 'excitation', 'refocusing' or 'inversion'.
        If `return_gz=True` and `slice_thickness` was not provided.
    """

    valid_use_pulses = ['excitation', 'refocusing', 'inversion']
    if use != '' and use not in valid_use_pulses:
        raise ValueError(
            f"Invalid use parameter. Must be one of 'excitation', 'refocusing' or 'inversion'. Passed: {use}"
        )

    if pulse_cfg.pulse_type == 'slr':
        [signal, t, pulse] = make_slr(flip_angle=flip_angle,
                                      time_bw_product=time_bw_product,
                                      duration=duration,
                                      system=system,
                                      pulse_cfg=pulse_cfg,
                                      disp=True)
    if pulse_cfg.pulse_type == 'sms':
        [signal, t, pulse] = make_sms(flip_angle=flip_angle,
                                      time_bw_product=time_bw_product,
                                      duration=duration,
                                      system=system,
                                      pulse_cfg=pulse_cfg,
                                      disp=True)

    rfp = SimpleNamespace()
    rfp.type = 'rf'
    rfp.signal = signal
    rfp.t = t
    rfp.freq_offset = freq_offset
    rfp.phase_offset = phase_offset
    rfp.dead_time = system.rf_dead_time
    rfp.ringdown_time = system.rf_ringdown_time
    rfp.delay = delay

    if use != '':
        rfp.use = use

    if rfp.dead_time > rfp.delay:
        rfp.delay = rfp.dead_time

    if return_gz:
        if slice_thickness == 0:
            raise ValueError('Slice thickness must be provided')

        if max_grad > 0:
            system.max_grad = max_grad

        if max_slew > 0:
            system.max_slew = max_slew
        BW = time_bw_product / duration
        amplitude = BW / slice_thickness
        area = amplitude * duration
        gz = make_trapezoid(channel='z',
                            system=system,
                            flat_time=duration,
                            flat_area=area)
        gzr = make_trapezoid(channel='z',
                             system=system,
                             area=-area * (1 - center_pos) - 0.5 *
                             (gz.area - area))

        if rfp.delay > gz.rise_time:
            gz.delay = math.ceil(
                (rfp.delay - gz.rise_time) /
                system.grad_raster_time) * system.grad_raster_time

        if rfp.delay < (gz.rise_time + gz.delay):
            rfp.delay = gz.rise_time + gz.delay

    if rfp.ringdown_time > 0:
        t_fill = np.arange(1, round(rfp.ringdown_time / 1e-6) + 1) * 1e-6
        rfp.t = np.concatenate((rfp.t, rfp.t[-1] + t_fill))
        rfp.signal = np.concatenate((rfp.signal, np.zeros(len(t_fill))))

    # Following 2 lines of code are workarounds for numpy returning 3.14... for np.angle(-0.00...)
    negative_zero_indices = np.where(rfp.signal == -0.0)
    rfp.signal[negative_zero_indices] = 0

    if return_gz:
        return rfp, gz, gzr, pulse
    else:
        return rfp
Ejemplo n.º 23
0
def make_arbitrary_rf(
    signal: np.ndarray,
    flip_angle: float,
    bandwidth: float = 0,
    delay: float = 0,
    freq_offset: float = 0,
    max_grad: float = 0,
    max_slew: float = 0,
    phase_offset: float = 0,
    return_gz: bool = False,
    slice_thickness: float = 0,
    system: Opts = Opts(),
    time_bw_product: float = 0,
    use: str = str()
) -> Union[SimpleNamespace, Tuple[SimpleNamespace, SimpleNamespace]]:
    """
    Creates a radio-frequency pulse event with arbitrary pulse shape and optionally an accompanying slice select
    trapezoidal gradient event.

    Parameters
    ----------
    signal : numpy.ndarray
        Arbitrary waveform.
    flip_angle : float
        Flip angle in radians.
    bandwidth : float, default=0
        Bandwidth in Hertz (Hz).
    delay : float, default=0
        Delay in milliseconds (ms) of accompanying slice select trapezoidal event.
    freq_offset : float, default=0
        Frequency offset in Hertz (Hz).
    max_grad : float, default=system.max_grad
        Maximum gradient strength of accompanying slice select trapezoidal event.
    max_slew : float, default=system.max_slew
        Maximum slew rate of accompanying slice select trapezoidal event.
    phase_offset : float, default=0
        Phase offset in Hertz (Hz).a
    return_gz : bool, default=False
        Boolean flag to indicate if slice-selective gradient has to be returned.
    slice_thickness : float, default=0
        Slice thickness of accompanying slice select trapezoidal event. The slice thickness determines the area of the
        slice select event.
    system : Opts, default=Opts()
        System limits.
    time_bw_product : float, default=4
        Time-bandwidth product.
    use : str, default=str()
        Use of arbitrary radio-frequency pulse event. Must be one of 'excitation', 'refocusing' or 'inversion'.

    Returns
    -------
    rf : SimpleNamespace
        Radio-frequency pulse event with arbitrary pulse shape.
    gz : SimpleNamespace, if return_gz=True
        Slice select trapezoidal gradient event accompanying the arbitrary radio-frequency pulse event.

    Raises
    ------
    ValueError
        If invalid `use` parameter is passed. Must be one of 'excitation', 'refocusing' or 'inversion'.
        If `signal` with ndim > 1 is passed.
        If `return_gz=True`, and `slice_thickness` and `bandwith` are not passed.
    """
    valid_use_pulses = ['excitation', 'refocusing', 'inversion']
    if use != '' and use not in valid_use_pulses:
        raise ValueError(
            f"Invalid use parameter. Must be one of 'excitation', 'refocusing' or 'inversion'. Passed: {use}"
        )

    signal = np.squeeze(signal)
    if signal.ndim > 1:
        raise ValueError(
            f'signal should have ndim=1. Passed ndim={signal.ndim}')
    signal = signal / np.sum(
        signal * system.rf_raster_time) * flip_angle / (2 * np.pi)

    N = len(signal)
    duration = N * system.rf_raster_time
    t = np.arange(1, N + 1) * system.rf_raster_time

    rf = SimpleNamespace()
    rf.type = 'rf'
    rf.signal = signal
    rf.t = t
    rf.freq_offset = freq_offset
    rf.phase_offset = phase_offset
    rf.dead_time = system.rf_dead_time
    rf.ringdown_time = system.rf_ringdown_time
    rf.delay = delay

    if use != '':
        rf.use = use

    if rf.dead_time > rf.delay:
        rf.delay = rf.dead_time

    if return_gz:
        if slice_thickness <= 0:
            raise ValueError('Slice thickness must be provided.')
        if bandwidth <= 0:
            raise ValueError('Bandwidth of pulse must be provided.')

        if max_grad > 0:
            system.max_grad = max_grad
        if max_slew > 0:
            system.max_slew = max_slew

        BW = bandwidth
        if time_bw_product > 0:
            BW = time_bw_product / duration

        amplitude = BW / slice_thickness
        area = amplitude * duration
        gz = make_trapezoid(channel='z',
                            system=system,
                            flat_time=duration,
                            flat_area=area)

        if rf.delay > gz.rise_time:
            gz.delay = math.ceil(
                (rf.delay - gz.rise_time) /
                system.grad_raster_time) * system.grad_raster_time

        if rf.delay < (gz.rise_time + gz.delay):
            rf.delay = gz.rise_time + gz.delay

    if rf.ringdown_time > 0:
        t_fill = np.arange(1, round(rf.ringdown_time / 1e-6) + 1) * 1e-6
        rf.t = np.concatenate((rf.t, rf.t[-1] + t_fill))
        rf.signal = np.concatenate((rf.signal, np.zeros(len(t_fill))))

    return rf, gz if return_gz else rf
              rf_ringdown_time=20e-6,
              rf_dead_time=100e-6,
              adc_dead_time=10e-6)

# Fat saturation
if fatsat_enable:
    b0 = 1.494
    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)

# Create 90 degree slice selection pulse and gradient
rf, gz, _ = make_sinc_pulse(flip_angle=math.pi / 2,
                            system=system,
                            duration=3e-3,
                            slice_thickness=slice_thickness,
                            apodization=0.5,
                            time_bw_product=4)

# Refocusing pulse with spoiling gradients
rf180, gz180, _ = make_sinc_pulse(flip_angle=math.pi,
                                  system=system,
                                  duration=5e-3,
                                  slice_thickness=slice_thickness,
Ejemplo n.º 25
0
    "time_bw_product": 4
}
rf, gz = make_sinc_pulse(kwargs_for_sinc, 2)
# plt.plot(rf.t[0], rf.signal[0])
# plt.show()

delta_k = 1 / fov
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)
Ejemplo n.º 26
0
def make_sinc_pulse(flip_angle,
                    system=Opts(),
                    duration=0,
                    freq_offset=0,
                    phase_offset=0,
                    time_bw_product=4,
                    bandwidth=0,
                    apodization=0,
                    center_pos=0.5,
                    max_grad=0,
                    max_slew=0,
                    slice_thickness=0,
                    delay=0,
                    use=None):
    """
    Makes a Holder object for an rf pulse Event.

    Parameters
    ----------
    kwargs : dict
        Key value mappings of rf Event parameters_params and values.
    nargout: int
        Number of output arguments to be returned. Default is 1, only rf Event is returned. Passing any number greater
        than 1 will return the Gz Event along with the rf Event.

    Returns
    -------
    rf : Holder
        rf Event configured based on supplied kwargs.
    gz : Holder
        Slice select trapezoidal gradient Event.
    """

    if bandwidth == 0:
        BW = time_bw_product / duration
    else:
        BW = bandwidth
    alpha = apodization
    N = int(round(duration / 1e-6))
    t = np.arange(1, N + 1) * system.rf_raster_time
    tt = t - (duration * center_pos)
    window = 1 - alpha + alpha * np.cos(2 * np.pi * tt / duration)
    signal = np.multiply(window, __gauss(BW * tt))
    flip = np.sum(signal) * system.rf_raster_time * 2 * np.pi
    signal = signal * flip_angle / flip

    rf = SimpleNamespace()
    rf.type = 'rf'
    rf.signal = signal
    rf.t = t
    rf.freq_offset = freq_offset
    rf.phase_offset = phase_offset
    rf.dead_time = system.rf_dead_time
    rf.ring_down_time = system.rf_ringdown_time
    rf.delay = delay
    if use is not None:
        rf.use = use

    if rf.dead_time > rf.delay:
        rf.delay = rf.dead_time

    try:
        if slice_thickness == 0:
            raise ValueError('Slice thickness must be provided')

        if max_grad > 0:
            system.max_grad = max_grad

        if max_slew > 0:
            system.max_slew = max_slew

        amplitude = BW / slice_thickness
        area = amplitude * duration
        gz = make_trapezoid(channel='z',
                            system=system,
                            flat_time=duration,
                            flat_area=area)
        gzr = make_trapezoid(channel='z',
                             system=system,
                             area=-area * (1 - center_pos) - 0.5 *
                             (gz.area - area))

        if rf.delay > gz.rise_time:
            gz.delay = math.ceil(
                (rf.delay - gz.rise_time) /
                system.grad_raster_time) * system.grad_raster_time

        if rf.delay < (gz.rise_time + gz.delay):
            rf.delay = gz.rise_time + gz.delay
    except:
        gz = None
        gzr = None

    if rf.ring_down_time > 0:
        t_fill = np.arange(1, round(rf.ring_down_time / 1e-6) + 1) * 1e-6
        rf.t = np.concatenate((rf.t, rf.t[-1] + t_fill))
        rf.signal = np.concatenate((rf.signal, np.zeros(len(t_fill))))

    # Following 2 lines of code are workarounds for numpy returning 3.14... for np.angle(-0.00...)
    negative_zero_indices = np.where(rf.signal == -0.0)
    rf.signal[negative_zero_indices] = 0

    return rf, gz, gzr
Ejemplo n.º 27
0
t_ex = 2.5e-3
t_ex_wd = t_ex + system.rf_ringdown_time + system.rf_dead_time
t_ref = 2e-3
tf_ref_wd = t_ref + system.rf_ringdown_time + system.rf_dead_time
t_sp = 0.5 * (TE - readout_time - tf_ref_wd)
t_sp_ex = 0.5 * (TE - t_ex_wd - tf_ref_wd)
fspR = 1.0
fspS = 0.5

rfex_phase = math.pi / 2
rfref_phase = 0

flipex = 90 * math.pi / 180
rfex, gz, _ = make_sinc_pulse(flip_angle=flipex, system=system, duration=t_ex, slice_thickness=slice_thickness,
                              apodization=0.5, time_bw_product=4, phase_offset=rfex_phase)
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)
Ejemplo n.º 28
0
    # Sequence Parameters
    FOV = 200e-3  # [m]
    Ny = 128
    Nstartup = 11

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

    # Calculate Crushers
    gzSpoil_INV = make_trapezoid('z',
                                 area=-3e3,
                                 duration=9.5e-3,
                                 system=system)
    FlatArea_crusher = [0, 0.4e3, 0.32e3, 0.27e3, 0.32e3, 0, 0.53e3, 0.32e3]
    FlatTime_crusher = [0, 5e-4, 6.3e-4, 6.3e-4, 6.3e-4, 0, 9.3e-4, 6.3e-4]
    crushers_in_between = 1

    # Create adiabatic inversion pulse
    rf_inv = make_hyperSec_pulse(system=system, duration=10.24 * 1e-3)
    InvDur = rf_inv.t[-1]  # duration

    Tdelay_trig = 550e-3  # [s]
    trig = make_trigger(delay=100e-6,
                        duration=Tdelay_trig - (Nstartup + 1) * TR,
                        system=system)
    trig_BetweenInversion = make_trigger(delay=100e-6,
                                         duration=Tdelay_trig,
    # unit to [Hz/m], make spiral gradients
    sp_x *= system.gamma
    sp_y *= system.gamma

    spirals[k]['spiral'][0] = make_arbitrary_grad(channel='x', waveform=sp_x, delay=system.adc_dead_time, system=system)
    spirals[k]['spiral'][1] = make_arbitrary_grad(channel='y', waveform=sp_y, delay=system.adc_dead_time, system=system)

    if spiraltype==1:
        # calculate rephaser area
        area_x = sp_x.sum()*system.grad_raster_time
        area_y = sp_y.sum()*system.grad_raster_time

        # calculate rephasers and make gradients
        amp_x, ftop_x, ramp_x = ph.trap_from_area(-area_x, system, slewrate = 100) # reduce slew rate to 100 T/m/s to avoid stimulation
        amp_y, ftop_y, ramp_y = ph.trap_from_area(-area_y, system, slewrate = 100)
        spirals[k]['reph'][0] = make_trapezoid(channel='x', system=system, amplitude=amp_x, flat_time=ftop_x, rise_time=ramp_x)
        spirals[k]['reph'][1] = make_trapezoid(channel='y', system=system, amplitude=amp_y, flat_time=ftop_y, rise_time=ramp_y)
        reph_dur.append(max(ftop_x+2*ramp_x, ftop_y+2*ramp_y))


# check for acoustic resonances (checks only spirals)
freq_max = ph.check_resonances([spiral_x,spiral_y])

#%% Gradient Spoiler on slice axis

spoiler_area = 1.5*gz.flat_area - gz.area/2 # 1.5x moment under excitation pulse
amp_spoil, ftop_spoil, ramp_spoil = ph.trap_from_area(spoiler_area, system, slewrate=100) # reduce slew rate to 100 T/m/s to avoid stimulation
spoiler_z = make_trapezoid(channel='z',system=system, amplitude=amp_spoil, flat_time=ftop_spoil, rise_time=ramp_spoil, delay=100e-6)
spoiler_dur = calc_duration(spoiler_z)
reph_delay = spoiler_dur - np.max(reph_dur)
Ejemplo n.º 30
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