Coverage for python/lsst/meas/base/applyApCorr.py: 23%

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

123 statements  

1# This file is part of meas_base. 

2# 

3# Developed for the LSST Data Management System. 

4# This product includes software developed by the LSST Project 

5# (https://www.lsst.org). 

6# See the COPYRIGHT file at the top-level directory of this distribution 

7# for details of code ownership. 

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 GNU General Public License 

20# along with this program. If not, see <https://www.gnu.org/licenses/>. 

21 

22import math 

23import numpy as np 

24import time 

25 

26import lsst.pex.config 

27import lsst.pex.exceptions 

28import lsst.afw.image 

29import lsst.pipe.base 

30from .apCorrRegistry import getApCorrNameSet 

31 

32# If True then scale instFlux error by apCorr; if False then use a more complex computation 

33# that over-estimates instFlux error (often grossly so) because it double-counts photon noise. 

34# This flag is intended to be temporary until we figure out a better way to compute 

35# the effects of aperture correction on instFlux uncertainty 

36UseNaiveFluxErr = True 

37 

38__all__ = ("ApplyApCorrConfig", "ApplyApCorrTask") 

39 

40 

41class ApCorrInfo: 

42 """Catalog field names and keys needed to aperture correct a particular 

43 instrument flux. 

44 

45 Parameters 

46 ---------- 

47 schema : `lsst.afw.table` 

48 Source catalog schema. Three fields are used to generate keys: 

49 - ``{name}_instFlux`` 

50 - ``{name}_instFluxErr`` 

51 - ``{name}_flag`` 

52 Three fields are added: 

53 - ``{name}_apCorr`` (only if not already added by proxy) 

54 - ``{name}_apCorrErr`` (only if not already added by proxy) 

55 - ``{name}_flag_apCorr`` 

56 model : `str` 

57 Field name prefix for instFlux with aperture correction model, e.g. 

58 "base_PsfFlux" 

59 name : `str` 

60 Field name prefix for instFlux needing aperture correction; may be 

61 `None` if it is the same as ``model`` 

62 

63 Notes 

64 ----- 

65 The aperture correction can be derived from the meaasurements in the 

66 column being aperture-corrected or from measurements in a different 

67 column (a "proxy"). In the first case, we will add columns to contain 

68 the aperture correction values; in the second case (using a proxy), 

69 we will add an alias to the proxy's aperture correction values. In 

70 all cases, we add a flag. 

71 """ 

72 

73 name = None 

74 """Field name prefix for flux needing aperture correction (`str`). 

75 """ 

76 

77 modelName = None 

78 """Field name for aperture correction model for flux (`str`). 

79 """ 

80 

81 modelSigmaName = None 

82 """Field name for aperture correction model for fluxErr (`str`). 

83 """ 

84 

85 doApCorrColumn = None 

86 """Should we write the aperture correction values (`bool`)? 

87 

88 They should not be written if they're already being written by a proxy. 

89 """ 

90 

91 instFluxName = None 

92 """Name of ``instFlux`` field (`str`). 

93 """ 

94 

95 instFluxErrName = None 

96 """Name of ``instFlux`` sigma field (`str`). 

97 """ 

98 

99 instFluxKey = None 

100 """Key to ``instFlux`` field (`lsst.afw.table.schema.Key`). 

101 """ 

102 

103 instFluxErrKey = None 

104 """Key to ``instFlux`` sigma field (`lsst.afw.table.schema.Key`). 

105 """ 

106 

107 fluxFlagKey = None 

108 """Key to the flux flag field (`lsst.afw.table.schema.Key`). 

109 """ 

110 

111 apCorrKey = None 

112 """Key to new aperture correction field (`lsst.afw.table.schema.Key`). 

113 """ 

114 

115 apCorrErrKey = None 

116 """Key to new aperture correction sigma field (`lsst.afw.table.schema.Key`). 

117 """ 

118 

119 apCorrFlagKey = None 

120 """Key to new aperture correction flag field (`lsst.afw.table.schema.Key`). 

121 """ 

122 

123 def __init__(self, schema, model, name=None): 

124 if name is None: 

125 name = model 

126 self.name = name 

127 self.modelName = model + "_instFlux" 

128 self.modelSigmaName = model + "_instFluxErr" 

129 self.instFluxName = name + "_instFlux" 

130 self.instFluxErrName = name + "_instFluxErr" 

131 self.instFluxKey = schema.find(self.instFluxName).key 

