23 from __future__
import absolute_import, division
25 __all__ = (
"MeasureApCorrConfig",
"MeasureApCorrTask")
27 from builtins
import zip
28 from builtins
import range
29 from builtins
import object
32 import lsst.pex.config
34 from lsst.afw.math
import ChebyshevBoundedField, ChebyshevBoundedFieldConfig
35 from lsst.pipe.base
import Task, Struct
36 from lsst.meas.base.apCorrRegistry
import getApCorrNameSet
38 from .
import flaggedStarSelector
39 from .starSelector
import starSelectorRegistry
43 """A collection of keys for a given flux measurement algorithm
45 __slots__ = (
"flux",
"err",
"flag",
"used")
48 """Construct a FluxKeys
50 @parma[in] name name of flux measurement algorithm, e.g. "base_PsfFlux"
51 @param[in,out] schema catalog schema containing the flux field
52 read: {name}_flux, {name}_fluxSigma, {name}_flag
53 added: apcorr_{name}_used
55 self.
flux = schema.find(name +
"_flux").key
56 self.
err = schema.find(name +
"_fluxSigma").key
57 self.
flag = schema.find(name +
"_flag").key
58 self.
used = schema.addField(
"apcorr_" + name +
"_used", type=
"Flag",
59 doc=
"set if source was used in measuring aperture correction")
71 """!Configuration for MeasureApCorrTask
73 refFluxName = lsst.pex.config.Field(
74 doc=
"Field name prefix for the flux other measurements should be aperture corrected to match",
76 default=
"slot_CalibFlux",
78 starSelector = starSelectorRegistry.makeField(
79 doc=
"Selector that sets the stars that aperture corrections will be measured from",
82 minDegreesOfFreedom = lsst.pex.config.RangeField(
83 doc=
"Minimum number of degrees of freedom (# of valid data points - # of parameters);" +
84 " if this is exceeded, the order of the fit is decreased (in both dimensions), and" +
85 " if we can't decrease it enough, we'll raise ValueError.",
90 fitConfig = lsst.pex.config.ConfigField(
91 doc=
"Configuration used in fitting the aperture correction fields",
92 dtype=ChebyshevBoundedFieldConfig,
94 numIter = lsst.pex.config.Field(
95 doc=
"Number of iterations for sigma clipping",
99 numSigmaClip = lsst.pex.config.Field(
100 doc=
"Number of standard devisations to clip at",
104 allowFailure = lsst.pex.config.ListField(
105 doc=
"Allow these measurement algorithms to fail without an exception",
111 lsst.pex.config.Config.validate(self)
112 if self.starSelector.target.usesMatches:
113 raise lsst.pex.config.FieldValidationError(
114 "Star selectors that require matches are not permitted"
119 """!Task to measure aperture correction
121 \section measAlg_MeasureApCorrTask_Contents Contents
123 - \ref measAlg_MeasureApCorrTask_Purpose
124 - \ref measAlg_MeasureApCorrTask_Config
125 - \ref measAlg_MeasureApCorrTask_Debug
127 \section measAlg_MeasureApCorrTask_Purpose Description
129 \copybrief MeasureApCorrTask
131 This task measures aperture correction for the flux fields returned by
132 lsst.meas.base.getApCorrNameSet()
134 The main method is \ref MeasureApCorrTask.run "run".
136 \section measAlg_MeasureApCorrTask_Config Configuration parameters
138 See \ref MeasureApCorrConfig
140 \section measAlg_MeasureApCorrTask_Debug Debug variables
142 The @link lsst.pipe.base.cmdLineTask.CmdLineTask command line task@endlink interface supports a flag
143 `--debug` to import `debug.py` from your `$PYTHONPATH`; see @ref baseDebug for more about `debug.py`.
145 MeasureApCorrTask has a debug dictionary containing a single boolean key:
148 <dd>If True: will show plots as aperture corrections are fitted
151 For example, put something like:
155 di = lsstDebug.getInfo(name) # N.b. lsstDebug.Info(name) would call us recursively
156 if name == "lsst.meas.algorithms.measureApCorr":
165 lsstDebug.Info = DebugInfo
167 into your `debug.py` file and run your command-line task with the `--debug` flag (or `import debug`).
169 ConfigClass = MeasureApCorrConfig
170 _DefaultName =
"measureApCorr"
173 """!Construct a MeasureApCorrTask
175 For every name in lsst.meas.base.getApCorrNameSet():
176 - If the corresponding flux fields exist in the schema:
177 - Add a new field apcorr_{name}_used
178 - Add an entry to the self.toCorrect dict
179 - Otherwise silently skip the name
181 Task.__init__(self, **kwds)
184 for name
in getApCorrNameSet():
190 self.makeSubtask(
"starSelector", schema=schema)
192 def run(self, exposure, catalog):
193 """!Measure aperture correction
195 @param[in] exposure Exposure aperture corrections are being measured
196 on. Aside from the bounding box, the exposure
197 is only used by the starSelector subtask (which
198 may need it to construct PsfCandidates, as
199 PsfCanidate construction can do some filtering).
200 The output aperture correction map is *not*
201 added to the exposure; this is left to the
204 @param[in] catalog SourceCatalog containing measurements to be used
205 to compute aperturecorrections.
207 @return an lsst.pipe.base.Struct containing:
208 - apCorrMap: an aperture correction map (lsst.afw.image.ApCorrMap) that contains two entries
210 - flux field (e.g. base_PsfFlux_flux): 2d model
211 - flux sigma field (e.g. base_PsfFlux_fluxSigma): 2d model of error
213 bbox = exposure.getBBox()
215 display = lsstDebug.Info(__name__).display
217 self.log.info(
"Measuring aperture corrections for %d flux fields" % (len(self.
toCorrect),))
220 subset1 = [record
for record
in self.starSelector.selectStars(exposure, catalog).starCat
221 if (
not record.get(self.refFluxKeys.flag)
and
222 numpy.isfinite(record.get(self.refFluxKeys.flux)))]
224 apCorrMap = ApCorrMap()
227 for name, keys
in self.toCorrect.items():
228 fluxName = name +
"_flux"
229 fluxSigmaName = name +
"_fluxSigma"
233 fluxes = numpy.fromiter((record.get(keys.flux)
for record
in subset1), float)
234 isGood = numpy.logical_and.reduce([
235 numpy.fromiter((
not record.get(keys.flag)
for record
in subset1), bool),
236 numpy.isfinite(fluxes),
239 subset2 = [record
for record, good
in zip(subset1, isGood)
if good]
243 if len(subset2) - 1 < self.config.minDegreesOfFreedom:
244 if name
in self.config.allowFailure:
245 self.log.warn(
"Unable to measure aperture correction for '%s': "
246 "only %d sources, but require at least %d." %
247 (name, len(subset2), self.config.minDegreesOfFreedom+1))
249 raise RuntimeError(
"Unable to measure aperture correction for required algorithm '%s': "
250 "only %d sources, but require at least %d." %
251 (name, len(subset2), self.config.minDegreesOfFreedom+1))
254 ctrl = self.config.fitConfig.makeControl()
255 while len(subset2) - ctrl.computeSize() < self.config.minDegreesOfFreedom:
262 x = numpy.zeros(len(subset2), dtype=float)
263 y = numpy.zeros(len(subset2), dtype=float)
264 apCorrData = numpy.zeros(len(subset2), dtype=float)
265 indices = numpy.arange(len(subset2), dtype=int)
266 for n, record
in enumerate(subset2):
269 apCorrData[n] = record.get(self.refFluxKeys.flux)/record.get(keys.flux)
271 for _i
in range(self.config.numIter):
274 apCorrField = ChebyshevBoundedField.fit(bbox, x, y, apCorrData, ctrl)
277 plotApCorr(bbox, x, y, apCorrData, apCorrField,
"%s, iteration %d" % (name, _i))
281 apCorrDiffs = apCorrField.evaluate(x, y)
282 apCorrDiffs -= apCorrData
283 apCorrErr = numpy.mean(apCorrDiffs**2)**0.5
286 apCorrDiffLim = self.config.numSigmaClip * apCorrErr
287 keep = numpy.fabs(apCorrDiffs) <= apCorrDiffLim
290 apCorrData = apCorrData[keep]
291 indices = indices[keep]
294 apCorrField = ChebyshevBoundedField.fit(bbox, x, y, apCorrData, ctrl)
296 self.log.info(
"Aperture correction for %s: RMS %f from %d" %
297 (name, numpy.mean((apCorrField.evaluate(x, y) - apCorrData)**2)**0.5, len(indices)))
300 plotApCorr(bbox, x, y, apCorrData, apCorrField,
"%s, final" % (name,))
306 apCorrMap[fluxName] = apCorrField
307 apCorrErrCoefficients = numpy.array([[apCorrErr]], dtype=float)
308 apCorrMap[fluxSigmaName] = ChebyshevBoundedField(bbox, apCorrErrCoefficients)
312 subset2[i].set(keys.used,
True)
320 """Plot aperture correction fit residuals
322 There are two subplots: residuals against x and y.
324 Intended for debugging.
326 @param bbox Bounding box (for bounds)
327 @param xx x coordinates
328 @param yy y coordinates
329 @param zzMeasure Measured value of the aperture correction
330 @param field Fit aperture correction field
331 @param title Title for plot
333 import matplotlib.pyplot
as plt
335 zzFit = field.evaluate(xx, yy)
336 residuals = zzMeasure - zzFit
338 fig, axes = plt.subplots(2, 1)
340 axes[0].scatter(xx, residuals, s=2, marker=
'o', lw=0, alpha=0.3)
341 axes[1].scatter(yy, residuals, s=2, marker=
'o', lw=0, alpha=0.3)
343 ax.set_ylabel(
"Residual")
344 ax.set_ylim(0.9*residuals.min(), 1.1*residuals.max())
345 axes[0].set_xlabel(
"x")
346 axes[0].set_xlim(bbox.getMinX(), bbox.getMaxX())
347 axes[1].set_xlabel(
"y")
348 axes[1].set_xlim(bbox.getMinY(), bbox.getMaxY())
Task to measure aperture correction.
Configuration for MeasureApCorrTask.
def run
Measure aperture correction.
def __init__
Construct a MeasureApCorrTask.