Coverage for tests/test_coaddBoundedField.py: 16%
136 statements
« prev ^ index » next coverage.py v6.5.0, created at 2022-10-04 10:06 +0000
« prev ^ index » next coverage.py v6.5.0, created at 2022-10-04 10:06 +0000
1#
2# LSST Data Management System
3# Copyright 2008-2017 LSST Corporation.
4#
5# This product includes software developed by the
6# LSST Project (http://www.lsst.org/).
7#
8# This program is free software: you can redistribute it and/or modify
9# it under the terms of the GNU General Public License as
10# the Free Software Foundation, either version 3 of the License, or
11# (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU General Public License for more details.
17#
18# You should have received a copy of the LSST License Statement and
19# the GNU General Public License along with this program. If not,
20# see <http://www.lsstcorp.org/LegalNotices/>.
21#
22import os
23import unittest
25import numpy as np
27import lsst.geom
28import lsst.afw.math
29import lsst.afw.geom
30import lsst.afw.image
31import lsst.meas.algorithms
32import lsst.pex.exceptions
33import lsst.utils.tests
36class CoaddBoundedFieldTestCase(lsst.utils.tests.TestCase):
38 def makeRandomWcs(self, crval, maxOffset=10.0):
39 """Make a random TAN Wcs that's complex enough to create an interesting test, by combining
40 random offsets and rotations. We don't include any random scaling, because we don't want
41 the Jacobian to be significantly different from one in these tests, as we want to compare
42 warped images (which implicitly include the Jacobian) against the behavior of CoaddBoundedField
43 (which intentionally does not).
44 """
45 crpix = lsst.geom.Point2D(*np.random.uniform(low=-maxOffset, high=maxOffset, size=2))
46 scale = 0.01*lsst.geom.arcseconds
47 orientation = np.pi*np.random.rand()*lsst.geom.radians
48 cdMatrix = lsst.afw.geom.makeCdMatrix(scale=scale, orientation=orientation)
49 return lsst.afw.geom.makeSkyWcs(crpix=crpix, crval=crval, cdMatrix=cdMatrix)
51 def makeRandomField(self, bbox):
52 """Create a random ChebyshevBoundedField"""
53 coefficients = np.random.randn(3, 3)
54 # We add a positive DC offset to make sure our fields more likely to combine constructively;
55 # this makes the test more stringent, because we get less accidental small numbers.
56 coefficients[0, 0] = np.random.uniform(low=4, high=6)
57 return lsst.afw.math.ChebyshevBoundedField(bbox, coefficients)
59 def constructElements(self, validBox):
60 """Construct the elements of a CoaddBoundedField."""
61 np.random.seed(50)
62 validPolygon = lsst.afw.geom.Polygon(lsst.geom.Box2D(validBox)) if validBox else None
63 elements = []
64 validBoxes = []
66 for i in range(10):
67 elements.append(
68 lsst.meas.algorithms.CoaddBoundedField.Element(
69 self.makeRandomField(self.elementBBox),
70 self.makeRandomWcs(self.crval),
71 validPolygon,
72 np.random.uniform(low=0.5, high=1.5)
73 )
74 )
75 validBoxes.append(validBox)
76 return elements, validBoxes
78 def setUp(self):
79 self.crval = lsst.geom.SpherePoint(45.0, 45.0, lsst.geom.degrees)
80 self.elementBBox = lsst.geom.Box2I(lsst.geom.Point2I(-50, -50), lsst.geom.Point2I(50, 50))
81 self.coaddWcs = self.makeRandomWcs(self.crval, maxOffset=0.0)
82 self.bbox = lsst.geom.Box2I(lsst.geom.Point2I(-75, -75), lsst.geom.Point2I(75, 75))
83 self.possibleValidBoxes = (None, lsst.geom.Box2I(lsst.geom.Point2I(-25, -25),
84 lsst.geom.Point2I(25, 25)))
86 def testEvaluate(self):
87 """Test the main implementation of CoaddBoundedField::evaluate() by creating images of
88 each constituent field, warping them, and coadding them to form a check image.
90 We run this test twice: once with a regular ValidPolygon, and once
91 with the polygon undefined (ie, set to None); see DM-11008.
92 """
93 def runTest(elements, validBoxes):
94 field = lsst.meas.algorithms.CoaddBoundedField(self.bbox, self.coaddWcs, elements, 0.0)
95 coaddImage = lsst.afw.image.ImageF(self.bbox)
96 warpCtrl = lsst.afw.math.WarpingControl("bilinear")
97 weightMap = lsst.afw.image.ImageF(self.bbox)
98 for element, validBox in zip(elements, validBoxes):
99 elementImage = (lsst.afw.image.ImageF(validBox) if validBox
100 else lsst.afw.image.ImageF(element.field.getBBox()))
101 element.field.fillImage(elementImage, True)
102 warp = lsst.afw.image.ImageF(self.bbox)
103 lsst.afw.math.warpImage(warp, self.coaddWcs, elementImage, element.wcs, warpCtrl, 0.0)
104 coaddImage.scaledPlus(element.weight, warp)
105 warp.getArray()[warp.getArray() != 0.0] = element.weight
106 weightMap += warp
107 coaddImage /= weightMap
108 coaddImage.getArray()[np.isnan(coaddImage.getArray())] = 0.0
109 fieldImage = lsst.afw.image.ImageF(self.bbox)
110 field.fillImage(fieldImage)
112 # The coaddImage we're comparing to has artifacts at the edges of all the input exposures,
113 # due to the interpolation, so we just check that the number of unequal pixels is small (<10%)
114 # This can depend on the random number seed, so if a failure is seen, uncomment the line below
115 # to enable a plot of the differences, and verify by eye that they look like edge artifacts. If
116 # they do, just modify the seed (at the top of this file) or change number-of-pixels threshold
117 # until the test passes.
119 diff = np.abs(fieldImage.getArray() - coaddImage.getArray())
120 relTo = np.abs(fieldImage.getArray())
121 rtol = 1E-2
122 atol = 1E-7
123 bad = np.logical_and(diff > rtol*relTo, diff > atol)
125 # enable this to see a plot of the comparison (but it will always
126 # fail, since it doesn't take into account the artifacts in
127 # coaddImage)
128 if False:
129 self.assertFloatsAlmostEqual(fieldImage.getArray(), coaddImage.getArray(), plotOnFailure=True,
130 rtol=rtol, atol=atol, relTo=relTo)
132 self.assertLess(bad.sum(), 0.10*self.bbox.getArea())
134 for validBox in self.possibleValidBoxes:
135 elements, validBoxes = self.constructElements(validBox)
136 self.assertNotEqual(elements[0], elements[1])
137 runTest(elements, validBoxes)
139 def testEquality(self):
140 def runTest(elements, validBoxes):
141 field1 = lsst.meas.algorithms.CoaddBoundedField(self.bbox, self.coaddWcs, elements, 0.0)
142 field2 = lsst.meas.algorithms.CoaddBoundedField(self.bbox, self.coaddWcs, elements, 0.0)
143 self.assertEqual(field1, field2)
145 bbox = lsst.geom.Box2I(lsst.geom.Point2I(-75, -75), lsst.geom.Point2I(75, 75))
146 field3 = lsst.meas.algorithms.CoaddBoundedField(bbox, self.coaddWcs, elements, 0.0)
147 self.assertEqual(field1, field3)
149 field4 = lsst.meas.algorithms.CoaddBoundedField(self.bbox, self.coaddWcs, elements, 0.0)
150 self.assertEqual(field1, field4)
152 # NOTE: make a copy of the list; [:] to copy segfaults,
153 # copy.copy() doesn't behave nicely on py2 w/our pybind11 objects,
154 # and .elements.copy() doesn't exist on py2.
155 elementsCopy = list(elements)
156 field5 = lsst.meas.algorithms.CoaddBoundedField(self.bbox, self.coaddWcs, elementsCopy, 0.0)
157 self.assertEqual(field1, field5)
159 # inequality tests below here
160 field6 = lsst.meas.algorithms.CoaddBoundedField(self.bbox, self.coaddWcs, elements, 3.0)
161 self.assertNotEqual(field1, field6)
162 field7 = lsst.meas.algorithms.CoaddBoundedField(self.bbox, self.coaddWcs, [], 0.0)
163 self.assertNotEqual(field1, field7)
165 bbox = lsst.geom.Box2I(lsst.geom.Point2I(-74, -75), lsst.geom.Point2I(75, 75))
166 field8 = lsst.meas.algorithms.CoaddBoundedField(bbox, self.coaddWcs, elements, 0.0)
167 self.assertNotEqual(field1, field8)
169 crval = lsst.geom.SpherePoint(45.0, 45.0, lsst.geom.degrees)
170 coaddWcs = self.makeRandomWcs(crval, maxOffset=2.0)
171 field9 = lsst.meas.algorithms.CoaddBoundedField(self.bbox, coaddWcs, elements, 0.0)
172 self.assertNotEqual(field1, field9)
174 elementsCopy = list(elements)
175 elementsCopy[2].weight = 1000000
176 field10 = lsst.meas.algorithms.CoaddBoundedField(self.bbox, self.coaddWcs, elementsCopy, 0.0)
177 self.assertNotEqual(field1, field10)
178 elementsCopy = list(elements)
179 elementsCopy.pop(0)
180 field11 = lsst.meas.algorithms.CoaddBoundedField(self.bbox, self.coaddWcs, elementsCopy, 0.0)
181 self.assertNotEqual(field1, field11)
183 for validBox in self.possibleValidBoxes:
184 runTest(*self.constructElements(validBox))
186 def testPersistence(self):
187 """Test that we can round-trip a CoaddBoundedField through FITS."""
188 def runTest(elements, validBoxes):
189 filename = "testCoaddBoundedField.fits"
190 field1 = lsst.meas.algorithms.CoaddBoundedField(self.bbox, self.coaddWcs, elements, 0.0)
191 field1.writeFits(filename)
192 field2 = lsst.meas.algorithms.CoaddBoundedField.readFits(filename)
193 self.assertEqual(field1, field2)
194 elements2 = field2.getElements()
195 self.assertEqual(elements, elements2)
196 for el1, el2 in zip(elements, elements2):
197 self.assertEqual(el1, el2)
198 self.assertEqual(el1.field, el2.field)
199 self.assertEqual(el1.wcs, el2.wcs)
200 self.assertWcsAlmostEqualOverBBox(el1.wcs, el2.wcs, self.bbox,
201 maxDiffPix=0, maxDiffSky=0*lsst.geom.arcseconds)
202 self.assertEqual(el1.validPolygon, el2.validPolygon)
203 self.assertEqual(el1.weight, el2.weight)
205 self.assertEqual(field1.getCoaddWcs(), field2.getCoaddWcs())
206 self.assertWcsAlmostEqualOverBBox(self.coaddWcs, field2.getCoaddWcs(), self.bbox,
207 maxDiffPix=0, maxDiffSky=0*lsst.geom.arcseconds)
208 self.assertEqual(field1.getDefault(), field2.getDefault())
209 image1 = lsst.afw.image.ImageD(self.bbox)
210 image2 = lsst.afw.image.ImageD(self.bbox)
211 field1.fillImage(image1)
212 field2.fillImage(image2)
213 self.assertImagesEqual(image1, image2)
214 os.remove(filename)
216 for validBox in self.possibleValidBoxes:
217 runTest(*self.constructElements(validBox))
219 def tearDown(self):
220 del self.coaddWcs
221 del self.bbox
224class TestMemory(lsst.utils.tests.MemoryTestCase):
225 pass
228def setup_module(module):
229 lsst.utils.tests.init()
232if __name__ == "__main__": 232 ↛ 233line 232 didn't jump to line 233, because the condition on line 232 was never true
233 lsst.utils.tests.init()
234 unittest.main()