Esempio n. 1
0
def align(*args) -> list:
    """
    Aligns `SimpleNamespace` blocks as per specified alignment options by setting delays of the pulse sequence events
    within the block. All previously configured delays within objects are taken into account during calculating of the
    block duration but then reset according to the selected alignment. Possible values for align_spec are 'left',
    'center', 'right'.

    Parameters
    ----------
    args : list
        List of alignment options and `SimpleNamespace` blocks.
        Template: [alignment_spec, 'SimpleNamespace` block, [alignment_spec, `SimpleNamespace` block, ...]].
        Alignment spec can be one of `left`, `center` or `right`.

    Returns
    -------
    objects : list
        List of aligned `SimpleNamespace` blocks.
    """
    alignment_options = ['left', 'center', 'right']
    if not isinstance(args[0], str):
        raise ValueError('First parameter must be of type str.')

    curr_align = alignment_options.index(
        args[0]) if args[0] in alignment_options else None

    i_objects = []
    alignments = []
    for i in range(1, len(args)):
        if curr_align is None:
            raise ValueError('Invalid alignment spec.')
        if isinstance(args[i], str):
            curr_align = alignment_options.index(
                args[i]) if args[i] in alignment_options else None
            continue
        i_objects.append(i)
        alignments.append(curr_align)

    args = np.array(args)
    objects = args[i_objects]
    dur = calc_duration(*objects)

    for i in range(len(objects)):
        if alignments[i] == 0:
            objects[i].delay = 0
        elif alignments[i] == 1:
            objects[i].delay = (dur - calc_duration(objects[i])) / 2
        elif alignments[i] == 2:
            objects[i].delay = dur - calc_duration(
                objects[i]) + objects[i].delay

    return objects
Esempio n. 2
0
    def gradient_waveforms(self):
        duration, num_blocks, _ = self.duration()

        wave_length = math.ceil(duration / self.grad_raster_time)
        grad_channels = 3
        grad_waveforms = np.zeros((grad_channels, wave_length))
        grad_channels = ['gx', 'gy', 'gz']

        t0 = 0
        t0_n = 0
        for i in range(num_blocks):
            block = self.get_block(i + 1)
            for j in range(len(grad_channels)):
                if hasattr(block, grad_channels[j]):
                    grad = getattr(block, grad_channels[j])
                    if grad.type == 'grad':
                        nt_start = round(
                            (grad.delay + grad.t[0]) / self.grad_raster_time)
                        waveform = grad.waveform
                    else:
                        nt_start = round(grad.delay / self.grad_raster_time)
                        if abs(grad.flat_time) > np.finfo(float).eps:
                            t = np.cumsum([
                                0, grad.rise_time, grad.flat_time,
                                grad.fall_time
                            ])
                            trap_form = np.multiply([0, 1, 1, 0],
                                                    grad.amplitude)
                        else:
                            t = np.cumsum([0, grad.rise_time, grad.fall_time])
                            trap_form = np.multiply([0, 1, 0], grad.amplitude)

                        tn = math.floor(t[-1] / self.grad_raster_time)
                        t = np.append(t, t[-1] + self.grad_raster_time)
                        trap_form = np.append(trap_form, 0)

                        if abs(grad.amplitude) > np.finfo(float).eps:
                            waveform = points_to_waveform(
                                t, trap_form, self.grad_raster_time)
                        else:
                            waveform = np.zeros(tn + 1)

                    if waveform.size != np.sum(np.isfinite(waveform)):
                        raise Warning(
                            'Not all elements of the generated waveform are finite'
                        )

                    grad_waveforms[
                        j,
                        int(t0_n +
                            nt_start):int(t0_n + nt_start +
                                          max(waveform.shape))] = waveform

            t0 += calc_duration(block)
            t0_n = round(t0 / self.grad_raster_time)

        return grad_waveforms
Esempio n. 3
0
    def duration(self):
        num_blocks = len(self.block_events)
        event_count = np.zeros(len(self.block_events[1]))
        duration = 0
        for i in range(num_blocks):
            block = self.get_block(i + 1)
            event_count += self.block_events[i + 1] > 0
            duration += calc_duration(block)

        return duration, num_blocks, event_count
Esempio n. 4
0
def _SAR_from_seq(
        seq: Sequence, Qtmf: np.ndarray,
        Qhmf: np.ndarray) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
    """
    Compute global whole body and head only SAR values for the given `seq` object.

    Parameters
    ----------
    seq : Sequence
        Sequence object to calculate for which SAR values will be calculated.
    Qtmf : numpy.ndarray
        Q-matrix of global SAR values for body-mass.
    Qhmf : numpy.ndarray
        Q-matrix of global SAR values for head-mass.

    Returns
    -------
    SAR_wbg : numpy.ndarray
        SAR values for body-mass.
    SAR_hg : numpy.ndarray
        SAR values for head-mass.
    t : numpy.ndarray
        Corresponding time points.
    """
    # Identify RF blocks and compute SAR - 10 seconds must be less than twice and 6 minutes must be less than
    # 4 (WB) and 3.2 (head-20)
    block_events = seq.dict_block_events
    num_events = len(block_events)
    t = np.zeros(num_events)
    SAR_wbg = np.zeros(t.shape)
    SAR_hg = np.zeros(t.shape)
    t_prev = 0

    for block_counter in block_events:
        block = seq.get_block(block_counter)
        block_dur = calc_duration(block)
        t[block_counter - 1] = t_prev + block_dur
        t_prev = t[block_counter - 1]
        if hasattr(block, 'rf'):  # has rf
            rf = block.rf
            signal = rf.signal
            # This rf could be parallel transmit as well
            SAR_wbg[block_counter] = _calc_SAR(Qtmf, signal)
            SAR_hg[block_counter] = _calc_SAR(Qhmf, signal)

    return SAR_wbg, SAR_hg, t
