Coverage for tests/test_assembleCoadd.py: 37%

179 statements  

« prev     ^ index     » next       coverage.py v6.4.2, created at 2022-07-19 05:40 -0700

1# This file is part of pipe_tasks. 

2# 

3# LSST Data Management System 

4# This product includes software developed by the 

5# LSST Project (http://www.lsst.org/). 

6# See COPYRIGHT file at the top of the source tree. 

7# 

8# This program is free software: you can redistribute it and/or modify 

9# it under the terms of the GNU General Public License as published by 

10# the Free Software Foundation, either version 3 of the License, or 

11# (at your option) any later version. 

12# 

13# This program is distributed in the hope that it will be useful, 

14# but WITHOUT ANY WARRANTY; without even the implied warranty of 

15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

16# GNU General Public License for more details. 

17# 

18# You should have received a copy of the LSST License Statement and 

19# the GNU General Public License along with this program. If not, 

20# see <https://www.lsstcorp.org/LegalNotices/>. 

21# 

22"""Test AssembleCoaddTask and its variants. 

23 

24This uses 

25""" 

26import unittest 

27import numpy as np 

28 

29import lsst.utils.tests 

30 

31from lsst.pipe.tasks.assembleCoadd import (AssembleCoaddTask, AssembleCoaddConfig, 

32 SafeClipAssembleCoaddTask, SafeClipAssembleCoaddConfig, 

33 CompareWarpAssembleCoaddTask, CompareWarpAssembleCoaddConfig) 

34from lsst.pipe.tasks.dcrAssembleCoadd import DcrAssembleCoaddTask, DcrAssembleCoaddConfig 

35from assembleCoaddTestUtils import makeMockSkyInfo, MockCoaddTestData 

36 

37__all__ = ["MockAssembleCoaddConfig", "MockAssembleCoaddTask", 

38 "MockCompareWarpAssembleCoaddConfig", "MockCompareWarpAssembleCoaddTask"] 

39 

40 

41class MockAssembleCoaddConfig(AssembleCoaddConfig): 

42 

43 def setDefaults(self): 

44 super().setDefaults() 

45 self.doWrite = False 

46 

47 

48class MockAssembleCoaddTask(AssembleCoaddTask): 

49 """Lightly modified version of `AssembleCoaddTask` for use with unit tests. 

50 

51 The modifications bypass the usual middleware for loading data and setting 

52 up the Task, and instead supply in-memory mock data references to the `run` 

53 method so that the coaddition algorithms can be tested without a Butler. 

54 """ 

55 ConfigClass = MockAssembleCoaddConfig 

56 

57 def __init__(self, **kwargs): 

58 super().__init__(**kwargs) 

59 self.warpType = self.config.warpType 

60 self.makeSubtask("interpImage") 

61 self.makeSubtask("scaleZeroPoint") 

62 

63 def processResults(self, *args, **kwargs): 

64 "This should be tested separately." 

65 pass 

66 

67 def _dataRef2DebugPath(self, *args, **kwargs): 

68 raise NotImplementedError("This lightweight version of the task is not " 

69 "meant to test debugging options.") 

70 

71 def runQuantum(self, mockSkyInfo, warpRefList, *args): 

72 """Modified interface for testing coaddition algorithms without a Butler. 

73 

74 Parameters 

75 ---------- 

76 mockSkyInfo : `lsst.pipe.base.Struct` 

77 A simple container that supplies a bounding box and WCS in the 

78 same format as the output of 

79 `lsst.pipe.tasks.CoaddBaseTask.getSkyInfo` 

80 warpRefList : `list` of `lsst.pipe.tasks.MockExposureReference` 

81 Data references to the test exposures that will be coadded, 

82 using the Gen 3 API. 

83 

84 Returns 

85 ------- 

86 retStruct : `lsst.pipe.base.Struct` 

87 The coadded exposure and associated metadata. 

88 """ 

89 inputs = self.prepareInputs(warpRefList) 

90 supplementaryData = self.makeSupplementaryData(mockSkyInfo, warpRefList=inputs.tempExpRefList) 

91 retStruct = self.run(mockSkyInfo, inputs.tempExpRefList, inputs.imageScalerList, 

92 inputs.weightList, supplementaryData=supplementaryData) 

93 return retStruct 

94 

95 def runDataRef(self, mockSkyInfo, selectDataList=None, warpRefList=None): 

