Coverage for tests/test_exposure.py: 10%

697 statements  

« prev     ^ index     » next       coverage.py v7.5.0, created at 2024-04-25 00:01 -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 with self.assertWarns(FutureWarning): 

220 self.assertEqual(exposure.getFilterLabel(), 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 with self.assertWarns(FutureWarning): 

233 self.assertEqual(exposure.getInfo().getFilterLabel(), gFilterLabel) 

234 

235 def testNullWcs(self): 

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

237 

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

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

240 """ 

241 maskedImage = self.exposureMiOnly.getMaskedImage() 

242 exposure = afwImage.ExposureF(maskedImage, None) 

243 self.assertFalse(exposure.hasWcs()) 

244 self.assertFalse(exposure.hasPsf()) 

245 

246 def testExposureInfoSetNone(self): 

247 exposureInfo = afwImage.ExposureInfo() 

248 exposureInfo.setDetector(None) 

249 exposureInfo.setValidPolygon(None) 

250 exposureInfo.setPsf(None) 

251 exposureInfo.setWcs(None) 

252 exposureInfo.setPhotoCalib(None) 

253 exposureInfo.setCoaddInputs(None) 

254 exposureInfo.setVisitInfo(None) 

255 exposureInfo.setApCorrMap(None) 

256 for key in self.extras: 

257 exposureInfo.setComponent(key, None) 

258 

259 def testSetExposureInfo(self): 

260 exposureInfo = afwImage.ExposureInfo() 

261 exposureInfo.setWcs(self.wcs) 

262 exposureInfo.setDetector(self.detector) 

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

264 exposureInfo.setFilter(gFilterLabel) 

265 exposureInfo.setId(self.id) 

266 maskedImage = afwImage.MaskedImageF(inFilePathSmall) 

267 exposure = afwImage.ExposureF(maskedImage) 

268 self.assertFalse(exposure.hasWcs()) 

269 

270 exposure.setInfo(exposureInfo) 

271 

272 self.assertTrue(exposure.hasWcs()) 

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

274 self.wcs.getPixelOrigin()) 

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

276 self.detector.getName()) 

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

278 self.detector.getSerial()) 

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

280 with self.assertWarns(FutureWarning): 

281 self.assertEqual(exposure.getFilterLabel(), gFilterLabel) 

282 

283 # test properties 

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

285 self.assertEqual(exposure.filter, gFilterLabel) 

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

287 

288 def testVisitInfoFitsPersistence(self): 

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

290 exposureId = 5 

291 exposureTime = 12.3 

292 boresightRotAngle = 45.6 * lsst.geom.degrees 

293 weather = Weather(1.1, 2.2, 0.3) 

294 visitInfo = afwImage.VisitInfo( 

295 exposureId=exposureId, 

296 exposureTime=exposureTime, 

297 boresightRotAngle=boresightRotAngle, 

298 weather=weather, 

299 ) 

300 photoCalib = afwImage.PhotoCalib(3.4, 5.6) 

301 exposureInfo = afwImage.ExposureInfo() 

302 exposureInfo.setVisitInfo(visitInfo) 

303 exposureInfo.setPhotoCalib(photoCalib) 

304 exposureInfo.setDetector(self.detector) 

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

306 exposureInfo.setFilter(gFilterLabel) 

307 maskedImage = afwImage.MaskedImageF(inFilePathSmall) 

308 exposure = afwImage.ExposureF(maskedImage, exposureInfo) 

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

310 exposure.writeFits(tmpFile) 

311 rtExposure = afwImage.ExposureF(tmpFile) 

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

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

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

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

316 with self.assertWarns(FutureWarning): 

317 self.assertEqual(rtExposure.getFilterLabel(), gFilterLabel) 

318 

319 # Test property getters. 

320 self.assertEqual(rtExposure.photoCalib, photoCalib) 

321 self.assertEqual(rtExposure.filter, gFilterLabel) 

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

323 self.assertIsNotNone(rtExposure.visitInfo) 

324 

325 def testSetMembers(self): 

326 """ 

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

328 """ 

329 exposure = afwImage.ExposureF() 

330 

331 maskedImage = afwImage.MaskedImageF(inFilePathSmall) 

332 exposure.setMaskedImage(maskedImage) 

333 exposure.setWcs(self.wcs) 

334 exposure.setDetector(self.detector) 

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

336 

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

338 self.detector.getName()) 

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

340 self.detector.getSerial()) 

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

342 with self.assertWarns(FutureWarning): 

343 self.assertEqual(exposure.getFilterLabel().bandLabel, "g") 

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

345 

346 # The PhotoCalib tests are in test_photoCalib.py; 

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

348 self.assertIsNone(exposure.getPhotoCalib()) 

349 

350 photoCalib = afwImage.PhotoCalib(511.1, 44.4) 

351 exposure.setPhotoCalib(photoCalib) 

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

353 

354 # Psfs next 

355 self.assertFalse(exposure.hasPsf()) 

356 exposure.setPsf(self.psf) 

357 self.assertTrue(exposure.hasPsf()) 

358 

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

360 

361 # extras next 

362 info = exposure.getInfo() 

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

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

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

366 info.setComponent(key, value) 

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

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

369 info.removeComponent(key) 

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

371 

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

373 # that already has both 

374 self.exposureMiWcs.setMaskedImage(maskedImage) 

375 exposure.setWcs(self.wcs) 

376 

377 def testHasWcs(self): 

378 """ 

379 Test if an Exposure has a WCS or not. 

380 """ 

381 self.assertFalse(self.exposureBlank.hasWcs()) 

382 

383 self.assertFalse(self.exposureMiOnly.hasWcs()) 

384 self.assertTrue(self.exposureMiWcs.hasWcs()) 

385 self.assertTrue(self.exposureCrWcs.hasWcs()) 

386 self.assertFalse(self.exposureCrOnly.hasWcs()) 

387 

388 def testGetSubExposure(self): 

389 """ 

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

391 

392 The MaskedImage class should throw a 

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

394 subRegion is not fully contained within the original 

395 MaskedImage. 

396 

397 """ 

398 # 

399 # This subExposure is valid 

400 # 

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

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

403 subExposure = self.exposureCrWcs.Factory( 

404 self.exposureCrWcs, subBBox, afwImage.LOCAL) 

405 

406 self.checkWcs(self.exposureCrWcs, subExposure) 

407 

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

409 # from the MaskedImage class and should trigger an exception 

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

411 

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

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

414 

415 def getSubRegion(): 

416 self.exposureCrWcs.Factory( 

417 self.exposureCrWcs, subRegion3, afwImage.LOCAL) 

418 

419 self.assertRaises(pexExcept.LengthError, getSubRegion) 

420 

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

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

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

424 

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

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

427 

428 def getSubRegion(): 

429 self.exposureCrWcs.Factory( 

430 self.exposureCrWcs, subRegion4, afwImage.LOCAL) 

431 

432 self.assertRaises(pexExcept.LengthError, getSubRegion) 

433 

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

435 # transformation 

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

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

438 subExposure = self.exposureCrWcs.Factory( 

439 self.exposureCrWcs, subBBox, afwImage.LOCAL) 

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

441 

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

443 

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

445 

446 def testReadWriteFits(self): 

447 """Test readFits and writeFits. 

448 """ 

449 # This should pass without an exception 

450 mainExposure = afwImage.ExposureF(inFilePathSmall) 

451 mainExposure.info.setId(self.id) 

452 mainExposure.setDetector(self.detector) 

453 

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

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

456 subExposure = mainExposure.Factory( 

457 mainExposure, subBBox, afwImage.LOCAL) 

458 self.checkWcs(mainExposure, subExposure) 

459 det = subExposure.getDetector() 

460 self.assertTrue(det) 

461 

462 subExposure = afwImage.ExposureF( 

463 inFilePathSmall, subBBox, afwImage.LOCAL) 

464 

465 self.checkWcs(mainExposure, subExposure) 

466 

467 # This should throw an exception 

468 def getExposure(): 

469 afwImage.ExposureF(inFilePathSmallImage) 

470 

471 self.assertRaises(FitsError, getExposure) 

472 

473 mainExposure.setPsf(self.psf) 

474 

475 # Make sure we can write without an exception 

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

477 mainExposure.setPhotoCalib(photoCalib) 

478 

479 mainInfo = mainExposure.getInfo() 

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

481 mainInfo.setComponent(key, value) 

482 

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

484 mainExposure.writeFits(tmpFile) 

485 

486 readExposure = type(mainExposure)(tmpFile) 

487 

488 # 

489 # Check the round-tripping 

490 # 

491 self.assertIsNotNone(mainExposure.getFilter()) 

492 self.assertEqual(mainExposure.getFilter(), 

493 readExposure.getFilter()) 

494 with self.assertWarns(FutureWarning): 

495 self.assertIsNotNone(mainExposure.getFilterLabel()) 

496 self.assertEqual(mainExposure.getFilterLabel(), 

497 readExposure.getFilterLabel()) 

498 

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

500 

501 readInfo = readExposure.getInfo() 

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

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

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

505 

506 psf = readExposure.getPsf() 

507 self.assertIsNotNone(psf) 

508 self.assertEqual(psf, self.psf) 

509 # check psf property getter 

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

511 

512 def checkWcs(self, parentExposure, subExposure): 

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

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

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

516 to get the same sky coordinates for each. 

517 """ 

518 subMI = subExposure.getMaskedImage() 

519 subDim = subMI.getDimensions() 

520 

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

522 # with WCS 

523 mainWcs = parentExposure.getWcs() 

524 subWcs = subExposure.getWcs() 

525 

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

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

528 self.assertSpherePointsAlmostEqual( 

529 mainWcs.pixelToSky( 

530 afwImage.indexToPosition(xSubInd), 

531 afwImage.indexToPosition(ySubInd), 

532 ), 

533 subWcs.pixelToSky( 

534 afwImage.indexToPosition(xSubInd), 

535 afwImage.indexToPosition(ySubInd), 

536 )) 

537 

538 def cmpExposure(self, e1, e2): 

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

540 e2.getDetector().getName()) 

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

