Coverage for tests/test_processCcd.py: 16%

Shortcuts on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

159 statements  

1# 

2# LSST Data Management System 

3# Copyright 2008-2016 AURA/LSST. 

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

21# 

22"""Test ProcessCcdTask and its immediate subtasks. 

23 

24Run the task on one obs_test image and perform various checks on the results 

25""" 

26import os 

27import shutil 

28import tempfile 

29import unittest 

30 

31import numpy as np 

32 

33import lsst.utils 

34import lsst.geom as geom 

35import lsst.utils.tests 

36from lsst.ip.isr import IsrTask # we assume obs_test uses base IsrTask here; may change in future. 

37from lsst.pipe.tasks.characterizeImage import CharacterizeImageTask 

38from lsst.pipe.tasks.calibrate import CalibrateTask 

39from lsst.pipe.tasks.processCcd import ProcessCcdTask 

40 

41obsTestDir = lsst.utils.getPackageDir('obs_test') 

42InputDir = os.path.join(obsTestDir, "data", "input") 

43 

44OutputName = None # specify a name (as a string) to save the output repository 

45 

46 

47def getObsTestConfig(TaskClass): 

48 """Helper function to get a command-line task config customized by obs_test. 

49 

50 This duplicates the config override code in pipe_base's ArgumentParser, but 

51 essentially in order to test it. 

52 """ 

53 config = TaskClass.ConfigClass() 

54 filename = os.path.join(obsTestDir, "config", TaskClass._DefaultName + ".py") 

55 if os.path.exists(filename): 

56 config.load(filename) 

57 return config 

58 

59 

60class ProcessCcdTestCase(lsst.utils.tests.TestCase): 

61 

62 def testProcessCcd(self): 

63 """test ProcessCcdTask via parseAndRun (simulating the command line) 

64 

65 This is intended as a sanity check of the task, not a detailed test of its sub-tasks. As such 

66 comparisons are intentionally loose, so as to allow evolution of the sub-tasks over time 

67 without breaking this test. 

68 """ 

69 outPath = tempfile.mkdtemp() if OutputName is None else "{}-ProcessCcd".format(OutputName) 

70 try: 

71 dataId = dict(visit=1) 

72 dataIdStrList = ["%s=%s" % (key, val) for key, val in dataId.items()] 

73 fullResult = ProcessCcdTask.parseAndRun( 

74 args=[InputDir, "--output", outPath, "--clobber-config", "--doraise", 

75 "-c", "charImage.doWriteExposure=True", "--id"] + dataIdStrList, 

76 doReturnResults=True 

77 ) 

78 butler = fullResult.parsedCmd.butler 

79 self.assertEqual(len(fullResult.resultList), 1) 

80 runResult = fullResult.resultList[0] 

81 fullDataId = runResult.dataRef.dataId 

82 self.assertEqual(len(fullDataId), 2) 

83 self.assertEqual(fullDataId["visit"], dataId["visit"]) 

84 self.assertEqual(fullDataId["filter"], "g") 

85 result = runResult.result 

86 

87 icExpBackground = butler.get("icExpBackground", dataId, immediate=True) 

88 bg0Arr = icExpBackground.getImage().getArray() 

89 bgMean = bg0Arr.mean(dtype=np.float64) 

90 bgStdDev = bg0Arr.std(dtype=np.float64) 

91 

92 icSrc = butler.get("icSrc", dataId) 

93 src = butler.get("src", dataId) 

94 

95 # the following makes pyflakes linter happy and the code more robust 

96 oldImMean = None 

97 oldImStdDev = None 

98 oldVarMean = None 

99 oldVarStdDev = None 

100 oldPsfIxx = None 

101 oldPsfIyy = None 

102 oldPsfIxy = None 

103 

104 for i, exposure in enumerate((butler.get("calexp", dataId), result.exposure)): 

105 self.assertEqual(exposure.getBBox(), 

106 geom.Box2I(geom.Point2I(0, 0), geom.Extent2I(1018, 2000))) 

107 maskedImage = exposure.getMaskedImage() 

108 maskArr = maskedImage.getMask().getArray() 

109 numGoodPix = np.sum(maskArr == 0) 

110 

111 imageArr = maskedImage.getImage().getArray() 

112 imMean = imageArr.mean(dtype=np.float64) 

113 imStdDev = imageArr.std(dtype=np.float64) 