96 """Modified interface for testing coaddition algorithms without a Butler. 

97 

98 Notes 

99 ----- 

100 This tests the coaddition algorithms using Gen 2 Butler data references, 

101 and can be removed once that is fully deprecated. 

102 

103 Both `runDataRef` and `runQuantum` are needed even those their 

104 implementation here is identical, because the Gen 2 and Gen 3 versions 

105 of `makeSupplementaryData` call `runDataRef` and `runQuantum` to build 

106 initial templates, respectively. 

107 

108 Parameters 

109 ---------- 

110 mockSkyInfo : `lsst.pipe.base.Struct` 

111 A simple container that supplies a bounding box and WCS in the 

112 same format as the output of 

113 `lsst.pipe.tasks.CoaddBaseTask.getSkyInfo` 

114 warpRefList : `list` of `lsst.pipe.tasks.MockGen2ExposureReference` 

115 Data references to the test exposures that will be coadded, 

116 using the Gen 2 API. 

117 

118 Returns 

119 ------- 

120 retStruct : `lsst.pipe.base.Struct` 

121 The coadded exposure and associated metadata. 

122 """ 

123 inputData = self.prepareInputs(warpRefList) 

124 supplementaryData = self.makeSupplementaryData(mockSkyInfo, warpRefList=inputData.tempExpRefList) 

125 retStruct = self.run(mockSkyInfo, inputData.tempExpRefList, inputData.imageScalerList, 

126 inputData.weightList, supplementaryData=supplementaryData) 

127 return retStruct 

128 

129 

130class MockSafeClipAssembleCoaddConfig(SafeClipAssembleCoaddConfig): 

131 

132 def setDefaults(self): 

133 super().setDefaults() 

134 self.doWrite = False 

135 

136 

137class MockSafeClipAssembleCoaddTask(MockAssembleCoaddTask, SafeClipAssembleCoaddTask): 

138 """Lightly modified version of `SafeClipAssembleCoaddTask` 

139 for use with unit tests. 

140 

141 The modifications bypass the usual middleware for loading data and setting 

142 up the Task, and instead supply in-memory mock data references to the `run` 

143 method so that the coaddition algorithms can be tested without a Butler. 

144 """ 

145 ConfigClass = MockSafeClipAssembleCoaddConfig 

146 _DefaultName = "safeClipAssembleCoadd" 

147 

148 def __init__(self, *args, **kwargs): 

149 SafeClipAssembleCoaddTask.__init__(self, *args, **kwargs) 

150 

151 

152class MockCompareWarpAssembleCoaddConfig(CompareWarpAssembleCoaddConfig): 

153 

154 def setDefaults(self): 

155 super().setDefaults() 

156 self.assembleStaticSkyModel.retarget(MockAssembleCoaddTask) 

157 self.assembleStaticSkyModel.doWrite = False 

158 self.doWrite = False 

159 

160 

161class MockCompareWarpAssembleCoaddTask(MockAssembleCoaddTask, CompareWarpAssembleCoaddTask): 

162 """Lightly modified version of `CompareWarpAssembleCoaddTask` 

163 for use with unit tests. 

164 

165 The modifications bypass the usual middleware for loading data and setting 

166 up the Task, and instead supply in-memory mock data references to the `run` 

167 method so that the coaddition algorithms can be tested without a Butler. 

168 """ 

169 ConfigClass = MockCompareWarpAssembleCoaddConfig 

170 _DefaultName = "compareWarpAssembleCoadd" 

171 

172 def __init__(self, *args, **kwargs): 

173 CompareWarpAssembleCoaddTask.__init__(self, *args, **kwargs) 

174 

175 

176class MockDcrAssembleCoaddConfig(DcrAssembleCoaddConfig): 

177 

178 def setDefaults(self): 

179 super().setDefaults() 

180 self.assembleStaticSkyModel.retarget(MockCompareWarpAssembleCoaddTask) 

181 self.assembleStaticSkyModel.doWrite = False 

182 self.doWrite = False 

183 self.effectiveWavelength = 476.31 # Use LSST g band values for the test. 

184 self.bandwidth = 552. - 405. 

185 

186 

187class MockDcrAssembleCoaddTask(MockAssembleCoaddTask, DcrAssembleCoaddTask): 

188 """Lightly modified version of `DcrAssembleCoaddTask` 

189 for use with unit tests. 

190 

191 The modifications bypass the usual middleware for loading data and setting 

192 up the Task, and instead supply in-memory mock data references to the `run` 

193 method so that the coaddition algorithms can be tested without a Butler. 

194 """ 

