Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

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

21 

22import unittest 

23from copy import deepcopy 

24 

25import numpy as np 

26 

27import lsst.utils.tests 

28from lsst.afw.detection import Psf, GaussianPsf 

29from lsst.afw.image import Image 

30from lsst.geom import Box2I, Extent2I, Point2I, Point2D 

31from lsst.afw.geom.ellipses import Quadrupole 

32import testPsfTrampolineLib as cppLib 

33 

34 

35# Subclass Psf in python. Main tests here are that python virtual methods get 

36# resolved by trampoline class. The test suite below calls python compute* 

37# methods which are implemented in c++ to call the _doCompute* methods defined 

38# in the PyGaussianPsf class. 

39class PyGaussianPsf(Psf): 

40 def __init__(self, width, height, sigma): 

41 Psf.__init__(self, isFixed=True) 

42 self.dimensions = Extent2I(width, height) 

43 self.sigma = sigma 

44 

45 # "public" virtual overrides 

46 def __deepcopy__(self, memo=None): 

47 return PyGaussianPsf(self.dimensions.x, self.dimensions.y, self.sigma) 

48 

49 def resized(self, width, height): 

50 return PyGaussianPsf(width, height, self.sigma) 

51 

52 # "private" virtual overrides are underscored 

53 def _doComputeKernelImage(self, position=None, color=None): 

54 bbox = self.computeBBox() 

55 img = Image(bbox, dtype=np.float64) 

56 x, y = np.ogrid[bbox.minY:bbox.maxY+1, bbox.minX:bbox.maxX+1] 

57 rsqr = x**2 + y**2 

58 img.array[:] = np.exp(-0.5*rsqr/self.sigma**2) 

59 img.array /= np.sum(img.array) 

60 return img 

61 

62 def _doComputeBBox(self, position=None, color=None): 

63 return Box2I(Point2I(-self.dimensions/2), self.dimensions) 

64 

65 def _doComputeShape(self, position=None, color=None): 

66 return Quadrupole(self.sigma**2, self.sigma**2, 0.0) 

67 

68 def _doComputeApertureFlux(self, radius, position=None, color=None): 

69 return 1 - np.exp(-0.5*(radius/self.sigma)**2) 

70 

71 

72class PsfTrampolineTestSuite(lsst.utils.tests.TestCase): 

73 def setUp(self): 

74 self.pgps = [] 

75 self.gps = [] 

76 for width, height, sigma in [ 

77 (5, 5, 1.1), 

78 (5, 3, 1.2), 

79 (7, 7, 1.3) 

80 ]: 

81 self.pgps.append(PyGaussianPsf(width, height, sigma)) 

82 self.gps.append(GaussianPsf(width, height, sigma)) 

83 

84 def testImages(self): 

85 for pgp, gp in zip(self.pgps, self.gps): 

86 self.assertImagesAlmostEqual( 

87 pgp.computeImage(), 

88 gp.computeImage() 

89 ) 

90 self.assertImagesAlmostEqual( 

91 pgp.computeKernelImage(), 

92 gp.computeKernelImage() 

93 ) 

94 

95 def testApertureFlux(self): 

96 for pgp, gp in zip(self.pgps, self.gps): 

97 for r in [0.1, 0.2, 0.3]: 

98 self.assertAlmostEqual( 

99 pgp.computeApertureFlux(r), 

100 gp.computeApertureFlux(r) 

101 ) 

102 

103 def testPeak(self): 

104 for pgp, gp in zip(self.pgps, self.gps): 

105 self.assertAlmostEqual( 

106 pgp.computePeak(), 

107 gp.computePeak() 

108 ) 

109 

110 def testBBox(self): 

111 for pgp, gp in zip(self.pgps, self.gps): 

112 self.assertEqual( 

113 pgp.computeBBox(), 

114 gp.computeBBox() 

115 ) 

116 

117 def testShape(self): 

118 for pgp, gp in zip(self.pgps, self.gps): 

119 self.assertAlmostEqual( 

120 pgp.computeShape(), 

121 gp.computeShape() 

122 ) 

