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

119 psfIxx = psfShape.getIxx() 

120 psfIyy = psfShape.getIyy() 

121 psfIxy = psfShape.getIxy() 

122 

123 if i == 0: 

124 print("\nMeasured results:") 

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

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

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

128 

129 print("numGoodPix =", numGoodPix) 

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

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

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

133 

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

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

136 

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

138 # updated to reflect major algorithmic changes. 

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

140 # small numeric changes here, check on slack at 

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

142 # reasonable, and then replace the failing values by 

143 # running the test to determine the updated values. 

144 expectedPlaces = 7 # Tolerance for numerical comparisons 

145 for name, var, val in [ 

146 ("bgMean", bgMean, 191.48623786891795), 

147 ("bgStdDev", bgStdDev, 0.23994185672586282), 

148 ("numGoodPix", numGoodPix, 1966606), 

149 ("imMean", imMean, 1.1242456954648634), 

150 ("imStdDev", imStdDev, 85.8129750182329), 

151 ("varMean", varMean, 131.24003624152013), 

152 ("varStdDev", varStdDev, 55.98012493452948), 

153 ("psfIxx", psfIxx, 2.843329671276296), 

154 ("psfIyy", psfIyy, 2.2249941554078156), 

155 ("psfIxy", psfIxy, 0.16073332780683286) 

156 ]: 

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

158 

159 else: 

160 self.assertEqual(imMean, oldImMean) 

161 self.assertEqual(imStdDev, oldImStdDev) 

162 self.assertEqual(varMean, oldVarMean) 

163 self.assertEqual(varStdDev, oldVarStdDev) 

164 self.assertEqual(psfIxx, oldPsfIxx) 

165 self.assertEqual(psfIyy, oldPsfIyy) 

166 self.assertEqual(psfIxy, oldPsfIxy) 

167 

168 oldImMean = imMean 

169 oldImStdDev = imStdDev 

170 oldVarMean = varMean 

171 oldVarStdDev = varStdDev 

172 oldPsfIxx = psfIxx 

173 oldPsfIyy = psfIyy 

174 oldPsfIxy = psfIxy 

175 

176 finally: 

177 if OutputName is None: 

178 shutil.rmtree(outPath) 

179 else: 

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

181 

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

183 """Compare two Catalogs for equality. 

184 

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

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

187 

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

189 """ 

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

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

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

193 

194 def fixNaN(x): 

195 if x != x: 

196 return "NaN" 

197 

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

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

200 if name not in skipCols: 

201 self.assertEqual( 

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

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

204 ) 

205 

206 def assertBackgroundListsEqual(self, bkg1, bkg2): 

207 """Compare two BackgroundLists for equality. 

208 

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

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

211 

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

213 """ 

214 im1 = bkg1.getImage() 

215 im2 = bkg2.getImage() 

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

217 self.assertImagesEqual(im1, im2) 

218 

219 def testComponents(self): 

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

221 

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

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

224 

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

226 present and equivalent to both in-memory results. 

227 """ 

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

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

230 # to those tasks. 

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

232 # Construct task instances we can use directly from Python 

233 isrTask = IsrTask( 

234 config=getObsTestConfig(IsrTask), 

235 name="isr2" 

236 ) 

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

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

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

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

241 charImageTask = CharacterizeImageTask( 

242 config=getObsTestConfig(CharacterizeImageTask), 

243 name="charImage2" 

244 ) 

245 calibrateTask = CalibrateTask( 

246 config=getObsTestConfig(CalibrateTask), 

247 name="calibrate2", 

248 icSourceSchema=charImageTask.schema 

249 ) 

250 try: 

251 dataId = dict(visit=1) 

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

253 

254 isrResult1 = IsrTask.parseAndRun( 

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

256 doReturnResults=True, 

257 ) 

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

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

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

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

262 camera = dataRef.get("camera") 

263 isrData = isrTask.readIsrData(dataRef, rawExposure) 

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

265 isrResult2 = isrTask.run( 

266 rawExposure, 

267 bias=isrData.bias, 

268 linearizer=isrData.linearizer, 

269 flat=isrData.flat, 

270 defects=isrData.defects, 

271 fringes=isrData.fringes, 

272 bfKernel=isrData.bfKernel, 

273 camera=camera, 

274 ) 

275 self.assertMaskedImagesEqual( 

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

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

278 ) 

279 self.assertMaskedImagesEqual( 

280 isrResult2.exposure.getMaskedImage(), 

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

282 ) 

283 

284 icResult1 = CharacterizeImageTask.parseAndRun( 

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

286 doReturnResults=True, 

287 ) 

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

289 self.assertMaskedImagesEqual( 

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

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

292 ) 

293 self.assertMaskedImagesEqual( 

294 icResult2.exposure.getMaskedImage(), 

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

296 ) 

297 self.assertCatalogsEqual( 

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

299 icResult1.resultList[0].result.sourceCat 

300 ) 

301 self.assertCatalogsEqual( 

302 icResult2.sourceCat, 

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

304 ) 

305 self.assertBackgroundListsEqual( 

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

307 icResult1.resultList[0].result.background 

308 ) 

309 self.assertBackgroundListsEqual( 

310 icResult2.background, 

311 icResult1.resultList[0].result.background 

312 ) 

313 

314 calResult1 = CalibrateTask.parseAndRun( 

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

316 doReturnResults=True, 

317 ) 

318 calResult2 = calibrateTask.run( 

319 icResult2.exposure, 

320 background=icResult2.background, 

321 icSourceCat=icResult2.sourceCat, 

322 exposureIdInfo=exposureIdInfo, 

323 ) 

324 self.assertMaskedImagesEqual( 

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

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

327 ) 

328 self.assertMaskedImagesEqual( 

329 calResult2.exposure.getMaskedImage(), 

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

331 ) 

332 self.assertCatalogsEqual( 

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

334 calResult1.resultList[0].result.sourceCat 

335 ) 

336 self.assertCatalogsEqual( 

337 calResult2.sourceCat, 

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

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

340 ) 

341 self.assertBackgroundListsEqual( 

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

343 calResult1.resultList[0].result.background 

344 ) 

345 self.assertBackgroundListsEqual( 

346 calResult2.background, 

347 calResult1.resultList[0].result.background 

348 ) 

349 

350 finally: 

351 if OutputName is None: 

352 shutil.rmtree(outPath) 

353 else: 

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

355 

356 

357def setup_module(module): 

358 lsst.utils.tests.init() 

359 

360 

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

362 pass 

363 

364 

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

366 lsst.utils.tests.init() 

367 unittest.main()