Coverage for tests/test_exposure.py: 11%

657 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-07-08 03:13 -0700

1# This file is part of afw. 

2# 

3# Developed for the LSST Data Management System. 

4# This product includes software developed by the LSST Project 

5# (https://www.lsst.org). 

6# See the COPYRIGHT file at the top-level directory of this distribution 

7# for details of code ownership. 

8# 

9# This program is free software: you can redistribute it and/or modify 

10# it under the terms of the GNU General Public License as published by 

11# the Free Software Foundation, either version 3 of the License, or 

12# (at your option) any later version. 

13# 

14# This program is distributed in the hope that it will be useful, 

15# but WITHOUT ANY WARRANTY; without even the implied warranty of 

16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

17# GNU General Public License for more details. 

18# 

19# You should have received a copy of the GNU General Public License 

20# along with this program. If not, see <https://www.gnu.org/licenses/>. 

21 

22""" 

23Test lsst.afw.image.Exposure 

24""" 

25 

26import dataclasses 

27import os.path 

28import unittest 

29 

30import numpy as np 

31from numpy.testing import assert_allclose 

32import yaml 

33import astropy.units as units 

34 

35import lsst.utils 

36import lsst.utils.tests 

37import lsst.geom 

38import lsst.afw.image as afwImage 

39from lsst.afw.coord import Weather 

40import lsst.afw.geom as afwGeom 

41import lsst.afw.table as afwTable 

42import lsst.pex.exceptions as pexExcept 

43from lsst.afw.fits import readMetadata, FitsError 

44from lsst.afw.cameraGeom.testUtils import DetectorWrapper 

45from lsst.log import Log 

46from testTableArchivesLib import DummyPsf 

47 

48Log.getLogger("lsst.afw.image.Mask").setLevel(Log.INFO) 

49 

50try: 

51 dataDir = os.path.join(lsst.utils.getPackageDir("afwdata"), "data") 

52except LookupError: 

53 dataDir = None 

54else: 

55 InputMaskedImageName = "871034p_1_MI.fits" 

56 InputMaskedImageNameSmall = "small_MI.fits" 

57 InputImageNameSmall = "small" 

58 OutputMaskedImageName = "871034p_1_MInew.fits" 

59 

60 currDir = os.path.abspath(os.path.dirname(__file__)) 

61 inFilePath = os.path.join(dataDir, InputMaskedImageName) 

62 inFilePathSmall = os.path.join(dataDir, InputMaskedImageNameSmall) 

63 inFilePathSmallImage = os.path.join(dataDir, InputImageNameSmall) 

64 

65 

66@unittest.skipIf(dataDir is None, "afwdata not setup") 

67class ExposureTestCase(lsst.utils.tests.TestCase): 

68 """ 

69 A test case for the Exposure Class 

70 """ 

71 

72 def setUp(self): 

73 maskedImage = afwImage.MaskedImageF(inFilePathSmall) 

74 maskedImageMD = readMetadata(inFilePathSmall) 

75 

76 self.smallExposure = afwImage.ExposureF(inFilePathSmall) 

77 self.width = maskedImage.getWidth() 

78 self.height = maskedImage.getHeight() 

79 self.wcs = afwGeom.makeSkyWcs(maskedImageMD, False) 

80 self.md = maskedImageMD 

81 self.psf = DummyPsf(2.0) 

82 self.detector = DetectorWrapper().detector 

83 self.id = 42 

84 self.extras = {"MISC": DummyPsf(3.5)} 

85 

86 self.exposureBlank = afwImage.ExposureF() 

87 self.exposureMiOnly = afwImage.makeExposure(maskedImage) 

88 self.exposureMiWcs = afwImage.makeExposure(maskedImage, self.wcs) 

89 # n.b. the (100, 100, ...) form 

90 self.exposureCrWcs = afwImage.ExposureF(100, 100, self.wcs) 

91 # test with ExtentI(100, 100) too 

92 self.exposureCrOnly = afwImage.ExposureF(lsst.geom.ExtentI(100, 100)) 

93 

94 def tearDown(self): 

95 del self.smallExposure 

96 del self.wcs 

97 del self.psf 

98 del self.detector 

99 del self.extras 

100 

101 del self.exposureBlank 

102 del self.exposureMiOnly 

103 del self.exposureMiWcs 

104 del self.exposureCrWcs 

105 del self.exposureCrOnly 

106 

107 def testGetMaskedImage(self): 

108 """ 

109 Test to ensure a MaskedImage can be obtained from each 

110 Exposure. An Exposure is required to have a MaskedImage, 

111 therefore each of the Exposures should return a MaskedImage. 

112 

113 MaskedImage class should throw appropriate 

114 lsst::pex::exceptions::NotFound if the MaskedImage can not be 

115 obtained. 

116 """ 

117 maskedImageBlank = self.exposureBlank.getMaskedImage() 

118 blankWidth = maskedImageBlank.getWidth() 

119 blankHeight = maskedImageBlank.getHeight() 

120 if blankWidth != blankHeight != 0: 

121 self.fail(f"{blankWidth} = {blankHeight} != 0") 

122 

123 maskedImageMiOnly = self.exposureMiOnly.getMaskedImage() 

124 miOnlyWidth = maskedImageMiOnly.getWidth() 

125 miOnlyHeight = maskedImageMiOnly.getHeight() 

126 self.assertAlmostEqual(miOnlyWidth, self.width) 

127 self.assertAlmostEqual(miOnlyHeight, self.height) 

128 

129 # NOTE: Unittests for Exposures created from a MaskedImage and 

130 # a WCS object are incomplete. No way to test the validity of 

131 # the WCS being copied/created. 

132 

133 maskedImageMiWcs = self.exposureMiWcs.getMaskedImage() 

134 miWcsWidth = maskedImageMiWcs.getWidth() 

135 miWcsHeight = maskedImageMiWcs.getHeight() 

136 self.assertAlmostEqual(miWcsWidth, self.width) 

137 self.assertAlmostEqual(miWcsHeight, self.height) 

138 

139 maskedImageCrWcs = self.exposureCrWcs.getMaskedImage() 

140 crWcsWidth = maskedImageCrWcs.getWidth() 

141 crWcsHeight = maskedImageCrWcs.getHeight() 

142 if crWcsWidth != crWcsHeight != 0: 

143 self.fail(f"{crWcsWidth} != {crWcsHeight} != 0") 

144 

145 maskedImageCrOnly = self.exposureCrOnly.getMaskedImage() 

146 crOnlyWidth = maskedImageCrOnly.getWidth() 

147 crOnlyHeight = maskedImageCrOnly.getHeight() 

148 if crOnlyWidth != crOnlyHeight != 0: 

149 self.fail(f"{crOnlyWidth} != {crOnlyHeight} != 0") 

150 

151 # Check Exposure.getWidth() returns the MaskedImage's width 

152 self.assertEqual(crOnlyWidth, self.exposureCrOnly.getWidth()) 

153 self.assertEqual(crOnlyHeight, self.exposureCrOnly.getHeight()) 

154 # check width/height properties 

155 self.assertEqual(crOnlyWidth, self.exposureCrOnly.width) 

156 self.assertEqual(crOnlyHeight, self.exposureCrOnly.height) 

157 

158 def testProperties(self): 

159 self.assertMaskedImagesEqual(self.exposureMiOnly.maskedImage, 

160 self.exposureMiOnly.getMaskedImage()) 

