Coverage for python/lsst/meas/modelfit/psf/psfContinued.py: 35%

108 statements  

« prev     ^ index     » next       coverage.py v6.4.2, created at 2022-07-20 03:25 -0700

1#!/usr/bin/env python 

2# 

3# LSST Data Management System 

4# Copyright 2008-2014 LSST Corporation. 

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 <http://www.lsstcorp.org/LegalNotices/>. 

22# 

23 

24# all new classes here are accessed via registries, not direct imports. 

25__all__ = ( 

26 "GeneralPsfFitterComponentConfig", 

27 "GeneralPsfFitterConfig" 

28) 

29 

30import lsst.pex.config 

31import lsst.meas.base 

32from .psf import ( 

33 GeneralPsfFitterControl, GeneralPsfFitterComponentControl, 

34 GeneralPsfFitter, GeneralPsfFitterAlgorithm, 

35 DoubleShapeletPsfApproxAlgorithm, DoubleShapeletPsfApproxControl 

36) 

37 

38 

39lsst.meas.base.wrapSimpleAlgorithm( 

40 DoubleShapeletPsfApproxAlgorithm, 

41 Control=DoubleShapeletPsfApproxControl, 

42 module='lsst.meas.modelfit', 

43 name='modelfit_DoubleShapeletPsfApprox', 

44 executionOrder=lsst.meas.base.BasePlugin.SHAPE_ORDER 

45) 

46 

47 

48GeneralPsfFitterComponentConfig = lsst.pex.config.makeConfigClass( 

49 GeneralPsfFitterComponentControl, 

50 module='lsst.meas.modelfit' 

51) 

52GeneralPsfFitterConfig = lsst.pex.config.makeConfigClass( 

53 GeneralPsfFitterControl, 

54 module='lsst.meas.modelfit' 

55) 

56GeneralPsfFitter.ConfigClass = GeneralPsfFitterConfig 

57 

58 

59class GeneralShapeletPsfApproxConfig(lsst.pex.config.Config): 

60 models = lsst.pex.config.ConfigDictField( 

61 keytype=str, 

62 itemtype=GeneralPsfFitterConfig, 

63 doc="a dictionary of models that can be used to fit the PSF", 

64 default={} # populated in setDefaults; can't do it on a single line 

65 ) 

66 sequence = lsst.pex.config.ListField( 

67 dtype=str, 

68 doc=("a sequence of model names indicating which models should be fit," 

69 " and their order"), 

70 default=["DoubleShapelet"] 

71 ) 

72 

73 def setDefaults(self): 

74 super(GeneralShapeletPsfApproxConfig, self).setDefaults() 

75 self.models["SingleGaussian"] = GeneralPsfFitterConfig() 

76 self.models["SingleGaussian"].inner.order = -1 

77 self.models["SingleGaussian"].primary.order = 0 

78 self.models["SingleGaussian"].wings.order = -1 

79 self.models["SingleGaussian"].outer.order = -1 

80 self.models["DoubleGaussian"] = GeneralPsfFitterConfig() 

81 self.models["DoubleGaussian"].inner.order = -1 

82 self.models["DoubleGaussian"].primary.order = 0 

83 self.models["DoubleGaussian"].wings.order = 0 

84 self.models["DoubleGaussian"].outer.order = -1 

85 self.models["DoubleShapelet"] = GeneralPsfFitterConfig() 

86 self.models["DoubleShapelet"].inner.order = -1 

87 self.models["DoubleShapelet"].primary.order = 2 

88 self.models["DoubleShapelet"].wings.order = 1 

89 self.models["DoubleShapelet"].outer.order = -1 

90 self.models["Full"] = GeneralPsfFitterConfig() 

91 self.models["Full"].inner.order = 0 

92 self.models["Full"].primary.order = 4 

93 self.models["Full"].wings.order = 4 

94 self.models["Full"].outer.order = 0 

95 

96 def validate(self): 

97 super(GeneralShapeletPsfApproxConfig, self).validate() 

98 if len(self.sequence) < 1: 

99 raise ValueError("sequence must have at least one element") 

100 for m in self.sequence: 

101 if m not in self.models: 

102 raise KeyError( 

103 "All elements in sequence must be keys in models dict" 

104 ) 

105 

106 

107class GeneralShapeletPsfApproxMixin: 

108 """Mixin base class for fitting shapelet approximations to the PSF model 

109 

110 This class does almost all of the work for its two derived classes, 

111 GeneralShapeletPsfApproxSingleFramePlugin and 

112 GeneralShapeletPsfApproxForcedPlugin, which simply adapt it to the 

113 slightly different interfaces for single-frame and forced measurement. It 

114 in turn delegates its work to the C++ GeneralPsfFitter class; it holds 

115 sequence of these corresponding to different models (generally with 

116 increasing complexity). Each GeneralPsfFitter starts with the result of 

117 the previous one as an input, using GeneralPsfFitter::adapt to hopefully 

118 allow these previous fits to reduce the time spent on the next one. 

119 

120 At present, this plugin does not define any failure flags, which will 

121 almost certainly have to be changed in the future. So far, however, I 

122 haven't actually seen it fail on any PSFs I've given it, so I'll wait 

123 until we can run on large enough data volumes to see what the actual 

124 failure modes are, instead of trying to guess them in advance. 

125 """ 

126 

127 def __init__(self, config, name, schema): 

128 """Initialize the plugin, creating a sequence of GeneralPsfFitter 

129 instances to do the fitting and MultiShapeletFunctionKey instances to 

130 save the results to a record. 

131 """ 

132 self.sequence = [] 

133 for m in config.sequence: 

