Coverage for tests/test_coaddBoundedField.py: 16%

136 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2023-04-13 10:38 +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 

24 

25import numpy as np 

26 

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 

34 

35 

36class CoaddBoundedFieldTestCase(lsst.utils.tests.TestCase): 

37 

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) 

50 

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) 

58 

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 = [] 

65 

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 

77 

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))) 

85 

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. 

89 

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) 

111 

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. 

118 

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) 

124 

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) 

131 

132 self.assertLess(bad.sum(), 0.10*self.bbox.getArea()) 

133 

134 for validBox in self.possibleValidBoxes: 

135 elements, validBoxes = self.constructElements(validBox) 

136 self.assertNotEqual(elements[0], elements[1]) 

137 runTest(elements, validBoxes) 

138 

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) 

144 

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) 

148 

149 field4 = lsst.meas.algorithms.CoaddBoundedField(self.bbox, self.coaddWcs, elements, 0.0) 

150 self.assertEqual(field1, field4) 

151 

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) 

158 

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) 

164 

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) 

168 

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) 

173 

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) 

182 

183 for validBox in self.possibleValidBoxes: 

184 runTest(*self.constructElements(validBox)) 

185 

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) 

204 

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) 

215 

216 for validBox in self.possibleValidBoxes: 

217 runTest(*self.constructElements(validBox)) 

218 

219 def tearDown(self): 

220 del self.coaddWcs 

221 del self.bbox 

222 

223 

224class TestMemory(lsst.utils.tests.MemoryTestCase): 

225 pass 

226 

227 

228def setup_module(module): 

229 lsst.utils.tests.init() 

230 

231 

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()