Coverage for tests/test_assemble_coadd.py: 29%

153 statements  

« prev     ^ index     » next       coverage.py v7.4.0, created at 2023-12-29 13:38 +0000

1# This file is part of drp_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""" 

24import unittest 

25 

26import lsst.pipe.base as pipeBase 

27import lsst.utils.tests 

28import numpy as np 

29from assemble_coadd_test_utils import MockCoaddTestData, makeMockSkyInfo 

30from lsst.drp.tasks.assemble_coadd import ( 

31 AssembleCoaddConfig, 

32 AssembleCoaddTask, 

33 CompareWarpAssembleCoaddConfig, 

34 CompareWarpAssembleCoaddTask, 

35) 

36from lsst.drp.tasks.dcr_assemble_coadd import DcrAssembleCoaddConfig, DcrAssembleCoaddTask 

37 

38__all__ = [ 

39 "MockAssembleCoaddConfig", 

40 "MockAssembleCoaddTask", 

41 "MockCompareWarpAssembleCoaddConfig", 

42 "MockCompareWarpAssembleCoaddTask", 

43] 

44 

45 

46class MockAssembleCoaddConfig(AssembleCoaddConfig): 

47 def setDefaults(self): 

48 super().setDefaults() 

49 self.doWrite = False 

50 

51 

52class MockAssembleCoaddTask(AssembleCoaddTask): 

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

54 

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

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

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

58 """ 

59 

60 ConfigClass = MockAssembleCoaddConfig 

61 

62 def __init__(self, **kwargs): 

63 super().__init__(**kwargs) 

64 self.warpType = self.config.warpType 

65 self.makeSubtask("interpImage") 

66 self.makeSubtask("scaleZeroPoint") 

67 

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

69 "This should be tested separately." 

70 pass 

71 

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

73 """Modified interface for testing coaddition algorithms without a 

74 Butler. 

75 

76 Parameters 

77 ---------- 

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

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

80 same format as the output of 

81 `lsst.pipe.tasks.CoaddBaseTask.getSkyInfo` 

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

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

84 using the Gen 3 API. 

85 

86 Returns 

87 ------- 

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

89 The coadded exposure and associated metadata. 

90 """ 

91 inputs = self.prepareInputs(warpRefList) 

92 

93 retStruct = self.run( 

94 mockSkyInfo, 

95 inputs.tempExpRefList, 

96 inputs.imageScalerList, 

97 inputs.weightList, 

98 supplementaryData=pipeBase.Struct(), 

99 ) 

100 return retStruct 

101 

102 

103class MockCompareWarpAssembleCoaddConfig(CompareWarpAssembleCoaddConfig): 

104 def setDefaults(self): 

105 super().setDefaults() 

106 self.assembleStaticSkyModel.retarget(MockAssembleCoaddTask) 

107 self.assembleStaticSkyModel.doWrite = False 

108 self.doWrite = False 

109 

110 

111class MockCompareWarpAssembleCoaddTask(MockAssembleCoaddTask, CompareWarpAssembleCoaddTask): 

112 """Lightly modified version of `CompareWarpAssembleCoaddTask` 

113 for use with unit tests. 

114 

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

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

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

118 """ 

119 

120 ConfigClass = MockCompareWarpAssembleCoaddConfig 

121 _DefaultName = "compareWarpAssembleCoadd" 

122 

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

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

125 

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

127 inputs = self.prepareInputs(warpRefList) 

128 

129 assembleStaticSkyModel = MockAssembleCoaddTask(config=self.config.assembleStaticSkyModel) 

130 templateCoadd = assembleStaticSkyModel.runQuantum(mockSkyInfo, warpRefList) 

131 

132 supplementaryData = pipeBase.Struct( 

133 templateCoadd=templateCoadd.coaddExposure, 

134 nImage=templateCoadd.nImage, 

135 warpRefList=templateCoadd.warpRefList, 

136 imageScalerList=templateCoadd.imageScalerList, 

137 weightList=templateCoadd.weightList, 

138 ) 

139 

140 retStruct = self.run( 

141 mockSkyInfo, 

142 inputs.tempExpRefList, 

143 inputs.imageScalerList, 

144 inputs.weightList, 

145 supplementaryData=supplementaryData, 

146 ) 

147 return retStruct 

148 

149 

150class MockDcrAssembleCoaddConfig(DcrAssembleCoaddConfig): 

151 def setDefaults(self): 

152 super().setDefaults() 

153 self.assembleStaticSkyModel.retarget(MockCompareWarpAssembleCoaddTask) 

154 self.assembleStaticSkyModel.doWrite = False 

155 self.doWrite = False 

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

157 self.bandwidth = 552.0 - 405.0 

158 

159 

160class MockDcrAssembleCoaddTask(MockCompareWarpAssembleCoaddTask, DcrAssembleCoaddTask): 

161 """Lightly modified version of `DcrAssembleCoaddTask` 

162 for use with unit tests. 

163 

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

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

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

