Hide keyboard shortcuts

Hot-keys 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

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 psfShape = exposure.getPsf().computeShape() 

121 psfIxx = psfShape.getIxx() 

122 psfIyy = psfShape.getIyy() 

123 psfIxy = psfShape.getIxy() 

124 

125 if i == 0: 

126 print("\nMeasured results:") 

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

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

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

130 

131 print("numGoodPix =", numGoodPix) 

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

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

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

135 

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

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

138 

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

140 # updated to reflect major algorithmic changes. 

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

142 # small numeric changes here, check on slack at 

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

144 # reasonable, and then replace the failing values by 

145 # running the test to determine the updated values. 

146 expectedPlaces = 7 # Tolerance for numerical comparisons 

147 for name, var, val in [ 

148 ("bgMean", bgMean, 191.48623786891795), 

149 ("bgStdDev", bgStdDev, 0.23994185672586282), 

150 ("numGoodPix", numGoodPix, 1966606), 

151 ("imMean", imMean, 1.1242456954648634), 

152 ("imStdDev", imStdDev, 85.8129750182329), 

153 ("varMean", varMean, 131.24003624152013), 

154 ("varStdDev", varStdDev, 55.98012493452948), 

155 ("psfIxx", psfIxx, 2.843329671276296), 

156 ("psfIyy", psfIyy, 2.2249941554078156), 

157 ("psfIxy", psfIxy, 0.16073332780683286), 

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

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

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

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

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

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

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

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

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

167 ("summary_skyBg", summary.skyBg, 191.4352211728692), 

168 ("summary_skyNoise", summary.skyNoise, 12.512330367008024), 

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

170 ]: 

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

172 

173 else: 

174 self.assertEqual(imMean, oldImMean) 

175 self.assertEqual(imStdDev, oldImStdDev) 

176 self.assertEqual(varMean, oldVarMean) 

177 self.assertEqual(varStdDev, oldVarStdDev) 

178 self.assertEqual(psfIxx, oldPsfIxx) 

179 self.assertEqual(psfIyy, oldPsfIyy) 

180 self.assertEqual(psfIxy, oldPsfIxy) 

181 

182 oldImMean = imMean 

183 oldImStdDev = imStdDev 

184 oldVarMean = varMean 

185 oldVarStdDev = varStdDev 

186 oldPsfIxx = psfIxx 

187 oldPsfIyy = psfIyy 

188 oldPsfIxy = psfIxy 

189 

190 finally: 

191 if OutputName is None: 

192 shutil.rmtree(outPath) 

193 else: 

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

195 

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

197 """Compare two Catalogs for equality. 

198 

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

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

201 

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

203 """ 

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

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

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

207 

208 def fixNaN(x): 

209 if x != x: 

210 return "NaN" 

211 

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

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

214 if name not in skipCols: 

215 self.assertEqual( 

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

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

218 ) 

219 

220 def assertBackgroundListsEqual(self, bkg1, bkg2): 

221 """Compare two BackgroundLists for equality. 

222 

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

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

225 

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

227 """ 

228 im1 = bkg1.getImage() 

229 im2 = bkg2.getImage() 

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

231 self.assertImagesEqual(im1, im2) 

232 

233 def testComponents(self): 

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

235 

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

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

238 

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

240 present and equivalent to both in-memory results. 

241 """ 

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

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

244 # to those tasks. 

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

246 # Construct task instances we can use directly from Python 

247 isrTask = IsrTask( 

248 config=getObsTestConfig(IsrTask), 

249 name="isr2" 

250 ) 

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

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

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

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

255 charImageTask = CharacterizeImageTask( 

256 config=getObsTestConfig(CharacterizeImageTask), 

257 name="charImage2" 

258 ) 

259 calibrateTask = CalibrateTask( 

260 config=getObsTestConfig(CalibrateTask), 

261 name="calibrate2", 

262 icSourceSchema=charImageTask.schema 

263 ) 

264 try: 

265 dataId = dict(visit=1) 

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

267 

268 isrResult1 = IsrTask.parseAndRun( 

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

270 doReturnResults=True, 

271 ) 

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

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

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

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

276 camera = dataRef.get("camera") 

277 isrData = isrTask.readIsrData(dataRef, rawExposure) 

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

279 isrResult2 = isrTask.run( 

280 rawExposure, 

281 bias=isrData.bias, 

282 linearizer=isrData.linearizer, 

283 flat=isrData.flat, 

284 defects=isrData.defects, 

285 fringes=isrData.fringes, 

286 bfKernel=isrData.bfKernel, 

287 camera=camera, 

288 ) 

289 self.assertMaskedImagesEqual( 

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

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

292 ) 

293 self.assertMaskedImagesEqual( 

294 isrResult2.exposure.getMaskedImage(), 

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

296 ) 

297 

298 icResult1 = CharacterizeImageTask.parseAndRun( 

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

300 doReturnResults=True, 

301 ) 

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

303 self.assertMaskedImagesEqual( 

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

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

306 ) 

307 self.assertMaskedImagesEqual( 

308 icResult2.exposure.getMaskedImage(), 

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

310 ) 

311 self.assertCatalogsEqual( 

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

313 icResult1.resultList[0].result.sourceCat 

314 ) 

315 self.assertCatalogsEqual( 

316 icResult2.sourceCat, 

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

318 ) 

319 self.assertBackgroundListsEqual( 

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

321 icResult1.resultList[0].result.background 

322 ) 

323 self.assertBackgroundListsEqual( 

324 icResult2.background, 

325 icResult1.resultList[0].result.background 

326 ) 

327 

328 calResult1 = CalibrateTask.parseAndRun( 

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

330 doReturnResults=True, 

331 ) 

332 calResult2 = calibrateTask.run( 

333 icResult2.exposure, 

334 background=icResult2.background, 

335 icSourceCat=icResult2.sourceCat, 

336 exposureIdInfo=exposureIdInfo, 

337 ) 

338 self.assertMaskedImagesEqual( 

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

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

341 ) 

342 self.assertMaskedImagesEqual( 

343 calResult2.exposure.getMaskedImage(), 

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

345 ) 

346 self.assertCatalogsEqual( 

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

348 calResult1.resultList[0].result.sourceCat 

349 ) 

350 self.assertCatalogsEqual( 

351 calResult2.sourceCat, 

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

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

354 ) 

355 self.assertBackgroundListsEqual( 

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

357 calResult1.resultList[0].result.background 

358 ) 

359 self.assertBackgroundListsEqual( 

360 calResult2.background, 

361 calResult1.resultList[0].result.background 

362 ) 

363 

364 finally: 

365 if OutputName is None: 

366 shutil.rmtree(outPath) 

367 else: 

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

369 

370 

371def setup_module(module): 

372 lsst.utils.tests.init() 

373 

374 

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

376 pass 

377 

378 

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

380 lsst.utils.tests.init() 

381 unittest.main()