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

102 statements  

« prev     ^ index     » next       coverage.py v7.5.1, created at 2024-05-08 04:48 -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/>. 

21 

22import dataclasses 

23import os 

24from typing import Any 

25 

26import numpy as np 

27 

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 

39 

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

41 

42 

43class QuickLookIsrTaskConnections(IsrTaskConnections): 

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

45 

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

50 

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 ) 

62 

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 

73 

74 

75class QuickLookIsrTaskConfig(pipeBase.PipelineTaskConfig, pipelineConnections=QuickLookIsrTaskConnections): 

76 """Configuration parameters for QuickLookIsrTask.""" 

77 

78 doRepairCosmics = pexConfig.Field(dtype=bool, doc="Interpolate over cosmic rays?", default=True) 

79 

80 

81class QuickLookIsrTask(pipeBase.PipelineTask): 

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

83 

84 Automatically performs as much isr as is possible, depending on the 

85 calibration products available. All calibration products that can be found 

86 are applied, and if none are found, the image is assembled, the overscan is 

87 subtracted and the assembled image is returned. Optionally, cosmic rays are 

88 interpolated over. 

89 """ 

90 

91 ConfigClass = QuickLookIsrTaskConfig 

92 _DefaultName = "quickLook" 

93 

94 def __init__(self, isrTask: IsrTask = IsrTask, **kwargs: Any): 

95 super().__init__(**kwargs) 

96 # Pass in IsrTask so that we can modify it slightly for unit tests. 

97 # Note that this is not an instance of the IsrTask class, but the class 

98 # itself, which is then instantiated later on, in the run() method, 

99 # with the dynamically generated config. 

100 self.isrTask = IsrTask 

101 

102 def run( 

103 self, 

104 ccdExposure: afwImage.Exposure, 

105 *, 

106 camera: camGeom.Camera | None = None, 

107 bias: afwImage.Exposure | None = None, 

108 dark: afwImage.Exposure | None = None, 

109 flat: afwImage.Exposure | None = None, 

110 fringes: afwImage.Exposure | None = None, 

111 defects: ipIsr.Defects | None = None, 

112 linearizer: ipIsr.linearize.LinearizeBase | None = None, 

113 crosstalk: ipIsr.crosstalk.CrosstalkCalib | None = None, 

114 bfKernel: ipIsr.BrighterFatterKernel | None = None, 

115 newBFKernel: np.ndarray | None = None, 

116 ptc: ipIsr.PhotonTransferCurveDataset | None = None, 

117 crosstalkSources: list | None = None, 

118 isrBaseConfig: ipIsr.IsrTaskConfig | None = None, 

119 filterTransmission: afwImage.TransmissionCurve | None = None, 

120 opticsTransmission: afwImage.TransmissionCurve | None = None, 

121 strayLightData: Any | None = None, 

122 sensorTransmission: afwImage.TransmissionCurve | None = None, 

123 atmosphereTransmission: afwImage.TransmissionCurve | None = None, 

124 deferredChargeCalib: Any | None = None, 

125 illumMaskedImage: afwImage.MaskedImage | None = None, 

126 ) -> pipeBase.Struct: 

127 """Run isr and cosmic ray repair using, doing as much isr as possible. 

128 

129 Retrieves as many calibration products as are available, and runs isr 

130 with those settings enabled, but always returns an assembled image at 

131 a minimum. Then performs cosmic ray repair if configured to. 

132 

133 Parameters 

134 ---------- 

135 ccdExposure : `lsst.afw.image.Exposure` 

136 The raw exposure that is to be run through ISR. The 

137 exposure is modified by this method. 

138 camera : `lsst.afw.cameraGeom.Camera`, optional 

139 The camera geometry for this exposure. Required if 

140 one or more of ``ccdExposure``, ``bias``, ``dark``, or 

141 ``flat`` does not have an associated detector. 

142 bias : `lsst.afw.image.Exposure`, optional 

143 Bias calibration frame. 

144 dark : `lsst.afw.image.Exposure`, optional 

145 Dark calibration frame. 

146 flat : `lsst.afw.image.Exposure`, optional 

147 Flat calibration frame. 

148 fringes : `lsst.afw.image.Exposure`, optional 

149 The fringe correction data. 

150 This input is slightly different than the `fringes` keyword to 

151 `lsst.ip.isr.IsrTask`, since the processing done in that task's 

152 `runQuantum` method is instead done here. 

153 defects : `lsst.ip.isr.Defects`, optional 

154 List of defects. 

155 linearizer : `lsst.ip.isr.linearize.LinearizeBase`, optional 

156 Functor for linearization. 

157 crosstalk : `lsst.ip.isr.crosstalk.CrosstalkCalib`, optional 

158 Calibration for crosstalk. 

159 bfKernel : `ipIsr.BrighterFatterKernel`, optional 

160 Brighter-fatter kernel. 

161 newBFKernel : `numpy.ndarray`, optional 

162 New Brighter-fatter kernel. 

163 ptc : `lsst.ip.isr.PhotonTransferCurveDataset`, optional 

164 Photon transfer curve dataset, with, e.g., gains 

165 and read noise. 

166 crosstalkSources : `list`, optional 

167 List of possible crosstalk sources. 

168 isrBaseConfig : `lsst.ip.isr.IsrTaskConfig`, optional 

169 An isrTask config to act as the base configuration. Options which 

170 involve applying a calibration product are ignored, but this allows 

171 for the configuration of e.g. the number of overscan columns. 

