Coverage for tests/test_SincPhotSums.py: 24%

141 statements  

« prev     ^ index     » next       coverage.py v6.4, created at 2022-05-27 11:34 +0000

1# This file is part of meas_base. 

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 math 

23import unittest 

24 

25import numpy as np 

26 

27import lsst.geom 

28import lsst.afw.image as afwImage 

29import lsst.afw.detection as afwDetection 

30import lsst.afw.math as afwMath 

31import lsst.afw.geom as afwGeom 

32import lsst.afw.geom.ellipses as afwEll 

33import lsst.meas.base as measBase 

34import lsst.utils.tests 

35 

36try: 

37 display 

38except NameError: 

39 display = False 

40else: 

41 import lsst.afw.display as afwDisplay 

42 afwDisplay.setDefaultMaskTransparency(75) 

43 

44 

45def plantSources(bbox, kwid, sky, coordList, addPoissonNoise=True): 

46 """Make an exposure with stars (modelled as Gaussians). 

47 

48 Parameters 

49 ---------- 

50 bbox : `lsst.afw.geom.Box2I` 

51 Parent bounding box of output exposure. 

52 kwid : `float` 

53 Kernel width (and height; kernel is square). 

54 sky : `float` 

55 Amount of sky background (counts) 

56 coordList : iterable of `list` 

57 Where: 

58 - ``coordList[0]`` is the X coord of the star relative to the origin 

59 - ``coordList[1]`` is the Y coord of the star relative to the origin 

60 - ``coordList[2]`` is the integrated counts in the star 

61 - ``coordlist[3]`` is the Gaussian sigma in pixels. 

62 addPoissonNoise : `bool`, optional 

63 Add Poisson noise to the output exposure? 

64 

65 Returns 

66 ------- 

67 exposure : `lsst.afw.image.ExposureF` 

68 Resulting exposure with simulated stars. 

69 """ 

70 # make an image with sources 

71 img = afwImage.ImageD(bbox) 

72 meanSigma = 0.0 

73 for coord in coordList: 

74 x, y, counts, sigma = coord 

75 meanSigma += sigma 

76 

77 # make a single gaussian psf 

78 psf = afwDetection.GaussianPsf(kwid, kwid, sigma) 

79 

80 # make an image of it and scale to the desired number of counts 

81 thisPsfImg = psf.computeImage(lsst.geom.PointD(int(x), int(y))) 

82 thisPsfImg *= counts 

83 

84 # bbox a window in our image and add the fake star image 

85 imgSeg = img.Factory(img, thisPsfImg.getBBox()) 

86 imgSeg += thisPsfImg 

87 meanSigma /= len(coordList) 

88 

89 img += sky 

90 

91 # add Poisson noise 

92 if (addPoissonNoise): 

93 np.random.seed(seed=1) # make results reproducible 

94 imgArr = img.getArray() 

95 imgArr[:] = np.random.poisson(imgArr) 

96 

97 # bundle into a maskedimage and an exposure 

98 mask = afwImage.Mask(bbox) 

99 var = img.convertFloat() 

100 img -= sky 

101 mimg = afwImage.MaskedImageF(img.convertFloat(), mask, var) 

102 exposure = afwImage.makeExposure(mimg) 

103 

104 # insert an approximate psf 

105 psf = afwDetection.GaussianPsf(kwid, kwid, meanSigma) 

106 exposure.setPsf(psf) 

107 

108 return exposure 

109 

110 

111class SincPhotSums(lsst.utils.tests.TestCase): 

112 

113 def setUp(self): 

114 self.nx = 64 

115 self.ny = 64 

116 self.kwid = 15 

117 self.sky = 100.0 

118 self.val = 10000.0 

119 self.sigma = 4.0 

120 coordList = [[self.nx/2, self.ny/2, self.val, self.sigma]] 

121 

122 # exposure with gaussian 

123 bbox = lsst.geom.Box2I(lsst.geom.Point2I(0, 0), lsst.geom.Extent2I(self.nx, self.ny)) 

124 self.expGaussPsf = plantSources(bbox, self.kwid, self.sky, coordList, addPoissonNoise=False) 

125 

126 # just plain sky (ie. a constant) 

127 self.mimg = afwImage.MaskedImageF(lsst.geom.ExtentI(self.nx, self.ny)) 

128 self.mimg.set(self.sky, 0x0, self.sky) 

129 self.expSky = afwImage.makeExposure(self.mimg) 

130 

131 if display: 

132 frame = 0 

133 disp = afwDisplay.Display(frame=frame) 

134 disp.mtv(self.expGaussPsf, title=self._testMethodName + ": expGaussPsf") 

135 

136 def tearDown(self): 

137 del self.mimg 

138 del self.expGaussPsf 

139 del self.expSky 

140 

141 def testEllipticalGaussian(self): 

142 """Test measuring elliptical aperture mags for an elliptical Gaussian. 

143 """ 

144 width, height = 200, 200 

145 xcen, ycen = 0.5*width, 0.5*height 

146 # 

147 # Make the object 

148 # 

149 gal = afwImage.ImageF(lsst.geom.ExtentI(width, height)) 

150 a, b, theta = float(10), float(5), 20 

151 instFlux = 1e4 

152 I0 = instFlux/(2*math.pi*a*b) 

153 

