Coverage for tests/test_projectedLikelihood.py: 15%

175 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2023-03-09 04:07 -0800

1# 

2# LSST Data Management System 

3# 

4# Copyright 2008-2016 AURA/LSST. 

5# 

6# This product includes software developed by the 

7# LSST Project (http://www.lsst.org/). 

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 LSST License Statement and 

20# the GNU General Public License along with this program. If not, 

21# see <https://www.lsstcorp.org/LegalNotices/>. 

22# 

23import unittest 

24import numpy 

25 

26import lsst.utils.tests 

27import lsst.shapelet.tests 

28import lsst.geom 

29import lsst.afw.geom 

30import lsst.afw.geom.ellipses 

31import lsst.afw.image 

32import lsst.afw.math 

33import lsst.afw.detection 

34import lsst.meas.modelfit 

35 

36 

37ASSERT_CLOSE_KWDS = dict(plotOnFailure=False, printFailures=False) 

38 

39 

40def makeGaussianFunction(ellipse, flux=1.0): 

41 """Create a single-Gaussian MultiShapeletFunction 

42 

43 ellipse may be an afw.geom.ellipses.Ellipse or a float radius for a circle 

44 """ 

45 s = lsst.shapelet.ShapeletFunction(0, lsst.shapelet.HERMITE, ellipse) 

46 s.getCoefficients()[0] = 1.0 

47 s.normalize() 

48 s.getCoefficients()[0] *= flux 

49 msf = lsst.shapelet.MultiShapeletFunction() 

50 msf.addComponent(s) 

51 return msf 

52 

53 

54def addGaussian(exposure, ellipse, flux, psf=None): 

55 s = makeGaussianFunction(ellipse, flux) 

56 if psf is not None: 

57 s = s.convolve(psf) 

58 imageF = exposure.getMaskedImage().getImage() 

59 imageD = lsst.afw.image.ImageD(imageF.getBBox()) 

60 s.evaluate().addToImage(imageD) 

61 imageF += imageD.convertF() 

62 

63 

64def scaleExposure(exposure, factor): 

65 mi = exposure.getMaskedImage() 

66 mi *= factor 

67 

68 

69class UnitTransformedLikelihoodTestCase(lsst.utils.tests.TestCase): 

70 

71 def setUp(self): 

72 numpy.random.seed(500) 

73 self.position = lsst.geom.SpherePoint(45.0, 45.0, lsst.geom.degrees) 

74 self.model = lsst.meas.modelfit.Model.makeGaussian(lsst.meas.modelfit.Model.FIXED_CENTER) 

75 self.ellipse = lsst.afw.geom.ellipses.Ellipse(lsst.afw.geom.ellipses.Axes(6.0, 5.0, numpy.pi/6)) 

76 self.flux = 50.0 

77 ev = self.model.makeEllipseVector() 

78 ev[0].setCore(self.ellipse.getCore()) 

79 ev[0].setCenter(self.ellipse.getCenter()) 

80 self.nonlinear = numpy.zeros(self.model.getNonlinearDim(), dtype=lsst.meas.modelfit.Scalar) 

81 self.fixed = numpy.zeros(self.model.getFixedDim(), dtype=lsst.meas.modelfit.Scalar) 

82 self.model.readEllipses(ev, self.nonlinear, self.fixed) 

83 self.amplitudes = numpy.zeros(self.model.getAmplitudeDim(), dtype=lsst.meas.modelfit.Scalar) 

84 self.amplitudes[:] = self.flux 

85 # setup ideal exposure0: uses fit Wcs and PhotoCalib, has delta function PSF 

86 scale0 = 0.2*lsst.geom.arcseconds 

87 self.crpix0 = lsst.geom.Point2D(0, 0) 

88 wcs0 = lsst.afw.geom.makeSkyWcs(crpix=self.crpix0, 

89 crval=self.position, 

90 cdMatrix=lsst.afw.geom.makeCdMatrix(scale=scale0)) 

91 photoCalib0 = lsst.afw.image.PhotoCalib(10) 

92 self.psf0 = makeGaussianFunction(0.0) 