161 mi2 = afwImage.MaskedImageF(self.exposureMiOnly.getDimensions()) 

162 mi2.image.array[:] = 5.0 

163 mi2.variance.array[:] = 3.0 

164 mi2.mask.array[:] = 0x1 

165 self.exposureMiOnly.maskedImage = mi2 

166 self.assertMaskedImagesEqual(self.exposureMiOnly.maskedImage, mi2) 

167 self.assertImagesEqual(self.exposureMiOnly.image, 

168 self.exposureMiOnly.maskedImage.image) 

169 

170 image3 = afwImage.ImageF(self.exposureMiOnly.getDimensions()) 

171 image3.array[:] = 3.0 

172 self.exposureMiOnly.image = image3 

173 self.assertImagesEqual(self.exposureMiOnly.image, image3) 

174 

175 mask3 = afwImage.MaskX(self.exposureMiOnly.getDimensions()) 

176 mask3.array[:] = 0x2 

177 self.exposureMiOnly.mask = mask3 

178 self.assertMasksEqual(self.exposureMiOnly.mask, mask3) 

179 

180 var3 = afwImage.ImageF(self.exposureMiOnly.getDimensions()) 

181 var3.array[:] = 2.0 

182 self.exposureMiOnly.variance = var3 

183 self.assertImagesEqual(self.exposureMiOnly.variance, var3) 

184 

185 # Test the property getter for a null VisitInfo. 

186 self.assertIsNone(self.exposureMiOnly.visitInfo) 

187 

188 def testGetWcs(self): 

189 """Test that a WCS can be obtained from each Exposure created with 

190 a WCS, and that an Exposure lacking a WCS returns None. 

191 """ 

192 # These exposures don't contain a WCS 

193 self.assertIsNone(self.exposureBlank.getWcs()) 

194 self.assertIsNone(self.exposureMiOnly.getWcs()) 

195 self.assertIsNone(self.exposureCrOnly.getWcs()) 

196 

197 # These exposures should contain a WCS 

198 self.assertEqual(self.wcs, self.exposureMiWcs.getWcs()) 

199 self.assertEqual(self.wcs, self.exposureCrWcs.getWcs()) 

200 

201 def testExposureInfoConstructor(self): 

202 """Test the Exposure(maskedImage, exposureInfo) constructor""" 

203 exposureInfo = afwImage.ExposureInfo() 

204 exposureInfo.setWcs(self.wcs) 

205 exposureInfo.setDetector(self.detector) 

206 gFilterLabel = afwImage.FilterLabel(band="g") 

207 exposureInfo.setFilter(gFilterLabel) 

208 maskedImage = afwImage.MaskedImageF(inFilePathSmall) 

209 exposure = afwImage.ExposureF(maskedImage, exposureInfo) 

210 

211 self.assertTrue(exposure.hasWcs()) 

212 self.assertEqual(exposure.getWcs().getPixelOrigin(), 

213 self.wcs.getPixelOrigin()) 

214 self.assertEqual(exposure.getDetector().getName(), 

215 self.detector.getName()) 

216 self.assertEqual(exposure.getDetector().getSerial(), 

217 self.detector.getSerial()) 

218 self.assertEqual(exposure.getFilter(), gFilterLabel) 

219 

220 self.assertTrue(exposure.getInfo().hasWcs()) 

221 # check the ExposureInfo property 

222 self.assertTrue(exposure.info.hasWcs()) 

223 self.assertEqual(exposure.getInfo().getWcs().getPixelOrigin(), 

224 self.wcs.getPixelOrigin()) 

225 self.assertEqual(exposure.getInfo().getDetector().getName(), 

226 self.detector.getName()) 

227 self.assertEqual(exposure.getInfo().getDetector().getSerial(), 

228 self.detector.getSerial()) 

229 self.assertEqual(exposure.getInfo().getFilter(), gFilterLabel) 

230 

231 def testNullWcs(self): 

232 """Test that an Exposure constructed with second argument None is usable 

233 

234 When the exposureInfo constructor was first added, trying to get a WCS 

235 or other info caused a segfault because the ExposureInfo did not exist. 

236 """ 

237 maskedImage = self.exposureMiOnly.getMaskedImage() 

238 exposure = afwImage.ExposureF(maskedImage, None) 

239 self.assertFalse(exposure.hasWcs()) 

240 self.assertFalse(exposure.hasPsf()) 

241 

242 def testExposureInfoSetNone(self): 

243 exposureInfo = afwImage.ExposureInfo() 

244 exposureInfo.setDetector(None) 

245 exposureInfo.setValidPolygon(None) 

246 exposureInfo.setPsf(None) 

247 exposureInfo.setWcs(None) 

248 exposureInfo.setPhotoCalib(None) 

249 exposureInfo.setCoaddInputs(None) 

250 exposureInfo.setVisitInfo(None) 

251 exposureInfo.setApCorrMap(None) 

252 for key in self.extras: 

253 exposureInfo.setComponent(key, None) 

254 

255 def testSetExposureInfo(self): 

256 exposureInfo = afwImage.ExposureInfo() 

257 exposureInfo.setWcs(self.wcs) 

258 exposureInfo.setDetector(self.detector) 

259 gFilterLabel = afwImage.FilterLabel(band="g") 

260 exposureInfo.setFilter(gFilterLabel) 

261 exposureInfo.setId(self.id) 

262 maskedImage = afwImage.MaskedImageF(inFilePathSmall) 

263 exposure = afwImage.ExposureF(maskedImage) 

264 self.assertFalse(exposure.hasWcs()) 

265 

266 exposure.setInfo(exposureInfo) 

267 

268 self.assertTrue(exposure.hasWcs()) 

269 self.assertEqual(exposure.getWcs().getPixelOrigin(), 

270 self.wcs.getPixelOrigin()) 

271 self.assertEqual(exposure.getDetector().getName(), 

272 self.detector.getName()) 

273 self.assertEqual(exposure.getDetector().getSerial(), 

274 self.detector.getSerial()) 

275 self.assertEqual(exposure.getFilter(), gFilterLabel) 

276 

277 # test properties 

278 self.assertEqual(exposure.detector.getName(), self.detector.getName()) 

279 self.assertEqual(exposure.filter, gFilterLabel) 

280 self.assertEqual(exposure.wcs, self.wcs) 

281 

282 def testVisitInfoFitsPersistence(self): 

283 """Test saving an exposure to FITS and reading it back in preserves (some) VisitInfo fields""" 

284 exposureTime = 12.3 

285 boresightRotAngle = 45.6 * lsst.geom.degrees 

286 weather = Weather(1.1, 2.2, 0.3) 

287 visitInfo = afwImage.VisitInfo( 

288 exposureTime=exposureTime, 

289 boresightRotAngle=boresightRotAngle, 

290 weather=weather, 

291 ) 

292 photoCalib = afwImage.PhotoCalib(3.4, 5.6) 

293 exposureInfo = afwImage.ExposureInfo() 

294 exposureInfo.setVisitInfo(visitInfo) 

295 exposureInfo.setPhotoCalib(photoCalib) 

296 exposureInfo.setDetector(self.detector) 

297 gFilterLabel = afwImage.FilterLabel(band="g") 

298 exposureInfo.setFilter(gFilterLabel) 

299 maskedImage = afwImage.MaskedImageF(inFilePathSmall) 

300 exposure = afwImage.ExposureF(maskedImage, exposureInfo) 

301 with lsst.utils.tests.getTempFilePath(".fits") as tmpFile: 

