Coverage for tests/test_exposure.py: 11%

672 statements  

« prev     ^ index     » next       coverage.py v7.2.1, created at 2023-03-12 20:52 -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 exposureId = 5 

285 exposureTime = 12.3 

286 boresightRotAngle = 45.6 * lsst.geom.degrees 

287 weather = Weather(1.1, 2.2, 0.3) 

288 visitInfo = afwImage.VisitInfo( 

289 exposureId=exposureId, 

290 exposureTime=exposureTime, 

291 boresightRotAngle=boresightRotAngle, 

292 weather=weather, 

293 ) 

294 photoCalib = afwImage.PhotoCalib(3.4, 5.6) 

295 exposureInfo = afwImage.ExposureInfo() 

296 exposureInfo.setVisitInfo(visitInfo) 

297 exposureInfo.setPhotoCalib(photoCalib) 

298 exposureInfo.setDetector(self.detector) 

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

300 exposureInfo.setFilter(gFilterLabel) 

301 maskedImage = afwImage.MaskedImageF(inFilePathSmall) 

302 exposure = afwImage.ExposureF(maskedImage, exposureInfo) 

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

304 exposure.writeFits(tmpFile) 

305 rtExposure = afwImage.ExposureF(tmpFile) 

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

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

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

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

310 

311 # Test property getters. 

312 self.assertEqual(rtExposure.photoCalib, photoCalib) 

313 self.assertEqual(rtExposure.filter, gFilterLabel) 

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

315 self.assertIsNotNone(rtExposure.visitInfo) 

316 

317 def testSetMembers(self): 

318 """ 

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

320 """ 

321 exposure = afwImage.ExposureF() 

322 

323 maskedImage = afwImage.MaskedImageF(inFilePathSmall) 

324 exposure.setMaskedImage(maskedImage) 

325 exposure.setWcs(self.wcs) 

326 exposure.setDetector(self.detector) 

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

328 

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

330 self.detector.getName()) 

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

332 self.detector.getSerial()) 

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

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

335 

336 # The PhotoCalib tests are in test_photoCalib.py; 

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

338 self.assertIsNone(exposure.getPhotoCalib()) 

339 

340 photoCalib = afwImage.PhotoCalib(511.1, 44.4) 

341 exposure.setPhotoCalib(photoCalib) 

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

343 

344 # Psfs next 

345 self.assertFalse(exposure.hasPsf()) 

346 exposure.setPsf(self.psf) 

347 self.assertTrue(exposure.hasPsf()) 

348 

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

350 

351 # extras next 

352 info = exposure.getInfo() 

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

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

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

356 info.setComponent(key, value) 

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

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

359 info.removeComponent(key) 

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

361 

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

363 # that already has both 

364 self.exposureMiWcs.setMaskedImage(maskedImage) 

365 exposure.setWcs(self.wcs) 

366 

367 def testHasWcs(self): 

368 """ 

369 Test if an Exposure has a WCS or not. 

370 """ 

371 self.assertFalse(self.exposureBlank.hasWcs()) 

372 

373 self.assertFalse(self.exposureMiOnly.hasWcs()) 

374 self.assertTrue(self.exposureMiWcs.hasWcs()) 

375 self.assertTrue(self.exposureCrWcs.hasWcs()) 

376 self.assertFalse(self.exposureCrOnly.hasWcs()) 

377 

378 def testGetSubExposure(self): 

379 """ 

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

381 

382 The MaskedImage class should throw a 

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

384 subRegion is not fully contained within the original 

385 MaskedImage. 

386 

387 """ 

388 # 

389 # This subExposure is valid 

390 # 

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

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

393 subExposure = self.exposureCrWcs.Factory( 

394 self.exposureCrWcs, subBBox, afwImage.LOCAL) 

395 

396 self.checkWcs(self.exposureCrWcs, subExposure) 

397 

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

399 # from the MaskedImage class and should trigger an exception 

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

401 

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

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

404 

405 def getSubRegion(): 