Esempio n. 5
0
def SARfromseq(fname, Qtmf, Qhmf):
    """
    This definition computes the global whole body and head only SAR values

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

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

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

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

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

    return SARwbg_vec, SARhg_vec, t_vec
Esempio n. 6
0
def check_timing(system, *events):
    total_dur = calc_duration(*events)
    is_ok = __div_check(total_dur, system.grad_raster_time)
    if is_ok:
        text_err = str()
    else:
        text_err = f'Total duration: {total_dur * 1e6} us'

    for i in range(len(events)):
        e = events[i]
        ok = True
        if hasattr(e, 'type') and e.type == 'adc' or e.type == 'rf':
            raster = system.rf_raster_time
        else:
            raster = system.grad_raster_time

        if hasattr(e, 'delay'):
            if not __div_check(e.delay, raster):
                ok = False

        if hasattr(e, 'type') and e.type == 'trap':
            if not __div_check(e.rise_time, system.grad_raster_time) or \
                    not __div_check(e.flat_time, system.grad_raster_time) or \
                    not __div_check(e.fall_time, system.grad_raster_time):
                ok = False

        if not ok:
            is_ok = False
            if len(text_err) != 0:
                text_err += ' '

            text_err += '[ '
            if hasattr(e, 'type'):
                text_err += f'type: {e.type} '
            if hasattr(e, 'delay'):
                text_err += f'delay: {e.delay * 1e6} us '
            if hasattr(e, 'type') and e.type == 'trap':
                text_err += f'rise time: {e.rise_time * 1e6} flat time: {e.flat_time * 1e6} ' \
                    f'fall time: {e.fall_time * 1e6} us '
            text_err += ']'

    return is_ok, text_err
Esempio n. 7
0
def __SAR_from_seq(seq, Qtmf, Qhmf):
    """
    Compute global whole body and head only SAR values.

    Parameters
    ----------
    seq : Sequence
    Qtmf : numpy.ndarray
    Qhmf : numpy.ndarray

    Returns
    -------
    SAR_wbg_vec : numpy.ndarray
    SAR_hg_vec : numpy.ndarray
    t_vec : numpy.ndarray
        Contains the Q-matrix, GSAR head and body for now.
    """

    # Identify RF blocks and compute SAR - 10 seconds must be less than twice and 6 minutes must be less than
    # 4 (WB) and 3.2 (head-20)
    block_events = seq.block_events
    num_events = len(block_events)
    t_vec = np.zeros(num_events)
    SAR_wbg_vec = np.zeros(t_vec.shape)
    SAR_hg_vec = np.zeros(t_vec.shape)
    t_prev = 0

    for iB in block_events:
        block = seq.get_block(iB)
        block_dur = calc_duration(block)
        t_vec[iB - 1] = t_prev + block_dur
        t_prev = t_vec[iB - 1]
        if hasattr(block, 'rf'):  # has rf
            rf = block.rf
            t = rf.t
            signal = rf.signal
            # This rf could be parallel transmit as well
            SAR_wbg_vec[iB] = __calc_SAR(Qtmf, signal)
            SAR_hg_vec[iB] = __calc_SAR(Qhmf, signal)

    return SAR_wbg_vec, SAR_hg_vec, t_vec
Esempio n. 8
0
    def duration(self):
        """
        Get duration of this sequence.

        Returns
        -------
        duration : float
            Duration of this sequence in millis.
        num_blocks : int
            Number of blocks in this sequence.
        event_count : int
            Number of events in this sequence.
        """

        num_blocks = len(self.block_events)
        event_count = np.zeros(len(self.block_events[1]))
        duration = 0
        for i in range(num_blocks):
            block = self.get_block(i + 1)
            event_count += self.block_events[i + 1] > 0
            duration += calc_duration(block)

        return duration, num_blocks, event_count
Esempio n. 9
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
Esempio n. 10
0
def opt_TE_bv_TRSE(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
    -------
    TE : float
       	 echo time (s).

    bval : float
     	   b-value in s/mm2

    waveform : float
               diffusion-weighting gradient waveform.

    gscl_max : float
               gradient scale factor of the diffusion-weighting gradient waveform.

    d1 : float
         duration of the first diffusion-weighting gradient lobe between the RF90 and the first RF180 (s).

    d2 : float
         duration of the second diffusion-weighting gradient lobe after the first RF180 (s).

    d3 : float
         duration of the third diffusion-weighting gradient lobe before the second RF180 (s).

    d4 : float
         duration of the last diffusion-weighting gradient lobe after the second RF180 (s).
    """

    # Description
    # For TRSE 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
    # Finally, with TRSE low b-values can not be acquired, thus proper scaling is needed.
    # Looks time-inefficient but it is fast enough to make it user-friendly.

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

    rf180 = grads_times_Dict["rf180"]  # RF180
    rf180_center_with_delay = grads_times_Dict[
        "rf180_center_with_delay"]  # time to the center of the RF180
    rf_center_with_delay = grads_times_Dict[
        "rf_center_with_delay"]  # time to the center of the RF90
    gz_spoil_1 = grads_times_Dict[
        "gz_spoil_1"]  # Spoil gradient to be placed around the first RF180
    gz_spoil_2 = grads_times_Dict[
        "gz_spoil_2"]  # Spoil gradient to be placed around the second RF180
    gz = grads_times_Dict["gz"]  # RF90 gradient
    duration_center = grads_times_Dict[
        "duration_center"]  # Time needed to achieve the center of the k-space.
    pre_time = grads_times_Dict["pre_time"]  # (s)

    seq = seq_sys_Dict["seq"]  # Sequence
    system = seq_sys_Dict["system"]  # System

    # Find minimum TE considering the readout times and RF + spoil gradient durations
    TE = 80e-3  # [s] It's a reasonable estimate for TRSE.
    delay_tela = -1  # Large TE/2
    while delay_tela <= 0:
        TE = TE + 0.02e-3  # [ms]
        delay_tela = math.ceil((TE / 2 + (- calc_duration(rf180) + rf180_center_with_delay - calc_duration(gz_spoil_2) - \
                          duration_center) + (- calc_duration(gz) + rf_center_with_delay - \
                          pre_time - calc_duration(gz_spoil_1) - rf180_center_with_delay)) / seq.grad_raster_time) * seq.grad_raster_time

    # Sice there are 3 equations and 4 parameters (TGReese2002 - https://doi.org/10.1002/mrm.10308)
    # One parameter can be tuned, while the other 3 are then fixed. This is why we fix d4.
    d4 = 3e-3  # [ms]

    # Find minimum TE for the target d4
    # Waveform Ramp time
    gdiff_rt = math.ceil(system.max_grad / system.max_slew /
                         seq.grad_raster_time) * seq.grad_raster_time
    d1 = -1
    while d1 <= 2 * gdiff_rt:  # Include this condition to have trapezoids everywhere
        TE = TE + 2 * seq.grad_raster_time  # [ms]
        # Large TE/2
        delay_tela = math.ceil((TE / 2 + (- calc_duration(rf180) + rf180_center_with_delay - calc_duration(gz_spoil_2) - \
                          duration_center) + (- calc_duration(gz) + rf_center_with_delay - \
                          pre_time - calc_duration(gz_spoil_1) - rf180_center_with_delay)) / seq.grad_raster_time) * seq.grad_raster_time

        # Short TE/2 (Time between RF180s)
        delay_tes = math.ceil((TE / 2 - calc_duration(rf180) + rf180_center_with_delay - calc_duration(gz_spoil_1) - \
                               calc_duration(gz_spoil_2) - rf180_center_with_delay) / seq.grad_raster_time) * seq.grad_raster_time

        d1 = math.ceil(
            (delay_tela - d4) / seq.grad_raster_time) * seq.grad_raster_time

    # Find minimum TE for the target b-value
    bvalue_tmp = 0

    # We initially substract 2 times the raster time because we don't know if it will achieve the desired b-value from the beginning.
    # Note that for low b-values, the TE is the same for all of them due to the large number of elements of this sequence.
    TE = TE - 2 * seq.grad_raster_time
    while bvalue_tmp < np.max(bvalue):
        # The following scalar multiplication is just to increase speed
        TE = TE + int(math.ceil(
            np.max(bvalue) / 500)) * 4 * seq.grad_raster_time  # [ms]

        # Large TE/2
        delay_tela = math.ceil((TE / 2 + (- calc_duration(rf180) + rf180_center_with_delay - calc_duration(gz_spoil_2) - \
                        duration_center) + (- calc_duration(gz) + rf_center_with_delay - \
                        pre_time - calc_duration(gz_spoil_1) - rf180_center_with_delay)) / seq.grad_raster_time) * seq.grad_raster_time

        # Short TE/2 (Time between RF180s)
        delay_tes = math.ceil((TE / 2 - calc_duration(rf180) + rf180_center_with_delay - calc_duration(gz_spoil_1) - \
                 calc_duration(gz_spoil_2) - rf180_center_with_delay) / seq.grad_raster_time) * seq.grad_raster_time

        d1 = math.ceil(
            (delay_tela - d4) / seq.grad_raster_time) * seq.grad_raster_time
        d3 = math.ceil(((d1 + delay_tes - d4) / 2) /
                       seq.grad_raster_time) * seq.grad_raster_time
        d2 = math.ceil(
            (delay_tes - d3) / seq.grad_raster_time) * seq.grad_raster_time

        # Due to the complexity of the sequence and that I have not found its b-value, I implement it by its definition.
        # However, for simplicity I compute each gradient lobe as ideal rectangular pulses.

        # d1 start after the RF90
        n1 = int(
            math.ceil((calc_duration(gz) - rf_center_with_delay + pre_time +
                       seq.grad_raster_time) / seq.grad_raster_time) *
            seq.grad_raster_time / seq.grad_raster_time)
        nd1 = int(
            math.ceil(d1 / seq.grad_raster_time) * seq.grad_raster_time /
            seq.grad_raster_time)

        # d2 start after the first RF180
        n2 = int(
            math.ceil(((n1 + nd1) * seq.grad_raster_time +
                       2 * calc_duration(gz_spoil_1) + calc_duration(rf180) +
                       seq.grad_raster_time) / seq.grad_raster_time) *
            seq.grad_raster_time / seq.grad_raster_time)
        nd2 = int(
            math.ceil(d2 / seq.grad_raster_time) * seq.grad_raster_time /
            seq.grad_raster_time)

        # d3 starts after d2
        n3 = int(
            math.ceil(
                ((n2 + nd2) * seq.grad_raster_time + seq.grad_raster_time) /
                seq.grad_raster_time) * seq.grad_raster_time /
            seq.grad_raster_time)
        nd3 = int(
            math.ceil(d3 / seq.grad_raster_time) * seq.grad_raster_time /
            seq.grad_raster_time)

        # d4 starts after the second RF180
        n4 = int(
            math.ceil(((n2 + nd2 + nd3) * seq.grad_raster_time +
                       2 * calc_duration(gz_spoil_2) + calc_duration(rf180) +
                       seq.grad_raster_time) / seq.grad_raster_time) *
            seq.grad_raster_time / seq.grad_raster_time)
        nd4 = int(
            math.ceil(d4 / seq.grad_raster_time) * seq.grad_raster_time /
            seq.grad_raster_time)

        # Due to the large TE of TRSE and the high resolution we might need to shrink the following array.
        # To speed up computation we calculate the b-value with short_f times lower of the time resolution.

        # Shortening factor:
        short_f = 5
        n = int(np.ceil(TE / seq.grad_raster_time) / short_f)
        waveform = np.zeros(n)

        # Compose waveform
        waveform[math.ceil(n1 / short_f):math.floor((n1 + nd1 + 1) /
                                                    short_f)] = system.max_grad
        waveform[math.ceil(n2 /
                           short_f):math.floor((n2 + nd2 + 1) /
                                               short_f)] = -system.max_grad
        waveform[math.ceil(n3 / short_f):math.floor((n3 + nd3 + 1) /
                                                    short_f)] = system.max_grad
        waveform[math.ceil(n4 /
                           short_f):math.floor((n4 + nd4 + 1) /
                                               short_f)] = -system.max_grad

        # Include ramps
        nrt = int(
            math.floor(
                math.floor(
                    system.max_grad / system.max_slew / seq.grad_raster_time) *
                seq.grad_raster_time / seq.grad_raster_time / short_f))
        ramp_values = np.linspace(
            1, nrt, nrt) * seq.grad_raster_time * system.max_slew * short_f
        # d1
        if n1 % short_f == 0:
            waveform[math.ceil(n1 / short_f):math.ceil(n1 / short_f) +
                     nrt] = ramp_values
        else:
            waveform[math.ceil(n1 / short_f):math.ceil((n1 + 1) / short_f) +
                     nrt] = ramp_values

        waveform[math.ceil((n1 + nd1 - 1) / short_f -
                           nrt):math.ceil((n1 + nd1 - 1) /
                                          short_f)] = np.flip(ramp_values)

        # d2
        if n2 % short_f == 0:
            waveform[math.ceil(n2 / short_f):math.ceil(n2 / short_f) +
                     nrt] = -ramp_values
        else:
            waveform[math.ceil(n2 / short_f):math.ceil((n2 + 1) / short_f) +
                     nrt] = -ramp_values

        waveform[math.ceil((n2 + nd2 - 1) / short_f -
                           nrt):math.ceil((n2 + nd2 - 1) /
                                          short_f)] = -np.flip(ramp_values)

        # d3
        if n3 % short_f == 0:
            waveform[math.ceil(n3 / short_f):math.ceil(n3 / short_f) +
                     nrt] = ramp_values
        else:
            waveform[math.ceil(n3 / short_f):math.ceil((n3 + 1) / short_f) +
                     nrt] = ramp_values

        waveform[math.ceil((n3 + nd3 - 1) / short_f -
                           nrt):math.ceil((n3 + nd3 - 1) /
                                          short_f)] = np.flip(ramp_values)

        # d4
        if n4 % short_f == 0:
            waveform[math.ceil(n4 / short_f):math.ceil(n4 / short_f) +
                     nrt] = -ramp_values
        else:
            waveform[math.ceil(n4 / short_f):math.ceil((n4 + 1) / short_f) +
                     nrt] = -ramp_values

        waveform[math.ceil((n4 + nd4 - 1) / short_f -
                           nrt):math.ceil((n4 + nd4 - 1) /
                                          short_f)] = -np.flip(ramp_values)

        # Prepare Integral
        nRF180_1 = int(
            math.ceil((n2 * seq.grad_raster_time - calc_duration(rf180) +
                       rf180_center_with_delay - calc_duration(gz_spoil_1)) /
                      short_f / seq.grad_raster_time) * seq.grad_raster_time /
            seq.grad_raster_time)
        nRF180_2 = int(
            math.ceil((n4 * seq.grad_raster_time - calc_duration(rf180) +
                       rf180_center_with_delay - calc_duration(gz_spoil_2)) /
                      short_f / seq.grad_raster_time) * seq.grad_raster_time /
            seq.grad_raster_time)
        INV = np.ones(n)
        INV[nRF180_1:nRF180_2 + 1] = -1

        bvalue_tmp = calc_exact_bval(waveform, INV, short_f, seq)

    print("b-value low time resolution:", round(bvalue_tmp, 2), "s/mm2")

    # To know the exact b-value - repeat the above with full time resolution
    short_f = 1
    n = int(np.ceil(TE / seq.grad_raster_time) / short_f)
    waveform = np.zeros(n)

    # Compose waveform
    waveform[math.ceil(n1 / short_f):math.floor((n1 + nd1 + 1) /
                                                short_f)] = system.max_grad
    waveform[math.ceil(n2 / short_f):math.floor((n2 + nd2 + 1) /
                                                short_f)] = -system.max_grad
    waveform[math.ceil(n3 / short_f):math.floor((n3 + nd3 + 1) /
                                                short_f)] = system.max_grad
    waveform[math.ceil(n4 / short_f):math.floor((n4 + nd4 + 1) /
                                                short_f)] = -system.max_grad

    # Include ramps
    nrt = int(
        math.floor(
            math.floor(
                system.max_grad / system.max_slew / seq.grad_raster_time) *
            seq.grad_raster_time / seq.grad_raster_time / short_f))
    ramp_values = np.linspace(
        0, nrt, nrt) * seq.grad_raster_time * system.max_slew * short_f
    # d1
    if n1 % short_f == 0:
        waveform[math.ceil(n1 / short_f):math.ceil(n1 / short_f) +
                 nrt] = ramp_values
    else:
        waveform[math.ceil(n1 / short_f):math.ceil((n1 + 1) / short_f) +
                 nrt] = ramp_values

    if (n1 + nd1) % short_f == 0:
        waveform[math.ceil((n1 + nd1) / short_f - nrt) +
                 1:math.floor((n1 + nd1 + 1) / short_f)] = np.flip(ramp_values)
    else:
        waveform[math.ceil((n1 + nd1) / short_f -
                           nrt):math.floor((n1 + nd1 + 1) /
                                           short_f)] = np.flip(ramp_values)

    # d2
    if n2 % short_f == 0:
        waveform[math.ceil(n2 / short_f):math.ceil(n2 / short_f) +
                 nrt] = -ramp_values
    else:
        waveform[math.ceil(n2 / short_f):math.ceil((n2 + 1) / short_f) +
                 nrt] = -ramp_values

    if (n2 + nd2) % short_f == 0:
        waveform[math.ceil((n2 + nd2) / short_f - nrt) +
                 1:math.floor((n2 + nd2 + 1) /
                              short_f)] = -np.flip(ramp_values)
    else:
        waveform[math.ceil((n2 + nd2) / short_f -
                           nrt):math.floor((n2 + nd2 + 1) /
                                           short_f)] = -np.flip(ramp_values)

    # d3
    if n3 % short_f == 0:
        waveform[math.ceil(n3 / short_f):math.ceil(n3 / short_f) +
                 nrt] = ramp_values
    else:
        waveform[math.ceil(n3 / short_f):math.ceil((n3 + 1) / short_f) +
                 nrt] = ramp_values

    if (n3 + nd3) % short_f == 0:
        waveform[math.ceil((n3 + nd3) / short_f - nrt) +
                 1:math.floor((n3 + nd3 + 1) / short_f)] = np.flip(ramp_values)
    else:
        waveform[math.ceil((n3 + nd3) / short_f -
                           nrt):math.floor((n3 + nd3 + 1) /
                                           short_f)] = np.flip(ramp_values)

    # d4
    if n4 % short_f == 0:
        waveform[math.ceil(n4 / short_f):math.ceil(n4 / short_f) +
                 nrt] = -ramp_values
    else:
        waveform[math.ceil(n4 / short_f):math.ceil((n4 + 1) / short_f) +
                 nrt] = -ramp_values

    if (n4 + nd4) % short_f == 0:
        waveform[math.ceil((n4 + nd4) / short_f - nrt) +
                 1:math.floor((n4 + nd4 + 1) /
                              short_f)] = -np.flip(ramp_values)
    else:
        waveform[math.ceil((n4 + nd4) / short_f -
                           nrt):math.floor((n4 + nd4 + 1) /
                                           short_f)] = -np.flip(ramp_values)

    # Prepare Integral
    nRF180_1 = int(
        math.ceil((n2 * seq.grad_raster_time - calc_duration(rf180) +
                   rf180_center_with_delay - calc_duration(gz_spoil_1)) /
                  short_f / seq.grad_raster_time) * seq.grad_raster_time /
        seq.grad_raster_time)
    nRF180_2 = int(
        math.ceil((n4 * seq.grad_raster_time - calc_duration(rf180) +
                   rf180_center_with_delay - calc_duration(gz_spoil_2)) /
                  short_f / seq.grad_raster_time) * seq.grad_raster_time /
        seq.grad_raster_time)
    INV = np.ones(n)
    INV[nRF180_1:nRF180_2 + 1] = -1

    bval = calc_exact_bval(waveform, INV, short_f, seq)

    print("b-value high time resolution:", round(bval, 2), "s/mm2")

    # Scale gradients amplitude accordingly.
    if bval > np.max(bvalue):
        gscl_max = np.sqrt(np.max(bvalue) / bval)
    else:
        gscl_max = 1

    # Show final TE and b-values:
    print("Final times:")
    print("TE:", round(TE * 1e3, 2), "ms")
    for bv in range(1, nbvals + 1):
        bval_tmp = calc_exact_bval(waveform * gscl_max * gscl[bv], INV,
                                   short_f, seq)
        print(round(bval_tmp, 2), "s/mm2")

    return TE, bval, waveform, gscl_max, d1, d2, d3, d4