542 e2.getDetector().getSerial()) 

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

544 with self.assertWarns(FutureWarning): 

545 self.assertEqual(e1.getFilterLabel(), e2.getFilterLabel()) 

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

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

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

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

550 # check PSF identity 

551 if not e1.getPsf(): 

552 self.assertFalse(e2.getPsf()) 

553 else: 

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

555 # Check extra components 

556 i1 = e1.getInfo() 

557 i2 = e2.getInfo() 

558 for key in self.extras: 

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

560 if i1.hasComponent(key): 

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

562 

563 def testCopyExposure(self): 

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

565 

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

567 exposureU.setWcs(self.wcs) 

568 exposureU.setDetector(self.detector) 

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

570 exposureU.setPsf(DummyPsf(4.0)) 

571 infoU = exposureU.getInfo() 

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

573 infoU.setComponent(key, value) 

574 

575 exposureF = exposureU.convertF() 

576 self.cmpExposure(exposureF, exposureU) 

577 

578 nexp = exposureF.Factory(exposureF, False) 

579 self.cmpExposure(exposureF, nexp) 

580 

581 # Ensure that the copy was deep. 

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

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

584 # x0,y0 = cen0 

585 # det = exposureF.getDetector() 

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

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

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

589 

590 def testDeepCopyData(self): 

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