132 self.instFluxErrKey = schema.find(self.instFluxErrName).key 

133 self.fluxFlagKey = schema.find(name + "_flag").key 

134 

135 # No need to write the same aperture corrections multiple times 

136 self.doApCorrColumn = (name == model or model + "_apCorr" not in schema) 

137 if self.doApCorrColumn: 

138 self.apCorrKey = schema.addField( 

139 name + "_apCorr", 

140 doc="aperture correction applied to %s" % (name,), 

141 type=np.float64, 

142 ) 

143 self.apCorrErrKey = schema.addField( 

144 name + "_apCorrErr", 

145 doc="standard deviation of aperture correction applied to %s" % (name,), 

146 type=np.float64, 

147 ) 

148 else: 

149 aliases = schema.getAliasMap() 

150 aliases.set(name + "_apCorr", model + "_apCorr") 

151 aliases.set(name + "_apCorrErr", model + "_apCorrErr") 

152 self.apCorrKey = schema.find(name + "_apCorr").key 

153 self.apCorrErrKey = schema.find(name + "_apCorrErr").key 

154 

155 self.apCorrFlagKey = schema.addField( 

156 name + "_flag_apCorr", 

157 doc="set if unable to aperture correct %s" % (name,), 

158 type="Flag", 

159 ) 

160 

161 

162class ApplyApCorrConfig(lsst.pex.config.Config): 

163 """Aperture correction configuration. 

164 """ 

165 

166 ignoreList = lsst.pex.config.ListField( 

167 doc="flux measurement algorithms in getApCorrNameSet() to ignore; " 

168 "if a name is listed that does not appear in getApCorrNameSet() then a warning is logged", 

169 dtype=str, 

170 optional=False, 

171 default=(), 

172 ) 

173 doFlagApCorrFailures = lsst.pex.config.Field( 

174 doc="set the general failure flag for a flux when it cannot be aperture-corrected?", 

175 dtype=bool, 

176 default=True, 

177 ) 

178 proxies = lsst.pex.config.DictField( 

179 doc="flux measurement algorithms to be aperture-corrected by reference to another algorithm; " 

180 "this is a mapping alg1:alg2, where 'alg1' is the algorithm being corrected, and 'alg2' " 

181 "is the algorithm supplying the corrections", 

182 keytype=str, 

183 itemtype=str, 

184 default={}, 

185 ) 

186 loggingInterval = lsst.pex.config.Field( 

187 doc="Interval (in seconds) to log messages (at VERBOSE level) while aperture correction is running", 

188 dtype=int, 

189 default=600, 

190 ) 

191 

192 

193class ApplyApCorrTask(lsst.pipe.base.Task): 

194 """Apply aperture corrections. 

195 

196 Parameters 

197 ---------- 

198 schema : `lsst.afw.table.Schema` 

199 """ 

200 ConfigClass = ApplyApCorrConfig 

201 _DefaultName = "applyApCorr" 

202 

203 def __init__(self, schema, **kwds): 

204 lsst.pipe.base.Task.__init__(self, **kwds) 

205 

206 self.apCorrInfoDict = dict() 

207 apCorrNameSet = getApCorrNameSet() 

208 ignoreSet = set(self.config.ignoreList) 

209 missingNameSet = ignoreSet - set(apCorrNameSet) 

210 if missingNameSet: 

211 self.log.warning("Fields in ignoreList that are not in fluxCorrectList: %s", 

212 sorted(missingNameSet)) 

213 for name in sorted(apCorrNameSet - ignoreSet): 

214 if name + "_instFlux" not in schema: 

215 # if a field in the registry is missing from the schema, silently ignore it. 

216 continue 

217 self.apCorrInfoDict[name] = ApCorrInfo(schema=schema, model=name) 

218 

219 for name, model in self.config.proxies.items(): 

220 if name in apCorrNameSet: 

221 # Already done or ignored 

222 continue 

223 if name + "_instFlux" not in schema: 

224 # Silently ignore 

225 continue 

226 self.apCorrInfoDict[name] = ApCorrInfo(schema=schema, model=model, name=name) 

227 

228 def run(self, catalog, apCorrMap): 

229 """Apply aperture corrections to a catalog of sources. 

230 

231 Parameters 

232 ---------- 

233 catalog : `lsst.afw.table.SourceCatalog` 

234 Catalog of sources. Will be updated in place. 

235 apCorrMap : `lsst.afw.image.ApCorrMap` 

236 Aperture correction map 

237 

238 Notes 

239 ----- 

240 If you show debug-level log messages then you will see statistics for 

241 the effects of aperture correction. 

242 """ 

