Exemplo n.º 1
0
def make_label(type: str, label: str, value: Union[bool, float,
                                                   int]) -> SimpleNamespace:
    """
    Parameters
    ----------
    type : str
        Label type. Must be one of 'SET' or 'INC'.
    label : str
        Must be one of 'SLC', 'SEG', 'REP', 'AVG', 'SET', 'ECO', 'PHS', 'LIN', 'PAR', 'NAV', 'REV', or 'SMS'.
    value : bool, float or int
        Label value.

    Returns
    -------
    out : SimpleNamespace
        Label object.

    Raises
    ------
    ValueError
        If a valid `label` was not passed. Must be one of 'SLC', 'SEG', 'REP', 'AVG', 'SET', 'ECO', 'PHS', 'LIN', 'PAR',
                                                                                                NAV', 'REV', or 'SMS'.
        If a valid `type` was not passed. Must be one of 'SET' or 'INC'.
        If `value` was not a valid numerical or logical value.
    """
    arr_supported_labels = supported_labels.get_supported_labels()

    if label not in arr_supported_labels:
        raise ValueError(
            "Invalid label. Must be one of 'SLC', 'SEG', 'REP', 'AVG', 'SET', 'ECO', 'PHS', 'LIN', 'PAR', "
            "NAV', 'REV', or 'SMS'.")
    if type not in ['SET', 'INC']:
        raise ValueError("Invalid type. Must be one of 'SET' or 'INC'.")
    if not isinstance(value, (bool, float, int)):
        raise ValueError('Must supply a valid numerical or logical value.')

    out = SimpleNamespace()
    if type == 'SET':
        out.type = 'labelset'
    elif type == 'INC':
        out.type = 'labelinc'

    out.label = label
    out.value = value

    return out
