def remove_ring_based_wavelet_fft(mat, level=5, size=1, wavelet_name="db9", sort=False): """ Remove ring artifacts in a reconstructed image by combining the polar transform and the wavelet-fft-based method (Ref. [1]). Parameters ---------- mat : array_like Square array. Reconstructed image level : int Wavelet decomposition level. size : int Damping parameter. Larger is stronger. wavelet_name : str Name of a wavelet. Search pywavelets API for a full list. sort : bool, optional Apply sorting (Ref. [2]) if True. Returns ------- array_like Ring-removed image. References ---------- .. [1] https://doi.org/10.1364/OE.17.008567 .. [2] https://doi.org/10.1364/OE.26.028396 """ (nrow, ncol) = mat.shape if nrow != ncol: raise ValueError( "Width and height of the reconstructed image are not the same") mask = util.make_circle_mask(ncol, 1.0) (x_mat, y_mat) = util.rectangular_from_polar(ncol, ncol, ncol, ncol) (r_mat, theta_mat) = util.polar_from_rectangular(ncol, ncol, ncol, ncol) polar_mat = util.mapping(mat, x_mat, y_mat) polar_mat = remo.remove_stripe_based_wavelet_fft(polar_mat, level, size, wavelet_name, sort=sort) mat_rec = util.mapping(polar_mat, r_mat, theta_mat) return mat_rec * mask
def remove_ring_based_fft(mat, u=20, n=8, v=1, sort=False): """ Remove ring artifacts in the reconstructed image by combining the polar transform and the fft-based method. Parameters ---------- mat : array_like Square array. Reconstructed image u : int Cutoff frequency. n : int Filter order. v : int Number of rows (* 2) to be applied the filter. sort : bool, optional Apply sorting (Ref. [2]) if True. Returns ------- array_like Ring-removed image. References ---------- .. [1] https://doi.org/10.1063/1.1149043 .. [2] https://doi.org/10.1364/OE.26.028396 """ (nrow, ncol) = mat.shape if nrow != ncol: raise ValueError( "Width and height of the reconstructed image are not the same") mask = util.make_circle_mask(ncol, 1.0) (x_mat, y_mat) = util.rectangular_from_polar(ncol, ncol, ncol, ncol) (r_mat, theta_mat) = util.polar_from_rectangular(ncol, ncol, ncol, ncol) polar_mat = util.mapping(mat, x_mat, y_mat) polar_mat = remo.remove_stripe_based_fft(polar_mat, u, n, v, sort=sort) mat_rec = util.mapping(polar_mat, r_mat, theta_mat) return mat_rec * mask
def astra_reconstruction(sinogram, center, angles=None, ratio=1.0, method="FBP_CUDA", num_iter=1, filter_name="hann", pad=None, apply_log=True): """ Wrapper of reconstruction methods implemented in the astra toolbox package. https://www.astra-toolbox.com/docs/algs/index.html Users must install Astra Toolbox before using this function. Parameters ---------- sinogram : array_like 2D array. Sinogram image. center : float Center of rotation. angles : array_like 1D array. List of angles (radian) corresponding to the sinogram. ratio : float To apply a circle mask to the reconstructed image. method : str Reconstruction algorithms. for CPU: 'FBP', 'SIRT', 'SART', 'ART', 'CGLS'. for GPU: 'FBP_CUDA', 'SIRT_CUDA', 'SART_CUDA', 'CGLS_CUDA'. num_iter : int Number of iterations if using iteration methods. filter_name : str Apply filter if using FBP method. Options: 'hamming', 'hann', 'lanczos', 'kaiser', 'parzen',... pad : int Padding to reduce the side effect of FFT. apply_log : bool Apply the logarithm function to the sinogram before reconstruction. Returns ------- array_like Square array. """ try: import astra except ImportError: print("!!!!!! Error !!!!!!!") print("You must install Astra Toolbox before using this function!") raise if apply_log is True: sinogram = -np.log(sinogram) if pad is None: pad = int(0.1 * sinogram.shape[1]) sinogram = np.pad(sinogram, ((0, 0), (pad, pad)), mode='edge') (nrow, ncol) = sinogram.shape if angles is None: angles = np.linspace(0.0, 180.0, nrow) * np.pi / 180.0 proj_geom = astra.create_proj_geom('parallel', 1, ncol, angles) vol_geom = astra.create_vol_geom(ncol, ncol) cen_col = (ncol - 1.0) / 2.0 sinogram = shift(sinogram, (0, cen_col - (center + pad)), mode='nearest') sino_id = astra.data2d.create('-sino', proj_geom, sinogram) rec_id = astra.data2d.create('-vol', vol_geom) if "CUDA" not in method: proj_id = astra.create_projector('line', proj_geom, vol_geom) cfg = astra.astra_dict(method) cfg['ProjectionDataId'] = sino_id cfg['ReconstructionDataId'] = rec_id if "CUDA" not in method: cfg['ProjectorId'] = proj_id if (method == "FBP_CUDA") or (method == "FBP"): cfg["FilterType"] = filter_name alg_id = astra.algorithm.create(cfg) astra.algorithm.run(alg_id, num_iter) recon = astra.data2d.get(rec_id) astra.algorithm.delete(alg_id) astra.data2d.delete(sino_id) astra.data2d.delete(rec_id) recon = recon[pad:ncol - pad, pad:ncol - pad] if ratio is not None: ncol0 = ncol - 2 * pad if ratio == 0.0: ratio = min(center, ncol0 - center) / (0.5 * ncol0) mask = util.make_circle_mask(ncol0, ratio) recon = recon * mask return recon
def gridrec_reconstruction(sinogram, center, angles=None, ratio=1.0, filter_name="shepp", apply_log=True, pad=True, ncore=1): """ Wrapper of the gridrec method implemented in the tomopy package: https://tomopy.readthedocs.io/en/latest/api/tomopy.recon.algorithm.html Users must install Tomopy before using this function. Parameters ---------- sinogram : array_like 2D array. Sinogram image. center : float Center of rotation. angles : array_like 1D array. List of angles (radian) corresponding to the sinogram. ratio : float To apply a circle mask to the reconstructed image. filter_name : str Apply a smoothing filter. Full list is at: https://github.com/tomopy/tomopy/blob/master/source/tomopy/recon/algorithm.py apply_log : bool Apply the logarithm function to the sinogram before reconstruction. pad : bool Apply edge padding to the nearest power of 2. Returns ------- array_like Square array. """ try: import tomopy except ImportError: print("!!!!!! Error !!!!!!!") print("You must install Tomopy before using this function!") raise pad_left = 0 ncol = sinogram.shape[-1] if isinstance(pad, bool): if pad is True: ncol_pad = int(2**np.ceil(np.log2(1.0 * ncol))) pad_left = (ncol_pad - ncol) // 2 pad_right = ncol_pad - ncol - pad_left sinogram = np.pad(sinogram, ((0, 0), (pad_left, pad_right)), mode='edge') else: pad_left = pad sinogram = np.pad(sinogram, ((0, 0), (pad, pad)), mode='edge') if apply_log is True: sinogram = -np.log(sinogram) if filter_name is None: filter_name = "shepp" if angles is None: angles = np.linspace(0.0, 180.0, sinogram.shape[0]) * np.pi / 180.0 recon = tomopy.recon(np.expand_dims(sinogram, 1), angles, center=center + pad_left, algorithm='gridrec', filter_name=filter_name, ncore=ncore)[0] recon = recon[pad_left:pad_left + ncol, pad_left:pad_left + ncol] if ratio is not None: if ratio == 0.0: ratio = min(center, ncol - center) / (0.5 * ncol) mask = util.make_circle_mask(ncol, ratio) recon = recon * mask return recon
def dfi_reconstruction(sinogram, center, angles=None, ratio=1.0, filter_name="hann", pad_rate=0.25, pad_mode="edge", apply_log=True): """ Apply the DFI (direct Fourier inversion) reconstruction method to a sinogram image (Ref. [1]_). The method is a practical and direct implementation of the Fourier slice theorem (Ref. [2]_). Parameters ---------- sinogram : array_like 2D array. Sinogram image. center : float Center of rotation. angles : array_like 1D array. List of angles (in radian) corresponding to the sinogram. ratio : float To apply a circle mask to the reconstructed image. filter_name : {None, "hann", "bartlett", "blackman", "hamming", "nuttall",\\ "parzen", "triang"} Apply a smoothing filter. pad_rate : float To apply padding before the FFT. The padding width equals to (pad_rate * image_width). pad_mode : str Padding method. Full list can be found at numpy.pad documentation. apply_log : bool Apply the logarithm function to the sinogram before reconstruction. Returns ------- array_like Square array. Reconstructed image. References ---------- .. [1] https://doi.org/10.1364/OE.418448 .. [2] https://doi.org/10.1071/PH560198 """ if apply_log is True: sinogram = -np.log(sinogram) (nrow, ncol) = sinogram.shape if ncol % 2 == 0: sinogram = np.pad(sinogram, ((0, 0), (0, 1)), mode="edge") ncol1 = sinogram.shape[1] xshift = (ncol1 - 1) / 2.0 - center sinogram = shift(sinogram, (0, xshift), mode='nearest') if angles is not None: t_ang = np.sum(np.abs(np.diff(angles * 180.0 / np.pi))) if abs(t_ang - 360) < 10: nrow = nrow // 2 + 1 sinogram = (sinogram[:nrow] + np.fliplr(sinogram[-nrow:])) / 2 step = np.mean(np.abs(np.diff(angles))) b_ang = angles[0] - (angles[0] // (2 * np.pi)) * (2 * np.pi) sino_360 = np.vstack((sinogram[:nrow - 1], np.fliplr(sinogram))) sinogram = shift(sino_360, (b_ang / step, 0), mode='wrap')[:nrow] if angles[-1] < angles[0]: sinogram = np.flipud(np.fliplr(sinogram)) num_pad = int(pad_rate * ncol1) sinogram = np.pad(sinogram, ((0, 0), (num_pad, num_pad)), mode=pad_mode) ncol2 = sinogram.shape[1] mask = util.make_circle_mask(ncol2, 1.0) (r_mat, theta_mat) = generate_mapping_coordinate(ncol2, nrow, ncol2, ncol2) sino_fft = fft.fftshift(fft.fft(fft.ifftshift(sinogram, axes=1)), axes=1) if filter_name is not None: window = make_smoothing_window(filter_name, ncol2) sino_fft = sino_fft * np.tile(window, (nrow, 1)) mat_real = np.real(sino_fft) mat_imag = np.imag(sino_fft) reg_real = util.mapping( mat_real, r_mat, theta_mat, order=5, mode="reflect") * mask reg_imag = util.mapping( mat_imag, r_mat, theta_mat, order=5, mode="reflect") * mask recon = np.real( fft.fftshift(fft.ifft2( fft.ifftshift(reg_real + 1j * reg_imag))))[num_pad:ncol + num_pad, num_pad:ncol + num_pad] if ratio is not None: if ratio == 0.0: ratio = min(center, ncol - center) / (0.5 * ncol) mask = util.make_circle_mask(ncol, ratio) recon = recon * mask return recon
def fbp_reconstruction(sinogram, center, angles=None, ratio=1.0, ramp_win=None, filter_name="hann", pad=None, pad_mode="edge", apply_log=True, gpu=True): """ Apply the FBP (filtered back-projection) reconstruction method to a sinogram image. Parameters ---------- sinogram : array_like 2D array. Sinogram image. center : float Center of rotation. angles : array_like, optional 1D array. List of angles (in radian) corresponding to the sinogram. ratio : float, optional To apply a circle mask to the reconstructed image. ramp_win : complex ndarray, optional Ramp window in the Fourier space. It will be generated if None is given. filter_name : {None, "hann", "bartlett", "blackman", "hamming", "nuttall",\\ "parzen", "triang"} Apply a smoothing filter. pad : int, optional To apply padding before the FFT. The value is set to 10% of the image width if None is given. pad_mode : str, optional Padding method. Full list can be found at numpy.pad documentation. apply_log : bool, optional Apply the logarithm function to the sinogram before reconstruction. gpu : bool, optional Use GPU for computing if True. Returns ------- array_like Square array. Reconstructed image. """ if gpu is True: if cuda.is_available() is False: print("!!! No Nvidia GPU found !!! Run with CPU instead !!!") gpu = False if apply_log is True: sinogram = -np.log(sinogram) (nrow, ncol) = sinogram.shape if angles is None: angles = np.linspace(0.0, 180.0, nrow) * np.pi / 180.0 else: num_pro = len(angles) if num_pro != nrow: msg = "!!!Number of angles is not the same as the row number of " \ "the sinogram!!!" raise ValueError(msg) xlist = np.float32(np.arange(0.0, ncol) - center) sino_filtered = apply_ramp_filter(sinogram, ramp_win, filter_name, pad, pad_mode) recon = np.zeros((ncol, ncol), dtype=np.float32) if gpu is True: fbp_block = (16, 16) fbp_grid = (int(np.ceil(1.0 * ncol / fbp_block[0])), int(np.ceil(1.0 * ncol / fbp_block[1]))) back_projection_gpu[fbp_grid, fbp_block](recon, np.float32(sino_filtered), np.float32(angles), xlist, np.float32(center), np.int32(nrow), np.int32(ncol)) else: recon = back_projection_cpu(np.float32(sino_filtered), np.float32(angles), np.float32(xlist), np.float32(center)) if ratio is not None: if ratio == 0.0: ratio = min(center, ncol - center) / (0.5 * ncol) mask = util.make_circle_mask(ncol, ratio) recon = recon * mask return recon * np.pi / (nrow - 1)