170 def run(self, exposure, catalog):
171 """Measure aperture correction
175 exposure : `lsst.afw.image.Exposure`
176 Exposure aperture corrections are being measured on. The
177 bounding box is retrieved from it, and it is passed to the
178 sourceSelector. The output aperture correction map is *not*
179 added to the exposure; this is left to the caller.
180 catalog : `lsst.afw.table.SourceCatalog`
181 SourceCatalog containing measurements to be used to
182 compute aperture corrections.
186 Struct : `lsst.pipe.base.Struct`
187 Contains the following:
190 aperture correction map (`lsst.afw.image.ApCorrMap`)
191 that contains two entries for each flux field:
192 - flux field (e.g. base_PsfFlux_instFlux): 2d model
193 - flux sigma field (e.g. base_PsfFlux_instFluxErr): 2d model of error
195 bbox = exposure.getBBox()
200 self.log.info(
"Measuring aperture corrections for %d flux fields", len(self.
toCorrect))
204 selected = self.sourceSelector.
run(catalog, exposure=exposure)
208 & (np.isfinite(selected.sourceCat[self.
refFluxNames.fluxName]))
210 goodRefCat = selected.sourceCat[use].copy()
215 for name, fluxNames
in self.
toCorrect.items():
218 fluxes = goodRefCat[fluxNames.fluxName]
219 with np.errstate(invalid=
"ignore"):
221 (~goodRefCat[fluxNames.flagName])
222 & (np.isfinite(fluxes))
228 if (isGood.sum() - 1) < self.config.minDegreesOfFreedom:
229 if name
in self.config.allowFailure:
230 self.log.warning(
"Unable to measure aperture correction for '%s': "
231 "only %d sources, but require at least %d.",
232 name, isGood.sum(), self.config.minDegreesOfFreedom + 1)
235 msg = (
"Unable to measure aperture correction for required algorithm '%s': "
236 "only %d sources, but require at least %d." %
237 (name, isGood.sum(), self.config.minDegreesOfFreedom + 1))
238 self.log.warning(msg)
241 goodCat = goodRefCat[isGood].copy()
243 x = goodCat[
'slot_Centroid_x']
244 y = goodCat[
'slot_Centroid_y']
245 z = goodCat[self.
refFluxNames.fluxName]/goodCat[fluxNames.fluxName]
250 fitValues = np.median(z)
252 ctrl = self.config.fitConfig.makeControl()
255 for iteration
in range(self.config.numIter):
256 resid = z - fitValues
260 apCorrErr = median_abs_deviation(resid, scale=
"normal") + 1e-7
261 keep = np.abs(resid) <= self.config.numSigmaClip * apCorrErr
263 self.log.debug(
"Removing %d sources as outliers.", len(resid) - keep.sum())
270 while (len(x) - ctrl.computeSize()) < self.config.minDegreesOfFreedom:
283 if name
in self.config.allowFailure:
284 self.log.warning(
"Unable to measure aperture correction for '%s': "
285 "only %d sources remain, but require at least %d." %
286 (name, keep.sum(), self.config.minDegreesOfFreedom + 1))
289 msg = (
"Unable to measure aperture correction for required algorithm "
290 "'%s': only %d sources remain, but require at least %d." %
291 (name, keep.sum(), self.config.minDegreesOfFreedom + 1))
292 self.log.warning(msg)
295 apCorrField = ChebyshevBoundedField.fit(bbox, x, y, z, ctrl)
296 fitValues = apCorrField.evaluate(x, y)
302 "Aperture correction for %s from %d stars: MAD %f, RMS %f",
304 median_abs_deviation(fitValues - z, scale=
"normal"),
305 np.mean((fitValues - z)**2.)**0.5,
310 plotApCorr(bbox, x, y, z, apCorrField,
"%s, final" % (name,), doPause)
313 used = np.zeros(len(catalog), dtype=bool)
314 used[np.searchsorted(catalog[
'id'], ids)] =
True
315 catalog[fluxNames.usedName] = used
321 apCorrMap[fluxNames.fluxName] = apCorrField
324 np.array([[apCorrErr]]),