Coverage for tests/test_psfFitter.py: 14%
173 statements
« prev ^ index » next coverage.py v7.3.3, created at 2023-12-15 12:36 +0000
« prev ^ index » next coverage.py v7.3.3, created at 2023-12-15 12:36 +0000
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 os
25import glob
26import numpy
28import lsst.utils.tests
29import lsst.shapelet
30import lsst.geom
31import lsst.log
32import lsst.utils.logging
33import lsst.meas.modelfit
34import lsst.meas.base
36numpy.random.seed(500)
38# Set trace to 0-5 to view debug messages. Level 5 enables all traces.
39lsst.utils.logging.trace_set_at("lsst.meas.modelfit.optimizer.Optimizer", -1)
40lsst.utils.logging.trace_set_at("lsst.meas.modelfit.optimizer.solveTrustRegion", -1)
42ELLIPSE_PARAMETER_NAMES = ["eta1", "eta2", "logR", "x", "y"]
43DATA_DIR = os.path.join(os.environ["MEAS_MODELFIT_DIR"], "tests", "data")
46def computeMoments(image):
47 """Helper function to compute moments of a postage stamp about its origin."""
48 maskedImage = lsst.afw.image.MaskedImageD(image)
49 result = lsst.meas.base.SdssShapeAlgorithm.computeAdaptiveMoments(
50 maskedImage,
51 lsst.geom.Point2D(0.0, 0.0)
52 )
53 return result.getShape()
56class GeneralPsfFitterTestCase(lsst.utils.tests.TestCase):
58 def setUp(self):
59 self.configs = {}
60 self.configs['fixed'] = lsst.meas.modelfit.GeneralPsfFitterConfig()
61 self.configs['fixed'].primary.ellipticityPriorSigma = 0.0
62 self.configs['fixed'].primary.radiusPriorSigma = 0.0
63 self.configs['fixed'].primary.positionPriorSigma = 0.0
64 self.configs['fixed'].wings.ellipticityPriorSigma = 0.0
65 self.configs['fixed'].wings.radiusPriorSigma = 0.0
66 self.configs['fixed'].wings.positionPriorSigma = 0.0
67 self.configs['ellipse'] = lsst.meas.modelfit.GeneralPsfFitterConfig()
68 self.configs['ellipse'].primary.positionPriorSigma = 0.0
69 self.configs['ellipse'].wings.positionPriorSigma = 0.0
70 self.configs['full'] = lsst.meas.modelfit.GeneralPsfFitterConfig()
71 self.configs['full'].inner.order = 0
72 self.configs['full'].primary.order = 4
73 self.configs['full'].wings.order = 4
74 self.configs['full'].outer.order = 0
75 self.configs['full'].inner.ellipticityPriorSigma = 0.3
76 self.configs['full'].inner.radiusPriorSigma = 0.5
77 self.configs['full'].inner.positionPriorSigma = 0.1
78 self.configs['full'].primary.ellipticityPriorSigma = 0.3
79 self.configs['full'].primary.radiusPriorSigma = 0.5
80 self.configs['full'].primary.positionPriorSigma = 0.1
81 self.configs['full'].wings.ellipticityPriorSigma = 0.3
82 self.configs['full'].wings.radiusPriorSigma = 0.5
83 self.configs['full'].wings.positionPriorSigma = 0.1
84 self.configs['full'].outer.ellipticityPriorSigma = 0.3
85 self.configs['full'].outer.radiusPriorSigma = 0.5
86 self.configs['full'].outer.positionPriorSigma = 0.1
88 def tearDown(self):
89 del self.configs
91 def testFixedModel(self):
92 fitter = lsst.meas.modelfit.GeneralPsfFitter(self.configs['fixed'].makeControl())
93 model = fitter.getModel()
95 # check that we have the right numbers and names for parameters
96 self.assertEqual(model.getNonlinearDim(), 0)
97 self.assertEqual(model.getFixedDim(), 10)
98 self.assertEqual(model.getAmplitudeDim(), 2)
99 self.assertEqual(model.getBasisCount(), 2)
100 self.assertEqual(list(model.getNonlinearNames()), [])
101 self.assertEqual(list(model.getAmplitudeNames()), ["primary.alpha[0,0]", "wings.alpha[0,0]"])
102 self.assertEqual(list(model.getFixedNames()),
103 [f"primary.fiducial.{s}" for s in ELLIPSE_PARAMETER_NAMES]
104 + [f"wings.fiducial.{s}" for s in ELLIPSE_PARAMETER_NAMES])
106 # test that we can round-trip ellipses through the model, and that this agrees
107 # with makeShapeletFunction
108 ellipseParameters = numpy.array([[0.01, -0.01, 1.1, 0.03, -0.04],
109 [0.02, -0.02, 0.9, 0.05, -0.06]])
110 ellipses1 = model.makeEllipseVector()
111 for i in range(len(ellipses1)):
112 ellipses1[i].setParameterVector(ellipseParameters[i])
113 nonlinear = numpy.zeros(model.getNonlinearDim(), dtype=lsst.meas.modelfit.Scalar)
114 fixed = numpy.zeros(model.getFixedDim(), dtype=lsst.meas.modelfit.Scalar)
115 amplitudes = numpy.array([1.0, 0.1], dtype=lsst.meas.modelfit.Scalar)
116 model.readEllipses(ellipses1, nonlinear, fixed)
117 self.assertFloatsAlmostEqual(fixed, ellipseParameters.ravel())
118 ellipses2 = model.writeEllipses(nonlinear, fixed)
119 msf = model.makeShapeletFunction(nonlinear, amplitudes, fixed)
120 self.assertFloatsAlmostEqual(len(msf.getComponents()), len(ellipses1))
121 ellipses3 = model.makeEllipseVector()
122 for i in range(len(ellipses2)):
123 self.assertFloatsAlmostEqual(ellipses1[i].getParameterVector(), ellipses2[i].getParameterVector())
124 # need to convert ellipse parametrization
125 ellipses3[i].setCore(msf.getComponents()[i].getEllipse().getCore())
126 ellipses3[i].setCenter(msf.getComponents()[i].getEllipse().getCenter())
127 self.assertFloatsAlmostEqual(ellipses1[i].getParameterVector(), ellipses3[i].getParameterVector())
128 self.assertFloatsAlmostEqual(amplitudes[i:i+1], msf.getComponents()[i].getCoefficients())
130 def testEllipseModel(self):
131 fitter = lsst.meas.modelfit.GeneralPsfFitter(self.configs['ellipse'].makeControl())
132 model = fitter.getModel()
134 # check that we have the right numbers and names for parameters
135 self.assertEqual(model.getNonlinearDim(), 6)
136 self.assertEqual(model.getFixedDim(), 10)
137 self.assertEqual(model.getAmplitudeDim(), 2)
138 self.assertEqual(model.getBasisCount(), 2)
139 self.assertEqual(list(model.getNonlinearNames()),
140 [f"primary.{s}" for s in ELLIPSE_PARAMETER_NAMES[:3]]
141 + [f"wings.{s}" for s in ELLIPSE_PARAMETER_NAMES[:3]]
142 )
143 self.assertEqual(list(model.getAmplitudeNames()), ["primary.alpha[0,0]", "wings.alpha[0,0]"])
144 self.assertEqual(list(model.getFixedNames()),
145 [f"primary.fiducial.{s}" for s in ELLIPSE_PARAMETER_NAMES]
146 + [f"wings.fiducial.{s}" for s in ELLIPSE_PARAMETER_NAMES])
148 # test that we can round-trip ellipses through the model, and that this agrees
149 # with makeShapeletFunction
150 ellipseParameters = numpy.array([[0.01, -0.01, 1.1, 0.03, -0.04],
151 [0.02, -0.02, 0.9, 0.05, -0.06]])
152 ellipses1 = model.makeEllipseVector()
153 for i in range(len(ellipses1)):
154 ellipses1[i].setParameterVector(ellipseParameters[i])
155 nonlinear = numpy.zeros(model.getNonlinearDim(), dtype=lsst.meas.modelfit.Scalar)
156 fixed = numpy.zeros(model.getFixedDim(), dtype=lsst.meas.modelfit.Scalar)
157 amplitudes = numpy.array([1.0, 0.1], dtype=lsst.meas.modelfit.Scalar)
158 model.readEllipses(ellipses1, nonlinear, fixed)
159 self.assertFloatsAlmostEqual(nonlinear, numpy.zeros(model.getNonlinearDim(),
160 dtype=lsst.meas.modelfit.Scalar))
161 self.assertFloatsAlmostEqual(fixed, ellipseParameters.ravel())
162 ellipses2 = model.writeEllipses(nonlinear, fixed)
163 msf = model.makeShapeletFunction(nonlinear, amplitudes, fixed)
164 self.assertEqual(len(msf.getComponents()), len(ellipses1))
165 ellipses3 = model.makeEllipseVector()
166 for i in range(len(ellipses2)):
167 self.assertFloatsAlmostEqual(ellipses1[i].getParameterVector(), ellipses2[i].getParameterVector(),
168 rtol=1E-8)
169 # need to convert ellipse parametrization
170 ellipses3[i].setCore(msf.getComponents()[i].getEllipse().getCore())
171 ellipses3[i].setCenter(msf.getComponents()[i].getEllipse().getCenter())
172 self.assertFloatsAlmostEqual(ellipses1[i].getParameterVector(), ellipses3[i].getParameterVector(),
173 rtol=1E-8)
174 self.assertFloatsAlmostEqual(amplitudes[i:i+1], msf.getComponents()[i].getCoefficients(),
175 rtol=1E-8)
177 # test the ellipse round-tripping again, this time starting with nonzero nonlinear parameters:
178 # this will be read back in by adding to the fixed parameters and zeroing the nonlinear parameters.
179 nonlinear[:] = 0.5*ellipseParameters[:, :3].ravel()
180 ellipses4 = model.writeEllipses(nonlinear, fixed)
181 model.readEllipses(ellipses4, nonlinear, fixed)
182 self.assertFloatsAlmostEqual(nonlinear, numpy.zeros(model.getNonlinearDim(),
183 dtype=lsst.meas.modelfit.Scalar),
184 rtol=1E-8)
185 self.assertFloatsAlmostEqual(fixed.reshape(2, 5)[:, :3], 1.5*ellipseParameters[:, :3], rtol=1E-8)
186 self.assertFloatsAlmostEqual(fixed.reshape(2, 5)[:, 3:], ellipseParameters[:, 3:], rtol=1E-8)
188 def testFullModel(self):
189 fitter = lsst.meas.modelfit.GeneralPsfFitter(self.configs['full'].makeControl())
190 model = fitter.getModel()
192 # check that we have the right numbers and names for parameters
193 self.assertEqual(model.getNonlinearDim(), 20)
194 self.assertEqual(model.getFixedDim(), 20)
195 self.assertEqual(model.getAmplitudeDim(), 2*(1 + lsst.shapelet.computeSize(4)))
196 self.assertEqual(model.getBasisCount(), 4)
197 self.assertEqual(list(model.getNonlinearNames()),
198 [f"inner.{s}" for s in ELLIPSE_PARAMETER_NAMES]
199 + [f"primary.{s}" for s in ELLIPSE_PARAMETER_NAMES]
200 + [f"wings.{s}" for s in ELLIPSE_PARAMETER_NAMES]
201 + [f"outer.{s}" for s in ELLIPSE_PARAMETER_NAMES]
202 )
203 self.assertEqual(list(model.getAmplitudeNames()),
204 ["inner.alpha[0,0]"]
205 + [f"primary.alpha[{x},{y}]"
206 for n, x, y in lsst.shapelet.HermiteIndexGenerator(4)]
207 + [f"wings.alpha[{x},{y}]"
208 for n, x, y in lsst.shapelet.HermiteIndexGenerator(4)]
209 + ["outer.alpha[0,0]"])
210 self.assertEqual(list(model.getFixedNames()),
211 [f"inner.fiducial.{s}" for s in ELLIPSE_PARAMETER_NAMES]
212 + [f"primary.fiducial.{s}" for s in ELLIPSE_PARAMETER_NAMES]
213 + [f"wings.fiducial.{s}" for s in ELLIPSE_PARAMETER_NAMES]
214 + [f"outer.fiducial.{s}" for s in ELLIPSE_PARAMETER_NAMES]
215 )
217 # test that we can round-trip ellipses through the model, and that this agrees
218 # with makeShapeletFunction
219 ellipseParameters = numpy.array([[0.01, -0.01, 1.1, 0.03, -0.04],
220 [0.015, -0.015, 1.0, 0.04, -0.05],
221 [0.02, -0.02, 0.9, 0.05, -0.06],
222 [0.025, -0.025, 0.8, 0.06, -0.07],
223 ])
224 ellipses1 = model.makeEllipseVector()
225 for i in range(len(ellipses1)):
226 ellipses1[i].setParameterVector(ellipseParameters[i])
227 nonlinear = numpy.zeros(model.getNonlinearDim(), dtype=lsst.meas.modelfit.Scalar)
228 fixed = numpy.zeros(model.getFixedDim(), dtype=lsst.meas.modelfit.Scalar)
229 amplitudes = numpy.random.randn(model.getAmplitudeDim())
230 model.readEllipses(ellipses1, nonlinear, fixed)
231 self.assertFloatsAlmostEqual(nonlinear, numpy.zeros(model.getNonlinearDim(),
232 dtype=lsst.meas.modelfit.Scalar))
233 self.assertFloatsAlmostEqual(fixed, ellipseParameters.ravel())
234 ellipses2 = model.writeEllipses(nonlinear, fixed)
235 msf = model.makeShapeletFunction(nonlinear, amplitudes, fixed)
236 self.assertFloatsAlmostEqual(len(msf.getComponents()), len(ellipses1))
237 ellipses3 = model.makeEllipseVector()
238 amplitudeOffset = 0
239 for i in range(len(ellipses2)):
240 self.assertFloatsAlmostEqual(ellipses1[i].getParameterVector(), ellipses2[i].getParameterVector(),
241 rtol=1E-8)
242 # need to convert ellipse parametrization
243 ellipses3[i].setCore(msf.getComponents()[i].getEllipse().getCore())
244 ellipses3[i].setCenter(msf.getComponents()[i].getEllipse().getCenter())
245 amplitudeCount = len(msf.getComponents()[i].getCoefficients())
246 self.assertFloatsAlmostEqual(ellipses1[i].getParameterVector(), ellipses3[i].getParameterVector(),
247 rtol=1E-8)
248 self.assertFloatsAlmostEqual(amplitudes[amplitudeOffset:amplitudeOffset+amplitudeCount],
249 msf.getComponents()[i].getCoefficients(), rtol=1E-8)
250 amplitudeOffset += amplitudeCount
252 # test the ellipse round-tripping again, this time starting with nonzero nonlinear parameters:
253 # this will be read back in by adding to the fixed parameters and zeroing the nonlinear parameters.
254 nonlinear[:] = 0.5*ellipseParameters.ravel()
255 ellipses4 = model.writeEllipses(nonlinear, fixed)
256 model.readEllipses(ellipses4, nonlinear, fixed)
257 self.assertFloatsAlmostEqual(nonlinear, numpy.zeros(model.getNonlinearDim(),
258 dtype=lsst.meas.modelfit.Scalar))
259 self.assertFloatsAlmostEqual(fixed, 1.5*ellipseParameters.ravel())
261 def testApply(self):
262 tolerances = {"full": 3E-4, "ellipse": 8E-3, "fixed": 1E-2}
263 for filename in glob.glob(os.path.join(DATA_DIR, "psfs", "great3*.fits")):
264 kernelImage = lsst.afw.image.ImageD(filename)
265 shape = computeMoments(kernelImage)
266 for configKey in ["full", "ellipse", "fixed"]:
267 fitter = lsst.meas.modelfit.GeneralPsfFitter(self.configs[configKey].makeControl())
268 multiShapeletFit = fitter.apply(kernelImage, shape, 0.01)
269 modelImage = lsst.afw.image.ImageD(kernelImage.getBBox(lsst.afw.image.PARENT))
270 multiShapeletFit.evaluate().addToImage(modelImage)
271 self.assertFloatsAlmostEqual(kernelImage.getArray(), modelImage.getArray(),
272 atol=tolerances[configKey],
273 plotOnFailure=True)
276class TestMemory(lsst.utils.tests.MemoryTestCase):
277 pass
280def setup_module(module):
281 lsst.utils.tests.init()
284if __name__ == "__main__": 284 ↛ 285line 284 didn't jump to line 285, because the condition on line 284 was never true
285 lsst.utils.tests.init()
286 unittest.main()