134 fitter = GeneralPsfFitterAlgorithm( 

135 config.models[m].makeControl(), 

136 schema, 

137 schema[name][m].getPrefix() 

138 ) 

139 self.sequence.append((fitter, schema[name][m].getPrefix())) 

140 

141 def measure(self, measRecord, exposure): 

142 """Fit the configured sequence of models the given Exposure's Psf, as 

143 evaluated at measRecord.getCentroid(), then save the results to 

144 measRecord. 

145 """ 

146 if not exposure.hasPsf(): 

147 raise lsst.meas.base.FatalAlgorithmError( 

148 "GeneralShapeletPsfApprox requires Exposure to have a Psf") 

149 psf = exposure.getPsf() 

150 psfImage = psf.computeKernelImage(measRecord.getCentroid()) 

151 psfShape = psf.computeShape(measRecord.getCentroid()) 

152 lastError = None 

153 lastModel = None 

154 # Fit the first element in the sequence, using the PSFs moments to 

155 # initialize the parameters For every other element in the fitting 

156 # sequence, use the previous fit to initialize the parameters 

157 lastResult = None 

158 for fitter, name in self.sequence: 

159 try: 

160 if lastModel is None: 

161 fitter.measure(measRecord, psfImage, psfShape) 

162 else: 

163 fitter.measure(measRecord, psfImage, 

164 fitter.adapt(lastResult, lastModel)) 

165 lastResult = measRecord.get(fitter.getKey()) 

166 lastModel = fitter.getModel() 

167 except lsst.meas.base.baseMeasurement.FATAL_EXCEPTIONS: 

168 raise 

169 except lsst.meas.base.MeasurementError as error: 

170 fitter.fail(measRecord, error.cpp) 

171 lastError = error 

172 except Exception as error: 

173 fitter.fail(measRecord) 

174 lastError = error 

175 # When we are done with all the fitters, raise the last error if there 

176 # was one. This gives the calling task a chance to do whatever it 

177 # wants 

178 if lastError is not None: 

179 raise lastError 

180 

181 # This plugin doesn't need to set a flag on fail, because it should have 

182 # been done already by the individual fitters in the sequence 

183 def fail(self, measRecord, error=None): 

184 pass 

185 

186 

187class GeneralShapeletPsfApproxSingleFrameConfig( 

188 lsst.meas.base.SingleFramePluginConfig, 

189 GeneralShapeletPsfApproxConfig 

190): 

191 

192 def setDefaults(self): 

193 lsst.meas.base.SingleFramePluginConfig.setDefaults(self) 

194 GeneralShapeletPsfApproxConfig.setDefaults(self) 

195 

196 

197@lsst.meas.base.register("modelfit_GeneralShapeletPsfApprox") 

198class GeneralShapeletPsfApproxSingleFramePlugin( 

199 lsst.meas.base.SingleFramePlugin, 

200 GeneralShapeletPsfApproxMixin 

201): 

202 """Minimal subclass of GeneralShapeletPsfApproxMixin to conform to the 

203 single-frame measurement API. 

204 

205 This class simply provides __init__ and measure methods that matched the 

206 SingleFramePlugin signatures and delegate to the 

207 GeneralShapeletPsfApproxMixin's implementations. 

208 """ 

209 ConfigClass = GeneralShapeletPsfApproxSingleFrameConfig 

210 

211 @staticmethod 

212 def getExecutionOrder(): 

213 return 1.0 

214 

215 def __init__(self, config, name, schema, metadata): 

216 GeneralShapeletPsfApproxMixin.__init__(self, config, name, schema) 

217 lsst.meas.base.SingleFramePlugin.__init__(self, config, name, schema, 

218 metadata) 

219 

220 def measure(self, measRecord, exposure): 

221 GeneralShapeletPsfApproxMixin.measure(self, measRecord, exposure) 

222 

223 def fail(self, measRecord, error=None): 

224 GeneralShapeletPsfApproxMixin.fail(self, measRecord, error) 

225 

226 

227class GeneralShapeletPsfApproxForcedConfig( 

228 lsst.meas.base.ForcedPluginConfig, 

229 GeneralShapeletPsfApproxConfig 

230): 

231 

232 def setDefaults(self): 

233 lsst.meas.base.ForcedPluginConfig.setDefaults(self) 

234 GeneralShapeletPsfApproxConfig.setDefaults(self) 

235 

236 

237@lsst.meas.base.register("modelfit_GeneralShapeletPsfApprox") 

238class GeneralShapeletPsfApproxForcedPlugin( 

239 lsst.meas.base.ForcedPlugin, 

240 GeneralShapeletPsfApproxMixin 

241): 

242 """Minimal subclass of GeneralShapeletPsfApproxMixin to conform to the 

243 forced measurement API. 

244 

245 This class simply provides __init__ and measure methods that matched the 

246 ForcedPlugin signatures and delegate to the 

247 GeneralShapeletPsfApproxMixin's implementations. 

248 """ 

249 ConfigClass = GeneralShapeletPsfApproxForcedConfig 

250 

251 @staticmethod 

252 def getExecutionOrder(): 

253 return 1.0 

254 

255 def __init__(self, config, name, schemaMapper, metadata): 

256 GeneralShapeletPsfApproxMixin.__init__(self, config, name, 

257 schemaMapper.editOutputSchema()) 

258 lsst.meas.base.ForcedPlugin.__init__(self, config, name, schemaMapper, 

259 metadata) 

260 

261 def measure(self, measRecord, exposure, refRecord, refWcs): 

262 GeneralShapeletPsfApproxMixin.measure(self, measRecord, exposure) 

263 

264 def fail(self, measRecord, error=None): 

265 GeneralShapeletPsfApproxMixin.fail(self, measRecord, error)