264 calExpOrder = {ref.dataId[
"detector"]
for ref
in inputRefs.calExps}
265 calBkgOrder = {ref.dataId[
"detector"]
for ref
in inputRefs.calBkgs}
266 detectorOrder = calExpOrder & calBkgOrder
267 if self.config.doApplyFlatBackgroundRatio:
268 ratioOrder = {ref.dataId[
"detector"]
for ref
in inputRefs.backgroundToPhotometricRatioHandles}
269 detectorOrder &= ratioOrder
270 if self.config.doSky:
271 skyFrameOrder = {ref.dataId[
"detector"]
for ref
in inputRefs.skyFrames}
272 detectorOrder &= skyFrameOrder
273 detectorOrder = sorted(detectorOrder)
275 inputRefs.calExps, [ref.dataId[
"detector"]
for ref
in inputRefs.calExps], detectorOrder
278 inputRefs.calBkgs, [ref.dataId[
"detector"]
for ref
in inputRefs.calBkgs], detectorOrder
281 if self.config.doSky:
283 inputRefs.skyFrames, [ref.dataId[
"detector"]
for ref
in inputRefs.skyFrames], detectorOrder
286 inputRefs.skyFrames = []
288 if self.config.doApplyFlatBackgroundRatio:
290 inputRefs.backgroundToPhotometricRatioHandles,
291 [ref.dataId[
"detector"]
for ref
in inputRefs.backgroundToPhotometricRatioHandles],
295 inputRefs.backgroundToPhotometricRatioHandles = []
297 outputRefs.skyCorr, [ref.dataId[
"detector"]
for ref
in outputRefs.skyCorr], detectorOrder
299 inputs = butlerQC.get(inputRefs)
300 inputs.pop(
"rawLinker",
None)
301 outputs = self.
run(**inputs)
302 butlerQC.put(outputs, outputRefs)
304 def run(self, calExps, calBkgs, skyFrames, camera, backgroundToPhotometricRatioHandles=[]):
305 """Perform sky correction on a visit.
307 The original visit-level background is first restored to the calibrated
308 exposure and the existing background model is inverted in-place. If
309 doMaskObjects is True, the mask map associated with this exposure will
310 be iteratively updated (over nIter loops) by re-estimating the
311 background each iteration and redetecting footprints.
313 An initial full focal plane sky subtraction (bgModel1) will take place
314 prior to scaling and subtracting the sky frame.
316 If doSky is True, the sky frame will be scaled to the flux in the input
319 If doBgModel2 is True, a final full focal plane sky subtraction will
320 take place after the sky frame has been subtracted.
322 The first N elements of the returned skyCorr will consist of inverted
323 elements of the calexpBackground model (i.e., subtractive). All
324 subsequent elements appended to skyCorr thereafter will be additive
325 such that, when skyCorr is subtracted from a calexp, the net result
326 will be to undo the initial per-detector background solution and then
327 apply the skyCorr model thereafter. Adding skyCorr to a
328 calexpBackground will effectively negate the calexpBackground,
329 returning only the additive background components of the skyCorr
334 calExps : `list` [`lsst.afw.image.ExposureF`]
335 Detector calibrated exposure images for the visit.
336 calBkgs : `list` [`lsst.afw.math.BackgroundList`]
337 Detector background lists matching the calibrated exposures.
338 skyFrames : `list` [`lsst.afw.image.ExposureF`]
339 Sky frame calibration data for the input detectors.
340 camera : `lsst.afw.cameraGeom.Camera`
341 Camera matching the input data to process.
342 backgroundToPhotometricRatioHandles :
343 `list` [`lsst.daf.butler.DeferredDatasetHandle`], optional
344 Deferred dataset handles pointing to the Background to photometric
345 ratio images for the input detectors.
349 results : `Struct` containing:
350 skyFrameScale : `float`
351 Scale factor applied to the sky frame.
352 skyCorr : `list` [`lsst.afw.math.BackgroundList`]
353 Detector-level sky correction background lists.
354 calExpMosaic : `lsst.afw.image.ExposureF`
355 Visit-level mosaic of the sky corrected data, binned.
356 Analogous to `calexp - skyCorr`.
357 calBkgMosaic : `lsst.afw.image.ExposureF`
358 Visit-level mosaic of the sky correction background, binned.
359 Analogous to `calexpBackground + skyCorr`.
363 bgModel1 = FocalPlaneBackground.fromCamera(self.config.bgModel1, camera)
368 if not self.config.doApplyFlatBackgroundRatio:
369 backgroundToPhotometricRatioHandles = [
None] * len(calExps)
370 for calExpHandle, calBkg, backgroundToPhotometricRatioHandle
in zip(
371 calExps, calBkgs, backgroundToPhotometricRatioHandles
374 calExpHandle, backgroundToPhotometricRatioHandle=backgroundToPhotometricRatioHandle
376 detectors.append(calExp.getDetector())
380 masks.append(calExp.mask)
381 skyCorrs.append(calBkg)
382 bgModel1Indices.append(len(calBkg))
385 bgModel1Detector = FocalPlaneBackground.fromCamera(self.config.bgModel1, camera)
386 bgModel1Detector.addCcd(calExp)
387 bgModel1.merge(bgModel1Detector)
389 "Detector %d: Merged %d unmasked pixels (%.1f%s of detector area) into initial BG model",
390 calExp.getDetector().getId(),
391 bgModel1Detector._numbers.getArray().sum(),
392 100 * bgModel1Detector._numbers.getArray().sum() / calExp.getBBox().getArea(),
400 for detector, skyCorr
in zip(detectors, skyCorrs):
401 with warnings.catch_warnings():
402 warnings.filterwarnings(
"ignore",
"invalid value encountered")
403 calBkgElement = bgModel1.toCcdBackground(detector, detector.getBBox())
404 skyCorr.append(calBkgElement[0])
408 if self.config.doSky:
410 calExps, masks, skyCorrs, skyFrames, backgroundToPhotometricRatioHandles
414 if self.config.undoBgModel1:
415 for skyCorr, bgModel1Index
in zip(skyCorrs, bgModel1Indices):
416 skyCorr._backgrounds.pop(bgModel1Index)
418 "Initial background models (bgModel1s) have been removed from all skyCorr background lists",
422 if self.config.doBgModel2:
423 bgModel2 = FocalPlaneBackground.fromCamera(self.config.bgModel2, camera)
424 for calExpHandle, mask, skyCorr, backgroundToPhotometricRatioHandle
in zip(
425 calExps, masks, skyCorrs, backgroundToPhotometricRatioHandles
427 calExp = self.
_getCalExp(calExpHandle, mask, skyCorr, backgroundToPhotometricRatioHandle)
430 bgModel2Detector = FocalPlaneBackground.fromCamera(self.config.bgModel2, camera)
431 bgModel2Detector.addCcd(calExp)
432 bgModel2.merge(bgModel2Detector)
434 "Detector %d: Merged %d unmasked pixels (%.1f%s of detector area) into final BG model",
435 calExp.getDetector().getId(),
436 bgModel2Detector._numbers.getArray().sum(),
437 100 * bgModel2Detector._numbers.getArray().sum() / calExp.getBBox().getArea(),
445 for detector, skyCorr
in zip(detectors, skyCorrs):
446 with warnings.catch_warnings():
447 warnings.filterwarnings(
"ignore",
"invalid value encountered")
448 calBkgElement = bgModel2.toCcdBackground(detector, detector.getBBox())
449 skyCorr.append(calBkgElement[0])
454 for calExpHandle, mask, skyCorr, backgroundToPhotometricRatioHandle, bgModel1Index
in zip(
455 calExps, masks, skyCorrs, backgroundToPhotometricRatioHandles, bgModel1Indices
457 calExp = self.
_getCalExp(calExpHandle, mask, skyCorr, backgroundToPhotometricRatioHandle)
459 skyCorrExtra = skyCorr.clone()
460 skyCorrExtra._backgrounds = skyCorrExtra._backgrounds[bgModel1Index:]
461 skyCorrExtraMI = makeMaskedImage(skyCorrExtra.getImage())
462 skyCorrExtraMI.setMask(calExp.getMask())
464 calExpsBinned.append(binImage(calExp.getMaskedImage(), self.config.binning))
465 calBkgsBinned.append(binImage(skyCorrExtraMI, self.config.binning))
468 mosConfig.binning = self.config.binning
470 detectorIds = [detector.getId()
for detector
in detectors]
471 calExpMosaic = mosTask.run(calExpsBinned, camera, inputIds=detectorIds).outputData
472 calBkgMosaic = mosTask.run(calBkgsBinned, camera, inputIds=detectorIds).outputData
475 skyFrameScale=skyFrameScale,
477 calExpMosaic=calExpMosaic,
478 calBkgMosaic=calBkgMosaic,
481 def _getCalExp(self, calExpHandle, mask=None, skyCorr=None, backgroundToPhotometricRatioHandle=None):
482 """Get a calexp from a DeferredDatasetHandle, and optionally apply an
483 updated mask and skyCorr.
487 calExpHandle : `~lsst.afw.image.ExposureF`
488 | `lsst.daf.butler.DeferredDatasetHandle`
489 Either the image exposure data or a handle to the calexp dataset.
490 mask : `lsst.afw.image.Mask`, optional
491 Mask to apply to the calexp.
492 skyCorr : `lsst.afw.math.BackgroundList`, optional
493 Background list to subtract from the calexp.
497 calExp : `lsst.afw.image.ExposureF`
498 The calexp with the mask and skyCorr applied.
500 if isinstance(calExpHandle, DeferredDatasetHandle):
501 calExp: ExposureF = calExpHandle.get()
505 calExp: ExposureF = calExpHandle.clone()
510 if self.config.doApplyFlatBackgroundRatio:
511 if not backgroundToPhotometricRatioHandle:
513 "A list of backgroundToPhotometricRatioHandles must be supplied if "
514 "config.doApplyFlatBackgroundRatio=True.",
516 ratioImage = backgroundToPhotometricRatioHandle.get()
517 calExp.maskedImage *= ratioImage
519 "Detector %d: Converted background-flattened image to a photometric-flattened image",
520 calExp.getDetector().getId(),
525 if skyCorr
is not None:
526 image = calExp.getMaskedImage()
527 image -= skyCorr.getImage()
552 """Restore original background to a calexp and invert the related
553 background model; optionally refine the mask plane.
555 The original visit-level background is restored to the calibrated
556 exposure and the existing background model is inverted in-place. If
557 doMaskObjects is True, the mask map associated with the exposure will
558 be iteratively updated (over nIter loops) by re-estimating the
559 background each iteration and redetecting footprints.
561 The background model modified in-place in this method will comprise the
562 first N elements of the skyCorr dataset type, i.e., these N elements
563 are the inverse of the calexpBackground model. All subsequent elements
564 appended to skyCorr will be additive such that, when skyCorr is
565 subtracted from a calexp, the net result will be to undo the initial
566 per-detector background solution and then apply the skyCorr model
567 thereafter. Adding skyCorr to a calexpBackground will effectively
568 negate the calexpBackground, returning only the additive background
569 components of the skyCorr background model.
573 calExp : `lsst.afw.image.ExposureF`
574 Detector level calexp image.
575 calBkg : `lsst.afw.math.BackgroundList`
576 Detector level background lists associated with the calexp.
580 calExp : `lsst.afw.image.ExposureF`
581 The calexp with the originally subtracted background restored.
582 skyCorrBase : `lsst.afw.math.BackgroundList`
583 The inverted original background models; the genesis for skyCorr.
585 image = calExp.getMaskedImage()
588 for calBkgElement
in calBkg:
589 statsImage = calBkgElement[0].getStatsImage()
591 skyCorrBase = calBkg.getImage()
594 stats = np.nanpercentile(skyCorrBase.array, [50, 75, 25])
596 "Detector %d: Original background restored (BG median = %.1f counts, BG IQR = %.1f counts)",
597 calExp.getDetector().getId(),
599 np.subtract(*stats[1:]),
603 if self.config.doMaskObjects:
604 maskFrac0 = 1 - np.sum(calExp.mask.array == 0) / calExp.mask.array.size
605 self.maskObjects.findObjects(calExp)
606 maskFrac1 = 1 - np.sum(calExp.mask.array == 0) / calExp.mask.array.size
609 "Detector %d: Iterative source detection and mask growth has increased masked area by %.1f%%",
610 calExp.getDetector().getId(),
611 (100 * (maskFrac1 - maskFrac0)),
614 return calExp, skyCorrBase
617 """Check that the background model contains enough valid superpixels,
618 and raise a useful error if not.
623 Identifier for the background model.
624 bgModel : `~lsst.pipe.tasks.background.FocalPlaneBackground`
625 Background model to check.
626 config : `~lsst.pipe.tasks.background.FocalPlaneBackgroundConfig`
627 Configuration used to create the background model.
629 bgModelArray = bgModel._numbers.getArray()
630 spArea = (config.xSize / config.pixelSize) * (config.ySize / config.pixelSize)
632 "%s: FP background model constructed using %.2f x %.2f mm (%d x %d pixel) superpixels",
636 int(config.xSize / config.pixelSize),
637 int(config.ySize / config.pixelSize),
640 "%s: Pixel data exists in %d of %d superpixels; the most populated superpixel is %.1f%% filled",
642 np.sum(bgModelArray > 0),
644 100 * np.max(bgModelArray) / spArea,
647 thresh = config.minFrac * spArea
648 if np.all(bgModelArray < thresh):
650 f
"No background model superpixels are more than {100*config.minFrac}% filled. "
651 "Try decreasing the minFrac configuration parameter, optimizing the subset of detectors "
652 "being processed, or increasing the number of detectors being processed."
655 with warnings.catch_warnings():
656 warnings.filterwarnings(
"ignore",
r"invalid value encountered")
657 stats = np.nanpercentile(bgModel.getStatsImage().array, [50, 75, 25])
659 "%s: FP BG median = %.1f counts, FP BG IQR = %.1f counts",
662 np.subtract(*stats[1:]),
665 def _fitSkyFrame(self, calExps, masks, skyCorrs, skyFrames, backgroundToPhotometricRatioHandles):
666 """Determine the full focal plane sky frame scale factor relative to
667 an input list of calibrated exposures.
669 This method measures the sky frame scale on all inputs, resulting in
670 values equal to the background method solveScales().
672 Input skyCorrs are updated in-place.
676 calExps : `list` [`lsst.afw.image.ExposureF`]
677 Calibrated exposures to be background subtracted.
678 masks : `list` [`lsst.afw.image.Mask`]
679 Masks associated with the input calibrated exposures.
680 skyCorrs : `list` [`lsst.afw.math.BackgroundList`]
681 Background lists associated with the input calibrated exposures.
682 skyFrames : `list` [`lsst.afw.image.ExposureF`]
683 Sky frame calibration data for the input detectors.
688 Fitted scale factor applied to the sky frame.
692 for calExpHandle, mask, skyCorr, skyFrameHandle, backgroundToPhotometricRatioHandle
in zip(
693 calExps, masks, skyCorrs, skyFrames, backgroundToPhotometricRatioHandles
695 calExp = self.
_getCalExp(calExpHandle, mask, skyCorr, backgroundToPhotometricRatioHandle)
697 skyBkg = self.sky.exposureToBackground(skyFrame)
699 skyBkgs.append(skyBkg)
701 samples = self.sky.measureScale(calExp.getMaskedImage(), skyBkg)
702 scales.append(samples)
703 scale = self.sky.solveScales(scales)
704 for skyCorr, skyBkg
in zip(skyCorrs, skyBkgs):
705 bgData = list(skyBkg[0])
707 statsImage = bg.getStatsImage().clone()
710 newBgData = [newBg] + bgData[1:]
711 skyCorr.append(newBgData)
712 self.log.info(
"Sky frame subtracted with a scale factor of %.5f", scale)