24 __all__ = (
"MeasureApCorrConfig",
"MeasureApCorrTask")
30 from lsst.afw.math import ChebyshevBoundedField, ChebyshevBoundedFieldConfig
34 from .sourceSelector
import sourceSelectorRegistry
38 """A collection of keys for a given flux measurement algorithm
40 __slots__ = (
"flux",
"err",
"flag",
"used")
43 """Construct a FluxKeys
45 @parma[in] name name of flux measurement algorithm, e.g. "base_PsfFlux"
46 @param[in,out] schema catalog schema containing the flux field
47 read: {name}_instFlux, {name}_instFluxErr, {name}_flag
48 added: apcorr_{name}_used
50 self.
flux = schema.find(name +
"_instFlux").key
51 self.
err = schema.find(name +
"_instFluxErr").key
52 self.
flag = schema.find(name +
"_flag").key
53 self.
used = schema.addField(
"apcorr_" + name +
"_used", type=
"Flag",
54 doc=
"set if source was used in measuring aperture correction")
66 """!Configuration for MeasureApCorrTask
68 refFluxName = lsst.pex.config.Field(
69 doc=
"Field name prefix for the flux other measurements should be aperture corrected to match",
71 default=
"slot_CalibFlux",
73 sourceSelector = sourceSelectorRegistry.makeField(
74 doc=
"Selector that sets the stars that aperture corrections will be measured from.",
77 minDegreesOfFreedom = lsst.pex.config.RangeField(
78 doc=
"Minimum number of degrees of freedom (# of valid data points - # of parameters);"
79 " if this is exceeded, the order of the fit is decreased (in both dimensions), and"
80 " if we can't decrease it enough, we'll raise ValueError.",
85 fitConfig = lsst.pex.config.ConfigField(
86 doc=
"Configuration used in fitting the aperture correction fields",
87 dtype=ChebyshevBoundedFieldConfig,
89 numIter = lsst.pex.config.Field(
90 doc=
"Number of iterations for sigma clipping",
94 numSigmaClip = lsst.pex.config.Field(
95 doc=
"Number of standard devisations to clip at",
99 allowFailure = lsst.pex.config.ListField(
100 doc=
"Allow these measurement algorithms to fail without an exception",
106 lsst.pex.config.Config.validate(self)
108 raise lsst.pex.config.FieldValidationError(
109 MeasureApCorrConfig.sourceSelector,
111 "Star selectors that require matches are not permitted")
115 r"""!Task to measure aperture correction
117 @section measAlg_MeasureApCorrTask_Contents Contents
119 - @ref measAlg_MeasureApCorrTask_Purpose
120 - @ref measAlg_MeasureApCorrTask_Config
121 - @ref measAlg_MeasureApCorrTask_Debug
123 @section measAlg_MeasureApCorrTask_Purpose Description
125 @copybrief MeasureApCorrTask
127 This task measures aperture correction for the flux fields returned by
128 lsst.meas.base.getApCorrNameSet()
130 The main method is @ref MeasureApCorrTask.run "run".
132 @section measAlg_MeasureApCorrTask_Config Configuration parameters
134 See @ref MeasureApCorrConfig
136 @section measAlg_MeasureApCorrTask_Debug Debug variables
138 The @link lsst.pipe.base.cmdLineTask.CmdLineTask command line task@endlink interface supports a flag
139 `--debug` to import `debug.py` from your `$PYTHONPATH`; see @ref baseDebug for more about `debug.py`.
141 MeasureApCorrTask has a debug dictionary containing a single boolean key:
144 <dd>If True: will show plots as aperture corrections are fitted
147 For example, put something like:
151 di = lsstDebug.getInfo(name) # N.b. lsstDebug.Info(name) would call us recursively
152 if name == "lsst.meas.algorithms.measureApCorr":
161 lsstDebug.Info = DebugInfo
163 into your `debug.py` file and run your command-line task with the `--debug` flag (or `import debug`).
165 ConfigClass = MeasureApCorrConfig
166 _DefaultName =
"measureApCorr"
169 """!Construct a MeasureApCorrTask
171 For every name in lsst.meas.base.getApCorrNameSet():
172 - If the corresponding flux fields exist in the schema:
173 - Add a new field apcorr_{name}_used
174 - Add an entry to the self.toCorrect dict
175 - Otherwise silently skip the name
177 Task.__init__(self, **kwds)
180 for name
in getApCorrNameSet():
186 self.makeSubtask(
"sourceSelector")
188 def run(self, exposure, catalog):
189 """!Measure aperture correction
191 @param[in] exposure Exposure aperture corrections are being measured
192 on. The bounding box is retrieved from it, and
193 it is passed to the sourceSelector.
194 The output aperture correction map is *not*
195 added to the exposure; this is left to the
198 @param[in] catalog SourceCatalog containing measurements to be used
199 to compute aperturecorrections.
201 @return an lsst.pipe.base.Struct containing:
202 - apCorrMap: an aperture correction map (lsst.afw.image.ApCorrMap) that contains two entries
204 - flux field (e.g. base_PsfFlux_instFlux): 2d model
205 - flux sigma field (e.g. base_PsfFlux_instFluxErr): 2d model of error
207 bbox = exposure.getBBox()
212 self.log.info(
"Measuring aperture corrections for %d flux fields" % (len(self.
toCorrect),))
215 subset1 = [record
for record
in self.sourceSelector.
run(catalog, exposure=exposure).sourceCat
217 and numpy.isfinite(record.get(self.
refFluxKeys.flux)))]
222 for name, keys
in self.
toCorrect.items():
223 fluxName = name +
"_instFlux"
224 fluxErrName = name +
"_instFluxErr"
228 fluxes = numpy.fromiter((record.get(keys.flux)
for record
in subset1), float)
229 with numpy.errstate(invalid=
"ignore"):
230 isGood = numpy.logical_and.reduce([
231 numpy.fromiter((
not record.get(keys.flag)
for record
in subset1), bool),
232 numpy.isfinite(fluxes),
235 subset2 = [record
for record, good
in zip(subset1, isGood)
if good]
239 if len(subset2) - 1 < self.config.minDegreesOfFreedom:
240 if name
in self.config.allowFailure:
241 self.log.warn(
"Unable to measure aperture correction for '%s': "
242 "only %d sources, but require at least %d." %
243 (name, len(subset2), self.config.minDegreesOfFreedom+1))
245 raise RuntimeError(
"Unable to measure aperture correction for required algorithm '%s': "
246 "only %d sources, but require at least %d." %
247 (name, len(subset2), self.config.minDegreesOfFreedom+1))
250 ctrl = self.config.fitConfig.makeControl()
251 while len(subset2) - ctrl.computeSize() < self.config.minDegreesOfFreedom:
258 x = numpy.zeros(len(subset2), dtype=float)
259 y = numpy.zeros(len(subset2), dtype=float)
260 apCorrData = numpy.zeros(len(subset2), dtype=float)
261 indices = numpy.arange(len(subset2), dtype=int)
262 for n, record
in enumerate(subset2):
265 apCorrData[n] = record.get(self.
refFluxKeys.flux)/record.get(keys.flux)
267 for _i
in range(self.config.numIter):
270 apCorrField = ChebyshevBoundedField.fit(bbox, x, y, apCorrData, ctrl)
273 plotApCorr(bbox, x, y, apCorrData, apCorrField,
"%s, iteration %d" % (name, _i), doPause)
277 apCorrDiffs = apCorrField.evaluate(x, y)
278 apCorrDiffs -= apCorrData
279 apCorrErr = numpy.mean(apCorrDiffs**2)**0.5
282 apCorrDiffLim = self.config.numSigmaClip * apCorrErr
283 with numpy.errstate(invalid=
"ignore"):
284 keep = numpy.fabs(apCorrDiffs) <= apCorrDiffLim
287 apCorrData = apCorrData[keep]
288 indices = indices[keep]
291 apCorrField = ChebyshevBoundedField.fit(bbox, x, y, apCorrData, ctrl)
293 self.log.info(
"Aperture correction for %s: RMS %f from %d" %
294 (name, numpy.mean((apCorrField.evaluate(x, y) - apCorrData)**2)**0.5, len(indices)))
297 plotApCorr(bbox, x, y, apCorrData, apCorrField,
"%s, final" % (name,), doPause)
303 apCorrMap[fluxName] = apCorrField
304 apCorrErrCoefficients = numpy.array([[apCorrErr]], dtype=float)
309 subset2[i].set(keys.used,
True)
316 def plotApCorr(bbox, xx, yy, zzMeasure, field, title, doPause):
317 """Plot aperture correction fit residuals
319 There are two subplots: residuals against x and y.
321 Intended for debugging.
323 @param bbox Bounding box (for bounds)
324 @param xx x coordinates
325 @param yy y coordinates
326 @param zzMeasure Measured value of the aperture correction
327 @param field Fit aperture correction field
328 @param title Title for plot
329 @param doPause Pause to inspect the residuals plot? If False,
330 there will be a 4 second delay to allow for
331 inspection of the plot before closing it and
334 import matplotlib.pyplot
as plt
336 zzFit = field.evaluate(xx, yy)
337 residuals = zzMeasure - zzFit
339 fig, axes = plt.subplots(2, 1)
341 axes[0].scatter(xx, residuals, s=3, marker=
'o', lw=0, alpha=0.7)
342 axes[1].scatter(yy, residuals, s=3, marker=
'o', lw=0, alpha=0.7)
344 ax.set_ylabel(
"ApCorr Fit Residual")
345 ax.set_ylim(0.9*residuals.min(), 1.1*residuals.max())
346 axes[0].set_xlabel(
"x")
347 axes[0].set_xlim(bbox.getMinX(), bbox.getMaxX())
348 axes[1].set_xlabel(
"y")
349 axes[1].set_xlim(bbox.getMinY(), bbox.getMaxY())
357 print(
"%s: plt.pause() failed. Please close plots when done." % __name__)
360 print(
"%s: Please close plots when done." % __name__)