225 calExpOrder = {ref.dataId[
"detector"]
for ref
in inputRefs.calExps}
226 calBkgOrder = {ref.dataId[
"detector"]
for ref
in inputRefs.calBkgs}
227 detectorOrder = calExpOrder & calBkgOrder
228 if self.config.doApplyFlatBackgroundRatio:
229 ratioOrder = {ref.dataId[
"detector"]
for ref
in inputRefs.backgroundToPhotometricRatioHandles}
230 detectorOrder &= ratioOrder
231 if self.config.doSky:
232 skyFrameOrder = {ref.dataId[
"detector"]
for ref
in inputRefs.skyFrames}
233 detectorOrder &= skyFrameOrder
234 detectorOrder = sorted(detectorOrder)
236 inputRefs.calExps, [ref.dataId[
"detector"]
for ref
in inputRefs.calExps], detectorOrder
239 inputRefs.calBkgs, [ref.dataId[
"detector"]
for ref
in inputRefs.calBkgs], detectorOrder
242 if self.config.doSky:
244 inputRefs.skyFrames, [ref.dataId[
"detector"]
for ref
in inputRefs.skyFrames], detectorOrder
247 inputRefs.skyFrames = []
249 if self.config.doApplyFlatBackgroundRatio:
251 inputRefs.backgroundToPhotometricRatioHandles,
252 [ref.dataId[
"detector"]
for ref
in inputRefs.backgroundToPhotometricRatioHandles],
256 inputRefs.backgroundToPhotometricRatioHandles = []
258 outputRefs.skyCorr, [ref.dataId[
"detector"]
for ref
in outputRefs.skyCorr], detectorOrder
260 inputs = butlerQC.get(inputRefs)
261 outputs = self.
run(**inputs)
262 butlerQC.put(outputs, outputRefs)
264 def run(self, calExps, calBkgs, skyFrames, camera, backgroundToPhotometricRatioHandles=[]):
265 """Perform sky correction on a visit.
267 The original visit-level background is first restored to the calibrated
268 exposure and the existing background model is inverted in-place. If
269 doMaskObjects is True, the mask map associated with this exposure will
270 be iteratively updated (over nIter loops) by re-estimating the
271 background each iteration and redetecting footprints.
273 An initial full focal plane sky subtraction (bgModel1) will take place
274 prior to scaling and subtracting the sky frame.
276 If doSky is True, the sky frame will be scaled to the flux in the input
279 If doBgModel2 is True, a final full focal plane sky subtraction will
280 take place after the sky frame has been subtracted.
282 The first N elements of the returned skyCorr will consist of inverted
283 elements of the calexpBackground model (i.e., subtractive). All
284 subsequent elements appended to skyCorr thereafter will be additive
285 such that, when skyCorr is subtracted from a calexp, the net result
286 will be to undo the initial per-detector background solution and then
287 apply the skyCorr model thereafter. Adding skyCorr to a
288 calexpBackground will effectively negate the calexpBackground,
289 returning only the additive background components of the skyCorr
294 calExps : `list` [`lsst.afw.image.ExposureF`]
295 Detector calibrated exposure images for the visit.
296 calBkgs : `list` [`lsst.afw.math.BackgroundList`]
297 Detector background lists matching the calibrated exposures.
298 skyFrames : `list` [`lsst.afw.image.ExposureF`]
299 Sky frame calibration data for the input detectors.
300 camera : `lsst.afw.cameraGeom.Camera`
301 Camera matching the input data to process.
302 backgroundToPhotometricRatioHandles :
303 `list` [`lsst.daf.butler.DeferredDatasetHandle`], optional
304 Deferred dataset handles pointing to the Background to photometric
305 ratio images for the input detectors.
309 results : `Struct` containing:
310 skyFrameScale : `float`
311 Scale factor applied to the sky frame.
312 skyCorr : `list` [`lsst.afw.math.BackgroundList`]
313 Detector-level sky correction background lists.
314 calExpMosaic : `lsst.afw.image.ExposureF`
315 Visit-level mosaic of the sky corrected data, binned.
316 Analogous to `calexp - skyCorr`.
317 calBkgMosaic : `lsst.afw.image.ExposureF`
318 Visit-level mosaic of the sky correction background, binned.
319 Analogous to `calexpBackground + skyCorr`.
323 bgModel1 = FocalPlaneBackground.fromCamera(self.config.bgModel1, camera)
328 if not self.config.doApplyFlatBackgroundRatio:
329 backgroundToPhotometricRatioHandles = [
None] * len(calExps)
330 for calExpHandle, calBkg, backgroundToPhotometricRatioHandle
in zip(
331 calExps, calBkgs, backgroundToPhotometricRatioHandles
334 calExpHandle, backgroundToPhotometricRatioHandle=backgroundToPhotometricRatioHandle
336 detectors.append(calExp.getDetector())
340 masks.append(calExp.mask)
341 skyCorrs.append(calBkg)
342 bgModel1Indices.append(len(calBkg))
345 bgModel1Detector = FocalPlaneBackground.fromCamera(self.config.bgModel1, camera)
346 bgModel1Detector.addCcd(calExp)
347 bgModel1.merge(bgModel1Detector)
349 "Detector %d: Merged %d unmasked pixels (%.1f%s of detector area) into initial BG model",
350 calExp.getDetector().getId(),
351 bgModel1Detector._numbers.getArray().sum(),
352 100 * bgModel1Detector._numbers.getArray().sum() / calExp.getBBox().getArea(),
360 for detector, skyCorr
in zip(detectors, skyCorrs):
361 with warnings.catch_warnings():
362 warnings.filterwarnings(
"ignore",
"invalid value encountered")
363 calBkgElement = bgModel1.toCcdBackground(detector, detector.getBBox())
364 skyCorr.append(calBkgElement[0])
368 if self.config.doSky:
370 calExps, masks, skyCorrs, skyFrames, backgroundToPhotometricRatioHandles
374 if self.config.undoBgModel1:
375 for skyCorr, bgModel1Index
in zip(skyCorrs, bgModel1Indices):
376 skyCorr._backgrounds.pop(bgModel1Index)
378 "Initial background models (bgModel1s) have been removed from all skyCorr background lists",
382 if self.config.doBgModel2:
383 bgModel2 = FocalPlaneBackground.fromCamera(self.config.bgModel2, camera)
384 for calExpHandle, mask, skyCorr, backgroundToPhotometricRatioHandle
in zip(
385 calExps, masks, skyCorrs, backgroundToPhotometricRatioHandles
387 calExp = self.
_getCalExp(calExpHandle, mask, skyCorr, backgroundToPhotometricRatioHandle)
390 bgModel2Detector = FocalPlaneBackground.fromCamera(self.config.bgModel2, camera)
391 bgModel2Detector.addCcd(calExp)
392 bgModel2.merge(bgModel2Detector)
394 "Detector %d: Merged %d unmasked pixels (%.1f%s of detector area) into final BG model",
395 calExp.getDetector().getId(),
396 bgModel2Detector._numbers.getArray().sum(),
397 100 * bgModel2Detector._numbers.getArray().sum() / calExp.getBBox().getArea(),
405 for detector, skyCorr
in zip(detectors, skyCorrs):
406 with warnings.catch_warnings():
407 warnings.filterwarnings(
"ignore",
"invalid value encountered")
408 calBkgElement = bgModel2.toCcdBackground(detector, detector.getBBox())
409 skyCorr.append(calBkgElement[0])
414 for calExpHandle, mask, skyCorr, backgroundToPhotometricRatioHandle, bgModel1Index
in zip(
415 calExps, masks, skyCorrs, backgroundToPhotometricRatioHandles, bgModel1Indices
417 calExp = self.
_getCalExp(calExpHandle, mask, skyCorr, backgroundToPhotometricRatioHandle)
419 skyCorrExtra = skyCorr.clone()
420 skyCorrExtra._backgrounds = skyCorrExtra._backgrounds[bgModel1Index:]
421 skyCorrExtraMI = makeMaskedImage(skyCorrExtra.getImage())
422 skyCorrExtraMI.setMask(calExp.getMask())
424 calExpsBinned.append(binImage(calExp.getMaskedImage(), self.config.binning))
425 calBkgsBinned.append(binImage(skyCorrExtraMI, self.config.binning))
428 mosConfig.binning = self.config.binning
430 detectorIds = [detector.getId()
for detector
in detectors]
431 calExpMosaic = mosTask.run(calExpsBinned, camera, inputIds=detectorIds).outputData
432 calBkgMosaic = mosTask.run(calBkgsBinned, camera, inputIds=detectorIds).outputData
435 skyFrameScale=skyFrameScale,
437 calExpMosaic=calExpMosaic,
438 calBkgMosaic=calBkgMosaic,
441 def _getCalExp(self, calExpHandle, mask=None, skyCorr=None, backgroundToPhotometricRatioHandle=None):
442 """Get a calexp from a DeferredDatasetHandle, and optionally apply an
443 updated mask and skyCorr.
447 calExpHandle : `~lsst.afw.image.ExposureF`
448 | `lsst.daf.butler.DeferredDatasetHandle`
449 Either the image exposure data or a handle to the calexp dataset.
450 mask : `lsst.afw.image.Mask`, optional
451 Mask to apply to the calexp.
452 skyCorr : `lsst.afw.math.BackgroundList`, optional
453 Background list to subtract from the calexp.
457 calExp : `lsst.afw.image.ExposureF`
458 The calexp with the mask and skyCorr applied.
460 if isinstance(calExpHandle, DeferredDatasetHandle):
461 calExp: ExposureF = calExpHandle.get()
465 calExp: ExposureF = calExpHandle.clone()
470 if self.config.doApplyFlatBackgroundRatio:
471 if not backgroundToPhotometricRatioHandle:
473 "A list of backgroundToPhotometricRatioHandles must be supplied if "
474 "config.doApplyFlatBackgroundRatio=True.",
476 ratioImage = backgroundToPhotometricRatioHandle.get()
477 calExp.maskedImage *= ratioImage
479 "Detector %d: Converted background-flattened image to a photometric-flattened image",
480 calExp.getDetector().getId(),
485 if skyCorr
is not None:
486 image = calExp.getMaskedImage()
487 image -= skyCorr.getImage()
512 """Restore original background to a calexp and invert the related
513 background model; optionally refine the mask plane.
515 The original visit-level background is restored to the calibrated
516 exposure and the existing background model is inverted in-place. If
517 doMaskObjects is True, the mask map associated with the exposure will
518 be iteratively updated (over nIter loops) by re-estimating the
519 background each iteration and redetecting footprints.
521 The background model modified in-place in this method will comprise the
522 first N elements of the skyCorr dataset type, i.e., these N elements
523 are the inverse of the calexpBackground model. All subsequent elements
524 appended to skyCorr will be additive such that, when skyCorr is
525 subtracted from a calexp, the net result will be to undo the initial
526 per-detector background solution and then apply the skyCorr model
527 thereafter. Adding skyCorr to a calexpBackground will effectively
528 negate the calexpBackground, returning only the additive background
529 components of the skyCorr background model.
533 calExp : `lsst.afw.image.ExposureF`
534 Detector level calexp image.
535 calBkg : `lsst.afw.math.BackgroundList`
536 Detector level background lists associated with the calexp.
540 calExp : `lsst.afw.image.ExposureF`
541 The calexp with the originally subtracted background restored.
542 skyCorrBase : `lsst.afw.math.BackgroundList`
543 The inverted original background models; the genesis for skyCorr.
545 image = calExp.getMaskedImage()
548 for calBkgElement
in calBkg:
549 statsImage = calBkgElement[0].getStatsImage()
551 skyCorrBase = calBkg.getImage()
554 stats = np.nanpercentile(skyCorrBase.array, [50, 75, 25])
556 "Detector %d: Original background restored (BG median = %.1f counts, BG IQR = %.1f counts)",
557 calExp.getDetector().getId(),
559 np.subtract(*stats[1:]),
563 if self.config.doMaskObjects:
564 maskFrac0 = 1 - np.sum(calExp.mask.array == 0) / calExp.mask.array.size
565 self.maskObjects.findObjects(calExp)
566 maskFrac1 = 1 - np.sum(calExp.mask.array == 0) / calExp.mask.array.size
569 "Detector %d: Iterative source detection and mask growth has increased masked area by %.1f%%",
570 calExp.getDetector().getId(),
571 (100 * (maskFrac1 - maskFrac0)),
574 return calExp, skyCorrBase
577 """Check that the background model contains enough valid superpixels,
578 and raise a useful error if not.
583 Identifier for the background model.
584 bgModel : `~lsst.pipe.tasks.background.FocalPlaneBackground`
585 Background model to check.
586 config : `~lsst.pipe.tasks.background.FocalPlaneBackgroundConfig`
587 Configuration used to create the background model.
589 bgModelArray = bgModel._numbers.getArray()
590 spArea = (config.xSize / config.pixelSize) * (config.ySize / config.pixelSize)
592 "%s: FP background model constructed using %.2f x %.2f mm (%d x %d pixel) superpixels",
596 int(config.xSize / config.pixelSize),
597 int(config.ySize / config.pixelSize),
600 "%s: Pixel data exists in %d of %d superpixels; the most populated superpixel is %.1f%% filled",
602 np.sum(bgModelArray > 0),
604 100 * np.max(bgModelArray) / spArea,
607 thresh = config.minFrac * spArea
608 if np.all(bgModelArray < thresh):
610 f
"No background model superpixels are more than {100*config.minFrac}% filled. "
611 "Try decreasing the minFrac configuration parameter, optimizing the subset of detectors "
612 "being processed, or increasing the number of detectors being processed."
615 with warnings.catch_warnings():
616 warnings.filterwarnings(
"ignore",
r"invalid value encountered")
617 stats = np.nanpercentile(bgModel.getStatsImage().array, [50, 75, 25])
619 "%s: FP BG median = %.1f counts, FP BG IQR = %.1f counts",
622 np.subtract(*stats[1:]),
625 def _fitSkyFrame(self, calExps, masks, skyCorrs, skyFrames, backgroundToPhotometricRatioHandles):
626 """Determine the full focal plane sky frame scale factor relative to
627 an input list of calibrated exposures.
629 This method measures the sky frame scale on all inputs, resulting in
630 values equal to the background method solveScales().
632 Input skyCorrs are updated in-place.
636 calExps : `list` [`lsst.afw.image.ExposureF`]
637 Calibrated exposures to be background subtracted.
638 masks : `list` [`lsst.afw.image.Mask`]
639 Masks associated with the input calibrated exposures.
640 skyCorrs : `list` [`lsst.afw.math.BackgroundList`]
641 Background lists associated with the input calibrated exposures.
642 skyFrames : `list` [`lsst.afw.image.ExposureF`]
643 Sky frame calibration data for the input detectors.
648 Fitted scale factor applied to the sky frame.
652 for calExpHandle, mask, skyCorr, skyFrameHandle, backgroundToPhotometricRatioHandle
in zip(
653 calExps, masks, skyCorrs, skyFrames, backgroundToPhotometricRatioHandles
655 calExp = self.
_getCalExp(calExpHandle, mask, skyCorr, backgroundToPhotometricRatioHandle)
657 skyBkg = self.sky.exposureToBackground(skyFrame)
659 skyBkgs.append(skyBkg)
661 samples = self.sky.measureScale(calExp.getMaskedImage(), skyBkg)
662 scales.append(samples)
663 scale = self.sky.solveScales(scales)
664 for skyCorr, skyBkg
in zip(skyCorrs, skyBkgs):
665 bgData = list(skyBkg[0])
667 statsImage = bg.getStatsImage().clone()
670 newBgData = [newBg] + bgData[1:]
671 skyCorr.append(newBgData)
672 self.log.info(
"Sky frame subtracted with a scale factor of %.5f", scale)