406 self.exposureCrWcs.Factory( 

407 self.exposureCrWcs, subRegion3, afwImage.LOCAL) 

408 

409 self.assertRaises(pexExcept.LengthError, getSubRegion) 

410 

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

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

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

414 

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

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

417 

418 def getSubRegion(): 

419 self.exposureCrWcs.Factory( 

420 self.exposureCrWcs, subRegion4, afwImage.LOCAL) 

421 

422 self.assertRaises(pexExcept.LengthError, getSubRegion) 

423 

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

425 # transformation 

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

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

428 subExposure = self.exposureCrWcs.Factory( 

429 self.exposureCrWcs, subBBox, afwImage.LOCAL) 

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

431 

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

433 

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

435 

436 def testReadWriteFits(self): 

437 """Test readFits and writeFits. 

438 """ 

439 # This should pass without an exception 

440 mainExposure = afwImage.ExposureF(inFilePathSmall) 

441 mainExposure.info.setId(self.id) 

442 mainExposure.setDetector(self.detector) 

443 

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

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

446 subExposure = mainExposure.Factory( 

447 mainExposure, subBBox, afwImage.LOCAL) 

448 self.checkWcs(mainExposure, subExposure) 

449 det = subExposure.getDetector() 

450 self.assertTrue(det) 

451 

452 subExposure = afwImage.ExposureF( 

453 inFilePathSmall, subBBox, afwImage.LOCAL) 

454 

455 self.checkWcs(mainExposure, subExposure) 

456 

457 # This should throw an exception 

458 def getExposure(): 

459 afwImage.ExposureF(inFilePathSmallImage) 

460 

461 self.assertRaises(FitsError, getExposure) 

462 

463 mainExposure.setPsf(self.psf) 

464 

465 # Make sure we can write without an exception 

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

467 mainExposure.setPhotoCalib(photoCalib) 

468 

469 mainInfo = mainExposure.getInfo() 

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

471 mainInfo.setComponent(key, value) 

472 

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

474 mainExposure.writeFits(tmpFile) 

475 

476 readExposure = type(mainExposure)(tmpFile) 

477 

478 # 

479 # Check the round-tripping 

480 # 

481 self.assertIsNotNone(mainExposure.getFilter()) 

482 self.assertEqual(mainExposure.getFilter(), 

483 readExposure.getFilter()) 

484 

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

486 

487 readInfo = readExposure.getInfo() 

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

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

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

491 

492 psf = readExposure.getPsf() 

493 self.assertIsNotNone(psf) 

494 self.assertEqual(psf, self.psf) 

495 # check psf property getter 

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

497 

498 def checkWcs(self, parentExposure, subExposure): 

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

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

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

502 to get the same sky coordinates for each. 

503 """ 

504 subMI = subExposure.getMaskedImage() 

505 subDim = subMI.getDimensions() 

506 

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

508 # with WCS 

509 mainWcs = parentExposure.getWcs() 

510 subWcs = subExposure.getWcs() 

511 

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

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

514 self.assertSpherePointsAlmostEqual( 

515 mainWcs.pixelToSky( 

516 afwImage.indexToPosition(xSubInd), 

517 afwImage.indexToPosition(ySubInd), 

518 ), 

519 subWcs.pixelToSky( 

520 afwImage.indexToPosition(xSubInd), 

521 afwImage.indexToPosition(ySubInd), 

522 )) 

523 

524 def cmpExposure(self, e1, e2): 

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

526 e2.getDetector().getName()) 

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

528 e2.getDetector().getSerial()) 

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

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

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

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

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

534 # check PSF identity 

535 if not e1.getPsf(): 

536 self.assertFalse(e2.getPsf()) 

537 else: 

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

539 # Check extra components 

540 i1 = e1.getInfo() 

541 i2 = e2.getInfo() 

542 for key in self.extras: 

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

544 if i1.hasComponent(key): 

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

546 

547 def testCopyExposure(self): 

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

549 

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

551 exposureU.setWcs(self.wcs) 

552 exposureU.setDetector(self.detector) 

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

554 exposureU.setPsf(DummyPsf(4.0)) 

555 infoU = exposureU.getInfo() 

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

557 infoU.setComponent(key, value) 

558 

559 exposureF = exposureU.convertF() 

560 self.cmpExposure(exposureF, exposureU) 

561 

562 nexp = exposureF.Factory(exposureF, False) 

563 self.cmpExposure(exposureF, nexp) 

564 

565 # Ensure that the copy was deep. 

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

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

568 # x0,y0 = cen0 

569 # det = exposureF.getDetector() 

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

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

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

573 

574 def testDeepCopyData(self): 

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

576 """ 