Exemplo n.º 2
0
    def plot(self, label: str = str(), save: bool = False, time_range=(0, np.inf), time_disp: str = 's',
             plot_type: str = 'Gradient') -> None:
        """
        Plot `Sequence`.

        Parameters
        ----------
        label : str, defualt=str()

        save : bool, default=False
            Boolean flag indicating if plots should be saved. The two figures will be saved as JPG with numerical
            suffixes to the filename 'seq_plot'.
        time_range : iterable, default=(0, np.inf)
            Time range (x-axis limits) for plotting the sequence. Default is 0 to infinity (entire sequence).
        time_disp : str, default='s'
            Time display type, must be one of `s`, `ms` or `us`.
        plot_type : str, default='Gradient'
            Gradients display type, must be one of either 'Gradient' or 'Kspace'.
        """
        mpl.rcParams['lines.linewidth'] = 0.75  # Set default Matplotlib linewidth

        valid_plot_types = ['Gradient', 'Kspace']
        valid_time_units = ['s', 'ms', 'us']
        valid_labels = get_supported_labels()
        if plot_type not in valid_plot_types:
            raise ValueError('Unsupported plot type')
        if not all([isinstance(x, (int, float)) for x in time_range]) or len(time_range) != 2:
            raise ValueError('Invalid time range')
        if time_disp not in valid_time_units:
            raise ValueError('Unsupported time unit')

        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_subplots = [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
        label_defined = False
        label_idx_to_plot = []
        label_legend_to_plot = []
        label_store = dict()
        for i in range(len(valid_labels)):
            label_store[valid_labels[i]] = 0
            if label.upper() == valid_labels[i]:
                label_idx_to_plot.append(i)
                label_legend_to_plot.append(valid_labels[i])

        if len(label_idx_to_plot) != 0:
            p = parula.main(len(label_idx_to_plot) + 1)
            label_colors_to_plot = p(np.arange(len(label_idx_to_plot)))

        for block_counter in range(len(self.dict_block_events)):
            block = self.get_block(block_counter + 1)
            is_valid = time_range[0] <= t0 <= time_range[1]
            if is_valid:
                if hasattr(block, 'label'):
                    for i in range(len(block.label)):
                        if block.label[i].type == 'labelinc':
                            label_store[block.label[i].label] += block.label[i].value
                        else:
                            label_store[block.label[i].label] = block.label[i].value
                    label_defined = True

                if hasattr(block, 'adc'):
                    adc = block.adc
                    # From Pulseq: According to the information from Klaus Scheffler and indirectly from Siemens this
                    # is the present convention - the samples are shifted by 0.5 dwell
                    t = adc.delay + (np.arange(int(adc.num_samples)) + 0.5) * adc.dwell
                    sp11.plot(t_factor * (t0 + t), np.zeros(len(t)), 'rx')
                    sp13.plot(t_factor * (t0 + t),
                              np.angle(np.exp(1j * adc.phase_offset) * np.exp(1j * 2 * np.pi * t * adc.freq_offset)),
                              'b.')

                    if label_defined and len(label_idx_to_plot) != 0:
                        cycler = mpl.cycler(color=label_colors_to_plot)
                        sp11.set_prop_cycle(cycler)
                        label_store_arr = list(label_store.values())
                        lbl_vals = np.take(label_store_arr, label_idx_to_plot)
                        t = t0 + adc.delay + (adc.num_samples - 1) / 2 * adc.dwell
                        p = sp11.plot(t_factor * t, lbl_vals, '.')
                        if len(label_legend_to_plot) != 0:
                            sp11.legend(p, label_legend_to_plot, loc='upper left')
                            label_legend_to_plot = []

                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), np.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(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 = 1e-3 * np.array((grad.first, *grad.waveform, grad.last))
                        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_subplots[x].plot(t_factor * (t0 + t), waveform)
            t0 += self.arr_block_durations[block_counter]

        grad_plot_labels = ['x', 'y', 'z']
        sp11.set_ylabel('ADC')
        sp12.set_ylabel('RF mag (Hz)')
        sp13.set_ylabel('RF/ADC phase (rad)')
        sp13.set_xlabel('t(s)')
        for x in range(3):
            _label = grad_plot_labels[x]
            fig2_subplots[x].set_ylabel(f'G{_label} (kHz/m)')
        fig2_subplots[-1].set_xlabel('t(s)')

        # Setting display limits
        disp_range = t_factor * np.array([time_range[0], min(t0, time_range[1])])
        [x.set_xlim(disp_range) for x in [sp11, sp12, sp13, *fig2_subplots]]

        fig1.tight_layout()
        fig2.tight_layout()
        if save:
            fig1.savefig('seq_plot1.jpg')
            fig2.savefig('seq_plot2.jpg')
        plt.show()
Exemplo n.º 3
0
def write(self, file_name: str) -> None:
    """
    Writes the calling `Sequence` object as a `.seq` file with filename `file_name`.

    Parameters
    ----------
    file_name : str
        File name of `.seq` file to be written to disk.

    Raises
    ------
    RuntimeError
        If an unsupported definition is encountered.
    """
    # `>.0f` is used when only decimals have to be displayed.
    # `>g` is used when insignificant zeros have to be truncated.
    file_name += '.seq' if file_name[-4:] != '.seq' not in file_name else ''
    output_file = open(file_name, 'w')
    output_file.write('# Pulseq sequence file\n')
    output_file.write('# Created by PyPulseq\n\n')

    output_file.write('[VERSION]\n')
    output_file.write(f'major {self.version_major}\n')
    output_file.write(f'minor {self.version_minor}\n')
    output_file.write(f'revision {self.version_revision}\n')
    output_file.write('\n')

    if len(self.dict_definitions) != 0:
        output_file.write('[DEFINITIONS]\n')
        keys = list(self.dict_definitions.keys())
        values = list(self.dict_definitions.values())
        for block_counter in range(len(keys)):
            output_file.write(f'{keys[block_counter]} ')
            if isinstance(values[block_counter], str):
                output_file.write(values[block_counter] + ' ')
            elif isinstance(values[block_counter], float):
                output_file.write(f'{values[block_counter]:0.9g} ')
            elif isinstance(
                    values[block_counter],
                (list, tuple, np.ndarray)):  # For example, [FOV, FOV, FOV]
                for i in range(len(values[block_counter])):
                    if isinstance(values[block_counter][i], (int, float)):
                        output_file.write(f'{values[block_counter][i]:0.9g} ')
                    else:
                        output_file.write(f'{values[block_counter][i]} ')
            else:
                raise RuntimeError('Unsupported definition')
            output_file.write('\n')
        output_file.write('\n')

    output_file.write('# Format of blocks:\n')
    output_file.write('#  #  D RF  GX  GY  GZ ADC EXT\n')
    output_file.write('[BLOCKS]\n')
    id_format_width = '{:' + str(len(str(len(self.dict_block_events)))) + 'd}'
    id_format_str = id_format_width + ' ' + '{:2d} {:2d} {:3d} {:3d} {:3d} {:2d} {:2d}\n'
    for block_counter in range(len(self.dict_block_events)):
        s = id_format_str.format(*(block_counter + 1,
                                   *self.dict_block_events[block_counter + 1]))
        output_file.write(s)
    output_file.write('\n')

    if len(self.rf_library.keys) != 0:
        output_file.write('# Format of RF events:\n')
        output_file.write('# id amplitude mag_id phase_id delay freq phase\n')
        output_file.write('# ..        Hz   ....     ....    us   Hz   rad\n')
        output_file.write('[RF]\n')
        rf_lib_keys = self.rf_library.keys
        # See comment at the beginning of this method definition
        id_format_str = '{:.0f} {:12g} {:.0f} {:.0f} {:g} {:g} {:g}\n'
        for k in rf_lib_keys.keys():
            lib_data1 = self.rf_library.data[k][0:3]
            lib_data2 = self.rf_library.data[k][4:6]
            delay = np.round(self.rf_library.data[k][3] * 1e6)
            s = id_format_str.format(k, *lib_data1, delay, *lib_data2)
            output_file.write(s)
        output_file.write('\n')

    grad_lib_values = np.array(list(self.grad_library.type.values()))
    arb_grad_mask = grad_lib_values == 'g'
    trap_grad_mask = grad_lib_values == 't'

    if any(arb_grad_mask):
        output_file.write('# Format of arbitrary gradients:\n')
        output_file.write('# id amplitude shape_id delay\n')
        output_file.write('# ..      Hz/m     ....    us\n')
        output_file.write('[GRADIENTS]\n')
        id_format_str = '{:.0f} {:12g} {:.0f} {:.0f}\n'  # See comment at the beginning of this method definition
        keys = np.array(list(self.grad_library.keys.keys()))
        for k in keys[arb_grad_mask]:
            s = id_format_str.format(
                k, *self.grad_library.data[k][:2],
                np.round(self.grad_library.data[k][2] * 1e6))
            output_file.write(s)
        output_file.write('\n')

    if any(trap_grad_mask):
        output_file.write('# Format of trapezoid gradients:\n')
        output_file.write('# id amplitude rise flat fall delay\n')
        output_file.write('# ..      Hz/m   us   us   us    us\n')
        output_file.write('[TRAP]\n')
        keys = np.array(list(self.grad_library.keys.keys()))
        id_format_str = '{:2g} {:12g} {:3g} {:4g} {:3g} {:3g}\n'
        for k in keys[trap_grad_mask]:
            data = np.copy(self.grad_library.data[k]
                           )  # Make a copy to leave the original untouched
            data[1:] = np.round(1e6 * data[1:])
            """
            Python always rounds to nearest even value, this can cause inconsistencies with MATLAB Pulseq's .seq files.
            Read more - https://stackoverflow.com/questions/29671945/format-string-rounding-inconsistent
            Numpy too - https://stackoverflow.com/questions/50374779/how-to-avoid-incorrect-rounding-with-numpy-round
            """
            s = id_format_str.format(k, *data)
            output_file.write(s)
        output_file.write('\n')

    if len(self.adc_library.keys) != 0:
        output_file.write('# Format of ADC events:\n')
        output_file.write('# id num dwell delay freq phase\n')
        output_file.write('# ..  ..    ns    us   Hz   rad\n')
        output_file.write('[ADC]\n')
        keys = self.adc_library.keys
        # See comment at the beginning of this method definition
        id_format_str = '{:.0f} {:.0f} {:.0f} {:.0f} {:g} {:g}\n'
        for k in keys.values():
            data = np.multiply(self.adc_library.data[k][0:5],
                               [1, 1e9, 1e6, 1, 1])
            s = id_format_str.format(k, *data)
            output_file.write(s)
        output_file.write('\n')

    if len(self.delay_library.keys) != 0:
        output_file.write('# Format of delays:\n')
        output_file.write('# id delay (us)\n')
        output_file.write('[DELAYS]\n')
        keys = self.delay_library.keys
        id_format_str = '{:.0f} {:.0f}\n'  # See if-block for self.rf.library.keys
        for k in keys.values():
            s = id_format_str.format(
                k, *np.round(1e6 * self.delay_library.data[k]))
            output_file.write(s)
        output_file.write('\n')

    if len(self.extensions_library.keys) != 0:
        output_file.write('# Format of extension lists:\n')
        output_file.write('# id type ref next_id\n')
        output_file.write('# next_id of 0 terminates the list\n')
        output_file.write(
            '# Extension list is followed by extension specifications\n')
        output_file.write('[EXTENSIONS]\n')
        keys = self.extensions_library.keys
        id_format_str = '{:.0f} {:.0f} {:.0f} {:.0f}\n'  # See comment at the beginning of this method definition
        for k in keys.values():
            s = id_format_str.format(
                k, *np.round(self.extensions_library.data[k]))
            output_file.write(s)
        output_file.write('\n')

    if len(self.trigger_library.keys) != 0:
        output_file.write(
            '# Extension specification for digital output and input triggers:\n'
        )
        output_file.write('# id type channel delay (us) duration (us)\n')
        output_file.write(
            f'extension TRIGGERS {self.get_extension_type_ID("TRIGGERS")}\n')
        keys = self.trigger_library.keys
        id_format_str = '{:.0f} {:.0f} {:.0f} {:.0f} {:.0f}\n'  # See comment at the beginning of this method definition
        for k in keys.values():
            s = id_format_str.format(
                k, *np.round(self.trigger_library.data[k] * [1, 1, 1e6, 1e6]))
            output_file.write(s)
        output_file.write('\n')

    if len(self.label_set_library.keys) != 0:
        lbls = get_supported_labels()

        output_file.write('# Extension specification for setting labels:\n')
        output_file.write('# id set labelstring\n')
        tid = self.get_extension_type_ID('LABELSET')
        output_file.write(f'extension LABELSET {tid}\n')
        keys = self.label_set_library.keys
        id_format_str = '{:.0f} {:.0f} {}\n'  # See comment at the beginning of this method definition
        for k in keys.values():
            s = id_format_str.format(
                k, self.label_set_library.data[k][0],
                lbls[self.label_set_library.data[k][1] - 1])
            output_file.write(s)
        output_file.write('\n')

        output_file.write('# Extension specification for setting labels:\n')
        output_file.write('# id set labelstring\n')
        tid = self.get_extension_type_ID('LABELINC')
        output_file.write(f'extension LABELINC {tid}\n')
        keys = self.label_inc_library.keys
        id_format_str = '{:.0f} {:.0f} {}\n'  # See comment at the beginning of this method definition
        for k in keys.values():
            s = id_format_str.format(
                k, self.label_inc_library.data[k][0],
                lbls[self.label_inc_library.data[k][1] - 1])
            output_file.write(s)
        output_file.write('\n')

    if len(self.shape_library.keys) != 0:
        output_file.write('# Sequence Shapes\n')
        output_file.write('[SHAPES]\n\n')
        keys = self.shape_library.keys
        for k in keys.values():
            shape_data = self.shape_library.data[k]
            s = 'shape_id {:.0f}\n'
            s = s.format(k)
            output_file.write(s)
            s = 'num_samples {:.0f}\n'
            s = s.format(shape_data[0])
            output_file.write(s)
            s = '{:.9g}\n' * len(shape_data[1:])
            s = s.format(*shape_data[1:])
            output_file.write(s)
            output_file.write('\n')
Exemplo n.º 4
0
def get_block(self, block_index: int) -> SimpleNamespace:
    """
    Returns PyPulseq block at `block_index` position in `self.dict_block_events`.

    Parameters
    ----------
    block_index : int
        Index of PyPulseq block to be retrieved from `self.dict_block_events`.

    Returns
    -------
    block : SimpleNamespace
        PyPulseq block at 'block_index' position in `self.dict_block_events`.

    Raises
    ------
    ValueError
        If a trigger event of an unsupported control type is encountered.
        If a label object of an unknown extension ID is encountered.
    """

    block = SimpleNamespace()
    event_ind = self.dict_block_events[block_index]

    if event_ind[0] > 0:  # Delay
        delay = SimpleNamespace()
        delay.type = 'delay'
        delay.delay = self.delay_library.data[event_ind[0]][0]
        block.delay = delay

    if event_ind[1] > 0:  # RF
        block.rf = self.rf_from_lib_data(self.rf_library.data[event_ind[1]])

    # Gradients
    grad_channels = ['gx', 'gy', 'gz']
    for i in range(1, len(grad_channels) + 1):
        if event_ind[2 + (i - 1)] > 0:
            grad, compressed = SimpleNamespace(), SimpleNamespace()
            grad_type = self.grad_library.type[event_ind[2 + (i - 1)]]
            lib_data = self.grad_library.data[event_ind[2 + (i - 1)]]
            grad.type = 'trap' if grad_type == 't' else 'grad'
            grad.channel = grad_channels[i - 1][1]
            if grad.type == 'grad':
                amplitude = lib_data[0]
                shape_id = lib_data[1]
                delay = lib_data[2]
                shape_data = self.shape_library.data[shape_id]
                compressed.num_samples = shape_data[0]
                compressed.data = shape_data[1:]
                g = decompress_shape(compressed)
                grad.waveform = amplitude * g
                grad.t = np.arange(g.size) * self.grad_raster_time
                grad.delay = delay
                if len(lib_data) > 4:
                    grad.first = lib_data[3]
                    grad.last = lib_data[4]
                else:
                    grad.first = grad.waveform[0]
                    grad.last = grad.waveform[-1]
            else:
                if max(lib_data.shape) < 5:  # added by GT
                    grad.amplitude, grad.rise_time, grad.flat_time, grad.fall_time = [
                        lib_data[x] for x in range(4)
                    ]
                    grad.delay = 0
                else:
                    grad.amplitude, grad.rise_time, grad.flat_time, grad.fall_time, grad.delay = [
                        lib_data[x] for x in range(5)
                    ]
                grad.area = grad.amplitude * (
                    grad.flat_time + grad.rise_time / 2 + grad.fall_time / 2)
                grad.flat_area = grad.amplitude * grad.flat_time
            setattr(block, grad_channels[i - 1], grad)
    # ADC
    if event_ind[5] > 0:
        lib_data = self.adc_library.data[event_ind[5]]
        if len(lib_data) < 6:
            lib_data = np.append(lib_data, 0)

        adc = SimpleNamespace()
        adc.num_samples, adc.dwell, adc.delay, adc.freq_offset, adc.phase_offset, adc.dead_time = [
            lib_data[x] for x in range(6)
        ]
        adc.num_samples = int(adc.num_samples)
        adc.type = 'adc'
        block.adc = adc

    # Triggers
    if event_ind[6] > 0:
        # We have extensions - triggers, labels, etc.
        next_ext_id = event_ind[6]
        while next_ext_id != 0:
            ext_data = self.extensions_library.data[next_ext_id]
            # Format: ext_type, ext_id, next_ext_id
            ext_type = self.get_extension_type_string(ext_data[0])

            if ext_type == 'TRIGGERS':
                trigger_types = ['output', 'trigger']
                data = self.trigger_library.data[ext_data[1]]
                trigger = SimpleNamespace()
                trigger.type = trigger_types[int(data[0])]
                if data[0] == 0:
                    trigger_channels = ['osc0', 'osc1', 'ext1']
                    trigger.channel = trigger_channels[int(data[1])]
                elif data[0] == 1:
                    trigger_channels = ['physio1', 'physio2']
                    trigger.channel = trigger_channels[int(data[1])]
                else:
                    raise ValueError('Unsupported trigger event type')

                trigger.delay = data[2]
                trigger.duration = data[3]
                # Allow for multiple triggers per block
                if hasattr(block, 'trigger'):
                    block.trigger[len(block.trigger)] = trigger
                else:
                    block.trigger = {0: trigger}
            elif ext_type == 'LABELSET' or ext_type == 'LABELINC':
                label = SimpleNamespace()
                label.type = ext_type.lower()
                supported_labels = get_supported_labels()
                if ext_type == 'LABELSET':
                    data = self.label_set_library.data[ext_data[1]]
                else:
                    data = self.label_inc_library.data[ext_data[1]]

                label.label = supported_labels[data[1] - 1]
                label.value = data[0]
                # Allow for multiple labels per block
                if hasattr(block, 'label'):
                    block.label[len(block.label)] = label
                else:
                    block.label = {0: label}
            else:
                raise RuntimeError(f'Unknown extension ID {ext_data[0]}')

            next_ext_id = ext_data[2]

    return block
Exemplo n.º 5
0
def add_block(self, block_index: int, *args: SimpleNamespace) -> None:
    """
    Inserts PyPulseq block of sequence events into `self.dict_block_events` at position `block_index`. Also performs
    gradient checks.

    Parameters
    ----------
    block_index : int
        Index at which `SimpleNamespace` objects have to be inserted into `self.dict_block_events`.
    args : iterable[SimpleNamespace]
        Iterable of `SimpleNamespace` objects to be added to `self.dict_block_events`.

    Raises
    ------
    ValueError
        If trigger event that is passed is of unsupported control event type.
        If delay is set for a gradient even that starts with a non-zero amplitude.
    RuntimeError
        If two consecutive gradients to not have the same amplitude at the connection point.
        If the first gradient in the block does not start with 0.
        If a gradient that doesn't end at zero is not aligned to the block boundary.
    """
    events = block_to_events(args)
    block_duration = calc_duration(*events)
    self.dict_block_events[block_index] = np.zeros(7, dtype=np.int)
    duration = 0

    check_g = {}  # Key-value mapping of index and  pairs of gradients/times
    extensions = []

    for event in events:
        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 = [
                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.dict_block_events[block_index][1] = data_id
            duration = max(duration,
                           len(mag) * self.rf_raster_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 = [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')
            self.dict_block_events[block_index][idx] = grad_id
            duration = max(duration,
                           event.delay + 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 = [
                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.dict_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 = [
                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.dict_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 = [event.delay]
            delay_id, found = self.delay_library.find(data)
            if not found:
                self.delay_library.insert(delay_id, data)
            self.dict_block_events[block_index][0] = delay_id
            duration = max(duration, event.delay)
        elif event.type == 'output' or event.type == 'trigger':
            event_type = ['output', 'trigger'].index(event.type) + 1
            if event_type == 1:
                # Trigger codes supported by the Siemens interpreter as of May 2019
                event_channel = ['osc0', 'osc1', 'ext1'].index(
                    event.channel) + 1
            elif event_type == 2:
                # Trigger codes supported by the Siemens interpreter as of June 2019
                event_channel = ['physio1', 'physio2'].index(event.channel) + 1
            else:
                raise ValueError('Unsupported control event type.')

            data = [event_type, event_channel, event.delay, event.duration]
            trigger_id, found = self.trigger_library.find(data)
            if not found:
                self.trigger_library.insert(trigger_id, data)

            # Now we collect the list of extension objects and we will add it to the event table later
            ext = {
                'type': self.get_extension_type_ID('TRIGGERS'),
                'ref': trigger_id
            }
            extensions.append(ext)
            duration = max(duration, event.delay + event.duration)
        elif event.type == 'labelset':
            label_id = get_supported_labels().index(event.label) + 1
            data = [event.value, label_id]
            label_id2, found = self.label_set_library.find(data)
            if not found:
                self.label_set_library.insert(label_id2, data)

            ext = {
                'type': self.get_extension_type_ID('LABELSET'),
                'ref': label_id2
            }
            extensions.append(ext)
        elif event.type == 'labelinc':
            label_id = get_supported_labels().index(event.label) + 1
            data = [event.value, label_id]
            label_id2, found = self.label_inc_library.find(data)
            if not found:
                self.label_inc_library.insert(label_id2, data)

            ext = {
                'type': self.get_extension_type_ID('LABELINC'),
                'ref': label_id2
            }
            extensions.append(ext)

    # =========
    # ADD EXTENSIONS
    # =========
    if len(extensions) > 0:
        """
        Add extensions now... but it's tricky actually we need to check whether the exactly the same list of extensions 
        already exists, otherwise we have to create a new one... ooops, we have a potential problem with the key 
        mapping then... The trick is that we rely on the sorting of the extension IDs and then we can always find the 
        last one in the list by setting the reference to the next to 0 and then proceed with the other elements.
        """
        sort_idx = np.argsort([e['ref'] for e in extensions])
        extensions = np.take(extensions, sort_idx)
        all_found = True
        extension_id = 0
        for i in range(len(extensions)):
            data = [extensions[i]['type'], extensions[i]['ref'], extension_id]
            extension_id, found = self.extensions_library.find(data)
            all_found = all_found and found
            if not found:
                break

        if not all_found:
            # Add the list
            extension_id = 0
            for i in range(len(extensions)):
                data = [
                    extensions[i]['type'], extensions[i]['ref'], extension_id
                ]
                extension_id, found = self.extensions_library.find(data)
                if not found:
                    self.extensions_library.insert(extension_id, data)

        # Now we add the ID
        self.dict_block_events[block_index][6] = extension_id

    # =========
    # PERFORM GRADIENT CHECKS
    # =========
    for grad_to_check in check_g.values():

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

            if block_index > 1:
                prev_id = self.dict_block_events[block_index -
                                                 1][grad_to_check.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 RuntimeError(
                            'Two consecutive gradients need to have the same amplitude at the connection point'
                        )
                    elif prev_type == 'g':
                        last = prev_dat[4]
                        if abs(
                                last - grad_to_check.start[1]
                        ) > self.system.max_slew * self.system.grad_raster_time:
                            raise RuntimeError(
                                'Two consecutive gradients need to have the same amplitude at the connection point'
                            )
            else:
                raise RuntimeError(
                    'First gradient in the the first block has to start at 0.')

        if grad_to_check.stop[
                1] > self.system.max_slew * self.system.grad_raster_time and abs(
                    grad_to_check.stop[0] - block_duration) > 1e-7:
            raise RuntimeError(
                "A gradient that doesn't end at zero needs to be aligned to the block boundary."
            )

    eps = np.finfo(np.float).eps
    assert abs(duration - block_duration) < eps
    self.arr_block_durations.append(block_duration)