Coverage for tests/test_assemble_coadd.py: 29%

153 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-10-27 12:15 +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 

25import numpy as np 

26 

27import lsst.utils.tests 

28 

29import lsst.pipe.base as pipeBase 

30from lsst.drp.tasks.assemble_coadd import (AssembleCoaddTask, AssembleCoaddConfig, 

31 CompareWarpAssembleCoaddTask, CompareWarpAssembleCoaddConfig, 

32 ) 

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

34from assemble_coadd_test_utils import makeMockSkyInfo, MockCoaddTestData 

35 

36__all__ = ["MockAssembleCoaddConfig", "MockAssembleCoaddTask", 

37 "MockCompareWarpAssembleCoaddConfig", "MockCompareWarpAssembleCoaddTask"] 

38 

39 

40class MockAssembleCoaddConfig(AssembleCoaddConfig): 

41 

42 def setDefaults(self): 

43 super().setDefaults() 

44 self.doWrite = False 

45 

46 

47class MockAssembleCoaddTask(AssembleCoaddTask): 

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

49 

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

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

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

53 """ 

54 ConfigClass = MockAssembleCoaddConfig 

55 

56 def __init__(self, **kwargs): 

57 super().__init__(**kwargs) 

58 self.warpType = self.config.warpType 

59 self.makeSubtask("interpImage") 

60 self.makeSubtask("scaleZeroPoint") 

61 

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

63 "This should be tested separately." 

64 pass 

65 

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

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

68 Butler. 

69 

70 Parameters 

71 ---------- 

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

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

74 same format as the output of 

75 `lsst.pipe.tasks.CoaddBaseTask.getSkyInfo` 

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

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

78 using the Gen 3 API. 

79 

80 Returns 

81 ------- 

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

83 The coadded exposure and associated metadata. 

84 """ 

85 inputs = self.prepareInputs(warpRefList) 

86 

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

88 inputs.weightList, supplementaryData=pipeBase.Struct()) 

89 return retStruct 

90 

91 

92class MockCompareWarpAssembleCoaddConfig(CompareWarpAssembleCoaddConfig): 

93 

94 def setDefaults(self): 

95 super().setDefaults() 

96 self.assembleStaticSkyModel.retarget(MockAssembleCoaddTask) 

97 self.assembleStaticSkyModel.doWrite = False 

98 self.doWrite = False 

99 

100 

101class MockCompareWarpAssembleCoaddTask(MockAssembleCoaddTask, CompareWarpAssembleCoaddTask): 

102 """Lightly modified version of `CompareWarpAssembleCoaddTask` 

103 for use with unit tests. 

104 

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

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

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

108 """ 

109 ConfigClass = MockCompareWarpAssembleCoaddConfig 

110 _DefaultName = "compareWarpAssembleCoadd" 

111 

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

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

114 

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

116 inputs = self.prepareInputs(warpRefList) 

117 

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

119 templateCoadd = assembleStaticSkyModel.runQuantum(mockSkyInfo, warpRefList) 

120 

121 supplementaryData = pipeBase.Struct( 

122 templateCoadd=templateCoadd.coaddExposure, 

123 nImage=templateCoadd.nImage, 

124 warpRefList=templateCoadd.warpRefList, 

125 imageScalerList=templateCoadd.imageScalerList, 

126 weightList=templateCoadd.weightList) 

127 

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

129 inputs.weightList, supplementaryData=supplementaryData) 

130 return retStruct 

131 

132 

133class MockDcrAssembleCoaddConfig(DcrAssembleCoaddConfig): 

134 

135 def setDefaults(self): 

136 super().setDefaults() 

137 self.assembleStaticSkyModel.retarget(MockCompareWarpAssembleCoaddTask) 

138 self.assembleStaticSkyModel.doWrite = False 

139 self.doWrite = False 

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

141 self.bandwidth = 552. - 405. 

142 

143 

144class MockDcrAssembleCoaddTask(MockCompareWarpAssembleCoaddTask, DcrAssembleCoaddTask): 

145 """Lightly modified version of `DcrAssembleCoaddTask` 

146 for use with unit tests. 

147 

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

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

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

151 """ 

152 ConfigClass = MockDcrAssembleCoaddConfig 

153 _DefaultName = "dcrAssembleCoadd" 