114 varArr = maskedImage.getVariance().getArray() 

115 varMean = varArr.mean(dtype=np.float64) 

116 varStdDev = varArr.std(dtype=np.float64) 

117 

118 summary = exposure.getInfo().getSummaryStats() 

119 

120 psf = exposure.getPsf() 

121 psfAvgPos = psf.getAveragePosition() 

122 psfShape = psf.computeShape(psfAvgPos) 

123 psfIxx = psfShape.getIxx() 

124 psfIyy = psfShape.getIyy() 

125 psfIxy = psfShape.getIxy() 

126 

127 if i == 0: 

128 print("\nMeasured results:") 

129 print("background mean = %r, stdDev = %r" % (bgMean, bgStdDev)) 

130 print("len(icSrc) :", len(icSrc)) 

131 print("len(src) :", len(src)) 

132 

133 print("numGoodPix =", numGoodPix) 

134 print("image mean = %r, stdDev = %r" % (imMean, imStdDev)) 

135 print("variance mean = %r, stdDev = %r" % (varMean, varStdDev)) 

136 print("psf Ixx = %r, Iyy = %r, Ixy = %r" % (psfIxx, psfIyy, psfIxy)) 

137 

138 self.assertEqual(len(icSrc), 28) 

139 self.assertEqual(len(src), 184 + 100) # 184 real sources plus 100 sky sources 

140 

141 # NOTE: These values are purely empirical, and need to be 

142 # updated to reflect major algorithmic changes. 

143 # If this test fails after an algorithmic change due to 

144 # small numeric changes here, check on slack at 

145 # #dm-science-pipelines as to whether the changes are 

146 # reasonable, and then replace the failing values by 

147 # running the test to determine the updated values. 

148 expectedPlaces = 7 # Tolerance for numerical comparisons 

149 for name, var, val in [ 

150 ("bgMean", bgMean, 191.48623786891795), 

151 ("bgStdDev", bgStdDev, 0.23994185672586282), 

152 ("numGoodPix", numGoodPix, 1966606), 

153 ("imMean", imMean, 1.1242456954648634), 

154 ("imStdDev", imStdDev, 85.8129750182329), 

155 ("varMean", varMean, 131.24003624152013), 

156 ("varStdDev", varStdDev, 55.98012493452948), 

157 ("psfIxx", psfIxx, 2.843329671276296), 

158 ("psfIyy", psfIyy, 2.2249941554078156), 

159 ("psfIxy", psfIxy, 0.16073332780683286), 

160 ("summary_psfSigma", summary.psfSigma, 1.581520120798809), 

161 ("summary_psfIxx", summary.psfIxx, 2.8524883317493583), 

162 ("summary_psfIyy", summary.psfIyy, 2.2028393759764615), 

163 ("summary_psfIxy", summary.psfIxy, 0.16595993509518148), 

164 ("summary_psfArea", summary.psfArea, 38.63468352371086), 

165 ("summary_ra", summary.ra, 78.85551507080474), 

166 ("summary_decl", summary.decl, -9.800258687592303), 

167 ("summary_zenithDistance", float('%.6f' % (summary.zenithDistance)), 42.361001), 

168 ("summary_zeroPoint", summary.zeroPoint, 30.940228147639207), 

169 ("summary_skyBg", summary.skyBg, 191.37726892903447), 

170 ("summary_skyNoise", summary.skyNoise, 12.512272992606531), 

171 ("summary_meanVar", summary.meanVar, 130.61335199119068) 

172 ]: 

173 self.assertAlmostEqual(var, val, places=expectedPlaces, msg=name) 

174 

175 else: 

176 self.assertEqual(imMean, oldImMean) 

177 self.assertEqual(imStdDev, oldImStdDev) 

178 self.assertEqual(varMean, oldVarMean) 

179 self.assertEqual(varStdDev, oldVarStdDev) 

180 self.assertEqual(psfIxx, oldPsfIxx) 

181 self.assertEqual(psfIyy, oldPsfIyy) 

182 self.assertEqual(psfIxy, oldPsfIxy) 

183 

184 oldImMean = imMean 

185 oldImStdDev = imStdDev 

186 oldVarMean = varMean 

187 oldVarStdDev = varStdDev 

188 oldPsfIxx = psfIxx 

189 oldPsfIyy = psfIyy 

190 oldPsfIxy = psfIxy 

191 

192 finally: 

