LSST Applications g0fba68d861+05a5e06e50,g1fd858c14a+904f1f4196,g2c84ff76c0+247cd845a2,g2c9e612ef2+f5117f1d55,g35bb328faa+fcb1d3bbc8,g4af146b050+123c2f4d45,g4d2262a081+a9e7a6e053,g4e0f332c67+c58e4b632d,g53246c7159+fcb1d3bbc8,g5a012ec0e7+ac98094cfc,g60b5630c4e+f5117f1d55,g67b6fd64d1+dd0349c22b,g78460c75b0+2f9a1b4bcd,g786e29fd12+cf7ec2a62a,g7b71ed6315+fcb1d3bbc8,g87b7deb4dc+2a1aa4536a,g8852436030+d21aad2af4,g89139ef638+dd0349c22b,g8d6b6b353c+f5117f1d55,g9125e01d80+fcb1d3bbc8,g989de1cb63+dd0349c22b,g9f33ca652e+0a580a37dd,g9f7030ddb1+ae84e0d5a5,ga2b97cdc51+f5117f1d55,gabe3b4be73+1e0a283bba,gb1101e3267+1d994e7598,gb58c049af0+f03b321e39,gb89ab40317+dd0349c22b,gcca6a94b71+4ed007ada1,gcf25f946ba+d21aad2af4,gd315a588df+9d9c5ccff1,gd6cbbdb0b4+75aa4b1db4,gd9a9a58781+fcb1d3bbc8,gde0f65d7ad+6fce216140,ge278dab8ac+c61fbefdff,ge410e46f29+dd0349c22b,ge82c20c137+e12a08b75a,gf67bdafdda+dd0349c22b,v28.0.2.rc1
LSST Data Management Base Package
Loading...
Searching...
No Matches
skyCorrection.py
Go to the documentation of this file.
1# This file is part of pipe_tasks.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (https://www.lsst.org).
6# See the COPYRIGHT file at the top-level directory of this distribution
7# for details of code ownership.
8#
9# This program is free software: you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation, either version 3 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program. If not, see <https://www.gnu.org/licenses/>.
21
22__all__ = ["SkyCorrectionTask", "SkyCorrectionConfig"]
23
24import warnings
25
26import lsst.afw.image as afwImage
27import lsst.afw.math as afwMath
28import lsst.pipe.base.connectionTypes as cT
29import numpy as np
30from lsst.pex.config import Config, ConfigField, ConfigurableField, Field
31from lsst.pipe.base import PipelineTask, PipelineTaskConfig, PipelineTaskConnections, Struct
33 FocalPlaneBackground,
34 FocalPlaneBackgroundConfig,
35 MaskObjectsTask,
36 SkyMeasurementTask,
37)
38from lsst.pipe.tasks.visualizeVisit import VisualizeMosaicExpConfig, VisualizeMosaicExpTask
39
40
41def _skyFrameLookup(datasetType, registry, quantumDataId, collections):
42 """Lookup function to identify sky frames.
43
44 Parameters
45 ----------
46 datasetType : `lsst.daf.butler.DatasetType`
47 Dataset to lookup.
48 registry : `lsst.daf.butler.Registry`
49 Butler registry to query.
50 quantumDataId : `lsst.daf.butler.DataCoordinate`
51 Data id to transform to find sky frames.
52 The ``detector`` entry will be stripped.
53 collections : `lsst.daf.butler.CollectionSearch`
54 Collections to search through.
55
56 Returns
57 -------
58 results : `list` [`lsst.daf.butler.DatasetRef`]
59 List of datasets that will be used as sky calibration frames.
60 """
61 newDataId = quantumDataId.subset(registry.dimensions.conform(["instrument", "visit"]))
62 skyFrames = []
63 for dataId in registry.queryDataIds(["visit", "detector"], dataId=newDataId).expanded():
64 skyFrame = registry.findDataset(
65 datasetType, dataId, collections=collections, timespan=dataId.timespan
66 )
67 skyFrames.append(skyFrame)
68 return skyFrames
69
70
71def _reorderAndPadList(inputList, inputKeys, outputKeys, padWith=None):
72 """Match the order of one list to another, padding if necessary.
73
74 Parameters
75 ----------
76 inputList : `list`
77 List to be reordered and padded. Elements can be any type.
78 inputKeys : iterable
79 Iterable of values to be compared with outputKeys.
80 Length must match `inputList`.
81 outputKeys : iterable
82 Iterable of values to be compared with inputKeys.
83 padWith :
84 Any value to be inserted where one of inputKeys is not in outputKeys.
85
86 Returns
87 -------
88 outputList : `list`
89 Copy of inputList reordered per outputKeys and padded with `padWith`
90 so that the length matches length of outputKeys.
91 """
92 outputList = []
93 for outputKey in outputKeys:
94 if outputKey in inputKeys:
95 outputList.append(inputList[inputKeys.index(outputKey)])
96 else:
97 outputList.append(padWith)
98 return outputList
99
100
101class SkyCorrectionConnections(PipelineTaskConnections, dimensions=("instrument", "visit")):
102 rawLinker = cT.Input(
103 doc="Raw data to provide exp-visit linkage to connect calExp inputs to camera/sky calibs.",
104 name="raw",
105 multiple=True,
106 deferLoad=True,
107 storageClass="Exposure",
108 dimensions=["instrument", "exposure", "detector"],
109 )
110 calExps = cT.Input(
111 doc="Background-subtracted calibrated exposures.",
112 name="calexp",
113 multiple=True,
114 storageClass="ExposureF",
115 dimensions=["instrument", "visit", "detector"],
116 )
117 calBkgs = cT.Input(
118 doc="Subtracted backgrounds for input calibrated exposures.",
119 multiple=True,
120 name="calexpBackground",
121 storageClass="Background",
122 dimensions=["instrument", "visit", "detector"],
123 )
124 backgroundToPhotometricRatioHandles = cT.Input(
125 doc="Ratio of a background-flattened image to a photometric-flattened image. "
126 "Only used if doApplyFlatBackgroundRatio is True.",
127 multiple=True,
128 name="background_to_photometric_ratio",
129 storageClass="Image",
130 dimensions=["instrument", "visit", "detector"],
131 deferLoad=True,
132 )
133 skyFrames = cT.PrerequisiteInput(
134 doc="Calibration sky frames.",
135 name="sky",
136 multiple=True,
137 storageClass="ExposureF",
138 dimensions=["instrument", "physical_filter", "detector"],
139 isCalibration=True,
140 lookupFunction=_skyFrameLookup,
141 )
142 camera = cT.PrerequisiteInput(
143 doc="Input camera.",
144 name="camera",
145 storageClass="Camera",
146 dimensions=["instrument"],
147 isCalibration=True,
148 )
149 skyCorr = cT.Output(
150 doc="Sky correction data, to be subtracted from the calibrated exposures.",
151 name="skyCorr",
152 multiple=True,
153 storageClass="Background",
154 dimensions=["instrument", "visit", "detector"],
155 )
156 calExpMosaic = cT.Output(
157 doc="Full focal plane mosaicked image of the sky corrected calibrated exposures.",
158 name="calexp_skyCorr_visit_mosaic",
159 storageClass="ImageF",
160 dimensions=["instrument", "visit"],
161 )
162 calBkgMosaic = cT.Output(
163 doc="Full focal plane mosaicked image of the sky corrected calibrated exposure backgrounds.",
164 name="calexpBackground_skyCorr_visit_mosaic",
165 storageClass="ImageF",
166 dimensions=["instrument", "visit"],
167 )
168
169 def __init__(self, *, config: "SkyCorrectionConfig | None" = None):
170 super().__init__(config=config)
171 assert config is not None
172 if not config.doSky:
173 del self.skyFrames
174 if not config.doApplyFlatBackgroundRatio:
176
177
178class SkyCorrectionConfig(PipelineTaskConfig, pipelineConnections=SkyCorrectionConnections):
179 doApplyFlatBackgroundRatio = Field(
180 dtype=bool,
181 default=False,
182 doc="This should be True if the input image was processed with an illumination correction.",
183 )
184 maskObjects = ConfigurableField(
185 target=MaskObjectsTask,
186 doc="Mask Objects",
187 )
188 doMaskObjects = Field(
189 dtype=bool,
190 default=True,
191 doc="Iteratively mask objects to find good sky?",
192 )
193 bgModel1 = ConfigField(
194 dtype=FocalPlaneBackgroundConfig,
195 doc="Initial background model, prior to sky frame subtraction",
196 )
197 undoBgModel1 = Field(
198 dtype=bool,
199 default=False,
200 doc="If True, adds back initial background model after sky and removes bgModel1 from the list",
201 )
203 target=SkyMeasurementTask,
204 doc="Sky measurement",
205 )
206 doSky = Field(
207 dtype=bool,
208 default=True,
209 doc="Do sky frame subtraction?",
210 )
211 bgModel2 = ConfigField(
212 dtype=FocalPlaneBackgroundConfig,
213 doc="Final (cleanup) background model, after sky frame subtraction",
214 )
215 doBgModel2 = Field(
216 dtype=bool,
217 default=True,
218 doc="Do final (cleanup) background model subtraction, after sky frame subtraction?",
219 )
220 binning = Field(
221 dtype=int,
222 default=8,
223 doc="Binning factor for constructing full focal plane '*_camera' output datasets",
224 )
225
226 def setDefaults(self):
227 Config.setDefaults(self)
228 self.bgModel2.doSmooth = True
229 self.bgModel2.minFrac = 0.5
230 self.bgModel2.xSize = 256
231 self.bgModel2.ySize = 256
232 self.bgModel2.smoothScale = 1.0
233
234 def validate(self):
235 super().validate()
236 if self.undoBgModel1 and not self.doSky and not self.doBgModel2:
237 raise ValueError("If undoBgModel1 is True, task requires at least one of doSky or doBgModel2.")
238
239
240class SkyCorrectionTask(PipelineTask):
241 """Perform a full focal plane sky correction."""
242
243 ConfigClass = SkyCorrectionConfig
244 _DefaultName = "skyCorr"
245
246 def __init__(self, *args, **kwargs):
247 super().__init__(**kwargs)
248 self.makeSubtask("sky")
249 self.makeSubtask("maskObjects")
250
251 def runQuantum(self, butlerQC, inputRefs, outputRefs):
252 # Sort the calExps, calBkgs and skyFrames inputRefs and the
253 # skyCorr outputRef by detector ID to ensure reproducibility.
254 detectorOrder = [ref.dataId["detector"] for ref in inputRefs.calExps]
255 detectorOrder.sort()
256 inputRefs.calExps = _reorderAndPadList(
257 inputRefs.calExps, [ref.dataId["detector"] for ref in inputRefs.calExps], detectorOrder
258 )
259 inputRefs.calBkgs = _reorderAndPadList(
260 inputRefs.calBkgs, [ref.dataId["detector"] for ref in inputRefs.calBkgs], detectorOrder
261 )
262 # Only attempt to fetch sky frames if they are going to be applied.
263 if self.config.doSky:
264 inputRefs.skyFrames = _reorderAndPadList(
265 inputRefs.skyFrames, [ref.dataId["detector"] for ref in inputRefs.skyFrames], detectorOrder
266 )
267 else:
268 inputRefs.skyFrames = []
269 # Only attempt to fetch flat ratios if they are going to be applied.
270 if self.config.doApplyFlatBackgroundRatio:
271 inputRefs.backgroundToPhotometricRatioHandles = _reorderAndPadList(
272 inputRefs.backgroundToPhotometricRatioHandles,
273 [ref.dataId["detector"] for ref in inputRefs.backgroundToPhotometricRatioHandles],
274 detectorOrder,
275 )
276 else:
277 inputRefs.backgroundToPhotometricRatioHandles = []
278 outputRefs.skyCorr = _reorderAndPadList(
279 outputRefs.skyCorr, [ref.dataId["detector"] for ref in outputRefs.skyCorr], detectorOrder
280 )
281 inputs = butlerQC.get(inputRefs)
282 inputs.pop("rawLinker", None)
283 outputs = self.run(**inputs)
284 butlerQC.put(outputs, outputRefs)
285
286 def run(self, calExps, calBkgs, skyFrames, camera, backgroundToPhotometricRatioHandles=[]):
287 """Perform sky correction on a visit.
288
289 The original visit-level background is first restored to the calibrated
290 exposure and the existing background model is inverted in-place. If
291 doMaskObjects is True, the mask map associated with this exposure will
292 be iteratively updated (over nIter loops) by re-estimating the
293 background each iteration and redetecting footprints.
294
295 An initial full focal plane sky subtraction (bgModel1) will take place
296 prior to scaling and subtracting the sky frame.
297
298 If doSky is True, the sky frame will be scaled to the flux in the input
299 visit.
300
301 If doBgModel2 is True, a final full focal plane sky subtraction will
302 take place after the sky frame has been subtracted.
303
304 The first N elements of the returned skyCorr will consist of inverted
305 elements of the calexpBackground model (i.e., subtractive). All
306 subsequent elements appended to skyCorr thereafter will be additive
307 such that, when skyCorr is subtracted from a calexp, the net result
308 will be to undo the initial per-detector background solution and then
309 apply the skyCorr model thereafter. Adding skyCorr to a
310 calexpBackground will effectively negate the calexpBackground,
311 returning only the additive background components of the skyCorr
312 background model.
313
314 Parameters
315 ----------
316 calExps : `list` [`lsst.afw.image.ExposureF`]
317 Detector calibrated exposure images for the visit.
318 calBkgs : `list` [`lsst.afw.math.BackgroundList`]
319 Detector background lists matching the calibrated exposures.
320 skyFrames : `list` [`lsst.afw.image.ExposureF`]
321 Sky frame calibration data for the input detectors.
322 camera : `lsst.afw.cameraGeom.Camera`
323 Camera matching the input data to process.
324 backgroundToPhotometricRatioHandles : `list` [`lsst.daf.butler.DeferredDatasetHandle`], optional
325 Deferred dataset handles pointing to the Background to photometric ratio images
326 for the input detectors.
327
328 Returns
329 -------
330 results : `Struct` containing:
331 skyFrameScale : `float`
332 Scale factor applied to the sky frame.
333 skyCorr : `list` [`lsst.afw.math.BackgroundList`]
334 Detector-level sky correction background lists.
335 calExpMosaic : `lsst.afw.image.ExposureF`
336 Visit-level mosaic of the sky corrected data, binned.
337 Analogous to `calexp - skyCorr`.
338 calBkgMosaic : `lsst.afw.image.ExposureF`
339 Visit-level mosaic of the sky correction background, binned.
340 Analogous to `calexpBackground + skyCorr`.
341 """
342 if self.config.doApplyFlatBackgroundRatio:
343 if not backgroundToPhotometricRatioHandles:
344 raise ValueError(
345 "A list of backgroundToPhotometricRatioHandles must be supplied if "
346 "config.doApplyFlatBackgroundRatio=True.",
347 )
348 # Convert from photometric flattened images to background flattened
349 # images.
350 for calExp, ratioHandle in zip(calExps, backgroundToPhotometricRatioHandles):
351 ratioImage = ratioHandle.get()
352 calExp.maskedImage *= ratioImage
353
354 # Restore original backgrounds in-place; optionally refine mask maps
355 numOrigBkgElements = [len(calBkg) for calBkg in calBkgs]
356 _ = self._restoreOriginalBackgroundRefineMask(calExps, calBkgs)
357
358 # Bin exposures, generate full-fp bg, map to CCDs and subtract in-place
359 _ = self._subtractVisitBackground(calExps, calBkgs, camera, self.config.bgModel1)
360 initialBackgroundIndex = len(calBkgs[0]._backgrounds) - 1
361
362 # Subtract a scaled sky frame from all input exposures
363 skyFrameScale = None
364 if self.config.doSky:
365 skyFrameScale = self._subtractSkyFrame(calExps, skyFrames, calBkgs)
366
367 # Adds full-fp bg back onto exposures, removes it from list
368 if self.config.undoBgModel1:
369 _ = self._undoInitialBackground(calExps, calBkgs, initialBackgroundIndex)
370
371 # Bin exposures, generate full-fp bg, map to CCDs and subtract in-place
372 if self.config.doBgModel2:
373 _ = self._subtractVisitBackground(calExps, calBkgs, camera, self.config.bgModel2)
374
375 # Make camera-level images of bg subtracted calexps and subtracted bgs
376 calExpIds = [exp.getDetector().getId() for exp in calExps]
377 skyCorrExtras = []
378 for calBkg, num in zip(calBkgs, numOrigBkgElements):
379 skyCorrExtra = calBkg.clone()
380 skyCorrExtra._backgrounds = skyCorrExtra._backgrounds[num:]
381 skyCorrExtras.append(skyCorrExtra)
382 calExpMosaic = self._binAndMosaic(calExps, camera, self.config.binning, ids=calExpIds, refExps=None)
383 calBkgMosaic = self._binAndMosaic(
384 skyCorrExtras, camera, self.config.binning, ids=calExpIds, refExps=calExps
385 )
386
387 if self.config.doApplyFlatBackgroundRatio:
388 # Convert from background flattened images to photometric flattened
389 # images.
390 for calExp, ratioHandle in zip(calExps, backgroundToPhotometricRatioHandles):
391 ratioImage = ratioHandle.get()
392 calExp.maskedImage /= ratioImage
393
394 return Struct(
395 skyFrameScale=skyFrameScale, skyCorr=calBkgs, calExpMosaic=calExpMosaic, calBkgMosaic=calBkgMosaic
396 )
397
398 def _restoreOriginalBackgroundRefineMask(self, calExps, calBkgs):
399 """Restore original background to each calexp and invert the related
400 background model; optionally refine the mask plane.
401
402 The original visit-level background is restored to each calibrated
403 exposure and the existing background model is inverted in-place. If
404 doMaskObjects is True, the mask map associated with the exposure will
405 be iteratively updated (over nIter loops) by re-estimating the
406 background each iteration and redetecting footprints.
407
408 The background model modified in-place in this method will comprise the
409 first N elements of the skyCorr dataset type, i.e., these N elements
410 are the inverse of the calexpBackground model. All subsequent elements
411 appended to skyCorr will be additive such that, when skyCorr is
412 subtracted from a calexp, the net result will be to undo the initial
413 per-detector background solution and then apply the skyCorr model
414 thereafter. Adding skyCorr to a calexpBackground will effectively
415 negate the calexpBackground, returning only the additive background
416 components of the skyCorr background model.
417
418 Parameters
419 ----------
420 calExps : `lsst.afw.image.ExposureF`
421 Detector level calexp images to process.
422 calBkgs : `lsst.afw.math.BackgroundList`
423 Detector level background lists associated with the calexps.
424
425 Returns
426 -------
427 calExps : `lsst.afw.image.ExposureF`
428 The calexps with the originally subtracted background restored.
429 skyCorrBases : `lsst.afw.math.BackgroundList`
430 The inverted original background models; the genesis for skyCorrs.
431 """
432 skyCorrBases = []
433 for calExp, calBkg in zip(calExps, calBkgs):
434 image = calExp.getMaskedImage()
435
436 # Invert all elements of the existing bg model; restore in calexp
437 for calBkgElement in calBkg:
438 statsImage = calBkgElement[0].getStatsImage()
439 statsImage *= -1
440 skyCorrBase = calBkg.getImage()
441 image -= skyCorrBase
442
443 # Iteratively subtract bg, re-detect sources, and add bg back on
444 if self.config.doMaskObjects:
445 self.maskObjects.findObjects(calExp)
446
447 stats = np.nanpercentile(skyCorrBase.array, [50, 75, 25])
448 self.log.info(
449 "Detector %d: Original background restored; BG median = %.1f counts, BG IQR = %.1f counts",
450 calExp.getDetector().getId(),
451 -stats[0],
452 np.subtract(*stats[1:]),
453 )
454 skyCorrBases.append(skyCorrBase)
455 return calExps, skyCorrBases
456
457 def _undoInitialBackground(self, calExps, calBkgs, initialBackgroundIndex):
458 """Undo the initial background subtraction (bgModel1) after sky frame
459 subtraction.
460
461 Parameters
462 ----------
463 calExps : `list` [`lsst.afw.image.ExposureF`]
464 Calibrated exposures to be background subtracted.
465 calBkgs : `list` [`lsst.afw.math.BackgroundList`]
466 Background lists associated with the input calibrated exposures.
467 initialBackgroundIndex : `int`
468 Index of the initial background (bgModel1) in the background list.
469
470 Notes
471 -----
472 Inputs are modified in-place.
473 """
474 for calExp, calBkg in zip(calExps, calBkgs):
475 image = calExp.getMaskedImage()
476
477 # Remove bgModel1 from the background list; restore in the image
478 initialBackground = calBkg[initialBackgroundIndex][0].getImageF()
479 image += initialBackground
480 calBkg._backgrounds.pop(initialBackgroundIndex)
481
482 self.log.info(
483 "Detector %d: The initial background model prior to sky frame subtraction (bgModel1) has "
484 "been removed from the background list",
485 calExp.getDetector().getId(),
486 )
487
488 def _subtractVisitBackground(self, calExps, calBkgs, camera, config):
489 """Perform a full focal-plane background subtraction for a visit.
490
491 Generate a full focal plane background model, binning all masked
492 detectors into bins of [bgModelN.xSize, bgModelN.ySize]. After,
493 subtract the resultant background model (translated back into CCD
494 coordinates) from the original detector exposure.
495
496 Return a list of background subtracted images and a list of full focal
497 plane background parameters.
498
499 Parameters
500 ----------
501 calExps : `list` [`lsst.afw.image.ExposureF`]
502 Calibrated exposures to be background subtracted.
503 calBkgs : `list` [`lsst.afw.math.BackgroundList`]
504 Background lists associated with the input calibrated exposures.
505 camera : `lsst.afw.cameraGeom.Camera`
506 Camera description.
507 config : `lsst.pipe.tasks.background.FocalPlaneBackgroundConfig`
508 Configuration to use for background subtraction.
509
510 Returns
511 -------
512 calExps : `list` [`lsst.afw.image.maskedImage.MaskedImageF`]
513 Background subtracted exposures for creating a focal plane image.
514 calBkgs : `list` [`lsst.afw.math.BackgroundList`]
515 Updated background lists with a visit-level model appended.
516 """
517 # Set up empty full focal plane background model object
518 bgModelBase = FocalPlaneBackground.fromCamera(config, camera)
519
520 # Loop over each detector, bin into [xSize, ySize] bins, and update
521 # summed flux (_values) and number of contributing pixels (_numbers)
522 # in focal plane coordinates. Append outputs to bgModels.
523 bgModels = []
524 for calExp in calExps:
525 bgModel = bgModelBase.clone()
526 bgModel.addCcd(calExp)
527 bgModels.append(bgModel)
528
529 # Merge detector models to make a single full focal plane bg model
530 for bgModel, calExp in zip(bgModels, calExps):
531 msg = (
532 "Detector %d: Merging %d unmasked pixels (%.1f%s of detector area) into focal plane "
533 "background model"
534 )
535 self.log.debug(
536 msg,
537 calExp.getDetector().getId(),
538 bgModel._numbers.getArray().sum(),
539 100 * bgModel._numbers.getArray().sum() / calExp.getBBox().getArea(),
540 "%",
541 )
542 bgModelBase.merge(bgModel)
543
544 # Map full focal plane bg solution to detector; subtract from exposure
545 calBkgElements = []
546 for calExp in calExps:
547 _, calBkgElement = self._subtractDetectorBackground(calExp, bgModelBase)
548 calBkgElements.append(calBkgElement)
549
550 msg = (
551 "Focal plane background model constructed using %.2f x %.2f mm (%d x %d pixel) superpixels; "
552 "FP BG median = %.1f counts, FP BG IQR = %.1f counts"
553 )
554 with warnings.catch_warnings():
555 warnings.filterwarnings("ignore", r"invalid value encountered")
556 stats = np.nanpercentile(bgModelBase.getStatsImage().array, [50, 75, 25])
557 self.log.info(
558 msg,
559 config.xSize,
560 config.ySize,
561 int(config.xSize / config.pixelSize),
562 int(config.ySize / config.pixelSize),
563 stats[0],
564 np.subtract(*stats[1:]),
565 )
566
567 for calBkg, calBkgElement in zip(calBkgs, calBkgElements):
568 calBkg.append(calBkgElement[0])
569 return calExps, calBkgs
570
571 def _subtractDetectorBackground(self, calExp, bgModel):
572 """Generate CCD background model and subtract from image.
573
574 Translate the full focal plane background into CCD coordinates and
575 subtract from the original science exposure image.
576
577 Parameters
578 ----------
579 calExp : `lsst.afw.image.ExposureF`
580 Exposure to subtract the background model from.
581 bgModel : `lsst.pipe.tasks.background.FocalPlaneBackground`
582 Full focal plane camera-level background model.
583
584 Returns
585 -------
586 calExp : `lsst.afw.image.ExposureF`
587 Background subtracted input exposure.
588 calBkgElement : `lsst.afw.math.BackgroundList`
589 Detector level realization of the full focal plane bg model.
590 """
591 image = calExp.getMaskedImage()
592 with warnings.catch_warnings():
593 warnings.filterwarnings("ignore", r"invalid value encountered")
594 calBkgElement = bgModel.toCcdBackground(calExp.getDetector(), image.getBBox())
595 image -= calBkgElement.getImage()
596 return calExp, calBkgElement
597
598 def _subtractSkyFrame(self, calExps, skyFrames, calBkgs):
599 """Determine the full focal plane sky frame scale factor relative to
600 an input list of calibrated exposures and subtract.
601
602 This method measures the sky frame scale on all inputs, resulting in
603 values equal to the background method solveScales(). The sky frame is
604 then subtracted as in subtractSkyFrame() using the appropriate scale.
605
606 Input calExps and calBkgs are updated in-place, returning sky frame
607 subtracted calExps and sky frame updated calBkgs, respectively.
608
609 Parameters
610 ----------
611 calExps : `list` [`lsst.afw.image.ExposureF`]
612 Calibrated exposures to be background subtracted.
613 skyFrames : `list` [`lsst.afw.image.ExposureF`]
614 Sky frame calibration data for the input detectors.
615 calBkgs : `list` [`lsst.afw.math.BackgroundList`]
616 Background lists associated with the input calibrated exposures.
617
618 Returns
619 -------
620 scale : `float`
621 Scale factor applied to the sky frame.
622 """
623 skyFrameBgModels = []
624 scales = []
625 for calExp, skyFrame in zip(calExps, skyFrames):
626 skyFrameBgModel = self.sky.exposureToBackground(skyFrame)
627 skyFrameBgModels.append(skyFrameBgModel)
628 # return a tuple of gridded image and sky frame clipped means
629 samples = self.sky.measureScale(calExp.getMaskedImage(), skyFrameBgModel)
630 scales.append(samples)
631 scale = self.sky.solveScales(scales)
632 for calExp, skyFrameBgModel, calBkg in zip(calExps, skyFrameBgModels, calBkgs):
633 # subtract the scaled sky frame model from each calExp in-place,
634 # also updating the calBkg list in-place
635 self.sky.subtractSkyFrame(calExp.getMaskedImage(), skyFrameBgModel, scale, calBkg)
636 self.log.info("Sky frame subtracted with a scale factor of %.5f", scale)
637 return scale
638
639 def _binAndMosaic(self, exposures, camera, binning, ids=None, refExps=None):
640 """Bin input exposures and mosaic across the entire focal plane.
641
642 Input exposures are binned and then mosaicked at the position of
643 the detector in the focal plane of the camera.
644
645 Parameters
646 ----------
647 exposures : `list`
648 Detector level list of either calexp `ExposureF` types or
649 calexpBackground `BackgroundList` types.
650 camera : `lsst.afw.cameraGeom.Camera`
651 Camera matching the input data to process.
652 binning : `int`
653 Binning size to be applied to input images.
654 ids : `list` [`int`], optional
655 List of detector ids to iterate over.
656 refExps : `list` [`lsst.afw.image.ExposureF`], optional
657 If supplied, mask planes from these reference images will be used.
658 Returns
659 -------
660 mosaicImage : `lsst.afw.image.ExposureF`
661 Mosaicked full focal plane image.
662 """
663 refExps = np.resize(refExps, len(exposures)) # type: ignore
664 binnedImages = []
665 for exp, refExp in zip(exposures, refExps):
666 try:
667 nativeImage = exp.getMaskedImage()
668 except AttributeError:
669 nativeImage = afwImage.makeMaskedImage(exp.getImage())
670 if refExp:
671 nativeImage.setMask(refExp.getMask())
672 binnedImage = afwMath.binImage(nativeImage, binning)
673 binnedImages.append(binnedImage)
674 mosConfig = VisualizeMosaicExpConfig()
675 mosConfig.binning = binning
676 mosTask = VisualizeMosaicExpTask(config=mosConfig)
677 imageStruct = mosTask.run(binnedImages, camera, inputIds=ids)
678 mosaicImage = imageStruct.outputData
679 return mosaicImage
__init__(self, *, "SkyCorrectionConfig | None" config=None)
_subtractVisitBackground(self, calExps, calBkgs, camera, config)
run(self, calExps, calBkgs, skyFrames, camera, backgroundToPhotometricRatioHandles=[])
_subtractSkyFrame(self, calExps, skyFrames, calBkgs)
runQuantum(self, butlerQC, inputRefs, outputRefs)
_binAndMosaic(self, exposures, camera, binning, ids=None, refExps=None)
_undoInitialBackground(self, calExps, calBkgs, initialBackgroundIndex)
_restoreOriginalBackgroundRefineMask(self, calExps, calBkgs)
MaskedImage< ImagePixelT, MaskPixelT, VariancePixelT > * makeMaskedImage(typename std::shared_ptr< Image< ImagePixelT > > image, typename std::shared_ptr< Mask< MaskPixelT > > mask=Mask< MaskPixelT >(), typename std::shared_ptr< Image< VariancePixelT > > variance=Image< VariancePixelT >())
A function to return a MaskedImage of the correct type (cf.
std::shared_ptr< ImageT > binImage(ImageT const &inImage, int const binX, int const binY, lsst::afw::math::Property const flags=lsst::afw::math::MEAN)
Definition binImage.cc:44
_reorderAndPadList(inputList, inputKeys, outputKeys, padWith=None)
_skyFrameLookup(datasetType, registry, quantumDataId, collections)