def iradon(radon_image, a=0, b=1, theta=None): """ adopted from scikit-image :param radon_image: sinogram :param a, b: transformation parameters :return: 2D image """ if radon_image.ndim != 2: raise ValueError('The input image must be 2-D') if theta is None: theta = np.linspace(0, 180, radon_image.shape[1], endpoint=False) angles_count = len(theta) if angles_count != radon_image.shape[1]: raise ValueError("The given ``theta`` does not match the number of " "projections in ``radon_image``.") img_shape = radon_image.shape[0] output_size = img_shape radon_image = _sinogram_circle_to_square(radon_image) img_shape = radon_image.shape[0] # Resize image to next power of two (but no less than 64) for # Fourier analysis; speeds up Fourier and lessens artifacts projection_size_padded = max(64, int(2**np.ceil(np.log2(2 * img_shape)))) pad_width = ((0, projection_size_padded - img_shape), (0, 0)) img = np.pad(radon_image, pad_width, mode='constant', constant_values=0) # Apply filter in Fourier domain fourier_filter = smooth_sharpen_filter(projection_size_padded, a, b) projection = fft(img, axis=0) * fourier_filter radon_filtered = np.real(ifft(projection, axis=0)[:img_shape, :]) # Reconstruct image by interpolation reconstructed = np.zeros((output_size, output_size)) radius = output_size // 2 xpr, ypr = np.mgrid[:output_size, :output_size] - radius x = np.arange(img_shape) - img_shape // 2 for col, angle in zip(radon_filtered.T, np.deg2rad(theta)): t = ypr * np.cos(angle) - xpr * np.sin(angle) interpolant = partial(np.interp, xp=x, fp=col, left=0, right=0) reconstructed += interpolant(t) out_reconstruction_circle = (xpr**2 + ypr**2) > radius**2 reconstructed[out_reconstruction_circle] = 0. return reconstructed * np.pi / (2 * angles_count)
def check_sinogram_circle_to_square(size): from skimage.transform.radon_transform import _sinogram_circle_to_square image = _random_circle((size, size)) theta = np.linspace(0.0, 180.0, size, False) sinogram_circle = radon(image, theta, circle=True) argmax_shape = lambda a: np.unravel_index(np.argmax(a), a.shape) print("\n\targmax of circle:", argmax_shape(sinogram_circle)) sinogram_square = radon(image, theta, circle=False) print("\targmax of square:", argmax_shape(sinogram_square)) sinogram_circle_to_square = _sinogram_circle_to_square(sinogram_circle) print("\targmax of circle to square:", argmax_shape(sinogram_circle_to_square)) error = abs(sinogram_square - sinogram_circle_to_square) print(np.mean(error), np.max(error)) assert argmax_shape(sinogram_square) == argmax_shape(sinogram_circle_to_square)
def check_sinogram_circle_to_square(size): from skimage.transform.radon_transform import _sinogram_circle_to_square image = _random_circle((size, size)) theta = np.linspace(0., 180., size, False) sinogram_circle = radon(image, theta, circle=True) argmax_shape = lambda a: np.unravel_index(np.argmax(a), a.shape) print('\n\targmax of circle:', argmax_shape(sinogram_circle)) sinogram_square = radon(image, theta, circle=False) print('\targmax of square:', argmax_shape(sinogram_square)) sinogram_circle_to_square = _sinogram_circle_to_square(sinogram_circle) print('\targmax of circle to square:', argmax_shape(sinogram_circle_to_square)) error = abs(sinogram_square - sinogram_circle_to_square) print(np.mean(error), np.max(error)) assert (argmax_shape(sinogram_square) == argmax_shape( sinogram_circle_to_square))
def iradonT(radon_image, theta=None, output_size=None, filter="ramp", interpolation="linear", circle=False): """ Inverse radon transform. Reconstruct an image from the radon transform, using the filtered back projection algorithm. Parameters ---------- radon_image : array_like, dtype=float Image containing radon transform (sinogram). Each column of the image corresponds to a projection along a different angle. The tomography rotation axis should lie at the pixel index ``radon_image.shape[0] // 2`` along the 0th dimension of ``radon_image``. theta : array_like, dtype=float, optional Reconstruction angles (in degrees). Default: m angles evenly spaced between 0 and 180 (if the shape of `radon_image` is (N, M)). output_size : int Number of rows and columns in the reconstruction. filter : str, optional (default ramp) Filter used in frequency domain filtering. Ramp filter used by default. Filters available: ramp, shepp-logan, cosine, hamming, hann. Assign None to use no filter. interpolation : str, optional (default 'linear') Interpolation method used in reconstruction. Methods available: 'linear', 'nearest', and 'cubic' ('cubic' is slow). circle : boolean, optional Assume the reconstructed image is zero outside the inscribed circle. Also changes the default output_size to match the behaviour of ``radon`` called with ``circle=True``. Returns ------- reconstructed : ndarray Reconstructed image. The rotation axis will be located in the pixel with indices ``(reconstructed.shape[0] // 2, reconstructed.shape[1] // 2)``. Notes ----- It applies the Fourier slice theorem to reconstruct an image by multiplying the frequency domain of the filter with the FFT of the projection data. This algorithm is called filtered back projection. """ if radon_image.ndim != 2: raise ValueError('The input image must be 2-D') if theta is None: m, n = radon_image.shape theta = np.linspace(0, 180, n, endpoint=False) else: theta = np.asarray(theta) if len(theta) != radon_image.shape[1]: raise ValueError("The given ``theta`` does not match the number of " "projections in ``radon_image``.") interpolation_types = ('linear', 'nearest', 'cubic') if not interpolation in interpolation_types: raise ValueError("Unknown interpolation: %s" % interpolation) if not output_size: # If output size not specified, estimate from input radon image if circle: output_size = radon_image.shape[0] else: output_size = int(np.floor(np.sqrt((radon_image.shape[0])**2 / 2.0))) if circle: radon_image = _sinogram_circle_to_square(radon_image) th = (np.pi / 180.0) * theta # resize image to next power of two (but no less than 64) for # Fourier analysis; speeds up Fourier and lessens artifacts projection_size_padded = \ max(64, int(2**np.ceil(np.log2(2 * radon_image.shape[0])))) pad_width = ((0, projection_size_padded - radon_image.shape[0]), (0, 0)) img = util.pad(radon_image, pad_width, mode='constant', constant_values=0) # Construct the Fourier filter #delta = 0.005 l1 = (2*np.pi)**(-4/5) * (delta)**(8/5) /5 l2 = (2*np.pi)**(-4/5) * (delta)**(-2/5) *4/5 f = fftfreq(projection_size_padded).reshape(-1, 1) # digital frequency omega = 2 * np.pi * f # angular frequency fourier_filter = 2 * np.abs(f) # ramp filter if filter == "ramp": pass elif filter == "tigran": g = fftfreq(projection_size_padded).reshape(-1, 1) w = abs(omega) g[1:] = l2 / (l1 * ((w[1:])**5 / (2*np.pi)) + l2) - np.sqrt(l1*l2) * (w[1:])**2 * np.sqrt(l1*((w[1:])**5 / (2*np.pi)) + l2 - w[1:]/(2*np.pi)) / (l1 * ((w[1:])**5 / (2*np.pi)) + l2) fourier_filter[1:] = fourier_filter[1:] * g[1:] elif filter == "shepp-logan": # Start from first element to avoid divide by zero fourier_filter[1:] = fourier_filter[1:] * np.sin(omega[1:]) / omega[1:] elif filter == "cosine": fourier_filter *= np.cos(omega) elif filter == "hamming": fourier_filter *= (0.54 + 0.46 * np.cos(omega / 2)) elif filter == "hann": fourier_filter *= (1 + np.cos(omega / 2)) / 2 elif filter is None: fourier_filter[:] = 1 else: raise ValueError("Unknown filter: %s" % filter) # Apply filter in Fourier domain projection = fft(img, axis=0) * fourier_filter radon_filtered = np.real(ifft(projection, axis=0)) # Resize filtered image back to original size radon_filtered = radon_filtered[:radon_image.shape[0], :] reconstructed = np.zeros((output_size, output_size)) # Determine the center of the projections (= center of sinogram) mid_index = radon_image.shape[0] // 2 [X, Y] = np.mgrid[0:output_size, 0:output_size] xpr = X - int(output_size) // 2 ypr = Y - int(output_size) // 2 # Reconstruct image by interpolation for i in range(len(theta)): t = ypr * np.cos(th[i]) - xpr * np.sin(th[i]) x = np.arange(radon_filtered.shape[0]) - mid_index if interpolation == 'linear': backprojected = np.interp(t, x, radon_filtered[:, i], left=0, right=0) else: interpolant = interp1d(x, radon_filtered[:, i], kind=interpolation, bounds_error=False, fill_value=0) backprojected = interpolant(t) reconstructed += backprojected if circle: radius = output_size // 2 reconstruction_circle = (xpr**2 + ypr**2) <= radius**2 reconstructed[~reconstruction_circle] = 0. return reconstructed * np.pi / (2 * len(th))
def iradonT(radon_image, theta=None, output_size=None, filter="ramp", interpolation="linear", circle=False): """ Inverse radon transform. Reconstruct an image from the radon transform, using the filtered back projection algorithm. Parameters ---------- radon_image : array_like, dtype=float Image containing radon transform (sinogram). Each column of the image corresponds to a projection along a different angle. The tomography rotation axis should lie at the pixel index ``radon_image.shape[0] // 2`` along the 0th dimension of ``radon_image``. theta : array_like, dtype=float, optional Reconstruction angles (in degrees). Default: m angles evenly spaced between 0 and 180 (if the shape of `radon_image` is (N, M)). output_size : int Number of rows and columns in the reconstruction. filter : str, optional (default ramp) Filter used in frequency domain filtering. Ramp filter used by default. Filters available: ramp, shepp-logan, cosine, hamming, hann. Assign None to use no filter. interpolation : str, optional (default 'linear') Interpolation method used in reconstruction. Methods available: 'linear', 'nearest', and 'cubic' ('cubic' is slow). circle : boolean, optional Assume the reconstructed image is zero outside the inscribed circle. Also changes the default output_size to match the behaviour of ``radon`` called with ``circle=True``. Returns ------- reconstructed : ndarray Reconstructed image. The rotation axis will be located in the pixel with indices ``(reconstructed.shape[0] // 2, reconstructed.shape[1] // 2)``. Notes ----- It applies the Fourier slice theorem to reconstruct an image by multiplying the frequency domain of the filter with the FFT of the projection data. This algorithm is called filtered back projection. """ if radon_image.ndim != 2: raise ValueError('The input image must be 2-D') if theta is None: m, n = radon_image.shape theta = np.linspace(0, 180, n, endpoint=False) else: theta = np.asarray(theta) if len(theta) != radon_image.shape[1]: raise ValueError("The given ``theta`` does not match the number of " "projections in ``radon_image``.") interpolation_types = ('linear', 'nearest', 'cubic') if not interpolation in interpolation_types: raise ValueError("Unknown interpolation: %s" % interpolation) if not output_size: # If output size not specified, estimate from input radon image if circle: output_size = radon_image.shape[0] else: output_size = int( np.floor(np.sqrt((radon_image.shape[0])**2 / 2.0))) if circle: radon_image = _sinogram_circle_to_square(radon_image) th = (np.pi / 180.0) * theta # resize image to next power of two (but no less than 64) for # Fourier analysis; speeds up Fourier and lessens artifacts projection_size_padded = \ max(64, int(2**np.ceil(np.log2(2 * radon_image.shape[0])))) pad_width = ((0, projection_size_padded - radon_image.shape[0]), (0, 0)) img = util.pad(radon_image, pad_width, mode='constant', constant_values=0) # Construct the Fourier filter #delta = 1 l1 = (2 * np.pi)**(-4 / 5) * (delta)**(8 / 5) / 5 l2 = (2 * np.pi)**(-4 / 5) * (delta)**(-2 / 5) * 4 / 5 f = fftfreq(projection_size_padded).reshape(-1, 1) # digital frequency omega = 2 * np.pi * f # angular frequency fourier_filter = 2 * np.abs(f) # ramp filter if filter == "ramp": pass elif filter == "tigran": g = fftfreq(projection_size_padded).reshape(-1, 1) w = abs(omega) g[1:] = l2 / (l1 * ( (w[1:])**5 / (2 * np.pi)) + l2) + np.sqrt(l1 * l2) * (w[1:])**2 * np.sqrt(l1 * ( (w[1:])**5 / (2 * np.pi)) + l2 - w[1:] / (2 * np.pi)) / (l1 * ((w[1:])**5 / (2 * np.pi)) + l2) fourier_filter[1:] = fourier_filter[1:] * g[1:] elif filter == "shepp-logan": # Start from first element to avoid divide by zero fourier_filter[1:] = fourier_filter[1:] * np.sin(omega[1:]) / omega[1:] elif filter == "cosine": fourier_filter *= np.cos(omega) elif filter == "hamming": fourier_filter *= (0.54 + 0.46 * np.cos(omega / 2)) elif filter == "hann": fourier_filter *= (1 + np.cos(omega / 2)) / 2 elif filter is None: fourier_filter[:] = 1 else: raise ValueError("Unknown filter: %s" % filter) # Apply filter in Fourier domain projection = fft(img, axis=0) * fourier_filter radon_filtered = np.real(ifft(projection, axis=0)) # Resize filtered image back to original size radon_filtered = radon_filtered[:radon_image.shape[0], :] reconstructed = np.zeros((output_size, output_size)) # Determine the center of the projections (= center of sinogram) mid_index = radon_image.shape[0] // 2 [X, Y] = np.mgrid[0:output_size, 0:output_size] xpr = X - int(output_size) // 2 ypr = Y - int(output_size) // 2 # Reconstruct image by interpolation for i in range(len(theta)): t = ypr * np.cos(th[i]) - xpr * np.sin(th[i]) x = np.arange(radon_filtered.shape[0]) - mid_index if interpolation == 'linear': backprojected = np.interp(t, x, radon_filtered[:, i], left=0, right=0) else: interpolant = interp1d(x, radon_filtered[:, i], kind=interpolation, bounds_error=False, fill_value=0) backprojected = interpolant(t) reconstructed += backprojected if circle: radius = output_size // 2 reconstruction_circle = (xpr**2 + ypr**2) <= radius**2 reconstructed[~reconstruction_circle] = 0. return reconstructed * np.pi / (2 * len(th))