93 self.bbox0 = lsst.geom.Box2I(lsst.geom.Point2I(-100, -100), lsst.geom.Point2I(100, 100)) 

94 self.spanSet0 = lsst.afw.geom.SpanSet(self.bbox0) 

95 self.footprint0 = lsst.afw.detection.Footprint(self.spanSet0) 

96 self.exposure0 = lsst.afw.image.ExposureF(self.bbox0) 

97 self.exposure0.setWcs(wcs0) 

98 self.exposure0.setPhotoCalib(photoCalib0) 

99 self.sys0 = lsst.meas.modelfit.UnitSystem(self.exposure0) 

100 addGaussian(self.exposure0, self.ellipse, self.flux, psf=self.psf0) 

101 self.exposure0.getMaskedImage().getVariance().set(1.0) 

102 # setup secondary exposure: 2x pixel scale, 3x gain, Gaussian PSF with sigma=2.5pix 

103 scale1 = 0.4 * lsst.geom.arcseconds 

104 wcs1 = lsst.afw.geom.makeSkyWcs(crpix=lsst.geom.Point2D(), 

105 crval=self.position, 

106 cdMatrix=lsst.afw.geom.makeCdMatrix(scale=scale1)) 

107 photoCalib1 = lsst.afw.image.PhotoCalib(30) 

108 self.sys1 = lsst.meas.modelfit.UnitSystem(wcs1, photoCalib1) 

109 # transform object that maps between exposures (not including PSF) 

110 self.t01 = lsst.meas.modelfit.LocalUnitTransform(self.sys0.wcs.getPixelOrigin(), self.sys0, self.sys1) 

111 self.bbox1 = lsst.geom.Box2I(self.bbox0) 

112 self.bbox1.grow(-60) 

113 self.spanSet1 = lsst.afw.geom.SpanSet(self.bbox1) 

114 self.footprint1 = lsst.afw.detection.Footprint(self.spanSet1) 

115 self.psfSigma1 = 2.5 

116 self.psf1 = makeGaussianFunction(self.psfSigma1) 

117 

118 def tearDown(self): 

119 del self.position 

120 del self.model 

121 del self.ellipse 

122 del self.bbox0 

123 del self.spanSet0 

124 del self.footprint0 

125 del self.exposure0 

126 del self.sys0 

127 del self.sys1 

128 del self.t01 

129 del self.bbox1 

130 del self.spanSet1 

131 del self.footprint1 

132 del self.psf1 

133 

134 def checkLikelihood(self, likelihood, data, rtol=1E-6, atol=None): 

135 # default for rtol is 1E-6 and atol = 1E-7 for the second check 

136 # this allow to override for a specific testcase 

137 extra_flag1 = dict() 

138 extra_flag2 = dict() 

139 if atol is None: 

140 extra_flag2['atol'] = 1E-7 

141 else: 

142 extra_flag1['atol'] = atol 

143 extra_flag2['atol'] = atol 

144 self.assertFloatsAlmostEqual(likelihood.getData().reshape(data.shape), data, rtol, **extra_flag1, 

145 **ASSERT_CLOSE_KWDS) 

146 matrix = numpy.zeros((1, likelihood.getDataDim()), dtype=lsst.meas.modelfit.Pixel).transpose() 

147 likelihood.computeModelMatrix(matrix, self.nonlinear) 

148 model = numpy.dot(matrix, self.amplitudes) 

149 self.assertFloatsAlmostEqual(model.reshape(data.shape), data, rtol, **extra_flag2, 

150 **ASSERT_CLOSE_KWDS) 

151 

152 def testModel(self): 

153 """Test that when we use a Model to create a MultiShapeletFunction from our parameter vectors 

154 it agrees with the reimplementation here.""" 

155 msf = self.model.makeShapeletFunction(self.nonlinear, self.amplitudes, self.fixed) 

156 image0a = lsst.afw.image.ImageD(self.bbox0) 

157 msf.evaluate().addToImage(image0a) 

158 self.assertFloatsAlmostEqual(image0a.getArray(), 

159 self.exposure0.getMaskedImage().getImage().getArray(), 

160 rtol=1E-6, atol=1E-7, **ASSERT_CLOSE_KWDS) 