592 """ 

593 exp = afwImage.ExposureF(6, 7) 

594 mi = exp.getMaskedImage() 

595 mi.getImage().set(100) 

596 mi.getMask().set(5) 

597 mi.getVariance().set(200) 

598 

599 expCopy = exp.clone() 

600 miCopy = expCopy.getMaskedImage() 

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

602 miCopy.getMask().set(2) 

603 miCopy.getVariance().set(175) 

604 

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

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

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

608 

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

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

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

612 

613 def testDeepCopySubData(self): 

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

615 """ 

616 exp = afwImage.ExposureF(6, 7) 

617 mi = exp.getMaskedImage() 

618 mi.getImage().set(100) 

619 mi.getMask().set(5) 

620 mi.getVariance().set(200) 

621 

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

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

624 miCopy = expCopy.getMaskedImage() 

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

626 miCopy.getMask().set(2) 

627 miCopy.getVariance().set(175) 

628 

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

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

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

632 

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

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

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

636 

637 def testDeepCopyMetadata(self): 

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

639 """ 

640 exp = afwImage.ExposureF(10, 10) 

641 expMeta = exp.getMetadata() 

642 expMeta.set("foo", 5) 

643 expCopy = exp.clone() 

644 expCopyMeta = expCopy.getMetadata() 

645 expCopyMeta.set("foo", 6) 

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

647 # this will fail if the bug is present 

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

649 

650 def testDeepCopySubMetadata(self): 

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

652 """ 

653 exp = afwImage.ExposureF(10, 10) 

654 expMeta = exp.getMetadata() 

655 expMeta.set("foo", 5) 

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

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

658 expCopyMeta = expCopy.getMetadata() 

