Coverage for python / lsst / summit / utils / quickLook.py: 19%
103 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-05-05 19:02 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-05-05 19:02 +0000
1# This file is part of summit_utils.
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/>.
22import dataclasses
23import os
24from typing import Any
26import numpy as np
28import lsst.afw.cameraGeom as camGeom
29import lsst.afw.image as afwImage
30import lsst.ip.isr as ipIsr
31import lsst.pex.config as pexConfig
32import lsst.pipe.base as pipeBase
33import lsst.pipe.base.connectionTypes as cT
34from lsst.ip.isr import IsrTask
35from lsst.ip.isr.isrTask import IsrTaskConnections
36from lsst.meas.algorithms.installGaussianPsf import InstallGaussianPsfTask
37from lsst.pipe.tasks.characterizeImage import CharacterizeImageTask
38from lsst.utils import getPackageDir
40__all__ = ["QuickLookIsrTask", "QuickLookIsrTaskConfig"]
43class QuickLookIsrTaskConnections(IsrTaskConnections):
44 """Copy isrTask's connections, changing prereq min values to zero.
46 Copy all the connections directly for IsrTask, keeping ccdExposure as
47 required as non-zero, but changing all the other PrerequisiteInputs'
48 minimum values to zero.
49 """
51 def __init__(self, *, config: Any = None):
52 # programatically clone all of the connections from isrTask
53 # setting minimum values to zero for everything except the ccdExposure
54 super().__init__(config=IsrTask.ConfigClass()) # need a dummy config, isn't used other than for ctor
55 for name, connection in self.allConnections.items():
56 if hasattr(connection, "minimum"):
57 setattr(
58 self,
59 name,
60 dataclasses.replace(connection, minimum=(0 if name != "ccdExposure" else 1)),
61 )
63 exposure = cT.Output( # called just "exposure" to mimic isrTask's return struct
64 name="quickLookExp",
65 doc="The quickLook output exposure.",
66 storageClass="ExposureF",
67 dimensions=("instrument", "exposure", "detector"),
68 )
69 # set like this to make it explicit that the outputExposure
70 # and the exposure are identical. The only reason there are two is for
71 # API compatibility.
72 self.outputExposure = exposure
75class QuickLookIsrTaskConfig(
76 pipeBase.PipelineTaskConfig, pipelineConnections=QuickLookIsrTaskConnections # type: ignore
77):
78 """Configuration parameters for QuickLookIsrTask."""
80 doRepairCosmics: pexConfig.Field[bool] = pexConfig.Field(
81 dtype=bool, doc="Interpolate over cosmic rays?", default=True
82 )
85class QuickLookIsrTask(pipeBase.PipelineTask):
86 """Task automatically performing as much isr as possible. Should never fail
88 Automatically performs as much isr as is possible, depending on the
89 calibration products available. All calibration products that can be found
90 are applied, and if none are found, the image is assembled, the overscan is
91 subtracted and the assembled image is returned. Optionally, cosmic rays are
92 interpolated over.
93 """
95 ConfigClass = QuickLookIsrTaskConfig
96 config: QuickLookIsrTaskConfig
97 _DefaultName = "quickLook"
99 def __init__(self, isrTask: IsrTask = IsrTask, **kwargs: Any):
100 super().__init__(**kwargs)
101 # Pass in IsrTask so that we can modify it slightly for unit tests.
102 # Note that this is not an instance of the IsrTask class, but the class
103 # itself, which is then instantiated later on, in the run() method,
104 # with the dynamically generated config.
105 self.isrTask = IsrTask
107 def run(
108 self,
109 ccdExposure: afwImage.Exposure,
110 *,
111 camera: camGeom.Camera | None = None,
112 bias: afwImage.Exposure | None = None,
113 dark: afwImage.Exposure | None = None,
114 flat: afwImage.Exposure | None = None,
115 fringes: afwImage.Exposure | None = None,
116 defects: ipIsr.Defects | None = None,
117 linearizer: ipIsr.linearize.LinearizeBase | None = None,
118 crosstalk: ipIsr.crosstalk.CrosstalkCalib | None = None,
119 bfKernel: np.ndarray | None = None,
120 newBFKernel: ipIsr.BrighterFatterKernel | None = None,
121 ptc: ipIsr.PhotonTransferCurveDataset | None = None,
122 crosstalkSources: list | None = None,
123 isrBaseConfig: ipIsr.IsrTaskConfig | None = None,
124 filterTransmission: afwImage.TransmissionCurve | None = None,
125 opticsTransmission: afwImage.TransmissionCurve | None = None,
126 strayLightData: Any | None = None,
127 sensorTransmission: afwImage.TransmissionCurve | None = None,
128 atmosphereTransmission: afwImage.TransmissionCurve | None = None,
129 deferredChargeCalib: Any | None = None,
130 illumMaskedImage: afwImage.MaskedImage | None = None,
131 ) -> pipeBase.Struct:
132 """Run isr and cosmic ray repair using, doing as much isr as possible.
134 Retrieves as many calibration products as are available, and runs isr
135 with those settings enabled, but always returns an assembled image at
136 a minimum. Then performs cosmic ray repair if configured to.
138 Parameters
139 ----------
140 ccdExposure : `lsst.afw.image.Exposure`
141 The raw exposure that is to be run through ISR. The
142 exposure is modified by this method.
143 camera : `lsst.afw.cameraGeom.Camera`, optional
144 The camera geometry for this exposure. Required if
145 one or more of ``ccdExposure``, ``bias``, ``dark``, or
146 ``flat`` does not have an associated detector.
147 bias : `lsst.afw.image.Exposure`, optional
148 Bias calibration frame.
149 dark : `lsst.afw.image.Exposure`, optional
150 Dark calibration frame.
151 flat : `lsst.afw.image.Exposure`, optional
152 Flat calibration frame.
153 fringes : `lsst.afw.image.Exposure`, optional
154 The fringe correction data.
155 This input is slightly different than the `fringes` keyword to
156 `lsst.ip.isr.IsrTask`, since the processing done in that task's
157 `runQuantum` method is instead done here.
158 defects : `lsst.ip.isr.Defects`, optional
159 List of defects.
160 linearizer : `lsst.ip.isr.linearize.LinearizeBase`, optional
161 Functor for linearization.
162 crosstalk : `lsst.ip.isr.crosstalk.CrosstalkCalib`, optional
163 Calibration for crosstalk.
164 bfKernel : `numpy.ndarray`, optional
165 Brighter-fatter kernel.
166 newBFKernel : `ipIsr.BrighterFatterKernel`, optional
167 New Brighter-fatter kernel.
168 ptc : `lsst.ip.isr.PhotonTransferCurveDataset`, optional
169 Photon transfer curve dataset, with, e.g., gains
170 and read noise.
171 crosstalkSources : `list`, optional
172 List of possible crosstalk sources.
173 isrBaseConfig : `lsst.ip.isr.IsrTaskConfig`, optional
174 An isrTask config to act as the base configuration. Options which
175 involve applying a calibration product are ignored, but this allows
176 for the configuration of e.g. the number of overscan columns.
177 filterTransmission : `lsst.afw.image.TransmissionCurve`
178 A ``TransmissionCurve`` that represents the throughput of the
179 filter itself, to be evaluated in focal-plane coordinates.
180 opticsTransmission: `lsst.afw.image.TransmissionCurve`, optional
181 A ``TransmissionCurve`` that represents the throughput of the,
182 optics, to be evaluated in focal-plane coordinates.
183 strayLightData : `object`, optional
184 Opaque object containing calibration information for stray-light
185 correction. If `None`, no correction will be performed.
186 sensorTransmission : `lsst.afw.image.TransmissionCurve`
187 A ``TransmissionCurve`` that represents the throughput of the
188 sensor itself, to be evaluated in post-assembly trimmed detector
189 coordinates.
190 atmosphereTransmission : `lsst.afw.image.TransmissionCurve`
191 A ``TransmissionCurve`` that represents the throughput of the
192 atmosphere, assumed to be spatially constant.
193 illumMaskedImage : `lsst.afw.image.MaskedImage`, optional
194 Illumination correction image.
195 bfGains : `dict` of `float`, optional
196 Gains used to override the detector's nominal gains for the
197 brighter-fatter correction. A dict keyed by amplifier name for
198 the detector in question.
200 Returns
201 -------
202 result : `lsst.pipe.base.Struct`
203 Result struct with component:
204 - ``exposure`` : `afw.image.Exposure`
205 The ISRed and cosmic-ray-repaired exposure.
206 """
207 if not isrBaseConfig:
208 isrConfig = IsrTask.ConfigClass()
209 packageDir = getPackageDir("summit_utils")
210 isrConfig.load(os.path.join(packageDir, "config", "quickLookIsr.py"))
211 else:
212 isrConfig = isrBaseConfig
214 isrConfig.doBias = False
215 isrConfig.doDark = False
216 isrConfig.doFlat = False
217 isrConfig.doFringe = False
218 isrConfig.doDefect = False
219 isrConfig.doLinearize = False
220 isrConfig.doCrosstalk = False
221 isrConfig.doBrighterFatter = False
222 isrConfig.usePtcGains = False
224 if bias:
225 isrConfig.doBias = True
226 self.log.info("Running with bias correction")
228 if dark:
229 isrConfig.doDark = True
230 self.log.info("Running with dark correction")
232 if flat:
233 isrConfig.doFlat = True
234 self.log.info("Running with flat correction")
236 if fringes:
237 isrConfig.doFringe = True
238 self.log.info("Running with fringe correction")
240 if defects:
241 isrConfig.doDefect = True
242 self.log.info("Running with defect correction")
244 if linearizer:
245 isrConfig.doLinearize = True
246 self.log.info("Running with linearity correction")
248 if crosstalk:
249 isrConfig.doCrosstalk = True
250 self.log.info("Running with crosstalk correction")
252 if newBFKernel is not None:
253 bfGains = newBFKernel.gain
254 isrConfig.doBrighterFatter = True
255 self.log.info("Running with new brighter-fatter correction")
256 else:
257 bfGains = None
259 if bfKernel is not None and bfGains is None:
260 isrConfig.doBrighterFatter = True
261 self.log.info("Running with brighter-fatter correction")
263 if ptc:
264 isrConfig.usePtcGains = True
265 self.log.info("Running with ptc correction")
267 isrConfig.doWrite = False
268 isrTask = self.isrTask(config=isrConfig)
270 if fringes:
271 # Must be run after isrTask is instantiated.
272 isrTask.fringe.loadFringes(
273 fringes,
274 expId=ccdExposure.info.id,
275 assembler=isrTask.assembleCcd if isrConfig.doAssembleIsrExposures else None,
276 )
278 result = isrTask.run(
279 ccdExposure,
280 camera=camera,
281 bias=bias,
282 dark=dark,
283 flat=flat,
284 fringes=fringes,
285 defects=defects,
286 linearizer=linearizer,
287 crosstalk=crosstalk,
288 bfKernel=bfKernel,
289 bfGains=bfGains,
290 ptc=ptc,
291 crosstalkSources=crosstalkSources,
292 filterTransmission=filterTransmission,
293 opticsTransmission=opticsTransmission,
294 sensorTransmission=sensorTransmission,
295 atmosphereTransmission=atmosphereTransmission,
296 strayLightData=strayLightData,
297 deferredChargeCalib=deferredChargeCalib,
298 illumMaskedImage=illumMaskedImage,
299 )
301 postIsr = result.exposure
303 if self.config.doRepairCosmics:
304 try: # can fail due to too many CRs detected, and we always want an exposure back
305 self.log.info("Repairing cosmics...")
306 if postIsr.getPsf() is None:
307 installPsfTask = InstallGaussianPsfTask()
308 installPsfTask.run(postIsr)
310 # TODO: try adding a reasonably wide Gaussian as a temp PSF
311 # and then just running repairTask on its own without any
312 # imChar. It should work, and be faster.
313 repairConfig = CharacterizeImageTask.ConfigClass()
314 repairConfig.doMeasurePsf = False
315 repairConfig.doApCorr = False
316 repairConfig.doDeblend = False
317 repairConfig.doWrite = False
318 repairConfig.repair.cosmicray.nCrPixelMax = 200000
319 repairTask = CharacterizeImageTask(config=repairConfig)
321 repairTask.repair.run(postIsr)
322 except Exception as e:
323 self.log.warning(f"During CR repair caught: {e}")
325 # exposure is returned for convenience to mimic isrTask's API.
326 return pipeBase.Struct(exposure=postIsr, outputExposure=postIsr)