#!/usr/bin/env python3
"""Define unittest test case for the PiecewiseBicubicPSF class."""
import unittest
import numpy
from astrowisp.tests.utilities import FloatTestCase
from astrowisp.fake_image.piecewise_bicubic_psf import PiecewiseBicubicPSF
[docs]
def point_in_grid(point, grid):
"""Return True iff the point is within grid."""
return (
point['x'] >= grid['x'][0]
and
point['x'] <= grid['x'][-1]
and
point['y'] >= grid['y'][0]
and
point['y'] <= grid['y'][-1]
)
[docs]
class TestPiecewiseBicubicPSF(FloatTestCase):
"""Make sure the PiecewiseBicubicPSF class functions as expected."""
[docs]
def setUp(self):
"""Define a set of picewise PSF grids to use during tests."""
self._grids = [
{'x': numpy.array([0.0, 1.0]),
'y': numpy.array([0.0, 1.0])},
{'x': numpy.array([0.0, 0.5]),
'y': numpy.array([0.0, numpy.pi / 3.0])},
{'x': numpy.array([0.0, 0.5, 1.0]),
'y': numpy.array([0.0, 1.0])},
]
self._test_points = [
[
{'x': x, 'y': y}
for x in numpy.linspace(-numpy.pi / 2, numpy.pi, 10)
for y in numpy.linspace(-numpy.pi / 2,
numpy.pi, 10)
],
[
{'x': x, 'y': y}
for x in numpy.linspace(-numpy.pi / 2, numpy.pi, 10)
for y in numpy.linspace(-numpy.pi / 2, numpy.pi, 10)
],
[
{'x': x, 'y': y}
for x in numpy.linspace(-numpy.pi / 2, numpy.pi, 10)
for y in numpy.linspace(-numpy.pi / 2, numpy.pi, 10)
]
]
self.set_tolerance(10.0)
[docs]
def test_zero(self):
"""Make sure that all-zero input produces an identically zero PSF."""
for grid, test_points in zip(self._grids, self._test_points):
shape = (grid['y'].size, grid['x'].size)
psf = PiecewiseBicubicPSF(
psf_parameters={'values': numpy.zeros(shape),
'd_dx': numpy.zeros(shape),
'd_dy': numpy.zeros(shape),
'd2_dxdy': numpy.zeros(shape)},
boundaries=grid
)
for point in test_points:
self.assertEqual(psf(**point), 0.0)
for point1 in test_points:
for point2 in test_points:
self.assertEqual(
psf.integrate(left=point1['x'],
bottom=point1['y'],
width=point2['x'] - point1['x'],
height=point2['y'] - point1['y']),
0.0
)
[docs]
def test_one(self):
"""Make sure that a PSF = 1 everywhere within the grid works."""
for grid, test_points in zip(self._grids, self._test_points):
shape = (grid['y'].size, grid['x'].size)
psf = PiecewiseBicubicPSF(
psf_parameters={'values': numpy.ones(shape),
'd_dx': numpy.zeros(shape),
'd_dy': numpy.zeros(shape),
'd2_dxdy': numpy.zeros(shape)},
boundaries=grid
)
for point in test_points:
self.assertEqual(psf(**point),
1.0 if point_in_grid(point, grid) else 0.0,
f'1.0({point["x"]:f}, {point["y"]:f})')
for point1 in test_points:
for point2 in test_points:
width = (
max(grid['x'][0],
min(grid['x'][-1], point2['x']))
-
max(grid['x'][0],
min(grid['x'][-1], point1['x']))
)
height = (
max(grid['y'][0],
min(grid['y'][-1], point2['y']))
-
max(grid['y'][0],
min(grid['y'][-1], point1['y']))
)
self.assertEqual(
psf.integrate(left=point1['x'],
bottom=point1['y'],
width=point2['x'] - point1['x'],
height=point2['y'] - point1['y']),
width * height,
f"Int(1, {point1['x']:f} < x < {point2['x']:f}, "
f"{point1['y']:f} < y < {point2['y']:f})"
)
[docs]
def test_pi(self):
"""Make sure that a PSF = 1 everywhere within the grid works."""
for grid, test_points in zip(self._grids, self._test_points):
shape = (grid['y'].size, grid['x'].size)
psf = PiecewiseBicubicPSF(
psf_parameters={'values': numpy.full(shape, numpy.pi),
'd_dx': numpy.zeros(shape),
'd_dy': numpy.zeros(shape),
'd2_dxdy': numpy.zeros(shape)},
boundaries=grid
)
for point in test_points:
self.assertEqual(
psf(**point),
numpy.pi if point_in_grid(point, grid) else 0.0,
f"pi({point['x']:f}, {point['y']:f})"
)
for point1 in test_points:
for point2 in test_points:
width = (
max(grid['x'][0],
min(grid['x'][-1], point2['x']))
-
max(grid['x'][0],
min(grid['x'][-1], point1['x']))
)
height = (
max(grid['y'][0],
min(grid['y'][-1], point2['y']))
-
max(grid['y'][0],
min(grid['y'][-1], point1['y']))
)
self.assertApprox(
psf.integrate(left=point1['x'],
bottom=point1['y'],
width=point2['x'] - point1['x'],
height=point2['y'] - point1['y']),
width * height * numpy.pi,
f"Int(pi, {point1['x']:f} < x < {point2['x']:f}, "
f"{point1['y']:f} < y < {point2['y']:f}"
)
[docs]
def test_linear(self):
"""Test PSFs that is are linear functions of x and/or y work."""
def test_evaluation(psf, slope, grid, test_points):
"""
Test PSF evalutaion.
Args:
psf: The PSF to test.
slope: Dictionary giving the expected coefficient of `x` or
`y` of the PSF (keys - `x` and `y` respectively).
grid: The grid on which the PSF was constructed (see
boundaries argument of PicewiseBicubicPSF.__init__
test_points: A collection of points at which to try to
evaluate the PSF. It is perfectly valid for the points to be
outside the range of the PSF.
Returns:
None
"""
for point in test_points:
if(
point['x'] < grid['x'][0]
or
point['y'] < grid['y'][0]
or
point['x'] > grid['x'][-1]
or
point['y'] > grid['y'][-1]
):
answer = 0.0
else:
answer = slope['x'] * point['x'] + slope['y'] * point['y']
self.assertEqual(psf(**point),
answer,
f"{slope['x']:f} * {point['x']:f} + "
f"{slope['y']:f} * {point['y']:f}")
def test_integration(psf, slope, grid, test_points):
"""
Test that integrating the PSF produces the expected results.
Args:
See test_evaluation.
Returns:
None
"""
for point1 in test_points:
for point2 in test_points:
x_1 = max(
grid['x'][0],
min(grid['x'][-1], point1['x'])
)
x_2 = max(
grid['x'][0],
min(grid['x'][-1], point2['x'])
)
y_1 = max(
grid['y'][0],
min(grid['y'][-1], point1['y'])
)
y_2 = max(
grid['y'][0],
min(grid['y'][-1], point2['y'])
)
width = point2['x'] - point1['x']
height = point2['y'] - point1['y']
answer = (
(x_2**2 - x_1**2) * slope['x'] * (y_2 - y_1)
+
(y_2**2 - y_1**2) * slope['y'] * (x_2 - x_1)
) / 2.0
self.assertApprox(
psf.integrate(left=point1['x'],
bottom=point1['y'],
width=width,
height=height),
answer,
f"int(psf = {slope['x']:f} * x + {slope['y']:f} * y, "
f"{point1['x']:f} < x < {point2['x']:f}, "
f"{point1['y']:f} < y < {point2['y']:f}"
)
slopes = [0.0, 1.0, numpy.pi]
for grid, test_points in zip(self._grids, self._test_points):
shape = (grid['y'].size,
grid['x'].size)
for x_slope in slopes:
for y_slope in slopes:
grid_x, grid_y = numpy.meshgrid(grid['x'],
grid['y'])
psf = PiecewiseBicubicPSF(
psf_parameters={
'values': grid_x * x_slope + grid_y * y_slope,
'd_dx': numpy.full(shape, x_slope),
'd_dy': numpy.full(shape, y_slope),
'd2_dxdy': numpy.zeros(shape)
},
boundaries=grid
)
psf_slope = {'x': x_slope, 'y': y_slope}
test_evaluation(psf, psf_slope, grid, test_points)
test_integration(psf, psf_slope, grid, test_points)
#Broke this up into as many pieces as was reasonable.
#pylint: disable=too-many-locals
[docs]
def test_random_single_patch(self):
"""Test PSFs equal to random bi-cubic polynomials."""
def test_evaluation(psf, coef, grid, test_points):
"""
Test PSF evalutaion.
Args:
psf: The PSF to test.
coef: The coefficients of the PSF polynomial.
grid: The grid on which the PSF was constructed (see
boundaries argument of PicewiseBicubicPSF.__init__
test_points: A collection of points at which to try to
evaluate the PSF. It is perfectly valid for the points to be
outside the range of the PSF.
Returns:
None
"""
for point in test_points:
expected = 0.0
if point_in_grid(point, grid):
y_term = 1.0
for y_pow in range(4):
x_term = 1.0
for x_pow in range(4):
expected += coef[y_pow, x_pow] * x_term * y_term
x_term *= point['x']
y_term *= point['y']
got = psf(**point)
self.assertApprox(
got,
expected,
f'PSF(random coef)({point["x"]:f}, {point["y"]:f})'
)
def test_integration(psf, coef, grid, test_points):
"""
Test the PSF integrals over rectangular areas.
Args:
See test_evaluation.
Returns:
None
"""
for point1 in test_points:
for point2 in test_points:
expected = 0.0
x_1 = max(
grid['x'][0],
min(grid['x'][-1], point1['x'])
)
x_2 = max(
grid['x'][0],
min(grid['x'][-1], point2['x'])
)
y_1 = max(
grid['y'][0],
min(grid['y'][-1], point1['y'])
)
y_2 = max(
grid['y'][0],
min(grid['y'][-1], point2['y'])
)
y1_term = y_1
y2_term = y_2
for y_pow in range(1, 5):
x1_term = x_1
x2_term = x_2
for x_pow in range(1, 5):
expected += (coef[y_pow - 1, x_pow - 1]
*
(x2_term - x1_term) / x_pow
*
(y2_term - y1_term) / y_pow)
x1_term *= x_1
x2_term *= x_2
y1_term *= y_1
y2_term *= y_2
got = psf.integrate(left=point1['x'],
bottom=point1['y'],
width=point2['x'] - point1['x'],
height=point2['y'] - point1['y'])
self.assertApprox(
got,
expected,
"int(PSF(random coef), "
f"{point1['x']:f} < x < {point2['x']:f}, "
f"{point1['y']:f} < y < {point2['y']:f})"
)
coef = numpy.random.rand(4, 4) * numpy.pi
for grid, test_points in zip(self._grids, self._test_points):
print('Testing grid: ' + repr(grid))
shape = (grid['y'].size,
grid['x'].size)
grid_x, grid_y = numpy.meshgrid(grid['x'], grid['y'])
psf_parameters = {'values': numpy.zeros(shape),
'd_dx': numpy.zeros(shape),
'd_dy': numpy.zeros(shape),
'd2_dxdy': numpy.zeros(shape)}
y_term = numpy.ones(shape)
dy_term = numpy.ones(shape)
for y_pow in range(4):
x_term = numpy.ones(shape)
dx_term = numpy.ones(shape)
for x_pow in range(4):
psf_parameters['values'] += (
coef[y_pow, x_pow] * x_term * y_term
)
psf_parameters['d_dx'] += (
x_pow * coef[y_pow, x_pow] * dx_term * y_term
)
psf_parameters['d_dy'] += (
y_pow * coef[y_pow, x_pow] * x_term * dy_term
)
psf_parameters['d2_dxdy'] += (
x_pow * y_pow * coef[y_pow, x_pow] * dx_term * dy_term
)
x_term *= grid_x
if x_pow != 0:
dx_term *= grid_x
y_term *= grid_y
if y_pow != 0:
dy_term *= grid_y
psf = PiecewiseBicubicPSF(
psf_parameters=psf_parameters,
boundaries=grid
)
test_evaluation(psf, coef, grid, test_points)
test_integration(psf, coef, grid, test_points)
#pylint: enable=too-many-locals
if __name__ == '__main__':
unittest.main(failfast=True)