Coverage for tests/test_coaddInputs.py: 29%

131 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-11-04 11:12 +0000

1# 

2# LSST Data Management System 

3# Copyright 2008-2016 LSST Corporation. 

4# 

5# This product includes software developed by the 

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

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 <http://www.lsstcorp.org/LegalNotices/>. 

21# 

22""" 

23Tests for lsst.afw.table.CoaddInputs 

24 

25Note: generating the version 1 data requires some complicated machinations 

26because CoaddInputRecorderTask had bugs: 

27- Setup pipe_tasks 12.1, or a similar version old enough to have version 1 CoaddInput/ExposureTable 

28- Edit this file as follows: 

29 - Set SaveCoadd = True 

30 - Set self.version to 2 in CoaddInputsTestCase 

31- Edit CoaddInputRecorderTask as follows: 

32 - Apply all fixes to CoaddInputRecorderTask from DM-7976 

33 - Comment out the line that saves VisitInfo (since version 1 doesn't have it) 

34- Edit ups/pipe_tasks.table as follows: 

35 - Do not import obs_base (since it did not exist at that time) 

36- "setup -r . 'j" to setup this version of pipe_tasks 

37- "python tests/testCoaddInputs.py" to run this test. 

38 You will see some errors, but the needed data file will be written: 

39 tests/data/testCoaddInputs_coadd_with_version_1_data.fits 

40- Save the data file to the repository (but do not save any other changes). 

41""" 

42import os.path 

43import unittest 

44 

45import numpy as np 

46 

47import lsst.utils.tests 

48import lsst.pex.exceptions 

49from lsst.daf.base import DateTime 

50import lsst.afw.cameraGeom.testUtils 

51from lsst.afw.coord import Observatory, Weather 

52import lsst.geom 

53import lsst.afw.geom 

54import lsst.afw.image 

55from lsst.afw.detection import GaussianPsf 

56from lsst.afw.math import ChebyshevBoundedField 

57from lsst.pipe.tasks.coaddInputRecorder import CoaddInputRecorderTask 

58 

59SaveCoadd = False # if True then save coadd even if test passes (always saved if a test fails) 

60 

61 

62class MockExposure: 

63 

64 """Factory to make simple mock exposures suitable to put in a coadd 

65 

66 The metadata is set, but not the pixels 

67 """ 

68 

69 def __init__(self, numExp, coaddInputRecorder, version=2): 

70 """Make a MockExposure 

71 

72 @param[in] numExp total number of exposures that will be made 

73 @param[in] coaddInputRecoder an instance of CoaddInputRecorderTask 

74 @param[in] version desired version of CoaddInput/ExposureTable; 

75 1 for no VisitInfo; this will only produce the desired result 

76 if you run the test with code from before VisitInfo, e.g. version 12.1) 

77 2 to include VisitInfo 

78 """ 

79 self.numExp = int(numExp) 

80 self.coaddInputRecorder = coaddInputRecorder 

81 self.version = int(version) 

82 

83 def makeExposure(self, universalId): 

84 """Make a tiny exposure with exposure info set, but no pixels 

85 

86 In particular, exposure info is set as a record in a table, so it can be recorded in a coadd 

87 """ 

88 inputRecorder = self.coaddInputRecorder.makeCoaddTempExpRecorder(universalId, self.numExp) 

89 bbox = lsst.geom.Box2I(lsst.geom.Point2I(100, 100), lsst.geom.Extent2I(10, 10)) 

90 

91 detectorName = "detector {}".format(universalId) 

92 detector = lsst.afw.cameraGeom.testUtils.DetectorWrapper(name=detectorName, id=universalId).detector 

93 

94 exp = lsst.afw.image.ExposureF(bbox) 

95 exp.setDetector(detector) 

96 

97 expInfo = exp.getInfo() 

98 expInfo.id = 10313423 

99 scale = 5.1e-5*lsst.geom.degrees 

100 cdMatrix = lsst.afw.geom.makeCdMatrix(scale=scale) 

101 wcs = lsst.afw.geom.makeSkyWcs( 

102 crpix=lsst.geom.Point2D(5, 5), 

103 crval=lsst.geom.SpherePoint(10, 45, lsst.geom.degrees), 

104 cdMatrix=cdMatrix, 

105 ) 