Esempio n. 11
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
Esempio n. 12
0
def split_gradient(grad: SimpleNamespace,
                   system: Opts = Opts()) -> Tuple[SimpleNamespace, SimpleNamespace, SimpleNamespace]:
    """
    Split gradient waveform `grad` into two gradient waveforms at the center.

    Parameters
    ----------
    grad : array_like
        Gradient waveform to be split into two gradient waveforms.
    system : Opts, optional, default=Opts()
        System limits.

    Returns
    -------
    grad1, grad2 : numpy.ndarray
        Split gradient waveforms.

    Raises
    ------
    ValueError
         If arbitrary gradients are passed.
         If non-gradient event is passed.
    """
    grad_raster_time = system.grad_raster_time
    total_length = calc_duration(grad)

    if grad.type == 'trap':
        ch = grad.channel
        grad.delay = round(grad.delay / grad_raster_time) * grad_raster_time
        grad.rise_time = round(grad.rise_time / grad_raster_time) * grad_raster_time
        grad.flat_time = round(grad.flat_time / grad_raster_time) * grad_raster_time
        grad.fall_time = round(grad.fall_time / grad_raster_time) * grad_raster_time

        times = [0, grad.rise_time]
        amplitudes = [0, grad.amplitude]
        ramp_up = make_extended_trapezoid(channel=ch, system=system, times=times, amplitudes=amplitudes,
                                          skip_check=True)
        ramp_up.delay = grad.delay

        times = [0, grad.fall_time]
        amplitudes = [grad.amplitude, 0]
        ramp_down = make_extended_trapezoid(channel=ch, system=system, times=times, amplitudes=amplitudes,
                                            skip_check=True)
        ramp_down.delay = total_length - grad.fall_time
        ramp_down.t = ramp_down.t * grad_raster_time

        flat_top = SimpleNamespace()
        flat_top.type = 'grad'
        flat_top.channel = ch
        flat_top.delay = grad.delay + grad.rise_time
        flat_top.t = np.arange(step=grad_raster_time,
                               stop=ramp_down.delay - grad_raster_time - grad.delay - grad.rise_time)
        flat_top.waveform = grad.amplitude * np.ones(len(flat_top.t))
        flat_top.first = grad.amplitude
        flat_top.last = grad.amplitude

        return ramp_up, flat_top, ramp_down
    elif grad.type == 'grad':
        raise ValueError('Splitting of arbitrary gradients is not implemented yet.')
    else:
        raise ValueError('Splitting of unsupported event.')
              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,
