264 def run(self, calExps, calBkgs, skyFrames, camera, backgroundToPhotometricRatioHandles=[]):
265 """Perform sky correction on a visit.
266
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.
272
273 An initial full focal plane sky subtraction (bgModel1) will take place
274 prior to scaling and subtracting the sky frame.
275
276 If doSky is True, the sky frame will be scaled to the flux in the input
277 visit.
278
279 If doBgModel2 is True, a final full focal plane sky subtraction will
280 take place after the sky frame has been subtracted.
281
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
290 background model.
291
292 Parameters
293 ----------
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.
306
307 Returns
308 -------
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`.
320 """
321
322
323 bgModel1 = FocalPlaneBackground.fromCamera(self.config.bgModel1, camera)
324 detectors = []
325 masks = []
326 skyCorrs = []
327 bgModel1Indices = []
328 if not self.config.doApplyFlatBackgroundRatio:
329 backgroundToPhotometricRatioHandles = [None] * len(calExps)
330 for calExpHandle, calBkg, backgroundToPhotometricRatioHandle in zip(
331 calExps, calBkgs, backgroundToPhotometricRatioHandles
332 ):
333 calExp = self._getCalExp(
334 calExpHandle, backgroundToPhotometricRatioHandle=backgroundToPhotometricRatioHandle
335 )
336 detectors.append(calExp.getDetector())
337
338
339 _ = self._restoreOriginalBackgroundRefineMask(calExp, calBkg)
340 masks.append(calExp.mask)
341 skyCorrs.append(calBkg)
342 bgModel1Indices.append(len(calBkg))
343
344
345 bgModel1Detector = FocalPlaneBackground.fromCamera(self.config.bgModel1, camera)
346 bgModel1Detector.addCcd(calExp)
347 bgModel1.merge(bgModel1Detector)
348 self.log.info(
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(),
353 "%",
354 )
355
356
357 self._validateBgModel("bgModel1", bgModel1, self.config.bgModel1)
358
359
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])
365
366
367 skyFrameScale = None
368 if self.config.doSky:
369 skyFrameScale = self._fitSkyFrame(
370 calExps, masks, skyCorrs, skyFrames, backgroundToPhotometricRatioHandles
371 )
372
373
374 if self.config.undoBgModel1:
375 for skyCorr, bgModel1Index in zip(skyCorrs, bgModel1Indices):
376 skyCorr._backgrounds.pop(bgModel1Index)
377 self.log.info(
378 "Initial background models (bgModel1s) have been removed from all skyCorr background lists",
379 )
380
381
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
386 ):
387 calExp = self._getCalExp(calExpHandle, mask, skyCorr, backgroundToPhotometricRatioHandle)
388
389
390 bgModel2Detector = FocalPlaneBackground.fromCamera(self.config.bgModel2, camera)
391 bgModel2Detector.addCcd(calExp)
392 bgModel2.merge(bgModel2Detector)
393 self.log.info(
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(),
398 "%",
399 )
400
401
402 self._validateBgModel("bgModel2", bgModel2, self.config.bgModel2)
403
404
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])
410
411
412 calExpsBinned = []
413 calBkgsBinned = []
414 for calExpHandle, mask, skyCorr, backgroundToPhotometricRatioHandle, bgModel1Index in zip(
415 calExps, masks, skyCorrs, backgroundToPhotometricRatioHandles, bgModel1Indices
416 ):
417 calExp = self._getCalExp(calExpHandle, mask, skyCorr, backgroundToPhotometricRatioHandle)
418
419 skyCorrExtra = skyCorr.clone()
420 skyCorrExtra._backgrounds = skyCorrExtra._backgrounds[bgModel1Index:]
421 skyCorrExtraMI = makeMaskedImage(skyCorrExtra.getImage())
422 skyCorrExtraMI.setMask(calExp.getMask())
423
424 calExpsBinned.append(binImage(calExp.getMaskedImage(), self.config.binning))
425 calBkgsBinned.append(binImage(skyCorrExtraMI, self.config.binning))
426
427 mosConfig = VisualizeMosaicExpConfig()
428 mosConfig.binning = self.config.binning
429 mosTask = VisualizeMosaicExpTask(config=mosConfig)
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
433
434 return Struct(
435 skyFrameScale=skyFrameScale,
436 skyCorr=skyCorrs,
437 calExpMosaic=calExpMosaic,
438 calBkgMosaic=calBkgMosaic,
439 )
440