Coverage for tests/test_exposure.py: 11%

701 statements  

« prev     ^ index     » next       coverage.py v7.5.1, created at 2024-05-16 03:19 -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 

29import warnings 

30 

31import numpy as np 

32from numpy.testing import assert_allclose 

33import yaml 

34import astropy.units as units 

35 

36import lsst.utils 

37import lsst.utils.tests 

38import lsst.geom 

39import lsst.afw.image as afwImage 

40from lsst.afw.coord import Weather 

41import lsst.afw.geom as afwGeom 

42import lsst.afw.table as afwTable 

43import lsst.pex.exceptions as pexExcept 

44from lsst.afw.fits import readMetadata, FitsError 

45from lsst.afw.cameraGeom.testUtils import DetectorWrapper 

46from lsst.daf.base import PropertyList 

47from lsst.log import Log 

48from testTableArchivesLib import DummyPsf 

49 

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

51 

52try: 

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

54except LookupError: 

55 dataDir = None 

56else: 

57 InputMaskedImageName = "871034p_1_MI.fits" 

58 InputMaskedImageNameSmall = "small_MI.fits" 

59 InputImageNameSmall = "small" 

60 OutputMaskedImageName = "871034p_1_MInew.fits" 

61 

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

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

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

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

66 

67 

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

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

70 """ 

71 A test case for the Exposure Class 

72 """ 

73 

74 def setUp(self): 

75 maskedImage = afwImage.MaskedImageF(inFilePathSmall) 

76 maskedImageMD = readMetadata(inFilePathSmall) 

77 

78 self.smallExposure = afwImage.ExposureF(inFilePathSmall) 

79 self.width = maskedImage.getWidth() 

80 self.height = maskedImage.getHeight() 

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

82 self.md = maskedImageMD 

83 self.psf = DummyPsf(2.0) 

84 self.detector = DetectorWrapper().detector 

85 self.id = 42 

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

87 

88 self.exposureBlank = afwImage.ExposureF() 

89 self.exposureMiOnly = afwImage.makeExposure(maskedImage) 

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

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

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

93 # test with ExtentI(100, 100) too 

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

95 

96 def tearDown(self): 

97 del self.smallExposure 

98 del self.wcs 

99 del self.psf 

100 del self.detector 

101 del self.extras 

102 

103 del self.exposureBlank 

104 del self.exposureMiOnly 

105 del self.exposureMiWcs 

106 del self.exposureCrWcs 

107 del self.exposureCrOnly 

108 

109 def testGetMaskedImage(self): 

110 """ 

111 Test to ensure a MaskedImage can be obtained from each 

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

113 therefore each of the Exposures should return a MaskedImage. 

114 

115 MaskedImage class should throw appropriate 

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

117 obtained. 

118 """ 

119 maskedImageBlank = self.exposureBlank.getMaskedImage() 

120 blankWidth = maskedImageBlank.getWidth() 

121 blankHeight = maskedImageBlank.getHeight() 

122 if blankWidth != blankHeight != 0: 

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

124 

125 maskedImageMiOnly = self.exposureMiOnly.getMaskedImage() 

126 miOnlyWidth = maskedImageMiOnly.getWidth() 

127 miOnlyHeight = maskedImageMiOnly.getHeight() 

128 self.assertAlmostEqual(miOnlyWidth, self.width) 

129 self.assertAlmostEqual(miOnlyHeight, self.height) 

130 

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

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

133 # the WCS being copied/created. 

134 

135 maskedImageMiWcs = self.exposureMiWcs.getMaskedImage() 

136 miWcsWidth = maskedImageMiWcs.getWidth() 

137 miWcsHeight = maskedImageMiWcs.getHeight() 

138 self.assertAlmostEqual(miWcsWidth, self.width) 

139 self.assertAlmostEqual(miWcsHeight, self.height) 

140 

141 maskedImageCrWcs = self.exposureCrWcs.getMaskedImage() 

142 crWcsWidth = maskedImageCrWcs.getWidth() 

143 crWcsHeight = maskedImageCrWcs.getHeight() 

144 if crWcsWidth != crWcsHeight != 0: 

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

146 

147 maskedImageCrOnly = self.exposureCrOnly.getMaskedImage() 

148 crOnlyWidth = maskedImageCrOnly.getWidth() 

149 crOnlyHeight = maskedImageCrOnly.getHeight() 

150 if crOnlyWidth != crOnlyHeight != 0: 

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

152 

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

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

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

156 # check width/height properties 

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

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

159 

160 def testProperties(self): 

