171 def run(self, exposure, catalog):
172 """Measure aperture correction
176 exposure : `lsst.afw.image.Exposure`
177 Exposure aperture corrections are being measured on. The
178 bounding box is retrieved from it, and it is passed to the
179 sourceSelector. The output aperture correction map is *not*
180 added to the exposure; this is left to the caller.
181 catalog : `lsst.afw.table.SourceCatalog`
182 SourceCatalog containing measurements to be used to
183 compute aperture corrections.
187 Struct : `lsst.pipe.base.Struct`
188 Contains the following:
191 aperture correction map (`lsst.afw.image.ApCorrMap`)
192 that contains two entries for each flux field:
193 - flux field (e.g. base_PsfFlux_instFlux): 2d model
194 - flux sigma field (e.g. base_PsfFlux_instFluxErr): 2d model of error
196 bbox = exposure.getBBox()
201 self.log.info(
"Measuring aperture corrections for %d flux fields", len(self.
toCorrect))
205 selected = self.sourceSelector.
run(catalog, exposure=exposure)
209 & (np.isfinite(selected.sourceCat[self.
refFluxNames.fluxName]))
211 goodRefCat = selected.sourceCat[use].copy()
216 for name, fluxNames
in self.
toCorrect.items():
219 fluxes = goodRefCat[fluxNames.fluxName]
220 with np.errstate(invalid=
"ignore"):
222 (~goodRefCat[fluxNames.flagName])
223 & (np.isfinite(fluxes))
229 if (isGood.sum() - 1) < self.config.minDegreesOfFreedom:
230 if name
in self.config.allowFailure:
231 self.log.warning(
"Unable to measure aperture correction for '%s': "
232 "only %d sources, but require at least %d.",
233 name, isGood.sum(), self.config.minDegreesOfFreedom + 1)
236 msg = (
"Unable to measure aperture correction for required algorithm '%s': "
237 "only %d sources, but require at least %d." %
238 (name, isGood.sum(), self.config.minDegreesOfFreedom + 1))
239 self.log.warning(msg)
242 goodCat = goodRefCat[isGood].copy()
244 x = goodCat[
'slot_Centroid_x']
245 y = goodCat[
'slot_Centroid_y']
246 z = goodCat[self.
refFluxNames.fluxName]/goodCat[fluxNames.fluxName]
251 fitValues = np.median(z)
253 ctrl = self.config.fitConfig.makeControl()
256 for iteration
in range(self.config.numIter):
257 resid = z - fitValues
261 apCorrErr = median_abs_deviation(resid, scale=
"normal") + 1e-7
262 keep = np.abs(resid) <= self.config.numSigmaClip * apCorrErr
264 self.log.debug(
"Removing %d sources as outliers.", len(resid) - keep.sum())
271 while (len(x) - ctrl.computeSize()) < self.config.minDegreesOfFreedom:
284 if name
in self.config.allowFailure:
285 self.log.warning(
"Unable to measure aperture correction for '%s': "
286 "only %d sources remain, but require at least %d." %
287 (name, keep.sum(), self.config.minDegreesOfFreedom + 1))
290 msg = (
"Unable to measure aperture correction for required algorithm "
291 "'%s': only %d sources remain, but require at least %d." %
292 (name, keep.sum(), self.config.minDegreesOfFreedom + 1))
293 self.log.warning(msg)
296 apCorrField = ChebyshevBoundedField.fit(bbox, x, y, z, ctrl)
297 fitValues = apCorrField.evaluate(x, y)
303 "Aperture correction for %s from %d stars: MAD %f, RMS %f",
305 median_abs_deviation(fitValues - z, scale=
"normal"),
306 np.mean((fitValues - z)**2.)**0.5,
311 plotApCorr(bbox, x, y, z, apCorrField,
"%s, final" % (name,), doPause)
314 used = np.zeros(len(catalog), dtype=bool)
315 used[np.searchsorted(catalog[
'id'], ids)] =
True
316 catalog[fluxNames.usedName] = used
322 apCorrMap[fluxNames.fluxName] = apCorrField
325 np.array([[apCorrErr]]),