659 expCopyMeta.set("foo", 6) 

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

661 # this will fail if the bug is present 

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

663 

664 def testMakeExposureLeaks(self): 

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

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

667 afwImage.makeExposure(afwImage.makeMaskedImage( 

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

669 

670 def testImageSlices(self): 

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

672 exp = afwImage.ExposureF(10, 20) 

673 mi = exp.getMaskedImage() 

674 mi.image[9, 19] = 10 

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

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

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

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

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

680 self.assertEqual(sexp.getDimensions(), 

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

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

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

684 

685 def testConversionToScalar(self): 

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

687 im = afwImage.ExposureF(10, 20) 

688 

689 # only single pixel images may be converted 

690 self.assertRaises(TypeError, float, im) 

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

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

693 

694 def testReadMetadata(self): 

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

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

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

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

699 self.exposureCrWcs.writeFits(tmpFile) 

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

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

702 # frazzle and the Wcs from the PropertySet returned by 

703 # testReadMetadata. 

704 md = readMetadata(tmpFile) 

705 wcs = afwGeom.makeSkyWcs(md, False) 

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

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

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

709 frazzle = md.getScalar("FRAZZLE") 

710 self.assertTrue(frazzle) 

711 

712 def testArchiveKeys(self): 

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

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

715 exposure1.setPsf(self.psf) 

716 exposure1.writeFits(tmpFile) 

717 exposure2 = afwImage.ExposureF(tmpFile) 

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

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

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

721 

722 def testTicket2861(self): 

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

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

725 exposure1.setPsf(self.psf) 

726 schema = afwTable.ExposureTable.makeMinimalSchema() 

727 coaddInputs = afwImage.CoaddInputs(schema, schema) 

728 exposure1.getInfo().setCoaddInputs(coaddInputs) 

729 exposure2 = afwImage.ExposureF(exposure1, True) 

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

731 exposure2.writeFits(tmpFile) 

732 exposure3 = afwImage.ExposureF(tmpFile) 

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

734 

735 def testGetCutout(self): 

736 wcs = self.smallExposure.getWcs() 

737 

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

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

740 2*self.smallExposure.getDimensions()] 

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

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

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

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

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

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

747 for cutoutSize in dimensions: 

748 for label, cutoutCenter in locations: 

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

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

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

752 centerInPixels = wcs.skyToPixel(cutoutCenter) 

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

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

755 self._checkCutoutPixels( 

756 cutout, 

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

758 msg) 

759 

760 # Need a valid WCS 

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

762 self.exposureMiOnly.getCutout(cutoutCenter, cutoutSize) 

763 else: 

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

765 self.smallExposure.getCutout(cutoutCenter, cutoutSize) 

766 

767 def testGetConvexPolygon(self): 

768 """Test the convex polygon.""" 

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

770 self.assertIsNone(self.exposureMiOnly.convex_polygon) 

771 

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

773 bbox = self.exposureMiWcs.getBBox() 

774 # Grow by the default padding. 

775 bbox.grow(10) 

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

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

778 wcs = self.exposureMiWcs.wcs 

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

780 y.ravel()) 

781 

782 poly = self.exposureMiWcs.convex_polygon 

783 contains = poly.contains(ra, dec) 

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

785 

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

787 bbox.grow(1) 

788 

789 ra, dec = wcs.pixelToSkyArray( 

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

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

792 contains = poly.contains(ra, dec) 

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

794 

795 ra, dec = wcs.pixelToSkyArray( 

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

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

798 contains = poly.contains(ra, dec) 

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

800 

801 ra, dec = wcs.pixelToSkyArray( 

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

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

804 contains = poly.contains(ra, dec) 

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

806 

807 ra, dec = wcs.pixelToSkyArray( 

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

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

810 contains = poly.contains(ra, dec) 

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

812 

813 def testContainsSkyCoords(self): 

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

815 self.assertRaisesRegex(ValueError, 

816 "Exposure does not have a valid WCS", 

817 self.exposureMiOnly.containsSkyCoords, 

818 0.0, 

819 0.0) 

820 

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

822 bbox = self.exposureMiWcs.getBBox() 

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

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

825 wcs = self.exposureMiWcs.wcs 

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

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

828 

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

830 dec*units.radian) 

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

832 

833 # Same test, everything in degrees. 

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

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

836 degrees=True) 

837 

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

839 dec*units.degree) 

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

841 

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

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

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

845 

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

847 dec*units.degree) 

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

849 compare[0] = False 

850 compare[-1] = False 

851 np.testing.assert_array_equal(contains, compare) 

852 

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

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

855 

856 Parameters 

857 ---------- 

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

859 The cutout to test. 

860 size : `lsst.geom.Extent2I` 

861 The expected dimensions of ``cutout``. 

862 center : `lsst.geom.SpherePoint` 

863 The expected center of ``cutout``. 

864 precision : `lsst.geom.Angle` 

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

866 msg : `str` 

867 An error message suffix describing test parameters. 

868 """ 

869 newCenter = self._getExposureCenter(cutout) 

870 self.assertIsNotNone(cutout, msg=msg) 

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

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

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

874 

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

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

877 

878 Parameters 

879 ---------- 

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

881 The cutout to test. 

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

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

884 msg : `str` 

885 An error message suffix describing test parameters. 

886 """ 

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

888 edgeMask = mask.getPlaneBitMask("NO_DATA") 

889 

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

891 maskBitsSet = mask[corner] & edgeMask 

892 if corner in validCorners: 

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

894 else: 

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

896 

897 def _getExposureCenter(self, exposure): 

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

899 

900 Parameters 

901 ---------- 

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

903 The image whose center is desired. 

904 

905 Returns 

906 ------- 

907 center : `lsst.geom.SpherePoint` 

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

909 """ 

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

911 

912 def _getValidCorners(self, imageBox, cutoutBox): 

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

914 

915 Parameters 

916 ---------- 

917 imageBox: `lsst.geom.Extent2I` 

918 The bounding box of the original image. 

919 cutoutBox : `lsst.geom.Box2I` 

920 The bounding box of the cutout. 

921 

922 Returns 

923 ------- 

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

925 The corners that are drawn from the original image. 

926 """ 

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

928 

929 

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

931 def setUp(self): 

932 super().setUp() 

933 

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

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

936 np.identity(2), 

937 ) 

