lsst.meas.base  16.0-12-g5ad1ebf+4
applyApCorr.py
Go to the documentation of this file.
1 #
2 # LSST Data Management System
3 # Copyright 2015 LSST Corporation.
4 #
5 # This product includes software developed by the
6 # LSST Project (http://www.lsst.org/).
7 #
8 # This program is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation, either version 3 of the License, or
11 # (at your option) any later version.
12 #
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
17 #
18 # You should have received a copy of the LSST License Statement and
19 # the GNU General Public License along with this program. If not,
20 # see <http://www.lsstcorp.org/LegalNotices/>.
21 #
22 import math
23 
24 import numpy as np
25 
26 import lsst.pex.config
28 import lsst.afw.image
29 import lsst.pipe.base
30 from .apCorrRegistry import getApCorrNameSet
31 
32 # If True then scale flux sigma by apCorr; if False then use a more complex computation
33 # that over-estimates flux 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 flux uncertainty
36 UseNaiveFluxErr = True
37 
38 __all__ = ("ApplyApCorrConfig", "ApplyApCorrTask")
39 
40 
41 class ApCorrInfo:
42  """!Catalog field names and keys needed to aperture correct a particular flux
43  """
44 
45  def __init__(self, schema, model, name=None):
46  """!Construct an ApCorrInfo and add fields to the schema
47 
48  The aperture correction can be derived from the meaasurements in the
49  column being aperture-corrected or from measurements in a different
50  column (a "proxy"). In the first case, we will add columns to contain
51  the aperture correction values; in the second case (using a proxy),
52  we will add an alias to the proxy's aperture correction values. In
53  all cases, we add a flag.
54 
55  @param[in,out] schema source catalog schema;
56  three fields are used to generate keys:
57  - {name}_flux
58  - {name}_fluxErr
59  - {name}_flag
60  three fields are added:
61  - {name}_apCorr (only if not already added by proxy)
62  - {name}_apCorrErr (only if not already added by proxy)
63  - {name}_flag_apCorr
64  @param[in] model field name prefix for flux with aperture correction model, e.g. "base_PsfFlux"
65  @param[in] name field name prefix for flux needing aperture correction; may be None if it's the
66  same as for the 'model' parameter
67 
68  ApCorrInfo has the following attributes:
69  - name: field name prefix for flux needing aperture correction
70  - modelName: field name for aperture correction model for flux
71  - modelSigmaName: field name for aperture correction model for fluxErr
72  - doApCorrColumn: should we write the aperture correction values? (not if they're already being
73  written by a proxy)
74  - fluxName: name of flux field
75  - fluxErrName: name of flux sigma field
76  - fluxKey: key to flux field
77  - fluxErrKey: key to flux sigma field
78  - fluxFlagKey: key to flux flag field
79  - apCorrKey: key to new aperture correction field
80  - apCorrErrKey: key to new aperture correction sigma field
81  - apCorrFlagKey: key to new aperture correction flag field
82  """
83  if name is None:
84  name = model
85  self.name = name
86  self.modelName = model + "_flux"
87  self.modelSigmaName = model + "_fluxErr"
88  self.fluxName = name + "_flux"
89  self.fluxErrName = name + "_fluxErr"
90  self.fluxKey = schema.find(self.fluxName).key
91  self.fluxErrKey = schema.find(self.fluxErrName).key
92  self.fluxFlagKey = schema.find(name + "_flag").key
93 
94  # No need to write the same aperture corrections multiple times
95  self.doApCorrColumn = (name == model or model + "_apCorr" not in schema)
96  if self.doApCorrColumn:
97  self.apCorrKey = schema.addField(
98  name + "_apCorr",
99  doc="aperture correction applied to %s" % (name,),
100  type=np.float64,
101  )
102  self.apCorrErrKey = schema.addField(
103  name + "_apCorrErr",
104  doc="standard deviation of aperture correction applied to %s" % (name,),
105  type=np.float64,
106  )
107  else:
108  aliases = schema.getAliasMap()
109  aliases.set(name + "_apCorr", model + "_apCorr")
110  aliases.set(name + "_apCorrErr", model + "_apCorrErr")
111  self.apCorrKey = schema.find(name + "_apCorr").key
112  self.apCorrErrKey = schema.find(name + "_apCorrErr").key
113 
114  self.apCorrFlagKey = schema.addField(
115  name + "_flag_apCorr",
116  doc="set if unable to aperture correct %s" % (name,),
117  type="Flag",
118  )
119 
120 
121 class ApplyApCorrConfig(lsst.pex.config.Config):
122  ignoreList = lsst.pex.config.ListField(
123  doc="flux measurement algorithms in getApCorrNameSet() to ignore; "
124  "if a name is listed that does not appear in getApCorrNameSet() then a warning is logged",
125  dtype=str,
126  optional=False,
127  default=(),
128  )
129  doFlagApCorrFailures = lsst.pex.config.Field(
130  doc="set the general failure flag for a flux when it cannot be aperture-corrected?",
131  dtype=bool,
132  default=True,
133  )
134  proxies = lsst.pex.config.DictField(
135  doc="flux measurement algorithms to be aperture-corrected by reference to another algorithm; "
136  "this is a mapping alg1:alg2, where 'alg1' is the algorithm being corrected, and 'alg2' "
137  "is the algorithm supplying the corrections",
138  keytype=str,
139  itemtype=str,
140  default={},
141  )
142 
143 
144 class ApplyApCorrTask(lsst.pipe.base.Task):
145  """!Apply aperture corrections
146  """
147  ConfigClass = ApplyApCorrConfig
148  _DefaultName = "applyApCorr"
149 
150  def __init__(self, schema, **kwds):
151  """Construct an instance of this task
152  """
153  lsst.pipe.base.Task.__init__(self, **kwds)
154 
155  self.apCorrInfoDict = dict()
156  apCorrNameSet = getApCorrNameSet()
157  ignoreSet = set(self.config.ignoreList)
158  missingNameSet = ignoreSet - set(apCorrNameSet)
159  if missingNameSet:
160  self.log.warn("Fields in ignoreList that are not in fluxCorrectList: %s",
161  sorted(list(missingNameSet)))
162  for name in apCorrNameSet - ignoreSet:
163  if name + "_flux" not in schema:
164  # if a field in the registry is missing from the schema, silently ignore it.
165  continue
166  self.apCorrInfoDict[name] = ApCorrInfo(schema=schema, model=name)
167 
168  for name, model in self.config.proxies.items():
169  if name in apCorrNameSet:
170  # Already done or ignored
171  continue
172  if name + "_flux" not in schema:
173  # Silently ignore
174  continue
175  self.apCorrInfoDict[name] = ApCorrInfo(schema=schema, model=model, name=name)
176 
177  def run(self, catalog, apCorrMap):
178  """Apply aperture corrections to a catalog of sources
179 
180  @param[in,out] catalog catalog of sources
181  @param[in] apCorrMap aperture correction map (an lsst.afw.image.ApCorrMap)
182 
183  If you show debug-level log messages then you will see statistics for the effects of
184  aperture correction.
185  """
186  self.log.info("Applying aperture corrections to %d flux fields", len(self.apCorrInfoDict))
187  if UseNaiveFluxErr:
188  self.log.debug("Use naive flux sigma computation")
189  else:
190  self.log.debug("Use complex flux sigma computation that double-counts photon noise "
191  "and thus over-estimates flux uncertainty")
192  for apCorrInfo in self.apCorrInfoDict.values():
193  apCorrModel = apCorrMap.get(apCorrInfo.modelName)
194  apCorrErrModel = apCorrMap.get(apCorrInfo.modelSigmaName)
195  if None in (apCorrModel, apCorrErrModel):
196  missingNames = [(apCorrInfo.modelName, apCorrInfo.modelSigmaName)[i]
197  for i, model in enumerate((apCorrModel, apCorrErrModel)) if model is None]
198  self.log.warn("Cannot aperture correct %s because could not find %s in apCorrMap" %
199  (apCorrInfo.name, " or ".join(missingNames),))
200  for source in catalog:
201  source.set(apCorrInfo.apCorrFlagKey, True)
202  continue
203 
204  for source in catalog:
205  center = source.getCentroid()
206  # say we've failed when we start; we'll unset these flags when we succeed
207  source.set(apCorrInfo.apCorrFlagKey, True)
208  oldFluxFlagState = False
209  if self.config.doFlagApCorrFailures:
210  oldFluxFlagState = source.get(apCorrInfo.fluxFlagKey)
211  source.set(apCorrInfo.fluxFlagKey, True)
212 
213  apCorr = 1.0
214  apCorrErr = 0.0
215  try:
216  apCorr = apCorrModel.evaluate(center)
217  if not UseNaiveFluxErr:
218  apCorrErr = apCorrErrModel.evaluate(center)
220  continue
221 
222  if apCorrInfo.doApCorrColumn:
223  source.set(apCorrInfo.apCorrKey, apCorr)
224  source.set(apCorrInfo.apCorrErrKey, apCorrErr)
225 
226  if apCorr <= 0.0 or apCorrErr < 0.0:
227  continue
228 
229  flux = source.get(apCorrInfo.fluxKey)
230  fluxErr = source.get(apCorrInfo.fluxErrKey)
231  source.set(apCorrInfo.fluxKey, flux*apCorr)
232  if UseNaiveFluxErr:
233  source.set(apCorrInfo.fluxErrKey, fluxErr*apCorr)
234  else:
235  a = fluxErr/flux
236  b = apCorrErr/apCorr
237  source.set(apCorrInfo.fluxErrKey, abs(flux*apCorr)*math.sqrt(a*a + b*b))
238  source.set(apCorrInfo.apCorrFlagKey, False)
239  if self.config.doFlagApCorrFailures:
240  source.set(apCorrInfo.fluxFlagKey, oldFluxFlagState)
241 
242  if self.log.getLevel() <= self.log.DEBUG:
243  # log statistics on the effects of aperture correction
244  apCorrArr = np.array([s.get(apCorrInfo.apCorrKey) for s in catalog])
245  apCorrErrArr = np.array([s.get(apCorrInfo.apCorrErrKey) for s in catalog])
246  self.log.debug("For flux field %r: mean apCorr=%s, stdDev apCorr=%s, "
247  "mean apCorrErr=%s, stdDev apCorrErr=%s for %s sources",
248  apCorrInfo.name, apCorrArr.mean(), apCorrArr.std(),
249  apCorrErrArr.mean(), apCorrErrArr.std(), len(catalog))
def getApCorrNameSet()
Return a copy of the set of field name prefixes for fluxes that should be aperture corrected...
def __init__(self, schema, model, name=None)
Construct an ApCorrInfo and add fields to the schema.
Definition: applyApCorr.py:45
Catalog field names and keys needed to aperture correct a particular flux.
Definition: applyApCorr.py:41
def run(self, catalog, apCorrMap)
Definition: applyApCorr.py:177