Coverage for tests/test_quickLook.py: 18%

153 statements  

« 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/>. 

21 

22import contextlib 

23import tempfile 

24import unittest 

25 

26import lsst.afw.image as afwImage 

27import lsst.daf.butler.tests as butlerTests 

28import lsst.ip.isr as ipIsr 

29import lsst.ip.isr.isrMockLSST as isrMock 

30import lsst.pex.exceptions 

31import lsst.pipe.base as pipeBase 

32import lsst.pipe.base.testUtils 

33import lsst.utils.tests 

34from lsst.summit.utils.quickLook import QuickLookIsrTask, QuickLookIsrTaskConfig 

35 

36 

37class QuickLookIsrTaskTestCase(unittest.TestCase): 

38 """Tests of the run method with fake data.""" 

39 

40 def setUp(self): 

41 self.mockConfig = isrMock.IsrMockLSSTConfig() 

42 self.camera = isrMock.IsrMockLSST(config=self.mockConfig).getCamera() 

43 

44 self.ccdExposure = isrMock.RawMockLSST(config=self.mockConfig).run() 

45 self.detector = self.ccdExposure.getDetector() 

46 amps = self.detector.getAmplifiers() 

47 ampNames = [amp.getName() for amp in amps] 

48 

49 # # Mock other optional parameters 

50 self.bias = isrMock.BiasMockLSST(config=self.mockConfig).run() 

51 self.dark = isrMock.DarkMockLSST(config=self.mockConfig).run() 

52 self.flat = isrMock.FlatMockLSST(config=self.mockConfig).run() 

53 self.defects = isrMock.DefectMockLSST(config=self.mockConfig).run() 

54 self.ptc = ipIsr.PhotonTransferCurveDataset(ampNames=ampNames) # Mock PTC dataset 

55 for amp, gain in self.mockConfig.gainDict.items(): 

56 self.ptc.gain[amp] = 1.0 

57 self.bfKernel = isrMock.BfKernelMockLSST(config=self.mockConfig).run() 

58 self.task = QuickLookIsrTask(config=QuickLookIsrTaskConfig()) 

59 

60 def test_runQuickLook(self): 

61 # Execute the run method with the mock data 

62 result = self.task.run( 

63 self.ccdExposure, 

64 camera=self.camera, 

65 bias=self.bias, 

66 dark=self.dark, 

67 flat=self.flat, 

68 defects=self.defects, 

69 linearizer=None, 

70 crosstalk=None, 

71 bfKernel=self.bfKernel, 

72 ptc=self.ptc, 

73 ) 

74 self.assertIsNotNone(result, "Result of run method should not be None") 

75 self.assertIsInstance(result, pipeBase.Struct, "Result should be of type lsst.pipe.base.Struct") 

76 self.assertIsInstance( 

77 result.exposure, 

78 afwImage.Exposure, 

79 "Resulting exposure should be an instance of lsst.afw.image.Exposure", 

80 ) 

81 

82 def test_runQuickLookMissingData(self): 

83 # Test without any inputs other than the exposure. And the PTC. 

84 result = self.task.run(self.ccdExposure, ptc=self.ptc) 

85 self.assertIsInstance(result.exposure, afwImage.Exposure) 

86 

87 def test_runQuickLookBadDark(self): 

88 # Test with an incorrect dark frame 

89 bbox = self.ccdExposure.getBBox() 

90 bbox.grow(-20) 

91 with self.assertRaises(lsst.pex.exceptions.wrappers.LengthError): 

92 self.task.run( 

93 self.ccdExposure, 

94 camera=self.camera, 

95 bias=self.bias, 

96 dark=self.dark[bbox], 

97 flat=self.flat, 

98 defects=self.defects, 

99 ) 

100 

101 

102class QuickLookIsrTaskRunQuantumTests(lsst.utils.tests.TestCase): 

103 """Tests of ``QuickLookIsrTask.runQuantum``, which need a test butler, 

104 but do not need real images. 

105 

106 Adapted from the unit tests of ``CalibrateImageTask.runQuantum`` 

107 """ 

108 

109 def setUp(self): 

110 # These need to be real, not empty: 

111 self.mockConfig = isrMock.IsrMockLSSTConfig() 

112 self.camera = isrMock.IsrMockLSST(config=self.mockConfig).getCamera() 

113 

114 self.ccdExposure = isrMock.RawMockLSST(config=self.mockConfig).run() 

115 self.bias = isrMock.BiasMockLSST(config=self.mockConfig).run() 

116 self.dark = isrMock.DarkMockLSST(config=self.mockConfig).run() 

117 self.flat = isrMock.FlatMockLSST(config=self.mockConfig).run() 

118 self.defects = isrMock.DefectMockLSST(config=self.mockConfig).run() 