106 expInfo.setWcs(wcs) 

107 expInfo.setPsf(GaussianPsf(5, 5, 2.5)) 

108 expInfo.setPhotoCalib(lsst.afw.image.makePhotoCalibFromCalibZeroPoint(1.1e12, 2.2e10)) 

109 expInfo.setApCorrMap(self.makeApCorrMap()) 

110 expInfo.setValidPolygon(lsst.afw.geom.Polygon(lsst.geom.Box2D(bbox).getCorners())) 

111 expInfo.setFilter(lsst.afw.image.FilterLabel(physical="fakeFilter", band="fake")) 

112 if self.version > 1: 

113 expInfo.setVisitInfo(self.makeVisitInfo()) 

114 

115 inputRecorder.addCalExp(calExp=exp, ccdId=universalId, nGoodPix=100) 

116 inputRecorder.finish(coaddTempExp=exp, nGoodPix=100) 

117 

118 return exp 

119 

120 @staticmethod 

121 def makeWcs(): 

122 scale = 5.1e-5*lsst.geom.degrees 

123 cdMatrix = lsst.afw.geom.makeCdMatrix(scale=scale) 

124 return lsst.afw.geom.makeSkyWcs( 

125 crpix=lsst.geom.Point2D(5, 5), 

126 crval=lsst.geom.SpherePoint(10, 45, lsst.geom.degrees), 

127 cdMatrix=cdMatrix, 

128 ) 

129 

130 @staticmethod 

131 def makeVisitInfo(): 

132 return lsst.afw.image.VisitInfo( 

133 10.01, 

134 11.02, 

135 DateTime(65321.1, DateTime.MJD, DateTime.TAI), 

136 12345.1, 

137 45.1*lsst.geom.degrees, 

138 lsst.geom.SpherePoint(23.1, 73.2, lsst.geom.degrees), 

139 lsst.geom.SpherePoint(134.5, 33.3, lsst.geom.degrees), 

140 1.73, 

141 73.2*lsst.geom.degrees, 

142 lsst.afw.image.RotType.SKY, 

143 Observatory(11.1*lsst.geom.degrees, 22.2*lsst.geom.degrees, 0.333), 

144 Weather(1.1, 2.2, 34.5), 

145 ) 

146 

147 @staticmethod 

148 def makeApCorrMap(): 

149 """Make a trivial ApCorrMap with three elements""" 

150 bbox = lsst.geom.Box2I(lsst.geom.Point2I(-5, -5), lsst.geom.Point2I(5, 5)) 

151 apCorrMap = lsst.afw.image.ApCorrMap() 

152 for name in ("a", "b", "c"): 

153 apCorrMap.set(name, ChebyshevBoundedField(bbox, np.zeros((3, 3), dtype=float))) 

154 return apCorrMap 

155 

156 

157class MockCoadd: 

158 

159 """Class to make a mock coadd 

160 """ 

161 

162 def __init__(self, numExp, version=2): 

163 """Create a coadd with the specified number of exposures. 

164 

165 @param[in] numExp total number of exposures that will be made 

166 @param[in] version desired version of CoaddInput/ExposureTable; see MockExposure for details 

167 

168 Useful fields include: 

169 - coadd 

170 - exposures a list of exposures that went into the coadd 

171 """ 

172 coaddInputRecorder = CoaddInputRecorderTask(name="coaddInputRecorder") 

173 mockExposure = MockExposure(numExp=numExp, coaddInputRecorder=coaddInputRecorder, version=version) 

174 self.exposures = [mockExposure.makeExposure(i) for i in range(numExp)] 

175 

176 exp0 = self.exposures[0] 

177 self.coadd = lsst.afw.image.ExposureF(exp0.getBBox()) 

178 self.coadd.setWcs(exp0.getWcs()) 

179 

180 coaddInputs = coaddInputRecorder.makeCoaddInputs() 

181 for exp in self.exposures: 

182 coaddInputRecorder.addVisitToCoadd(coaddInputs=coaddInputs, coaddTempExp=exp, weight=1.0/numExp) 