577 exp = afwImage.ExposureF(6, 7) 

578 mi = exp.getMaskedImage() 

579 mi.getImage().set(100) 

580 mi.getMask().set(5) 

581 mi.getVariance().set(200) 

582 

583 expCopy = exp.clone() 

584 miCopy = expCopy.getMaskedImage() 

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

586 miCopy.getMask().set(2) 

587 miCopy.getVariance().set(175) 

588 

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

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

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

592 

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

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

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

596 

597 def testDeepCopySubData(self): 

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

599 """ 

600 exp = afwImage.ExposureF(6, 7) 

601 mi = exp.getMaskedImage() 

602 mi.getImage().set(100) 

603 mi.getMask().set(5) 

604 mi.getVariance().set(200) 

605 

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

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

608 miCopy = expCopy.getMaskedImage() 

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

610 miCopy.getMask().set(2) 

611 miCopy.getVariance().set(175) 

612 

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

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

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

616 

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

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

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

620 

621 def testDeepCopyMetadata(self): 

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

623 """ 

624 exp = afwImage.ExposureF(10, 10) 

625 expMeta = exp.getMetadata() 

626 expMeta.set("foo", 5) 

627 expCopy = exp.clone() 

628 expCopyMeta = expCopy.getMetadata() 

629 expCopyMeta.set("foo", 6) 

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

631 # this will fail if the bug is present 

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

633 

634 def testDeepCopySubMetadata(self): 

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