193 if OutputName is None: 

194 shutil.rmtree(outPath) 

195 else: 

196 print("testProcessCcd.py's output data saved to %r" % (OutputName,)) 

197 

198 def assertCatalogsEqual(self, catalog1, catalog2, skipCols=()): 

199 """Compare two Catalogs for equality. 

200 

201 This should only be used in contexts where it's unlikely that the catalogs will be subtly different; 

202 instead of comparing all values we simply do a spot check of a few cells. 

203 

204 This does not require that either catalog be contiguous (which is why we can't use column access). 

205 """ 

206 self.assertEqual(catalog1.schema, catalog2.schema) 

207 self.assertEqual(len(catalog1), len(catalog2)) 

208 d = catalog1.schema.extract("*") 

209 

210 def fixNaN(x): 

211 if x != x: 

212 return "NaN" 

213 

214 for record1, record2 in zip(catalog1, catalog2): 

215 for name, item in d.items(): 

216 if name not in skipCols: 

217 self.assertEqual( 

218 fixNaN(record1.get(item.key)), fixNaN(record2.get(item.key)), 

219 "{} != {} in field {}".format(record1.get(item.key), record2.get(item.key), name) 

220 ) 

221 

222 def assertBackgroundListsEqual(self, bkg1, bkg2): 

223 """Compare two BackgroundLists for equality. 

224 

225 This should only be used in contexts where it's unlikely that the catalogs will be subtly different; 

226 instead of comparing all values we simply do a spot check of a few cells. 

227 

228 This does not require that either catalog be contiguous (which is why we can't use column access). 

229 """ 

230 im1 = bkg1.getImage() 

231 im2 = bkg2.getImage() 

232 self.assertEqual(im1.getBBox(), im2.getBBox()) 

233 self.assertImagesEqual(im1, im2) 

234 

235 def testComponents(self): 

236 """Test that we can run the first-level subtasks of ProcessCcdTasks. 

237 

238 This tests that we can run these subtasks from the command-line independently (they're all 

239 CmdLineTasks) as well as directly from Python (without giving them access to a Butler). 

240 

241 Aside from verifying that no exceptions are raised, we simply tests that most persisted results are 

242 present and equivalent to both in-memory results. 

243 """ 

244 outPath = tempfile.mkdtemp() if OutputName is None else "{}-Components".format(OutputName) 

245 # We'll use an input butler to get data for the tasks we call from Python, but we won't ever give it 

246 # to those tasks. 

247 inputButler = lsst.daf.persistence.Butler(InputDir) 

248 # Construct task instances we can use directly from Python 

249 isrTask = IsrTask( 

250 config=getObsTestConfig(IsrTask), 

251 name="isr2" 

252 ) 

253 # If we ever enable astrometry and photocal in obs_test, we'll need to pass a refObjLoader to these 

254 # tasks. To maintain the spirit of these tests, we'd ideally have a LoadReferenceObjectsTask class 

255 # that doesn't require a Butler. If we don't, we should construct a butler-based on outside these 

256 # task constructors and pass the LoadReferenceObjectsTask instance to the task constructors. 

257 charImageTask = CharacterizeImageTask( 

258 config=getObsTestConfig(CharacterizeImageTask), 

259 name="charImage2" 

260 ) 

261 calibrateTask = CalibrateTask( 

262 config=getObsTestConfig(CalibrateTask), 

263 name="calibrate2", 

264 icSourceSchema=charImageTask.schema 

265 ) 

266 try: 

267 dataId = dict(visit=1) 

268 dataIdStrList = ["%s=%s" % (key, val) for key, val in dataId.items()] 

269 

270 isrResult1 = IsrTask.parseAndRun( 

271 args=[InputDir, "--output", outPath, "--clobber-config", "--doraise", "--id"] + dataIdStrList, 

272 doReturnResults=True, 

273 ) 

274 # We'll just use the butler to get the original image and calibration frames; it's not clear 

275 # extending the test coverage to include that is worth it. 

276 dataRef = inputButler.dataRef("raw", dataId=dataId) 

277 rawExposure = dataRef.get("raw", immediate=True) 

278 camera = dataRef.get("camera") 

279 isrData = isrTask.readIsrData(dataRef, rawExposure) 

280 exposureIdInfo = inputButler.get("expIdInfo", dataId=dataId) 