154 c, s = math.cos(math.radians(theta)), math.sin(math.radians(theta)) 

155 for y in range(height): 

156 for x in range(width): 

157 dx, dy = x - xcen, y - ycen 

158 u = c*dx + s*dy 

159 v = -s*dx + c*dy 

160 val = I0*math.exp(-0.5*((u/a)**2 + (v/b)**2)) 

161 if val < 0: 

162 val = 0 

163 gal[x, y, afwImage.LOCAL] = val 

164 

165 objImg = afwImage.makeExposure(afwImage.makeMaskedImage(gal)) 

166 del gal 

167 

168 if display: 

169 frame = 1 

170 disp = afwDisplay.Display(frame=frame) 

171 disp.mtv(objImg, title=self._testMethodName + ": Elliptical") 

172 

173 self.assertAlmostEqual(1.0, afwMath.makeStatistics(objImg.getMaskedImage().getImage(), 

174 afwMath.SUM).getValue()/instFlux) 

175 # 

176 # Now measure some annuli 

177 # 

178 for r1, r2 in [(0., 0.45*a), 

179 (0.45*a, 1.0*a), 

180 (1.0*a, 2.0*a), 

181 (2.0*a, 3.0*a), 

182 (3.0*a, 5.0*a), 

183 (3.0*a, 10.0*a), 

184 ]: 

185 if display: # draw the inner and outer boundaries of the aperture 

186 Mxx = 1 

187 Myy = (b/a)**2 

188 

189 mxx, mxy, myy = c**2*Mxx + s**2*Myy, c*s*(Mxx - Myy), s**2*Mxx + c**2*Myy 

190 for r in (r1, r2): 

191 disp.dot("@:%g,%g,%g" % (r**2*mxx, r**2*mxy, r**2*myy), xcen, ycen) 

192 

193 center = lsst.geom.Point2D(xcen, ycen) 

194 

195 # this tests tests a sync algorithm with an inner and outer radius 

196 # since that is no longer available from the ApertureFluxAlgorithm, 

197 # we will calculate the two and subtract. 

198 

199 axes = afwGeom.ellipses.Axes(r2, r2*(1-b/a), math.radians(theta)) 

200 ellipse = afwGeom.Ellipse(axes, center) 

201 result2 = measBase.ApertureFluxAlgorithm.computeSincFlux(objImg.getMaskedImage(), ellipse) 

202 

203 axes = afwGeom.ellipses.Axes(r1, r1*(1-b/a), math.radians(theta)) 

204 ellipse = afwGeom.Ellipse(axes, center) 

205 result1 = measBase.ApertureFluxAlgorithm.computeSincFlux(objImg.getMaskedImage(), ellipse) 

206 

207 self.assertAlmostEqual(math.exp(-0.5*(r1/a)**2) - math.exp(-0.5*(r2/a)**2), 

208 (result2.instFlux-result1.instFlux)/instFlux, 4) 

209 

210 

211class SincCoeffTestCase(lsst.utils.tests.TestCase): 

212 

213 def setUp(self): 

214 self.ellipse = afwEll.Axes(10.0, 5.0, 0.12345) 

215 self.radius1 = 0.1234 

216 self.radius2 = 4.3210 

217 self.inner = self.radius1/self.radius2 

218 

219 def tearDown(self): 

220 del self.ellipse 

221 

222 def assertCached(self, coeff1, coeff2): 

223 np.testing.assert_array_equal(coeff1.getArray(), coeff2.getArray()) 

224 # This compares the memory address and read-only attributes of the images. 

225 self.assertEqual(coeff1.array.ctypes.data, 

226 coeff2.array.ctypes.data) 

227 

228 def assertNotCached(self, coeff1, coeff2): 

229 np.testing.assert_array_equal(coeff1.getArray(), coeff2.getArray()) 

230 # This compares the memory address and read-only attributes of the images. 

231 self.assertNotEqual(coeff1.array.ctypes.data, 

232 coeff2.array.ctypes.data) 

233 

234 def getCoeffCircle(self, radius2): 

235 circle = afwEll.Axes(radius2, radius2, 0.0) 

236 inner = self.radius1/radius2 

237 coeff1 = measBase.SincCoeffsF.get(circle, inner) 

238 coeff2 = measBase.SincCoeffsF.get(circle, inner) 

239 return coeff1, coeff2 

240 

241 def testNoCachingElliptical(self): 

242 coeff1 = measBase.SincCoeffsF.get(self.ellipse, self.inner) 

243 coeff2 = measBase.SincCoeffsF.get(self.ellipse, self.inner) 

244 self.assertNotCached(coeff1, coeff2) 

245 

246 def testNoCachingCircular(self): 

247 coeff1, coeff2 = self.getCoeffCircle(2*self.radius2) # not self.radius2 because that may be cached 

248 self.assertNotCached(coeff1, coeff2) 

249 

250 def testWithCaching(self): 

251 measBase.SincCoeffsF.cache(self.radius1, self.radius2) 

252 coeff1, coeff2 = self.getCoeffCircle(self.radius2) 

253 self.assertCached(coeff1, coeff2) 

254 

255 

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

257 pass 

258 

259 

260def setup_module(module): 

261 lsst.utils.tests.init() 

262 

263 

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

265 lsst.utils.tests.init() 

266 unittest.main()