636 """ 

637 exp = afwImage.ExposureF(10, 10) 

638 expMeta = exp.getMetadata() 

639 expMeta.set("foo", 5) 

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

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

642 expCopyMeta = expCopy.getMetadata() 

643 expCopyMeta.set("foo", 6) 

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

645 # this will fail if the bug is present 

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

647 

648 def testMakeExposureLeaks(self): 

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

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

651 afwImage.makeExposure(afwImage.makeMaskedImage( 

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

653 

654 def testImageSlices(self): 

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

656 exp = afwImage.ExposureF(10, 20) 

657 mi = exp.getMaskedImage() 

658 mi.image[9, 19] = 10 

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

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

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

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

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

664 self.assertEqual(sexp.getDimensions(), 

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

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

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

668 

669 def testConversionToScalar(self): 

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

671 im = afwImage.ExposureF(10, 20) 

672 

673 # only single pixel images may be converted 

674 self.assertRaises(TypeError, float, im) 

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

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

677 

678 def testReadMetadata(self): 

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

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

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

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

683 self.exposureCrWcs.writeFits(tmpFile) 

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

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

686 # frazzle and the Wcs from the PropertySet returned by 

687 # testReadMetadata. 

688 md = readMetadata(tmpFile) 

689 wcs = afwGeom.makeSkyWcs(md, False) 

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

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

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

693 frazzle = md.getScalar("FRAZZLE") 

694 self.assertTrue(frazzle) 

695 

696 def testArchiveKeys(self): 

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

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

699 exposure1.setPsf(self.psf) 

700 exposure1.writeFits(tmpFile) 

701 exposure2 = afwImage.ExposureF(tmpFile) 

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

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

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

705 

706 def testTicket2861(self): 

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

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

709 exposure1.setPsf(self.psf) 

710 schema = afwTable.ExposureTable.makeMinimalSchema() 

711 coaddInputs = afwImage.CoaddInputs(schema, schema) 

712 exposure1.getInfo().setCoaddInputs(coaddInputs) 

713 exposure2 = afwImage.ExposureF(exposure1, True) 

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

715 exposure2.writeFits(tmpFile) 

716 exposure3 = afwImage.ExposureF(tmpFile) 

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

718 

719 def testGetCutout(self): 

720 wcs = self.smallExposure.getWcs() 

721 

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

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

724 2*self.smallExposure.getDimensions()] 

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

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

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

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

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

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

731 for cutoutSize in dimensions: 

732 for label, cutoutCenter in locations: 

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

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

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

736 centerInPixels = wcs.skyToPixel(cutoutCenter) 

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

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

739 self._checkCutoutPixels( 

740 cutout, 

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

742 msg) 

743 

744 # Need a valid WCS 

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

746 self.exposureMiOnly.getCutout(cutoutCenter, cutoutSize) 

747 else: 

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

749 self.smallExposure.getCutout(cutoutCenter, cutoutSize) 

750 

751 def testGetConvexPolygon(self): 

752 """Test the convex polygon.""" 

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

754 self.assertIsNone(self.exposureMiOnly.convex_polygon) 

755 

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

757 bbox = self.exposureMiWcs.getBBox() 

758 # Grow by the default padding. 

759 bbox.grow(10) 

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

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

762 wcs = self.exposureMiWcs.wcs 

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

764 y.ravel()) 

765 

766 poly = self.exposureMiWcs.convex_polygon 

767 contains = poly.contains(ra, dec) 

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

769 

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

771 bbox.grow(1) 

772 

773 ra, dec = wcs.pixelToSkyArray( 

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

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

776 contains = poly.contains(ra, dec) 

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

778 

779 ra, dec = wcs.pixelToSkyArray( 

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

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

782 contains = poly.contains(ra, dec) 

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

784 

785 ra, dec = wcs.pixelToSkyArray( 

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

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

788 contains = poly.contains(ra, dec) 

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

790 

791 ra, dec = wcs.pixelToSkyArray( 

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

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

794 contains = poly.contains(ra, dec) 

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

796 

797 def testContainsSkyCoords(self): 

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

799 self.assertRaisesRegex(ValueError, 

800 "Exposure does not have a valid WCS", 

801 self.exposureMiOnly.containsSkyCoords, 

802 0.0, 

803 0.0) 

804 

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

806 bbox = self.exposureMiWcs.getBBox() 

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

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

809 wcs = self.exposureMiWcs.wcs 

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

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

812 

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

814 dec*units.radian) 

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

816 

817 # Same test, everything in degrees. 

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

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

820 degrees=True) 

821 

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

823 dec*units.degree) 

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

825 

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

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

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

829 

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

831 dec*units.degree) 

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

833 compare[0] = False 

834 compare[-1] = False 

835 np.testing.assert_array_equal(contains, compare) 

836 

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

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

839 

840 Parameters 

841 ---------- 

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

843 The cutout to test. 

844 size : `lsst.geom.Extent2I` 

845 The expected dimensions of ``cutout``. 

846 center : `lsst.geom.SpherePoint` 

847 The expected center of ``cutout``. 

848 precision : `lsst.geom.Angle` 

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

850 msg : `str` 

851 An error message suffix describing test parameters. 

852 """ 

853 newCenter = self._getExposureCenter(cutout) 

854 self.assertIsNotNone(cutout, msg=msg) 

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

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

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

858 

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

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

861 

862 Parameters 

863 ---------- 

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

865 The cutout to test. 

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

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

868 msg : `str` 

869 An error message suffix describing test parameters. 

870 """ 

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

872 edgeMask = mask.getPlaneBitMask("NO_DATA") 

873 

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

875 maskBitsSet = mask[corner] & edgeMask 

876 if corner in validCorners: 

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

878 else: 

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

880 

881 def _getExposureCenter(self, exposure): 

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

883 

884 Parameters 

885 ---------- 

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

887 The image whose center is desired. 

888 

889 Returns 

890 ------- 

891 center : `lsst.geom.SpherePoint` 

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

893 """ 

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

