diff --git a/pypulseq/Sequence/sequence.py b/pypulseq/Sequence/sequence.py index c4e6d45..9bfb3de 100755 --- a/pypulseq/Sequence/sequence.py +++ b/pypulseq/Sequence/sequence.py @@ -1,726 +1,725 @@ import math from collections import OrderedDict from types import SimpleNamespace from typing import Tuple from typing import Union from warnings import warn import matplotlib as mpl import numpy as np from matplotlib import pyplot as plt from pypulseq import major, minor, revision from pypulseq.Sequence import block from pypulseq.Sequence import parula from pypulseq.Sequence.read_seq import read from pypulseq.Sequence.test_report import test_report as ext_test_report from pypulseq.Sequence.write_seq import write from pypulseq.calc_rf_center import calc_rf_center from pypulseq.check_timing import check_timing as ext_check_timing from pypulseq.decompress_shape import decompress_shape from pypulseq.event_lib import EventLibrary from pypulseq.opts import Opts from pypulseq.points_to_waveform import points_to_waveform from pypulseq.supported_labels import get_supported_labels class Sequence: - version_major = major - version_minor = minor - version_revision = revision + version_major: int = major + version_minor: int = minor + version_revision: int = revision if isinstance(version_revision, str): - version_revision = version_revision.split('.')[0] - version_revision = int(version_revision) + version_revision = int(version_revision[0]) def __init__(self, system=Opts()): # ========= # EVENT LIBRARIES # ========= self.adc_library = EventLibrary() # Library of ADC events self.delay_library = EventLibrary() # Library of delay events # Library of extension events. Extension events form single-linked zero-terminated lists self.extensions_library = EventLibrary() self.grad_library = EventLibrary() # Library of gradient events self.label_inc_library = EventLibrary() # Library of Label(inc) events (reference from the extensions library) self.label_set_library = EventLibrary() # Library of Label(set) events (reference from the extensions library) self.rf_library = EventLibrary() # Library of RF events self.shape_library = EventLibrary() # Library of compressed shapes self.trigger_library = EventLibrary() # Library of trigger events # ========= # OTHER # ========= self.system = system self.rf_raster_time = self.system.rf_raster_time # RF raster time (system dependent) self.grad_raster_time = self.system.grad_raster_time # Gradient raster time (system dependent) self.dict_block_events = OrderedDict() # Event table self.dict_definitions = OrderedDict() # Optional sequence dict_definitions self.arr_block_durations = [] # Cache of block durations self.arr_extension_numeric_idx = [] # numeric IDs of the used extensions self.arr_extension_string_idx = [] # string IDs of the used extensions def __str__(self): s = "Sequence:" s += "\nshape_library: " + str(self.shape_library) s += "\nrf_library: " + str(self.rf_library) s += "\ngrad_library: " + str(self.grad_library) s += "\nadc_library: " + str(self.adc_library) s += "\ndelay_library: " + str(self.delay_library) s += "\nextensions_library: " + str(self.extensions_library) # inserted for trigger support by mveldmann s += "\nrf_raster_time: " + str(self.rf_raster_time) s += "\ngrad_raster_time: " + str(self.grad_raster_time) s += "\ndict_block_events: " + str(len(self.dict_block_events)) return s def add_block(self, *args: SimpleNamespace) -> None: """ Adds event(s) as a block to `Sequence`. Parameters ---------- args Event or list of events to be added as a block to `Sequence`. """ block.add_block(self, len(self.dict_block_events) + 1, *args) def calculate_kspace(self, trajectory_delay: int = 0) -> Tuple[np.array, np.array, np.array, np.array, np.array]: """ Calculates the k-space trajectory of the entire pulse sequence. Parameters ---------- trajectory_delay : int, default=0 Compensation factor in millis to align ADC and gradients in the reconstruction. Returns ------- k_traj_adc : numpy.array K-space trajectory sampled at `t_adc` timepoints. k_traj : numpy.array K-space trajectory of the entire pulse sequence. t_excitation : numpy.array Excitation timepoints. t_refocusing : numpy.array Refocusing timepoints. t_adc : numpy.array Sampling timepoints. """ # Initialise the counters and accumulator objects count_excitation = 0 count_refocusing = 0 count_adc_samples = 0 # Loop through the blocks to prepare preallocations for block_counter in range(len(self.dict_block_events)): block = self.get_block(block_counter + 1) if hasattr(block, 'rf'): if not hasattr(block.rf, 'use') or block.rf.use != 'refocusing': count_excitation += 1 else: count_refocusing += 1 if hasattr(block, 'adc'): count_adc_samples += int(block.adc.num_samples) t_excitation = np.zeros(count_excitation) t_refocusing = np.zeros(count_refocusing) k_time = np.zeros(count_adc_samples) current_duration = 0 count_excitation = 0 count_refocusing = 0 kc_outer = 0 traj_recon_delay = trajectory_delay # Go through the blocks and collect RF and ADC timing data for block_counter in range(len(self.dict_block_events)): block = self.get_block(block_counter + 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[count_excitation] = current_duration + t count_excitation += 1 else: t_refocusing[count_refocusing] = current_duration + t count_refocusing += 1 if hasattr(block, 'adc'): _k_time = np.arange(block.adc.num_samples) + 0.5 _k_time = _k_time * block.adc.dwell + block.adc.delay + current_duration + traj_recon_delay k_time[kc_outer:kc_outer + block.adc.num_samples] = _k_time kc_outer += block.adc.num_samples current_duration += self.arr_block_durations[block_counter] # Now calculate the actual k-space trajectory based on the gradient waveforms gw = self.gradient_waveforms() i_excitation = np.round(t_excitation / self.grad_raster_time) i_refocusing = np.round(t_refocusing / self.grad_raster_time) i_periods = np.sort([1, *(i_excitation + 1), *(i_refocusing + 1), gw.shape[1] + 1]).astype(np.int) # i_periods -= 1 # Python is 0-indexed ii_next_excitation = min(len(i_excitation), 1) ii_next_refocusing = min(len(i_refocusing), 1) k_traj = np.zeros_like(gw) k = np.zeros((3, 1)) for i in range(len(i_periods) - 1): i_period_end = i_periods[i + 1] - 1 k_period = np.concatenate((k, gw[:, i_periods[i] - 1:i_period_end] * self.grad_raster_time), axis=1) k_period = np.cumsum(k_period, axis=1) k_traj[:, i_periods[i] - 1:i_period_end] = k_period[:, 1:] k = k_period[:, -1] if ii_next_excitation > 0 and i_excitation[ii_next_excitation - 1] == i_period_end: k[:] = 0 k_traj[:, i_period_end - 1] = np.nan ii_next_excitation = min(len(i_excitation), ii_next_excitation + 1) if ii_next_refocusing > 0 and i_refocusing[ii_next_refocusing - 1] == i_period_end: k = -k ii_next_refocusing = min(len(i_refocusing), ii_next_refocusing + 1) k = k.reshape((-1, 1)) # To be compatible with np.concatenate k_traj_adc = [] for _k_traj_row in k_traj: result = np.interp(xp=np.array(range(1, k_traj.shape[1] + 1)) * self.grad_raster_time, fp=_k_traj_row, x=k_time) k_traj_adc.append(result) k_traj_adc = np.stack(k_traj_adc) t_adc = k_time return k_traj_adc, k_traj, t_excitation, t_refocusing, t_adc def check_timing(self) -> Tuple[bool, list]: """ Check timing of the events in each block based on grad raster time system limit. Returns ------- is_ok : bool Boolean flag indicating timing errors. error_report : str Error report in case of timing errors. """ error_report = [] is_ok = True num_blocks = len(self.dict_block_events) total_duration = 0 for block_counter in range(num_blocks): block = self.get_block(block_counter + 1) event_names = ['rf', 'gx', 'gy', 'gz', 'adc', 'delay'] ind = [hasattr(block, attr) for attr in event_names] events = [getattr(block, event_names[i]) for i in range(len(event_names)) if ind[i] == 1] res, rep, duration = ext_check_timing(self.system, *events) is_ok = is_ok and res if len(rep) != 0: error_report.append(f'Event: {block_counter} - {rep}\n') total_duration += duration # Check if all the gradients in the last block are ramped down properly if len(events) != 0: for e in range(len(events)): if not isinstance(events[e], list) and events[e].type == 'grad': if events[e].last != 0: error_report.append( f'Event {block_counter} gradients do not ramp to 0 at the end of the sequence') self.set_definition('Total duration', total_duration) return is_ok, error_report def duration(self) -> Tuple[int, int, np.ndarray]: """ Returns the total duration of this sequence, and the total count of blocks and events. Returns ------- duration : int Duration of this sequence in millis. num_blocks : int Number of blocks in this sequence. event_count : numpy.ndarray Number of events in this sequence. """ num_blocks = len(self.dict_block_events) event_count = np.zeros(len(self.dict_block_events[1])) duration = 0 for block_counter in range(num_blocks): event_count += self.dict_block_events[block_counter + 1] > 0 duration += self.arr_block_durations[block_counter] return duration, num_blocks, event_count def flip_grad_axis(self, axis: str) -> None: """ Convenience function to invert all gradients along specified axis. Parameters ---------- axis : str Gradients to invert or scale. Must be one of 'x', 'y' or 'z'. """ self.mod_grad_axis(axis, modifier=-1) def get_block(self, block_index: int) -> SimpleNamespace: """ Retrieves block of events identified by `block_index` from `Sequence`. Parameters ---------- block_index : int Index of block to be retrieved from `Sequence`. Returns ------- SimpleNamespace Event identified by `block_index`. """ return block.get_block(self, block_index) def get_definition(self, key: str) -> str: """ Retrieves definition identified by `key` from `Sequence`. Returns `None` if no matching definition is found. Parameters ---------- key : str Key of definition to retrieve. Returns ------- str Definition identified by `key` if found, else returns ''. """ if key in self.dict_definitions: return self.dict_definitions[key] else: return '' def get_extension_type_ID(self, extension_string: str) -> int: """ Get numeric extension ID for `extension_string`. Will automatically create a new ID if unknown. Parameters ---------- extension_string : str Given string extension ID. Returns ------- extension_id : int Numeric ID for given string extension ID. """ if extension_string not in self.arr_extension_string_idx: if len(self.arr_extension_numeric_idx) == 0: extension_id = 1 else: extension_id = 1 + max(self.arr_extension_numeric_idx) self.arr_extension_numeric_idx.append(extension_id) self.arr_extension_string_idx.append(extension_string) assert len(self.arr_extension_numeric_idx) == len(self.arr_extension_string_idx) else: num = self.arr_extension_string_idx.index(extension_string) extension_id = self.arr_extension_numeric_idx[num] return extension_id def get_extension_type_string(self, extension_id: int) -> str: """ Get string extension ID for `extension_id`. Parameters ---------- extension_id : int Given numeric extension ID. Returns ------- extension_str : str String ID for the given numeric extension ID. Raises ------ ValueError If given numeric extension ID is unknown. """ if extension_id in self.arr_extension_numeric_idx: num = self.arr_extension_numeric_idx.index(extension_id) else: raise ValueError(f'Extension for the given ID - {extension_id} - is unknown.') extension_str = self.arr_extension_string_idx[num] return extension_str def gradient_waveforms(self) -> np.ndarray: """ Decompress the entire gradient waveform. Returns an array of shape `gradient_axes x timepoints`. `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 block_counter in range(num_blocks): block = self.get_block(block_counter + 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(times=t, amplitudes=trap_form, grad_raster_time=self.grad_raster_time) else: waveform = np.zeros(tn + 1) if len(waveform) != np.sum(np.isfinite(waveform)): warn('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 is a workaround """ l1, l2 = int(t0_n + nt_start), int(t0_n + nt_start + len(waveform)) if l2 > grad_waveforms.shape[1]: z = np.zeros((grad_waveforms.shape[0], l2 - grad_waveforms.shape[1])) grad_waveforms = np.hstack((grad_waveforms, z)) grad_waveforms[j, l1:l2] = waveform t0 += self.arr_block_durations[block_counter] t0_n = round(t0 / self.grad_raster_time) return grad_waveforms def mod_grad_axis(self, axis: str, modifier: int) -> None: """ Invert or scale all gradients along the corresponding axis/channel. The function acts on all gradient objects already added to the sequence object. Parameters ---------- axis : str Gradients to invert or scale. Must be one of 'x', 'y' or 'z'. modifier : int Scaling value. Raises ------ ValueError If invalid `axis` is passed. Must be one of 'x', 'y','z'. RuntimeError If same gradient event is used on multiple axes. """ if axis not in ['x', 'y', 'z']: raise ValueError(f"Invalid axis. Must be one of 'x', 'y','z'. Passed: {axis}") channel_num = ['x', 'y', 'z'].index(axis) other_channels = [0, 1, 2] other_channels.remove(channel_num) # Go through all event table entries and list gradient objects in the library all_grad_events = np.array(list(self.dict_block_events.values())) all_grad_events = all_grad_events[:, 2:5] selected_events = np.unique(all_grad_events[:, channel_num]) selected_events = selected_events[selected_events != 0] other_events = np.unique(all_grad_events[:, other_channels]) if len(np.intersect1d(selected_events, other_events)) > 0: raise RuntimeError('mod_grad_axis does not yet support the same gradient event used on multiple axes.') for i in range(len(selected_events)): self.grad_library.data[selected_events[i]][0] *= modifier if self.grad_library.type[selected_events[i]] == 'g' and self.grad_library.lengths[selected_events[i]] == 5: # Need to update first and last fields self.grad_library.data[selected_events[i]][3] *= modifier self.grad_library.data[selected_events[i]][4] *= modifier 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() def read(self, file_path: str) -> None: """ Read `.seq` file from `file_path`. Parameters ---------- file_path : str Path to `.seq` file to be read. """ read(self, file_path) def rf_from_lib_data(self, lib_data: list) -> SimpleNamespace: """ Construct RF object from `lib_data`. Parameters ---------- lib_data : list RF envelope. Returns ------- rf : SimpleNamespace RF object constructed from lib_data. """ rf = SimpleNamespace() rf.type = 'rf' amplitude, mag_shape, phase_shape = lib_data[0], lib_data[1], lib_data[2] shape_data = self.shape_library.data[mag_shape] compressed = SimpleNamespace() compressed.num_samples = shape_data[0] compressed.data = shape_data[1:] mag = decompress_shape(compressed) shape_data = self.shape_library.data[phase_shape] compressed.num_samples = shape_data[0] compressed.data = shape_data[1:] phase = decompress_shape(compressed) rf.signal = amplitude * mag * np.exp(1j * 2 * np.pi * phase) rf.t = np.arange(1, len(mag) + 1) * self.rf_raster_time rf.delay = lib_data[3] rf.freq_offset = lib_data[4] rf.phase_offset = lib_data[5] if len(lib_data) < 7: lib_data = np.append(lib_data, 0) rf.dead_time = lib_data[6] if len(lib_data.shape) < 8: lib_data = np.append(lib_data, 0) rf.ringdown_time = lib_data[7] if len(lib_data.shape) < 9: lib_data = np.append(lib_data, 0) use_cases = {1: 'excitation', 2: 'refocusing', 3: 'inversion'} if lib_data[8] in use_cases: rf.use = use_cases[lib_data[8]] return rf def set_definition(self, key: str, val: Union[int, list, np.ndarray, str, tuple]) -> None: """ Sets custom definition to the `Sequence`. Parameters ---------- key : str Definition key. val : int, list, numpy.ndarray, str or tuple Definition value. """ if key == 'FOV': if max(val) > 1: text = 'Definition FOV uses values exceeding 1 m.' text += 'New Pulseq interpreters expect values in units of meters.' warn(text) self.dict_definitions[key] = val def set_extension_string_ID(self, extension_str: str, extension_id: int) -> None: """ Set numeric ID for the given string extension ID. Parameters ---------- extension_str : str Given string extension ID. extension_id : int Given numeric extension ID. Raises ------ ValueError If given numeric or string extension ID is not unique. """ if extension_str in self.arr_extension_string_idx or extension_id in self.arr_extension_numeric_idx: raise ValueError('Numeric or string ID is not unique') self.arr_extension_numeric_idx.append(extension_id) self.arr_extension_string_idx.append(extension_str) assert len(self.arr_extension_numeric_idx) == len(self.arr_extension_string_idx) def test_report(self) -> str: """ Analyze the sequence and return a text report. """ return ext_test_report(self) def write(self, name: str) -> None: """ Writes the calling `Sequence` object as a `.seq` file with filename `name`. Parameters ---------- name :str Filename of `.seq` file to be written to disk. """ write(self, name) diff --git a/pypulseq/__init__.py b/pypulseq/__init__.py index 75b9a80..43a5d01 100755 --- a/pypulseq/__init__.py +++ b/pypulseq/__init__.py @@ -1,28 +1,32 @@ -major = 1 -minor = 3 -revision = 1 +from pathlib import Path +path_version = Path(__file__).parent.parent / 'VERSION' +with open(str(path_version), 'r') as version_file: + version = version_file.read().strip().split('.') + major, minor, revision = [int(v) for v in version] + +from pypulseq.SAR.SAR_calc import calc_SAR from pypulseq.Sequence.sequence import Sequence from pypulseq.add_gradients import add_gradients from pypulseq.align import align from pypulseq.calc_duration import calc_duration from pypulseq.calc_ramp import calc_ramp from pypulseq.calc_rf_center import calc_rf_center from pypulseq.make_adc import make_adc from pypulseq.make_arbitrary_rf import make_arbitrary_rf from pypulseq.make_block_pulse import make_block_pulse from pypulseq.make_delay import make_delay from pypulseq.make_digital_output_pulse import make_digital_output_pulse from pypulseq.make_extended_trapezoid import make_extended_trapezoid from pypulseq.make_extended_trapezoid_area import make_extended_trapezoid_area from pypulseq.make_gauss_pulse import make_gauss_pulse from pypulseq.make_label import make_label from pypulseq.make_sinc_pulse import make_sinc_pulse from pypulseq.make_trap_pulse import make_trapezoid from pypulseq.make_trigger import make_trigger from pypulseq.opts import Opts from pypulseq.points_to_waveform import points_to_waveform from pypulseq.split_gradient import split_gradient from pypulseq.split_gradient_at import split_gradient_at from pypulseq.supported_labels import get_supported_labels from pypulseq.traj_to_grad import traj_to_grad