def get_Smaps(k_space, img_shape, samples, thresh, min_samples, max_samples, mode='gridding', method='linear', window_fun=None, density_comp=None, n_cpu=1, fourier_op_kwargs=None): r""" Get Smaps for from pMRI sample data. Estimate the sensitivity maps information from parallel mri acquisition and for variable density sampling scheme where the k-space center had been heavily sampled. Reference : Self-Calibrating Nonlinear Reconstruction Algorithms for Variable Density Sampling and Parallel Reception MRI https://ieeexplore.ieee.org/abstract/document/8448776 Parameters ---------- k_space: np.ndarray The acquired kspace of shape (M,L), where M is the number of samples acquired and L is the number of coils used img_shape: tuple The final output shape of Sensitivity Maps. samples: np.ndarray The sample locations where the above k_space data was acquired thresh: tuple The value of threshold in kspace for thresholding k-space center min_samples: tuple The minimum values in k-space where gridding must be done max_samples: tuple The maximum values in k-space where gridding must be done mode: string 'FFT' | 'NFFT' | 'gridding' | 'Stack', default='gridding' Defines the mode in which we would want to interpolate, NOTE: FFT should be considered only if the input has been sampled on the grid method: string 'linear' | 'cubic' | 'nearest', default='linear' For gridding mode, it defines the way interpolation must be done window_fun: "Hann", "Hanning", "Hamming", or a callable, default None. The window function to apply to the selected data. It is computed with the center locations selected. Only works with circular mask. If window_fun is a callable, it takes as input the n_samples x n_dims of samples position and would return an array of n_samples weight to be applied to the selected k-space values, before the smaps estimation. density_comp: np.ndarray default None The density compensation for kspace data in case it exists and we use density compensated adjoint for Smap estimation n_cpu: int default=1 Number of parallel jobs in case of parallel MRI fourier_op_kwargs: dict, default None The keyword arguments given to fourier_op initialization if mode == 'NFFT'. If None, we choose implementation of fourier op to 'gpuNUFFT' if library is installed. Returns ------- Smaps: np.ndarray the estimated sensitivity maps of shape (img_shape, L) with L the number of channels SOS: np.ndarray The sum of Square used to extract the sensitivity maps Notes ----- The Hann (or Hanning) and Hamming window of width :math:`2\theta` are defined as: .. math:: w(x,y) = a_0 - (1-a_0) * \cos(\pi * \sqrt{x^2+y^2}/\theta), \sqrt{x^2+y^2} \le \theta In the case of Hann window :math:`a_0=0.5`. For Hamming window we consider the optimal value in the equiripple sens: :math:`a_0=0.53836`. .. Wikipedia:: https://en.wikipedia.org/wiki/Window_function#Hann_and_Hamming_windows """ if len(min_samples) != len(img_shape) \ or len(max_samples) != len(img_shape): raise NameError('The img_shape, max_samples, and ' 'min_samples must be of same length') k_space, samples, *density_comp = \ extract_k_space_center_and_locations( data_values=k_space, samples_locations=samples, thr=thresh, img_shape=img_shape, is_fft=mode == 'FFT', window_fun=window_fun, density_comp=density_comp, ) if density_comp: density_comp = density_comp[0] else: density_comp = None if samples is None: mode = 'FFT' L, M = k_space.shape Smaps_shape = (L, *img_shape) Smaps = np.zeros(Smaps_shape).astype('complex128') if mode == 'FFT': if not M == img_shape[0] * img_shape[1]: raise ValueError([ 'The number of samples in the k-space must be', 'equal to the (image size, the number of coils)' ]) k_space = k_space.reshape(Smaps_shape) for l in range(Smaps_shape[2]): Smaps[l] = pfft.ifftshift(pfft.ifft2(pfft.fftshift(k_space[l]))) elif mode == 'NFFT': if fourier_op_kwargs is None: if gpunufft_available: fourier_op_kwargs = {'implementation': 'gpuNUFFT'} else: fourier_op_kwargs = {} fourier_op = NonCartesianFFT( samples=samples, shape=img_shape, density_comp=density_comp, n_coils=L, **fourier_op_kwargs, ) Smaps = fourier_op.adj_op(np.ascontiguousarray(k_space)) elif mode == 'Stack': grid_space = [ np.linspace(min_samples[i], max_samples[i], num=img_shape[i], endpoint=False) for i in np.arange(np.size(img_shape) - 1) ] grid = np.meshgrid(*grid_space) kspace_plane_loc, _, sort_pos, idx_mask_z = \ get_stacks_fourier(samples, img_shape) Smaps = Parallel(n_jobs=n_cpu, mmap_mode='r+')( delayed(gridded_inverse_fourier_transform_stack)( kspace_data_sorted=k_space[l, sort_pos], kspace_plane_loc=kspace_plane_loc, idx_mask_z=idx_mask_z, grid=tuple(grid), volume_shape=img_shape, method=method) for l in range(L)) Smaps = np.asarray(Smaps) elif mode == 'gridding': grid_space = [ np.linspace(min_samples[i], max_samples[i], num=img_shape[i], endpoint=False) for i in np.arange(np.size(img_shape)) ] grid = np.meshgrid(*grid_space) Smaps = \ Parallel(n_jobs=n_cpu, verbose=1, mmap_mode='r+')( delayed(gridded_inverse_fourier_transform_nd)( kspace_loc=samples, kspace_data=k_space[l], grid=tuple(grid), method=method ) for l in range(L) ) Smaps = np.asarray(Smaps) else: raise ValueError('Bad smap_extract_mode chosen! ' 'Please choose between : ' '`FFT` | `NFFT` | `gridding` | `Stack`') SOS = np.sqrt(np.sum(np.abs(Smaps)**2, axis=0)) for r in range(L): Smaps[r] /= SOS return Smaps, SOS
# undersample the k-space using a radial acquisition mask # We then reconstruct the zero order solution as a baseline # Get the locations of the kspace samples and the associated observations fourier_op = Stacked3DNFFT(kspace_loc=kspace_loc, shape=image.shape, implementation='cpu', n_coils=1) kspace_obs = fourier_op.op(image.data) # Gridded solution grid_space = [ np.linspace(-0.5, 0.5, num=img_shape) for img_shape in image.shape[:-1] ] grid = np.meshgrid(*tuple(grid_space)) kspace_plane_loc, z_sample_loc, sort_pos, idx_mask_z = get_stacks_fourier( kspace_loc, image.shape) grid_soln = gridded_inverse_fourier_transform_stack( kspace_data_sorted=kspace_obs[sort_pos], kspace_plane_loc=kspace_plane_loc, idx_mask_z=idx_mask_z, grid=tuple(grid), volume_shape=image.shape, method='linear') image_rec0 = pysap.Image(data=grid_soln) # image_rec0.show() base_ssim = ssim(image_rec0, image) print('The Base SSIM is : ' + str(base_ssim)) ############################################################################# # FISTA optimization
def get_Smaps(k_space, img_shape, samples, thresh, min_samples, max_samples, mode='Gridding', method='linear', n_cpu=1): """ This method estimate the sensitivity maps information from parallel mri acquisition and for variable density sampling scheme where teh k-space center had been heavily sampled. Reference : Self-Calibrating Nonlinear Reconstruction Algorithms for Variable Density Sampling and Parallel Reception MRI https://ieeexplore.ieee.org/abstract/document/8448776 Parameters ---------- k_space: np.ndarray The acquired kspace of shape (M,L), where M is the number of samples acquired and L is the number of coils used img_shape: tuple The final output shape of Sensitivity Maps. samples: np.ndarray The sample locations where the above k_space data was acquired thresh: tuple The value of threshold in kspace for thresholding k-space center min_samples: tuple The minimum values in k-space where gridding must be done max_samples: tuple The maximum values in k-space where gridding must be done mode: string 'FFT' | 'NFFT' | 'gridding', default='gridding' Defines the mode in which we would want to interpolate, NOTE: FFT should be considered only if the input has been sampled on the grid method: string 'linear' | 'cubic' | 'nearest', default='linear' For gridding mode, it defines the way interpolation must be done n_cpu: intm default=1 Number of parallel jobs in case of parallel MRI Returns ------- Smaps: np.ndarray the estimated sensitivity maps of shape (img_shape, L) with L the number of channels ISOS: np.ndarray The sum of Square used to extract the sensitivity maps """ if len(min_samples) != len(img_shape) \ or len(max_samples) != len(img_shape) \ or len(thresh) != len(img_shape): raise NameError('The img_shape, max_samples, ' 'min_samples and thresh must be of same length') k_space, samples = \ extract_k_space_center_and_locations( data_values=k_space, samples_locations=samples, thr=thresh, img_shape=img_shape) if samples is None: mode = 'FFT' L, M = k_space.shape Smaps_shape = (L, *img_shape) Smaps = np.zeros(Smaps_shape).astype('complex128') if mode == 'FFT': if not M == img_shape[0]*img_shape[1]: raise ValueError(['The number of samples in the k-space must be', 'equal to the (image size, the number of coils)' ]) k_space = k_space.reshape(Smaps_shape) for l in range(Smaps_shape[2]): Smaps[l] = pfft.ifftshift(pfft.ifft2(pfft.fftshift(k_space[l]))) elif mode == 'NFFT': fourier_op = NonCartesianFFT(samples=samples, shape=img_shape, implementation='cpu') Smaps = np.asarray([fourier_op.adj_op(k_space[l]) for l in range(L)]) elif mode == 'Stack': grid_space = [np.linspace(min_samples[i], max_samples[i], num=img_shape[i], endpoint=False) for i in np.arange(np.size(img_shape)-1)] grid = np.meshgrid(*grid_space) kspace_plane_loc, z_sample_loc, sort_pos = \ get_stacks_fourier(samples) Smaps = Parallel(n_jobs=n_cpu)( delayed(gridded_inverse_fourier_transform_stack) (kspace_plane_loc=kspace_plane_loc, z_samples=z_sample_loc, kspace_data=k_space[l, sort_pos], grid=tuple(grid), method=method) for l in range(L)) Smaps = np.asarray(Smaps) else: grid_space = [np.linspace(min_samples[i], max_samples[i], num=img_shape[i], endpoint=False) for i in np.arange(np.size(img_shape))] grid = np.meshgrid(*grid_space) Smaps = \ Parallel(n_jobs=n_cpu)(delayed( gridded_inverse_fourier_transform_nd) (kspace_loc=samples, kspace_data=k_space[l], grid=tuple(grid), method=method) for l in range(L)) Smaps = np.asarray(Smaps) SOS = np.sqrt(np.sum(np.abs(Smaps)**2, axis=0)) for r in range(L): Smaps[r] /= SOS return Smaps, SOS
# We then reconstruct the zero order solution as a baseline # Get the locations of the kspace samples and the associated observations fourier_op = Stacked3DNFFT(kspace_loc=kspace_loc, shape=image.shape, implementation='cpu', n_coils=1) kspace_obs = fourier_op.op(image.data) # Gridded solution grid_space = [ np.linspace(-0.5, 0.5, num=image.shape[i]) for i in range(len(image.shape) - 1) ] grid = np.meshgrid(*tuple(grid_space)) kspace_plane_loc, z_sample_loc, sort_pos = get_stacks_fourier(kspace_loc) grid_soln = gridded_inverse_fourier_transform_stack(kspace_plane_loc, z_sample_loc, kspace_obs, tuple(grid), 'linear') image_rec0 = pysap.Image(data=grid_soln) # image_rec0.show() base_ssim = ssim(image_rec0, image) print('The Base SSIM is : ' + str(base_ssim)) ############################################################################# # FISTA optimization # ------------------ # # We now want to refine the zero order solution using a FISTA optimization. # The cost function is set to Proximity Cost + Gradient Cost