def make_sms(flip_angle: float, time_bw_product: float = 4, duration: float = 0, system: Opts = Opts(), pulse_cfg: pulse_opts = pulse_opts(), disp: bool = False): N = int(round(duration / 1e-6)) t = np.arange(1, N + 1) * system.rf_raster_time # Insert sigpy ptype = pulse_cfg.ptype ftype = pulse_cfg.ftype d1 = pulse_cfg.d1 d2 = pulse_cfg.d2 cancel_alpha_phs = pulse_cfg.cancel_alpha_phs n_bands = pulse_cfg.n_bands band_sep = pulse_cfg.band_sep phs_0_pt = pulse_cfg.phs_0_pt pulse_in = rf.slr.dzrf(n=N, tb=time_bw_product, ptype=ptype, ftype=ftype, d1=d1, d2=d2, cancel_alpha_phs=cancel_alpha_phs) pulse = rf.multiband.mb_rf(pulse_in, n_bands=n_bands, band_sep=band_sep, phs_0_pt=phs_0_pt) flip = np.sum(pulse) * system.rf_raster_time * 2 * np.pi signal = pulse * flip_angle / flip if (disp): pl.LinePlot(pulse_in) pl.LinePlot(pulse) pl.LinePlot(signal) # Simulate it [a, b] = rf.sim.abrm( pulse, np.arange(-20 * time_bw_product, 20 * time_bw_product, 40 * time_bw_product / 2000), True) Mxy = 2 * np.multiply(np.conj(a), b) pl.LinePlot(Mxy) return signal, t, pulse
def test_slr(self): print('Testing SLR design') time_bw_product = 4 slice_thickness = 3e-3 # Slice thickness flip_angle = np.pi / 2 # Set system limits system = Opts(max_grad=32, grad_unit='mT/m', max_slew=130, slew_unit='T/m/s', rf_ringdown_time=30e-6, rf_dead_time=100e-6) pulse_cfg = pulse_opts(pulse_type='slr', ptype='st', ftype='ls', d1=0.01, d2=0.01, cancel_alpha_phs=False, n_bands=3, band_sep=20, phs_0_pt='None') rfp, gz, _, pulse = sp.sigpy_n_seq(flip_angle=flip_angle, system=system, duration=3e-3, slice_thickness=slice_thickness, time_bw_product=4, return_gz=True, pulse_cfg=pulse_cfg) [a, b] = rf.sim.abrm(pulse, np.arange(-20 * time_bw_product, 20 * time_bw_product, 40 * time_bw_product / 2000), True) Mxy = 2 * np.multiply(np.conj(a), b) # pl.LinePlot(Mxy) # print(np.sum(np.abs(Mxy))) # peaks, dict = sis.find_peaks(np.abs(Mxy),threshold=0.5, plateau_size=40) plateau_widths = np.sum(np.abs(Mxy) > 0.8) self.assertTrue(29,plateau_widths)
def sigpy_n_seq( flip_angle: float, apodization: float = 0, delay: float = 0, duration: float = 0, freq_offset: float = 0, center_pos: float = 0.5, max_grad: float = 0, max_slew: float = 0, phase_offset: float = 0, return_gz: bool = True, slice_thickness: float = 0, system: Opts = Opts(), time_bw_product: float = 4, pulse_cfg: pulse_opts = pulse_opts(), use: str = str() ) -> Union[SimpleNamespace, Tuple[SimpleNamespace, SimpleNamespace, SimpleNamespace]]: """ Creates a radio-frequency sinc pulse event using the sigpy rf pulse library and optionally accompanying slice select, slice select rephasing trapezoidal gradient events. Parameters ---------- flip_angle : float Flip angle in radians. apodization : float, optional, default=0 Apodization. center_pos : float, optional, default=0.5 Position of peak.5 (midway). delay : float, optional, default=0 Delay in milliseconds (ms). duration : float, optional, default=0 Duration in milliseconds (ms). freq_offset : float, optional, default=0 Frequency offset in Hertz (Hz). max_grad : float, optional, default=0 Maximum gradient strength of accompanying slice select trapezoidal event. max_slew : float, optional, default=0 Maximum slew rate of accompanying slice select trapezoidal event. phase_offset : float, optional, default=0 Phase offset in Hertz (Hz). return_gz:bool, default=False Boolean flag to indicate if slice-selective gradient has to be returned. slice_thickness : float, optional, default=0 Slice thickness of accompanying slice select trapezoidal event. The slice thickness determines the area of the slice select event. system : Opts, optional System limits. Default is a system limits object initialised to default values. time_bw_product : float, optional, default=4 Time-bandwidth product. use : str, optional, default=str() Use of radio-frequency sinc pulse. Must be one of 'excitation', 'refocusing' or 'inversion'. Returns ------- rf : SimpleNamespace Radio-frequency sinc pulse event. gz : SimpleNamespace, optional Accompanying slice select trapezoidal gradient event. Returned only if `slice_thickness` is provided. gzr : SimpleNamespace, optional Accompanying slice select rephasing trapezoidal gradient event. Returned only if `slice_thickness` is provided. Raises ------ ValueError If invalid `use` parameter was passed. Must be one of 'excitation', 'refocusing' or 'inversion'. If `return_gz=True` and `slice_thickness` was not provided. """ valid_use_pulses = ['excitation', 'refocusing', 'inversion'] if use != '' and use not in valid_use_pulses: raise ValueError( f"Invalid use parameter. Must be one of 'excitation', 'refocusing' or 'inversion'. Passed: {use}" ) if pulse_cfg.pulse_type == 'slr': [signal, t, pulse] = make_slr(flip_angle=flip_angle, time_bw_product=time_bw_product, duration=duration, system=system, pulse_cfg=pulse_cfg, disp=True) if pulse_cfg.pulse_type == 'sms': [signal, t, pulse] = make_sms(flip_angle=flip_angle, time_bw_product=time_bw_product, duration=duration, system=system, pulse_cfg=pulse_cfg, disp=True) rfp = SimpleNamespace() rfp.type = 'rf' rfp.signal = signal rfp.t = t rfp.freq_offset = freq_offset rfp.phase_offset = phase_offset rfp.dead_time = system.rf_dead_time rfp.ringdown_time = system.rf_ringdown_time rfp.delay = delay if use != '': rfp.use = use if rfp.dead_time > rfp.delay: rfp.delay = rfp.dead_time if return_gz: 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 BW = time_bw_product / duration amplitude = BW / slice_thickness area = amplitude * duration gz = make_trapezoid(channel='z', system=system, flat_time=duration, flat_area=area) gzr = make_trapezoid(channel='z', system=system, area=-area * (1 - center_pos) - 0.5 * (gz.area - area)) if rfp.delay > gz.rise_time: gz.delay = math.ceil( (rfp.delay - gz.rise_time) / system.grad_raster_time) * system.grad_raster_time if rfp.delay < (gz.rise_time + gz.delay): rfp.delay = gz.rise_time + gz.delay if rfp.ringdown_time > 0: t_fill = np.arange(1, round(rfp.ringdown_time / 1e-6) + 1) * 1e-6 rfp.t = np.concatenate((rfp.t, rfp.t[-1] + t_fill)) rfp.signal = np.concatenate((rfp.signal, np.zeros(len(t_fill)))) # Following 2 lines of code are workarounds for numpy returning 3.14... for np.angle(-0.00...) negative_zero_indices = np.where(rfp.signal == -0.0) rfp.signal[negative_zero_indices] = 0 if return_gz: return rfp, gz, gzr, pulse else: return rfp
grad_unit='mT/m', max_slew=150, slew_unit='T/m/s', rf_ringdown_time=20e-6, rf_dead_time=100e-6, adc_dead_time=10e-6) # ====== # CREATE EVENTS # ====== if (ext_pulse_library): pulse_cfg = pulse_opts(pulse_type='sms', ptype='st', ftype='ls', d1=0.01, d2=0.01, cancel_alpha_phs=False, n_bands=3, band_sep=20, phs_0_pt='None') rf, gz, gzr, _ = sp.sigpy_n_seq(flip_angle=np.pi / 2, system=system, duration=3e-3, slice_thickness=slice_thickness, time_bw_product=4, return_gz=True, pulse_cfg=pulse_cfg) else: rf, gz, gzr = pp.make_sinc_pulse(flip_angle=alpha * math.pi / 180, duration=3e-3, slice_thickness=slice_thickness,