Coverage for python/lsst/cp/pipe/cpSkyTask.py: 58%
93 statements
« prev ^ index » next coverage.py v7.5.0, created at 2024-05-03 03:47 -0700
« prev ^ index » next coverage.py v7.5.0, created at 2024-05-03 03:47 -0700
1# This file is part of cp_pipe.
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 <http://www.gnu.org/licenses/>.
21import numpy as np
23import lsst.pex.config as pexConfig
24import lsst.pipe.base as pipeBase
25import lsst.pipe.base.connectionTypes as cT
27from lsst.pipe.tasks.background import (FocalPlaneBackground, MaskObjectsTask, SkyMeasurementTask,
28 FocalPlaneBackgroundConfig)
29from lsst.daf.base import PropertyList
30from .cpCombine import CalibCombineTask
32__all__ = ['CpSkyImageTask', 'CpSkyImageConfig',
33 'CpSkyScaleMeasureTask', 'CpSkyScaleMeasureConfig',
34 'CpSkySubtractBackgroundTask', 'CpSkySubtractBackgroundConfig',
35 'CpSkyCombineTask', 'CpSkyCombineConfig']
38class CpSkyImageConnections(pipeBase.PipelineTaskConnections,
39 dimensions=("instrument", "physical_filter", "exposure", "detector")):
40 inputExp = cT.Input(
41 name="postISRCCD",
42 doc="Input pre-processed exposures to combine.",
43 storageClass="Exposure",
44 dimensions=("instrument", "exposure", "detector"),
45 )
46 camera = cT.PrerequisiteInput(
47 name="camera",
48 doc="Input camera to use for geometry.",
49 storageClass="Camera",
50 dimensions=("instrument",),
51 isCalibration=True,
52 )
54 maskedExp = cT.Output(
55 name="cpSkyMaskedIsr",
56 doc="Output masked post-ISR exposure.",
57 storageClass="Exposure",
58 dimensions=("instrument", "exposure", "detector"),
59 )
60 maskedBkg = cT.Output(
61 name="cpSkyDetectorBackground",
62 doc="Initial background model from one image.",
63 storageClass="FocalPlaneBackground",
64 dimensions=("instrument", "exposure", "detector"),
65 )
68class CpSkyImageConfig(pipeBase.PipelineTaskConfig,
69 pipelineConnections=CpSkyImageConnections):
70 maskTask = pexConfig.ConfigurableField(
71 target=MaskObjectsTask,
72 doc="Object masker to use.",
73 )
75 maskThresh = pexConfig.Field(
76 dtype=float,
77 default=3.0,
78 doc="k-sigma threshold for masking pixels.",
79 )
80 maskList = pexConfig.ListField(
81 dtype=str,
82 default=["DETECTED", "BAD", "NO_DATA", "SAT"],
83 doc="Mask planes to reject.",
84 )
86 largeScaleBackground = pexConfig.ConfigField(
87 dtype=FocalPlaneBackgroundConfig,
88 doc="Large-scale background configuration.",
89 )
92class CpSkyImageTask(pipeBase.PipelineTask):
93 """Mask the detections on the postISRCCD.
95 This task maps the MaskObjectsTask across all of the initial ISR
96 processed cpSkyIsr images to create cpSkyMaskedIsr products for
97 all (exposure, detector) values.
98 """
100 ConfigClass = CpSkyImageConfig
101 _DefaultName = "CpSkyImage"
103 def __init__(self, **kwargs):
104 super().__init__(**kwargs)
105 self.makeSubtask("maskTask")
107 def run(self, inputExp, camera):
108 """Mask the detections on the postISRCCD.
110 Parameters
111 ----------
112 inputExp : `lsst.afw.image.Exposure`
113 An ISR processed exposure that will have detections
114 masked.
115 camera : `lsst.afw.cameraGeom.Camera`
116 The camera geometry for this exposure. This is needed to
117 create the background model.
119 Returns
120 -------
121 results : `lsst.pipe.base.Struct`
122 The results struct containing:
124 ``maskedExp`` : `lsst.afw.image.Exposure`
125 The detection-masked version of the ``inputExp``.
126 ``maskedBkg`` : `lsst.pipe.tasks.background.FocalPlaneBackground`
127 The partial focal plane background containing only
128 this exposure/detector's worth of data.
129 """
130 # As constructCalibs.py SkyTask.processSingleBackground()
131 # Except: check if a detector is fully masked to avoid
132 # self.maskTask raising.
133 currentMask = inputExp.getMask()
134 badMask = currentMask.getPlaneBitMask(self.config.maskList)
135 if (currentMask.getArray() & badMask).all():
136 self.log.warning("All pixels are masked!")
137 else:
138 self.maskTask.run(inputExp, self.config.maskList)
140 # As constructCalibs.py SkyTask.measureBackground()
141 bgModel = FocalPlaneBackground.fromCamera(self.config.largeScaleBackground, camera)
142 bgModel.addCcd(inputExp)
144 return pipeBase.Struct(
145 maskedExp=inputExp,
146 maskedBkg=bgModel,
147 )
150class CpSkyScaleMeasureConnections(pipeBase.PipelineTaskConnections,
151 dimensions=("instrument", "physical_filter", "exposure")):
152 inputBkgs = cT.Input(
153 name="cpSkyDetectorBackground",
154 doc="Initial background model from one exposure/detector",
155 storageClass="FocalPlaneBackground",
156 dimensions=("instrument", "exposure", "detector"),
157 multiple=True
158 )
160 outputBkg = cT.Output(
161 name="cpSkyExpBackground",
162 doc="Background model for a full exposure.",
163 storageClass="FocalPlaneBackground",
164 dimensions=("instrument", "exposure"),
165 )
166 outputScale = cT.Output(
167 name="cpSkyExpScale",
168 doc="Scale for the full exposure.",
169 storageClass="PropertyList",
170 dimensions=("instrument", "exposure"),
171 )
174class CpSkyScaleMeasureConfig(pipeBase.PipelineTaskConfig,
175 pipelineConnections=CpSkyScaleMeasureConnections):
176 # There are no configurable parameters here.
177 pass
180class CpSkyScaleMeasureTask(pipeBase.PipelineTask):
181 """Measure per-exposure scale factors and merge focal plane backgrounds.
183 Merge all the per-detector partial backgrounds to a full focal
184 plane background for each exposure, and measure the scale factor
185 from that full background.
186 """
188 ConfigClass = CpSkyScaleMeasureConfig
189 _DefaultName = "cpSkyScaleMeasure"
191 def run(self, inputBkgs):
192 """Merge focal plane backgrounds and measure the scale factor.
194 Parameters
195 ----------
196 inputBkgs : `list` [`lsst.pipe.tasks.background.FocalPlaneBackground`]
197 A list of all of the partial focal plane backgrounds, one
198 from each detector in this exposure.
200 Returns
201 -------
202 results : `lsst.pipe.base.Struct`
203 The results struct containing:
205 ``outputBkg`` : `lsst.pipe.tasks.background.FocalPlaneBackground`
206 The full merged background for the entire focal plane.
207 ``outputScale`` : `lsst.daf.base.PropertyList`
208 A metadata containing the median level of the
209 background, stored in the key 'scale'.
210 """
211 # As constructCalibs.py SkyTask.scatterProcess()
212 # Merge into the full focal plane.
213 background = inputBkgs[0]
214 for bg in inputBkgs[1:]:
215 background.merge(bg)
217 backgroundPixels = background.getStatsImage().getArray()
218 self.log.info("Background model min/max: %f %f. Scale %f",
219 np.min(backgroundPixels), np.max(backgroundPixels),
220 np.median(backgroundPixels))
222 # A property list is overkill, but FocalPlaneBackground
223 # doesn't have a metadata object that this can be stored in.
224 scale = np.median(background.getStatsImage().getArray())
225 scaleMD = PropertyList()
226 scaleMD.set("scale", float(scale))
228 return pipeBase.Struct(
229 outputBkg=background,
230 outputScale=scaleMD,
231 )
234class CpSkySubtractBackgroundConnections(pipeBase.PipelineTaskConnections,
235 dimensions=("instrument", "physical_filter",
236 "exposure", "detector")):
237 inputExp = cT.Input(
238 name="cpSkyMaskedIsr",
239 doc="Masked post-ISR image.",
240 storageClass="Exposure",
241 dimensions=("instrument", "exposure", "detector"),
242 )
243 inputBkg = cT.Input(
244 name="cpSkyExpBackground",
245 doc="Background model for the full exposure.",
246 storageClass="FocalPlaneBackground",
247 dimensions=("instrument", "exposure"),
248 )
249 inputScale = cT.Input(
250 name="cpSkyExpScale",
251 doc="Scale for the full exposure.",
252 storageClass="PropertyList",
253 dimensions=("instrument", "exposure"),
254 )
256 outputBkg = cT.Output(
257 name="cpExpBackground",
258 doc="Normalized, static background.",
259 storageClass="Background",
260 dimensions=("instrument", "exposure", "detector"),
261 )
264class CpSkySubtractBackgroundConfig(pipeBase.PipelineTaskConfig,
265 pipelineConnections=CpSkySubtractBackgroundConnections):
266 sky = pexConfig.ConfigurableField(
267 target=SkyMeasurementTask,
268 doc="Sky measurement",
269 )
272class CpSkySubtractBackgroundTask(pipeBase.PipelineTask):
273 """Subtract per-exposure background from individual detector masked images.
275 The cpSkyMaskedIsr images constructed by CpSkyImageTask have the
276 scaled background constructed by CpSkyScaleMeasureTask subtracted,
277 and new background models are constructed for the remaining
278 signal.
280 The output was called `icExpBackground` in gen2, but the product
281 created here has definition clashes that prevent that from being
282 reused.
283 """
285 ConfigClass = CpSkySubtractBackgroundConfig
286 _DefaultName = "cpSkySubtractBkg"
288 def __init__(self, **kwargs):
289 super().__init__(**kwargs)
290 self.makeSubtask("sky")
292 def run(self, inputExp, inputBkg, inputScale):
293 """Subtract per-exposure background from individual detector masked
294 images.
296 Parameters
297 ----------
298 inputExp : `lsst.afw.image.Exposure`
299 The ISR processed, detection masked image.
300 inputBkg : `lsst.pipe.tasks.background.FocalPlaneBackground.
301 Full focal plane background for this exposure.
302 inputScale : `lsst.daf.base.PropertyList`
303 Metadata containing the scale factor.
305 Returns
306 -------
307 results : `lsst.pipe.base.Struct`
308 The results struct containing:
310 ``outputBkg``
311 Remnant sky background with the full-exposure
312 component removed. (`lsst.afw.math.BackgroundList`)
313 """
314 # As constructCalibs.py SkyTask.processSingle()
315 image = inputExp.getMaskedImage()
316 detector = inputExp.getDetector()
317 bbox = image.getBBox()
319 scale = inputScale.get('scale')
320 background = inputBkg.toCcdBackground(detector, bbox)
321 image -= background.getImage()
322 image /= scale
324 newBackground = self.sky.measureBackground(image)
325 return pipeBase.Struct(
326 outputBkg=newBackground
327 )
330class CpSkyCombineConnections(pipeBase.PipelineTaskConnections,
331 dimensions=("instrument", "physical_filter", "detector")):
332 inputBkgs = cT.Input(
333 name="cpExpBackground",
334 doc="Normalized, static background.",
335 storageClass="Background",
336 dimensions=("instrument", "exposure", "detector"),
337 multiple=True,
338 )
339 inputExpHandles = cT.Input(
340 name="cpSkyMaskedIsr",
341 doc="Masked post-ISR image.",
342 storageClass="Exposure",
343 dimensions=("instrument", "exposure", "detector"),
344 multiple=True,
345 deferLoad=True,
346 )
348 outputCalib = cT.Output(
349 name="sky",
350 doc="Averaged static background.",
351 storageClass="ExposureF",
352 dimensions=("instrument", "detector", "physical_filter"),
353 isCalibration=True,
354 )
357class CpSkyCombineConfig(pipeBase.PipelineTaskConfig,
358 pipelineConnections=CpSkyCombineConnections):
359 sky = pexConfig.ConfigurableField(
360 target=SkyMeasurementTask,
361 doc="Sky measurement",
362 )
365class CpSkyCombineTask(pipeBase.PipelineTask):
366 """Merge per-exposure measurements into a detector level calibration.
368 Each of the per-detector results from all input exposures are
369 averaged to produce the final SKY calibration.
371 As before, this is written to a skyCalib instead of a SKY to avoid
372 definition classes in gen3.
373 """
375 ConfigClass = CpSkyCombineConfig
376 _DefaultName = "cpSkyCombine"
378 def __init__(self, **kwargs):
379 super().__init__(**kwargs)
380 self.makeSubtask("sky")
382 def run(self, inputBkgs, inputExpHandles):
383 """Merge per-exposure measurements into a detector level calibration.
385 Parameters
386 ----------
387 inputBkgs : `list` [`lsst.afw.math.BackgroundList`]
388 Remnant backgrounds from each exposure.
389 inputHandles : `list` [`lsst.daf.butler.DeferredDatasetHandles`]
390 The Butler handles to the ISR processed, detection masked images.
392 Returns
393 -------
394 results : `lsst.pipe.base.Struct`
395 The results struct containing:
397 `outputCalib` : `lsst.afw.image.Exposure`
398 The final sky calibration product.
399 """
400 skyCalib = self.sky.averageBackgrounds(inputBkgs)
401 skyCalib.setDetector(inputExpHandles[0].get(component='detector'))
402 skyCalib.setFilter(inputExpHandles[0].get(component='filter'))
404 CalibCombineTask().combineHeaders(inputExpHandles, skyCalib, calibType='SKY')
406 return pipeBase.Struct(
407 outputCalib=skyCalib,
408 )