# Licensed under a 3-clause BSD style license - see LICENSE.rst import pytest import numpy as np import numpy.ma as ma from contextlib import nullcontext from astropy.convolution.convolve import convolve, convolve_fft from astropy.convolution.kernels import Gaussian2DKernel from astropy.utils.exceptions import AstropyUserWarning from astropy import units as u from astropy.utils.compat.optional_deps import HAS_SCIPY, HAS_PANDAS # noqa from numpy.testing import (assert_array_almost_equal_nulp, assert_array_almost_equal, assert_allclose) import itertools VALID_DTYPES = ('>f4', 'f8', 'f8'), [3, 3, 3]), 10) elif boundary == 'extend': assert_array_almost_equal_nulp(z, np.array([[[62., 51., 40.], [72., 63., 54.], [82., 75., 68.]], [[93., 68., 43.], [105., 78., 51.], [117., 88., 59.]], [[124., 85., 46.], [138., 93., 48.], [152., 101., 50.]]], dtype='>f8')/kernsum, 10) else: raise ValueError("Invalid Boundary Option") @pytest.mark.parametrize(('boundary'), BOUNDARY_OPTIONS) def test_asymmetric_kernel(boundary): ''' Regression test for #6264: make sure that asymmetric convolution functions go the right direction ''' x = np.array([3., 0., 1.], dtype='>f8') y = np.array([1, 2, 3], dtype='>f8') z = convolve(x, y, boundary=boundary, normalize_kernel=False) if boundary == 'fill': assert_array_almost_equal_nulp(z, np.array([6., 10., 2.], dtype='float'), 10) elif boundary is None: assert_array_almost_equal_nulp(z, np.array([0., 10., 0.], dtype='float'), 10) elif boundary == 'extend': assert_array_almost_equal_nulp(z, np.array([15., 10., 3.], dtype='float'), 10) elif boundary == 'wrap': assert_array_almost_equal_nulp(z, np.array([9., 10., 5.], dtype='float'), 10) @pytest.mark.parametrize('ndims', (1, 2, 3)) def test_convolution_consistency(ndims): np.random.seed(0) array = np.random.randn(*([3]*ndims)) np.random.seed(0) kernel = np.random.rand(*([3]*ndims)) conv_f = convolve_fft(array, kernel, boundary='fill') conv_d = convolve(array, kernel, boundary='fill') assert_array_almost_equal_nulp(conv_f, conv_d, 30) def test_astropy_convolution_against_numpy(): x = np.array([1, 2, 3]) y = np.array([5, 4, 3, 2, 1]) assert_array_almost_equal(np.convolve(y, x, 'same'), convolve(y, x, normalize_kernel=False)) assert_array_almost_equal(np.convolve(y, x, 'same'), convolve_fft(y, x, normalize_kernel=False)) @pytest.mark.skipif('not HAS_SCIPY') def test_astropy_convolution_against_scipy(): from scipy.signal import fftconvolve x = np.array([1, 2, 3]) y = np.array([5, 4, 3, 2, 1]) assert_array_almost_equal(fftconvolve(y, x, 'same'), convolve(y, x, normalize_kernel=False)) assert_array_almost_equal(fftconvolve(y, x, 'same'), convolve_fft(y, x, normalize_kernel=False)) @pytest.mark.skipif('not HAS_PANDAS') def test_regression_6099(): import pandas wave = np.array(np.linspace(5000, 5100, 10)) boxcar = 3 nonseries_result = convolve(wave, np.ones((boxcar,))/boxcar) wave_series = pandas.Series(wave) series_result = convolve(wave_series, np.ones((boxcar,))/boxcar) assert_array_almost_equal(nonseries_result, series_result) def test_invalid_array_convolve(): kernel = np.ones(3)/3. with pytest.raises(TypeError): convolve('glork', kernel) @pytest.mark.parametrize(('boundary'), BOUNDARY_OPTIONS) def test_non_square_kernel_asymmetric(boundary): # Regression test for a bug that occurred when using non-square kernels in # 2D when using boundary=None kernel = np.array([[1, 2, 3, 2, 1], [0, 1, 2, 1, 0], [0, 0, 0, 0, 0]]) image = np.zeros((13, 13)) image[6, 6] = 1 result = convolve(image, kernel, normalize_kernel=False, boundary=boundary) assert_allclose(result[5:8, 4:9], kernel) @pytest.mark.parametrize(('boundary', 'normalize_kernel'), itertools.product(BOUNDARY_OPTIONS, NORMALIZE_OPTIONS)) def test_uninterpolated_nan_regions(boundary, normalize_kernel): #8086 # Test NaN interpolation of contiguous NaN regions with kernels of size # identical and greater than that of the region of NaN values. # Test case: kernel.shape == NaN_region.shape kernel = Gaussian2DKernel(1, 5, 5) nan_centroid = np.full(kernel.shape, np.nan) image = np.pad(nan_centroid, pad_width=kernel.shape[0]*2, mode='constant', constant_values=1) with pytest.warns(AstropyUserWarning, match=r"nan_treatment='interpolate', however, NaN values detected " r"post convolution. A contiguous region of NaN values, larger " r"than the kernel size, are present in the input array. " r"Increase the kernel size to avoid this."): result = convolve(image, kernel, boundary=boundary, nan_treatment='interpolate', normalize_kernel=normalize_kernel) assert(np.any(np.isnan(result))) # Test case: kernel.shape > NaN_region.shape nan_centroid = np.full((kernel.shape[0]-1, kernel.shape[1]-1), np.nan) # 1 smaller than kerenel image = np.pad(nan_centroid, pad_width=kernel.shape[0]*2, mode='constant', constant_values=1) result = convolve(image, kernel, boundary=boundary, nan_treatment='interpolate', normalize_kernel=normalize_kernel) assert(~np.any(np.isnan(result))) # Note: negation def test_regressiontest_issue9168(): """ Issue #9168 pointed out that kernels can be (unitless) quantities, which leads to crashes when inplace modifications are made to arrays in convolve/convolve_fft, so we now strip the quantity aspects off of kernels. """ x = np.array([[1., 2., 3.], [4., 5., 6.], [7., 8., 9.]],) kernel_fwhm = 1*u.arcsec pixel_size = 1*u.arcsec kernel = Gaussian2DKernel(x_stddev=kernel_fwhm/pixel_size) result = convolve_fft(x, kernel, boundary='fill', fill_value=np.nan, preserve_nan=True) result = convolve(x, kernel, boundary='fill', fill_value=np.nan, preserve_nan=True) def test_convolve_nan_zero_sum_kernel(): with pytest.raises(ValueError, match="Setting nan_treatment='interpolate' " "requires the kernel to be normalized, but the " "input kernel has a sum close to zero. For a " "zero-sum kernel and data with NaNs, set " "nan_treatment='fill'."): convolve([1, np.nan, 3], [-1, 2, -1], normalize_kernel=False)