938 self.photoCalib = afwImage.PhotoCalib(1.5) 

939 self.psf = DummyPsf(2.0) 

940 self.detector = DetectorWrapper().detector 

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

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

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

944 self.coaddInputs = afwImage.CoaddInputs() 

945 self.apCorrMap = afwImage.ApCorrMap() 

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

947 

948 self.exposureInfo = afwImage.ExposureInfo() 

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

950 self.exposureId = 42 

951 

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

953 self.assertFalse(has()) 

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

955 self.assertIsNone(get()) 

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

957 

958 self.exposureInfo.setComponent(key, value) 

959 self.assertTrue(has()) 

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

961 self.assertIsNotNone(get()) 

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

963 self.assertEqual(get(), value) 

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

965 

966 self.exposureInfo.removeComponent(key) 

967 self.assertFalse(has()) 

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

969 self.assertIsNone(get()) 

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

971 

972 def testAliases(self): 

973 cls = type(self.exposureInfo) 

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

975 self.exposureInfo.hasWcs, self.exposureInfo.getWcs) 

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

977 self.exposureInfo.hasPsf, self.exposureInfo.getPsf) 

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

979 self.exposureInfo.hasPhotoCalib, self.exposureInfo.getPhotoCalib) 

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

981 self.exposureInfo.hasDetector, self.exposureInfo.getDetector) 

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

983 self.exposureInfo.hasValidPolygon, self.exposureInfo.getValidPolygon) 

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

985 self.exposureInfo.hasCoaddInputs, self.exposureInfo.getCoaddInputs) 

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

987 self.exposureInfo.hasApCorrMap, self.exposureInfo.getApCorrMap) 

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

989 self.exposureInfo.hasTransmissionCurve, self.exposureInfo.getTransmissionCurve) 

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

991 self.exposureInfo.hasSummaryStats, self.exposureInfo.getSummaryStats) 

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

993 self.exposureInfo.hasFilter, self.exposureInfo.getFilter) 

994 with self.assertWarns(FutureWarning): 

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

996 self.exposureInfo.hasFilterLabel, self.exposureInfo.getFilterLabel) 

997 

998 def testId(self): 

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

1000 

1001 self.assertFalse(self.exposureInfo.hasId()) 

1002 self.assertIsNone(self.exposureInfo.getId()) 

1003 with self.assertWarns(FutureWarning): 

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

1005 self.assertIsNone(self.exposureInfo.id) 

1006 

1007 self.exposureInfo.setId(self.exposureId) 

