Coverage for tests / test_chebyshevBoundedField.py: 12%
314 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-26 02:01 -0700
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-26 02:01 -0700
1# This file is part of afw.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (https://www.lsst.org).
6# See the COPYRIGHT file at the top-level directory of this distribution
7# for details of code ownership.
8#
9# This program is free software: you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation, either version 3 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program. If not, see <https://www.gnu.org/licenses/>.
22"""
23Tests for math.ChebyshevBoundedField
25Run with:
26 python test_chebyshevBoundedField.py
27or
28 pytest test_chebyshevBoundedField.py
29"""
31import os
32import unittest
34import numpy as np
36import lsst.utils.tests
37import lsst.pex.exceptions
38import lsst.geom
39import lsst.afw.image
40import lsst.afw.math
41import lsst.afw.geom
43testPath = os.path.abspath(os.path.dirname(__file__))
45CHEBYSHEV_T = [
46 lambda x: x**0,
47 lambda x: x,
48 lambda x: 2*x**2 - 1,
49 lambda x: (4*x**2 - 3)*x,
50 lambda x: (8*x**2 - 8)*x**2 + 1,
51 lambda x: ((16*x**2 - 20)*x**2 + 5)*x,
52]
55def multiply(image, field):
56 """Return the product of image and field() at each point in image.
57 """
58 box = image.getBBox()
59 outImage = lsst.afw.image.ImageF(box)
60 for i in range(box.getMinX(), box.getMaxX() + 1):
61 for j in range(box.getMinY(), box.getMaxY() + 1):
62 outImage[i, j] = image[i, j]*field.evaluate(i, j)
63 return outImage
66def divide(image, field):
67 """Return the quotient of image and field() at each point in image.
68 """
69 box = image.getBBox()
70 outImage = lsst.afw.image.ImageF(box)
71 for i in range(box.getMinX(), box.getMaxX() + 1):
72 for j in range(box.getMinY(), box.getMaxY() + 1):
73 outImage[i, j] = image[i, j]/field.evaluate(i, j)
74 return outImage
77class ChebyshevBoundedFieldTestCase(lsst.utils.tests.TestCase):
79 def setUp(self):
80 self.longMessage = True
81 np.random.seed(5)
82 self.bbox = lsst.geom.Box2I(
83 lsst.geom.Point2I(-5, -5), lsst.geom.Point2I(5, 5))
84 self.x1d = np.linspace(self.bbox.getBeginX(), self.bbox.getEndX())
85 self.y1d = np.linspace(self.bbox.getBeginY(), self.bbox.getEndY())
86 self.x2d, self.y2d = np.meshgrid(self.x1d, self.y1d)
87 self.xFlat = np.ravel(self.x2d)
88 self.yFlat = np.ravel(self.y2d)
89 self.cases = []
90 for orderX in range(0, 5):
91 for orderY in range(0, 5):
92 indexX, indexY = np.meshgrid(np.arange(orderX + 1, dtype=int),
93 np.arange(orderY + 1, dtype=int))
94 for triangular in (True, False):
95 ctrl = lsst.afw.math.ChebyshevBoundedFieldControl()
96 ctrl.orderX = orderX
97 ctrl.orderY = orderY
98 ctrl.triangular = triangular
99 coefficients = np.random.randn(orderY + 1, orderX + 1)
100 if triangular:
101 coefficients[indexX + indexY > max(orderX, orderY)] = 0.0
102 self.cases.append((ctrl, coefficients))
104 array = np.arange(self.bbox.getArea(), dtype=np.float32).reshape(self.bbox.getDimensions())
105 self.image = lsst.afw.image.ImageF(array)
106 self.fields = [lsst.afw.math.ChebyshevBoundedField(self.bbox, coeffs) for _, coeffs in self.cases]
107 self.product = lsst.afw.math.ProductBoundedField(self.fields)
109 def tearDown(self):
110 del self.bbox
112 def testFillImageInterpolation(self):
113 ctrl, coefficients = self.cases[-2]
114 bbox = lsst.geom.Box2I(lsst.geom.Point2I(10, 15),
115 lsst.geom.Extent2I(360, 350))
116 field = lsst.afw.math.ChebyshevBoundedField(bbox, coefficients)
117 image1 = lsst.afw.image.ImageF(bbox)
118 image2 = lsst.afw.image.ImageF(bbox)
119 image3 = lsst.afw.image.ImageF(bbox)
120 image4 = lsst.afw.image.ImageF(bbox)
121 field.fillImage(image1)
122 field.fillImage(image2, xStep=3)
123 field.fillImage(image3, yStep=4)
124 field.fillImage(image4, xStep=3, yStep=4)
125 self.assertFloatsAlmostEqual(image1.array, image2.array, rtol=1E-2, atol=1E-2)
126 self.assertFloatsAlmostEqual(image1.array, image3.array, rtol=1.5E-2, atol=1.5E-2)
127 self.assertFloatsAlmostEqual(image1.array, image4.array, rtol=2E-2, atol=2E-2)
129 def testEvaluate(self):
130 """Test the single-point evaluate method against explicitly-defined 1-d Chebyshevs
131 (at the top of this file).
132 """
133 factor = 12.345
134 boxD = lsst.geom.Box2D(self.bbox)
135 # sx, sy: transform from self.bbox range to [-1, -1]
136 sx = 2.0/boxD.getWidth()
137 sy = 2.0/boxD.getHeight()
138 nPoints = 50
139 for ctrl, coefficients in self.cases:
140 field = lsst.afw.math.ChebyshevBoundedField(
141 self.bbox, coefficients)
142 x = np.random.rand(nPoints)*boxD.getWidth() + boxD.getMinX()
143 y = np.random.rand(nPoints)*boxD.getHeight() + boxD.getMinY()
144 z1 = field.evaluate(x, y)
145 tx = np.array([CHEBYSHEV_T[i](sx*x)
146 for i in range(coefficients.shape[1])])
147 ty = np.array([CHEBYSHEV_T[i](sy*y)
148 for i in range(coefficients.shape[0])])
149 self.assertEqual(tx.shape, (coefficients.shape[1], x.size))
150 self.assertEqual(ty.shape, (coefficients.shape[0], y.size))
151 z2 = np.array([np.dot(ty[:, i], np.dot(coefficients, tx[:, i]))
152 for i in range(nPoints)])
153 self.assertFloatsAlmostEqual(z1, z2, rtol=1E-12)
155 scaled = field*factor
156 self.assertFloatsAlmostEqual(scaled.evaluate(x, y),
157 factor*z2,
158 rtol=factor*1E-13)
159 self.assertFloatsEqual(
160 scaled.getCoefficients(), factor*field.getCoefficients())
162 def testProductEvaluate(self):
163 """Test that ProductBoundedField.evaluate is equivalent to multiplying
164 its nested BoundedFields.
165 """
166 zFlat1 = self.product.evaluate(self.xFlat, self.yFlat)
167 zFlat2 = np.array([self.product.evaluate(x, y) for x, y in zip(self.xFlat, self.yFlat)])
168 self.assertFloatsAlmostEqual(zFlat1, zFlat2)
169 zFlat3 = np.ones(zFlat1.shape, dtype=float)
170 for field in self.fields:
171 zFlat3 *= field.evaluate(self.xFlat, self.yFlat)
172 self.assertFloatsAlmostEqual(zFlat1, zFlat3)
174 def testMultiplyImage(self):
175 """Test multiplying an image in place.
176 """
177 _, coefficients = self.cases[-2]
178 field = lsst.afw.math.ChebyshevBoundedField(self.image.getBBox(), coefficients)
179 # multiplyImage() is in-place, so we have to make the expected result first.
180 expect = multiply(self.image, field)
181 field.multiplyImage(self.image)
182 self.assertImagesAlmostEqual(self.image, expect)
184 def testMultiplyMaskedImage(self):
185 """Test multiplying a masked image in place.
186 """
187 _, coefficients = self.cases[-2]
188 field = lsst.afw.math.ChebyshevBoundedField(self.image.getBBox(), coefficients)
189 # multiplyImage() is in-place, so we have to make the expected result first.
190 ones = lsst.afw.image.ImageF(self.image.getBBox())
191 ones.array[:, :] = 1.0
192 expect_masked_image = lsst.afw.image.MaskedImageF(
193 multiply(self.image, field),
194 None,
195 multiply(multiply(ones, field), field)
196 )
197 masked_image = lsst.afw.image.MaskedImageF(self.image)
198 masked_image.variance.array[:, :] = 1.0
199 field.multiplyImage(masked_image)
200 self.assertImagesAlmostEqual(masked_image.image, expect_masked_image.image)
201 self.assertMaskedImagesAlmostEqual(masked_image, expect_masked_image)
203 def testDivideImage(self):
204 """Test dividing an image in place.
205 """
206 _, coefficients = self.cases[-2]
207 field = lsst.afw.math.ChebyshevBoundedField(self.image.getBBox(), coefficients)
208 # divideImage() is in-place, so we have to make the expected result first.
209 expect = divide(self.image, field)
210 field.divideImage(self.image)
211 self.assertImagesAlmostEqual(self.image, expect)
213 def testDivideMaskedImage(self):
214 """Test dividing a masked image in place.
215 """
216 _, coefficients = self.cases[-2]
217 field = lsst.afw.math.ChebyshevBoundedField(self.image.getBBox(), coefficients)
218 # divideImage() is in-place, so we have to make the expected result first.
219 ones = lsst.afw.image.ImageF(self.image.getBBox())
220 ones.array[:, :] = 1.0
221 expect_masked_image = lsst.afw.image.MaskedImageF(
222 divide(self.image, field),
223 None,
224 divide(divide(ones, field), field)
225 )
226 masked_image = lsst.afw.image.MaskedImageF(self.image)
227 masked_image.variance.array[:, :] = 1.0
228 field.divideImage(masked_image)
229 self.assertImagesAlmostEqual(masked_image.image, expect_masked_image.image)
230 self.assertMaskedImagesAlmostEqual(masked_image, expect_masked_image)
232 def testMultiplyImageRaisesUnequalBBox(self):
233 """Multiplying an image with a different bbox should raise.
234 """
235 _, coefficients = self.cases[-2]
236 field = lsst.afw.math.ChebyshevBoundedField(self.image.getBBox(), coefficients)
237 subBox = lsst.geom.Box2I(lsst.geom.Point2I(0, 3), lsst.geom.Point2I(3, 4))
238 subImage = self.image.subset(subBox)
239 with self.assertRaises(RuntimeError):
240 field.multiplyImage(subImage)
242 def testMultiplyImageOverlapSubImage(self):
243 """Multiplying a subimage with overlapOnly=true should only modify
244 the subimage, when a subimage is passed in.
245 """
246 _, coefficients = self.cases[-2]
247 field = lsst.afw.math.ChebyshevBoundedField(self.image.getBBox(), coefficients)
248 subBox = lsst.geom.Box2I(lsst.geom.Point2I(0, 3), lsst.geom.Point2I(3, 4))
249 subImage = self.image.subset(subBox)
250 expect = self.image.clone()
251 expect[subBox] = multiply(subImage, field)
252 field.multiplyImage(subImage, overlapOnly=True)
253 self.assertImagesAlmostEqual(self.image, expect)
255 def testMultiplyImageOverlapSmallerBoundedField(self):
256 """Multiplying a subimage with overlapOnly=true should only modify
257 the subimage if the boundedField bbox is smaller than the image.
259 This is checking for a bug where the bounded field was writing outside
260 the overlap bbox.
261 """
262 _, coefficients = self.cases[-2]
263 subBox = lsst.geom.Box2I(lsst.geom.Point2I(0, 3), lsst.geom.Point2I(3, 4))
264 # The BF is only defined on the subBox, not the whole image bbox.
265 field = lsst.afw.math.ChebyshevBoundedField(subBox, coefficients)
266 subImage = self.image.subset(subBox)
267 expect = self.image.clone()
268 expect[subBox] = multiply(subImage, field)
269 field.multiplyImage(self.image, overlapOnly=True)
270 self.assertImagesAlmostEqual(self.image, expect)
272 def _testIntegrateBox(self, bbox, coeffs, expect):
273 field = lsst.afw.math.ChebyshevBoundedField(bbox, coeffs)
274 self.assertFloatsAlmostEqual(field.integrate(), expect, rtol=1E-14)
276 def testIntegrateTrivialBox(self):
277 """Test integrating over a "trivial" [-1,1] box.
279 NOTE: a "trivial" BBox can't be constructed exactly, given that Box2I
280 is inclusive, but the [0,1] box has the same area (because it is
281 actually (-0.5, 1.5) when converted to a Box2D), and the translation
282 doesn't affect the integral.
283 """
284 bbox = lsst.geom.Box2I(lsst.geom.Point2I(0, 0),
285 lsst.geom.Point2I(1, 1))
287 # 0th order polynomial
288 coeffs = np.array([[5.0]])
289 self._testIntegrateBox(bbox, coeffs, 4.0*coeffs[0, 0])
291 # 1st order polynomial: odd orders drop out of integral
292 coeffs = np.array([[5.0, 2.0], [3.0, 4.0]])
293 self._testIntegrateBox(bbox, coeffs, 4.0*coeffs[0, 0])
295 # 2nd order polynomial in x, 0th in y
296 coeffs = np.array([[5.0, 0.0, 7.0]])
297 self._testIntegrateBox(
298 bbox, coeffs, 4.0*coeffs[0, 0] - (4.0/3.0)*coeffs[0, 2])
300 # 2nd order polynomial in y, 0th in x
301 coeffs = np.zeros((3, 3))
302 coeffs[0, 0] = 5.0
303 coeffs[2, 0] = 7.0
304 self._testIntegrateBox(
305 bbox, coeffs, 4.0*coeffs[0, 0] - (4.0/3.0)*coeffs[2, 0])
307 # 2nd order polynomial in x and y, no cross-term
308 coeffs = np.zeros((3, 3))
309 coeffs[0, 0] = 5.0
310 coeffs[1, 0] = 7.0
311 coeffs[0, 2] = 3.0
312 self._testIntegrateBox(bbox, coeffs,
313 4.0*coeffs[0, 0] - (4.0/3.0)*coeffs[2, 0] - (4.0/3.0)*coeffs[0, 2])
315 def testIntegrateBox(self):
316 r"""Test integrating over an "interesting" box.
318 The values of these integrals were checked in Mathematica. The code
319 block below can be pasted into Mathematica to re-do those calculations.
321 ::
323 f[x_, y_, n_, m_] := \!\(
324 \*UnderoverscriptBox[\(\[Sum]\), \(i = 0\), \(n\)]\(
325 \*UnderoverscriptBox[\(\[Sum]\), \(j = 0\), \(m\)]
326 \*SubscriptBox[\(a\), \(i, j\)]*ChebyshevT[i, x]*ChebyshevT[j, y]\)\)
327 integrate2dBox[n_, m_, x0_, x1_, y0_, y1_] := \!\(
328 \*SubsuperscriptBox[\(\[Integral]\), \(y0\), \(y1\)]\(
329 \*SubsuperscriptBox[\(\[Integral]\), \(x0\), \(x1\)]f[
330 \*FractionBox[\(2 x - x0 - x1\), \(x1 - x0\)],
331 \*FractionBox[\(2 y - y0 - y1\), \(y1 - y0\)], n,
332 m] \[DifferentialD]x \[DifferentialD]y\)\)
333 integrate2dBox[0, 0, -2.5, 5.5, -3.5, 7.5]
334 integrate2dBox[1, 0, -2.5, 5.5, -3.5, 7.5]
335 integrate2dBox[0, 1, -2.5, 5.5, -3.5, 7.5]
336 integrate2dBox[1, 1, -2.5, 5.5, -3.5, 7.5]
337 integrate2dBox[1, 2, -2.5, 5.5, -3.5, 7.5]
338 integrate2dBox[2, 2, -2.5, 5.5, -3.5, 7.5]
339 """
340 bbox = lsst.geom.Box2I(
341 lsst.geom.Point2I(-2, -3), lsst.geom.Point2I(5, 7))
343 # 0th order polynomial
344 coeffs = np.array([[5.0]])
345 self._testIntegrateBox(bbox, coeffs, 88.0*coeffs[0, 0])
347 # 1st order polynomial: odd orders drop out of integral
348 coeffs = np.array([[5.0, 2.0], [3.0, 4.0]])
349 self._testIntegrateBox(bbox, coeffs, 88.0*coeffs[0, 0])
351 # 2nd order polynomial in x, 0th in y
352 coeffs = np.array([[5.0, 0.0, 7.0]])
353 self._testIntegrateBox(
354 bbox, coeffs, 88.0*coeffs[0, 0] - (88.0/3.0)*coeffs[0, 2])
356 # 2nd order polynomial in y, 0th in x
357 coeffs = np.zeros((3, 3))
358 coeffs[0, 0] = 5.0
359 coeffs[2, 0] = 7.0
360 self._testIntegrateBox(
361 bbox, coeffs, 88.0*coeffs[0, 0] - (88.0/3.0)*coeffs[2, 0])
363 # 2nd order polynomial in x,y
364 coeffs = np.zeros((3, 3))
365 coeffs[2, 2] = 11.0
366 self._testIntegrateBox(bbox, coeffs, (88.0/9.0)*coeffs[2, 2])
368 def testMean(self):
369 """The mean of the nth 1d Chebyshev (a_n*T_n(x)) on [-1,1] is
370 0 for odd n
371 a_n / (1-n^2) for even n
373 Similarly, the mean of the (n,m)th 2d Chebyshev is the appropriate
374 product of the above.
375 """
376 bbox = lsst.geom.Box2I(
377 lsst.geom.Point2I(-2, -3), lsst.geom.Point2I(5, 7))
379 coeffs = np.array([[5.0]])
380 field = lsst.afw.math.ChebyshevBoundedField(bbox, coeffs)
381 self.assertEqual(field.mean(), coeffs[0, 0])
383 coeffs = np.array([[5.0, 0.0, 3.0]])
384 field = lsst.afw.math.ChebyshevBoundedField(bbox, coeffs)
385 self.assertEqual(field.mean(), coeffs[0, 0] - coeffs[0, 2]/3.0)
387 # 2nd order polynomial in x,y
388 coeffs = np.zeros((3, 3))
389 coeffs[0, 0] = 7.0
390 coeffs[1, 0] = 31.0
391 coeffs[0, 2] = 13.0
392 coeffs[2, 2] = 11.0
393 field = lsst.afw.math.ChebyshevBoundedField(bbox, coeffs)
394 self.assertFloatsAlmostEqual(
395 field.mean(), coeffs[0, 0] - coeffs[0, 2]/3.0 + coeffs[2, 2]/9.0)
397 def testImageFit(self):
398 """Test that we can fit an image produced by a ChebyshevBoundedField and
399 get the same coefficients back.
400 """
401 for ctrl, coefficients in self.cases:
402 inField = lsst.afw.math.ChebyshevBoundedField(
403 self.bbox, coefficients)
404 for Image in (lsst.afw.image.ImageF, lsst.afw.image.ImageD):
405 image = Image(self.bbox)
406 inField.fillImage(image)
407 outField = lsst.afw.math.ChebyshevBoundedField.fit(image, ctrl)
408 self.assertFloatsAlmostEqual(
409 outField.getCoefficients(), coefficients, rtol=1E-6, atol=1E-7)
411 def testArrayFit(self):
412 """Test that we can fit 1-d arrays produced by a ChebyshevBoundedField and
413 get the same coefficients back.
414 """
415 for ctrl, coefficients in self.cases:
416 inField = lsst.afw.math.ChebyshevBoundedField(
417 self.bbox, coefficients)
418 for Image in (lsst.afw.image.ImageF, lsst.afw.image.ImageD):
419 array = inField.evaluate(self.xFlat, self.yFlat)
420 outField1 = lsst.afw.math.ChebyshevBoundedField.fit(self.bbox, self.xFlat, self.yFlat,
421 array, ctrl)
422 self.assertFloatsAlmostEqual(
423 outField1.getCoefficients(), coefficients, rtol=1E-6, atol=1E-7)
424 weights = (1.0 + np.random.randn(array.size)**2)
425 # Should get same results with different weights, since we still have no noise
426 # and a model that can exactly reproduce the data.
427 outField2 = lsst.afw.math.ChebyshevBoundedField.fit(self.bbox, self.xFlat, self.yFlat,
428 array, weights, ctrl)
429 self.assertFloatsAlmostEqual(
430 outField2.getCoefficients(), coefficients, rtol=1E-7, atol=1E-7)
431 # If we make a matrix for the same points and use it to fit,
432 # we get coefficients in unspecified order, but there are
433 # (up to round-off error) the same as the nonzero entries of
434 # the 2-d coefficients arrays.
435 matrix = lsst.afw.math.ChebyshevBoundedField.makeFitMatrix(self.bbox, self.xFlat, self.yFlat,
436 ctrl)
437 coefficientsPacked, _, _, _ = np.linalg.lstsq(matrix, array)
438 coefficientsPacked.sort()
439 coefficientsFlat = coefficients.flatten()
440 coefficientsFlat = coefficientsFlat[np.abs(coefficientsFlat) > 1E-7]
441 coefficientsFlat.sort()
442 self.assertFloatsAlmostEqual(coefficientsFlat, coefficientsPacked, rtol=1E-6, atol=1E-7)
444 def testApproximate(self):
445 """Test the approximate instantiation with the example of
446 fitting a PixelAreaBoundedField to reasonable precision.
447 """
449 # This HSC-R band wcs was chosen arbitrarily from the edge of
450 # field-of-view (ccd 4) for the w_2019_38 processing of RC2 as it
451 # represents the typical use case of the approxBoundedField method.
452 skyWcs = lsst.afw.geom.SkyWcs.readFits(os.path.join(testPath,
453 "data/jointcal_wcs-0034772-004.fits"))
454 bbox = lsst.geom.Box2I(lsst.geom.Point2I(0, 0),
455 lsst.geom.Point2I(2047, 4175))
457 pixelAreaField = lsst.afw.math.PixelAreaBoundedField(bbox, skyWcs,
458 unit=lsst.geom.arcseconds)
459 approxField = lsst.afw.math.ChebyshevBoundedField.approximate(pixelAreaField)
461 # Choose random points to test rather than a grid to ensure that
462 # we are not using the same gridded points as used for the
463 # approximation.
464 np.random.seed(seed=1000)
465 xTest = np.random.uniform(low=0.0, high=bbox.getMaxX(), size=10000)
466 yTest = np.random.uniform(low=0.0, high=bbox.getMaxY(), size=10000)
468 # The evaluation of approxField is ~80x faster than the
469 # evaluation of pixelAreaField.
470 expect = pixelAreaField.evaluate(xTest, yTest)
471 result = approxField.evaluate(xTest, yTest)
473 # The approximation is good to the 1e-7 level (absolute)
474 self.assertFloatsAlmostEqual(result, expect, atol=1e-7)
475 # and to the 1e-5 level (relative). This is < 0.01 mmag.
476 self.assertFloatsAlmostEqual(result, expect, rtol=1e-5)
478 def testPersistence(self):
479 """Test that we can round-trip a ChebyshevBoundedField through
480 persistence.
481 """
482 boxD = lsst.geom.Box2D(self.bbox)
483 nPoints = 50
484 with lsst.utils.tests.getTempFilePath(".fits") as filename:
485 for ctrl, coefficients in self.cases:
486 inField = lsst.afw.math.ChebyshevBoundedField(
487 self.bbox, coefficients)
488 inField.writeFits(filename)
489 outField = lsst.afw.math.ChebyshevBoundedField.readFits(filename)
490 self.assertEqual(inField.getBBox(), outField.getBBox())
491 self.assertFloatsAlmostEqual(
492 inField.getCoefficients(), outField.getCoefficients())
493 x = np.random.rand(nPoints)*boxD.getWidth() + boxD.getMinX()
494 y = np.random.rand(nPoints)*boxD.getHeight() + boxD.getMinY()
495 z1 = inField.evaluate(x, y)
496 z2 = inField.evaluate(x, y)
497 self.assertFloatsAlmostEqual(z1, z2, rtol=1E-13)
499 # test with an empty bbox
500 inField = lsst.afw.math.ChebyshevBoundedField(lsst.geom.Box2I(),
501 np.array([[1.0, 2.0], [3.0, 4.0]]))
502 inField.writeFits(filename)
503 outField = lsst.afw.math.ChebyshevBoundedField.readFits(filename)
504 self.assertEqual(inField.getBBox(), outField.getBBox())
506 def testProductPersistence(self):
507 """Test that we can round-trip a ProductBoundedField through
508 persistence.
509 """
510 with lsst.utils.tests.getTempFilePath(".fits") as filename:
511 self.product.writeFits(filename)
512 out = lsst.afw.math.ProductBoundedField.readFits(filename)
513 self.assertEqual(self.product, out)
515 def testTruncate(self):
516 """Test that truncate() works as expected.
517 """
518 for ctrl, coefficients in self.cases:
519 field1 = lsst.afw.math.ChebyshevBoundedField(
520 self.bbox, coefficients)
521 field2 = field1.truncate(ctrl)
522 self.assertFloatsAlmostEqual(
523 field1.getCoefficients(), field2.getCoefficients())
524 self.assertEqual(field1.getBBox(), field2.getBBox())
525 config3 = lsst.afw.math.ChebyshevBoundedField.ConfigClass()
526 config3.readControl(ctrl)
527 if ctrl.orderX > 0:
528 config3.orderX -= 1
529 if ctrl.orderY > 0:
530 config3.orderY -= 1
531 field3 = field1.truncate(config3.makeControl())
532 for i in range(config3.orderY + 1):
533 for j in range(config3.orderX + 1):
534 if config3.triangular and i + j > max(config3.orderX, config3.orderY):
535 self.assertEqual(field3.getCoefficients()[i, j], 0.0)
536 else:
537 self.assertEqual(field3.getCoefficients()[i, j],
538 field1.getCoefficients()[i, j])
540 def testEquality(self):
541 for ctrl, coefficients in self.cases:
542 field1 = lsst.afw.math.ChebyshevBoundedField(self.bbox, coefficients)
543 field2 = lsst.afw.math.ChebyshevBoundedField(self.bbox, coefficients)
544 self.assertEqual(field1, field2, msg=coefficients)
546 # same coefficients, instantiated from different arrays
547 field1 = lsst.afw.math.ChebyshevBoundedField(self.bbox, np.array([[1.0]]))
548 field2 = lsst.afw.math.ChebyshevBoundedField(self.bbox, np.array([[1.0]]))
549 self.assertEqual(field1, field2)
550 field1 = lsst.afw.math.ChebyshevBoundedField(self.bbox, np.array([[1.0, 2.0], [3., 4.]]))
551 field2 = lsst.afw.math.ChebyshevBoundedField(self.bbox, np.array([[1.0, 2.0], [3., 4.]]))
552 self.assertEqual(field1, field2)
554 # different coefficient(s)
555 field1 = lsst.afw.math.ChebyshevBoundedField(self.bbox, np.array([[1.0]]))
556 field2 = lsst.afw.math.ChebyshevBoundedField(self.bbox, np.array([[2.0]]))
557 self.assertNotEqual(field1, field2)
558 field1 = lsst.afw.math.ChebyshevBoundedField(self.bbox, np.array([[1.0, 0.0]]))
559 field2 = lsst.afw.math.ChebyshevBoundedField(self.bbox, np.array([[1.0], [0.0]]))
560 self.assertNotEqual(field1, field2)
562 # different bbox
563 bbox1 = lsst.geom.Box2I(lsst.geom.Point2I(-10, -10), lsst.geom.Point2I(5, 5))
564 bbox2 = lsst.geom.Box2I(lsst.geom.Point2I(-5, -5), lsst.geom.Point2I(5, 5))
565 field1 = lsst.afw.math.ChebyshevBoundedField(bbox1, np.array([[1.0]]))
566 field2 = lsst.afw.math.ChebyshevBoundedField(bbox2, np.array([[1.0]]))
567 self.assertNotEqual(field1, field2)
570class MemoryTester(lsst.utils.tests.MemoryTestCase):
571 pass
574def setup_module(module):
575 lsst.utils.tests.init()
578if __name__ == "__main__": 578 ↛ 579line 578 didn't jump to line 579 because the condition on line 578 was never true
579 lsst.utils.tests.init()
580 unittest.main()