Coverage for tests/test_stitched_psf.py: 20%

115 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2024-04-17 03:44 -0700

1# This file is part of cell_coadds. 

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/>. 

21 

22import pickle 

23import unittest 

24 

25import lsst.geom as geom 

26import lsst.meas.base.tests 

27import lsst.utils.tests 

28import numpy as np 

29from lsst.afw.detection import GaussianPsf 

30from lsst.afw.image import ImageD 

31from lsst.cell_coadds import GridContainer, StitchedPsf, UniformGrid 

32from lsst.meas.algorithms import SingleGaussianPsf 

33from lsst.skymap import Index2D 

34 

35 

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

37 """Test the methods of StitchedPsf class.""" 

38 

39 @classmethod 

40 def setUpClass(cls) -> None: # noqa: D102 

41 super().setUpClass() 

42 

43 shape = Index2D(x=3, y=2) 

44 

45 cls.psf_size = 15 

46 cls.psf_sigmas = { 

47 Index2D(x=0, y=0): 1.2, 

48 Index2D(x=0, y=1): 0.7, 

49 Index2D(x=1, y=0): 0.9, 

50 Index2D(x=1, y=1): 1.1, 

51 Index2D(x=2, y=0): 1.3, 

52 Index2D(x=2, y=1): 0.8, 

53 } 

54 

55 gc = GridContainer[ImageD](shape) 

56 for key, sigma in cls.psf_sigmas.items(): 

57 # It does not matter where we compute the kernel image. 

58 # Pick any point. 

59 gc[key] = GaussianPsf(cls.psf_size, cls.psf_size, sigma).computeKernelImage(geom.Point2D(1, 1)) 

60 

61 grid = UniformGrid(cell_size=geom.Extent2I(cls.psf_size, cls.psf_size), shape=shape) 

62 

63 cls.psf = StitchedPsf(gc, grid) 

64 

65 cls.test_positions = ( 

66 (geom.Point2D(5, 4), Index2D(x=0, y=0)), # inner point in lower left 

67 (geom.Point2D(6, 24), Index2D(x=0, y=1)), # inner point in upper left 

68 (geom.Point2D(23.2, 7.8), Index2D(x=1, y=0)), # inner point in lower middle 

69 (geom.Point2D(21, 22), Index2D(x=1, y=1)), # inner point in upper middle 

70 (geom.Point2D(37, 9.4), Index2D(x=2, y=0)), # inner point in lower right 

71 (geom.Point2D(42, 24), Index2D(x=2, y=1)), # inner point in upper right 

72 # Some points that lie on the border 

73 (geom.Point2D(31, 24), Index2D(x=2, y=1)), # inner point in upper right 

74 (geom.Point2D(44, 0), Index2D(x=2, y=0)), # inner point in lower right 

75 (geom.Point2D(17, 16), Index2D(x=1, y=1)), # inner point in upper middle 

76 (geom.Point2D(15, 8), Index2D(x=1, y=0)), # inner point in lower middle 

77 (geom.Point2D(0, 29), Index2D(x=0, y=1)), # inner point in upper left 

78 (geom.Point2D(0, 0), Index2D(x=0, y=0)), # inner point in lower left 

79 ) 

80 

81 def test_resized(self): 

82 """Test that the resized method works as it should.""" 

83 original_bbox = self.psf.computeBBox(self.psf.getAveragePosition()) 

84 # Resizing to original size should leave everything unchanged. 

85 psf = self.psf.resized(self.psf_size, self.psf_size) 

86 self.assertEqual(psf.getAveragePosition(), self.psf.getAveragePosition()) 

87 self.assertEqual( 

88 psf.computeBBox(psf.getAveragePosition()), self.psf.computeBBox(self.psf.getAveragePosition()) 

89 ) 

90 self.assertImagesEqual( 

91 psf.computeKernelImage(psf.getAveragePosition()), 

92 self.psf.computeKernelImage(self.psf.getAveragePosition()), 

93 ) 

94 self.assertEqual(psf, self.psf) 