302 exposure.writeFits(tmpFile) 

303 rtExposure = afwImage.ExposureF(tmpFile) 

304 rtVisitInfo = rtExposure.getInfo().getVisitInfo() 

305 self.assertEqual(rtVisitInfo.getWeather(), weather) 

306 self.assertEqual(rtExposure.getPhotoCalib(), photoCalib) 

307 self.assertEqual(rtExposure.getFilter(), gFilterLabel) 

308 

309 # Test property getters. 

310 self.assertEqual(rtExposure.photoCalib, photoCalib) 

311 self.assertEqual(rtExposure.filter, gFilterLabel) 

312 # NOTE: we can't test visitInfo equality, because most fields are NaN. 

313 self.assertIsNotNone(rtExposure.visitInfo) 

314 

315 def testSetMembers(self): 

316 """ 

317 Test that the MaskedImage and the WCS of an Exposure can be set. 

318 """ 

319 exposure = afwImage.ExposureF() 

320 

321 maskedImage = afwImage.MaskedImageF(inFilePathSmall) 

322 exposure.setMaskedImage(maskedImage) 

323 exposure.setWcs(self.wcs) 

324 exposure.setDetector(self.detector) 

325 exposure.setFilter(afwImage.FilterLabel(band="g")) 

326 

327 self.assertEqual(exposure.getDetector().getName(), 

328 self.detector.getName()) 

329 self.assertEqual(exposure.getDetector().getSerial(), 

330 self.detector.getSerial()) 

331 self.assertEqual(exposure.getFilter().bandLabel, "g") 

332 self.assertEqual(exposure.getWcs(), self.wcs) 

333 

334 # The PhotoCalib tests are in test_photoCalib.py; 

335 # here we just check that it's gettable and settable. 

336 self.assertIsNone(exposure.getPhotoCalib()) 

337 

338 photoCalib = afwImage.PhotoCalib(511.1, 44.4) 

339 exposure.setPhotoCalib(photoCalib) 

340 self.assertEqual(exposure.getPhotoCalib(), photoCalib) 

341 

342 # Psfs next 

343 self.assertFalse(exposure.hasPsf()) 

344 exposure.setPsf(self.psf) 

345 self.assertTrue(exposure.hasPsf()) 

346 

347 exposure.setPsf(DummyPsf(1.0)) # we can reset the Psf 

348 

349 # extras next 

350 info = exposure.getInfo() 

351 for key, value in self.extras.items(): 

352 self.assertFalse(info.hasComponent(key)) 

353 self.assertIsNone(info.getComponent(key)) 

354 info.setComponent(key, value) 

355 self.assertTrue(info.hasComponent(key)) 

356 self.assertEqual(info.getComponent(key), value) 

357 info.removeComponent(key) 

358 self.assertFalse(info.hasComponent(key)) 

359 

360 # Test that we can set the MaskedImage and WCS of an Exposure 

361 # that already has both 

362 self.exposureMiWcs.setMaskedImage(maskedImage) 

363 exposure.setWcs(self.wcs) 

364 

365 def testHasWcs(self): 

366 """ 

367 Test if an Exposure has a WCS or not. 

368 """ 

369 self.assertFalse(self.exposureBlank.hasWcs()) 

370 

371 self.assertFalse(self.exposureMiOnly.hasWcs()) 

372 self.assertTrue(self.exposureMiWcs.hasWcs()) 

373 self.assertTrue(self.exposureCrWcs.hasWcs()) 

374 self.assertFalse(self.exposureCrOnly.hasWcs()) 

375 

376 def testGetSubExposure(self): 

377 """ 

378 Test that a subExposure of the original Exposure can be obtained. 

379 

380 The MaskedImage class should throw a 

381 lsst::pex::exceptions::InvalidParameter if the requested 

382 subRegion is not fully contained within the original 

383 MaskedImage. 

384 

385 """ 

386 # 

387 # This subExposure is valid 

388 # 

389 subBBox = lsst.geom.Box2I(lsst.geom.Point2I(40, 50), 

390 lsst.geom.Extent2I(10, 10)) 

391 subExposure = self.exposureCrWcs.Factory( 

392 self.exposureCrWcs, subBBox, afwImage.LOCAL) 

393 

394 self.checkWcs(self.exposureCrWcs, subExposure) 

395 

396 # this subRegion is not valid and should trigger an exception 

397 # from the MaskedImage class and should trigger an exception 

398 # from the WCS class for the MaskedImage 871034p_1_MI. 

399 

400 subRegion3 = lsst.geom.Box2I(lsst.geom.Point2I(100, 100), 

401 lsst.geom.Extent2I(10, 10)) 

402 

403 def getSubRegion(): 

404 self.exposureCrWcs.Factory( 

405 self.exposureCrWcs, subRegion3, afwImage.LOCAL) 

406 

407 self.assertRaises(pexExcept.LengthError, getSubRegion) 

408 

409 # this subRegion is not valid and should trigger an exception 

410 # from the MaskedImage class only for the MaskedImage small_MI. 

411 # small_MI (cols, rows) = (256, 256) 

412 

413 subRegion4 = lsst.geom.Box2I(lsst.geom.Point2I(250, 250), 

414 lsst.geom.Extent2I(10, 10)) 

415 

416 def getSubRegion(): 

417 self.exposureCrWcs.Factory( 

418 self.exposureCrWcs, subRegion4, afwImage.LOCAL) 

419 

420 self.assertRaises(pexExcept.LengthError, getSubRegion) 

421 

422 # check the sub- and parent- exposures are using the same Wcs 

423 # transformation 

424 subBBox = lsst.geom.Box2I(lsst.geom.Point2I(40, 50), 

425 lsst.geom.Extent2I(10, 10)) 

426 subExposure = self.exposureCrWcs.Factory( 

427 self.exposureCrWcs, subBBox, afwImage.LOCAL) 

428 parentSkyPos = self.exposureCrWcs.getWcs().pixelToSky(0, 0) 

429 

430 subExpSkyPos = subExposure.getWcs().pixelToSky(0, 0) 

431 

432 self.assertSpherePointsAlmostEqual(parentSkyPos, subExpSkyPos, msg="Wcs in sub image has changed") 

433 

434 def testReadWriteFits(self): 

435 """Test readFits and writeFits. 

436 """ 

437 # This should pass without an exception 

438 mainExposure = afwImage.ExposureF(inFilePathSmall) 

439 mainExposure.info.setId(self.id) 

440 mainExposure.setDetector(self.detector) 

441 

442 subBBox = lsst.geom.Box2I(lsst.geom.Point2I(10, 10), 

443 lsst.geom.Extent2I(40, 50)) 

444 subExposure = mainExposure.Factory( 

445 mainExposure, subBBox, afwImage.LOCAL) 

446 self.checkWcs(mainExposure, subExposure) 

447 det = subExposure.getDetector() 

448 self.assertTrue(det) 

449 

450 subExposure = afwImage.ExposureF( 

451 inFilePathSmall, subBBox, afwImage.LOCAL) 

452 

453 self.checkWcs(mainExposure, subExposure) 

454 

455 # This should throw an exception 

456 def getExposure(): 

457 afwImage.ExposureF(inFilePathSmallImage) 

458 

459 self.assertRaises(FitsError, getExposure) 

460 

461 mainExposure.setPsf(self.psf) 

462 

463 # Make sure we can write without an exception 