167 """ 

168 

169 ConfigClass = MockDcrAssembleCoaddConfig 

170 _DefaultName = "dcrAssembleCoadd" 

171 

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

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

174 

175 

176class MockInputMapAssembleCoaddConfig(MockCompareWarpAssembleCoaddConfig): 

177 def setDefaults(self): 

178 super().setDefaults() 

179 self.doInputMap = True 

180 

181 

182class MockInputMapAssembleCoaddTask(MockCompareWarpAssembleCoaddTask): 

183 """Lightly modified version of `CompareWarpAssembleCoaddTask` 

184 for use with unit tests. 

185 

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

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

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

189 """ 

190 

191 ConfigClass = MockInputMapAssembleCoaddConfig 

192 _DefaultName = "inputMapAssembleCoadd" 

193 

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

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

196 

197 

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

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

200 

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

202 execution. 

203 """ 

204 

205 def setUp(self): 

206 patch = 42 

207 tract = 0 

208 testData = MockCoaddTestData(fluxRange=1e4) 

209 exposures = {} 

210 matchedExposures = {} 

211 for expId in range(100, 110): 

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

213 self.dataRefList = testData.makeDataRefList( 

214 exposures, matchedExposures, "direct", patch=patch, tract=tract 

215 ) 

216 self.dataRefListPsfMatched = testData.makeDataRefList( 

217 exposures, matchedExposures, "psfMatched", patch=patch, tract=tract 

218 ) 

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

220 

221 def checkRun(self, assembleTask, warpType="direct"): 

222 """Check that the task runs successfully.""" 

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

224 result = assembleTask.runQuantum(self.skyInfo, dataRefList) 

225 

226 # Check that we produced an exposure. 

227 self.assertTrue(result.coaddExposure is not None) 

228 

229 def testAssembleBasic(self): 

230 config = MockAssembleCoaddConfig() 

231 assembleTask = MockAssembleCoaddTask(config=config) 

232 self.checkRun(assembleTask) 

233 

234 def testAssemblePsfMatched(self): 

235 config = MockAssembleCoaddConfig(warpType="psfMatched") 

236 assembleTask = MockAssembleCoaddTask(config=config) 

237 self.checkRun(assembleTask, warpType="psfMatched") 

238 

239 def testAssembleCompareWarp(self): 

240 config = MockCompareWarpAssembleCoaddConfig() 

241 assembleTask = MockCompareWarpAssembleCoaddTask(config=config) 

242 self.checkRun(assembleTask) 

243 

244 def testAssembleDCR(self): 

245 config = MockDcrAssembleCoaddConfig() 

246 assembleTask = MockDcrAssembleCoaddTask(config=config) 

247 self.checkRun(assembleTask) 

248 

249 def testOnlineCoadd(self): 

250 config = MockInputMapAssembleCoaddConfig() 

251 config.statistic = "MEAN" 

252 assembleTask = MockInputMapAssembleCoaddTask(config=config) 

253 

254 dataRefList = self.dataRefList 

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

256 coadd = results.coaddExposure 

257 

258 configOnline = MockInputMapAssembleCoaddConfig() 

259 configOnline.statistic = "MEAN" 

260 configOnline.doOnlineForMean = True 

261 configOnline.validate() 

262 assembleTaskOnline = MockInputMapAssembleCoaddTask(config=configOnline) 

263 

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

265 coaddOnline = resultsOnline.coaddExposure 

266 

267 self.assertFloatsAlmostEqual(coaddOnline.image.array, coadd.image.array, rtol=1e-3) 

268 self.assertFloatsAlmostEqual(coaddOnline.variance.array, coadd.variance.array, rtol=1e-6) 

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

270 

271 def testInputMap(self): 

272 config = MockInputMapAssembleCoaddConfig() 

273 assembleTask = MockInputMapAssembleCoaddTask(config=config) 

274 

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

276 patch = 42 

277 tract = 0 

278 testData = MockCoaddTestData(fluxRange=1e4) 

279 exposures = {} 

280 matchedExposures = {} 

281 for expId in range(100, 110): 

282 if expId == 105: 

283 badBox = lsst.geom.Box2I( 

284 lsst.geom.Point2I(testData.bbox.beginX + 10, testData.bbox.beginY + 10), 

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

286 ) 

287 else: 

288 badBox = None 

289 exposures[expId], matchedExposures[expId] = testData.makeTestImage(expId, badRegionBox=badBox) 

290 dataRefList = testData.makeDataRefList( 

291 exposures, matchedExposures, "direct", patch=patch, tract=tract 

292 ) 

293 

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

295 

296 inputMap = results.inputMap 

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

298 

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

300 # Exposure 100 is the first one and they all have the same WCS in the 

301 # tests. 

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

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

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

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

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

307 

308 # Confirm that all exposures except 105 are completely covered 

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

310 metadata = inputMap.metadata 

311 visitBitDict = {} 

312 for bit in range(inputMap.wide_mask_maxbits): 

313 if f"B{bit:04d}VIS" in metadata: 

314 visitBitDict[metadata[f"B{bit:04d}VIS"]] = bit 

315 for expId in range(100, 110): 

316 if expId == 105: 

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

318 else: 

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

320 

321 

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

323 pass 

324 

325 

326def setup_module(module): 

327 lsst.utils.tests.init() 

328 

329 

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

331 lsst.utils.tests.init() 

332 unittest.main()