243 self.log.info("Applying aperture corrections to %d instFlux fields", len(self.apCorrInfoDict)) 

244 if UseNaiveFluxErr: 

245 self.log.debug("Use naive instFlux sigma computation") 

246 else: 

247 self.log.debug("Use complex instFlux sigma computation that double-counts photon noise " 

248 "and thus over-estimates instFlux uncertainty") 

249 

250 # Calculate the time to log the next heartbeat log message. 

251 nextLogTime = time.time() + self.config.loggingInterval 

252 

253 for apCorrInfo in self.apCorrInfoDict.values(): 

254 apCorrModel = apCorrMap.get(apCorrInfo.modelName) 

255 apCorrErrModel = apCorrMap.get(apCorrInfo.modelSigmaName) 

256 if None in (apCorrModel, apCorrErrModel): 

257 missingNames = [(apCorrInfo.modelName, apCorrInfo.modelSigmaName)[i] 

258 for i, model in enumerate((apCorrModel, apCorrErrModel)) if model is None] 

259 self.log.warning("Cannot aperture correct %s because could not find %s in apCorrMap", 

260 apCorrInfo.name, " or ".join(missingNames)) 

261 for source in catalog: 

262 source.set(apCorrInfo.apCorrFlagKey, True) 

263 continue 

264 

265 for sourceIndex, source in enumerate(catalog): 

266 center = source.getCentroid() 

267 # say we've failed when we start; we'll unset these flags when we succeed 

268 source.set(apCorrInfo.apCorrFlagKey, True) 

269 oldFluxFlagState = False 

270 if self.config.doFlagApCorrFailures: 

271 oldFluxFlagState = source.get(apCorrInfo.fluxFlagKey) 

272 source.set(apCorrInfo.fluxFlagKey, True) 

273 

274 apCorr = 1.0 

275 apCorrErr = 0.0 

276 try: 

277 apCorr = apCorrModel.evaluate(center) 

278 if not UseNaiveFluxErr: 

279 apCorrErr = apCorrErrModel.evaluate(center) 

280 except lsst.pex.exceptions.DomainError: 

281 continue 

282 

283 if apCorrInfo.doApCorrColumn: 

284 source.set(apCorrInfo.apCorrKey, apCorr) 

285 source.set(apCorrInfo.apCorrErrKey, apCorrErr) 

286 

287 if apCorr <= 0.0 or apCorrErr < 0.0: 

288 continue 

289 

290 instFlux = source.get(apCorrInfo.instFluxKey) 

291 instFluxErr = source.get(apCorrInfo.instFluxErrKey) 

292 source.set(apCorrInfo.instFluxKey, instFlux*apCorr) 

293 if UseNaiveFluxErr: 

294 source.set(apCorrInfo.instFluxErrKey, instFluxErr*apCorr) 

295 else: 

296 a = instFluxErr/instFlux 

297 b = apCorrErr/apCorr 

298 source.set(apCorrInfo.instFluxErrKey, abs(instFlux*apCorr)*math.sqrt(a*a + b*b)) 

299 source.set(apCorrInfo.apCorrFlagKey, False) 

300 if self.config.doFlagApCorrFailures: 

301 source.set(apCorrInfo.fluxFlagKey, oldFluxFlagState) 

302 

303 # Log a message if it has been a while since the last log. 

304 if (currentTime := time.time()) > nextLogTime: 

305 self.log.verbose("Aperture corrections applied to %d sources out of %d", 

306 sourceIndex + 1, len(catalog)) 

307 nextLogTime = currentTime + self.config.loggingInterval 

308 

309 if self.log.isEnabledFor(self.log.DEBUG): 

310 # log statistics on the effects of aperture correction 

311 apCorrArr = np.array([s.get(apCorrInfo.apCorrKey) for s in catalog]) 

312 apCorrErrArr = np.array([s.get(apCorrInfo.apCorrErrKey) for s in catalog]) 

313 self.log.debug("For instFlux field %r: mean apCorr=%s, stdDev apCorr=%s, " 

314 "mean apCorrErr=%s, stdDev apCorrErr=%s for %s sources", 

315 apCorrInfo.name, apCorrArr.mean(), apCorrArr.std(), 

316 apCorrErrArr.mean(), apCorrErrArr.std(), len(catalog))