464 photoCalib = afwImage.PhotoCalib(1e-10, 1e-12) 

465 mainExposure.setPhotoCalib(photoCalib) 

466 

467 mainInfo = mainExposure.getInfo() 

468 for key, value in self.extras.items(): 

469 mainInfo.setComponent(key, value) 

470 

471 with lsst.utils.tests.getTempFilePath(".fits") as tmpFile: 

472 mainExposure.writeFits(tmpFile) 

473 

474 readExposure = type(mainExposure)(tmpFile) 

475 

476 # 

477 # Check the round-tripping 

478 # 

479 self.assertIsNotNone(mainExposure.getFilter()) 

480 self.assertEqual(mainExposure.getFilter(), 

481 readExposure.getFilter()) 

482 

483 self.assertEqual(photoCalib, readExposure.getPhotoCalib()) 

484 

485 readInfo = readExposure.getInfo() 

486 self.assertEqual(mainExposure.info.getId(), readInfo.id) 

487 for key, value in self.extras.items(): 

488 self.assertEqual(value, readInfo.getComponent(key)) 

489 

490 psf = readExposure.getPsf() 

491 self.assertIsNotNone(psf) 

492 self.assertEqual(psf, self.psf) 

493 # check psf property getter 

494 self.assertEqual(readExposure.psf, self.psf) 

495 

496 def checkWcs(self, parentExposure, subExposure): 

497 """Compare WCS at corner points of a sub-exposure and its parent exposure 

498 By using the function indexToPosition, we should be able to convert the indices 

499 (of the four corners (of the sub-exposure)) to positions and use the wcs 

500 to get the same sky coordinates for each. 

501 """ 

502 subMI = subExposure.getMaskedImage() 

503 subDim = subMI.getDimensions() 

504 

505 # Note: pixel positions must be computed relative to XY0 when working 

506 # with WCS 

507 mainWcs = parentExposure.getWcs() 

508 subWcs = subExposure.getWcs() 

509 

510 for xSubInd in (0, subDim.getX()-1): 

511 for ySubInd in (0, subDim.getY()-1): 

512 self.assertSpherePointsAlmostEqual( 

513 mainWcs.pixelToSky( 

514 afwImage.indexToPosition(xSubInd), 

515 afwImage.indexToPosition(ySubInd), 

516 ), 

517 subWcs.pixelToSky( 

518 afwImage.indexToPosition(xSubInd), 

519 afwImage.indexToPosition(ySubInd), 

520 )) 

521 

522 def cmpExposure(self, e1, e2): 

523 self.assertEqual(e1.getDetector().getName(), 

524 e2.getDetector().getName()) 

525 self.assertEqual(e1.getDetector().getSerial(), 

526 e2.getDetector().getSerial()) 

527 self.assertEqual(e1.getFilter(), e2.getFilter()) 

528 xy = lsst.geom.Point2D(0, 0) 

529 self.assertEqual(e1.getWcs().pixelToSky(xy)[0], 

530 e2.getWcs().pixelToSky(xy)[0]) 

531 self.assertEqual(e1.getPhotoCalib(), e2.getPhotoCalib()) 

532 # check PSF identity 

533 if not e1.getPsf(): 

534 self.assertFalse(e2.getPsf()) 

535 else: 

536 self.assertEqual(e1.getPsf(), e2.getPsf()) 

537 # Check extra components 

538 i1 = e1.getInfo() 

539 i2 = e2.getInfo() 

540 for key in self.extras: 

541 self.assertEqual(i1.hasComponent(key), i2.hasComponent(key)) 

542 if i1.hasComponent(key): 

543 self.assertEqual(i1.getComponent(key), i2.getComponent(key)) 

544 

545 def testCopyExposure(self): 

546 """Copy an Exposure (maybe changing type)""" 

547 

548 exposureU = afwImage.ExposureU(inFilePathSmall, allowUnsafe=True) 

549 exposureU.setWcs(self.wcs) 

550 exposureU.setDetector(self.detector) 

551 exposureU.setFilter(afwImage.FilterLabel(band="g")) 

552 exposureU.setPsf(DummyPsf(4.0)) 

553 infoU = exposureU.getInfo() 

554 for key, value in self.extras.items(): 

555 infoU.setComponent(key, value) 

556 

557 exposureF = exposureU.convertF() 

558 self.cmpExposure(exposureF, exposureU) 

559 

560 nexp = exposureF.Factory(exposureF, False) 

561 self.cmpExposure(exposureF, nexp) 

562 

563 # Ensure that the copy was deep. 

564 # (actually this test is invalid since getDetector() returns a shared_ptr) 

565 # cen0 = exposureU.getDetector().getCenterPixel() 

566 # x0,y0 = cen0 

567 # det = exposureF.getDetector() 

568 # det.setCenterPixel(lsst.geom.Point2D(999.0, 437.8)) 

569 # self.assertEqual(exposureU.getDetector().getCenterPixel()[0], x0) 

570 # self.assertEqual(exposureU.getDetector().getCenterPixel()[1], y0) 

571 

572 def testDeepCopyData(self): 

573 """Make sure a deep copy of an Exposure has its own data (ticket #2625) 

574 """ 

575 exp = afwImage.ExposureF(6, 7) 

576 mi = exp.getMaskedImage() 

577 mi.getImage().set(100) 

578 mi.getMask().set(5) 

579 mi.getVariance().set(200) 

580 

581 expCopy = exp.clone() 

582 miCopy = expCopy.getMaskedImage() 

583 miCopy.getImage().set(-50) 

584 miCopy.getMask().set(2) 

585 miCopy.getVariance().set(175) 

586 

587 self.assertFloatsAlmostEqual(miCopy.getImage().getArray(), -50) 

588 self.assertTrue(np.all(miCopy.getMask().getArray() == 2)) 

589 self.assertFloatsAlmostEqual(miCopy.getVariance().getArray(), 175) 

590 

591 self.assertFloatsAlmostEqual(mi.getImage().getArray(), 100) 

592 self.assertTrue(np.all(mi.getMask().getArray() == 5)) 

593 self.assertFloatsAlmostEqual(mi.getVariance().getArray(), 200) 

594 

595 def testDeepCopySubData(self): 

596 """Make sure a deep copy of a subregion of an Exposure has its own data (ticket #2625) 

597 """ 

598 exp = afwImage.ExposureF(6, 7) 

599 mi = exp.getMaskedImage() 

600 mi.getImage().set(100) 

601 mi.getMask().set(5) 

602 mi.getVariance().set(200) 

603 

604 bbox = lsst.geom.Box2I(lsst.geom.Point2I(1, 0), lsst.geom.Extent2I(5, 4)) 

605 expCopy = exp.Factory(exp, bbox, afwImage.PARENT, True) 

606 miCopy = expCopy.getMaskedImage() 

607 miCopy.getImage().set(-50) 

608 miCopy.getMask().set(2) 

609 miCopy.getVariance().set(175) 

610 

611 self.assertFloatsAlmostEqual(miCopy.getImage().getArray(), -50) 

612 self.assertTrue(np.all(miCopy.getMask().getArray() == 2)) 

613 self.assertFloatsAlmostEqual(miCopy.getVariance().getArray(), 175) 

614 

615 self.assertFloatsAlmostEqual(mi.getImage().getArray(), 100) 

616 self.assertTrue(np.all(mi.getMask().getArray() == 5)) 

617 self.assertFloatsAlmostEqual(mi.getVariance().getArray(), 200) 

618 

