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
48 Name of flux measurement algorithm, e.g. "base_PsfFlux"
49 schema : `lsst.afw.table.Schema`
50 Catalog schema containing the flux field
51 read: {name}_instFlux, {name}_instFluxErr, {name}_flag
52 added: apcorr_{name}_used
54 self.
fluxflux = schema.find(name +
"_instFlux").key
55 self.
errerr = schema.find(name +
"_instFluxErr").key
56 self.
flagflag = schema.find(name +
"_flag").key
57 self.
usedused = schema.addField(
"apcorr_" + name +
"_used", type=
"Flag",
58 doc=
"set if source was used in measuring aperture correction")
62 """Configuration for MeasureApCorrTask
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.",
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.",
82 doc=
"Configuration used in fitting the aperture correction fields",
83 dtype=ChebyshevBoundedFieldConfig,
86 doc=
"Number of iterations for sigma clipping",
91 doc=
"Number of standard devisations to clip at",
96 doc=
"Allow these measurement algorithms to fail without an exception",
102 lsst.pex.config.Config.validate(self)
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)
134 self.makeSubtask(
"sourceSelector")
136 def run(self, exposure, catalog):
137 """Measure aperture correction
141 exposure : `lsst.afw.image.Exposure`
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.
146 catalog : `lsst.afw.table.SourceCatalog`
147 SourceCatalog containing measurements to be used to
148 compute aperture corrections.
152 Struct : `lsst.pipe.base.Struct`
153 Contains the following:
156 aperture correction map (`lsst.afw.image.ApCorrMap`)
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.
toCorrecttoCorrect))
169 subset1 = [record
for record
in self.sourceSelector.
run(catalog, exposure=exposure).sourceCat
170 if (
not record.get(self.
refFluxKeysrefFluxKeys.flag)
171 and numpy.isfinite(record.get(self.
refFluxKeysrefFluxKeys.flux)))]
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.
refFluxKeysrefFluxKeys.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)
270 def 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.
279 bbox : `lsst.geom.Box2I`
280 Bounding box (for bounds)
281 xx, yy : `numpy.ndarray`, (N)
284 Measured value of the aperture correction
285 field : `lsst.afw.math.ChebyshevBoundedField`
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__)
std::vector< SchemaItem< Flag > > * items
A thin wrapper around std::map to allow aperture corrections to be attached to Exposures.
A BoundedField based on 2-d Chebyshev polynomials of the first kind.
def __init__(self, name, schema)
def run(self, exposure, catalog)
def __init__(self, schema, **kwds)
daf::base::PropertySet * set
Backwards-compatibility support for depersisting the old Calib (FluxMag0/FluxMag0Err) objects.
def plotApCorr(bbox, xx, yy, zzMeasure, field, title, doPause)