161 

162 def testWarp(self): 

163 """Test that transforming ellipses and fluxes with LocalUnitTransform agrees with warping 

164 """ 

165 warpCtrl = lsst.afw.math.WarpingControl("lanczos5") 

166 # exposure1: check image; just a transform and scaling of exposure0 (no PSF...yet) 

167 exposure1 = lsst.afw.image.ExposureF(self.bbox1) 

168 addGaussian(exposure1, self.ellipse.transform(self.t01.geometric), self.flux * self.t01.flux) 

169 # exposure1a: warp exposure0 using warpExposure with WCS arguments 

170 exposure1a = lsst.afw.image.ExposureF(self.bbox1) 

171 exposure1a.setWcs(self.sys1.wcs) 

172 lsst.afw.math.warpExposure(exposure1a, self.exposure0, warpCtrl) 

173 exposure1a.setPhotoCalib(self.sys1.photoCalib) 

174 scaleExposure(exposure1a, self.t01.flux) 

175 self.assertFloatsAlmostEqual(exposure1.getMaskedImage().getImage().getArray(), 

176 exposure1a.getMaskedImage().getImage().getArray(), 

177 rtol=1E-6, **ASSERT_CLOSE_KWDS) 

178 # exposure1b: warp exposure0 using warpImage with AffineTransform arguments 

179 exposure1b = lsst.afw.image.ExposureF(self.bbox1) 

180 exposure1b.setWcs(self.sys1.wcs) 

181 srcToDest = lsst.afw.geom.makeTransform(self.t01.geometric) 

182 lsst.afw.math.warpImage(exposure1b.getMaskedImage(), self.exposure0.getMaskedImage(), 

183 srcToDest, warpCtrl) 

184 exposure1b.setPhotoCalib(self.sys1.photoCalib) 

185 scaleExposure(exposure1b, self.t01.flux) 

186 self.assertFloatsAlmostEqual(exposure1.getMaskedImage().getImage().getArray(), 

187 exposure1b.getMaskedImage().getImage().getArray(), 

188 rtol=1E-6, **ASSERT_CLOSE_KWDS) 

189 # now we rebuild exposure1 with the PSF convolution included, and convolve 1a->1c using an 

190 # afw::math::Kernel. Since 1a is the same as 1b, there's no need to convolve 1b too. 

191 exposure1 = lsst.afw.image.ExposureF(self.bbox1) 

192 addGaussian(exposure1, self.ellipse.transform(self.t01.geometric), self.flux * self.t01.flux, 

193 psf=self.psf1) 

194 kernel = lsst.afw.math.AnalyticKernel( 

195 int(self.psfSigma1*16)+1, int(self.psfSigma1*16)+1, 

196 lsst.afw.math.GaussianFunction2D(self.psfSigma1, self.psfSigma1) 

197 ) 

198 exposure1c = lsst.afw.image.ExposureF(self.bbox1) 

199 ctrl = lsst.afw.math.ConvolutionControl() 

200 ctrl.setDoCopyEdge(True) 

201 lsst.afw.math.convolve(exposure1c.getMaskedImage(), exposure1a.getMaskedImage(), kernel, ctrl) 

202 self.assertFloatsAlmostEqual(exposure1.getMaskedImage().getImage().getArray(), 

203 exposure1c.getMaskedImage().getImage().getArray(), 

204 rtol=1E-5, atol=1E-6, **ASSERT_CLOSE_KWDS) 

205 

206 def testDirect(self): 

207 """Test likelihood evaluation when the fit system is the same as the data system. 

208 """ 

209 ctrl = lsst.meas.modelfit.UnitTransformedLikelihoodControl() 

210 var = numpy.random.rand(self.bbox0.getHeight(), self.bbox0.getWidth()) + 2.0 

211 self.exposure0.getMaskedImage().getVariance().getArray()[:, :] = var 

212 efv = [lsst.meas.modelfit.EpochFootprint(self.footprint0, self.exposure0, self.psf0)] 

213 # test with per-pixel weights, using both ctors 

214 ctrl.usePixelWeights = True 