119 

120 amps = self.ccdExposure.getDetector().getAmplifiers() 

121 ampNames = [amp.getName() for amp in amps] 

122 self.ptc = ipIsr.PhotonTransferCurveDataset(ampNames=ampNames) # Mock PTC dataset 

123 for amp, gain in self.mockConfig.gainDict.items(): 

124 self.ptc.gain[amp] = 1.0 

125 self.crosstalk = lsst.ip.isr.crosstalk.CrosstalkCalib(nAmp=len(ampNames)) 

126 self.crosstalk.hasCrosstalk = True 

127 self.cti = isrMock.DeferredChargeMockLSST(config=self.mockConfig).run() 

128 self.mockConfig.doDeferredCharge = False # TODO: DM-54880 

129 self.bfKernel = isrMock.BfKernelMockLSST(config=self.mockConfig).run() 

130 

131 # dataId values: 

132 instrument = self.camera.getName() 

133 exposureId = 100 

134 visit = 100101 

135 detector = 0 

136 physical_filter = "testCam_filter" 

137 band = "X" 

138 

139 # Map the isrTask connection names to the names of the Butler dataset 

140 # inputs 

141 ccdExposure = "raw" 

142 camera = "camera" 

143 bias = "bias" 

144 dark = "dark" 

145 flat = "flat" 

146 defects = "defects" 

147 bfKernel = "bfk" 

148 ptc = "ptc" 

149 deferredChargeCalib = "cti" 

150 crosstalk = "crosstalk" 

151 linearizer = "linearizer" 

152 gainCorrection = "gain_correction" 

153 

154 # outputs 

155 outputExposure = "postISRCCD" 

156 outputStatistics = "isrStatistics" 

157 

158 # quickLook-only outputs 

159 exposure = "quickLookExp" 

160 

161 # Create a and populate a test butler for runQuantum tests. 

162 self.repo_path = tempfile.TemporaryDirectory(ignore_cleanup_errors=True) 

163 self.repo = butlerTests.makeTestRepo(self.repo_path.name) 

164 

165 # dataIds for fake data 

166 butlerTests.addDataIdValue(self.repo, "instrument", instrument) 

167 butlerTests.addDataIdValue(self.repo, "physical_filter", physical_filter, band=band) 

168 butlerTests.addDataIdValue(self.repo, "detector", detector) 

169 butlerTests.addDataIdValue(self.repo, "exposure", exposureId, physical_filter=physical_filter) 

170 butlerTests.addDataIdValue(self.repo, "visit", visit) 

171 

172 # inputs 

173 butlerTests.addDatasetType(self.repo, ccdExposure, {"instrument", "exposure", "detector"}, "Exposure") 

174 butlerTests.addDatasetType(self.repo, camera, {"instrument"}, "Camera") 

175 butlerTests.addDatasetType(self.repo, bias, {"instrument", "detector"}, "Exposure") 

176 butlerTests.addDatasetType(self.repo, dark, {"instrument", "detector"}, "Exposure") 

177 butlerTests.addDatasetType(self.repo, flat, {"instrument", "physical_filter", "detector"}, "Exposure") 

178 butlerTests.addDatasetType(self.repo, defects, {"instrument", "detector"}, "Defects") 

179 butlerTests.addDatasetType(self.repo, linearizer, {"instrument", "detector"}, "Linearizer") 

180 butlerTests.addDatasetType(self.repo, crosstalk, {"instrument", "detector"}, "CrosstalkCalib") 

181 butlerTests.addDatasetType(self.repo, bfKernel, {"instrument", "detector"}, "BrighterFatterKernel") 

182 butlerTests.addDatasetType(self.repo, ptc, {"instrument", "detector"}, "PhotonTransferCurveDataset") 

183 butlerTests.addDatasetType(self.repo, deferredChargeCalib, {"instrument", "detector"}, "IsrCalib") 

184 butlerTests.addDatasetType(self.repo, gainCorrection, {"instrument", "detector"}, "IsrCalib") 

185 

186 # outputs 

187 butlerTests.addDatasetType( 

188 self.repo, outputExposure, {"instrument", "exposure", "detector"}, "Exposure" 

189 ) 

190 butlerTests.addDatasetType(self.repo, exposure, {"instrument", "exposure", "detector"}, "Exposure") 

191 butlerTests.addDatasetType( 

192 self.repo, outputStatistics, {"instrument", "exposure", "detector"}, "StructuredDataDict" 

193 ) 

194 

195 # dataIds 

196 self.exposure_id = self.repo.registry.expandDataId( 

197 { 

198 "instrument": instrument, 

199 "exposure": exposureId, 

200 "detector": detector, 

201 "physical_filter": physical_filter, 

202 } 

203 ) 

