def generate_relaxation_pulse(relaxation_time, delta_t): """Returns a relaxation pulse Params: relaxation_time: (positive float) total time for relaxation pulse delta_t: (positive float) time step Returns: pulse_relax: (Pulse object) relaxation pulse """ # Check params if not (isinstance(relaxation_time, float) and relaxation_time > 0): raise TypeError("relaxation_time must be a positive float") if not (isinstance(delta_t, float) and delta_t > 0): raise TypeError("delta_t must be a positive float") # Compute relaxation pulse relax_length = int( relaxation_time / delta_t ) # wait long enough for transverse magnetization to decay and for longitudinal magnetization to rebuild Gx = np.zeros(relax_length) Gy = np.zeros(relax_length) Gz = np.zeros(relax_length) readout = np.zeros(relax_length, dtype=bool) pulse_relax = Pulse(mode='free', Gx=Gx, Gy=Gy, Gz=Gz, readout=readout, delta_t=delta_t) return pulse_relax
def generate_tipping_pulse(gyromagnetic_ratio, main_field, Gz_amplitude, slice_width, tip_angle, delta_t): """Generates the tipping pulse for a spin-echo sequence Params: gyromagnetic_ratio: (positive float) gyromagnetic ratio of species main_field: (positive float) main magnetic field Gz_amplitude: (positive float) z-gradient amplitude slice_width: (postive float) width of the slice tip_angle: (float \in (0,pi)) angle to tip delta_t: (positive float) time step Returns: pulse_tip: (list of Pulse objects) the pulse required to tip the magnetization in a slice """ # Check params if not (isinstance(gyromagnetic_ratio, float) and gyromagnetic_ratio > 0): raise TypeError('gyromagnetic_ratio must be a positive float') if not (isinstance(main_field, float) and main_field > 0): raise TypeError('main_field must be a positive float') if not (isinstance(Gz_amplitude, float) and Gz_amplitude > 0): raise TypeError('Gz_amplitude must be a positive float') if not (isinstance(slice_width, float) and slice_width > 0): raise TypeError('slice_width must be a positive float') if not (isinstance(tip_angle, float) and tip_angle > 0 and tip_angle < np.pi): raise TypeError('tip_angle must be a float between 0 and \pi') if not (isinstance(delta_t, float) and delta_t > 0): raise TypeError('delta_t must be a positive float') # Tipping pulse Gz_tip_amp = Gz_amplitude pulse_tip_duration = 2 * np.pi * 8 / (gyromagnetic_ratio * Gz_tip_amp * (slice_width / 2.0)) t_vals = np.arange(0.0, pulse_tip_duration, delta_t) t_vals_shifted = t_vals - pulse_tip_duration / 2.0 sinc_scaling = 2.0 * 8.0 / pulse_tip_duration gaussian_std = pulse_tip_duration / 3.0 B1x_tip = np.sinc(sinc_scaling * t_vals_shifted) * np.exp( -(t_vals_shifted**2) / (2 * gaussian_std**2)) # sinc pulse alpha_origin = gyromagnetic_ratio * sum(B1x_tip) * delta_t desired_alpha_origin = tip_angle B1x_amplitude_scaling = desired_alpha_origin / alpha_origin B1x_tip = B1x_tip * B1x_amplitude_scaling # rescale for desired tip angle pulse_tip_length = len(B1x_tip) B1y_tip = np.zeros(pulse_tip_length) Gz_tip = Gz_tip_amp * np.ones(pulse_tip_length) omega_rf = gyromagnetic_ratio * main_field # omega_rf = omega_0 pulse_tip = Pulse(mode='excite', delta_t=delta_t, B1x=B1x_tip, B1y=B1y_tip, Gz=Gz_tip, omega_rf=omega_rf) return pulse_tip
# Tipping pulse B1x_tip = np.sinc(sinc_scaling * t_vals_shifted) * np.exp( -(t_vals_shifted**2) / (2 * gaussian_std**2)) # sinc pulse alpha_origin = em_gyromagnetic_ratio * sum(B1x_tip) * delta_t desired_alpha_origin = 0.52 B1x_amplitude_scaling = desired_alpha_origin / alpha_origin B1x_tip = B1x_tip * B1x_amplitude_scaling # rescale for desired tip angle pulse_tip_length = len(B1x_tip) B1y_tip = np.zeros(pulse_tip_length) Gz_tip_amp = 5.0 * 1e-3 # T/m Gz_tip = Gz_tip_amp * np.ones(pulse_tip_length) omega_rf = em_gyromagnetic_ratio * main_field # omega_rf = omega_0 pulse_tip = Pulse(mode='excite', delta_t=delta_t, B1x=B1x_tip, B1y=B1y_tip, Gz=Gz_tip, omega_rf=omega_rf) # Refocusing pulse pulse_refocus_length = int(pulse_tip_length / 2) Gx_refocus = np.zeros(pulse_refocus_length) Gy_refocus = np.zeros(pulse_refocus_length) Gz_refocus = -Gz_tip_amp * np.ones(pulse_refocus_length) Gz_no_refocus = np.zeros(pulse_refocus_length) readout = np.zeros(pulse_refocus_length, dtype=bool) pulse_refocus = Pulse(mode='free', delta_t=delta_t, Gx=Gx_refocus, Gy=Gy_refocus, Gz=Gz_refocus,
from pypulse import Pulse from pysim import Sim import numpy as np import matplotlib.pyplot as plt # Declare a readout pulse time_step = 0.01 pulse_length = 500 mode = 'free' Gx = np.ones(pulse_length) Gy = np.ones(pulse_length) readout = np.zeros(pulse_length, dtype=bool) for i in range(len(readout)): if not i % 2: readout[i] = True pulse = Pulse(mode=mode, Gx=Gx, Gy=Gy, readout=readout, time_step=time_step) # Initialize simulation num_ems = 1 main_field = 10.0 pulse_sequence = [pulse] sim = Sim(num_ems, main_field, pulse_sequence) # Run simulation and collect MR signal mr_signals = sim.run_sim() mr_signal = mr_signals[0] # Plot MR signal (FID of one em) t_readout = [] for step_no in range(pulse_length): if readout[step_no]:
for pulse_duration in pulse_durations: # Declare excitation pulse delta_t = 0.01 t_vals = np.arange(0.0, pulse_duration, delta_t) t_vals_shifted = t_vals - pulse_duration / 2.0 B1x = np.sinc(pulse_duration * t_vals_shifted) * np.exp( -pulse_duration / 10 * (t_vals_shifted**2)) pulse_length = len(B1x) B1y = np.zeros(pulse_length) Gz_amp = 20.0 Gz = Gz_amp * np.ones(pulse_length) omega_rf = em_gyromagnetic_ratio * main_field # omega_rf = omega_0 pulse = Pulse(mode='excite', delta_t=delta_t, B1x=B1x, B1y=B1y, Gz=Gz, omega_rf=omega_rf) # Compute theoretical magnetization profile B1x_fft = np.roll(abs(np.fft.fft(B1x)), int(len(B1x) / 2)) / np.sqrt( len(B1x)) freq = np.fft.fftfreq(len(B1x), delta_t) freq = np.fft.fftshift(freq) z_theory = -2 * np.pi / em_gyromagnetic_ratio * (1.0 / Gz_amp) * freq m_theory = em_equilibrium_magnetization * B1x_fft # Compute theoretical tip angle at origin alpha_origin = em_gyromagnetic_ratio * sum(B1x) * delta_t print(alpha_origin)
def generate_dw_kspace_pulses(pulse_tip, fe_sample_radius, pe_sample_radius, kx_max, ky_max, kmove_time, kread_time, gyromagnetic_ratio, delta_t, read_all, diffusion_gradients, diffusion_gradient_time, diffusion_time): """Generates the set of pulses necessary to sample desired region of kspace using Cartesian sampling with diffusion weighting Params: pulse_tip: (Pulse object) tipping pulse in spin-echo sequence fe_sample_radius: (positive int) radius of number of samples in frequency-encoding direction pe_sample_radius: (positive int) radius of number of samples in phase-encoding direction kx_max: (positive float) maximum value of kx to sample ky_max: (positive float) maximum value of ky to sample kmove_time: (positive float) time to move to each location in kspace kread_time: (positive float) time for readout of kspace line gyromagnetic_ratio: (float) gyromagnetic ratio of species to be imaged delta_t: (positive float) time step for simulation read_all: (bool) if true, record the MR signal at all time steps during the read time diffusion_gradients: (numpy 3-vector of nonnegative floats) magnitude of diffusion gradient in each spatial direction diffusion_gradient_time: (positive float) time to apply diffusion gradient diffusion_time: (positive float) time between positive and negative lobe of diffusion gradient. Returns: kspace_pulses: (list of Pulse objects) set of readout pulses to sample given region of kspace Notes: kmove_time must be greater than half the tipping pulse time (see a 2DFT sequence diagram) the fact that num_fe_samples and num_pe_samples are odd numbers is necessary for shift_2DFT function """ # Check params if not (isinstance(pulse_tip, Pulse)): raise TypeError("pulse_tip must be a Pulse object") if not (isinstance(fe_sample_radius, int) and fe_sample_radius > 0): raise TypeError("fe_sample_radius must be a positive int") if not (isinstance(pe_sample_radius, int) and pe_sample_radius > 0): raise TypeError("pe_sample_radius must be a positive int") if not (isinstance(kx_max, float) and kx_max > 0): raise TypeError("kx_max must be a positive float") if not (isinstance(ky_max, float) and ky_max > 0): raise TypeError("ky_max must be a positive float") if not (isinstance(kmove_time, float) and kmove_time > 0): raise TypeError("kmove_time must be a positive float") if not (isinstance(kread_time, float) and kread_time > 0): raise TypeError("kread_time must be a positive float") if not isinstance(gyromagnetic_ratio, float): raise TypeError("gyromagnetic_ratio must be a float") if not (isinstance(delta_t, float) and delta_t > 0): raise TypeError("delta_t must be a positive float") if not (isinstance(read_all, bool)): raise TypeError("read_all must be a bool") if not (kmove_time > pulse_tip.length * delta_t / 2.0): print("kmove_time: " + str(kmove_time * 1e3)) print("pulse_tip time: " + str(pulse_tip.length * delta_t * 1e3 / 2.0)) raise ValueError( "kmove_time must be greater than half the tipping pulse time -- see a 2DFT sequence diagram" ) if not (diffusion_gradients.shape == (3, ) and diffusion_gradients.dtype == np.float64 and all([item >= 0.0 for item in diffusion_gradients])): raise TypeError( "diffusion_gradients must be a numpy 3-vector of nonnegative floats" ) if not (isinstance(diffusion_gradient_time, float) and diffusion_gradient_time > 0.0): raise TypeError("diffusion_gradient_time must be a positive float") if not (isinstance(diffusion_time, float) and diffusion_time > 0.0): raise TypeError("diffusion_time must be a positive float") if not (diffusion_time > diffusion_gradient_time): raise ValueError( "diffusion_time must be larger than diffusion_gradient_time") # Compute number of pe and fe samples num_fe_samples = int(2 * fe_sample_radius + 1) num_pe_samples = int(2 * pe_sample_radius + 1) # Frequency-encoding direction parameters adc_step = int(kread_time / (num_fe_samples * delta_t)) # Common parameters kread_len = (num_fe_samples - 1) * adc_step + 1 kmove_len = int(kmove_time / delta_t) # Refocusing lobe in longitudinal direction pulse_tip_length = pulse_tip.length Gz_tip_amp = pulse_tip.Gz[0] pulse_refocus_length = int(pulse_tip_length / 2) Gz_refocus_lobe = -Gz_tip_amp * np.ones(pulse_refocus_length) Gz_kmove = np.concatenate( (Gz_refocus_lobe, np.zeros(kmove_len - pulse_refocus_length))) # Phase-encoding direction parameters delta_ky = (2 * ky_max) / (num_pe_samples - 1) # Compute pulses in frequency-encoding direction Gx_kmove_amp = -(2 * np.pi / gyromagnetic_ratio) * kx_max / (kmove_time) Gx_kmove = Gx_kmove_amp * np.ones(kmove_len) Gx_kread_amp = (2 * np.pi / gyromagnetic_ratio) * (2 * kx_max) / (kread_len * delta_t) Gx_kread = Gx_kread_amp * np.ones(kread_len) # Compute diffusion-weighting pulses diff_len = int((diffusion_time + diffusion_gradient_time) / delta_t) diff_grad_len = int(diffusion_gradient_time / delta_t) diff_wait_len = diff_len - 2 * diff_grad_len Gx_diff = np.concatenate( (diffusion_gradients[0] * np.ones(diff_grad_len), np.zeros(diff_wait_len), -diffusion_gradients[0] * np.ones(diff_grad_len))) Gy_diff = np.concatenate( (diffusion_gradients[1] * np.ones(diff_grad_len), np.zeros(diff_wait_len), -diffusion_gradients[1] * np.ones(diff_grad_len))) Gz_diff = np.concatenate( (diffusion_gradients[2] * np.ones(diff_grad_len), np.zeros(diff_wait_len), -diffusion_gradients[2] * np.ones(diff_grad_len))) # Compute gradient in Gx = np.concatenate((Gx_kmove, Gx_diff, Gx_kread)) # Readout indices if read_all: readout = np.ones(kread_len, dtype=bool) else: readout = np.zeros(kread_len, dtype=bool) for fe_sample_no in range(num_fe_samples): fe_sample_index = fe_sample_no * adc_step readout[fe_sample_index] = True readout = np.concatenate((np.zeros(kmove_len + diff_len, dtype=bool), readout)) # Compute pulses in phase-encoding direction kspace_pulses = [] for pe_sample_no in range(num_pe_samples): ky = ky_max - pe_sample_no * delta_ky # Gradients for movement to position in kspace Gy_kmove_amp = (2 * np.pi / gyromagnetic_ratio) * ky / (kmove_time) Gy_kmove = Gy_kmove_amp * np.ones(kmove_len) # Gradients for readout movement Gy_kread = np.zeros(kread_len, dtype=float) # Gradients for sampling one line of kspace Gy = np.concatenate((Gy_kmove, Gy_diff, Gy_kread)) # Bundle into Pulse object Gz = np.concatenate((Gz_kmove, Gz_diff, np.zeros(kread_len))) pulse = Pulse(mode='free', Gx=Gx, Gy=Gy, Gz=Gz, readout=readout, delta_t=delta_t) kspace_pulses.append(pulse) return kspace_pulses
def generate_dw_pulse(pulse_tip, kread_time, gyromagnetic_ratio, delta_t, diffusion_gradients, diffusion_gradient_time, diffusion_time): """Generates a diffusion-weighting pulse Params: pulse_tip: (Pulse object) tipping pulse in spin-echo sequence kread_time: (positive float) time for readout of kspace line gyromagnetic_ratio: (float) gyromagnetic ratio of species to be imaged delta_t: (positive float) time step for simulation read_all: (bool) if true, record the MR signal at all time steps during the read time diffusion_gradients: (numpy 3-vector of nonnegative floats) magnitude of diffusion gradient in each spatial direction diffusion_gradient_time: (positive float) time to apply diffusion gradient diffusion_time: (positive float) time between positive and negative lobe of diffusion gradient. Returns: pulse: a diffusion-weighting pulse """ # Check params if not (isinstance(pulse_tip, Pulse)): raise TypeError("pulse_tip must be a Pulse object") if not (isinstance(kread_time, float) and kread_time > 0): raise TypeError("kread_time must be a positive float") if not isinstance(gyromagnetic_ratio, float): raise TypeError("gyromagnetic_ratio must be a float") if not (isinstance(delta_t, float) and delta_t > 0): raise TypeError("delta_t must be a positive float") if not (diffusion_gradients.shape == (3, ) and diffusion_gradients.dtype == np.float64 and all([item >= 0.0 for item in diffusion_gradients])): raise TypeError( "diffusion_gradients must be a numpy 3-vector of nonnegative floats" ) if not (isinstance(diffusion_gradient_time, float) and diffusion_gradient_time > 0.0): raise TypeError("diffusion_gradient_time must be a positive float") if not (isinstance(diffusion_time, float) and diffusion_time > 0.0): raise TypeError("diffusion_time must be a positive float") if not (diffusion_time > diffusion_gradient_time): raise ValueError( "diffusion_time must be larger than diffusion_gradient_time") # Common parameters kread_len = int(kread_time / delta_t) # Refocusing lobe in longitudinal direction pulse_tip_length = pulse_tip.length Gz_tip_amp = pulse_tip.Gz[0] pulse_refocus_length = int(pulse_tip_length / 2) Gz_refocus_lobe = -Gz_tip_amp * np.ones(pulse_refocus_length) Gx_refocus_lobe = np.zeros(pulse_refocus_length) Gy_refocus_lobe = np.zeros(pulse_refocus_length) Gz_read = np.zeros(kread_len) Gy_read = np.zeros(kread_len) Gx_read = np.zeros(kread_len) # Compute diffusion-weighting pulses diff_len = int((diffusion_time + diffusion_gradient_time) / delta_t) diff_grad_len = int(diffusion_gradient_time / delta_t) diff_wait_len = diff_len - 2 * diff_grad_len Gx_diff = np.concatenate( (diffusion_gradients[0] * np.ones(diff_grad_len), np.zeros(diff_wait_len), -diffusion_gradients[0] * np.ones(diff_grad_len))) Gy_diff = np.concatenate( (diffusion_gradients[1] * np.ones(diff_grad_len), np.zeros(diff_wait_len), -diffusion_gradients[1] * np.ones(diff_grad_len))) Gz_diff = np.concatenate( (diffusion_gradients[2] * np.ones(diff_grad_len), np.zeros(diff_wait_len), -diffusion_gradients[2] * np.ones(diff_grad_len))) # Compute gradients Gz = np.concatenate((Gz_refocus_lobe, Gz_diff, Gz_read)) Gy = np.concatenate((Gy_refocus_lobe, Gy_diff, Gy_read)) Gx = np.concatenate((Gx_refocus_lobe, Gx_diff, Gx_read)) # Readout indices readout = np.ones(kread_len, dtype=bool) readout = np.concatenate((np.zeros(pulse_refocus_length + diff_len, dtype=bool), readout)) # Bundle into Pulse object pulse = Pulse(mode='free', Gx=Gx, Gy=Gy, Gz=Gz, readout=readout, delta_t=delta_t) return pulse
B0 = 3.0 mu_eq = 1.0 sigma = 0.0 # Generate pulse sequence t_final = 6.0 delta_t = 1.0e-2 t_vals = np.arange(0.0, t_final, delta_t) pulse_length = len(t_vals) Gx = np.ones(pulse_length) Gy = np.zeros(pulse_length) Gz = np.zeros(pulse_length) readout = np.ones(pulse_length, dtype=bool) pulse = Pulse(mode='free', Gx=Gx, Gy=Gy, Gz=Gz, readout=readout, delta_t=delta_t) # Run simulation em_magnetizations = np.array([mu0]) em_positions = np.array([r0]) em_velocities = np.array([v0]) em_gyromagnetic_ratio = gamma em_shielding_constants = np.array([sigma]) em_equilibrium_magnetization = mu_eq def T1_map(position): return 1e6