619 def testDeepCopyMetadata(self): 

620 """Make sure a deep copy of an Exposure has a deep copy of metadata (ticket #2568) 

621 """ 

622 exp = afwImage.ExposureF(10, 10) 

623 expMeta = exp.getMetadata() 

624 expMeta.set("foo", 5) 

625 expCopy = exp.clone() 

626 expCopyMeta = expCopy.getMetadata() 

627 expCopyMeta.set("foo", 6) 

628 self.assertEqual(expCopyMeta.getScalar("foo"), 6) 

629 # this will fail if the bug is present 

630 self.assertEqual(expMeta.getScalar("foo"), 5) 

631 

632 def testDeepCopySubMetadata(self): 

633 """Make sure a deep copy of a subregion of an Exposure has a deep copy of metadata (ticket #2568) 

634 """ 

635 exp = afwImage.ExposureF(10, 10) 

636 expMeta = exp.getMetadata() 

637 expMeta.set("foo", 5) 

638 bbox = lsst.geom.Box2I(lsst.geom.Point2I(1, 0), lsst.geom.Extent2I(5, 5)) 

639 expCopy = exp.Factory(exp, bbox, afwImage.PARENT, True) 

640 expCopyMeta = expCopy.getMetadata() 

641 expCopyMeta.set("foo", 6) 

642 self.assertEqual(expCopyMeta.getScalar("foo"), 6) 

643 # this will fail if the bug is present 

644 self.assertEqual(expMeta.getScalar("foo"), 5) 

645 

646 def testMakeExposureLeaks(self): 

647 """Test for memory leaks in makeExposure (the test is in lsst.utils.tests.MemoryTestCase)""" 

648 afwImage.makeMaskedImage(afwImage.ImageU(lsst.geom.Extent2I(10, 20))) 

649 afwImage.makeExposure(afwImage.makeMaskedImage( 

650 afwImage.ImageU(lsst.geom.Extent2I(10, 20)))) 

651 

652 def testImageSlices(self): 

653 """Test image slicing, which generate sub-images using Box2I under the covers""" 

654 exp = afwImage.ExposureF(10, 20) 

655 mi = exp.getMaskedImage() 

656 mi.image[9, 19] = 10 

657 # N.b. Exposures don't support setting/getting the pixels so can't 

658 # replicate e.g. Image's slice tests 

659 sexp = exp[1:4, 6:10] 

660 self.assertEqual(sexp.getDimensions(), lsst.geom.ExtentI(3, 4)) 

661 sexp = exp[:, -3:, afwImage.LOCAL] 

662 self.assertEqual(sexp.getDimensions(), 

663 lsst.geom.ExtentI(exp.getWidth(), 3)) 

664 self.assertEqual(sexp.maskedImage[-1, -1, afwImage.LOCAL], 

665 exp.maskedImage[-1, -1, afwImage.LOCAL]) 

666 

667 def testConversionToScalar(self): 

668 """Test that even 1-pixel Exposures can't be converted to scalars""" 

669 im = afwImage.ExposureF(10, 20) 

670 

671 # only single pixel images may be converted 

672 self.assertRaises(TypeError, float, im) 

673 # actually, can't convert (img, msk, var) to scalar 

674 self.assertRaises(TypeError, float, im[0, 0]) 

675 

676 def testReadMetadata(self): 

677 with lsst.utils.tests.getTempFilePath(".fits") as tmpFile: 

678 self.exposureCrWcs.getMetadata().set("FRAZZLE", True) 

679 # This will write the main metadata (inc. FRAZZLE) to the primary HDU, and the 

680 # WCS to subsequent HDUs, along with INHERIT=T. 

681 self.exposureCrWcs.writeFits(tmpFile) 

682 # This should read the first non-empty HDU (i.e. it skips the primary), but 

683 # goes back and reads it if it finds INHERIT=T. That should let us read 

684 # frazzle and the Wcs from the PropertySet returned by 

685 # testReadMetadata. 

686 md = readMetadata(tmpFile) 

687 wcs = afwGeom.makeSkyWcs(md, False) 

688 self.assertPairsAlmostEqual(wcs.getPixelOrigin(), self.wcs.getPixelOrigin()) 

689 self.assertSpherePointsAlmostEqual(wcs.getSkyOrigin(), self.wcs.getSkyOrigin()) 

690 assert_allclose(wcs.getCdMatrix(), self.wcs.getCdMatrix(), atol=1e-10) 

691 frazzle = md.getScalar("FRAZZLE") 

692 self.assertTrue(frazzle) 

693 

694 def testArchiveKeys(self): 

695 with lsst.utils.tests.getTempFilePath(".fits") as tmpFile: 

696 exposure1 = afwImage.ExposureF(100, 100, self.wcs) 

697 exposure1.setPsf(self.psf) 

698 exposure1.writeFits(tmpFile) 

699 exposure2 = afwImage.ExposureF(tmpFile) 

700 self.assertFalse(exposure2.getMetadata().exists("AR_ID")) 

701 self.assertFalse(exposure2.getMetadata().exists("PSF_ID")) 

702 self.assertFalse(exposure2.getMetadata().exists("WCS_ID")) 

703 

704 def testTicket2861(self): 

705 with lsst.utils.tests.getTempFilePath(".fits") as tmpFile: 

706 exposure1 = afwImage.ExposureF(100, 100, self.wcs) 

707 exposure1.setPsf(self.psf) 

708 schema = afwTable.ExposureTable.makeMinimalSchema() 

709 coaddInputs = afwImage.CoaddInputs(schema, schema) 

710 exposure1.getInfo().setCoaddInputs(coaddInputs) 

711 exposure2 = afwImage.ExposureF(exposure1, True) 

712 self.assertIsNotNone(exposure2.getInfo().getCoaddInputs()) 

713 exposure2.writeFits(tmpFile) 

714 exposure3 = afwImage.ExposureF(tmpFile) 

715 self.assertIsNotNone(exposure3.getInfo().getCoaddInputs()) 

716 

717 def testGetCutout(self): 

718 wcs = self.smallExposure.getWcs() 

719 

720 dimensions = [lsst.geom.Extent2I(100, 50), lsst.geom.Extent2I(15, 15), lsst.geom.Extent2I(0, 10), 

721 lsst.geom.Extent2I(25, 30), lsst.geom.Extent2I(15, -5), 

722 2*self.smallExposure.getDimensions()] 

723 locations = [("center", self._getExposureCenter(self.smallExposure)), 

724 ("edge", wcs.pixelToSky(lsst.geom.Point2D(0, 0))), 

725 ("rounding test", wcs.pixelToSky(lsst.geom.Point2D(0.2, 0.7))), 

726 ("just inside", wcs.pixelToSky(lsst.geom.Point2D(-0.5 + 1e-4, -0.5 + 1e-4))), 

727 ("just outside", wcs.pixelToSky(lsst.geom.Point2D(-0.5 - 1e-4, -0.5 - 1e-4))), 

728 ("outside", wcs.pixelToSky(lsst.geom.Point2D(-1000, -1000)))] 

729 for cutoutSize in dimensions: 

730 for label, cutoutCenter in locations: 

731 msg = 'Cutout size = %s, location = %s' % (cutoutSize, label) 

732 if "outside" not in label and all(cutoutSize.gt(0)): 

733 cutout = self.smallExposure.getCutout(cutoutCenter, cutoutSize) 

734 centerInPixels = wcs.skyToPixel(cutoutCenter) 