Esempio n. 14
0
    def calculate_kspace(self, trajectory_delay=0):
        c_excitation = 0
        c_refocusing = 0
        c_adc_samples = 0

        for i in range(len(self.block_events)):
            block = self.get_block(i + 1)
            if hasattr(block, 'rf'):
                if not hasattr(block.rf,
                               'use') or block.rf.use != 'refocusing':
                    c_excitation += 1
                else:
                    c_refocusing += 1

            if hasattr(block, 'adc'):
                c_adc_samples += int(block.adc.num_samples)

        t_excitation = np.zeros(c_excitation)
        t_refocusing = np.zeros(c_refocusing)
        k_time = np.zeros(c_adc_samples)
        current_dur = 0
        c_excitation = 0
        c_refocusing = 0
        k_counter = 0
        traj_recon_delay = trajectory_delay

        for i in range(len(self.block_events)):
            block = self.get_block(i + 1)
            if hasattr(block, 'rf'):
                rf = block.rf
                rf_center, _ = calc_rf_center(rf)
                t = rf.delay + rf_center
                if not hasattr(block.rf,
                               'use') or block.rf.use != 'refocusing':
                    t_excitation[c_excitation] = current_dur + t
                    c_excitation += 1
                else:
                    t_refocusing[c_refocusing] = current_dur + t
                    c_refocusing += 1
            if hasattr(block, 'adc'):
                k_time[k_counter:k_counter + block.adc.num_samples] = np.arange(
                    block.adc.num_samples
                ) * block.adc.dwell + block.adc.delay + current_dur + traj_recon_delay
                k_counter += block.adc.num_samples
            current_dur += calc_duration(block)

        gw = self.gradient_waveforms()
        i_excitation = np.round(t_excitation / self.grad_raster_time)
        i_refocusing = np.round(t_refocusing / self.grad_raster_time)
        k_traj = np.zeros(gw.shape)
        k = [0, 0, 0]
        for i in range(gw.shape[1]):
            k += gw[:, i] * self.grad_raster_time
            k_traj[:, i] = k
            if len(np.where(i_excitation == i + 1)[0]) >= 1:
                k = 0
                k_traj[:, i] = np.nan
            if len(np.where(i_refocusing == i + 1)[0]) >= 1:
                k = -k

        k_traj_adc = []
        for i in range(k_traj.shape[0]):
            k_traj_adc.append(
                np.interp(
                    k_time,
                    np.arange(1, k_traj.shape[1] + 1) * self.grad_raster_time,
                    k_traj[i]))
        k_traj_adc = np.asarray(k_traj_adc)
        t_adc = k_time

        return k_traj_adc, k_traj, t_excitation, t_refocusing, t_adc