161 self.assertMaskedImagesEqual(self.exposureMiOnly.maskedImage, 

162 self.exposureMiOnly.getMaskedImage()) 

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

164 mi2.image.array[:] = 5.0 

165 mi2.variance.array[:] = 3.0 

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

167 self.exposureMiOnly.maskedImage = mi2 

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

169 self.assertImagesEqual(self.exposureMiOnly.image, 

170 self.exposureMiOnly.maskedImage.image) 

171 

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

173 image3.array[:] = 3.0 

174 self.exposureMiOnly.image = image3 

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

176 

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

178 mask3.array[:] = 0x2 

179 self.exposureMiOnly.mask = mask3 

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

181 

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

183 var3.array[:] = 2.0 

184 self.exposureMiOnly.variance = var3 

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

186 

187 # Test the property getter for a null VisitInfo. 

188 self.assertIsNone(self.exposureMiOnly.visitInfo) 

189 

190 def testGetWcs(self): 

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

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

193 """ 

194 # These exposures don't contain a WCS 

195 self.assertIsNone(self.exposureBlank.getWcs()) 

196 self.assertIsNone(self.exposureMiOnly.getWcs()) 

197 self.assertIsNone(self.exposureCrOnly.getWcs()) 

198 

199 # These exposures should contain a WCS 

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

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

202 

203 def testExposureInfoConstructor(self): 

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

205 exposureInfo = afwImage.ExposureInfo() 

206 exposureInfo.setWcs(self.wcs) 

207 exposureInfo.setDetector(self.detector) 

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

209 exposureInfo.setFilter(gFilterLabel) 

210 maskedImage = afwImage.MaskedImageF(inFilePathSmall) 

211 exposure = afwImage.ExposureF(maskedImage, exposureInfo) 

212 

213 self.assertTrue(exposure.hasWcs()) 

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

215 self.wcs.getPixelOrigin()) 

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

217 self.detector.getName()) 

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

219 self.detector.getSerial()) 

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

221 

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

223 # check the ExposureInfo property 

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

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

226 self.wcs.getPixelOrigin()) 

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

228 self.detector.getName()) 

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

230 self.detector.getSerial()) 

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

232 

233 def testNullWcs(self): 

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

235 

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

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

238 """ 

239 maskedImage = self.exposureMiOnly.getMaskedImage() 

240 exposure = afwImage.ExposureF(maskedImage, None) 

241 self.assertFalse(exposure.hasWcs()) 

242 self.assertFalse(exposure.hasPsf()) 

243 

244 def testExposureInfoSetNone(self): 

245 exposureInfo = afwImage.ExposureInfo() 

246 exposureInfo.setDetector(None) 

247 exposureInfo.setValidPolygon(None) 

248 exposureInfo.setPsf(None) 

249 exposureInfo.setWcs(None) 

250 exposureInfo.setPhotoCalib(None) 

251 exposureInfo.setCoaddInputs(None) 

252 exposureInfo.setVisitInfo(None) 

253 exposureInfo.setApCorrMap(None) 

254 for key in self.extras: 

255 exposureInfo.setComponent(key, None) 

256 

257 def testSetExposureInfo(self): 

258 exposureInfo = afwImage.ExposureInfo() 

259 exposureInfo.setWcs(self.wcs) 

260 exposureInfo.setDetector(self.detector) 

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

262 exposureInfo.setFilter(gFilterLabel) 

263 exposureInfo.setId(self.id) 

264 maskedImage = afwImage.MaskedImageF(inFilePathSmall) 

265 exposure = afwImage.ExposureF(maskedImage) 

266 self.assertFalse(exposure.hasWcs()) 

267 

268 exposure.setInfo(exposureInfo) 

269 

270 self.assertTrue(exposure.hasWcs()) 

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

272 self.wcs.getPixelOrigin()) 

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

274 self.detector.getName()) 

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

276 self.detector.getSerial()) 

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

278 

279 # test properties 

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

281 self.assertEqual(exposure.filter, gFilterLabel) 

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

283 

284 def testVisitInfoFitsPersistence(self): 

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

286 exposureTime = 12.3 

287 boresightRotAngle = 45.6 * lsst.geom.degrees 

288 weather = Weather(1.1, 2.2, 0.3) 

289 visitInfo = afwImage.VisitInfo( 

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 testGetCutoutSky(self): 

720 """Test we can get cutouts in sky coordinates, so long as there is a 

721 valid WCS. 

722 """ 

723 wcs = self.smallExposure.getWcs() 

724 

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

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

727 2*self.smallExposure.getDimensions()] 

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

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

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

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

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

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

734 for cutoutSize in dimensions: 