735 precision = (1 + 1e-4)*np.sqrt(0.5)*wcs.getPixelScale(centerInPixels) 

736 self._checkCutoutProperties(cutout, cutoutSize, cutoutCenter, precision, msg) 

737 self._checkCutoutPixels( 

738 cutout, 

739 self._getValidCorners(self.smallExposure.getBBox(), cutout.getBBox()), 

740 msg) 

741 

742 # Need a valid WCS 

743 with self.assertRaises(pexExcept.LogicError, msg=msg): 

744 self.exposureMiOnly.getCutout(cutoutCenter, cutoutSize) 

745 else: 

746 with self.assertRaises(pexExcept.InvalidParameterError, msg=msg): 

747 self.smallExposure.getCutout(cutoutCenter, cutoutSize) 

748 

749 def testGetConvexPolygon(self): 

750 """Test the convex polygon.""" 

751 # Check that we do not have a convex polygon for the plain exposure. 

752 self.assertIsNone(self.exposureMiOnly.convex_polygon) 

753 

754 # Check that all the points in the padded bounding box are in the polygon 

755 bbox = self.exposureMiWcs.getBBox() 

756 # Grow by the default padding. 

757 bbox.grow(10) 

758 x, y = np.meshgrid(np.arange(bbox.getBeginX(), bbox.getEndX(), dtype=np.float64), 

759 np.arange(bbox.getBeginY(), bbox.getEndY(), dtype=np.float64)) 

760 wcs = self.exposureMiWcs.wcs 

761 ra, dec = wcs.pixelToSkyArray(x.ravel(), 

762 y.ravel()) 

763 

764 poly = self.exposureMiWcs.convex_polygon 

765 contains = poly.contains(ra, dec) 

766 np.testing.assert_array_equal(contains, np.ones(len(contains), dtype=bool)) 

767 

768 # Check that points one pixel outside of the bounding box are not in the polygon 

769 bbox.grow(1) 

770 

771 ra, dec = wcs.pixelToSkyArray( 

772 np.linspace(bbox.getBeginX(), bbox.getEndX(), 100), 

773 np.full(100, bbox.getBeginY())) 

774 contains = poly.contains(ra, dec) 

775 np.testing.assert_array_equal(contains, np.zeros(len(contains), dtype=bool)) 

776 

777 ra, dec = wcs.pixelToSkyArray( 

778 np.linspace(bbox.getBeginX(), bbox.getEndX(), 100), 

779 np.full(100, bbox.getEndY())) 

780 contains = poly.contains(ra, dec) 

781 np.testing.assert_array_equal(contains, np.zeros(len(contains), dtype=bool)) 

782 

783 ra, dec = wcs.pixelToSkyArray( 

784 np.full(100, bbox.getBeginX()), 

785 np.linspace(bbox.getBeginY(), bbox.getEndY(), 100)) 

786 contains = poly.contains(ra, dec) 

787 np.testing.assert_array_equal(contains, np.zeros(len(contains), dtype=bool)) 

788 

789 ra, dec = wcs.pixelToSkyArray( 

790 np.full(100, bbox.getEndX()), 

791 np.linspace(bbox.getBeginY(), bbox.getEndY(), 100)) 

792 contains = poly.contains(ra, dec) 

793 np.testing.assert_array_equal(contains, np.zeros(len(contains), dtype=bool)) 

794 

795 def testContainsSkyCoords(self): 

796 """Test the sky coord containment code.""" 

797 self.assertRaisesRegex(ValueError, 

798 "Exposure does not have a valid WCS", 

799 self.exposureMiOnly.containsSkyCoords, 

800 0.0, 

801 0.0) 

802 

803 # Check that all the points within the bounding box are contained 

804 bbox = self.exposureMiWcs.getBBox() 

805 x, y = np.meshgrid(np.arange(bbox.getBeginX() + 1, bbox.getEndX() - 1), 

806 np.arange(bbox.getBeginY() + 1, bbox.getEndY() - 1)) 

807 wcs = self.exposureMiWcs.wcs 

808 ra, dec = wcs.pixelToSkyArray(x.ravel().astype(np.float64), 

809 y.ravel().astype(np.float64)) 

810 

811 contains = self.exposureMiWcs.containsSkyCoords(ra*units.radian, 

812 dec*units.radian) 

813 np.testing.assert_array_equal(contains, np.ones(len(contains), dtype=bool)) 

814 

815 # Same test, everything in degrees. 

816 ra, dec = wcs.pixelToSkyArray(x.ravel().astype(np.float64), 

817 y.ravel().astype(np.float64), 

818 degrees=True) 

819 

820 contains = self.exposureMiWcs.containsSkyCoords(ra*units.degree, 

821 dec*units.degree) 

822 np.testing.assert_array_equal(contains, np.ones(len(contains), dtype=bool)) 

823 

824 # Prepend and append some positions out of the box. 

825 ra = np.concatenate(([300.0], ra, [180.])) 

826 dec = np.concatenate(([50.0], dec, [50.0])) 

827 

828 contains = self.exposureMiWcs.containsSkyCoords(ra*units.degree, 

829 dec*units.degree) 

830 compare = np.ones(len(contains), dtype=bool) 

831 compare[0] = False 

832 compare[-1] = False 

833 np.testing.assert_array_equal(contains, compare) 

834 

835 def _checkCutoutProperties(self, cutout, size, center, precision, msg): 

836 """Test whether a cutout has the desired size and position. 

837 

838 Parameters 

839 ---------- 

840 cutout : `lsst.afw.image.Exposure` 

841 The cutout to test. 

842 size : `lsst.geom.Extent2I` 

843 The expected dimensions of ``cutout``. 

844 center : `lsst.geom.SpherePoint` 

845 The expected center of ``cutout``. 

846 precision : `lsst.geom.Angle` 

847 The precision to which ``center`` must match. 

848 msg : `str` 

849 An error message suffix describing test parameters. 

850 """ 

851 newCenter = self._getExposureCenter(cutout) 

852 self.assertIsNotNone(cutout, msg=msg) 

853 self.assertSpherePointsAlmostEqual(newCenter, center, maxSep=precision, msg=msg) 

854 self.assertEqual(cutout.getWidth(), size[0], msg=msg) 

855 self.assertEqual(cutout.getHeight(), size[1], msg=msg) 

856 

857 def _checkCutoutPixels(self, cutout, validCorners, msg): 

858 """Test whether a cutout has valid/empty pixels where expected. 

859 

860 Parameters 

861 ---------- 

862 cutout : `lsst.afw.image.Exposure` 

863 The cutout to test. 

864 validCorners : iterable of `lsst.geom.Point2I` 

865 The corners of ``cutout`` that should be drawn from the original image. 

866 msg : `str` 

867 An error message suffix describing test parameters. 

868 """ 

869 mask = cutout.getMaskedImage().getMask() 

870 edgeMask = mask.getPlaneBitMask("NO_DATA") 

871 

872 for corner in cutout.getBBox().getCorners(): 

873 maskBitsSet = mask[corner] & edgeMask 

874 if corner in validCorners: 

875 self.assertEqual(maskBitsSet, 0, msg=msg) 

876 else: 

877 self.assertEqual(maskBitsSet, edgeMask, msg=msg) 

878 

879 def _getExposureCenter(self, exposure): 