1008 self.assertTrue(self.exposureInfo.hasId()) 

1009 self.assertIsNotNone(self.exposureInfo.getId()) 

1010 self.assertIsNotNone(self.exposureInfo.id) 

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

1012 with self.assertWarns(FutureWarning): 

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

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

1015 

1016 self.exposureInfo.id = 99899 

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

1018 

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

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

1021 

1022 self.exposureInfo.id = None 

1023 self.assertFalse(self.exposureInfo.hasId()) 

1024 self.assertIsNone(self.exposureInfo.getId()) 

1025 self.assertIsNone(self.exposureInfo.id) 

1026 

1027 def testCopy(self): 

1028 # Test that ExposureInfos have independently settable state 

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

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

1031 

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

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

1034 np.identity(2), 

1035 ) 

1036 copy.setWcs(newWcs) 

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

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

1039 

1040 def testMissingProperties(self): 

1041 # Test that invalid properties return None instead of raising 

1042 exposureInfo = afwImage.ExposureInfo() 

1043 

1044 self.assertIsNone(exposureInfo.id) 

1045 

1046 

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

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

1049 

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

1051 """ 

1052 def setUp(self): 

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

1054 

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

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

1057 nx = ny = 10 

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

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

1060 mask = afwImage.MaskX(nx, ny) 

1061 mask.array[5, 5] = 5 

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

1063 self.exposureId = 12345 

1064 

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

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

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

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

1069 

1070 def testReadUnversioned(self): 

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

1072 """ 

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

1074 exposure = afwImage.ExposureF.readFits(filename) 

1075 

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

1077 

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

1079 with self.assertWarns(FutureWarning): 

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

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

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

1083 with self.assertWarns(FutureWarning): 

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

1085 

1086 def testReadVersion0(self): 

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

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

1089 is marked as ExposureInfo version 0 in the header. 

1090 """ 

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

1092 exposure = afwImage.ExposureF.readFits(filename) 

1093 

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

1095 

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

1097 with self.assertWarns(FutureWarning): 

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

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

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

1101 with self.assertWarns(FutureWarning): 

1102 self.assertEqual(exposure.getFilterLabel(), self.v1FilterLabel) 

1103 

1104 # Check that the metadata reader parses the file correctly 

1105 reader = afwImage.ExposureFitsReader(filename) 

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

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

1108 

1109 def testReadVersion1(self): 

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

1111 Version 1 replaced Calib with PhotoCalib. 

1112 """ 

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

1114 exposure = afwImage.ExposureF.readFits(filename) 

1115 

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

1117 

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

1119 with self.assertWarns(FutureWarning): 

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

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

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

1123 with self.assertWarns(FutureWarning): 

1124 self.assertEqual(exposure.getFilterLabel(), self.v1FilterLabel) 

1125 

1126 # Check that the metadata reader parses the file correctly 

1127 reader = afwImage.ExposureFitsReader(filename) 

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

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

1130 

1131 def testReadVersion2(self): 

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

1133 Version 2 replaced Filter with FilterLabel. 

1134 """ 

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

1136 exposure = afwImage.ExposureF.readFits(filename) 

1137 

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

1139 

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

1141 with self.assertWarns(FutureWarning): 

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

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

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

1145 with self.assertWarns(FutureWarning): 

1146 self.assertEqual(exposure.getFilterLabel(), 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 'decl': 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.decl, testDict['decl']) 

1165 

1166 def testExposureSummarySchema(self): 

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

1168 records with that schema. 

1169 """ 

1170 schema = afwTable.Schema() 

1171 afwImage.ExposureSummaryStats.update_schema(schema) 

1172 self.maxDiff = None 

1173 self.assertEqual( 

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

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

1176 ) 

1177 catalog = afwTable.BaseCatalog(schema) 

1178 summary1 = afwImage.ExposureSummaryStats() 

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

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

1181 if field.type == "float": 

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

1183 elif field.type == "int": 

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

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

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

1187 else: 

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

1189 record = catalog.addNew() 

1190 summary1.update_record(record) 

1191 summary2 = afwImage.ExposureSummaryStats.from_record(record) 

1192 self.assertEqual(summary1, summary2) 

1193 

1194 

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

1196 pass 

1197 

1198 

1199def setup_module(module): 

1200 lsst.utils.tests.init() 

1201 

1202 

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

1204 lsst.utils.tests.init() 

1205 unittest.main()