735 for label, cutoutCenter in locations: 

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

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

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

739 centerInPixels = wcs.skyToPixel(cutoutCenter) 

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

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

742 self._checkCutoutPixels( 

743 cutout, 

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

745 msg) 

746 

747 # Need a valid WCS 

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

749 self.exposureMiOnly.getCutout(cutoutCenter, cutoutSize) 

750 else: 

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

752 self.smallExposure.getCutout(cutoutCenter, cutoutSize) 

753 

754 def testGetCutoutPixel(self): 

755 """Test that we can get cutouts in pixel coordinates, even if the 

756 extent is off the edge of the image, even if there is no WCS. 

757 """ 

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

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

760 2*self.exposureMiOnly.getDimensions()] 

761 locations = [("center", lsst.geom.Box2D(self.exposureMiOnly.getBBox()).getCenter()), 

762 ("edge", lsst.geom.Point2D(0, 0)), 

763 ("rounding test", lsst.geom.Point2D(0.2, 0.7)), 

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

765 # These two should raise; center must be within image box. 

766 ("just outside", lsst.geom.Point2D(-0.5 - 1e-4, -0.5 - 1e-4)), 

767 ("outside", lsst.geom.Point2D(-1000, -1000))] 

768 for cutoutSize in dimensions: 

769 for label, cutoutCenter in locations: 

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

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

772 cutout = self.exposureMiOnly.getCutout(cutoutCenter, cutoutSize) 

773 self._checkCutoutPixels( 

774 cutout, 

775 self._getValidCorners(self.exposureMiOnly.getBBox(), cutout.getBBox()), 

776 msg) 

777 

778 # Same result even if there is a wcs. 

779 cutoutWithWcs = self.smallExposure.getCutout(cutoutCenter, cutoutSize) 

780 self.assertMaskedImagesEqual(cutout.maskedImage, cutoutWithWcs.maskedImage) 

781 

782 # Getting a cutout with a bbox should produce the same result. 

783 box = lsst.geom.Box2I.makeCenteredBox(cutoutCenter, lsst.geom.Extent2I(cutoutSize)) 

784 cutoutBox2I = self.exposureMiOnly.getCutout(box) 

785 self.assertMaskedImagesEqual(cutout.maskedImage, cutoutBox2I.maskedImage) 

786 else: 

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

788 self.exposureMiOnly.getCutout(cutoutCenter, cutoutSize) 

789 

790 def testGetConvexPolygon(self): 

791 """Test the convex polygon.""" 

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

793 self.assertIsNone(self.exposureMiOnly.convex_polygon) 

794 

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

796 bbox = self.exposureMiWcs.getBBox() 

797 # Grow by the default padding. 

798 bbox.grow(10) 

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

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

801 wcs = self.exposureMiWcs.wcs 

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

803 y.ravel()) 

804 

805 poly = self.exposureMiWcs.convex_polygon 

806 contains = poly.contains(ra, dec) 

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

808 

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

810 bbox.grow(1) 

811 

812 ra, dec = wcs.pixelToSkyArray( 

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

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

815 contains = poly.contains(ra, dec) 

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

817 

818 ra, dec = wcs.pixelToSkyArray( 

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

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

821 contains = poly.contains(ra, dec) 

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

823 

824 ra, dec = wcs.pixelToSkyArray( 

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

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

827 contains = poly.contains(ra, dec) 

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

829 

830 ra, dec = wcs.pixelToSkyArray( 

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

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

833 contains = poly.contains(ra, dec) 

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

835 

836 def testContainsSkyCoords(self): 

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

838 self.assertRaisesRegex(ValueError, 

839 "Exposure does not have a valid WCS", 

840 self.exposureMiOnly.containsSkyCoords, 

841 0.0, 

842 0.0) 

843 

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

845 bbox = self.exposureMiWcs.getBBox() 

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

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

848 wcs = self.exposureMiWcs.wcs 

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

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

851 

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

853 dec*units.radian) 

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

855 

856 # Same test, everything in degrees. 

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

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

859 degrees=True) 

860 

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

862 dec*units.degree) 

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

864 

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

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

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

868 

869 # Bad NaN handling appears as a warning, not an error 

870 with warnings.catch_warnings(): 

871 warnings.simplefilter("error", category=RuntimeWarning) 

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

873 dec*units.degree) 

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

875 compare[0] = False 

876 compare[-1] = False 

877 np.testing.assert_array_equal(contains, compare) 

878 

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

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

881 

882 Parameters 

883 ---------- 

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

885 The cutout to test. 

886 size : `lsst.geom.Extent2I` 

887 The expected dimensions of ``cutout``. 