281 isrResult2 = isrTask.run( 

282 rawExposure, 

283 bias=isrData.bias, 

284 linearizer=isrData.linearizer, 

285 flat=isrData.flat, 

286 defects=isrData.defects, 

287 fringes=isrData.fringes, 

288 bfKernel=isrData.bfKernel, 

289 camera=camera, 

290 ) 

291 self.assertMaskedImagesEqual( 

292 isrResult1.parsedCmd.butler.get("postISRCCD", dataId, immediate=True).getMaskedImage(), 

293 isrResult1.resultList[0].result.exposure.getMaskedImage() 

294 ) 

295 self.assertMaskedImagesEqual( 

296 isrResult2.exposure.getMaskedImage(), 

297 isrResult1.resultList[0].result.exposure.getMaskedImage() 

298 ) 

299 

300 icResult1 = CharacterizeImageTask.parseAndRun( 

301 args=[InputDir, "--output", outPath, "--clobber-config", "--doraise", "--id"] + dataIdStrList, 

302 doReturnResults=True, 

303 ) 

304 icResult2 = charImageTask.run(isrResult2.exposure, exposureIdInfo=exposureIdInfo) 

305 self.assertMaskedImagesEqual( 

306 icResult1.parsedCmd.butler.get("icExp", dataId, immediate=True).getMaskedImage(), 

307 icResult1.resultList[0].result.exposure.getMaskedImage() 

308 ) 

309 self.assertMaskedImagesEqual( 

310 icResult2.exposure.getMaskedImage(), 

311 icResult1.resultList[0].result.exposure.getMaskedImage() 

312 ) 

313 self.assertCatalogsEqual( 

314 icResult1.parsedCmd.butler.get("icSrc", dataId, immediate=True), 

315 icResult1.resultList[0].result.sourceCat 

316 ) 

317 self.assertCatalogsEqual( 

318 icResult2.sourceCat, 

319 icResult1.resultList[0].result.sourceCat, 

320 ) 

321 self.assertBackgroundListsEqual( 

322 icResult1.parsedCmd.butler.get("icExpBackground", dataId, immediate=True), 

323 icResult1.resultList[0].result.background 

324 ) 

325 self.assertBackgroundListsEqual( 

326 icResult2.background, 

327 icResult1.resultList[0].result.background 

328 ) 

329 

330 calResult1 = CalibrateTask.parseAndRun( 

331 args=[InputDir, "--output", outPath, "--clobber-config", "--doraise", "--id"] + dataIdStrList, 

332 doReturnResults=True, 

333 ) 

334 calResult2 = calibrateTask.run( 

335 icResult2.exposure, 

336 background=icResult2.background, 

337 icSourceCat=icResult2.sourceCat, 

338 exposureIdInfo=exposureIdInfo, 

339 ) 

340 self.assertMaskedImagesEqual( 

341 calResult1.parsedCmd.butler.get("calexp", dataId, immediate=True).getMaskedImage(), 

342 calResult1.resultList[0].result.exposure.getMaskedImage() 

343 ) 

344 self.assertMaskedImagesEqual( 

345 calResult2.exposure.getMaskedImage(), 

346 calResult1.resultList[0].result.exposure.getMaskedImage() 

347 ) 

348 self.assertCatalogsEqual( 

349 calResult1.parsedCmd.butler.get("src", dataId, immediate=True), 

350 calResult1.resultList[0].result.sourceCat 

351 ) 

352 self.assertCatalogsEqual( 

353 calResult2.sourceCat, 

354 calResult1.resultList[0].result.sourceCat, 

355 skipCols=("id", "parent") 

356 ) 

357 self.assertBackgroundListsEqual( 

358 calResult1.parsedCmd.butler.get("calexpBackground", dataId, immediate=True), 

359 calResult1.resultList[0].result.background 

360 ) 

361 self.assertBackgroundListsEqual( 

362 calResult2.background, 

363 calResult1.resultList[0].result.background 

364 ) 

365 

366 finally: 

367 if OutputName is None: 

368 shutil.rmtree(outPath) 

369 else: 

370 print("testProcessCcd.py's output data saved to %r" % (OutputName,)) 

371 

372 

373def setup_module(module): 

374 lsst.utils.tests.init() 

375 

376 

377class MemoryTestCase(lsst.utils.tests.MemoryTestCase): 

378 pass 

379 

380 

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

382 lsst.utils.tests.init() 

383 unittest.main()