lsst.meas.base  13.0-32-g2c0c40e+12
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 from __future__ import absolute_import, division, print_function
23 import math
24 
25 from builtins import object
26 import numpy
27 
28 import lsst.pex.config
29 import lsst.pex.exceptions
30 import lsst.afw.image
31 import lsst.pipe.base
32 from .apCorrRegistry import getApCorrNameSet
33 
34 # If True then scale flux sigma by apCorr; if False then use a more complex computation
35 # that over-estimates flux error (often grossly so) because it double-counts photon noise.
36 # This flag is intended to be temporary until we figure out a better way to compute
37 # the effects of aperture correction on flux uncertainty
38 UseNaiveFluxSigma = True
39 
40 __all__ = ("ApplyApCorrConfig", "ApplyApCorrTask")
41 
42 
43 class ApCorrInfo(object):
44  """!Catalog field names and keys needed to aperture correct a particular flux
45  """
46 
47  def __init__(self, schema, model, name=None):
48  """!Construct an ApCorrInfo and add fields to the schema
49 
50  The aperture correction can be derived from the meaasurements in the
51  column being aperture-corrected or from measurements in a different
52  column (a "proxy"). In the first case, we will add columns to contain
53  the aperture correction values; in the second case (using a proxy),
54  we will add an alias to the proxy's aperture correction values. In
55  all cases, we add a flag.
56 
57  @param[in,out] schema source catalog schema;
58  three fields are used to generate keys:
59  - {name}_flux
60  - {name}_fluxSigma
61  - {name}_flag
62  three fields are added:
63  - {name}_apCorr (only if not already added by proxy)
64  - {name}_apCorrSigma (only if not already added by proxy)
65  - {name}_flag_apCorr
66  @param[in] model field name prefix for flux with aperture correction model, e.g. "base_PsfFlux"
67  @param[in] name field name prefix for flux needing aperture correction; may be None if it's the
68  same as for the 'model' parameter
69 
70  ApCorrInfo has the following attributes:
71  - name: field name prefix for flux needing aperture correction
72  - modelName: field name for aperture correction model for flux
73  - modelSigmaName: field name for aperture correction model for fluxSigma
74  - doApCorrColumn: should we write the aperture correction values? (not if they're already being
75  written by a proxy)
76  - fluxName: name of flux field
77  - fluxSigmaName: name of flux sigma field
78  - fluxKey: key to flux field
79  - fluxSigmaKey: key to flux sigma field
80  - fluxFlagKey: key to flux flag field
81  - apCorrKey: key to new aperture correction field
82  - apCorrSigmaKey: key to new aperture correction sigma field
83  - apCorrFlagKey: key to new aperture correction flag field
84  """
85  if name is None:
86  name = model
87  self.name = name
88  self.modelName = model + "_flux"
89  self.modelSigmaName = model + "_fluxSigma"
90  self.fluxName = name + "_flux"
91  self.fluxSigmaName = name + "_fluxSigma"
92  self.fluxKey = schema.find(self.fluxName).key
93  self.fluxSigmaKey = schema.find(self.fluxSigmaName).key
94  self.fluxFlagKey = schema.find(name + "_flag").key
95 
96  # No need to write the same aperture corrections multiple times
97  self.doApCorrColumn = (name == model or model + "_apCorr" not in schema)
98  if self.doApCorrColumn:
99  self.apCorrKey = schema.addField(
100  name + "_apCorr",
101  doc="aperture correction applied to %s" % (name,),
102  type=numpy.float64,
103  )
104  self.apCorrSigmaKey = schema.addField(
105  name + "_apCorrSigma",
106  doc="aperture correction applied to %s" % (name,),
107  type=numpy.float64,
108  )
109  else:
110  aliases = schema.getAliasMap()
111  aliases.set(name + "_apCorr", model + "_apCorr")
112  aliases.set(name + "_apCorrSigma", model + "_apCorrSigma")
113  self.apCorrKey = schema.find(name + "_apCorr").key
114  self.apCorrSigmaKey = schema.find(name + "_apCorrSigma").key
115 
116  self.apCorrFlagKey = schema.addField(
117  name + "_flag_apCorr",
118  doc="set if unable to aperture correct %s" % (name,),
119  type="Flag",
120  )
121 
122 
123 class ApplyApCorrConfig(lsst.pex.config.Config):
124  ignoreList = lsst.pex.config.ListField(
125  doc="flux measurement algorithms in getApCorrNameSet() to ignore; "
126  "if a name is listed that does not appear in getApCorrNameSet() then a warning is logged",
127  dtype=str,
128  optional=False,
129  default=(),
130  )
131  doFlagApCorrFailures = lsst.pex.config.Field(
132  doc="set the general failure flag for a flux when it cannot be aperture-corrected?",
133  dtype=bool,
134  default=True,
135  )
136  proxies = lsst.pex.config.DictField(
137  doc="flux measurement algorithms to be aperture-corrected by reference to another algorithm; "
138  "this is a mapping alg1:alg2, where 'alg1' is the algorithm being corrected, and 'alg2' "
139  "is the algorithm supplying the corrections",
140  keytype=str,
141  itemtype=str,
142  default={},
143  )
144 
145 
146 class ApplyApCorrTask(lsst.pipe.base.Task):
147  """!Apply aperture corrections
148  """
149  ConfigClass = ApplyApCorrConfig
150  _DefaultName = "applyApCorr"
151 
152  def __init__(self, schema, **kwds):
153  """Construct an instance of this task
154  """
155  lsst.pipe.base.Task.__init__(self, **kwds)
156 
157  self.apCorrInfoDict = dict()
158  apCorrNameSet = getApCorrNameSet()
159  ignoreSet = set(self.config.ignoreList)
160  missingNameSet = ignoreSet - set(apCorrNameSet)
161  if missingNameSet:
162  self.log.warn("Fields in ignoreList that are not in fluxCorrectList: %s",
163  sorted(list(missingNameSet)))
164  for name in apCorrNameSet - ignoreSet:
165  if name + "_flux" not in schema:
166  # if a field in the registry is missing from the schema, silently ignore it.
167  continue
168  self.apCorrInfoDict[name] = ApCorrInfo(schema=schema, model=name)
169 
170  for name, model in self.config.proxies.items():
171  if name in apCorrNameSet:
172  # Already done or ignored
173  continue
174  if name + "_flux" not in schema:
175  # Silently ignore
176  continue
177  self.apCorrInfoDict[name] = ApCorrInfo(schema=schema, model=model, name=name)
178 
179  def run(self, catalog, apCorrMap):
180  """Apply aperture corrections to a catalog of sources
181 
182  @param[in,out] catalog catalog of sources
183  @param[in] apCorrMap aperture correction map (an lsst.afw.image.ApCorrMap)
184 
185  If you show debug-level log messages then you will see statistics for the effects of
186  aperture correction.
187  """
188  self.log.info("Applying aperture corrections to %d flux fields", len(self.apCorrInfoDict))
189  if UseNaiveFluxSigma:
190  self.log.debug("Use naive flux sigma computation")
191  else:
192  self.log.debug("Use complex flux sigma computation that double-counts photon noise "
193  "and thus over-estimates flux uncertainty")
194  for apCorrInfo in self.apCorrInfoDict.values():
195  apCorrModel = apCorrMap.get(apCorrInfo.modelName)
196  apCorrSigmaModel = apCorrMap.get(apCorrInfo.modelSigmaName)
197  if None in (apCorrModel, apCorrSigmaModel):
198  missingNames = [(apCorrInfo.modelName, apCorrInfo.modelSigmaName)[i]
199  for i, model in enumerate((apCorrModel, apCorrSigmaModel)) if model is None]
200  self.log.warn("Cannot aperture correct %s because could not find %s in apCorrMap" %
201  (apCorrInfo.name, " or ".join(missingNames),))
202  for source in catalog:
203  source.set(apCorrInfo.apCorrFlagKey, True)
204  continue
205 
206  for source in catalog:
207  center = source.getCentroid()
208  # say we've failed when we start; we'll unset these flags when we succeed
209  source.set(apCorrInfo.apCorrFlagKey, True)
210  oldFluxFlagState = False
211  if self.config.doFlagApCorrFailures:
212  oldFluxFlagState = source.get(apCorrInfo.fluxFlagKey)
213  source.set(apCorrInfo.fluxFlagKey, True)
214 
215  apCorr = 1.0
216  apCorrSigma = 0.0
217  try:
218  apCorr = apCorrModel.evaluate(center)
219  if not UseNaiveFluxSigma:
220  apCorrSigma = apCorrSigmaModel.evaluate(center)
221  except lsst.pex.exceptions.DomainError:
222  continue
223 
224  if apCorrInfo.doApCorrColumn:
225  source.set(apCorrInfo.apCorrKey, apCorr)
226  source.set(apCorrInfo.apCorrSigmaKey, apCorrSigma)
227 
228  if apCorr <= 0.0 or apCorrSigma < 0.0:
229  continue
230 
231  flux = source.get(apCorrInfo.fluxKey)
232  fluxSigma = source.get(apCorrInfo.fluxSigmaKey)
233  source.set(apCorrInfo.fluxKey, flux*apCorr)
234  if UseNaiveFluxSigma:
235  source.set(apCorrInfo.fluxSigmaKey, fluxSigma*apCorr)
236  else:
237  a = fluxSigma/flux
238  b = apCorrSigma/apCorr
239  source.set(apCorrInfo.fluxSigmaKey, abs(flux*apCorr)*math.sqrt(a*a + b*b))
240  source.set(apCorrInfo.apCorrFlagKey, False)
241  if self.config.doFlagApCorrFailures:
242  source.set(apCorrInfo.fluxFlagKey, oldFluxFlagState)
243 
244  if self.log.getLevel() <= self.log.DEBUG:
245  # log statistics on the effects of aperture correction
246  apCorrArr = numpy.array([s.get(apCorrInfo.apCorrKey) for s in catalog])
247  apCorrSigmaArr = numpy.array([s.get(apCorrInfo.apCorrSigmaKey) for s in catalog])
248  self.log.debug("For flux field %r: mean apCorr=%s, stdDev apCorr=%s, "
249  "mean apCorrSigma=%s, stdDev apCorrSigma=%s for %s sources",
250  apCorrInfo.name, apCorrArr.mean(), apCorrArr.std(),
251  apCorrSigmaArr.mean(), apCorrSigmaArr.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:47
Catalog field names and keys needed to aperture correct a particular flux.
Definition: applyApCorr.py:43
def run(self, catalog, apCorrMap)
Definition: applyApCorr.py:179