def test_kernel_hann_rotational(): kernel = kernels.get_kernel("hann_rotational", size=15) hann_kernel = kernels.get_kernel_hann_rotational(size=15) assert np.array_equal(kernel, hann_kernel) assert hann_kernel.shape == (15, 15) assert hann_kernel.dtype == np.float64 assert 1.0 == approx(hann_kernel.sum()) # check symmetries assert kernels.is_symmetric(hann_kernel, "axial") assert kernels.is_symmetric(hann_kernel, "rotational") kernel = kernels.get_kernel("hann_rotational", size=13, size_y=15) hann_kernel = kernels.get_kernel_hann_rotational(size=13, size_y=15) assert np.array_equal(kernel, hann_kernel) assert hann_kernel.shape == (13, 15) assert hann_kernel.dtype == np.float64 assert 1.0 == approx(hann_kernel.sum()) kernel = kernels.get_kernel("hann_rotational", size=13, size_y=11) hann_kernel = kernels.get_kernel_hann_rotational(size=13, size_y=11) assert np.array_equal(kernel, hann_kernel) assert hann_kernel.shape == (13, 11) assert hann_kernel.dtype == np.float64 assert 1.0 == approx(hann_kernel.sum()) return
def test_get_avail_names(): list = kernels.get_avail_names() # Basic type check. # Check that get_kernel() can use all of the provided names. # Check that all kernel names are implemented for element in list: assert type(element) == str kernels.get_kernel(name=element, size=5, option=1) return
def fourier(img: ndarray, shift: bool = True, window: Optional[str] = None, pad: Optional[Union[int, List[int], ndarray]] = None, implementation: str = 'scipy') -> ndarray: """Calculates the fourier transform of an image. Image will be downsampled to greyscale if necessary. Additionally to the basic fast fourier transformations (FFT), such as numpy or scipy, this function implements proper windowing prior to the transformation, as well as a proper shift of the zero frequency component for even-sized images. Different implementations are supported. For plotting, see `func:plenpy.utilities.plots.plot_fft`. Note, that color channels will be converted to greyscale prior to FFT calculation. Args: img: Input image. shift: If ``True``, shift spectrum, i.e. zero frequency component is shifted to the center. If image is even-sized, zero padding will be performed. window: Specify 2D window used to reduce spectral leakage. See :func:`.plenpy.utilities.kernels.get_avail_names()` for a list of available windows kernels. If ``None`` is specified, no windowing is performed. pad: Zero padding applied in all directions in pixel. If int is given, padding will be identical in all directions. If list/ndarray ist given, padding will be performed independently in x- and y-direction. implementation: The implementation used to calculate the Fourier transform. Available implementations are 'scipy', 'numpy', 'fftw'. See also: :func:`.scipy.fftpack.fft2()` and :func:`.numpy.fft.fft2()`. For 'fftw', ``pyFFTW`` has to be installed on the system, see https://github.com/hgomersall/pyFFTW . Default: 'scipy'. Returns: The (complex valued) fourier transform of the input image. """ implementation_list = ['numpy', 'scipy', 'fftw'] # Check image shape, convert to greyscale if necessary if img.ndim == 3: img = rgb2gray(img) n, m = img.shape if window is not None: # Get specified 2D window function kern = kernels.get_kernel(window, size=n, size_y=m, normalize='peak').astype(img.dtype) # Do element wise window multiplication img = np.multiply(kern, img) # If shape is even, add zero padding to center the zero frequency if n % 2 == 0: img = np.pad(img, ((0, 1), (0, 0)), mode='constant') n, m = img.shape if m % 2 == 0: img = np.pad(img, ((0, 0), (0, 1)), mode='constant') n, m = img.shape if pad is not None: # Perform zero padding if type(pad) == int: pad_x = pad pad_y = pad elif (type(pad) == list or type(pad) == ndarray) and len(pad) == 2: pad_x = pad[0] pad_y = pad[1] else: raise ValueError( "Option 'pad' must be int or list/ndarray of length 2") # Perform padding img = np.pad(img, ((pad_x, pad_x), (pad_y, pad_y)), mode='constant') n, m = img.shape if implementation not in implementation_list: raise ValueError( f"The implementation '{implementation}' is not one of the " f"supported implementations, {implementation_list}.") elif implementation == 'scipy': img = scipy.fftpack.fft2(img) elif implementation == 'numpy': img = np.fft.fft2(img) elif implementation == 'fftw': pyfftw.interfaces.cache.enable() img = pyfftw.interfaces.numpy_fft.fft2(img, threads=4, planner_effort='FFTW_ESTIMATE') if shift: img = np.fft.fftshift(img) return img
def test_kernel_disk(): kernel = kernels.get_kernel("disk", size=15) disk_kernel = kernels.get_kernel_disk(size=15) assert np.array_equal(kernel, disk_kernel) assert kernel.shape == (15, 15) assert kernel.dtype == np.float64 assert 1.0 == approx(kernel.sum()) kernel = kernels.get_kernel("disk", size=15) disk_kernel = kernels.get_kernel_disk(size=15, radius=7) assert np.array_equal(kernel, disk_kernel) assert kernel.shape == (15, 15) assert kernel.dtype == np.float64 assert 1.0 == approx(kernel.sum()) kernel = kernels.get_kernel("disk", size=15) disk_kernel = kernels.get_kernel_disk(size=15, radius_y=7) assert np.array_equal(kernel, disk_kernel) assert kernel.shape == (15, 15) assert kernel.dtype == np.float64 assert 1.0 == approx(kernel.sum()) kernel = kernels.get_kernel("disk", size=15, size_y=17, option=3, option_y=5) disk_kernel = kernels.get_kernel_disk(size=15, size_y=17, radius=3, radius_y=5) assert np.array_equal(kernel, disk_kernel) assert kernel.shape, (15, 17) assert kernel.dtype, np.float64 assert 1.0 == approx(kernel.sum()) # test disk kernel values size_x = 51 size_y = 61 radius_x = 21 radius_y = 29 kernel = kernels.get_kernel_disk(size=size_x, radius=radius_x, size_y=size_y, radius_y=radius_y) # set all non zero values to 1.0 for easy comparison kernel = np.divide(kernel, kernel.max()) mu_x = (size_x - 1) / 2.0 mu_y = (size_y - 1) / 2.0 x, y = np.meshgrid(np.arange(0, size_x, 1), np.arange(0, size_y, 1)) mask = (x - mu_x)**2 / radius_x**2 + (y - mu_y)**2 / radius_y**2 <= 1 # Check that all masked values are 1.0, and all unmask values are 0.0 assert np.all(kernel[mask.T]) assert not np.all(kernel[~mask.T]) return
def test_get_kernel_parsing(): with raises(ValueError) as cm: kernel = kernels.get_kernel("noValidKernelName", 3) assert ("Specified argument name 'noValidKernelName' is not one of the " f"recognized kernels: {kernels.get_avail_names()}") == str( cm.value) with raises(ValueError) as cm: kernel = kernels.get_kernel_gauss(3.5) assert "Specified size 3.5 is not an integer." == str(cm.value) with raises(ValueError) as cm: kernel = kernels.get_kernel_disk(3.5) assert "Specified size 3.5 is not an integer." == str(cm.value) with raises(ValueError) as cm: kernel = kernels.get_kernel_hann(3.5) assert "Specified size 3.5 is not an integer." == str(cm.value) with raises(ValueError) as cm: kernel = kernels.get_kernel_hann_rotational(3.5) assert "Specified size 3.5 is not an integer." == str(cm.value) with raises(ValueError) as cm: kernel = kernels.get_kernel_hamming(3.5) assert "Specified size 3.5 is not an integer." == str(cm.value) with raises(ValueError) as cm: kernel = kernels.get_kernel_kaiser(3.5) assert "Specified size 3.5 is not an integer." == str(cm.value) with raises(ValueError) as cm: kernel = kernels.get_kernel_disk(3, 1, size_y=3.5) assert "Specified size_y 3.5 is not an integer." == str(cm.value) with raises(ValueError) as cm: kernel = kernels.get_kernel_gauss(3, 1, size_y=3.5) assert "Specified size_y 3.5 is not an integer." == str(cm.value) with raises(ValueError) as cm: kernel = kernels.get_kernel_hann(3, size_y=3.5) assert "Specified size_y 3.5 is not an integer." == str(cm.value) with raises(ValueError) as cm: kernel = kernels.get_kernel_hann_rotational(3, size_y=3.5) assert "Specified size_y 3.5 is not an integer." == str(cm.value) with raises(ValueError) as cm: kernel = kernels.get_kernel_hamming(3, size_y=3.5) assert "Specified size_y 3.5 is not an integer." == str(cm.value) with raises(ValueError) as cm: kernel = kernels.get_kernel_kaiser(3, size_y=3.5) assert "Specified size_y 3.5 is not an integer." == str(cm.value) # Test a not implemented kernel name kernels.__availableKernelNames.append("notImplementedKernel") with raises(NotImplementedError) as cm: kernels.get_kernel(name="notImplementedKernel", size=5) assert ("The specified kernel name " "'notImplementedKernel' is not implemented.") == str(cm.value) return
def test_kernel_gauss(): kernel = kernels.get_kernel("gauss", size=15) gauss_kernel = kernels.get_kernel_gauss(size=15) assert np.array_equal(kernel, gauss_kernel) assert gauss_kernel.shape == (15, 15) assert gauss_kernel.dtype == np.float64 assert 1.0 == approx(gauss_kernel.sum()) # check symmetries assert kernels.is_symmetric(gauss_kernel, symmetry=None) kernel = kernels.get_kernel("gauss", size=14, size_y=15, option=3, option_y=4) gauss_kernel = kernels.get_kernel_gauss(size=14, size_y=15, sigma=3, sigma_y=4) assert np.array_equal(kernel, gauss_kernel) assert gauss_kernel.shape == (14, 15) assert gauss_kernel.dtype == np.float64 assert 1.0 == approx(gauss_kernel.sum()) kernel = kernels.get_kernel_gauss(size=14, size_y=14, sigma=3, sigma_y=3) gauss_kernel = kernels.get_kernel_gauss(size=14, sigma=3) assert np.array_equal(kernel, gauss_kernel) assert gauss_kernel.shape == (14, 14) assert gauss_kernel.dtype == np.float64 assert 1.0 == approx(gauss_kernel.sum()) kernel = kernels.get_kernel_gauss(size=14, size_y=14, sigma=14.0 / 5.0, sigma_y=3) gauss_kernel = kernels.get_kernel_gauss(size=14, sigma_y=3) assert np.array_equal(kernel, gauss_kernel) assert gauss_kernel.shape == (14, 14) assert gauss_kernel.dtype == np.float64 assert 1.0 == approx(gauss_kernel.sum()) # Test kernel values kernel = kernels.get_kernel_gauss(1) assert kernel == 1.0 kernel = kernels.get_kernel_gauss(2) assert np.array_equal(kernel, np.asarray([[0.25, 0.25], [0.25, 0.25]])) # test that kernel is of gauss form along main axes for size in [3, 7, 15, 25, 57, 333]: for sigma in [size / 3, size / 5, size / 7]: # Shift gauss reference to array middle mu = (size - 1) / 2.0 sigma_x = sigma sigma_y = sigma # test asymmetric kernel img = kernels.get_kernel_gauss(size, sigma=sigma_x, sigma_y=sigma_y) # Create a gauss reference, normalized to 1 x = np.arange(0, size, 1) gauss_ref = np.exp(-0.5 * (((x - mu) / sigma_x)**2)) # Get slice of 2D gauss kernel and normalize to one gauss_from_kern = img[:, (size - 1) // 2] gauss_from_kern = np.divide(gauss_from_kern, gauss_from_kern.max()) # Check that all values are equivalent up to float16 precision assert np.array_equal(gauss_ref.astype(np.float16), gauss_from_kern.astype(np.float16)) # Get slice along other axis, normalize to 1 # Sigma in y directions is now larger gauss_ref = np.exp(-0.5 * (((x - mu) / sigma_y)**2)) gauss_from_kern = img[(size - 1) // 2, :] gauss_from_kern = np.divide(gauss_from_kern, gauss_from_kern.max()) assert np.array_equal(gauss_ref.astype(np.float16), gauss_from_kern.astype(np.float16)) return
def test_fourier(): # Test image img = np.random.rand(51, 71).astype(np.float32) # Numpy FFT fft_np = np.fft.fft2(img) fft = images.fourier(img, shift=False, implementation='numpy') assert np.array_equal(fft_np, fft) # Scipy FFT fft_sc = sc_fft.fft2(img) fft = images.fourier(img, shift=False, implementation='scipy') assert np.array_equal(fft_sc, fft) # Invalid name with raises(ValueError) as cm: images.fourier(img, shift=False, implementation='nonsense') assert "The implementation 'nonsense' is not one of the supported " \ "implementations, ['numpy', 'scipy', 'fftw']." == str(cm.value) # Check greyscale conversion # Caution: Conversion is weighted using CIE standard img_color = img[..., np.newaxis].repeat(3, axis=-1) assert np.array_equal(img, img_color[..., 0]) assert np.array_equal(img, img_color[..., 1]) assert np.array_equal(img, img_color[..., 2]) fft_from_color = images.fourier(img_color) fft_bw = images.fourier(img) assert np.allclose(fft_bw, fft_from_color, atol=0.01) # Test windowing n, m = img.shape kern = kernels.get_kernel('hann', size=n, size_y=m, normalize='peak').astype(img.dtype) img_windowed = np.multiply(kern, img) fft_windowed = images.fourier(img_windowed, window=None) fft = images.fourier(img, window='hann') assert np.array_equal(fft, fft_windowed) # Test shift img = np.random.rand(41, 51) fft = images.fourier(img, shift=False) fft_shift = images.fourier(img, shift=True) assert np.array_equal([41, 51], fft.shape) assert np.array_equal([41, 51], fft_shift.shape) img = np.random.rand(43, 51) fft = images.fourier(img, shift=False) fft_shift = images.fourier(img, shift=True) assert np.array_equal([43, 51], fft.shape) assert np.array_equal([43, 51], fft_shift.shape) img = np.random.rand(51, 43) fft = images.fourier(img, shift=False) fft_shift = images.fourier(img, shift=True) assert np.array_equal([51, 43], fft.shape) assert np.array_equal([51, 43], fft_shift.shape) # Test padding # First, symmetric input img = np.random.rand(50, 50) fft = images.fourier(img, shift=False, pad=25) assert np.array_equal([101, 101], fft.shape) # List input fft = images.fourier(img, shift=False, pad=[10, 15]) assert np.array_equal([71, 81], fft.shape) # Nonsense input with raises(ValueError) as cm: fft = images.fourier(img, shift=False, pad=[10, 15, 9]) assert "Option 'pad' must be int or list/ndarray of length 2" == str(cm.value)