95 

96 # Resize to a rectangular postage stamp 

97 psf = self.psf.resized(25, 21) 

98 self.assertEqual(self.psf.computeBBox(self.psf.getAveragePosition()), original_bbox) 

99 self.assertEqual(psf.computeBBox(psf.getAveragePosition()).getDimensions(), geom.Extent2I(25, 21)) 

100 self.assertEqual( 

101 psf.computeBBox(psf.getAveragePosition()).getCenter(), 

102 self.psf.computeBBox(self.psf.getAveragePosition()).getCenter(), 

103 ) 

104 self.assertNotEqual(psf, self.psf) 

105 

106 # Test that resizing to even dimensions throws an error 

107 with self.assertRaises(ValueError): 

108 psf = self.psf.resized(26, 22) # even, even 

109 with self.assertRaises(ValueError): 

110 psf = self.psf.resized(25, 22) # odd, even 

111 with self.assertRaises(ValueError): 

112 psf = self.psf.resized(26, 21) # even, odd 

113 

114 def test_clone(self): 

115 """Test that the clone method works.""" 

116 psf = self.psf 

117 cloned = psf.clone() 

118 # Check that the cloned version is an exact replica of the original. 

119 # self.assertEqual(psf, cloned) # Should there be an __eq__ method? 

120 self.assertEqual(psf.getAveragePosition(), cloned.getAveragePosition()) 

121 self.assertEqual( 

122 psf.computeBBox(psf.getAveragePosition()), cloned.computeBBox(cloned.getAveragePosition()) 

123 ) 

124 self.assertImagesEqual( 

125 psf.computeKernelImage(psf.getAveragePosition()), 

126 cloned.computeKernelImage(cloned.getAveragePosition()), 

127 ) 

128 self.assertEqual(psf, cloned) 

129 psf = psf.resized(41, 41) 

130 # Now that one of them has been resized, they should not be equal. 

131 self.assertNotEqual( 

132 psf.computeBBox(psf.getAveragePosition()), cloned.computeBBox(cloned.getAveragePosition()) 

133 ) 

134 with self.assertRaises(TypeError): 

135 self.assertImagesEqual( 

136 psf.computeKernelImage(psf.getAveragePosition()), 

137 cloned.computeKernelImage(cloned.getAveragePosition()), 

138 ) 

139 self.assertNotEqual(psf, cloned) 

140 

141 def test_computeKernelImage(self): 

142 """Test the computeKernelImage method for a StitchedPsf object.""" 

143 stitched_psf = self.psf 

