Coverage for python/lsst/summit/utils/quickLook.py: 19%
97 statements
« prev ^ index » next coverage.py v7.14.1, created at 2026-05-29 02:24 -0700
« prev ^ index » next coverage.py v7.14.1, created at 2026-05-29 02:24 -0700
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 lsst.afw.cameraGeom as camGeom
27import lsst.afw.image as afwImage
28import lsst.ip.isr as ipIsr
29import lsst.pex.config as pexConfig
30import lsst.pipe.base as pipeBase
31import lsst.pipe.base.connectionTypes as cT
32from lsst.ip.isr import IsrTaskLSST
33from lsst.ip.isr.isrTaskLSST import IsrTaskLSSTConnections
34from lsst.meas.algorithms.installGaussianPsf import InstallGaussianPsfTask
35from lsst.pipe.tasks.characterizeImage import CharacterizeImageTask
37__all__ = ["QuickLookIsrTask", "QuickLookIsrTaskConfig"]
40class QuickLookIsrTaskConnections(IsrTaskLSSTConnections):
41 """Copy isrTask's connections, changing prereq min values to zero.
43 Copy all the connections directly for IsrTask, keeping ccdExposure as
44 required as non-zero, but changing all the other PrerequisiteInputs'
45 minimum values to zero.
46 """
48 def __init__(self, *, config: Any = None):
49 # programatically clone all of the connections from isrTask
50 # setting minimum values to zero for everything except the ccdExposure
51 super().__init__(
52 config=IsrTaskLSST.ConfigClass()
53 ) # 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: IsrTaskLSST = IsrTaskLSST, **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 if IsrTaskLSST._DefaultName != "isrLSST":
105 raise RuntimeError("QuickLookIsrTask should now always use IsrTaskLSST for processing.")
106 self.isrTask = IsrTaskLSST
108 def run(
109 self,
110 ccdExposure: afwImage.Exposure,
111 *,
112 camera: camGeom.Camera | None = None,
113 bias: afwImage.Exposure | None = None,
114 dark: afwImage.Exposure | None = None,
115 flat: 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: ipIsr.BrighterFatterKernel | None = None,
120 ptc: ipIsr.PhotonTransferCurveDataset | None = None,
121 isrBaseConfig: ipIsr.IsrTaskLSSTConfig | None = None,
122 deferredChargeCalib: Any | None = None,
123 gainCorrection: ipIsr.IsrCalib | None = None,
124 ) -> pipeBase.Struct:
125 """Run isr and cosmic ray repair using, doing as much isr as possible.
127 Retrieves as many calibration products as are available, and runs isr
128 with those settings enabled, but always returns an assembled image at
129 a minimum. Then performs cosmic ray repair if configured to.
131 Parameters
132 ----------
133 ccdExposure : `lsst.afw.image.Exposure`
134 The raw exposure that is to be run through ISR. The
135 exposure is modified by this method.
136 camera : `lsst.afw.cameraGeom.Camera`, optional
137 The camera geometry for this exposure. Required if
138 one or more of ``ccdExposure``, ``bias``, ``dark``, or
139 ``flat`` does not have an associated detector.
140 bias : `lsst.afw.image.Exposure`, optional
141 Bias calibration frame.
142 dark : `lsst.afw.image.Exposure`, optional
143 Dark calibration frame.
144 flat : `lsst.afw.image.Exposure`, optional
145 Flat calibration frame.
146 fringes : `lsst.afw.image.Exposure`, optional
147 The fringe correction data.
148 This input is slightly different than the `fringes` keyword to
149 `lsst.ip.isr.IsrTask`, since the processing done in that task's
150 `runQuantum` method is instead done here.
151 defects : `lsst.ip.isr.Defects`, optional
152 List of defects.
153 linearizer : `lsst.ip.isr.linearize.LinearizeBase`, optional
154 Functor for linearization.
155 crosstalk : `lsst.ip.isr.crosstalk.CrosstalkCalib`, optional
156 Calibration for crosstalk.
157 bfKernel : `ipIsr.BrighterFatterKernel`, optional
158 New Brighter-fatter kernel.
159 ptc : `lsst.ip.isr.PhotonTransferCurveDataset`, optional
160 Photon transfer curve dataset, with, e.g., gains
161 and read noise.
162 cti : `lsst.ip.isr.DeferredChargeCalib`, optional
163 Charge transfer inefficiency correction calibration.
164 isrBaseConfig : `lsst.ip.isr.IsrTaskLSSTConfig`, optional
165 An isrTask config to act as the base configuration. Options which
166 involve applying a calibration product are ignored, but this allows
167 for the configuration of e.g. the number of overscan columns.
169 Returns
170 -------
171 result : `lsst.pipe.base.Struct`
172 Result struct with component:
173 - ``exposure`` : `afw.image.Exposure`
174 The ISRed and cosmic-ray-repaired exposure.
175 """
176 if not isrBaseConfig:
177 isrConfig = IsrTaskLSST.ConfigClass()
178 with importlib.resources.path("lsst.summit.utils", "resources/config/quickLookIsr.py") as cfgPath:
179 isrConfig.load(cfgPath)
180 else:
181 isrConfig = isrBaseConfig
183 isrConfig.doBias = False
184 isrConfig.doDark = False
185 isrConfig.doFlat = False
186 isrConfig.doDefect = False
187 isrConfig.doLinearize = False
188 isrConfig.doCrosstalk = False
189 isrConfig.doBrighterFatter = False
190 isrConfig.doDeferredCharge = False
191 isrConfig.doCorrectGains = False
193 if bias:
194 isrConfig.doBias = True
195 self.log.info("Running with bias correction")
197 if dark:
198 isrConfig.doDark = True
199 self.log.info("Running with dark correction")
201 if flat:
202 isrConfig.doFlat = True
203 self.log.info("Running with flat correction")
205 if defects:
206 isrConfig.doDefect = True
207 self.log.info("Running with defect correction")
209 if linearizer:
210 isrConfig.doLinearize = True
211 self.log.info("Running with linearity correction")
213 if crosstalk:
214 isrConfig.doCrosstalk = True
215 self.log.info("Running with crosstalk correction")
217 if bfKernel is not None:
218 isrConfig.doBrighterFatter = True
219 self.log.info("Running with brighter-fatter correction")
221 if deferredChargeCalib is not None:
222 isrConfig.doDeferredCharge = True
223 self.log.info("Running with CTI correction")
225 if gainCorrection is not None:
226 isrConfig.doCorrectGains = True
227 self.log.info("Running with Gain corrections")
229 if ptc is None:
230 raise RuntimeError("IsrTaskLSST requires a PTC.")
232 isrTask = self.isrTask(config=isrConfig)
234 # DM-47959: TODO Add fringe correction to IsrTaskLSST.
236 result = isrTask.run(
237 ccdExposure,
238 camera=camera,
239 bias=bias,
240 dark=dark,
241 flat=flat,
242 defects=defects,
243 linearizer=linearizer,
244 crosstalk=crosstalk,
245 bfKernel=bfKernel,
246 ptc=ptc,
247 deferredChargeCalib=deferredChargeCalib,
248 gainCorrection=gainCorrection,
249 )
251 postIsr = result.exposure
253 if self.config.doRepairCosmics:
254 try: # can fail due to too many CRs detected, and we always want an exposure back
255 self.log.info("Repairing cosmics...")
256 if postIsr.getPsf() is None:
257 installPsfTask = InstallGaussianPsfTask()
258 installPsfTask.run(postIsr)
260 # TODO: try adding a reasonably wide Gaussian as a temp PSF
261 # and then just running repairTask on its own without any
262 # imChar. It should work, and be faster.
263 repairConfig = CharacterizeImageTask.ConfigClass()
264 repairConfig.doMeasurePsf = False
265 repairConfig.doApCorr = False
266 repairConfig.doDeblend = False
267 repairConfig.doWrite = False
268 repairConfig.repair.cosmicray.nCrPixelMax = 200000
269 repairTask = CharacterizeImageTask(config=repairConfig)
271 repairTask.repair.run(postIsr)
272 except Exception as e:
273 self.log.warning(f"During CR repair caught: {e}")
275 # exposure is returned for convenience to mimic isrTask's API.
276 return pipeBase.Struct(exposure=postIsr, outputExposure=postIsr)