def compress_shape(decompressed_shape): """ Returns a run-length encoded compressed shape. Parameters ---------- decompressed_shape : ndarray Decompressed shape. Returns ------- compressed_shape : Holder A Holder object containing the shape of the compressed shape ndarray and the compressed shape ndarray itself. """ if decompressed_shape.shape[0] != 1: raise ValueError("input should be of shape (1,x)") if not isinstance(decompressed_shape, np.ndarray): raise TypeError("input should be of type numpy.ndarray") data = np.array([decompressed_shape[0][0]]) data = np.concatenate((data, np.diff(decompressed_shape[0]))) mask_changes = np.array([1]) diff_as_ones = (abs(np.diff(data)) > 1e-8).astype(int) mask_changes = np.concatenate((mask_changes, diff_as_ones)) vals = data[np.nonzero(mask_changes)].astype(float) k = np.array(np.nonzero(np.append(mask_changes, 1))) k = k.reshape((1, k.shape[1])) n = np.diff(k)[0] n_extra = (n - 2).astype(float) vals2 = np.copy(vals) vals2[np.where(n_extra < 0)] = np.NAN n_extra[np.where(n_extra < 0)] = np.NAN v = np.array([vals, vals2, n_extra]) v = np.concatenate(np.hsplit(v, v.shape[1])) finite_vals = np.isfinite(v) v = v[finite_vals] v_abs = abs(v) smallest_indices = np.where(v_abs < 1e-10) v[smallest_indices] = 0 compressed_shape = Holder() compressed_shape.num_samples = decompressed_shape.shape[1] compressed_shape.data = v.reshape([1, v.shape[0]]) return compressed_shape
def makedelay(d): """ Makes a Holder object for an delay Event. Parameters ---------- d : float Delay time, in seconds. Returns ------- delay : Holder Delay Event. """ delay = Holder() if d < 0: raise ValueError('Delay {:.2f} ms is invalid'.format(d * 1e3)) delay.type = 'delay' delay.delay = d return delay
def makearbitrarygrad(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_result = kwargs.get("max_grad", 0) max_slew_result = kwargs.get("max_slew", 0) max_grad = max_grad_result if max_grad_result > 0 else system.max_grad max_slew = max_slew_result if max_slew_result > 0 else system.max_slew g = waveform 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.array([[x * system.grad_raster_time for x in range(len(g[0]))]]) return grad
def get_block(self, block_index): """ Returns Block at position specified by block_index. Parameters ---------- block_index : int Index of Block to be retrieved. Returns ------- block : dict Block at position specified by block_index. """ block = {} event_ind = self.block_events[block_index] if event_ind[0] > 0: delay = Holder() delay.type = 'delay' delay.delay = self.delay_library.data[event_ind[0]] block['delay'] = delay elif event_ind[1] > 0: rf = Holder() rf.type = 'rf' lib_data = self.rf_library.data[event_ind[1]] amplitude, mag_shape, phase_shape = lib_data[0], lib_data[1], lib_data[ 2] shape_data = self.shape_library.data[mag_shape] compressed = Holder() compressed.num_samples = shape_data[0][0] compressed.data = shape_data[0][1:] compressed.data = compressed.data.reshape( (1, compressed.data.shape[0])) mag = decompress_shape(compressed) shape_data = self.shape_library.data[phase_shape] compressed.num_samples = shape_data[0][0] compressed.data = shape_data[0][1:] compressed.data = compressed.data.reshape( (1, compressed.data.shape[0])) phase = decompress_shape(compressed) rf.signal = 1j * 2 * np.pi * phase rf.signal = amplitude * mag * np.exp(rf.signal) rf.t = [(x * self.rf_raster_time) for x in range(1, max(mag.shape) + 1)] rf.t = np.reshape(rf.t, (1, len(rf.t))) rf.freq_offset = lib_data[3] rf.phase_offset = lib_data[4] if max(lib_data.shape) < 6: lib_data = np.append(lib_data, 0) rf.dead_time = lib_data[5] block['rf'] = rf grad_channels = ['gx', 'gy', 'gz'] for i in range(1, len(grad_channels) + 1): if event_ind[2 + (i - 1)] > 0: grad, compressed = Holder(), Holder() 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 type == 't' else 'grad' grad.channel = grad_channels[i - 1][1] if grad.type == 'grad': amplitude = lib_data[0] shape_id = lib_data[1] shape_data = self.shape_library.data[shape_id] compressed.num_samples = shape_data[0][0] compressed.data = np.array([shape_data[0][1:]]) g = decompress_shape(compressed) grad.waveform = amplitude * g grad.t = np.array([[ x * self.grad_raster_time for x in range(1, g.size + 1) ]]) else: grad.amplitude, grad.rise_time, grad.flat_time, grad.fall_time = [ lib_data[x] for x in range(4) ] grad.area = grad.amplitude * ( grad.flat_time + grad.rise_time / 2 + grad.fall_time / 2) grad.flat_area = grad.amplitude * grad.flat_time block[grad_channels[i - 1]] = grad if event_ind[5] > 0: lib_data = self.adc_library.data[event_ind[5]] if max(lib_data.shape) < 6: lib_data = np.append(lib_data, 0) adc = Holder() 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.type = 'adc' block['adc'] = adc return block
def makeblockpulse(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 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 = maketrapezoid(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 nargout > 1: return rf, gz else: return rf
def makearbitraryrf(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 if nargout > 1: if slice_thickness <= 0: raise ValueError('Slice thickness must be provided') if bandwidth <= 0: raise ValueError('Bandwdith 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 = maketrapezoid(**kwargs_for_trap) t_fill = [x * 1e-6 for x in range(1, round(gz.riseTime / 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))] return rf, gz
def makesincpulse(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) 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 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 = maketrapezoid(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 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))) # 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
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 maketrapezoid(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