Esempio n. 15
0
    def gradient_waveforms(self) -> np.ndarray:
        """
        Decompress the entire gradient waveform. Returns an array of shape `gradient_axesxtimepoints`.
        `gradient_axes` is typically 3.

        Returns
        -------
        grad_waveforms : numpy.ndarray
            Decompressed gradient waveform.
        """
        duration, num_blocks, _ = self.duration()

        wave_length = math.ceil(duration / self.grad_raster_time)
        grad_channels = 3
        grad_waveforms = np.zeros((grad_channels, wave_length))
        grad_channels = ['gx', 'gy', 'gz']

        t0 = 0
        t0_n = 0
        for i in range(num_blocks):
            block = self.get_block(i + 1)
            for j in range(len(grad_channels)):
                if hasattr(block, grad_channels[j]):
                    grad = getattr(block, grad_channels[j])
                    if grad.type == 'grad':
                        nt_start = round(
                            (grad.delay + grad.t[0]) / self.grad_raster_time)
                        waveform = grad.waveform
                    else:
                        nt_start = round(grad.delay / self.grad_raster_time)
                        if abs(grad.flat_time) > np.finfo(float).eps:
                            t = np.cumsum([
                                0, grad.rise_time, grad.flat_time,
                                grad.fall_time
                            ])
                            trap_form = np.multiply([0, 1, 1, 0],
                                                    grad.amplitude)
                        else:
                            t = np.cumsum([0, grad.rise_time, grad.fall_time])
                            trap_form = np.multiply([0, 1, 0], grad.amplitude)

                        tn = math.floor(t[-1] / self.grad_raster_time)
                        t = np.append(t, t[-1] + self.grad_raster_time)
                        trap_form = np.append(trap_form, 0)

                        if abs(grad.amplitude) > np.finfo(float).eps:
                            waveform = points_to_waveform(
                                t, trap_form, self.grad_raster_time)
                        else:
                            waveform = np.zeros(tn + 1)

                    if waveform.size != np.sum(np.isfinite(waveform)):
                        raise Warning(
                            'Not all elements of the generated waveform are finite'
                        )
                    """
                    Matlab dynamically resizes arrays during slice assignment operation if assignment is out of bounds
                    Numpy does not
                    Following lines are a workaround
                    """
                    l1, l2 = int(t0_n + nt_start), int(t0_n + nt_start +
                                                       max(waveform.shape))
                    if l2 > grad_waveforms.shape[1]:
                        grad_waveforms.resize((len(grad_channels), l2))
                    grad_waveforms[j, l1:l2] = waveform

            t0 += calc_duration(block)
            t0_n = round(t0 / self.grad_raster_time)

        return grad_waveforms
Esempio n. 16
0
def align(
    **kwargs: Union[SimpleNamespace, List[SimpleNamespace]]
) -> List[SimpleNamespace]:
    """
    Aligns `SimpleNamespace` objects as per specified alignment options by setting delays of the pulse sequence events
    within the block. All previously configured delays within objects are taken into account during calculating of the
    block duration but then reset according to the selected alignment. Possible values for align_spec are 'left',
    'center', 'right'.

    Parameters
    ----------
    args : dict[str, list[SimpleNamespace]
        Dictionary mapping of alignment options and `SimpleNamespace` objects.
        Template: alignment_spec1=SimpleNamespace, alignment_spec2=[SimpleNamespace, ...], ...
        Alignment spec must be one of `left`, `center` or `right`.

    Returns
    -------
    objects : list
        List of aligned `SimpleNamespace` objects.

    Raises
    ------
    ValueError
        If first parameter is not of type `str`.
        If invalid alignment spec is passed. Must be one of `left`, `center` or `right`.
    """
    alignment_specs = list(kwargs.keys())
    if not isinstance(alignment_specs[0], str):
        raise ValueError(
            f'First parameter must be of type str. Passed: {type(alignment_specs[0])}'
        )

    alignment_options = ['left', 'center', 'right']
    if np.any(
        [align_opt not in alignment_options for align_opt in alignment_specs]):
        raise ValueError('Invalid alignment spec.')

    alignments = []
    objects = []
    for a in alignment_specs:
        objects_to_align = kwargs[a]
        a = alignment_options.index(a)
        if isinstance(objects_to_align, (list, np.ndarray, tuple)):
            alignments.extend([a] * len(objects_to_align))
            objects.extend(objects_to_align)
        elif isinstance(objects_to_align, SimpleNamespace):
            alignments.extend([a])
            objects.append(objects_to_align)

    dur = calc_duration(*objects)

    for i in range(len(objects)):
        if alignments[i] == 0:
            objects[i].delay = 0
        elif alignments[i] == 1:
            objects[i].delay = (dur - calc_duration(objects[i])) / 2
        elif alignments[i] == 2:
            objects[i].delay = dur - calc_duration(
                objects[i]) + objects[i].delay

    return objects
Esempio n. 17
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
Esempio n. 18
0
    # Scheme 5(3)3
    NImage_perInv = [5, 3]
    Nrecover_btwInv = [3]
    TI_Vector = [
        0.100, 0.100 + 0.08
    ]  # [s]     TI1 minimum TI of 100 ms, TI increment of 80 sec, Messroghli 2007
    TI_Vector_real = np.zeros(
        np.shape(TI_Vector))  # Initialize vector for final TI
    N_inversion = np.shape(NImage_perInv)  # Number of inversions

    print('Number of inversion:', N_inversion[0])
    print('Number of images per inversion:', NImage_perInv)
    print('Number of recovery heart beats between inversion:', Nrecover_btwInv)

    # Minimum Inversion Time
    TI_min_attainable = (Nstartup + Ny / 2) * TR + calc_duration(gzSpoil_INV)
    print('Minimum TI: %3.0f' % (TI_min_attainable * 1e3), 'ms')

    # Iterate between Inversions
    for nInv in np.arange(N_inversion[0]):
        print('----------- Inversion %3.0f -----------' % (nInv + 1))
        if nInv > 0:
            for nRec in np.arange(Nrecover_btwInv[nInv - 1]):
                seq.add_block(trig_BetweenInversion)

        # ***************************************************************************************************
        #                          Verify minimum possible TI
        #                   (nearest to TI_Vector[0], i.e. 100 ms)
        try:
            assert (all(TI_Vector[nInv] >= TI_min_attainable)
                    )  # verify if TI_vector value is possbile
Esempio n. 19
0
def make_pulseq_se_oblique(fov, n, thk, fa, tr, te, enc='xyz', slice_locs=None, write=False):
    """Makes a Spin Echo (SE) sequence in any plane

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

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

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

        """

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    print('Spin echo sequence (oblique) constructed')
    return seq
