def __init__(self, system=Opts()): self.system = system # EventLibrary.data is a dict self.shape_library = EventLibrary() self.rf_library = EventLibrary() self.grad_library = EventLibrary() self.adc_library = EventLibrary() self.delay_library = EventLibrary() self.block_events = {} self.rf_raster_time = self.system.rf_raster_time self.grad_raster_time = self.system.grad_raster_time
def compute(self): if 'ComputeEvents' in self.widgetEvents( ) or 'input' in self.portEvents(): out_dict = {} max_grad = self.getVal('Maximum Gradient') max_grad_unit = self.getVal('Maximum Gradient Unit') max_slew = self.getVal('Maximum Slew Rate') max_slew_unit = self.getVal('Maximum Slew Rate Unit') tr = self.getVal('Repetition Time (s)') te = self.getVal('Echo Time (s)') fov = self.getVal('Field of View') Nx = self.getVal('Nx') Ny = self.getVal('Ny') rise_time = self.getVal('Rise Time (s)') rf_dead_time = self.getVal('RF Dead Time (s)') adc_dead_time = self.getVal('ADC Dead Time (s)') rf_raster_time = self.getVal('RF Raster Time (s)') rf_raster_time = rf_raster_time if rf_raster_time != '' else 1e-6 grad_raster_time = self.getVal('Gradient Raster Time (s)') grad_raster_time = grad_raster_time if grad_raster_time != '' else 10e-6 kwargs_for_opts = { 'grad_unit': max_grad_unit, 'slew_unit': max_slew_unit } keys_for_opts = [ 'max_grad', 'max_slew', 'tr', 'te', 'fov', 'Nx', 'Ny', 'rise_tme', 'rf_dead_time', 'adc_dead_time', 'rf_raster_time', 'grad_raster_time' ] values_for_opts = [ max_grad, max_slew, tr, te, fov, Nx, Ny, rise_time, rf_dead_time, adc_dead_time, rf_raster_time, grad_raster_time ] for i in range(len(values_for_opts)): kwargs_for_opts[keys_for_opts[i]] = float(values_for_opts[i]) system = Opts(kwargs_for_opts) out_dict['system'] = system self.setData('output', out_dict) # To display the computed info in the TextBox self.setAttr('System limits', val=str(system)) return 0
def makeadc(kwargs): """ Makes a Holder object for an ADC Event. Parameters ---------- kwargs : dict Key value mappings of ADC Event parameters_params and values. Returns ------- adc : Holder ADC Event. """ num_samples = kwargs.get("num_samples", 0) system = kwargs.get("system", Opts()) dwell = kwargs.get("dwell", 0) duration = kwargs.get("duration", 0) delay = kwargs.get("delay", 0) freq_offset = kwargs.get("freq_offset", 0) phase_offset = kwargs.get("phase_offset", 0) adc = Holder() adc.type = 'adc' adc.num_samples = num_samples adc.dwell = dwell adc.delay = delay adc.freq_offset = freq_offset adc.phase_offset = phase_offset adc.dead_time = system.adc_dead_time if (dwell == 0 and duration == 0) or (dwell > 0 and duration > 0): raise ValueError("Either dwell or duration must be defined, not both") if duration > 0: # getcontext().prec = 4 adc.dwell = float(Decimal(duration) / Decimal(num_samples)) adc.duration = dwell * num_samples if dwell > 0 else 0 return adc
def makearbitrary_grad(kwargs): """ Makes a Holder object for an arbitrary gradient Event. Parameters ---------- kwargs : dict Key value mappings of RF Event parameters_params and values. Returns ------- grad : Holder Trapezoidal gradient Event configured based on supplied kwargs. """ channel = kwargs.get("channel", "z") system = kwargs.get("system", Opts()) waveform = kwargs.get("waveform") max_grad = kwargs.get("max_grad") if kwargs.get("max_grad", 0) > 0 else system.max_grad max_slew = kwargs.get("max_slew ") if kwargs.get( "max_slew ", 0) > 0 else system.max_slew g = np.reshape(waveform, (1, -1)) slew = (g[0][1:] - g[0][0:-1]) / system.grad_raster_time if max(abs(slew)) > max_slew: raise ValueError('Slew rate violation {:f}'.format( max(abs(slew)) / max_slew * 100)) if max(abs(g[0])) > max_grad: raise ValueError('Gradient amplitude violation {:f}'.format( max(abs(g)) / max_grad * 100)) grad = Holder() grad.type = 'grad' grad.channel = channel grad.waveform = g grad.t = np.arange(len(g[0])) * system.grad_raster_time grad.t = np.reshape(grad.t, (1, -1)) return grad
This is starter code to demonstrate a working example of the Gradient Recalled Echo as a pure Python implementation. """ from math import pi import numpy as np from pulseq.core.Sequence.sequence import Sequence from pulseq.core.calc_duration import calc_duration from pulseq.core.make_adc import makeadc from pulseq.core.make_delay import make_delay from pulseq.core.make_sinc import make_sinc_pulse from pulseq.core.make_trap import make_trapezoid from pulseq.core.opts import Opts kwargs_for_opts = {"rf_ring_down_time": 30e-6, "rf_dead_time": 100e-6} system = Opts(kwargs_for_opts) seq = Sequence(system) fov = 220e-3 Nx = 256 Ny = 256 slice_thickness = 5e-3 flip = 15 * pi / 180 kwargs_for_sinc = { "flip_angle": flip, "system": system, "duration": 4e-3, "slice_thickness": slice_thickness, "apodization": 0.5, "time_bw_product": 4
def compute(self): if 'File location' in self.widgetEvents(): file_location = self.getVal('File location') seq = Sequence(Opts()) seq.read(file_location) self.setData('output', {"seq": seq})
def make_arbitrary_rf(kwargs, nargout=1): """ Makes a Holder object for an arbitrary RF pulse Event. Parameters ---------- kwargs : dict Key value mappings of RF Event parameters_params and values. nargout: int Number of output arguments to be returned. Default is 1, only RF Event is returned. Passing any number greater than 1 will return the Gz Event along with the RF Event. Returns ------- rf : Holder RF Event configured based on supplied kwargs. gz : Holder Slice select trapezoidal gradient Event. """ flip_angle = kwargs.get("flip_angle") system = kwargs.get("system", Opts()) signal = kwargs.get("signal", 0) freq_offset = kwargs.get("freq_offset", 0) phase_offset = kwargs.get("phase_offset", 0) time_bw_product = kwargs.get("time_bw_product", 0) bandwidth = kwargs.get("bandwidth", 0) max_grad = kwargs.get("max_grad", 0) max_slew = kwargs.get("max_slew", 0) slice_thickness = kwargs.get("slice_thickness", 0) signal = signal / sum(signal * system.rf_raster_time) * flip_angle / (2 * pi) N = len(signal) duration = N * system.rf_raster_time t = [x * system.rfRasterTime for x in range(0, N)] rf = Holder() rf.type = 'rf' rf.signal = signal rf.t = t rf.freq_offset = freq_offset rf.phase_offset = phase_offset rf.rf_dead_time = system.rf_dead_time rf.ring_down_time = system.rf_ring_down_time if nargout > 1: if slice_thickness <= 0: raise ValueError('Slice thickness must be provided') if bandwidth <= 0: raise ValueError('Bandwidth of pulse must be provided') system.maxgrad = max_grad if max_grad > 0 else system.maxgrad system.max_slew = max_slew if max_slew > 0 else system.max_slew BW = bandwidth if time_bw_product > 0: BW = time_bw_product / duration amplitude = BW / slice_thickness area = amplitude * duration kwargs_for_trap = {"channel": 'z', "system": system, "flat_time": duration, "flat_area": area} gz = make_trapezoid(**kwargs_for_trap) t_fill = np.arange(1, round(gz.rise_time / 1e-6) + 1) * 1e-6 rf.t = [t_fill, rf.t + t_fill[-1], t_fill + rf.t[-1] + t_fill[-1]] rf.signal = [np.zeros(len(t_fill)), rf.signal, np.zeros(len(t_fill))] if rf.ring_down_time > 0: t_fill = np.arange(1, round(gz.rise_time / 1e-6) + 1) * 1e-6 rf.t = [rf.t, rf.t[-1] + t_fill] rf.signal = [rf.signal, np.zeros(len(t_fill))] return rf, gz
def make_block_pulse(kwargs, nargout=1): """ Makes a Holder object for an RF pulse Event. Parameters ---------- kwargs : dict Key value mappings of RF Event parameters_params and values. nargout: int Number of output arguments to be returned. Default is 1, only RF Event is returned. Passing any number greater than 1 will return the Gz Event along with the RF Event. Returns ------- Tuple consisting of: rf : Holder RF Event configured based on supplied kwargs. gz : Holder Slice select trapezoidal gradient Event. """ flip_angle = kwargs.get("flip_angle") system = kwargs.get("system", Opts()) duration = kwargs.get("duration", 0) freq_offset = kwargs.get("freq_offset", 0) phase_offset = kwargs.get("phase_offset", 0) time_bw_product = kwargs.get("time_bw_product", 4) bandwidth = kwargs.get("bandwidth", 0) max_grad = kwargs.get("max_grad", 0) max_slew = kwargs.get("max_slew", 0) slice_thickness = kwargs.get("slice_thickness", 0) if duration == 0: if time_bw_product > 0: duration = time_bw_product / bandwidth elif bandwidth > 0: duration = 1 / (4 * bandwidth) else: raise ValueError('Either bandwidth or duration must be defined') BW = 1 / (4 * duration) N = round(duration / 1e-6) t = [x * system.rf_raster_time for x in range(N)] signal = flip_angle / (2 * np.pi) / duration * np.ones(len(t)) rf = Holder() rf.type = 'rf' rf.signal = signal rf.t = t rf.freq_offset = freq_offset rf.phase_offset = phase_offset rf.dead_time = system.rf_dead_time rf.ring_down_time = system.rf_ring_down_time fill_time = 0 if nargout > 1: if slice_thickness < 0: raise ValueError('Slice thickness must be provided') if max_grad > 0: system.max_grad = max_grad if max_slew > 0: system.max_slew = max_slew amplitude = BW / slice_thickness area = amplitude * duration kwargs_for_trap = { 'channel': 'z', 'system': system, 'flat_time': duration, 'flat_area': area } gz = make_trapezoid(kwargs_for_trap) fill_time = gz.rise_time t_fill = np.array( [x * 1e-6 for x in range(int(round(fill_time / 1e-6)))]) rf.t = np.array( [t_fill, rf.t + t_fill[-1], t_fill + rf.t[-1] + t_fill[-1]]) rf.signal = np.array( [np.zeros(t_fill.size), rf.signal, np.zeros(t_fill.size)]) if fill_time < rf.dead_time: fill_time = rf.dead_time - fill_time t_fill = np.array( [x * 1e-6 for x in range(int(round(fill_time / 1e-6)))]) rf.t = np.insert(rf.t, 0, t_fill) + t_fill[-1] rf.t = np.reshape(rf.t, (1, len(rf.t))) rf.signal = np.insert(rf.signal, 0, np.zeros(t_fill.size)) rf.signal = np.reshape(rf.signal, (1, len(rf.signal))) if rf.ring_down_time > 0: t_fill = np.arange(1, round(rf.ring_down_time / 1e-6) + 1) * 1e-6 rf.t = [rf.t, rf.t[-1] + t_fill] rf.signal = [rf.signal, np.zeros(len(t_fill))] if nargout > 1: return rf, gz else: return rf
def make_trapezoid(kwargs): """ Makes a Holder object for an trapezoidal gradient Event. Parameters ---------- kwargs : dict Key value mappings of trapezoidal gradient Event parameters_params and values. Returns ------- grad : Holder Trapezoidal gradient Event configured based on supplied kwargs. """ channel = kwargs.get("channel", "z") system = kwargs.get("system", Opts()) duration = kwargs.get("duration", 0) area_result = kwargs.get("area", -1) flat_time_result = kwargs.get("flat_time", 0) flat_area_result = kwargs.get("flat_area", -1) amplitude_result = kwargs.get("amplitude", -1) max_grad = kwargs.get("max_grad", 0) max_slew = kwargs.get("max_slew", 0) rise_time = kwargs.get("rise_time", 0) max_grad = max_grad if max_grad > 0 else system.max_grad max_slew = max_slew if max_slew > 0 else system.max_slew rise_time = rise_time if rise_time > 0 else system.rise_time if area_result == -1 and flat_area_result == -1 and amplitude_result == -1: raise ValueError('Must supply either ' 'area' ', ' 'flat_area' ' or ' 'amplitude' '') if flat_time_result > 0: amplitude = amplitude_result if (amplitude_result != -1) else ( flat_area_result / flat_time_result) if rise_time == 0: rise_time = abs(amplitude) / max_slew rise_time = ceil( rise_time / system.grad_raster_time) * system.grad_raster_time fall_time, flat_time = rise_time, flat_time_result elif duration > 0: if amplitude_result != -1: amplitude = amplitude_result else: if rise_time == 0: dC = 1 / abs(2 * max_slew) + 1 / abs(2 * max_slew) amplitude = (duration - sqrt( pow(duration, 2) - 4 * abs(area_result) * dC)) / (2 * dC) else: amplitude = area_result / (duration - rise_time) if rise_time == 0: rise_time = ceil(amplitude / max_slew / system.grad_raster_time) * system.grad_raster_time fall_time = rise_time flat_time = (duration - rise_time - fall_time) amplitude = area_result / (rise_time / 2 + fall_time / 2 + flat_time ) if amplitude_result == -1 else amplitude else: raise ValueError('Must supply a duration') if abs(amplitude) > max_grad: raise ValueError("Amplitude violation") grad = Holder() grad.type = "trap" grad.channel = channel grad.amplitude = amplitude grad.rise_time = rise_time grad.flat_time = flat_time grad.fall_time = fall_time grad.area = amplitude * (flat_time + rise_time / 2 + fall_time / 2) grad.flat_area = amplitude * flat_time return grad
def make_se_epi(self): kwargs_for_opts = { "max_grad": self.max_grad, "grad_unit": "mT/m", "max_slew": self.max_slew, "slew_unit": "T/m/s", "rf_dead_time": 10e-6, "adc_dead_time": 10e-6 } system = Opts(kwargs_for_opts) seq = Sequence(system) slice_thickness = 3e-3 flip = 90 * pi / 180 kwargs_for_sinc = { "flip_angle": flip, "system": system, "duration": 2.5e-3, "slice_thickness": slice_thickness, "apodization": 0.5, "time_bw_product": 4 } rf, gz = make_sinc_pulse(kwargs_for_sinc, 2) # plt.plot(rf.t[0], rf.signal[0]) # plt.show() delta_k = 1 / self.fov k_width = self.Nx * delta_k readout_time = self.Nx * 4e-6 kwargs_for_gx = { "channel": 'x', "system": system, "flat_area": k_width, "flat_time": readout_time } gx = make_trapezoid(kwargs_for_gx) kwargs_for_adc = { "num_samples": self.Nx, "system": system, "duration": gx.flat_time, "delay": gx.rise_time } adc = makeadc(kwargs_for_adc) pre_time = 8e-4 kwargs_for_gxpre = { "channel": 'x', "system": system, "area": -gx.area / 2, "duration": pre_time } gx_pre = make_trapezoid(kwargs_for_gxpre) kwargs_for_gz_reph = { "channel": 'z', "system": system, "area": -gz.area / 2, "duration": pre_time } gz_reph = make_trapezoid(kwargs_for_gz_reph) kwargs_for_gy_pre = { "channel": 'y', "system": system, "area": -self.Ny / 2 * delta_k, "duration": pre_time } gy_pre = make_trapezoid(kwargs_for_gy_pre) dur = ceil(2 * sqrt(delta_k / system.max_slew) / 10e-6) * 10e-6 kwargs_for_gy = { "channel": 'y', "system": system, "area": delta_k, "duration": dur } gy = make_trapezoid(kwargs_for_gy) flip = 180 * pi / 180 kwargs_for_sinc = { "flip_angle": flip, "system": system, "duration": 2.5e-3 } rf180 = make_block_pulse(kwargs_for_sinc) kwargs_for_gz_spoil = { "channel": 'z', "system": system, "area": gz.area * 2, "duration": 3 * pre_time } gz_spoil = make_trapezoid(kwargs_for_gz_spoil) TE = self.te duration_to_center = (self.Nx / 2 + 0.5) * calc_duration( gx) + self.Ny / 2 * calc_duration(gy) delayTE1 = TE / 2 - calc_duration(gz) / 2 - pre_time - calc_duration( gz_spoil) - calc_duration(rf180) / 2 delayTE2 = TE / 2 - calc_duration(rf180) / 2 - calc_duration( gz_spoil) - duration_to_center delay1 = make_delay(delayTE1) delay2 = make_delay(delayTE2) seq.add_block(rf, gz) seq.add_block(gx_pre, gy_pre, gz_reph) seq.add_block(delay1) seq.add_block(gz_spoil) seq.add_block(rf180) seq.add_block(gz_spoil) seq.add_block(delay2) for i in range(self.Ny): seq.add_block(gx, adc) seq.add_block(gy) gx.amplitude = -gx.amplitude seq.add_block(make_delay(1)) return seq
def make_sinc_pulse(kwargs, nargout=1): """ Makes a Holder object for an RF pulse Event. Parameters ---------- kwargs : dict Key value mappings of RF Event parameters_params and values. nargout: int Number of output arguments to be returned. Default is 1, only RF Event is returned. Passing any number greater than 1 will return the Gz Event along with the RF Event. Returns ------- rf : Holder RF Event configured based on supplied kwargs. gz : Holder Slice select trapezoidal gradient Event. """ flip_angle = kwargs.get("flip_angle") system = kwargs.get("system", Opts()) duration = kwargs.get("duration", 0) freq_offset = kwargs.get("freq_offset", 0) phase_offset = kwargs.get("phase_offset", 0) time_bw_product = kwargs.get("time_bw_product", 4) apodization = kwargs.get("apodization", 0) max_grad = kwargs.get("max_grad", 0) max_slew = kwargs.get("max_slew", 0) slice_thickness = kwargs.get("slice_thickness", 0) BW = time_bw_product / duration alpha = apodization N = int(round(duration / 1e-6)) t = np.zeros((1, N)) for x in range(1, N + 1): t[0][x - 1] = x * system.rf_raster_time tt = t - (duration / 2) window = np.zeros((1, tt.shape[1])) for x in range(0, tt.shape[1]): window[0][x] = 1.0 - alpha + alpha * np.cos(2 * np.pi * tt[0][x] / duration) signal = np.multiply(window, np.sinc(BW * tt)) flip = np.sum(signal) * system.rf_raster_time * 2 * np.pi signal = signal * flip_angle / flip rf = Holder() rf.type = 'rf' rf.signal = signal rf.t = t rf.freq_offset = freq_offset rf.phase_offset = phase_offset rf.dead_time = system.rf_dead_time rf.ring_down_time = system.rf_ring_down_time fill_time = 0 if nargout > 1: if slice_thickness == 0: raise ValueError('Slice thickness must be provided') system.max_grad = max_grad if max_grad > 0 else system.max_grad system.max_slew = max_slew if max_slew > 0 else system.max_slew amplitude = BW / slice_thickness area = amplitude * duration kwargs_for_trap = {"channel": 'z', "system": system, "flat_time": duration, "flat_area": area} gz = make_trapezoid(kwargs_for_trap) fill_time = gz.rise_time nfill_time = int(round(fill_time / 1e-6)) t_fill = np.zeros((1, nfill_time)) for x in range(1, nfill_time + 1): t_fill[0][x - 1] = x * 1e-6 temp = np.concatenate((t_fill[0], rf.t[0] + t_fill[0][-1])) temp = temp.reshape((1, len(temp))) rf.t = np.resize(rf.t, temp.shape) rf.t[0] = temp z = np.zeros((1, t_fill.shape[1])) temp2 = np.concatenate((z[0], rf.signal[0])) temp2 = temp2.reshape((1, len(temp2))) rf.signal = np.resize(rf.signal, temp2.shape) rf.signal[0] = temp2 # Add dead time to start of pulse, if required if fill_time < rf.dead_time: fill_time = rf.dead_time - fill_time t_fill = (np.arange(int(round(fill_time / 1e-6))) * 1e-6)[np.newaxis, :] rf.t = np.concatenate((t_fill, (rf.t + t_fill[0, -1])), axis=1) rf.signal = np.concatenate((np.zeros(t_fill.shape), rf.signal), axis=1) if rf.ring_down_time > 0: t_fill = (np.arange(1, round(rf.ring_down_time / 1e-6) + 1) * 1e-6)[np.newaxis, :] rf.t = np.concatenate((rf.t, rf.t[0, -1] + t_fill), axis=1) rf.signal = np.concatenate((rf.signal, np.zeros(t_fill.shape)), axis=1) # Following 2 lines of code are workarounds for numpy returning 3.14... for np.angle(-0.00...) negative_zero_indices = np.where(rf.signal == -0.0) rf.signal[negative_zero_indices] = 0 if nargout > 1: return rf, gz else: return rf