895 

896 def _getValidCorners(self, imageBox, cutoutBox): 

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

898 

899 Parameters 

900 ---------- 

901 imageBox: `lsst.geom.Extent2I` 

902 The bounding box of the original image. 

903 cutoutBox : `lsst.geom.Box2I` 

904 The bounding box of the cutout. 

905 

906 Returns 

907 ------- 

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

909 The corners that are drawn from the original image. 

910 """ 

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

912 

913 

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

915 def setUp(self): 

916 super().setUp() 

917 

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

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

920 np.identity(2), 

921 ) 

922 self.photoCalib = afwImage.PhotoCalib(1.5) 

923 self.psf = DummyPsf(2.0) 

924 self.detector = DetectorWrapper().detector 

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

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

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

928 self.coaddInputs = afwImage.CoaddInputs() 

929 self.apCorrMap = afwImage.ApCorrMap() 

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

931 

932 self.exposureInfo = afwImage.ExposureInfo() 

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

934 self.exposureId = 42 

935 

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

937 self.assertFalse(has()) 

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

939 self.assertIsNone(get()) 

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

941 

942 self.exposureInfo.setComponent(key, value) 

943 self.assertTrue(has()) 

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

945 self.assertIsNotNone(get()) 

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

947 self.assertEqual(get(), value) 

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

949 

950 self.exposureInfo.removeComponent(key) 

951 self.assertFalse(has()) 

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

953 self.assertIsNone(get()) 

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

955 

956 def testAliases(self): 

957 cls = type(self.exposureInfo) 

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

959 self.exposureInfo.hasWcs, self.exposureInfo.getWcs) 

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

961 self.exposureInfo.hasPsf, self.exposureInfo.getPsf) 

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

963 self.exposureInfo.hasPhotoCalib, self.exposureInfo.getPhotoCalib) 

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

965 self.exposureInfo.hasDetector, self.exposureInfo.getDetector) 

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

967 self.exposureInfo.hasValidPolygon, self.exposureInfo.getValidPolygon) 

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

969 self.exposureInfo.hasCoaddInputs, self.exposureInfo.getCoaddInputs) 

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

971 self.exposureInfo.hasApCorrMap, self.exposureInfo.getApCorrMap) 

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

973 self.exposureInfo.hasTransmissionCurve, self.exposureInfo.getTransmissionCurve) 

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

975 self.exposureInfo.hasSummaryStats, self.exposureInfo.getSummaryStats) 

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

977 self.exposureInfo.hasFilter, self.exposureInfo.getFilter) 

978 

979 def testId(self): 

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

981 

982 self.assertFalse(self.exposureInfo.hasId()) 

983 self.assertIsNone(self.exposureInfo.getId()) 

984 with self.assertWarns(FutureWarning): 

985 self.assertEqual(self.exposureInfo.getVisitInfo().getExposureId(), 0) 

986 self.assertIsNone(self.exposureInfo.id) 

987 

988 self.exposureInfo.setId(self.exposureId) 

989 self.assertTrue(self.exposureInfo.hasId()) 

990 self.assertIsNotNone(self.exposureInfo.getId()) 

991 self.assertIsNotNone(self.exposureInfo.id) 

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

993 with self.assertWarns(FutureWarning): 

994 self.assertEqual(self.exposureInfo.getVisitInfo().getExposureId(), self.exposureId) 

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

996 

997 self.exposureInfo.id = 99899 

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

999 

1000 self.exposureInfo.setVisitInfo(afwImage.VisitInfo(exposureId=12321)) 

1001 self.assertEqual(self.exposureInfo.id, 12321) 

1002 

1003 self.exposureInfo.id = None 

1004 self.assertFalse(self.exposureInfo.hasId()) 

1005 self.assertIsNone(self.exposureInfo.getId()) 

1006 self.assertIsNone(self.exposureInfo.id) 

1007 

1008 def testCopy(self): 

1009 # Test that ExposureInfos have independently settable state 

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

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

1012 

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

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

1015 np.identity(2), 

1016 ) 

1017 copy.setWcs(newWcs) 

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

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

1020 

1021 def testMissingProperties(self): 

1022 # Test that invalid properties return None instead of raising 

1023 exposureInfo = afwImage.ExposureInfo() 

1024 

1025 self.assertIsNone(exposureInfo.id) 

1026 

1027 

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

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

1030 

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

1032 """ 