144 psf_bbox = geom.Box2I( 

145 geom.Point2I(-(self.psf_size // 2), -(self.psf_size // 2)), 

146 geom.Extent2I(self.psf_size, self.psf_size), 

147 ) 

148 

149 for position, cell_index in self.test_positions: 

150 image1 = stitched_psf.computeKernelImage(position) 

151 image2 = SingleGaussianPsf( 

152 self.psf_size, self.psf_size, self.psf_sigmas[cell_index] 

153 ).computeKernelImage(position) 

154 # Small differences may exist due to differences in evaluating 

155 # GaussianPsf vs. SingleGaussianPsf 

156 self.assertImagesAlmostEqual(image1, image2, atol=2e-16) 

157 self.assertEqual(image1.getBBox(), psf_bbox) 

158 

159 def test_computeBBox(self): 

160 """Test the computeBBox method for a StitchedPsf object.""" 

161 psf = self.psf 

162 psf_bbox = geom.Box2I( 

163 geom.Point2I(-(self.psf_size // 2), -(self.psf_size // 2)), 

164 geom.Extent2I(self.psf_size, self.psf_size), 

165 ) 

166 

167 for position, _ in self.test_positions: 

168 bbox = psf.computeBBox(position) 

169 self.assertEqual(bbox, psf_bbox) 

170 

171 def test_computeShape(self): 

172 """Test the results from the computeShape method on a StitchedPsf 

173 object matches the true input. 

174 """ 

175 stitched_psf = self.psf 

176 for position, cell_index in self.test_positions: 

177 psf_shape = stitched_psf.computeShape(position) # check we can compute shape 

178 self.assertIsNot(psf_shape.getIxx(), np.nan) 

179 self.assertIsNot(psf_shape.getIyy(), np.nan) 

180 self.assertIsNot(psf_shape.getIxy(), np.nan) 

181 

182 # Moments measured from pixellated images are significantly 

183 # underestimated for small PSFs. 

184 if self.psf_sigmas[cell_index] >= 1.0: 

185 self.assertAlmostEqual(psf_shape.getIxx(), self.psf_sigmas[cell_index] ** 2, delta=1e-3) 

186 self.assertAlmostEqual(psf_shape.getIyy(), self.psf_sigmas[cell_index] ** 2, delta=1e-3) 

187 self.assertAlmostEqual(psf_shape.getIxy(), 0.0) 

188 

189 def test_computeApertureFlux(self): 

190 """Test that the results from the computeApertureFlux method on a 

191 StitchedPsf object returns the analytical results for a Gaussian PSF. 

192 """ 

193 stitched_psf = self.psf 

194 for position, cell_index in self.test_positions: 

195 flux1sigma = stitched_psf.computeApertureFlux(self.psf_sigmas[cell_index], position=position) 

196 self.assertAlmostEqual(flux1sigma, 0.39, delta=5e-2) 

197 

198 flux3sigma = stitched_psf.computeApertureFlux( 

199 3.0 * self.psf_sigmas[cell_index], position=position 

200 ) 

201 self.assertAlmostEqual(flux3sigma, 0.97, delta=2e-2) 

202 

203 def test_computeImage(self): 

204 """Test the computeImage method for a StitchedPsf object produces 

205 the same result as that on GaussianPsf for Gaussian PSFs. 

206 """ 

207 stitched_psf = self.psf 

208 psf_extent = geom.Extent2I(self.psf_size, self.psf_size) 

209 

210 for position, cell_index in self.test_positions: 

211 image1 = stitched_psf.computeImage(position) 

212 image2 = GaussianPsf(self.psf_size, self.psf_size, self.psf_sigmas[cell_index]).computeImage( 

213 position 

214 ) 

215 self.assertImagesEqual(image1, image2) 

216 self.assertEqual(image1.getBBox().getDimensions(), psf_extent) 

217 

218 def test_computeImage_computeKernelImage(self): 

219 """Test that computeImage called at integer points gives the same 

220 result as calling computeKernelImage. 

221 """ 

222 stitched_psf = self.psf 

223 for position, _cell_index in self.test_positions: 

224 pos = geom.Point2D(geom.Point2I(position)) # round to integer 

225 image1 = stitched_psf.computeKernelImage(pos) 

226 image2 = stitched_psf.computeImage(pos) 

227 self.assertImagesEqual(image1, image2) 

228 

229 def test_pickle(self): 

230 """Test that StitchedPsf objects can be pickled and unpickled.""" 

231 self.assertTrue(self.psf.isPersistable()) 

232 stream = pickle.dumps(self.psf) 

233 psf = pickle.loads(stream) 

234 self.assertEqual(psf.getAveragePosition(), self.psf.getAveragePosition()) 

235 self.assertEqual( 

236 psf.computeBBox(psf.getAveragePosition()), self.psf.computeBBox(self.psf.getAveragePosition()) 

237 ) 

238 self.assertImagesEqual( 

239 psf.computeKernelImage(psf.getAveragePosition()), 

240 self.psf.computeKernelImage(self.psf.getAveragePosition()), 

241 ) 

242 self.assertEqual(psf, self.psf) 

243 

244 

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

246 """Test for memory/resource leaks.""" 

247 

248 

249def setup_module(module): # noqa: D103 

250 lsst.utils.tests.init() 

251 

252 

253if __name__ == "__main__": 253 ↛ 254line 253 didn't jump to line 254, because the condition on line 253 was never true

254 lsst.utils.tests.init() 

255 unittest.main()