154 

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

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

157 

158 

159class MockInputMapAssembleCoaddConfig(MockCompareWarpAssembleCoaddConfig): 

160 

161 def setDefaults(self): 

162 super().setDefaults() 

163 self.doInputMap = True 

164 

165 

166class MockInputMapAssembleCoaddTask(MockCompareWarpAssembleCoaddTask): 

167 """Lightly modified version of `CompareWarpAssembleCoaddTask` 

168 for use with unit tests. 

169 

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

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

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

173 """ 

174 ConfigClass = MockInputMapAssembleCoaddConfig 

175 _DefaultName = "inputMapAssembleCoadd" 

176 

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

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

179 

180 

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

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

183 

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

185 execution. 

186 """ 

187 

188 def setUp(self): 

189 patch = 42 

190 tract = 0 

191 testData = MockCoaddTestData(fluxRange=1e4) 

192 exposures = {} 

193 matchedExposures = {} 

194 for expId in range(100, 110): 

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

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

197 'direct', patch=patch, tract=tract) 

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

199 'psfMatched', patch=patch, tract=tract) 

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

201 

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

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

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

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

206 

207 # Check that we produced an exposure. 

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

209 

210 def testAssembleBasic(self): 

211 config = MockAssembleCoaddConfig() 

212 assembleTask = MockAssembleCoaddTask(config=config) 

213 self.checkRun(assembleTask) 

214 

215 def testAssemblePsfMatched(self): 

216 config = MockAssembleCoaddConfig(warpType="psfMatched") 

217 assembleTask = MockAssembleCoaddTask(config=config) 

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

219 

220 def testAssembleCompareWarp(self): 

221 config = MockCompareWarpAssembleCoaddConfig() 

222 assembleTask = MockCompareWarpAssembleCoaddTask(config=config) 

223 self.checkRun(assembleTask) 

224 

225 def testAssembleDCR(self): 

226 config = MockDcrAssembleCoaddConfig() 

227 assembleTask = MockDcrAssembleCoaddTask(config=config) 

228 self.checkRun(assembleTask) 

229 

230 def testOnlineCoadd(self): 

231 config = MockInputMapAssembleCoaddConfig() 

232 config.statistic = "MEAN" 

233 assembleTask = MockInputMapAssembleCoaddTask(config=config) 

234 

235 dataRefList = self.dataRefList 

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

237 coadd = results.coaddExposure 

238 

239 configOnline = MockInputMapAssembleCoaddConfig() 

240 configOnline.statistic = "MEAN" 

241 configOnline.doOnlineForMean = True 

242 configOnline.validate() 

243 assembleTaskOnline = MockInputMapAssembleCoaddTask(config=configOnline) 

244 

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

246 coaddOnline = resultsOnline.coaddExposure 

247 

248 self.assertFloatsAlmostEqual(coaddOnline.image.array, 

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

250 self.assertFloatsAlmostEqual(coaddOnline.variance.array, 

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

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

253 

254 def testInputMap(self): 

255 config = MockInputMapAssembleCoaddConfig() 

256 assembleTask = MockInputMapAssembleCoaddTask(config=config) 

257 

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

259 patch = 42 

260 tract = 0 

261 testData = MockCoaddTestData(fluxRange=1e4) 

262 exposures = {} 

263 matchedExposures = {} 

264 for expId in range(100, 110): 

265 if expId == 105: 

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

267 testData.bbox.beginY + 10), 

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

269 else: 

270 badBox = None 

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

272 badRegionBox=badBox) 

273 dataRefList = testData.makeDataRefList(exposures, matchedExposures, 

274 'direct', patch=patch, tract=tract) 

275 

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

277 

278 inputMap = results.inputMap 

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

280 

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

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

283 # tests. 

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

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

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

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

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

289 

290 # Confirm that all exposures except 105 are completely covered 

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

292 metadata = inputMap.metadata 

293 visitBitDict = {} 

294 for bit in range(inputMap.wide_mask_maxbits): 

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

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

297 for expId in range(100, 110): 

298 if expId == 105: 

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

300 else: 

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

302 

303 

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

305 pass 

306 

307 

308def setup_module(module): 

309 lsst.utils.tests.init() 

310 

311 

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

313 lsst.utils.tests.init() 

314 unittest.main()