lsst.meas.base  14.0-22-gcfb0d17
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
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)
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