Coverage for python/lsst/summit/utils/quickLook.py: 19%

97 statements  

« prev     ^ index     » next       coverage.py v7.14.1, created at 2026-05-28 09:04 +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/>. 

21 

22import dataclasses 

23import importlib.resources 

24from typing import Any 

25 

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 

36 

37__all__ = ["QuickLookIsrTask", "QuickLookIsrTaskConfig"] 

38 

39 

40class QuickLookIsrTaskConnections(IsrTaskLSSTConnections): 

41 """Copy isrTask's connections, changing prereq min values to zero. 

42 

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 """ 

47 

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 ) 

61 

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 

72 

73 

74class QuickLookIsrTaskConfig( 

75 pipeBase.PipelineTaskConfig, pipelineConnections=QuickLookIsrTaskConnections # type: ignore 

76): 

77 """Configuration parameters for QuickLookIsrTask.""" 

78 

79 doRepairCosmics: pexConfig.Field[bool] = pexConfig.Field( 

80 dtype=bool, doc="Interpolate over cosmic rays?", default=True 

81 ) 

82 

83 

84class QuickLookIsrTask(pipeBase.PipelineTask): 

85 """Task automatically performing as much isr as possible. Should never fail 

86 

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 """ 

93 

94 ConfigClass = QuickLookIsrTaskConfig 

95 config: QuickLookIsrTaskConfig 

96 _DefaultName = "quickLook" 

97 

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 

107 

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. 

126 

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. 

130 

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. 

168 

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 

182 

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 

192 

193 if bias: 

194 isrConfig.doBias = True 

195 self.log.info("Running with bias correction") 

196 

197 if dark: 

198 isrConfig.doDark = True 

199 self.log.info("Running with dark correction") 

200 

201 if flat: 

202 isrConfig.doFlat = True 

203 self.log.info("Running with flat correction") 

204 

205 if defects: 

206 isrConfig.doDefect = True 

207 self.log.info("Running with defect correction") 

208 

209 if linearizer: 

210 isrConfig.doLinearize = True 

211 self.log.info("Running with linearity correction") 

212 

213 if crosstalk: 

214 isrConfig.doCrosstalk = True 

215 self.log.info("Running with crosstalk correction") 

216 

217 if bfKernel is not None: 

218 isrConfig.doBrighterFatter = True 

219 self.log.info("Running with brighter-fatter correction") 

220 

221 if deferredChargeCalib is not None: 

222 isrConfig.doDeferredCharge = True 

223 self.log.info("Running with CTI correction") 

224 

225 if gainCorrection is not None: 

226 isrConfig.doCorrectGains = True 

227 self.log.info("Running with Gain corrections") 

228 

229 if ptc is None: 

230 raise RuntimeError("IsrTaskLSST requires a PTC.") 

231 

232 isrTask = self.isrTask(config=isrConfig) 

233 

234 # DM-47959: TODO Add fringe correction to IsrTaskLSST. 

235 

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 ) 

250 

251 postIsr = result.exposure 

252 

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) 

259 

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) 

270 

271 repairTask.repair.run(postIsr) 

272 except Exception as e: 

273 self.log.warning(f"During CR repair caught: {e}") 

274 

275 # exposure is returned for convenience to mimic isrTask's API. 

276 return pipeBase.Struct(exposure=postIsr, outputExposure=postIsr)