def test_parallel(): # Only relevant for 3d or 4d inputs # Make input data input_2d = image_gibbs.copy() input_3d = np.stack([input_2d, input_2d], axis=2) input_4d = np.stack([input_3d, input_3d], axis=3) # Test 3d case output_3d_parallel = gibbs_removal(input_3d, inplace=False, num_threads=2) output_3d_no_parallel = gibbs_removal( input_3d, inplace=False, num_threads=1 ) assert_array_almost_equal(output_3d_parallel, output_3d_no_parallel) # Test 4d case output_4d_parallel = gibbs_removal(input_4d, inplace=False, num_threads=2) output_4d_no_parallel = gibbs_removal( input_4d, inplace=False, num_threads=1 ) assert_array_almost_equal(output_4d_parallel, output_4d_no_parallel) # Test num_threads=None case output_4d_all_cpu = gibbs_removal( input_4d, inplace=False, num_threads=None ) assert_array_almost_equal(output_4d_all_cpu, output_4d_no_parallel)
def test_swapped_gibbs_2d(): # 2D case: In this case slice_axis is a dummy variable. Since data is # already a single 2D image, to axis swapping is required image_cor0 = gibbs_removal(image_gibbs, slice_axis=0, inplace=False) assert_array_almost_equal(image_cor0, image_cor) image_cor1 = gibbs_removal(image_gibbs, slice_axis=1, inplace=False) assert_array_almost_equal(image_cor1, image_cor) image_cor2 = gibbs_removal(image_gibbs, slice_axis=2, inplace=False) assert_array_almost_equal(image_cor2, image_cor)
def test_non_square_image(): # Produce non-square 2D image Nori = 32 img = np.zeros((6 * Nori, 6 * Nori)) img[Nori: 2 * Nori, Nori: 2 * Nori] = 1 img[2 * Nori: 3 * Nori, Nori: 3 * Nori] = 1 img[3 * Nori: 4 * Nori, 2 * Nori: 3 * Nori] = 2 img[4 * Nori: 5 * Nori, 3 * Nori: 5 * Nori] = 3 # Corrupt image with gibbs ringing c = np.fft.fft2(img) c = np.fft.fftshift(c) c_crop = c[48:144, :] img_gibbs = abs(np.fft.ifft2(c_crop)/2) # Produce ground truth Nre = 16 img_gt = np.zeros((6 * Nre, 6 * Nori)) img_gt[Nre: 2 * Nre, Nori: 2 * Nori] = 1 img_gt[2 * Nre: 3 * Nre, Nori: 3 * Nori] = 1 img_gt[3 * Nre: 4 * Nre, 2 * Nori: 3 * Nori] = 2 img_gt[4 * Nre: 5 * Nre, 3 * Nori: 5 * Nori] = 3 # Suppressing gibbs artefacts img_cor = gibbs_removal(img_gibbs, inplace=False) # Correction of gibbs ringing have to be closer to gt than denoised image diff_raw = np.mean(abs(img_gibbs - img_gt)) diff_cor = np.mean(abs(img_cor - img_gt)) assert_(diff_raw > diff_cor)
def test_swapped_gibbs_3d(): image3d = np.zeros((6 * Nre, 2, 6 * Nre)) image3d[:, 0, :] = image_gibbs image3d[:, 1, :] = image_gibbs image3d_cor = gibbs_removal(image3d, slice_axis=1) assert_array_almost_equal(image3d_cor[:, 0, :], image_cor) assert_array_almost_equal(image3d_cor[:, 1, :], image_cor) image3d = np.zeros((2, 6 * Nre, 6 * Nre)) image3d[0, :, :] = image_gibbs image3d[1, :, :] = image_gibbs image3d_cor = gibbs_removal(image3d, slice_axis=0) assert_array_almost_equal(image3d_cor[0, :, :], image_cor) assert_array_almost_equal(image3d_cor[1, :, :], image_cor)
def test_gibbs_3d(): image3d = np.zeros((6 * Nre, 6 * Nre, 2)) image3d[:, :, 0] = image_gibbs image3d[:, :, 1] = image_gibbs image3d_cor = gibbs_removal(image3d, 2) assert_array_almost_equal(image3d_cor[:, :, 0], image_cor) assert_array_almost_equal(image3d_cor[:, :, 1], image_cor)
def test_gibbs_2d(): # Correction of gibbs ringing have to be closer to gt than denoised image diff_raw = np.mean(abs(image_gibbs - image_gt)) diff_cor = np.mean(abs(image_cor - image_gt)) assert_(diff_raw > diff_cor) # Test if gibbs_removal works for 2D data image_cor2 = gibbs_removal(image_gibbs, inplace=False) assert_array_almost_equal(image_cor2, image_cor)
def main(iFname, oFname, plot): """Main.""" # load data data, affine = load_nifti(iFname) """ Gibbs oscillation suppression of all multi-shell data and all slices can be performed in the following way: """ data_corrected = gibbs_removal(data, slice_axis=2, num_threads=None) """ Due to the high dimensionality of diffusion-weighted data, we recommend that you specify which is the axis of data matrix that corresponds to different slices in the above step. This is done by using the optional parameter 'slice_axis'. Below we plot the results for an image acquired with b-value=0: """ if plot: fig2, ax = plt.subplots(1, 3, figsize=(12, 6), subplot_kw={'xticks': [], 'yticks': []}) ax.flat[0].imshow(data[:, :, 0, 0].T, cmap='gray', origin='lower', vmin=0, vmax=max(data)) ax.flat[0].set_title('Uncorrected b0') ax.flat[1].imshow(data_corrected[:, :, 0, 0].T, cmap='gray', origin='lower', vmin=0, vmax=max(data)) ax.flat[1].set_title('Corrected b0') ax.flat[2].imshow(data_corrected[:, :, 0, 0].T - data[:, :, 0, 0].T, cmap='gray', origin='lower', vmin=-500, vmax=500) ax.flat[2].set_title('Gibbs residuals') # plt.show() """ .. figure:: Gibbs_suppression_b0.png :align: center Uncorrected (left panel) and corrected (middle panel) b-value=0 images. For reference, the difference between uncorrected and corrected images is shown in the right panel. """ fig2.savefig(oFname+'.png') print("Result figure saved to: "+oFname+".png") # save as Nii nib.save(nib.Nifti1Image(data_corrected, affine), oFname+'.nii.gz') print("Ungibbsed dataset saved to: "+oFname+".nii.gz") """
def _unring(vol): print('unringing', vol) img = load(vol) unringed = gibbs_removal(img.get_fdata()) outPrefix = vol.split('.nii')[0] + '_ur' new_image = Nifti1Image(unringed, affine=img.affine, header=img.header) new_image.to_filename(outPrefix + '.nii.gz')
def run(self, input_files, slice_axis=2, n_points=3, num_processes=1, out_dir='', out_unring='dwi_unrig.nii.gz'): r"""Workflow for applying Gibbs Ringing method. Parameters ---------- input_files : string Path to the input volumes. This path may contain wildcards to process multiple inputs at once. slice_axis : int, optional Data axis corresponding to the number of acquired slices. Could be (0, 1, or 2): for example, a value of 2 would mean the third axis. n_points : int, optional Number of neighbour points to access local TV (see note). num_processes : int or None, optional Split the calculation to a pool of children processes. Only applies to 3D or 4D `data` arrays. Default is 1. If < 0 the maximal number of cores minus |num_processes + 1| is used (enter -1 to use as many cores as possible). 0 raises an error. out_dir : string, optional Output directory. (default current directory) out_unrig : string, optional Name of the resulting denoised volume. References ---------- .. [1] Neto Henriques, R., 2018. Advanced Methods for Diffusion MRI Data Analysis and their Application to the Healthy Ageing Brain (Doctoral thesis). https://doi.org/10.17863/CAM.29356 .. [2] Kellner E, Dhital B, Kiselev VG, Reisert M. Gibbs-ringing artifact removal based on local subvoxel-shifts. Magn Reson Med. 2016 doi: 10.1002/mrm.26054. """ io_it = self.get_io_iterator() for dwi, ounring in io_it: logging.info('Unringing %s', dwi) data, affine, image = load_nifti(dwi, return_img=True) unring_data = gibbs_removal(data, slice_axis=slice_axis, n_points=n_points, num_processes=num_processes) save_nifti(ounring, unring_data, affine, image.header) logging.info('Denoised volume saved as %s', ounring)
def run(self, input_files, slice_axis=2, n_points=3, num_threads=1, out_dir='', out_unring='dwi_unrig.nii.gz'): r"""Workflow for applying Gibbs Ringing method. Parameters ---------- input_files : string Path to the input volumes. This path may contain wildcards to process multiple inputs at once. slice_axis : int, optional Data axis corresponding to the number of acquired slices. Default is set to the third axis(2). Could be (0, 1, or 2). n_points : int, optional Number of neighbour points to access local TV (see note). Default is set to 3. num_threads : int or None, optional Number of threads. Only applies to 3D or 4D `data` arrays. If None then all available threads will be used. Otherwise, must be a positive integer. Default is set to 1. out_dir : string, optional Output directory (default input file directory) out_unrig : string, optional Name of the resulting denoised volume (default: dwi_unrig.nii.gz) References ---------- .. [1] Neto Henriques, R., 2018. Advanced Methods for Diffusion MRI Data Analysis and their Application to the Healthy Ageing Brain (Doctoral thesis). https://doi.org/10.17863/CAM.29356 .. [2] Kellner E, Dhital B, Kiselev VG, Reisert M. Gibbs-ringing artifact removal based on local subvoxel-shifts. Magn Reson Med. 2016 doi: 10.1002/mrm.26054. """ io_it = self.get_io_iterator() for dwi, ounring in io_it: logging.info('Unringing %s', dwi) data, affine, image = load_nifti(dwi, return_img=True) unring_data = gibbs_removal(data, slice_axis=slice_axis, n_points=n_points, num_threads=num_threads) save_nifti(ounring, unring_data, affine, image.header) logging.info('Denoised volume saved as %s', ounring)
def test_swapped_gibbs_4d(): image4d = np.zeros((2, 6 * Nre, 6 * Nre, 2)) image4d[0, :, :, 0] = image_gibbs image4d[1, :, :, 0] = image_gibbs image4d[0, :, :, 1] = image_gibbs image4d[1, :, :, 1] = image_gibbs image4d_cor = gibbs_removal(image4d, slice_axis=0) assert_array_almost_equal(image4d_cor[0, :, :, 0], image_cor) assert_array_almost_equal(image4d_cor[1, :, :, 0], image_cor) assert_array_almost_equal(image4d_cor[0, :, :, 1], image_cor) assert_array_almost_equal(image4d_cor[1, :, :, 1], image_cor)
def test_inplace(): # Make input data input_2d = image_gibbs.copy() input_3d = np.stack([input_2d, input_2d], axis=2) input_4d = np.stack([input_3d, input_3d], axis=3) # Test 2d cases output_2d = gibbs_removal(input_2d, inplace=False) assert_raises( AssertionError, assert_array_almost_equal, input_2d, output_2d ) output_2d = gibbs_removal(input_2d, inplace=True) assert_array_almost_equal(input_2d, output_2d) # Test 3d case output_3d = gibbs_removal(input_3d, inplace=False) assert_raises( AssertionError, assert_array_almost_equal, input_3d, output_3d ) output_3d = gibbs_removal(input_3d, inplace=True) assert_array_almost_equal(input_3d, output_3d) # Test 4d case output_4d = gibbs_removal(input_4d, inplace=False) assert_raises( AssertionError, assert_array_almost_equal, input_4d, output_4d ) output_4d = gibbs_removal(input_4d, inplace=True) assert_array_almost_equal(input_4d, output_4d)
def test_gibbs_4d(): image4d = np.zeros((6 * Nre, 6 * Nre, 2, 2)) image4d[:, :, 0, 0] = image_gibbs image4d[:, :, 1, 0] = image_gibbs image4d[:, :, 0, 1] = image_gibbs image4d[:, :, 1, 1] = image_gibbs image4d_cor = gibbs_removal(image4d) assert_array_almost_equal(image4d_cor[:, :, 0, 0], image_cor) assert_array_almost_equal(image4d_cor[:, :, 1, 0], image_cor) assert_array_almost_equal(image4d_cor[:, :, 0, 1], image_cor) assert_array_almost_equal(image4d_cor[:, :, 1, 1], image_cor)
removing high frequencies of the image's Fourier transform. """ c = np.fft.fft2(t1_slice) c = np.fft.fftshift(c) N = c.shape[0] c_crop = c[64: 192, 64: 192] N = c_crop.shape[0] t1_gibbs = abs(np.fft.ifft2(c_crop)/4) """ Gibbs oscillation suppression of this single data slice can be performed by running the following command: """ t1_unring = gibbs_removal(t1_gibbs, inplace=False) """ Let’s plot the results: """ fig1, ax = plt.subplots(1, 3, figsize=(12, 6), subplot_kw={'xticks': [], 'yticks': []}) ax.flat[0].imshow(t1_gibbs, cmap="gray", vmin=100, vmax=400) ax.flat[0].annotate('Rings', fontsize=10, xy=(81, 70), color='red', xycoords='data', xytext=(30, 0), textcoords='offset points', arrowprops=dict(arrowstyle="->", color='red'))
suppression algorithm, Gibbs artefacts are artificially introduced by removing high frequencies of the image's Fourier transform. """ c = np.fft.fft2(t1_slice) c = np.fft.fftshift(c) N = c.shape[0] c_crop = c[64:192, 64:192] N = c_crop.shape[0] t1_gibbs = abs(np.fft.ifft2(c_crop) / 4) """ Gibbs oscillation suppression of this single data slice can be performed by running the following command: """ t1_unring = gibbs_removal(t1_gibbs) """ Let’s plot the results: """ fig1, ax = plt.subplots(1, 3, figsize=(12, 6), subplot_kw={ 'xticks': [], 'yticks': [] }) ax.flat[0].imshow(t1_gibbs, cmap="gray", vmin=100, vmax=400) ax.flat[0].annotate('Rings', fontsize=10,
def denoise_pick(data, affine, hdr, outpath, mask, type_denoise='macenko', processes=1, savedenoise=True, verbose=False, forcestart=False, datareturn=False, display=None): allowed_strings = ['mpca', 'yes', 'all', 'gibbs', 'none', 'macenko'] string_inclusion(type_denoise, allowed_strings, "type_denoise") if type_denoise == 'macenko' or type_denoise == 'mpca' or type_denoise == 'yes' or type_denoise == 'all': type_denoise = '_mpca_' if type_denoise == 'gibbs': type_denoise = "_gibbs" if type_denoise is None or type_denoise == 'none': type_denoise = "_" outpath_denoise = outpath + type_denoise + 'dwi.nii.gz' if os.path.exists(outpath_denoise) and not forcestart: if verbose: txt = "Denoising already done at " + outpath_denoise print(txt) if datareturn: data = load_nifti(outpath_denoise) else: if type_denoise == '_mpca_': # data, snr = marcenko_denoise(data, False, verbose=verbose) t = time() denoised_arr, sigma = mppca(data, patch_radius=2, return_sigma=True, processes=processes, verbose=verbose) save_nifti(outpath_denoise, denoised_arr, affine, hdr=hdr) if verbose: txt = ("Saved image at " + outpath_denoise) print(txt) mask = np.array(mask, dtype=bool) mean_sigma = np.mean(sigma[mask]) b0 = denoised_arr[..., 0] mean_signal = np.mean(b0[mask]) snr = mean_signal / mean_sigma if verbose: print("Time taken for local MP-PCA ", -t + time()) print("The SNR of the b0 image appears to be at " + str(snr)) if display: denoise_fig(data, denoised_arr, type='macenko') data = denoised_arr if type_denoise == 'gibbs': outpath_denoise = outpath + '_gibbs.nii.gz' if os.path.exists(outpath_denoise) and not forcestart: if verbose: txt = "Denoising already done at " + outpath_denoise print(txt) if datareturn: data = load_nifti(outpath_denoise) t = time() data_corrected = gibbs_removal(data, slice_axis=2) save_nifti(outpath_denoise, denoised_arr, affine, hdr=hdr) if verbose: print("Time taken for the gibbs removal ", -t + time()) if display: denoise_fig(data, data_corrected, type='gibbs') data = data_corrected if type_denoise == "_": print('No denoising was done') save_nifti(outpath_denoise, data, affine, hdr=hdr) return data, outpath_denoise
Since the diffusion kurtosis models involves the estimation of a large number of parameters [TaxCMW2015]_ and since the non-Gaussian components of the diffusion signal are more sensitive to artefacts [NetoHe2012]_, it might be favorable to suppress the effects of noise and artefacts before diffusion kurtosis fitting. In this example the effects of noise are suppressed using the Marcenko-Pastur PCA denoising algorithm (:ref:`example-denoise-mppca`) and the Gibbs artefact suppression algorithm (:ref:`example-denoise-gibbs`). Due to the dataset's large number of diffusion-weighted volumes, these algorithm might take some hours to process. """ print("Denoising") den, sig = mppca(data, patch_radius=4, return_sigma=True) print("Removing Gibbs artifacts") deng = gibbs_removal(den, slice_axis=2) # img = nib.load('denoised_mppca.nii.gz') # img = nib.load('data_mppca_gibbs.nii.gz') # deng = img.get_fdata() """ Before carrying on with dki processing, I will just visually inspect the quality of data and preprocessing step. In the figure below, I am ploting a slice of the raw and denoised + gibbs suppressed data (upper panels). Lower left panel show the difference between raw and denoised data - this difference map does not present anatomicaly information, indicating that PCA denoising suppression noise with minimal low of anatomical information. Lower right map shows the noise std estimate. Lower noise std values are underestimated in background since noise in background appraoches a Rayleigh distribution. """ axial_slice = 10