Esempio n. 20
0
    def plot(self, time_range=(0, np.inf)):
        """
        Show Matplotlib plot of all Events in the Sequence object.

        Parameters
        ----------
        time_range : List
            Time range (x-axis limits) for plot to be shown. Default is 0 to infinity (entire plot shown).
        """

        fig1, fig2 = plt.figure(1), plt.figure(2)
        f11, f12, f13 = fig1.add_subplot(311), fig1.add_subplot(
            312), fig1.add_subplot(313)
        f2 = [
            fig2.add_subplot(311),
            fig2.add_subplot(312),
            fig2.add_subplot(313)
        ]
        t0 = 0
        for iB in range(1, len(self.block_events) + 1):
            block = self.get_block(iB)
            is_valid = time_range[0] <= t0 <= time_range[1]
            if is_valid:
                if block is not None:
                    if 'adc' in block:
                        adc = block['adc']
                        t = adc.delay + [
                            (x * adc.dwell)
                            for x in range(0, int(adc.num_samples))
                        ]
                        f11.plot((t0 + t), np.zeros(len(t)))
                    if 'rf' in block:
                        rf = block['rf']
                        t = rf.t
                        f12.plot(np.squeeze(t0 + t), abs(rf.signal))
                        f13.plot(np.squeeze(t0 + t), np.angle(rf.signal))
                    grad_channels = ['gx', 'gy', 'gz']
                    for x in range(0, len(grad_channels)):
                        if grad_channels[x] in block:
                            grad = block[grad_channels[x]]
                            if grad.type == 'grad':
                                t = grad.t
                                waveform = 1e-3 * grad.waveform
                            else:
                                t = np.cumsum([
                                    0, grad.rise_time, grad.flat_time,
                                    grad.fall_time
                                ])
                                waveform = [
                                    1e-3 * grad.amplitude * x
                                    for x in [0, 1, 1, 0]
                                ]
                            f2[x].plot(np.squeeze(t0 + t), waveform)
            t0 += calc_duration(block)

        f11.set_ylabel('adc')
        f12.set_ylabel('rf mag hz')
        f13.set_ylabel('rf phase rad')
        [f2[x].set_ylabel(grad_channels[x]) for x in range(3)]
        # Setting display limits
        disp_range = [time_range[0], min(t0, time_range[1])]
        f11.set_xlim(disp_range)
        f12.set_xlim(disp_range)
        f13.set_xlim(disp_range)
        [x.set_xlim(disp_range) for x in f2]

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    # Find number of lines in each block

    if seg_type == 'blocked':

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

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

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


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

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

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

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

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

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

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

    # Add blocks

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

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

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

            seq.add_block(delay3)

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

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

    print('EPI sequence (oblique) constructed')
    return seq, ro_dirs, ro_order
Esempio n. 22
0
}
gz_reph = make_trapezoid(kwargs_for_gz_reph)

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

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

for i in range(2):
    seq.add_block(rf, gz)
    kwargs_for_gy_pre = {
        "channel": 'y',
        "system": system,
        "area": -(Ny / 2 - i) * delta_k,
        "duration": readoutTime / 2
    }
Esempio n. 23
0
    def calculate_kspace(self, trajectory_delay: int = 0):
        """
        Calculates the k-space trajectory of the entire pulse sequence.

        Parameters
        ----------
        trajectory_delay : int
            Compensation factor in millis to align ADC and gradients in the reconstruction.

        Returns
        -------
        k_traj_adc : numpy.ndarray
            K-space trajectory sampled at `t_adc` timepoints.
        k_traj : numpy.ndarray
            K-space trajectory of the entire pulse sequence.
        t_excitation : numpy.ndarray
            Excitation timepoints.
        t_refocusing : numpy.ndarray
            Refocusing timepoints.
        t_adc : numpy.ndarray
            Sampling timepoints.
        """
        c_excitation = 0
        c_refocusing = 0
        c_adc_samples = 0

        for i in range(len(self.block_events)):
            block = self.get_block(i + 1)
            if hasattr(block, 'rf'):
                if not hasattr(block.rf,
                               'use') or block.rf.use != 'refocusing':
                    c_excitation += 1
                else:
                    c_refocusing += 1

            if hasattr(block, 'adc'):
                c_adc_samples += int(block.adc.num_samples)

        t_excitation = np.zeros(c_excitation)
        t_refocusing = np.zeros(c_refocusing)
        k_time = np.zeros(c_adc_samples)
        current_dur = 0
        c_excitation = 0
        c_refocusing = 0
        k_counter = 0
        traj_recon_delay = trajectory_delay

        for i in range(len(self.block_events)):
            block = self.get_block(i + 1)
            if hasattr(block, 'rf'):
                rf = block.rf
                rf_center, _ = calc_rf_center(rf)
                t = rf.delay + rf_center
                if not hasattr(block.rf,
                               'use') or block.rf.use != 'refocusing':
                    t_excitation[c_excitation] = current_dur + t
                    c_excitation += 1
                else:
                    t_refocusing[c_refocusing] = current_dur + t
                    c_refocusing += 1
            if hasattr(block, 'adc'):
                k_time[k_counter:k_counter + block.adc.num_samples] = np.arange(
                    block.adc.num_samples
                ) * block.adc.dwell + block.adc.delay + current_dur + traj_recon_delay
                k_counter += block.adc.num_samples
            current_dur += calc_duration(block)

        gw = self.gradient_waveforms()
        i_excitation = np.round(t_excitation / self.grad_raster_time)
        i_refocusing = np.round(t_refocusing / self.grad_raster_time)
        k_traj = np.zeros(gw.shape)
        k = [0, 0, 0]
        for i in range(gw.shape[1]):
            k += gw[:, i] * self.grad_raster_time
            k_traj[:, i] = k
            if len(np.where(i_excitation == i + 1)[0]) >= 1:
                k = 0
                k_traj[:, i] = np.nan
            if len(np.where(i_refocusing == i + 1)[0]) >= 1:
                k = -k

        k_traj_adc = []
        for i in range(k_traj.shape[0]):
            k_traj_adc.append(
                np.interp(
                    k_time,
                    np.arange(1, k_traj.shape[1] + 1) * self.grad_raster_time,
                    k_traj[i]))
        k_traj_adc = np.asarray(k_traj_adc)
        t_adc = k_time

        return k_traj_adc, k_traj, t_excitation, t_refocusing, t_adc
rf_dur      *= 1e-3 # [s]
slice_res   *= 1e-3 # [m]

if Nintl/redfac%1 != 0:
    raise ValueError('Number of interleaves is not multiple of reduction factor')

if spiraltype!=1 and spiraltype!=4:
    ValueError('Right now only spiraltype 1 (spiral out) and 4 (ROI) possible.')
    
#%% RF Pulse and slab/slice selection gradient

# make rf pulse and calculate duration of excitation and rewinding
rf, gz, gz_rew, rf_del = make_sinc_pulse(flip_angle=flip_angle*np.pi/180, system=system, duration=rf_dur, slice_thickness=slice_res,
                            apodization=0.5, time_bw_product=tbp_exc, use='excitation', return_gz=True, return_delay=True)
exc_to_rew = rf_del.delay - rf_dur/2 - rf.delay # time from middle of rf pulse to rewinder, rf_del.delay equals the block length
rew_dur = calc_duration(gz_rew)

# RF spoiling parameters
rf_spoiling_inc = 50 # increment of RF spoiling [°]
rf_phase        = 0 
rf_inc          = 0

# Fat saturation
if fatsat:
    if B0 > 4:
        fatsat_bw = 1000 # bandwidth of fatsat pulse [Hz]
    else:
        fatsat_bw = 300
    fatsat_fa = 110 # flip angle [°]
    fatsat_dur = ph.round_up_to_raster(fatsat_tbp/fatsat_bw, decimals=5)