888 center : `lsst.geom.SpherePoint` 

889 The expected center of ``cutout``. 

890 precision : `lsst.geom.Angle` 

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

892 msg : `str` 

893 An error message suffix describing test parameters. 

894 """ 

895 newCenter = self._getExposureCenter(cutout) 

896 self.assertIsNotNone(cutout, msg=msg) 

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

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

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

900 

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

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

903 

904 Parameters 

905 ---------- 

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

907 The cutout to test. 

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

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

910 msg : `str` 

911 An error message suffix describing test parameters. 

912 """ 

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

914 edgeMask = mask.getPlaneBitMask("NO_DATA") 

915 

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

917 maskBitsSet = mask[corner] & edgeMask 

918 if corner in validCorners: 

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

920 else: 

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

922 

923 def _getExposureCenter(self, exposure): 

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

925 

926 Parameters 

927 ---------- 

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

929 The image whose center is desired. 

930 

931 Returns 

932 ------- 

933 center : `lsst.geom.SpherePoint` 

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

935 """ 

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

937 

938 def _getValidCorners(self, imageBox, cutoutBox): 

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

940 

941 Parameters 

942 ---------- 

943 imageBox: `lsst.geom.Extent2I` 

944 The bounding box of the original image. 

945 cutoutBox : `lsst.geom.Box2I` 

946 The bounding box of the cutout. 

947 

948 Returns 

949 ------- 

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

951 The corners that are drawn from the original image. 

952 """ 

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

954 

955 

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

957 def setUp(self): 

958 super().setUp() 

959 

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

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

962 np.identity(2), 

963 ) 

964 self.photoCalib = afwImage.PhotoCalib(1.5) 

965 self.psf = DummyPsf(2.0) 

966 self.detector = DetectorWrapper().detector 

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

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

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

970 self.coaddInputs = afwImage.CoaddInputs() 

971 self.apCorrMap = afwImage.ApCorrMap() 

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

973 

974 self.exposureInfo = afwImage.ExposureInfo() 

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

976 self.exposureId = 42 

977 

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

979 self.assertFalse(has()) 

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

981 self.assertIsNone(get()) 

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

983 

984 self.exposureInfo.setComponent(key, value) 

985 self.assertTrue(has()) 

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

987 self.assertIsNotNone(get()) 

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

989 self.assertEqual(get(), value) 

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

991 

992 self.exposureInfo.removeComponent(key) 

993 self.assertFalse(has()) 

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

995 self.assertIsNone(get()) 

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

997 

998 def testAliases(self): 

999 cls = type(self.exposureInfo) 

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

1001 self.exposureInfo.hasWcs, self.exposureInfo.getWcs) 

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

1003 self.exposureInfo.hasPsf, self.exposureInfo.getPsf) 

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

1005 self.exposureInfo.hasPhotoCalib, self.exposureInfo.getPhotoCalib) 

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

1007 self.exposureInfo.hasDetector, self.exposureInfo.getDetector) 

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

1009 self.exposureInfo.hasValidPolygon, self.exposureInfo.getValidPolygon) 

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

1011 self.exposureInfo.hasCoaddInputs, self.exposureInfo.getCoaddInputs) 

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

1013 self.exposureInfo.hasApCorrMap, self.exposureInfo.getApCorrMap) 

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

1015 self.exposureInfo.hasTransmissionCurve, self.exposureInfo.getTransmissionCurve) 

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

1017 self.exposureInfo.hasSummaryStats, self.exposureInfo.getSummaryStats) 

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

1019 self.exposureInfo.hasFilter, self.exposureInfo.getFilter) 

1020 

1021 def testId(self): 

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

1023 

1024 self.assertFalse(self.exposureInfo.hasId()) 

1025 self.assertIsNone(self.exposureInfo.getId()) 

1026 self.assertIsNone(self.exposureInfo.id) 

1027 

1028 self.exposureInfo.setId(self.exposureId) 

1029 self.assertTrue(self.exposureInfo.hasId()) 

1030 self.assertIsNotNone(self.exposureInfo.getId()) 

1031 self.assertIsNotNone(self.exposureInfo.id) 

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

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

1034 

1035 self.exposureInfo.id = 99899 

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

1037 

1038 self.exposureInfo.id = None 

1039 self.assertFalse(self.exposureInfo.hasId()) 

1040 self.assertIsNone(self.exposureInfo.getId()) 

1041 self.assertIsNone(self.exposureInfo.id) 

1042 

1043 def testCopy(self): 

1044 # Test that ExposureInfos have independently settable state 

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

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

1047 

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

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