880 """Return the sky coordinates of an Exposure's center. 

881 

882 Parameters 

883 ---------- 

884 exposure : `lsst.afw.image.Exposure` 

885 The image whose center is desired. 

886 

887 Returns 

888 ------- 

889 center : `lsst.geom.SpherePoint` 

890 The position at the center of ``exposure``. 

891 """ 

892 return exposure.getWcs().pixelToSky(lsst.geom.Box2D(exposure.getBBox()).getCenter()) 

893 

894 def _getValidCorners(self, imageBox, cutoutBox): 

895 """Return the corners of a cutout that are constrained by the original image. 

896 

897 Parameters 

898 ---------- 

899 imageBox: `lsst.geom.Extent2I` 

900 The bounding box of the original image. 

901 cutoutBox : `lsst.geom.Box2I` 

902 The bounding box of the cutout. 

903 

904 Returns 

905 ------- 

906 corners : iterable of `lsst.geom.Point2I` 

907 The corners that are drawn from the original image. 

908 """ 

909 return [corner for corner in cutoutBox.getCorners() if corner in imageBox] 

910 

911 

912class ExposureInfoTestCase(lsst.utils.tests.TestCase): 

913 def setUp(self): 

914 super().setUp() 

915 

916 self.wcs = afwGeom.makeSkyWcs(lsst.geom.Point2D(0.0, 0.0), 

917 lsst.geom.SpherePoint(2.0, 34.0, lsst.geom.degrees), 

918 np.identity(2), 

919 ) 

920 self.photoCalib = afwImage.PhotoCalib(1.5) 

921 self.psf = DummyPsf(2.0) 

922 self.detector = DetectorWrapper().detector 

923 self.summaryStats = afwImage.ExposureSummaryStats(ra=100.0) 

924 self.polygon = afwGeom.Polygon(lsst.geom.Box2D(lsst.geom.Point2D(0.0, 0.0), 

925 lsst.geom.Point2D(25.0, 20.0))) 

926 self.coaddInputs = afwImage.CoaddInputs() 

927 self.apCorrMap = afwImage.ApCorrMap() 

928 self.transmissionCurve = afwImage.TransmissionCurve.makeIdentity() 

929 

930 self.exposureInfo = afwImage.ExposureInfo() 

931 self.gFilterLabel = afwImage.FilterLabel(band="g") 

932 self.exposureId = 42 

933 

934 def _checkAlias(self, exposureInfo, key, value, has, get): 

935 self.assertFalse(has()) 

936 self.assertFalse(exposureInfo.hasComponent(key)) 

937 self.assertIsNone(get()) 

938 self.assertIsNone(exposureInfo.getComponent(key)) 

939 

940 self.exposureInfo.setComponent(key, value) 

941 self.assertTrue(has()) 

942 self.assertTrue(exposureInfo.hasComponent(key)) 

943 self.assertIsNotNone(get()) 

944 self.assertIsNotNone(exposureInfo.getComponent(key)) 

945 self.assertEqual(get(), value) 

946 self.assertEqual(exposureInfo.getComponent(key), value) 

947 

948 self.exposureInfo.removeComponent(key) 

949 self.assertFalse(has()) 

950 self.assertFalse(exposureInfo.hasComponent(key)) 

951 self.assertIsNone(get()) 

952 self.assertIsNone(exposureInfo.getComponent(key)) 

953 

954 def testAliases(self): 

955 cls = type(self.exposureInfo) 

956 self._checkAlias(self.exposureInfo, cls.KEY_WCS, self.wcs, 

957 self.exposureInfo.hasWcs, self.exposureInfo.getWcs) 

958 self._checkAlias(self.exposureInfo, cls.KEY_PSF, self.psf, 

959 self.exposureInfo.hasPsf, self.exposureInfo.getPsf) 

960 self._checkAlias(self.exposureInfo, cls.KEY_PHOTO_CALIB, self.photoCalib, 

961 self.exposureInfo.hasPhotoCalib, self.exposureInfo.getPhotoCalib) 

962 self._checkAlias(self.exposureInfo, cls.KEY_DETECTOR, self.detector, 

963 self.exposureInfo.hasDetector, self.exposureInfo.getDetector) 

964 self._checkAlias(self.exposureInfo, cls.KEY_VALID_POLYGON, self.polygon, 

965 self.exposureInfo.hasValidPolygon, self.exposureInfo.getValidPolygon) 

966 self._checkAlias(self.exposureInfo, cls.KEY_COADD_INPUTS, self.coaddInputs, 

967 self.exposureInfo.hasCoaddInputs, self.exposureInfo.getCoaddInputs) 

968 self._checkAlias(self.exposureInfo, cls.KEY_AP_CORR_MAP, self.apCorrMap, 

969 self.exposureInfo.hasApCorrMap, self.exposureInfo.getApCorrMap) 

970 self._checkAlias(self.exposureInfo, cls.KEY_TRANSMISSION_CURVE, self.transmissionCurve, 

971 self.exposureInfo.hasTransmissionCurve, self.exposureInfo.getTransmissionCurve) 

972 self._checkAlias(self.exposureInfo, cls.KEY_SUMMARY_STATS, self.summaryStats, 

973 self.exposureInfo.hasSummaryStats, self.exposureInfo.getSummaryStats) 

974 self._checkAlias(self.exposureInfo, cls.KEY_FILTER, self.gFilterLabel, 

975 self.exposureInfo.hasFilter, self.exposureInfo.getFilter) 

976 

977 def testId(self): 

978 self.exposureInfo.setVisitInfo(afwImage.VisitInfo()) 

979 

980 self.assertFalse(self.exposureInfo.hasId()) 

981 self.assertIsNone(self.exposureInfo.getId()) 

982 self.assertIsNone(self.exposureInfo.id) 

983 

984 self.exposureInfo.setId(self.exposureId) 

985 self.assertTrue(self.exposureInfo.hasId()) 

986 self.assertIsNotNone(self.exposureInfo.getId()) 

987 self.assertIsNotNone(self.exposureInfo.id) 

988 self.assertEqual(self.exposureInfo.getId(), self.exposureId) 

989 self.assertEqual(self.exposureInfo.id, self.exposureId) 

990 

991 self.exposureInfo.id = 99899 

992 self.assertEqual(self.exposureInfo.getId(), 99899) 

993 

994 self.exposureInfo.id = None 

995 self.assertFalse(self.exposureInfo.hasId()) 

996 self.assertIsNone(self.exposureInfo.getId()) 

997 self.assertIsNone(self.exposureInfo.id) 

998 

999 def testCopy(self): 

1000 # Test that ExposureInfos have independently settable state 

1001 copy = afwImage.ExposureInfo(self.exposureInfo, True) 

1002 self.assertEqual(self.exposureInfo.getWcs(), copy.getWcs()) 

1003 

1004 newWcs = afwGeom.makeSkyWcs(lsst.geom.Point2D(-23.0, 8.0), 

1005 lsst.geom.SpherePoint(0.0, 0.0, lsst.geom.degrees), 

1006 np.identity(2), 

1007 ) 

1008 copy.setWcs(newWcs) 

1009 self.assertEqual(copy.getWcs(), newWcs) 

1010 self.assertNotEqual(self.exposureInfo.getWcs(), copy.getWcs()) 

1011 

1012 def testMissingProperties(self): 

1013 # Test that invalid properties return None instead of raising 

1014 exposureInfo = afwImage.ExposureInfo() 

1015 

1016 self.assertIsNone(exposureInfo.id) 

1017 

1018 

1019class ExposureNoAfwdataTestCase(lsst.utils.tests.TestCase): 