195 ConfigClass = MockDcrAssembleCoaddConfig 

196 _DefaultName = "dcrAssembleCoadd" 

197 

198 def __init__(self, *args, **kwargs): 

199 DcrAssembleCoaddTask.__init__(self, *args, **kwargs) 

200 

201 

202class MockInputMapAssembleCoaddConfig(MockCompareWarpAssembleCoaddConfig): 

203 

204 def setDefaults(self): 

205 super().setDefaults() 

206 self.doInputMap = True 

207 

208 

209class MockInputMapAssembleCoaddTask(MockCompareWarpAssembleCoaddTask): 

210 """Lightly modified version of `CompareWarpAssembleCoaddTask` 

211 for use with unit tests. 

212 

213 The modifications bypass the usual middleware for loading data and setting 

214 up the Task, and instead supply in-memory mock data references to the `run` 

215 method so that the coaddition algorithms can be tested without a Butler. 

216 """ 

217 ConfigClass = MockInputMapAssembleCoaddConfig 

218 _DefaultName = "inputMapAssembleCoadd" 

219 

220 def __init__(self, *args, **kwargs): 

221 CompareWarpAssembleCoaddTask.__init__(self, *args, **kwargs) 

222 

223 

224class AssembleCoaddTestCase(lsst.utils.tests.TestCase): 

225 """Tests of AssembleCoaddTask and its derived classes. 

226 

227 These tests bypass the middleware used for accessing data and managing Task 

228 execution. 

229 """ 

230 

231 def setUp(self): 

232 patch = 42 

233 patchGen2 = "2,3" 

234 tract = 0 

235 testData = MockCoaddTestData(fluxRange=1e4) 

236 exposures = {} 

237 matchedExposures = {} 

238 for expId in range(100, 110): 

239 exposures[expId], matchedExposures[expId] = testData.makeTestImage(expId) 

240 self.gen2DataRefList = testData.makeGen2DataRefList(exposures, matchedExposures, 

241 patch=patchGen2, tract=tract) 

242 self.dataRefList = testData.makeDataRefList(exposures, matchedExposures, 

243 'direct', patch=patch, tract=tract) 

244 self.dataRefListPsfMatched = testData.makeDataRefList(exposures, matchedExposures, 

245 'psfMatched', patch=patch, tract=tract) 

246 self.skyInfoGen2 = makeMockSkyInfo(testData.bbox, testData.wcs, patch=patchGen2) 

247 self.skyInfo = makeMockSkyInfo(testData.bbox, testData.wcs, patch=patch) 

248 

249 def checkGen2Gen3Compatibility(self, assembleTask, warpType="direct"): 

250 dataRefList = self.dataRefListPsfMatched if warpType == "psfMatched" else self.dataRefList 

251 resultsGen3 = assembleTask.runQuantum(self.skyInfo, dataRefList) 

252 resultsGen2 = assembleTask.runDataRef(self.skyInfoGen2, warpRefList=self.gen2DataRefList) 

253 coaddGen2 = resultsGen2.coaddExposure 

254 coaddGen3 = resultsGen3.coaddExposure 

255 self.assertFloatsEqual(coaddGen2.image.array, coaddGen3.image.array) 

256 

257 def testGen2Gen3Compatibility(self): 

258 config = MockAssembleCoaddConfig() 

259 config.validate() 

260 assembleTask = MockAssembleCoaddTask(config=config) 

261 self.checkGen2Gen3Compatibility(assembleTask) 

262 

263 def testPsfMatchedGen2Gen3Compatibility(self): 

264 config = MockAssembleCoaddConfig(warpType="psfMatched") 

265 config.validate() 

266 assembleTask = MockAssembleCoaddTask(config=config) 

267 self.checkGen2Gen3Compatibility(assembleTask, warpType="psfMatched") 

268 

269 def testSafeClipGen2Gen3Compatibility(self): 

270 config = MockSafeClipAssembleCoaddConfig() 

271 config.validate() 

272 assembleTask = MockSafeClipAssembleCoaddTask(config=config) 

273 self.checkGen2Gen3Compatibility(assembleTask) 

274 

275 def testCompareWarpGen2Gen3Compatibility(self): 

276 config = MockCompareWarpAssembleCoaddConfig() 

277 config.validate() 

278 assembleTask = MockCompareWarpAssembleCoaddTask(config=config) 

