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
Beispiel #3
0
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)