215 data = self.exposure0.getMaskedImage().getImage().getArray() / var**0.5 

216 l0a = lsst.meas.modelfit.UnitTransformedLikelihood(self.model, self.fixed, self.sys0, self.position, 

217 self.exposure0, self.footprint0, self.psf0, ctrl) 

218 self.checkLikelihood(l0a, data) 

219 l0b = lsst.meas.modelfit.UnitTransformedLikelihood(self.model, self.fixed, self.sys0, self.position, 

220 efv, ctrl) 

221 self.checkLikelihood(l0b, data) 

222 # test with constant weights, using both ctors 

223 ctrl.usePixelWeights = False 

224 # For the usePixelWeights = False relax checkLikeihood to pass on osx-arm64. 

225 # Code path uses single-precision exp/log/sum within the c++ code, 

226 # which can lead to differences on different CPU architecutres 

227 data = self.exposure0.getMaskedImage().getImage().getArray() 

228 l0c = lsst.meas.modelfit.UnitTransformedLikelihood(self.model, self.fixed, self.sys0, self.position, 

229 self.exposure0, self.footprint0, self.psf0, ctrl) 

230 weights = numpy.exp((-0.5*numpy.log(l0c.getVariance())/l0c.getDataDim()).sum()) 

231 self.checkLikelihood(l0c, data*weights, atol=1E-6) 

232 l0d = lsst.meas.modelfit.UnitTransformedLikelihood(self.model, self.fixed, self.sys0, self.position, 

233 efv, ctrl) 

234 self.checkLikelihood(l0d, data*weights, atol=1E-6) 

235 

236 def testProjected(self): 

237 """Test likelihood evaluation when the fit system is not the same as the data system. 

238 """ 

239 # Start by building the data exposure 

240 exposure1 = lsst.afw.image.ExposureF(self.bbox1) 

241 addGaussian(exposure1, self.ellipse.transform(self.t01.geometric), self.flux * self.t01.flux, 

242 psf=self.psf1) 

243 exposure1.setWcs(self.sys1.wcs) 

244 exposure1.setPhotoCalib(self.sys1.photoCalib) 

245 var = numpy.random.rand(self.bbox1.getHeight(), self.bbox1.getWidth()) + 2.0 

246 exposure1.getMaskedImage().getVariance().getArray()[:, :] = var 

247 ctrl = lsst.meas.modelfit.UnitTransformedLikelihoodControl() 

248 efv = [lsst.meas.modelfit.EpochFootprint(self.footprint1, exposure1, self.psf1)] 

249 # test with per-pixel weights, using both ctors 

250 ctrl.usePixelWeights = True 

251 data = exposure1.getMaskedImage().getImage().getArray() / var**0.5 

252 l1a = lsst.meas.modelfit.UnitTransformedLikelihood(self.model, self.fixed, self.sys0, self.position, 

253 exposure1, self.footprint1, self.psf1, ctrl) 

254 self.checkLikelihood(l1a, data) 

255 l1b = lsst.meas.modelfit.UnitTransformedLikelihood(self.model, self.fixed, self.sys0, self.position, 

256 efv, ctrl) 

257 self.checkLikelihood(l1b, data) 

258 # test with constant weights, using both ctors 

259 ctrl.usePixelWeights = False 

260 data = exposure1.getMaskedImage().getImage().getArray() 

261 l1c = lsst.meas.modelfit.UnitTransformedLikelihood(self.model, self.fixed, self.sys0, self.position, 

262 exposure1, self.footprint1, self.psf1, ctrl) 

263 weights = numpy.exp((-0.5*numpy.log(l1c.getVariance())/l1c.getDataDim()).sum()) 

264 self.checkLikelihood(l1c, data*weights) 

265 l1d = lsst.meas.modelfit.UnitTransformedLikelihood(self.model, self.fixed, self.sys0, self.position, 

266 efv, ctrl) 

267 self.checkLikelihood(l1d, data*weights) 

268 

269 

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

271 pass 

272 

273 

274def setup_module(module): 

275 lsst.utils.tests.init() 

276 

277 

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

279 lsst.utils.tests.init() 

280 unittest.main()