123 

124 def testResized(self): 

125 for pgp, gp in zip(self.pgps, self.gps): 

126 width, height = pgp.dimensions 

127 rpgp = pgp.resized(width+2, height+4) 

128 # cppLib.resizedPsf calls Psf::resized, which redirects to 

129 # PyGaussianPsf.resized above 

130 rpgp2 = cppLib.resizedPsf(pgp, width+2, height+4) 

131 rgp = gp.resized(width+2, height+4) 

132 self.assertImagesAlmostEqual( 

133 rpgp.computeImage(), 

134 rgp.computeImage() 

135 ) 

136 self.assertImagesAlmostEqual( 

137 rpgp2.computeImage(), 

138 rgp.computeImage() 

139 ) 

140 

141 def testClone(self): 

142 """Test different ways of invoking PyGaussianPsf.__deepcopy__ 

143 """ 

144 for pgp in self.pgps: 

145 # directly 

146 p1 = deepcopy(pgp) 

147 # cppLib::clonedPsf -> Psf::clone 

148 p2 = cppLib.clonedPsf(pgp) 

149 # cppLib::clonedStorablePsf -> Psf::cloneStorable 

150 p3 = cppLib.clonedStorablePsf(pgp) 

151 # Psf::clone() 

152 p4 = pgp.clone() 

153 

154 for p in [p1, p2, p3, p4]: 

155 self.assertIsNot(pgp, p) 

156 self.assertImagesEqual( 

157 pgp.computeImage(), 

158 p.computeImage() 

159 ) 

160 

161 

162# Psf with position-dependent image, but nonetheless may use isFixed=True. 

163# When isFixed=True, first image returned is cached for all subsequent image 

164# queries 

165class TestPsf(Psf): 

166 def __init__(self, isFixed): 

167 Psf.__init__(self, isFixed=isFixed) 

168 

169 def _doComputeKernelImage(self, position=None, color=None): 

170 bbox = Box2I(Point2I(-3, -3), Extent2I(7, 7)) 

171 img = Image(bbox, dtype=np.float64) 

172 x, y = np.ogrid[bbox.minY:bbox.maxY+1, bbox.minX:bbox.maxX+1] 

173 rsqr = x**2 + y**2 

174 if position.x >= 0.0: 

175 img.array[:] = np.exp(-0.5*rsqr) 

176 else: 

177 img.array[:] = np.exp(-0.5*rsqr/4) 

178 img.array /= np.sum(img.array) 

179 return img 

180 

181 

182class FixedPsfTestSuite(lsst.utils.tests.TestCase): 

183 def setUp(self): 

184 self.fixedPsf = TestPsf(isFixed=True) 

185 self.floatPsf = TestPsf(isFixed=False) 

186 

187 def testFloat(self): 

188 pos1 = Point2D(1.0, 1.0) 

189 pos2 = Point2D(-1.0, -1.0) 

190 img1 = self.floatPsf.computeKernelImage(pos1) 

191 img2 = self.floatPsf.computeKernelImage(pos2) 

192 self.assertFloatsNotEqual(img1.array, img2.array) 

193 

194 def testFixed(self): 

195 pos1 = Point2D(1.0, 1.0) 

196 pos2 = Point2D(-1.0, -1.0) 

197 img1 = self.fixedPsf.computeKernelImage(pos1) 

198 # Although _doComputeKernelImage would return a different image here due 

199 # do the difference between pos1 and pos2, for the fixed Psf, the 

200 # caching mechanism intercepts instead and _doComputeKernelImage is 

201 # never called with position=pos2. So img1 == img2. 

202 img2 = self.fixedPsf.computeKernelImage(pos2) 

203 self.assertFloatsEqual(img1.array, img2.array) 

204 

205 

206class MemoryTester(lsst.utils.tests.MemoryTestCase): 

207 pass 

208 

209 

210def setup_module(module): 

211 lsst.utils.tests.init() 

212 

213 

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

215 lsst.utils.tests.init() 

216 unittest.main()