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