lsst.meas.base g0129b61b94+2131942eb1
applyApCorr.py
Go to the documentation of this file.
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
24
25import lsst.afw.image
26import lsst.pex.config
28import lsst.pipe.base
29from lsst.utils.logging import PeriodicLogger
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
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.namename = name
127 self.modelNamemodelName = model + "_instFlux"
128 self.modelSigmaNamemodelSigmaName = model + "_instFluxErr"
129 self.instFluxNameinstFluxName = name + "_instFlux"
130 self.instFluxErrNameinstFluxErrName = name + "_instFluxErr"
131 self.instFluxKeyinstFluxKey = schema.find(self.instFluxNameinstFluxName).key
132 self.instFluxErrKeyinstFluxErrKey = schema.find(self.instFluxErrNameinstFluxErrName).key
133 self.fluxFlagKeyfluxFlagKey = schema.find(name + "_flag").key
134
135 # No need to write the same aperture corrections multiple times
136 self.doApCorrColumndoApCorrColumn = (name == model or model + "_apCorr" not in schema)
137 if self.doApCorrColumndoApCorrColumn:
138 self.apCorrKeyapCorrKey = schema.addField(
139 name + "_apCorr",
140 doc="aperture correction applied to %s" % (name,),
141 type=np.float64,
142 )
143 self.apCorrErrKeyapCorrErrKey = 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.apCorrKeyapCorrKey = schema.find(name + "_apCorr").key
153 self.apCorrErrKeyapCorrErrKey = schema.find(name + "_apCorrErr").key
154
155 self.apCorrFlagKeyapCorrFlagKey = 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 deprecated="This field is no longer used and will be removed in v25",
191 )
192
193
194class ApplyApCorrTask(lsst.pipe.base.Task):
195 """Apply aperture corrections.
196
197 Parameters
198 ----------
199 schema : `lsst.afw.table.Schema`
200 """
201 ConfigClass = ApplyApCorrConfig
202 _DefaultName = "applyApCorr"
203
204 def __init__(self, schema, **kwds):
205 lsst.pipe.base.Task.__init__(self, **kwds)
206
207 self.apCorrInfoDictapCorrInfoDict = dict()
208 apCorrNameSet = getApCorrNameSet()
209 ignoreSet = set(self.config.ignoreList)
210 missingNameSet = ignoreSet - set(apCorrNameSet)
211 if missingNameSet:
212 self.log.warning("Fields in ignoreList that are not in fluxCorrectList: %s",
213 sorted(missingNameSet))
214 for name in sorted(apCorrNameSet - ignoreSet):
215 if name + "_instFlux" not in schema:
216 # if a field in the registry is missing from the schema, silently ignore it.
217 continue
218 self.apCorrInfoDictapCorrInfoDict[name] = ApCorrInfo(schema=schema, model=name)
219
220 for name, model in self.config.proxies.items():
221 if name in apCorrNameSet:
222 # Already done or ignored
223 continue
224 if name + "_instFlux" not in schema:
225 # Silently ignore
226 continue
227 self.apCorrInfoDictapCorrInfoDict[name] = ApCorrInfo(schema=schema, model=model, name=name)
228
229 def run(self, catalog, apCorrMap):
230 """Apply aperture corrections to a catalog of sources.
231
232 Parameters
233 ----------
235 Catalog of sources. Will be updated in place.
236 apCorrMap : `lsst.afw.image.ApCorrMap`
237 Aperture correction map
238
239 Notes
240 -----
241 If you show debug-level log messages then you will see statistics for
242 the effects of aperture correction.
243 """
244 self.log.info("Applying aperture corrections to %d instFlux fields", len(self.apCorrInfoDictapCorrInfoDict))
245 if UseNaiveFluxErr:
246 self.log.debug("Use naive instFlux sigma computation")
247 else:
248 self.log.debug("Use complex instFlux sigma computation that double-counts photon noise "
249 "and thus over-estimates instFlux uncertainty")
250
251 # Wrap the task logger to a periodic logger.
252 periodicLog = PeriodicLogger(self.log)
253
254 for apCorrInfo in self.apCorrInfoDictapCorrInfoDict.values():
255 apCorrModel = apCorrMap.get(apCorrInfo.modelName)
256 apCorrErrModel = apCorrMap.get(apCorrInfo.modelSigmaName)
257 if None in (apCorrModel, apCorrErrModel):
258 missingNames = [(apCorrInfo.modelName, apCorrInfo.modelSigmaName)[i]
259 for i, model in enumerate((apCorrModel, apCorrErrModel)) if model is None]
260 self.log.warning("Cannot aperture correct %s because could not find %s in apCorrMap",
261 apCorrInfo.name, " or ".join(missingNames))
262 for source in catalog:
263 source.set(apCorrInfo.apCorrFlagKey, True)
264 continue
265
266 for sourceIndex, source in enumerate(catalog):
267 center = source.getCentroid()
268 # say we've failed when we start; we'll unset these flags when we succeed
269 source.set(apCorrInfo.apCorrFlagKey, True)
270 oldFluxFlagState = False
271 if self.config.doFlagApCorrFailures:
272 oldFluxFlagState = source.get(apCorrInfo.fluxFlagKey)
273 source.set(apCorrInfo.fluxFlagKey, True)
274
275 apCorr = 1.0
276 apCorrErr = 0.0
277 try:
278 apCorr = apCorrModel.evaluate(center)
279 if not UseNaiveFluxErr:
280 apCorrErr = apCorrErrModel.evaluate(center)
282 continue
283
284 if apCorrInfo.doApCorrColumn:
285 source.set(apCorrInfo.apCorrKey, apCorr)
286 source.set(apCorrInfo.apCorrErrKey, apCorrErr)
287
288 if apCorr <= 0.0 or apCorrErr < 0.0:
289 continue
290
291 instFlux = source.get(apCorrInfo.instFluxKey)
292 instFluxErr = source.get(apCorrInfo.instFluxErrKey)
293 source.set(apCorrInfo.instFluxKey, instFlux*apCorr)
294 if UseNaiveFluxErr:
295 source.set(apCorrInfo.instFluxErrKey, instFluxErr*apCorr)
296 else:
297 a = instFluxErr/instFlux
298 b = apCorrErr/apCorr
299 source.set(apCorrInfo.instFluxErrKey, abs(instFlux*apCorr)*math.sqrt(a*a + b*b))
300 source.set(apCorrInfo.apCorrFlagKey, False)
301 if self.config.doFlagApCorrFailures:
302 source.set(apCorrInfo.fluxFlagKey, oldFluxFlagState)
303
304 # Log a message if it has been a while since the last log.
305 periodicLog.log("Aperture corrections applied to %d sources out of %d",
306 sourceIndex + 1, len(catalog))
307
308 if self.log.isEnabledFor(self.log.DEBUG):
309 # log statistics on the effects of aperture correction
310 apCorrArr = np.array([s.get(apCorrInfo.apCorrKey) for s in catalog])
311 apCorrErrArr = np.array([s.get(apCorrInfo.apCorrErrKey) for s in catalog])
312 self.log.debug("For instFlux field %r: mean apCorr=%s, stdDev apCorr=%s, "
313 "mean apCorrErr=%s, stdDev apCorrErr=%s for %s sources",
314 apCorrInfo.name, apCorrArr.mean(), apCorrArr.std(),
315 apCorrErrArr.mean(), apCorrErrArr.std(), len(catalog))
def __init__(self, schema, model, name=None)
Definition: applyApCorr.py:123
def run(self, catalog, apCorrMap)
Definition: applyApCorr.py:229