172 filterTransmission : `lsst.afw.image.TransmissionCurve` 

173 A ``TransmissionCurve`` that represents the throughput of the 

174 filter itself, to be evaluated in focal-plane coordinates. 

175 opticsTransmission: `lsst.afw.image.TransmissionCurve`, optional 

176 A ``TransmissionCurve`` that represents the throughput of the, 

177 optics, to be evaluated in focal-plane coordinates. 

178 strayLightData : `object`, optional 

179 Opaque object containing calibration information for stray-light 

180 correction. If `None`, no correction will be performed. 

181 sensorTransmission : `lsst.afw.image.TransmissionCurve` 

182 A ``TransmissionCurve`` that represents the throughput of the 

183 sensor itself, to be evaluated in post-assembly trimmed detector 

184 coordinates. 

185 atmosphereTransmission : `lsst.afw.image.TransmissionCurve` 

186 A ``TransmissionCurve`` that represents the throughput of the 

187 atmosphere, assumed to be spatially constant. 

188 illumMaskedImage : `lsst.afw.image.MaskedImage`, optional 

189 Illumination correction image. 

190 bfGains : `dict` of `float`, optional 

191 Gains used to override the detector's nominal gains for the 

192 brighter-fatter correction. A dict keyed by amplifier name for 

193 the detector in question. 

194 

195 Returns 

196 ------- 

197 result : `lsst.pipe.base.Struct` 

198 Result struct with component: 

199 - ``exposure`` : `afw.image.Exposure` 

200 The ISRed and cosmic-ray-repaired exposure. 

201 """ 

202 if not isrBaseConfig: 

203 isrConfig = IsrTask.ConfigClass() 

204 packageDir = getPackageDir("summit_utils") 

205 isrConfig.load(os.path.join(packageDir, "config", "quickLookIsr.py")) 

206 else: 

207 isrConfig = isrBaseConfig 

208 

209 isrConfig.doBias = False 

210 isrConfig.doDark = False 

211 isrConfig.doFlat = False 

212 isrConfig.doFringe = False 

213 isrConfig.doDefect = False 

214 isrConfig.doLinearize = False 

215 isrConfig.doCrosstalk = False 

216 isrConfig.doBrighterFatter = False 

217 isrConfig.usePtcGains = False 

218 

219 if bias: 

220 isrConfig.doBias = True 

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

222 

223 if dark: 

224 isrConfig.doDark = True 

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

226 

227 if flat: 

228 isrConfig.doFlat = True 

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

230 

231 if fringes: 

232 isrConfig.doFringe = True 

233 self.log.info("Running with fringe correction") 

234 

235 if defects: 

236 isrConfig.doDefect = True 

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

238 

239 if linearizer: 

240 isrConfig.doLinearize = True 

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

242 

243 if crosstalk: 

244 isrConfig.doCrosstalk = True 

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

246 

247 if newBFKernel is not None: 

248 bfGains = newBFKernel.gain 

249 isrConfig.doBrighterFatter = True 

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

251 else: 

252 bfGains = None 

253 

254 if bfKernel is not None and bfGains is None: 

255 isrConfig.doBrighterFatter = True 

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

257 

258 if ptc: 

259 isrConfig.usePtcGains = True 

260 self.log.info("Running with ptc correction") 

261 

262 isrConfig.doWrite = False 

263 isrTask = self.isrTask(config=isrConfig) 

264 

265 if fringes: 

266 # Must be run after isrTask is instantiated. 

267 isrTask.fringe.loadFringes( 

268 fringes, 

269 expId=ccdExposure.info.id, 

270 assembler=isrTask.assembleCcd if isrConfig.doAssembleIsrExposures else None, 

271 ) 

272 

273 result = isrTask.run( 

274 ccdExposure, 

275 camera=camera, 

276 bias=bias, 

277 dark=dark, 

278 flat=flat, 

279 fringes=fringes, 

280 defects=defects, 

281 linearizer=linearizer, 

282 crosstalk=crosstalk, 

283 bfKernel=bfKernel, 

284 bfGains=bfGains, 

285 ptc=ptc, 

286 crosstalkSources=crosstalkSources, 

287 filterTransmission=filterTransmission, 

288 opticsTransmission=opticsTransmission, 

289 sensorTransmission=sensorTransmission, 

290 atmosphereTransmission=atmosphereTransmission, 

291 strayLightData=strayLightData, 

292 deferredChargeCalib=deferredChargeCalib, 

293 illumMaskedImage=illumMaskedImage, 

294 ) 

295 

296 postIsr = result.exposure 

297 

298 if self.config.doRepairCosmics: 

299 try: # can fail due to too many CRs detected, and we always want an exposure back 

300 self.log.info("Repairing cosmics...") 

301 if postIsr.getPsf() is None: 

302 installPsfTask = InstallGaussianPsfTask() 

303 installPsfTask.run(postIsr) 

304 

305 # TODO: try adding a reasonably wide Gaussian as a temp PSF 

306 # and then just running repairTask on its own without any 

307 # imChar. It should work, and be faster. 

308 repairConfig = CharacterizeImageTask.ConfigClass() 

309 repairConfig.doMeasurePsf = False 

310 repairConfig.doApCorr = False 

311 repairConfig.doDeblend = False 

312 repairConfig.doWrite = False 

313 repairConfig.repair.cosmicray.nCrPixelMax = 200000 

314 repairTask = CharacterizeImageTask(config=repairConfig) 

315 

316 repairTask.repair.run(postIsr) 

317 except Exception as e: 

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

319 

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

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