183 self.coadd.getInfo().setCoaddInputs(coaddInputs) 

184 

185 

186class CoaddInputsTestCase(lsst.utils.tests.TestCase): 

187 

188 def setUp(self): 

189 self.version = 2 # desired version of ExposureTable/CoaddInput 

190 self.numExp = 3 

191 mockCoadd = MockCoadd(numExp=self.numExp, version=self.version) 

192 self.coadd = mockCoadd.coadd 

193 self.exposures = mockCoadd.exposures 

194 self.dataDir = os.path.join(os.path.dirname(__file__), "data") 

195 

196 def tearDown(self): 

197 del self.coadd 

198 del self.exposures 

199 

200 def assertPsfsAlmostEqual(self, psf1, psf2): 

201 im1 = psf1.computeImage(psf1.getAveragePosition()) 

202 im2 = psf2.computeImage(psf2.getAveragePosition()) 

203 self.assertImagesAlmostEqual(im1, im2) 

204 

205 def getCoaddPath(self, version): 

206 return os.path.join(self.dataDir, 

207 "testCoaddInputs_coadd_with_version_{}_data.fits".format(version)) 

208 

209 def testPersistence(self): 

210 """Read and write a coadd and check the CoaddInputs""" 

211 coaddPath = self.getCoaddPath(version=self.version) 

212 self.coadd.writeFits(coaddPath) 

213 coadd = lsst.afw.image.ExposureF(coaddPath) 

214 coaddInputs = coadd.getInfo().getCoaddInputs() 

215 self.assertCoaddInputsOk(coaddInputs, version=self.version) 

216 if not SaveCoadd: 

217 os.unlink(coaddPath) 

218 else: 

219 print("SaveCoadd true; saved coadd as: %r" % (coaddPath,)) 

220 

221 def testReadV1Coadd(self): 

222 """Read a coadd that contains version 1 CoaddInputs 

223 

224 The test code in question has FK5 WCS 

225 """ 

226 coaddPath = self.getCoaddPath(version=1) 

227 print("coaddPath=", coaddPath) 

228 coadd = lsst.afw.image.ExposureF(coaddPath) 

229 coaddWcs = coadd.getWcs() 

230 # the exposure in question uses FK5 for its WCS so update the exposures 

231 for exposure in self.exposures: 

232 exposure.setWcs(coaddWcs) 

233 coaddInputs = coadd.getInfo().getCoaddInputs() 

234 self.assertCoaddInputsOk(coaddInputs, version=1) 

235 

236 def assertCoaddInputsOk(self, coaddInputs, version): 

237 self.assertIsNotNone(coaddInputs) 

238 for expTable in (coaddInputs.ccds, coaddInputs.visits): 

239 self.assertEqual(len(expTable), 3) 

240 for i, expRec in enumerate(expTable): 

241 exp = self.exposures[i] 

242 expInfo = exp.getInfo() 

243 self.assertEqual(expRec.getId(), i) 

244 self.assertEqual(expRec.getBBox(), exp.getBBox()) 

245 self.assertWcsAlmostEqualOverBBox(expRec.getWcs(), expInfo.getWcs(), expRec.getBBox()) 

246 self.assertPsfsAlmostEqual(expRec.getPsf(), exp.getPsf()) 

247 self.assertEqual(expRec.getPhotoCalib(), expInfo.getPhotoCalib()) 

248 self.assertEqual(len(expRec.getApCorrMap()), 3) 

249 self.assertEqual(set(expRec.getApCorrMap().keys()), set(expInfo.getApCorrMap().keys())) 

250 self.assertFloatsAlmostEqual(np.array(expRec.getValidPolygon().getVertices()), 

251 np.array(expInfo.getValidPolygon().getVertices())) 

252 if version > 1: 

253 self.assertEqual(expRec.getVisitInfo(), expInfo.getVisitInfo()) 

254 else: 

255 self.assertIsNone(expRec.getVisitInfo()) 

256 

257 

258class MemoryTester(lsst.utils.tests.MemoryTestCase): 

259 pass 

260 

261 

262def setup_module(module): 

263 lsst.utils.tests.init() 

264 

265 

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

267 lsst.utils.tests.init() 

268 unittest.main()