1020 """Tests of Exposure that don't require afwdata. 

1021 

1022 These tests use the trivial exposures written to ``afw/tests/data``. 

1023 """ 

1024 def setUp(self): 

1025 self.dataDir = os.path.join(os.path.split(__file__)[0], "data") 

1026 

1027 # Check the values below against what was written by comparing with 

1028 # the code in `afw/tests/data/makeTestExposure.py` 

1029 nx = ny = 10 

1030 image = afwImage.ImageF(np.arange(nx*ny, dtype='f').reshape(nx, ny)) 

1031 variance = afwImage.ImageF(np.ones((nx, ny), dtype='f')) 

1032 mask = afwImage.MaskX(nx, ny) 

1033 mask.array[5, 5] = 5 

1034 self.maskedImage = afwImage.MaskedImageF(image, mask, variance) 

1035 self.exposureId = 12345 

1036 

1037 self.v0PhotoCalib = afwImage.makePhotoCalibFromCalibZeroPoint(1e6, 2e4) 

1038 self.v1PhotoCalib = afwImage.PhotoCalib(1e6, 2e4) 

1039 self.v1FilterLabel = afwImage.FilterLabel(physical="ha") 

1040 self.v2FilterLabel = afwImage.FilterLabel(band="N656", physical="ha") 

1041 

1042 def testReadUnversioned(self): 

1043 """Test that we can read an unversioned (implicit verison 0) file. 

1044 """ 

1045 filename = os.path.join(self.dataDir, "exposure-noversion.fits") 

1046 exposure = afwImage.ExposureF.readFits(filename) 

1047 

1048 self.assertMaskedImagesEqual(exposure.maskedImage, self.maskedImage) 

1049 

1050 self.assertEqual(exposure.info.id, self.exposureId) 

1051 self.assertEqual(exposure.getPhotoCalib(), self.v0PhotoCalib) 

1052 self.assertEqual(exposure.getFilter(), self.v1FilterLabel) 

1053 

1054 def testReadVersion0(self): 

1055 """Test that we can read a version 0 file. 

1056 This file should be identical to the unversioned one, except that it 

1057 is marked as ExposureInfo version 0 in the header. 

1058 """ 

1059 filename = os.path.join(self.dataDir, "exposure-version-0.fits") 

1060 exposure = afwImage.ExposureF.readFits(filename) 

1061 

1062 self.assertMaskedImagesEqual(exposure.maskedImage, self.maskedImage) 

1063 

1064 self.assertEqual(exposure.info.id, self.exposureId) 

1065 self.assertEqual(exposure.getPhotoCalib(), self.v0PhotoCalib) 

1066 self.assertEqual(exposure.getFilter(), self.v1FilterLabel) 

1067 

1068 # Check that the metadata reader parses the file correctly 

1069 reader = afwImage.ExposureFitsReader(filename) 

1070 self.assertEqual(reader.readExposureInfo().getPhotoCalib(), self.v0PhotoCalib) 

1071 self.assertEqual(reader.readPhotoCalib(), self.v0PhotoCalib) 

1072 

1073 def testReadVersion1(self): 

1074 """Test that we can read a version 1 file. 

1075 Version 1 replaced Calib with PhotoCalib. 

1076 """ 

1077 filename = os.path.join(self.dataDir, "exposure-version-1.fits") 

1078 exposure = afwImage.ExposureF.readFits(filename) 

1079 

1080 self.assertMaskedImagesEqual(exposure.maskedImage, self.maskedImage) 

1081 

1082 self.assertEqual(exposure.info.id, self.exposureId) 

1083 self.assertEqual(exposure.getPhotoCalib(), self.v1PhotoCalib) 

1084 self.assertEqual(exposure.getFilter(), self.v1FilterLabel) 

1085 

1086 # Check that the metadata reader parses the file correctly 

1087 reader = afwImage.ExposureFitsReader(filename) 

1088 self.assertEqual(reader.readExposureInfo().getPhotoCalib(), self.v1PhotoCalib) 

1089 self.assertEqual(reader.readPhotoCalib(), self.v1PhotoCalib) 

1090 

1091 def testReadVersion2(self): 

1092 """Test that we can read a version 2 file. 

1093 Version 2 replaced Filter with FilterLabel. 

1094 """ 

1095 filename = os.path.join(self.dataDir, "exposure-version-2.fits") 

1096 exposure = afwImage.ExposureF.readFits(filename) 

1097 

1098 self.assertMaskedImagesEqual(exposure.maskedImage, self.maskedImage) 

1099 

1100 self.assertEqual(exposure.info.id, self.exposureId) 

1101 self.assertEqual(exposure.getPhotoCalib(), self.v1PhotoCalib) 

1102 self.assertEqual(exposure.getFilter(), self.v2FilterLabel) 

1103 

1104 # Check that the metadata reader parses the file correctly 

1105 reader = afwImage.ExposureFitsReader(filename) 

1106 self.assertEqual(reader.readExposureInfo().getPhotoCalib(), self.v1PhotoCalib) 

1107 self.assertEqual(reader.readPhotoCalib(), self.v1PhotoCalib) 

1108 

1109 def testExposureSummaryExtraComponents(self): 

1110 """Test that we can read an exposure summary with extra components. 

1111 """ 

1112 testDict = {'ra': 0.0, 

1113 'dec': 0.0, 

1114 'nonsense': 1.0} 

1115 bytes = yaml.dump(testDict, encoding='utf-8') 

1116 with self.assertWarns(FutureWarning): 

1117 summaryStats = lsst.afw.image.ExposureSummaryStats._read(bytes) 

1118 

1119 self.assertEqual(summaryStats.ra, testDict['ra']) 

1120 self.assertEqual(summaryStats.dec, testDict['dec']) 

1121 

1122 def testExposureSummarySchema(self): 

1123 """Test that we can make a schema for an exposure summary and populate 

1124 records with that schema. 

1125 """ 

1126 schema = afwTable.Schema() 

1127 afwImage.ExposureSummaryStats.update_schema(schema) 

1128 self.maxDiff = None 

1129 self.assertEqual( 

1130 {field.name for field in dataclasses.fields(afwImage.ExposureSummaryStats)}, 

1131 set(schema.getNames()) | {"version"}, 

1132 ) 

1133 catalog = afwTable.BaseCatalog(schema) 

1134 summary1 = afwImage.ExposureSummaryStats() 

1135 for n, field in enumerate(dataclasses.fields(afwImage.ExposureSummaryStats)): 

1136 # Set fields to deterministic, distinct, but arbitrary values. 

1137 if field.type == "float": 

1138 setattr(summary1, field.name, float(0.5**n)) 

1139 elif field.type == "int": 

1140 setattr(summary1, field.name, 10*n) 

1141 elif field.type == "list[float]": 

1142 setattr(summary1, field.name, [n + 0.1, n + 0.2, n + 0.3, n + 0.4]) 

1143 else: 

1144 raise TypeError(f"Unexpected type: {field.type!r}.") 

1145 record = catalog.addNew() 

1146 summary1.update_record(record) 

1147 summary2 = afwImage.ExposureSummaryStats.from_record(record) 

1148 self.assertEqual(summary1, summary2) 

1149 

1150 

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

1152 pass 

1153 

1154 

1155def setup_module(module): 

1156 lsst.utils.tests.init() 

1157 

1158 

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

1160 lsst.utils.tests.init() 

1161 unittest.main()