204 self.instrument_id = self.repo.registry.expandDataId({"instrument": instrument}) 

205 self.flat_id = self.repo.registry.expandDataId( 

206 {"instrument": instrument, "physical_filter": physical_filter, "detector": detector} 

207 ) 

208 self.detector_id = self.repo.registry.expandDataId({"instrument": instrument, "detector": detector}) 

209 self.filter_id = self.repo.registry.expandDataId( 

210 {"instrument": instrument, "physical_filter": physical_filter} 

211 ) 

212 

213 # put empty data 

214 self.butler = butlerTests.makeTestCollection(self.repo) 

215 self.butler.put(self.ccdExposure, ccdExposure, self.exposure_id) 

216 self.butler.put(self.camera, camera, self.instrument_id) 

217 self.butler.put(self.bias, bias, self.detector_id) 

218 self.butler.put(self.dark, dark, self.detector_id) 

219 self.butler.put(self.flat, flat, self.flat_id) 

220 self.butler.put(self.defects, defects, self.detector_id) 

221 self.butler.put(self.bfKernel, bfKernel, self.detector_id) 

222 self.butler.put(self.ptc, ptc, self.detector_id) 

223 self.butler.put(self.cti, deferredChargeCalib, self.detector_id) 

224 self.butler.put(self.crosstalk, crosstalk, self.detector_id) 

225 self.butler.put(lsst.ip.isr.linearize.Linearizer(), linearizer, self.detector_id) 

226 self.butler.put( 

227 lsst.ip.isr.GainCorrection( 

228 ampNames=ampNames, 

229 gainAdjustments=[1.0 for x in ampNames], 

230 ), 

231 gainCorrection, 

232 self.detector_id, 

233 ) 

234 

235 def tearDown(self): 

236 del self.repo_path # this removes the temporary directory 

237 

238 def test_runQuantum(self): 

239 config = ipIsr.IsrTaskConfig() 

240 # Remove some outputs 

241 config.doBinnedExposures = False 

242 config.doSaveInterpPixels = False 

243 config.qa.doThumbnailOss = False 

244 config.qa.doThumbnailFlattened = False 

245 config.doCalculateStatistics = False 

246 

247 # Turn on all optional inputs, except CTI, as that isn't 

248 # defined for LATISS. 

249 config.doDeferredCharge = False 

250 

251 config.usePtcReadNoise = True 

252 config.doCrosstalk = True 

253 config.doBrighterFatter = True 

254 

255 # Override a method in IsrTask that is executed early, to instead raise 

256 # a custom exception called ExitMock that we can catch and ignore. 

257 isrTask = ipIsr.IsrTask 

258 isrTask.ensureExposure = raiseExitMockError 

259 task = QuickLookIsrTask(isrTask=isrTask) 

260 lsst.pipe.base.testUtils.assertValidInitOutput(task) 

261 

262 # Use the names of the connections here, not the Butler dataset name 

263 quantum = lsst.pipe.base.testUtils.makeQuantum( 

264 task, 

265 self.butler, 

266 self.exposure_id, 

267 { 

268 "ccdExposure": self.exposure_id, 

269 "camera": self.instrument_id, 

270 "bias": self.detector_id, 

271 "dark": self.detector_id, 

272 "flat": self.flat_id, 

273 "defects": self.detector_id, 

274 "bfKernel": self.detector_id, 

275 "ptc": self.detector_id, 

276 "deferredChargeCalib": self.detector_id, 

277 "crosstalk": self.detector_id, 

278 "linearizer": self.detector_id, 

279 "fringes": self.flat_id, 

280 "gainCorrection": self.detector_id, 

281 # outputs 

282 "outputExposure": self.exposure_id, 

283 "outputStatistics": self.exposure_id, 

284 "exposure": self.exposure_id, 

285 }, 

286 ) 

287 # Check that the proper kwargs are passed to run(). 

288 with contextlib.suppress(ExitMockError): 

289 lsst.pipe.base.testUtils.runTestQuantum(task, self.butler, quantum, mockRun=False) 

290 

291 

292def raiseExitMockError(*args): 

293 """Raise a custom exception.""" 

294 raise ExitMockError 

295 

296 

297class ExitMockError(Exception): 

298 """A custom exception to catch during a unit test.""" 

299 

300 pass 

301 

302 

303class TestMemory(lsst.utils.tests.MemoryTestCase): 

304 pass 

305 

306 

307def setup_module(module): 

308 lsst.utils.tests.init() 

309 

310 

311if __name__ == "__main__": 311 ↛ 312line 311 didn't jump to line 312 because the condition on line 311 was never true

312 lsst.utils.tests.init() 

313 unittest.main()