Esempio n. 25
0
    def plot(self,
             type: str = 'Gradient',
             time_range=(0, np.inf),
             time_disp: str = 's',
             save: bool = False):
        """
        Plot `Sequence`.

        Parameters
        ----------
        type : str
            Gradients display type, must be one of either 'Gradient' or 'Kspace'.
        time_range : List
            Time range (x-axis limits) for plotting the sequence. Default is 0 to infinity (entire sequence).
        time_disp : str
            Time display type, must be one of `s`, `ms` or `us`.
        save_as : bool
            Boolean flag indicating if plots should be saved. The two figures will be saved as JPG with numerical
            suffixes to the filename 'seq_plot'.
        """

        valid_plot_types = ['Gradient', 'Kspace']
        valid_time_units = ['s', 'ms', 'us']
        if type not in valid_plot_types:
            raise Exception()
        if time_disp not in valid_time_units:
            raise Exception()

        fig1, fig2 = plt.figure(1), plt.figure(2)
        sp11 = fig1.add_subplot(311)
        sp12, sp13 = fig1.add_subplot(312, sharex=sp11), fig1.add_subplot(
            313, sharex=sp11)
        fig2_sp_list = [
            fig2.add_subplot(311, sharex=sp11),
            fig2.add_subplot(312, sharex=sp11),
            fig2.add_subplot(313, sharex=sp11)
        ]

        t_factor_list = [1, 1e3, 1e6]
        t_factor = t_factor_list[valid_time_units.index(time_disp)]
        t0 = 0
        for iB in range(1, len(self.block_events) + 1):
            block = self.get_block(iB)
            is_valid = time_range[0] <= t0 <= time_range[1]
            if is_valid:
                if hasattr(block, 'adc'):
                    adc = block.adc
                    t = adc.delay + [(x * adc.dwell)
                                     for x in range(0, int(adc.num_samples))]
                    sp11.plot((t0 + t), np.zeros(len(t)), 'rx')
                if hasattr(block, 'rf'):
                    rf = block.rf
                    tc, ic = calc_rf_center(rf)
                    t = rf.t + rf.delay
                    tc = tc + rf.delay
                    sp12.plot(t_factor * (t0 + t), abs(rf.signal))
                    sp13.plot(
                        t_factor * (t0 + t),
                        np.angle(
                            rf.signal * np.exp(1j * rf.phase_offset) *
                            np.exp(1j * 2 * math.pi * rf.t * rf.freq_offset)),
                        t_factor * (t0 + tc),
                        np.angle(rf.signal[ic] * np.exp(1j * rf.phase_offset) *
                                 np.exp(1j * 2 * math.pi * rf.t[ic] *
                                        rf.freq_offset)), 'xb')
                grad_channels = ['gx', 'gy', 'gz']
                for x in range(0, len(grad_channels)):
                    if hasattr(block, grad_channels[x]):
                        grad = getattr(block, grad_channels[x])
                        if grad.type == 'grad':
                            # In place unpacking of grad.t with the starred expression
                            t = grad.delay + [
                                0, *(grad.t + (grad.t[1] - grad.t[0]) / 2),
                                grad.t[-1] + grad.t[1] - grad.t[0]
                            ]
                            waveform = np.array([grad.first, grad.last])
                            waveform = 1e-3 * np.insert(
                                waveform, 1, grad.waveform)
                        else:
                            t = np.cumsum([
                                0, grad.delay, grad.rise_time, grad.flat_time,
                                grad.fall_time
                            ])
                            waveform = 1e-3 * grad.amplitude * np.array(
                                [0, 0, 1, 1, 0])
                        fig2_sp_list[x].plot(t_factor * (t0 + t), waveform)
            t0 += calc_duration(block)

        grad_plot_labels = ['x', 'y', 'z']
        sp11.set_ylabel('ADC')
        sp12.set_ylabel('RF mag (Hz)')
        sp13.set_ylabel('RF phase (rad)')
        [
            fig2_sp_list[x].set_ylabel(f'G{grad_plot_labels[x]} (kHz/m)')
            for x in range(3)
        ]
        # Setting display limits
        disp_range = t_factor * np.array(
            [time_range[0], min(t0, time_range[1])])
        sp11.set_xlim(disp_range)
        sp12.set_xlim(disp_range)
        sp13.set_xlim(disp_range)
        [x.set_xlim(disp_range) for x in fig2_sp_list]

        fig1.tight_layout()
        fig2.tight_layout()
        if save:
            fig1.savefig('seq_plot1.jpg')
            fig2.savefig('seq_plot2.jpg')
        plt.show()
Esempio n. 26
0
    "system": system,
    "area": -gx.area / 2,
    "duration": 2e-3
}
gx_pre = make_trapezoid(kwargs_for_gxpre)
kwargs_for_gz_reph = {
    "channel": 'z',
    "system": system,
    "area": -gz.area / 2,
    "duration": 2e-3
}
gz_reph = make_trapezoid(kwargs_for_gz_reph)
phase_areas = (np.arange(Ny) - (Ny / 2)) * delta_k

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

for i in range(Ny):
    seq.add_block(rf, gz)
    kwargsForGyPre = {
        "channel": 'y',
        "system": system,
        "area": phase_areas[i],
        "duration": 2e-3
    }
    gyPre = make_trapezoid(kwargsForGyPre)
    seq.add_block(gx_pre, gyPre, gz_reph)
Esempio n. 27
0
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()

rf_phase = 0
rf_inc = 0

for i in range(Ny):
    rf.phase_offset = rf_phase / 180 * np.pi
    adc.phase_offset = rf_phase / 180 * np.pi
    rf_inc = divmod(rf_inc + rf_spoiling_inc, 360.0)[1]
    rf_phase = divmod(rf_phase + rf_inc, 360.0)[1]
Esempio n. 28
0
def add_gradients(grads: Union[list, tuple],
                  system=Opts(),
                  max_grad: int = 0,
                  max_slew: int = 0) -> SimpleNamespace:
    """
    Superpose several gradient events.

    Parameters
    ----------
    grads : list or tuple
        List or tuple of 'SimpleNamespace' gradient events.
    system : Opts, optional, default=Opts()
        System limits.
    max_grad : float, optional, default=0
        Maximum gradient amplitude.
    max_slew : float, optional, default=0
        Maximum slew rate.

    Returns
    -------
    grad : SimpleNamespace
        Superimposition of gradient events from `grads`.
    """
    max_grad = max_grad if max_grad > 0 else system.max_grad
    max_slew = max_slew if max_slew > 0 else system.max_slew

    if len(grads) < 2:
        raise Exception()

    # First gradient defines channel
    channel = grads[0].channel

    # Find out the general delay of all gradients and other statistics
    delays, firsts, lasts, durs = [], [], [], []
    for ii in range(len(grads)):
        delays.append(grads[ii].delay)
        firsts.append(grads[ii].first)
        lasts.append(grads[ii].last)
        durs.append(calc_duration(grads[ii]))

    # Convert to numpy.ndarray for fancy-indexing later on
    firsts, lasts = np.array(firsts), np.array(lasts)

    common_delay = min(delays)
    total_duration = max(durs)

    waveforms = dict()
    max_length = 0
    for ii in range(len(grads)):
        g = grads[ii]
        if g.type == 'grad':
            waveforms[ii] = g.waveform
        elif g.type == 'trap':
            if g.flat_time > 0:  # Triangle or trapezoid
                times = [
                    g.delay - common_delay,
                    g.delay - common_delay + g.rise_time,
                    g.delay - common_delay + g.rise_time + g.flat_time,
                    g.delay - common_delay + g.rise_time + g.flat_time +
                    g.fall_time
                ]
                amplitudes = [0, g.amplitude, g.amplitude, 0]
            else:
                times = [
                    g.delay - common_delay,
                    g.delay - common_delay + g.rise_time,
                    g.delay - common_delay + g.rise_time + g.flat_time
                ]
                amplitudes = [0, g.amplitude, 0]
            waveforms[ii] = points_to_waveform(
                times=times,
                amplitudes=amplitudes,
                grad_raster_time=system.grad_raster_time)
        else:
            raise ValueError('Unknown gradient type')

        if g.delay - common_delay > 0:
            # Stop for numpy.arange is not g.delay - common_delay - system.grad_raster_time like in Matlab
            # so as to include the endpoint
            t_delay = np.arange(0,
                                g.delay - common_delay,
                                step=system.grad_raster_time)
            waveforms[ii] = np.insert(waveforms[ii], 0, t_delay)

        num_points = len(waveforms[ii])
        max_length = num_points if num_points > max_length else max_length

    w = np.zeros(max_length)
    for ii in range(len(grads)):
        wt = np.zeros(max_length)
        wt[0:len(waveforms[ii])] = waveforms[ii]
        w += wt

    grad = make_arbitrary_grad(channel,
                               w,
                               system,
                               max_slew=max_slew,
                               max_grad=max_grad,
                               delay=common_delay)
    grad.first = np.sum(firsts[np.array(delays) == common_delay])
    grad.last = np.sum(lasts[np.where(durs == total_duration)])

    return grad
