Coverage for tests/test_projectedLikelihood.py: 15%
175 statements
« prev ^ index » next coverage.py v6.5.0, created at 2023-01-31 03:08 -0800
« prev ^ index » next coverage.py v6.5.0, created at 2023-01-31 03:08 -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
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
37ASSERT_CLOSE_KWDS = dict(plotOnFailure=False, printFailures=False)
40def makeGaussianFunction(ellipse, flux=1.0):
41 """Create a single-Gaussian MultiShapeletFunction
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
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()
64def scaleExposure(exposure, factor):
65 mi = exposure.getMaskedImage()
66 mi *= factor
69class UnitTransformedLikelihoodTestCase(lsst.utils.tests.TestCase):
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)
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
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)
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)
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)
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)
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)
270class TestMemory(lsst.utils.tests.MemoryTestCase):
271 pass
274def setup_module(module):
275 lsst.utils.tests.init()
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()