1050 np.identity(2), 

1051 ) 

1052 copy.setWcs(newWcs) 

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

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

1055 

1056 def testMissingProperties(self): 

1057 # Test that invalid properties return None instead of raising 

1058 exposureInfo = afwImage.ExposureInfo() 

1059 

1060 self.assertIsNone(exposureInfo.id) 

1061 

1062 

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

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

1065 

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

1067 """ 

1068 def setUp(self): 

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

1070 

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

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

1073 nx = ny = 10 

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

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

1076 mask = afwImage.MaskX(nx, ny) 

1077 mask.array[5, 5] = 5 

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

1079 self.exposureId = 12345 

1080 

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

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

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

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

1085 

1086 def testReadUnversioned(self): 

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

1088 """ 

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

1090 exposure = afwImage.ExposureF.readFits(filename) 

1091 

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

1093 

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

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

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

1097 

1098 def testReadVersion0(self): 

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

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

1101 is marked as ExposureInfo version 0 in the header. 

1102 """ 

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

1104 exposure = afwImage.ExposureF.readFits(filename) 

1105 

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

1107 

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

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

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

1111 

1112 # Check that the metadata reader parses the file correctly 

1113 reader = afwImage.ExposureFitsReader(filename) 

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

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

1116 

1117 def testReadVersion1(self): 

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

1119 Version 1 replaced Calib with PhotoCalib. 

1120 """ 

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

1122 exposure = afwImage.ExposureF.readFits(filename) 

1123 

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

1125 

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

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

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

1129 

1130 # Check that the metadata reader parses the file correctly 

1131 reader = afwImage.ExposureFitsReader(filename) 

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

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

1134 

1135 def testReadVersion2(self): 

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

1137 Version 2 replaced Filter with FilterLabel. 

1138 """ 

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

1140 exposure = afwImage.ExposureF.readFits(filename) 

1141 

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

1143 

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

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

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

1147 

1148 # Check that the metadata reader parses the file correctly 

1149 reader = afwImage.ExposureFitsReader(filename) 

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

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

1152 

1153 def testExposureSummaryExtraComponents(self): 

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

1155 """ 

1156 testDict = {'ra': 0.0, 

1157 'dec': 0.0, 

1158 'nonsense': 1.0} 

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

1160 with self.assertWarns(FutureWarning): 

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

1162 

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

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

1165 

1166 def testExposureSummaryForwardComponents(self): 

1167 """Test that we can forward extra components (e.g. decl->dec). 

1168 """ 

1169 testDict = {'ra': 10.0, 

1170 'decl': 10.0} 

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

1172 # Cleanly forwarded fields must not result in a warning. 

1173 with warnings.catch_warnings(): 

1174 warnings.simplefilter("error") 

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

1176 

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

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

1179 

1180 # And check if there are both listed, it should use the new dec value. 

1181 testDict = {'ra': 10.0, 

1182 'dec': 5.0, 

1183 'decl': 10.0} 

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

1185 with self.assertWarns(FutureWarning): 

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

1187 

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

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

1190 

1191 def testExposureSummarySchema(self): 

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

1193 records with that schema. 

1194 """ 

1195 schema = afwTable.Schema() 

1196 afwImage.ExposureSummaryStats.update_schema(schema) 

1197 self.maxDiff = None 

1198 self.assertEqual( 

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

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

1201 ) 

1202 catalog = afwTable.BaseCatalog(schema) 

1203 summary1 = afwImage.ExposureSummaryStats() 

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

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

1206 if field.type == "float": 

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

1208 elif field.type == "int": 

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

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

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

1212 else: 

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

1214 record = catalog.addNew() 

1215 summary1.update_record(record) 

1216 summary2 = afwImage.ExposureSummaryStats.from_record(record) 

1217 self.assertEqual(summary1, summary2) 

1218 

1219 def testMetadataProperty(self): 

1220 """Test that the metadata property works as expected. 

1221 """ 

1222 exposure = afwImage.ExposureF(3, 4) 

1223 self.assertFalse(exposure.metadata) 

1224 self.assertIsNotNone(exposure.metadata) 

1225 exposure.metadata = None 

1226 self.assertIsNone(exposure.metadata) 

1227 metadata = PropertyList() 

1228 metadata["one"] = 1 

1229 exposure.metadata = metadata 

1230 self.assertEqual(exposure.metadata["one"], 1) 

1231 

1232 

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

1234 pass 

1235 

1236 

1237def setup_module(module): 

1238 lsst.utils.tests.init() 

1239 

1240 

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

1242 lsst.utils.tests.init() 

1243 unittest.main()