Esempio n. 29
0
def add_block(self, block_index: int, *args):
    """
    Inserts pulse sequence events into `self.block_events` at position `block_index`. Also performs gradient checks.

    Parameters
    ----------
    block_index : int
        Index at which `SimpleNamespace` events have to be inserted into `self.block_events`.
    args : list
        List of `SimpleNamespace` pulse sequence events to be added to `self.block_events`.
    """

    block_duration = calc_duration(*args)
    self.block_events[block_index] = np.zeros(6, dtype=np.int)
    duration = 0

    check_g = {}

    for event in args:
        if event.type == 'rf':
            mag = np.abs(event.signal)
            amplitude = np.max(mag)
            mag = np.divide(mag, amplitude)
            # Following line of code is a workaround for numpy's divide functions returning NaN when mathematical
            # edge cases are encountered (eg. divide by 0)
            mag[np.isnan(mag)] = 0
            phase = np.angle(event.signal)
            phase[phase < 0] += 2 * np.pi
            phase /= 2 * np.pi

            mag_shape = compress_shape(mag)
            data = np.insert(mag_shape.data, 0, mag_shape.num_samples)
            mag_id, found = self.shape_library.find(data)
            if not found:
                self.shape_library.insert(mag_id, data)

            phase_shape = compress_shape(phase)
            data = np.insert(phase_shape.data, 0, phase_shape.num_samples)
            phase_id, found = self.shape_library.find(data)
            if not found:
                self.shape_library.insert(phase_id, data)

            use = 0
            use_cases = {'excitation': 1, 'refocusing': 2, 'inversion': 3}
            if hasattr(event, 'use'):
                use = use_cases[event.use]

            data = np.array((amplitude, mag_id, phase_id, event.delay,
                             event.freq_offset, event.phase_offset,
                             event.dead_time, event.ringdown_time, use))
            data_id, found = self.rf_library.find(data)
            if not found:
                self.rf_library.insert(data_id, data)

            self.block_events[block_index][1] = data_id
            duration = max(
                duration,
                max(mag.shape) * self.rf_raster_time + event.dead_time +
                event.ringdown_time + event.delay)
        elif event.type == 'grad':
            channel_num = ['x', 'y', 'z'].index(event.channel)
            idx = 2 + channel_num

            check_g[channel_num] = SimpleNamespace()
            check_g[channel_num].idx = idx
            check_g[channel_num].start = np.array(
                (event.delay + min(event.t), event.first))
            check_g[channel_num].stop = np.array(
                (event.delay + max(event.t) + self.system.grad_raster_time,
                 event.last))

            amplitude = max(abs(event.waveform))
            if amplitude > 0:
                g = event.waveform / amplitude
            else:
                g = event.waveform
            shape = compress_shape(g)
            data = np.insert(shape.data, 0, shape.num_samples)
            shape_id, found = self.shape_library.find(data)
            if not found:
                self.shape_library.insert(shape_id, data)
            data = np.array(
                [amplitude, shape_id, event.delay, event.first, event.last])
            grad_id, found = self.grad_library.find(data)
            if not found:
                self.grad_library.insert(grad_id, data, 'g')
            idx = 2 + channel_num
            self.block_events[block_index][idx] = grad_id
            duration = max(duration, len(g) * self.grad_raster_time)
        elif event.type == 'trap':
            channel_num = ['x', 'y', 'z'].index(event.channel)
            idx = 2 + channel_num
            check_g[channel_num] = SimpleNamespace()
            check_g[channel_num].idx = idx
            check_g[channel_num].start = np.array((0, 0))
            check_g[channel_num].stop = np.array(
                (event.delay + event.rise_time + event.fall_time +
                 event.flat_time, 0))
            data = np.array([
                event.amplitude, event.rise_time, event.flat_time,
                event.fall_time, event.delay
            ])
            trap_id, found = self.grad_library.find(data)
            if not found:
                self.grad_library.insert(trap_id, data, 't')
            self.block_events[block_index][idx] = trap_id
            duration = max(
                duration, event.delay + event.rise_time + event.flat_time +
                event.fall_time)
        elif event.type == 'adc':
            data = np.array([
                event.num_samples, event.dwell,
                max(event.delay, event.dead_time), event.freq_offset,
                event.phase_offset, event.dead_time
            ])
            adc_id, found = self.adc_library.find(data)
            if not found:
                self.adc_library.insert(adc_id, data)
            self.block_events[block_index][5] = adc_id
            duration = max(
                duration, event.delay + event.num_samples * event.dwell +
                event.dead_time)
        elif event.type == 'delay':
            data = np.array([event.delay])
            delay_id, found = self.delay_library.find(data)
            if not found:
                self.delay_library.insert(delay_id, data)
            self.block_events[block_index][0] = delay_id
            duration = max(duration, event.delay)

    # =========
    # PERFORM GRADIENT CHECKS
    # =========
    for cg_temp in check_g.keys():
        cg = check_g[cg_temp]

        if abs(cg.start[1]
               ) > self.system.max_slew * self.system.grad_raster_time:
            if cg.start[0] != 0:
                raise ValueError(
                    'No delay allowed for gradients which start with a non-zero amplitude'
                )

            if block_index > 1:
                prev_id = self.block_events[block_index - 1][cg.idx]
                if prev_id != 0:
                    prev_lib = self.grad_library.get(prev_id)
                    prev_dat = prev_lib['data']
                    prev_type = prev_lib['type']
                    if prev_type == 't':
                        raise Exception(
                            'Two consecutive gradients need to have the same amplitude at the connection point'
                        )
                    elif prev_type == 'g':
                        last = prev_dat[4]
                        if abs(
                                last - cg.start[1]
                        ) > self.system.max_slew * self.system.grad_raster_time:
                            raise Exception(
                                'Two consecutive gradients need to have the same amplitude at the connection point'
                            )
            else:
                raise Exception(
                    'First gradient in the the first block has to start at 0.')
        if cg.stop[
                1] > self.system.max_slew * self.system.grad_raster_time and abs(
                    cg.stop[0] - block_duration) > 1e-7:
            raise Exception(
                'A gradient that doesnt end at zero needs to be aligned to the block boundary.'
            )
Esempio n. 30
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