279 self.checkGen2Gen3Compatibility(assembleTask) 

280 

281 def testDcrGen2Gen3Compatibility(self): 

282 config = MockDcrAssembleCoaddConfig() 

283 config.validate() 

284 assembleTask = MockDcrAssembleCoaddTask(config=config) 

285 self.checkGen2Gen3Compatibility(assembleTask) 

286 

287 def testOnlineCoaddGen3(self): 

288 config = MockInputMapAssembleCoaddConfig() 

289 config.statistic = "MEAN" 

290 config.validate() 

291 assembleTask = MockInputMapAssembleCoaddTask(config=config) 

292 

293 dataRefList = self.dataRefList 

294 results = assembleTask.runQuantum(self.skyInfo, dataRefList) 

295 coadd = results.coaddExposure 

296 

297 configOnline = MockInputMapAssembleCoaddConfig() 

298 configOnline.statistic = "MEAN" 

299 configOnline.doOnlineForMean = True 

300 configOnline.validate() 

301 assembleTaskOnline = MockInputMapAssembleCoaddTask(config=configOnline) 

302 

303 resultsOnline = assembleTaskOnline.runQuantum(self.skyInfo, dataRefList) 

304 coaddOnline = resultsOnline.coaddExposure 

305 

306 self.assertFloatsAlmostEqual(coaddOnline.image.array, 

307 coadd.image.array, rtol=1e-3) 

308 self.assertFloatsAlmostEqual(coaddOnline.variance.array, 

309 coadd.variance.array, rtol=1e-6) 

310 self.assertMasksEqual(coaddOnline.mask, coadd.mask) 

311 

312 def testInputMapGen3(self): 

313 config = MockInputMapAssembleCoaddConfig() 

314 config.validate() 

315 assembleTask = MockInputMapAssembleCoaddTask(config=config) 

316 

317 # Make exposures where one of them has a bad region. 

318 patch = 42 

319 tract = 0 

320 testData = MockCoaddTestData(fluxRange=1e4) 

321 exposures = {} 

322 matchedExposures = {} 

323 for expId in range(100, 110): 

324 if expId == 105: 

325 badBox = lsst.geom.Box2I(lsst.geom.Point2I(testData.bbox.beginX + 10, 

326 testData.bbox.beginY + 10), 

327 lsst.geom.Extent2I(100, 100)) 

328 else: 

329 badBox = None 

330 exposures[expId], matchedExposures[expId] = testData.makeTestImage(expId, 

331 badRegionBox=badBox) 

332 dataRefList = testData.makeDataRefList(exposures, matchedExposures, 

333 'direct', patch=patch, tract=tract) 

334 

335 results = assembleTask.runQuantum(self.skyInfo, dataRefList) 

336 

337 inputMap = results.inputMap 

338 validPix, raPix, decPix = inputMap.valid_pixels_pos(return_pixels=True) 

339 

340 # Confirm that all the map pixels are in the bounding box 

341 # Exposure 100 is the first one and they all have the same WCS in the tests. 

342 xPix, yPix = exposures[100].getWcs().skyToPixelArray(raPix, decPix, degrees=True) 

343 self.assertGreater(xPix.min(), testData.bbox.beginX) 

344 self.assertGreater(yPix.min(), testData.bbox.beginY) 

345 self.assertLess(xPix.max(), testData.bbox.endX) 

346 self.assertLess(xPix.max(), testData.bbox.endY) 

347 

348 # Confirm that all exposures except 105 are completely covered 

349 # This assumes we have one input per visit in the mock data. 

350 metadata = inputMap.metadata 

351 visitBitDict = {} 

352 for bit in range(inputMap.wide_mask_maxbits): 

353 if f'B{bit:04d}VIS' in metadata: 

354 visitBitDict[metadata[f'B{bit:04d}VIS']] = bit 

355 for expId in range(100, 110): 

356 if expId == 105: 

357 self.assertFalse(np.all(inputMap.check_bits_pix(validPix, [visitBitDict[expId]]))) 

358 else: 

359 self.assertTrue(np.all(inputMap.check_bits_pix(validPix, [visitBitDict[expId]]))) 

360 

361 

362class MyMemoryTestCase(lsst.utils.tests.MemoryTestCase): 

363 pass 

364 

365 

366def setup_module(module): 

367 lsst.utils.tests.init() 

368 

369 

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

371 lsst.utils.tests.init() 

372 unittest.main()