1033 def setUp(self): 

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

1035 

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

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

1038 nx = ny = 10 

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

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

1041 mask = afwImage.MaskX(nx, ny) 

1042 mask.array[5, 5] = 5 

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

1044 self.exposureId = 12345 

1045 

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

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

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

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

1050 

1051 def testReadUnversioned(self): 

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

1053 """ 

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

1055 exposure = afwImage.ExposureF.readFits(filename) 

1056 

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

1058 

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

1060 with self.assertWarns(FutureWarning): 

1061 self.assertEqual(exposure.info.getVisitInfo().getExposureId(), self.exposureId) 

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

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

1064 

1065 def testReadVersion0(self): 

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

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

1068 is marked as ExposureInfo version 0 in the header. 

1069 """ 

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

1071 exposure = afwImage.ExposureF.readFits(filename) 

1072 

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

1074 

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

1076 with self.assertWarns(FutureWarning): 

1077 self.assertEqual(exposure.info.getVisitInfo().getExposureId(), self.exposureId) 

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

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

1080 

1081 # Check that the metadata reader parses the file correctly 

1082 reader = afwImage.ExposureFitsReader(filename) 

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

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

1085 

1086 def testReadVersion1(self): 

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

1088 Version 1 replaced Calib with PhotoCalib. 

1089 """ 

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

1091 exposure = afwImage.ExposureF.readFits(filename) 

1092 

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

1094 

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

1096 with self.assertWarns(FutureWarning): 

1097 self.assertEqual(exposure.info.getVisitInfo().getExposureId(), self.exposureId) 

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

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

1100 

1101 # Check that the metadata reader parses the file correctly 

1102 reader = afwImage.ExposureFitsReader(filename) 

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

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

1105 

1106 def testReadVersion2(self): 

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

1108 Version 2 replaced Filter with FilterLabel. 

1109 """ 

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

1111 exposure = afwImage.ExposureF.readFits(filename) 

1112 

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

1114 

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

1116 with self.assertWarns(FutureWarning): 

1117 self.assertEqual(exposure.info.getVisitInfo().getExposureId(), self.exposureId) 

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

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

1120 

1121 # Check that the metadata reader parses the file correctly 

1122 reader = afwImage.ExposureFitsReader(filename) 

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

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

1125 

1126 def testExposureSummaryExtraComponents(self): 

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

1128 """ 

1129 testDict = {'ra': 0.0, 

1130 'decl': 0.0, 

1131 'nonsense': 1.0} 

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

1133 with self.assertWarns(FutureWarning): 

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

1135 

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

1137 self.assertEqual(summaryStats.decl, testDict['decl']) 

1138 

1139 def testExposureSummarySchema(self): 

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

1141 records with that schema. 

1142 """ 

1143 schema = afwTable.Schema() 

1144 afwImage.ExposureSummaryStats.update_schema(schema) 

1145 self.maxDiff = None 

1146 self.assertEqual( 

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

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

1149 ) 

1150 catalog = afwTable.BaseCatalog(schema) 

1151 summary1 = afwImage.ExposureSummaryStats() 

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

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

1154 if field.type == "float": 

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

1156 elif field.type == "int": 

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

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

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

1160 else: 

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

1162 record = catalog.addNew() 

1163 summary1.update_record(record) 

1164 summary2 = afwImage.ExposureSummaryStats.from_record(record) 

1165 self.assertEqual(summary1, summary2) 

1166 

1167 

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

1169 pass 

1170 

1171 

1172def setup_module(module): 

1173 lsst.utils.tests.init() 

1174 

1175 

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

1177 lsst.utils.tests.init() 

1178 unittest.main()