24__all__ = (
"MeasureApCorrConfig",
"MeasureApCorrTask")
30from lsst.afw.math import ChebyshevBoundedField, ChebyshevBoundedFieldConfig
34from .sourceSelector
import sourceSelectorRegistry
38 """A collection of keys for a given flux measurement algorithm
40 __slots__ = ("flux",
"err",
"flag",
"used")
43 """Construct a FluxKeys
48 Name of flux measurement algorithm, e.g. "base_PsfFlux"
50 Catalog schema containing the flux field
51 read: {name}_instFlux, {name}_instFluxErr, {name}_flag
52 added: apcorr_{name}_used
54 self.flux = schema.find(name + "_instFlux").key
55 self.
err = schema.find(name +
"_instFluxErr").key
56 self.
flag = schema.find(name +
"_flag").key
57 self.
used = schema.addField(
"apcorr_" + name +
"_used", type=
"Flag",
58 doc=
"set if source was used in measuring aperture correction")
62 """Configuration for MeasureApCorrTask
64 refFluxName = lsst.pex.config.Field(
65 doc="Field name prefix for the flux other measurements should be aperture corrected to match",
67 default=
"slot_CalibFlux",
69 sourceSelector = sourceSelectorRegistry.makeField(
70 doc=
"Selector that sets the stars that aperture corrections will be measured from.",
73 minDegreesOfFreedom = lsst.pex.config.RangeField(
74 doc=
"Minimum number of degrees of freedom (# of valid data points - # of parameters);"
75 " if this is exceeded, the order of the fit is decreased (in both dimensions), and"
76 " if we can't decrease it enough, we'll raise ValueError.",
81 fitConfig = lsst.pex.config.ConfigField(
82 doc=
"Configuration used in fitting the aperture correction fields",
83 dtype=ChebyshevBoundedFieldConfig,
85 numIter = lsst.pex.config.Field(
86 doc=
"Number of iterations for sigma clipping",
90 numSigmaClip = lsst.pex.config.Field(
91 doc=
"Number of standard devisations to clip at",
95 allowFailure = lsst.pex.config.ListField(
96 doc=
"Allow these measurement algorithms to fail without an exception",
102 lsst.pex.config.Config.validate(self)
104 raise lsst.pex.config.FieldValidationError(
105 MeasureApCorrConfig.sourceSelector,
107 "Star selectors that require matches are not permitted")
111 r"""Task to measure aperture correction
113 ConfigClass = MeasureApCorrConfig
114 _DefaultName = "measureApCorr"
117 """Construct a MeasureApCorrTask
119 For every name in lsst.meas.base.getApCorrNameSet():
120 - If the corresponding flux fields exist
in the schema:
121 - Add a new field apcorr_{name}_used
122 - Add an entry to the self.
toCorrect dict
123 - Otherwise silently skip the name
125 Task.__init__(self, **kwds)
128 for name
in sorted(getApCorrNameSet()):
134 self.makeSubtask(
"sourceSelector")
136 def run(self, exposure, catalog):
137 """Measure aperture correction
142 Exposure aperture corrections are being measured on. The
143 bounding box is retrieved
from it,
and it
is passed to the
144 sourceSelector. The output aperture correction map
is *
not*
145 added to the exposure; this
is left to the caller.
147 SourceCatalog containing measurements to be used to
148 compute aperture corrections.
152 Struct : `lsst.pipe.base.Struct`
153 Contains the following:
157 that contains two entries
for each flux field:
158 - flux field (e.g. base_PsfFlux_instFlux): 2d model
159 - flux sigma field (e.g. base_PsfFlux_instFluxErr): 2d model of error
161 bbox = exposure.getBBox()
166 self.log.info(
"Measuring aperture corrections for %d flux fields", len(self.
toCorrect))
169 subset1 = [record
for record
in self.sourceSelector.
run(catalog, exposure=exposure).sourceCat
171 and numpy.isfinite(record.get(self.
refFluxKeys.flux)))]
176 for name, keys
in self.
toCorrect.items():
177 fluxName = name +
"_instFlux"
178 fluxErrName = name +
"_instFluxErr"
182 fluxes = numpy.fromiter((record.get(keys.flux)
for record
in subset1), float)
183 with numpy.errstate(invalid=
"ignore"):
184 isGood = numpy.logical_and.reduce([
185 numpy.fromiter((
not record.get(keys.flag)
for record
in subset1), bool),
186 numpy.isfinite(fluxes),
189 subset2 = [record
for record, good
in zip(subset1, isGood)
if good]
193 if len(subset2) - 1 < self.config.minDegreesOfFreedom:
194 if name
in self.config.allowFailure:
195 self.log.warning(
"Unable to measure aperture correction for '%s': "
196 "only %d sources, but require at least %d.",
197 name, len(subset2), self.config.minDegreesOfFreedom + 1)
199 raise RuntimeError(
"Unable to measure aperture correction for required algorithm '%s': "
200 "only %d sources, but require at least %d." %
201 (name, len(subset2), self.config.minDegreesOfFreedom + 1))
204 ctrl = self.config.fitConfig.makeControl()
205 while len(subset2) - ctrl.computeSize() < self.config.minDegreesOfFreedom:
212 x = numpy.zeros(len(subset2), dtype=float)
213 y = numpy.zeros(len(subset2), dtype=float)
214 apCorrData = numpy.zeros(len(subset2), dtype=float)
215 indices = numpy.arange(len(subset2), dtype=int)
216 for n, record
in enumerate(subset2):
219 apCorrData[n] = record.get(self.
refFluxKeys.flux)/record.get(keys.flux)
221 for _i
in range(self.config.numIter):
224 apCorrField = ChebyshevBoundedField.fit(bbox, x, y, apCorrData, ctrl)
227 plotApCorr(bbox, x, y, apCorrData, apCorrField,
"%s, iteration %d" % (name, _i), doPause)
231 apCorrDiffs = apCorrField.evaluate(x, y)
232 apCorrDiffs -= apCorrData
233 apCorrErr = numpy.mean(apCorrDiffs**2)**0.5
236 apCorrDiffLim = self.config.numSigmaClip * apCorrErr
237 with numpy.errstate(invalid=
"ignore"):
238 keep = numpy.fabs(apCorrDiffs) <= apCorrDiffLim
241 apCorrData = apCorrData[keep]
242 indices = indices[keep]
245 apCorrField = ChebyshevBoundedField.fit(bbox, x, y, apCorrData, ctrl)
247 self.log.info(
"Aperture correction for %s: RMS %f from %d",
248 name, numpy.mean((apCorrField.evaluate(x, y) - apCorrData)**2)**0.5, len(indices))
251 plotApCorr(bbox, x, y, apCorrData, apCorrField,
"%s, final" % (name,), doPause)
257 apCorrMap[fluxName] = apCorrField
258 apCorrErrCoefficients = numpy.array([[apCorrErr]], dtype=float)
263 subset2[i].set(keys.used,
True)
270def plotApCorr(bbox, xx, yy, zzMeasure, field, title, doPause):
271 """Plot aperture correction fit residuals
273 There are two subplots: residuals against x and y.
275 Intended
for debugging.
280 Bounding box (
for bounds)
281 xx, yy : `numpy.ndarray`, (N)
284 Measured value of the aperture correction
286 Fit aperture correction field
290 Pause to inspect the residuals plot? If
291 False, there will be a 4 second delay to
292 allow
for inspection of the plot before
293 closing it
and moving on.
295 import matplotlib.pyplot
as plt
297 zzFit = field.evaluate(xx, yy)
298 residuals = zzMeasure - zzFit
300 fig, axes = plt.subplots(2, 1)
302 axes[0].scatter(xx, residuals, s=3, marker=
'o', lw=0, alpha=0.7)
303 axes[1].scatter(yy, residuals, s=3, marker=
'o', lw=0, alpha=0.7)
305 ax.set_ylabel(
"ApCorr Fit Residual")
306 ax.set_ylim(0.9*residuals.min(), 1.1*residuals.max())
307 axes[0].set_xlabel(
"x")
308 axes[0].set_xlim(bbox.getMinX(), bbox.getMaxX())
309 axes[1].set_xlabel(
"y")
310 axes[1].set_xlim(bbox.getMinY(), bbox.getMaxY())
318 print(
"%s: plt.pause() failed. Please close plots when done." % __name__)
321 print(
"%s: Please close plots when done." % __name__)
def __init__(self, name, schema)
def run(self, exposure, catalog)
def __init__(self, schema, **kwds)
def plotApCorr(bbox, xx, yy, zzMeasure, field, title, doPause)