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), 185 + 100) # 185 real sources plus 100 sky sources 

136 

137 expectedPlaces = 7 # Tolerance for numerical comparisons 

138 for name, var, val in [ 

139 ("bgMean", bgMean, 191.48635852060525), 

140 ("bgStdDev", bgStdDev, 0.2399466881603354), 

141 ("numGoodPix", numGoodPix, 1966820), 

142 ("imMean", imMean, 1.1237668985230562), 

143 ("imStdDev", imStdDev, 85.81296241298496), 

144 ("varMean", varMean, 131.24003624152013), 

145 ("varStdDev", varStdDev, 55.98012493452948), 

146 ("psfIxx", psfIxx, 2.769679536557131), 

147 ("psfIyy", psfIyy, 2.2013649766299324), 

148 ("psfIxy", psfIxy, 0.14797939531970852) 

149 ]: 

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

151 

152 else: 

153 self.assertEqual(imMean, oldImMean) 

154 self.assertEqual(imStdDev, oldImStdDev) 

155 self.assertEqual(varMean, oldVarMean) 

156 self.assertEqual(varStdDev, oldVarStdDev) 

157 self.assertEqual(psfIxx, oldPsfIxx) 

158 self.assertEqual(psfIyy, oldPsfIyy) 

159 self.assertEqual(psfIxy, oldPsfIxy) 

160 

161 oldImMean = imMean 

162 oldImStdDev = imStdDev 

163 oldVarMean = varMean 

164 oldVarStdDev = varStdDev 

165 oldPsfIxx = psfIxx 

166 oldPsfIyy = psfIyy 

167 oldPsfIxy = psfIxy 

168 

169 finally: 

170 if OutputName is None: 170 ↛ 173line 170 didn't jump to line 173, because the condition on line 170 was never false

171 shutil.rmtree(outPath) 

172 else: 

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

174 

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

176 """Compare two Catalogs for equality. 

177 

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

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

180 

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

182 """ 

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

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

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

186 

187 def fixNaN(x): 

188 if x != x: 

189 return "NaN" 

190 

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

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

193 if name not in skipCols: 

194 self.assertEqual( 

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

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

197 ) 

198 

199 def assertBackgroundListsEqual(self, bkg1, bkg2): 

200 """Compare two BackgroundLists for equality. 

201 

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

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

204 

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

206 """ 

207 im1 = bkg1.getImage() 

208 im2 = bkg2.getImage() 

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

210 self.assertImagesEqual(im1, im2) 

211 

212 def testComponents(self): 

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

214 

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

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

217 

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

219 present and equivalent to both in-memory results. 

220 """ 

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

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

223 # to those tasks. 

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

225 # Construct task instances we can use directly from Python 

226 isrTask = IsrTask( 

227 config=getObsTestConfig(IsrTask), 

228 name="isr2" 

229 ) 

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

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

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

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

234 charImageTask = CharacterizeImageTask( 

235 config=getObsTestConfig(CharacterizeImageTask), 

236 name="charImage2" 

237 ) 

238 calibrateTask = CalibrateTask( 

239 config=getObsTestConfig(CalibrateTask), 

240 name="calibrate2", 

241 icSourceSchema=charImageTask.schema 

242 ) 

243 try: 

244 dataId = dict(visit=1) 

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

246 

247 isrResult1 = IsrTask.parseAndRun( 

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

249 doReturnResults=True, 

250 ) 

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

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

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

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

255 camera = dataRef.get("camera") 

256 isrData = isrTask.readIsrData(dataRef, rawExposure) 

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

258 isrResult2 = isrTask.run( 

259 rawExposure, 

260 bias=isrData.bias, 

261 linearizer=isrData.linearizer, 

262 flat=isrData.flat, 

263 defects=isrData.defects, 

264 fringes=isrData.fringes, 

265 bfKernel=isrData.bfKernel, 

266 camera=camera, 

267 ) 

268 self.assertMaskedImagesEqual( 

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

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

271 ) 

272 self.assertMaskedImagesEqual( 

273 isrResult2.exposure.getMaskedImage(), 

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

275 ) 

276 

277 icResult1 = CharacterizeImageTask.parseAndRun( 

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

279 doReturnResults=True, 

280 ) 

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

282 self.assertMaskedImagesEqual( 

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

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

285 ) 

286 self.assertMaskedImagesEqual( 

287 icResult2.exposure.getMaskedImage(), 

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

289 ) 

290 self.assertCatalogsEqual( 

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

292 icResult1.resultList[0].result.sourceCat 

293 ) 

294 self.assertCatalogsEqual( 

295 icResult2.sourceCat, 

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

297 ) 

298 self.assertBackgroundListsEqual( 

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

300 icResult1.resultList[0].result.background 

301 ) 

302 self.assertBackgroundListsEqual( 

303 icResult2.background, 

304 icResult1.resultList[0].result.background 

305 ) 

306 

307 calResult1 = CalibrateTask.parseAndRun( 

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

309 doReturnResults=True, 

310 ) 

311 calResult2 = calibrateTask.run( 

312 icResult2.exposure, 

313 background=icResult2.background, 

314 icSourceCat=icResult2.sourceCat, 

315 exposureIdInfo=exposureIdInfo, 

316 ) 

317 self.assertMaskedImagesEqual( 

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

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

320 ) 

321 self.assertMaskedImagesEqual( 

322 calResult2.exposure.getMaskedImage(), 

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

324 ) 

325 self.assertCatalogsEqual( 

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

327 calResult1.resultList[0].result.sourceCat 

328 ) 

329 self.assertCatalogsEqual( 

330 calResult2.sourceCat, 

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

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

333 ) 

334 self.assertBackgroundListsEqual( 

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

336 calResult1.resultList[0].result.background 

337 ) 

338 self.assertBackgroundListsEqual( 

339 calResult2.background, 

340 calResult1.resultList[0].result.background 

341 ) 

342 

343 finally: 

344 if OutputName is None: 344 ↛ 347line 344 didn't jump to line 347, because the condition on line 344 was never false

345 shutil.rmtree(outPath) 

346 else: 

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

348 

349 

350def setup_module(module): 

351 lsst.utils.tests.init() 

352 

353 

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

355 pass 

356 

357 

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

359 lsst.utils.tests.init() 

360 unittest.main()