def test_im2ksp_noncart(self): # Image to k-space transformation for non-Cartesian data nufft_obj = nufft_init(kt=ktraj, params=acq_params) ksp = im2ksp( M=ph, cartesian_opt=0, NufftObj=nufft_obj, params=acq_params) # Sample the image along the trajectory self.assertEqual(ksp.shape, ktraj.shape) # Dimensions match
def test_ksp2im_noncart(self): # K-space to image transformation for non-Cartesian nufft_obj = nufft_init(kt=ktraj, params=acq_params) im = ksp2im(ksp=noncart_ksp, cartesian_opt=0, NufftObj=nufft_obj, params=acq_params) # reconstructed image self.assertEqual(im.shape, ph.shape) # dimensions match im = (im - np.min(im)) / (np.max(im) - np.min(im)) # normalize image diff = np.round(im - ph) diff_percent = np.nonzero(diff)[0].shape[0] / N**2 * 100 self.assertLess( diff_percent, 5 ) # Less than 5% of voxels should be different between original and reconstructed image
def add_or(M, kt, df, nonCart=None, params=None): '''Forward model for off-resonance simulation Parameters ---------- M : numpy.ndarray Image data kt : numpy.ndarray k-space trajectory df : numpy.ndarray Field map nonCart : int , optional Cartesian/Non-Cartesian trajectory option. Default is None. params : dict , optional Sequence parameters. Default is None. Returns ------- M_or : numpy.ndarray Off-resonance corrupted image data ''' '''Create a phase matrix - 2*pi*df*t for every df and every t''' if nonCart is not None: cartesian_opt = 0 NufftObj = nufft_init(kt, params) else: cartesian_opt = 1 NufftObj = None params = None kspace = im2ksp(M, cartesian_opt, NufftObj, params) M_or = np.zeros(M.shape, dtype=complex) for x in range(M.shape[0]): for y in range(M.shape[1]): phi = 2 * pi * df[x, y] * kt kspace_orc = kspace * np.exp(-1j * phi) M_corr = ksp2im(kspace_orc, cartesian_opt, NufftObj, params) M_or[x, y] = M_corr[x, y] return M_or
def test_missing_params_N(self): # NUFFT for non-cartesian data will fail if a required parameter is missing from the paramters dictionary (e.g. N) params = acq_params.copy() del params['N'] with self.assertRaises(ValueError): nufft_init(kt=ktraj, params=params)
def MFI(dataIn, dataInType, kt, df, Lx, nonCart=None, params=None): '''Off-resonance Correction by Multi-Frequency Interpolation Man, L., Pauly, J. M. and Macovski, A. (1997), Multifrequency interpolation for fast off‐resonance correction. Magn. Reson. Med., 37: 785-792. doi:10.1002/mrm.1910370523 Parameters ---------- dataIn : numpy.ndarray k-space raw data or image data dataInType : str Can be either 'raw' or 'im' kt : numpy.ndarray k-space trajectory df : numpy.ndarray Field map Lx : int L (frequency bins) factor nonCart : int Non-cartesian trajectory option. Default is None (Cartesian). params : dict Sequence parameters. Default is None. Returns ------- M_hat : numpy.ndarray Corrected image data. ''' if nonCart is not None and nonCart == 1: check_inputs_noncartesian(dataIn.shape, dataInType, kt.shape, df.shape, params) cartesian_opt = 0 NufftObj = nufft_init(kt, params) t_vector = params['t_vector'] T = np.tile(params['t_vector'], (1, kt.shape[1])) t_ro = T[-1, 0] - T[0, 0] # T[end] - TE N = params['N'] elif nonCart == 'EPI': # check_inputs_cartesian(dataIn.shape, dataInType, kt.shape, df.shape) cartesian_opt = 1 NufftObj = None T = np.flipud(params['t_vector']).reshape(params['N'], params['N']) T[1:params['N']:2, :] = np.fliplr(T[1:params['N']:2, :]) N = dataIn.shape[0] t_ro = params['t_readout'] t_vector = params['t_vector'] else: check_inputs_cartesian(dataIn.shape, dataInType, kt.shape, df.shape) cartesian_opt = 1 NufftObj = None N = dataIn.shape[0] t_vector = kt[0].reshape(kt.shape[1], 1) T = kt t_ro = T[0, -1] - T[0, 0] if dataInType == 'im': rawData = im2ksp(dataIn, cartesian_opt, NufftObj, params) elif dataInType == 'raw': rawData = dataIn df = np.round(df, 1) idx, idy = np.where(df == -0.0) df[idx, idy] = 0.0 # Number of frequency segments df_max = max(np.abs([df.max(), df.min()])) # Hz df_range = (df.min(), df.max()) L = ceil(df_max * 2 * pi * t_ro / pi) L = L * Lx if len(np.unique(df)) == 1: L = 1 f_L = np.linspace(df.min(), df.max(), L + 1) # Hz #T = np.tile(params['t_vector'], (1, kt.shape[1])) # reconstruct the L basic images M_MFI = np.zeros((N, N, L + 1), dtype=complex) for l in range(L + 1): phi = 2 * pi * f_L[l] * T kspace_L = rawData * np.exp(1j * phi) M_MFI[:, :, l] = ksp2im(kspace_L, cartesian_opt, NufftObj, params) # calculate MFI coefficients coeffs_LUT = coeffs_MFI_lsq(kt, f_L, df_range, t_vector) # final image reconstruction M_hat = np.zeros((N, N), dtype=complex) for i in range(M_hat.shape[0]): for j in range(M_hat.shape[1]): fieldmap_val = df[i, j] val_coeffs = coeffs_LUT[str(fieldmap_val)] M_hat[i, j] = sum(val_coeffs * M_MFI[i, j, :]) return M_hat
def fs_CPR(dataIn, dataInType, kt, df, Lx, nonCart=None, params=None): '''Off-resonance Correction by frequency-segmented Conjugate Phase Reconstruction Noll, D. C., Pauly, J. M., Meyer, C. H., Nishimura, D. G. and Macovskj, A. (1992), Deblurring for non‐2D fourier transform magnetic resonance imaging. Magn. Reson. Med., 25: 319-333. doi:10.1002/mrm.1910250210 Parameters ---------- dataIn : numpy.ndarray k-space raw data or image data dataInType : str Can be either 'raw' or 'im' kt : numpy.ndarray k-space trajectory df : numpy.ndarray Field map Lx : int L (frequency bins) factor nonCart : int Non-cartesian trajectory option. Default is None (Cartesian). params : dict Sequence parameters. Default is None (Cartesian). Returns ------- M_hat : numpy.ndarray Corrected image data. ''' if nonCart is not None and nonCart == 1: check_inputs_noncartesian(dataIn.shape, dataInType, kt.shape, df.shape, params) cartesian_opt = 0 NufftObj = nufft_init(kt, params) T = np.tile(params['t_vector'], (1, kt.shape[1])) N = params['N'] t_ro = T[-1, 0] - T[0, 0] # T[end] - TE elif nonCart == 'EPI': # check_inputs_cartesian(dataIn.shape, dataInType, kt.shape, df.shape) cartesian_opt = 1 NufftObj = None T = np.flipud(params['t_vector']).reshape(params['N'], params['N']) T[1:params['N']:2, :] = np.fliplr(T[1:params['N']:2, :]) N = dataIn.shape[0] t_ro = params['t_readout'] else: check_inputs_cartesian(dataIn.shape, dataInType, kt.shape, df.shape) cartesian_opt = 1 NufftObj = None N = dataIn.shape[0] t_vector = kt[0].reshape(kt.shape[1], 1) T = kt t_ro = T[0, -1] - T[0, 0] if dataInType == 'im': rawData = im2ksp(dataIn, cartesian_opt, NufftObj, params) elif dataInType == 'raw': rawData = dataIn # Number of frequency segments df_max = max(np.abs([df.max(), df.min()])) # Hz L = ceil(4 * df_max * 2 * pi * t_ro / pi) L = L * Lx if len(np.unique(df)) == 1: L = 1 f_L = np.linspace(df.min(), df.max(), L + 1) # Hz # T = np.tile(params['t_vector'], (1, kt.shape[1])) # reconstruct the L basic images M_fsCPR = np.zeros((N, N, L + 1), dtype=complex) for l in range(L + 1): phi = 2 * pi * f_L[l] * T kspace_L = rawData * np.exp(1j * phi) M_fsCPR[:, :, l] = ksp2im(kspace_L, cartesian_opt, NufftObj, params) # final image reconstruction M_hat = np.zeros((N, N), dtype=complex) for i in range(M_hat.shape[0]): for j in range(M_hat.shape[1]): fieldmap_val = df[i, j] closest_fL_idx = find_nearest(f_L, fieldmap_val) if fieldmap_val == f_L[closest_fL_idx]: pixel_val = M_fsCPR[i, j, closest_fL_idx] else: if fieldmap_val < f_L[closest_fL_idx]: f_vals = [f_L[closest_fL_idx - 1], f_L[closest_fL_idx]] im_vals = [ M_fsCPR[i, j, closest_fL_idx - 1], M_fsCPR[i, j, closest_fL_idx] ] else: f_vals = [f_L[closest_fL_idx], f_L[closest_fL_idx + 1]] im_vals = [ M_fsCPR[i, j, closest_fL_idx], M_fsCPR[i, j, closest_fL_idx + 1] ] pixel_val = np.interp(fieldmap_val, f_vals, im_vals) M_hat[i, j] = pixel_val return M_hat
def CPR(dataIn, dataInType, kt, df, nonCart=None, params=None): '''Off-resonance Correction by Conjugate Phase Reconstruction Maeda, A., Sano, K. and Yokoyama, T. (1988), Reconstruction by weighted correlation for MRI with time-varying gradients. IEEE Transactions on Medical Imaging, 7(1): 26-31. doi: 10.1109/42.3926 Parameters ---------- dataIn : numpy.ndarray k-space raw data or image data dataInType : str Can be either 'raw' or 'im' kt : numpy.ndarray k-space trajectory. df : numpy.ndarray Field map nonCart : int Non-cartesian trajectory option. Default is None (Cartesian). params : dict Sequence parameters. Default is None (Cartesian). Returns ------- M_hat : numpy.ndarray Corrected image data. ''' if nonCart is not None and nonCart == 1: check_inputs_noncartesian(dataIn.shape, dataInType, kt.shape, df.shape, params) cartesian_opt = 0 NufftObj = nufft_init(kt, params) T = np.tile(params['t_vector'], (1, kt.shape[1])) N = params['N'] elif nonCart == 'EPI': # check_inputs_cartesian(dataIn.shape, dataInType, kt.shape, df.shape) cartesian_opt = 1 NufftObj = None T = np.flipud(params['t_vector']).reshape(params['N'], params['N']) T[1:params['N']:2, :] = np.fliplr(T[1:params['N']:2, :]) N = dataIn.shape[0] else: check_inputs_cartesian(dataIn.shape, dataInType, kt.shape, df.shape) cartesian_opt = 1 NufftObj = None T = kt N = dataIn.shape[0] if dataInType == 'im': rawData = im2ksp(dataIn, cartesian_opt, NufftObj, params) elif dataInType == 'raw': rawData = dataIn df_values = np.unique(df) M_CPR = np.zeros((N, N, len(df_values)), dtype=complex) for i in range(len(df_values)): phi = 2 * pi * df_values[i] * T kspace_orc = rawData * np.exp(1j * phi) M_corr = ksp2im(kspace_orc, cartesian_opt, NufftObj, params) M_CPR[:, :, i] = M_corr M_hat = np.zeros((N, N), dtype=complex) for x in range(df.shape[0]): for y in range(df.shape[1]): fieldmap_val = df[x, y] idx = np.where(df_values == fieldmap_val) M_hat[x, y] = M_CPR[x, y, idx] return M_hat
def add_or_CPR(M, kt, df, nonCart=None, params=None): '''Forward model for off-resonance simulation. The number of fourier transforms = number of unique values in the field map. Parameters ---------- M : numpy.ndarray Image data kt : numpy.ndarray k-space trajectory df : numpy.ndarray Field map nonCart : int , optional Cartesian/Non-Cartesian trajectory option. Default is None. params : dict , optional Sequence parameters. Default is None. Returns ------- M_or : numpy.ndarray Off-resonance corrupted image data ''' # Create a phase matrix - 2*pi*df*t for every df and every t if nonCart is not None and nonCart == 1: cartesian_opt = 0 NufftObj = nufft_init(kt, params) T = np.tile(params['t_vector'], (1, kt.shape[1])) elif nonCart == 'EPI': cartesian_opt = 1 NufftObj = None T = np.flipud(params['t_vector']).reshape(params['N'], params['N']) T[1:params['N']:2, :] = np.fliplr(T[1:params['N']:2, :]) else: cartesian_opt = 1 NufftObj = None params = None T = kt kspace = im2ksp(M, cartesian_opt, NufftObj, params) df_values = np.unique(df) M_or_CPR = np.zeros((M.shape[0], M.shape[1], len(df_values)), dtype=complex) kspsave = np.zeros((kspace.shape[0], kspace.shape[1], len(df_values)), dtype=complex) for i in range(len(df_values)): phi = -2 * pi * df_values[i] * T kspace_or = kspace * np.exp(1j * phi) kspsave[:, :, i] = kspace_or M_corr = ksp2im(kspace_or, cartesian_opt, NufftObj, params) M_or_CPR[:, :, i] = M_corr M_or = np.zeros(M.shape, dtype=complex) for x in range(M.shape[0]): for y in range(M.shape[1]): fieldmap_val = df[x, y] idx = np.where(df_values == fieldmap_val) M_or[x, y] = M_or_CPR[x, y, idx] '''plt.imshow(abs(M_or)) plt.show()''' return M_or, kspsave
def spiral_recon(data, ktraj, N, plot=0, save=0, dst_folder=None): ''' Spiral image reconstruction from raw data Parameters ---------- data : str or np.ndarray Path containing the raw data file of array containing the raw data ktraj : np.ndarray k-space trajectory coordinates with dimensions [Npoints, Nshots] N : int Matrix size of the reconstructed image plot : bool, Optional Plotting a slice of the reconstructed image option. Default is 0 (not plot). save : bool, Optional Saving the data in a .npy file option. Default is 0 (not save). dst_folder : str, Optional Path to the folder where the reconstructed image is saved. Default is None. ''' ## # Load the raw data ## if isinstance(data, str): dat = get_data_from_file(data) elif isinstance(data, np.ndarray): dat = data ## # Acq parameters ## Npoints = ktraj.shape[0] Nshots = ktraj.shape[1] Nchannels = dat.shape[-1] if len(dat.shape) < 4: Nslices = 1 dat = dat.reshape(Npoints, Nshots, 1, Nchannels) else: Nslices = dat.shape[-2] if dat.shape[0] != ktraj.shape[0] or dat.shape[1] != ktraj.shape[1]: raise ValueError('Raw data and k-space trajectory do not match!') ## # Recon ## NUFFT_object = nufft_init(ktraj, { 'N': N, 'Npoints': Npoints, 'Nshots': Nshots }) im = np.zeros((N, N, Nslices, Nchannels), dtype=complex) for ch in range(Nchannels): for sl in range(Nslices): im[:, :, sl, ch] = ksp2im( dat[:, :, sl, ch], 0, NUFFT_object, { 'N': N, 'Npoints': Npoints, 'Nshots': Nshots } ) #NufftObj.solve(dat[:,:,sl,ch].flatten(), solver='cg', maxiter=50) #sos = np.sum(np.abs(im), -1) sos = np.sqrt(np.sum(np.abs(im)**2, -1)) sos = np.divide(sos, np.max(sos)) if plot: #plt.imshow(np.rot90(np.abs(sos[:,:,0]),-1), cmap='gray') plt.imshow(sos[:, :, 0], cmap='gray') plt.axis('off') plt.title('Uncorrected Image') plt.show() if save: if dst_folder is None: raise ValueError('Please specify destination folder') np.save(dst_folder + 'uncorrected_spiral.npy', sos) return sos