Coverage for tests/test_psfFitter.py: 14%

Shortcuts 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

173 statements  

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 

27 

28import lsst.utils.tests 

29import lsst.shapelet 

30import lsst.geom 

31import lsst.log 

32import lsst.log.utils 

33import lsst.meas.modelfit 

34import lsst.meas.base 

35 

36numpy.random.seed(500) 

37 

38# Set trace to 0-5 to view debug messages. Level 5 enables all traces. 

39lsst.log.utils.traceSetAt("meas.modelfit.optimizer.Optimizer", -1) 

40lsst.log.utils.traceSetAt("meas.modelfit.optimizer.solveTrustRegion", -1) 

41 

42ELLIPSE_PARAMETER_NAMES = ["eta1", "eta2", "logR", "x", "y"] 

43DATA_DIR = os.path.join(os.environ["MEAS_MODELFIT_DIR"], "tests", "data") 

44 

45 

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() 

54 

55 

56class GeneralPsfFitterTestCase(lsst.utils.tests.TestCase): 

57 

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 

87 

88 def tearDown(self): 

89 del self.configs 

90 

91 def testFixedModel(self): 

92 fitter = lsst.meas.modelfit.GeneralPsfFitter(self.configs['fixed'].makeControl()) 

93 model = fitter.getModel() 

94 

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]) 

105 

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()) 

129 

130 def testEllipseModel(self): 

131 fitter = lsst.meas.modelfit.GeneralPsfFitter(self.configs['ellipse'].makeControl()) 

132 model = fitter.getModel() 

133 

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]) 

147 

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) 

176 

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) 

187 

188 def testFullModel(self): 

189 fitter = lsst.meas.modelfit.GeneralPsfFitter(self.configs['full'].makeControl()) 

190 model = fitter.getModel() 

191 

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 ) 

216 

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 

251 

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()) 

260 

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) 

274 

275 

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

277 pass 

278 

279 

280def setup_module(module): 

281 lsst.utils.tests.init() 

282 

283 

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()