Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1# 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 os.path 

27import unittest 

28 

29import numpy as np 

30from numpy.testing import assert_allclose 

31 

32import lsst.utils 

33import lsst.utils.tests 

34import lsst.geom 

35import lsst.afw.image as afwImage 

36from lsst.afw.image.utils import defineFilter 

37from lsst.afw.coord import Weather 

38import lsst.afw.geom as afwGeom 

39import lsst.afw.table as afwTable 

40import lsst.pex.exceptions as pexExcept 

41from lsst.afw.fits import readMetadata, FitsError 

42from lsst.afw.cameraGeom.testUtils import DetectorWrapper 

43from lsst.log import Log 

44from testTableArchivesLib import DummyPsf 

45 

46Log.getLogger("afw.image.Mask").setLevel(Log.INFO) 

47 

48try: 

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

50except pexExcept.NotFoundError: 

51 dataDir = None 

52else: 

53 InputMaskedImageName = "871034p_1_MI.fits" 

54 InputMaskedImageNameSmall = "small_MI.fits" 

55 InputImageNameSmall = "small" 

56 OutputMaskedImageName = "871034p_1_MInew.fits" 

57 

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

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

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

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

62 

63 

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

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

66 """ 

67 A test case for the Exposure Class 

68 """ 

69 

70 def setUp(self): 

71 maskedImage = afwImage.MaskedImageF(inFilePathSmall) 

72 maskedImageMD = readMetadata(inFilePathSmall) 

73 

74 self.smallExposure = afwImage.ExposureF(inFilePathSmall) 

75 self.width = maskedImage.getWidth() 

76 self.height = maskedImage.getHeight() 

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

78 self.md = maskedImageMD 

79 self.psf = DummyPsf(2.0) 

80 self.detector = DetectorWrapper().detector 

81 self.extras = {"misc": DummyPsf(3.5)} 

82 

83 self.exposureBlank = afwImage.ExposureF() 

84 self.exposureMiOnly = afwImage.makeExposure(maskedImage) 

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

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

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

88 # test with ExtentI(100, 100) too 

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

90 

91 afwImage.Filter.reset() 

92 afwImage.FilterProperty.reset() 

93 

94 defineFilter("g", 470.0) 

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 

157 def testProperties(self): 

158 self.assertMaskedImagesEqual(self.exposureMiOnly.maskedImage, 

159 self.exposureMiOnly.getMaskedImage()) 

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

161 mi2.image.array[:] = 5.0 

162 mi2.variance.array[:] = 3.0 

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

164 self.exposureMiOnly.maskedImage = mi2 

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

166 self.assertImagesEqual(self.exposureMiOnly.image, 

167 self.exposureMiOnly.maskedImage.image) 

168 

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

170 image3.array[:] = 3.0 

171 self.exposureMiOnly.image = image3 

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

173 

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

175 mask3.array[:] = 0x2 

176 self.exposureMiOnly.mask = mask3 

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

178 

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

180 var3.array[:] = 2.0 

181 self.exposureMiOnly.variance = var3 

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

183 

184 def testGetWcs(self): 

185 """ 

186 Test if a WCS can be obtained from each Exposure created with 

187 a WCS. 

188 

189 Test that appropriate exceptions are thrown if a WCS is 

190 requested from an Exposure that was not created with a WCS. 

191 Python turns the pex::exceptions in the Exposure and 

192 MaskedImage classes into IndexErrors. 

193 

194 The exposureBlank, exposureMiOnly, and exposureCrOnly 

195 Exposures should throw a lsst::pex::exceptions::NotFound. 

196 """ 

197 

198 self.assertFalse(self.exposureBlank.getWcs()) 

199 self.assertFalse(self.exposureMiOnly.getWcs()) 

200 

201 # These two should pass 

202 self.exposureMiWcs.getWcs() 

203 self.exposureCrWcs.getWcs() 

204 

205 self.assertFalse(self.exposureCrOnly.getWcs()) 

206 

207 def testExposureInfoConstructor(self): 

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

209 exposureInfo = afwImage.ExposureInfo() 

210 exposureInfo.setWcs(self.wcs) 

211 exposureInfo.setDetector(self.detector) 

212 gFilter = afwImage.Filter("g") 

213 exposureInfo.setFilter(gFilter) 

214 maskedImage = afwImage.MaskedImageF(inFilePathSmall) 

215 exposure = afwImage.ExposureF(maskedImage, exposureInfo) 

216 

217 self.assertTrue(exposure.hasWcs()) 

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

219 self.wcs.getPixelOrigin()) 

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

221 self.detector.getName()) 

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

223 self.detector.getSerial()) 

224 self.assertEqual(exposure.getFilter(), gFilter) 

225 

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

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

228 self.wcs.getPixelOrigin()) 

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

230 self.detector.getName()) 

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

232 self.detector.getSerial()) 

233 self.assertEqual(exposure.getInfo().getFilter(), gFilter) 

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 gFilter = afwImage.Filter("g") 

264 exposureInfo.setFilter(gFilter) 

265 maskedImage = afwImage.MaskedImageF(inFilePathSmall) 

266 exposure = afwImage.ExposureF(maskedImage) 

267 self.assertFalse(exposure.hasWcs()) 

268 

269 exposure.setInfo(exposureInfo) 

270 

271 self.assertTrue(exposure.hasWcs()) 

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

273 self.wcs.getPixelOrigin()) 

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

275 self.detector.getName()) 

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

277 self.detector.getSerial()) 

278 self.assertEqual(exposure.getFilter(), gFilter) 

279 

280 def testVisitInfoFitsPersistence(self): 

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

282 exposureId = 5 

283 exposureTime = 12.3 

284 boresightRotAngle = 45.6 * lsst.geom.degrees 

285 weather = Weather(1.1, 2.2, 0.3) 

286 visitInfo = afwImage.VisitInfo( 

287 exposureId=exposureId, 

288 exposureTime=exposureTime, 

289 boresightRotAngle=boresightRotAngle, 

290 weather=weather, 

291 ) 

292 photoCalib = afwImage.PhotoCalib(3.4, 5.6) 

293 exposureInfo = afwImage.ExposureInfo() 

294 exposureInfo.setVisitInfo(visitInfo) 

295 exposureInfo.setPhotoCalib(photoCalib) 

296 exposureInfo.setDetector(self.detector) 

297 gFilter = afwImage.Filter("g") 

298 exposureInfo.setFilter(gFilter) 

299 maskedImage = afwImage.MaskedImageF(inFilePathSmall) 

300 exposure = afwImage.ExposureF(maskedImage, exposureInfo) 

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

302 exposure.writeFits(tmpFile) 

303 rtExposure = afwImage.ExposureF(tmpFile) 

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

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

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

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

308 

309 def testSetMembers(self): 

310 """ 

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

312 """ 

313 exposure = afwImage.ExposureF() 

314 

315 maskedImage = afwImage.MaskedImageF(inFilePathSmall) 

316 exposure.setMaskedImage(maskedImage) 

317 exposure.setWcs(self.wcs) 

318 exposure.setDetector(self.detector) 

319 exposure.setFilter(afwImage.Filter("g")) 

320 

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

322 self.detector.getName()) 

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

324 self.detector.getSerial()) 

325 self.assertEqual(exposure.getFilter().getName(), "g") 

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

327 

328 # The PhotoCalib tests are in test_photoCalib.py; 

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

330 self.assertIsNone(exposure.getPhotoCalib()) 

331 

332 photoCalib = afwImage.PhotoCalib(511.1, 44.4) 

333 exposure.setPhotoCalib(photoCalib) 

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

335 

336 # Psfs next 

337 self.assertFalse(exposure.hasPsf()) 

338 exposure.setPsf(self.psf) 

339 self.assertTrue(exposure.hasPsf()) 

340 

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

342 

343 # extras next 

344 info = exposure.getInfo() 

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

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

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

348 info.setComponent(key, value) 

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

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

351 info.removeComponent(key) 

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

353 

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

355 # that already has both 

356 self.exposureMiWcs.setMaskedImage(maskedImage) 

357 exposure.setWcs(self.wcs) 

358 

359 def testHasWcs(self): 

360 """ 

361 Test if an Exposure has a WCS or not. 

362 """ 

363 self.assertFalse(self.exposureBlank.hasWcs()) 

364 

365 self.assertFalse(self.exposureMiOnly.hasWcs()) 

366 self.assertTrue(self.exposureMiWcs.hasWcs()) 

367 self.assertTrue(self.exposureCrWcs.hasWcs()) 

368 self.assertFalse(self.exposureCrOnly.hasWcs()) 

369 

370 def testGetSubExposure(self): 

371 """ 

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

373 

374 The MaskedImage class should throw a 

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

376 subRegion is not fully contained within the original 

377 MaskedImage. 

378 

379 """ 

380 # 

381 # This subExposure is valid 

382 # 

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

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

385 subExposure = self.exposureCrWcs.Factory( 

386 self.exposureCrWcs, subBBox, afwImage.LOCAL) 

387 

388 self.checkWcs(self.exposureCrWcs, subExposure) 

389 

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

391 # from the MaskedImage class and should trigger an exception 

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

393 

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

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

396 

397 def getSubRegion(): 

398 self.exposureCrWcs.Factory( 

399 self.exposureCrWcs, subRegion3, afwImage.LOCAL) 

400 

401 self.assertRaises(pexExcept.LengthError, getSubRegion) 

402 

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

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

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

406 

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

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

409 

410 def getSubRegion(): 

411 self.exposureCrWcs.Factory( 

412 self.exposureCrWcs, subRegion4, afwImage.LOCAL) 

413 

414 self.assertRaises(pexExcept.LengthError, getSubRegion) 

415 

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

417 # transformation 

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

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

420 subExposure = self.exposureCrWcs.Factory( 

421 self.exposureCrWcs, subBBox, afwImage.LOCAL) 

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

423 

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

425 

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

427 

428 def testReadWriteFits(self): 

429 """Test readFits and writeFits. 

430 """ 

431 # This should pass without an exception 

432 mainExposure = afwImage.ExposureF(inFilePathSmall) 

433 mainExposure.setDetector(self.detector) 

434 

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

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

437 subExposure = mainExposure.Factory( 

438 mainExposure, subBBox, afwImage.LOCAL) 

439 self.checkWcs(mainExposure, subExposure) 

440 det = subExposure.getDetector() 

441 self.assertTrue(det) 

442 

443 subExposure = afwImage.ExposureF( 

444 inFilePathSmall, subBBox, afwImage.LOCAL) 

445 

446 self.checkWcs(mainExposure, subExposure) 

447 

448 # This should throw an exception 

449 def getExposure(): 

450 afwImage.ExposureF(inFilePathSmallImage) 

451 

452 self.assertRaises(FitsError, getExposure) 

453 

454 mainExposure.setPsf(self.psf) 

455 

456 # Make sure we can write without an exception 

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

458 mainExposure.setPhotoCalib(photoCalib) 

459 

460 mainInfo = mainExposure.getInfo() 

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

462 mainInfo.setComponent(key, value) 

463 

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

465 mainExposure.writeFits(tmpFile) 

466 

467 readExposure = type(mainExposure)(tmpFile) 

468 

469 # 

470 # Check the round-tripping 

471 # 

472 self.assertEqual(mainExposure.getFilter().getName(), 

473 readExposure.getFilter().getName()) 

474 

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

476 

477 psf = readExposure.getPsf() 

478 self.assertIsNotNone(psf) 

479 self.assertEqual(psf, self.psf) 

480 

481 # FIXME: This does not pass with conda dependencies. 

482 # readInfo = readExposure.getInfo() 

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

484 # self.assertEqual(value, readInfo.getComponent(key)) 

485 

486 def checkWcs(self, parentExposure, subExposure): 

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

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

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

490 to get the same sky coordinates for each. 

491 """ 

492 subMI = subExposure.getMaskedImage() 

493 subDim = subMI.getDimensions() 

494 

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

496 # with WCS 

497 mainWcs = parentExposure.getWcs() 

498 subWcs = subExposure.getWcs() 

499 

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

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

502 self.assertSpherePointsAlmostEqual( 

503 mainWcs.pixelToSky( 

504 afwImage.indexToPosition(xSubInd), 

505 afwImage.indexToPosition(ySubInd), 

506 ), 

507 subWcs.pixelToSky( 

508 afwImage.indexToPosition(xSubInd), 

509 afwImage.indexToPosition(ySubInd), 

510 )) 

511 

512 def cmpExposure(self, e1, e2): 

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

514 e2.getDetector().getName()) 

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

516 e2.getDetector().getSerial()) 

517 self.assertEqual(e1.getFilter().getName(), e2.getFilter().getName()) 

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

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

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

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

522 # check PSF identity 

523 if not e1.getPsf(): 

524 self.assertFalse(e2.getPsf()) 

525 else: 

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

527 # Check extra components 

528 i1 = e1.getInfo() 

529 i2 = e2.getInfo() 

530 for key in self.extras: 

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

532 if i1.hasComponent(key): 

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

534 

535 def testCopyExposure(self): 

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

537 

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

539 exposureU.setWcs(self.wcs) 

540 exposureU.setDetector(self.detector) 

541 exposureU.setFilter(afwImage.Filter("g")) 

542 exposureU.setPsf(DummyPsf(4.0)) 

543 infoU = exposureU.getInfo() 

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

545 infoU.setComponent(key, value) 

546 

547 exposureF = exposureU.convertF() 

548 self.cmpExposure(exposureF, exposureU) 

549 

550 nexp = exposureF.Factory(exposureF, False) 

551 self.cmpExposure(exposureF, nexp) 

552 

553 # Ensure that the copy was deep. 

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

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

556 # x0,y0 = cen0 

557 # det = exposureF.getDetector() 

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

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

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

561 

562 def testDeepCopyData(self): 

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

564 """ 

565 exp = afwImage.ExposureF(6, 7) 

566 mi = exp.getMaskedImage() 

567 mi.getImage().set(100) 

568 mi.getMask().set(5) 

569 mi.getVariance().set(200) 

570 

571 expCopy = exp.clone() 

572 miCopy = expCopy.getMaskedImage() 

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

574 miCopy.getMask().set(2) 

575 miCopy.getVariance().set(175) 

576 

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

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

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

580 

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

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

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

584 

585 def testDeepCopySubData(self): 

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

587 """ 

588 exp = afwImage.ExposureF(6, 7) 

589 mi = exp.getMaskedImage() 

590 mi.getImage().set(100) 

591 mi.getMask().set(5) 

592 mi.getVariance().set(200) 

593 

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

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

596 miCopy = expCopy.getMaskedImage() 

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

598 miCopy.getMask().set(2) 

599 miCopy.getVariance().set(175) 

600 

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

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

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

604 

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

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

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

608 

609 def testDeepCopyMetadata(self): 

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

611 """ 

612 exp = afwImage.ExposureF(10, 10) 

613 expMeta = exp.getMetadata() 

614 expMeta.set("foo", 5) 

615 expCopy = exp.clone() 

616 expCopyMeta = expCopy.getMetadata() 

617 expCopyMeta.set("foo", 6) 

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

619 # this will fail if the bug is present 

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

621 

622 def testDeepCopySubMetadata(self): 

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

624 """ 

625 exp = afwImage.ExposureF(10, 10) 

626 expMeta = exp.getMetadata() 

627 expMeta.set("foo", 5) 

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

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

630 expCopyMeta = expCopy.getMetadata() 

631 expCopyMeta.set("foo", 6) 

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

633 # this will fail if the bug is present 

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

635 

636 def testMakeExposureLeaks(self): 

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

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

639 afwImage.makeExposure(afwImage.makeMaskedImage( 

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

641 

642 def testImageSlices(self): 

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

644 exp = afwImage.ExposureF(10, 20) 

645 mi = exp.getMaskedImage() 

646 mi.image[9, 19] = 10 

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

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

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

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

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

652 self.assertEqual(sexp.getDimensions(), 

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

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

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

656 

657 def testConversionToScalar(self): 

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

659 im = afwImage.ExposureF(10, 20) 

660 

661 # only single pixel images may be converted 

662 self.assertRaises(TypeError, float, im) 

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

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

665 

666 def testReadMetadata(self): 

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

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

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

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

671 self.exposureCrWcs.writeFits(tmpFile) 

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

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

674 # frazzle and the Wcs from the PropertySet returned by 

675 # testReadMetadata. 

676 md = readMetadata(tmpFile) 

677 wcs = afwGeom.makeSkyWcs(md, False) 

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

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

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

681 frazzle = md.getScalar("FRAZZLE") 

682 self.assertTrue(frazzle) 

683 

684 def testArchiveKeys(self): 

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

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

687 exposure1.setPsf(self.psf) 

688 exposure1.writeFits(tmpFile) 

689 exposure2 = afwImage.ExposureF(tmpFile) 

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

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

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

693 

694 def testTicket2861(self): 

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

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

697 exposure1.setPsf(self.psf) 

698 schema = afwTable.ExposureTable.makeMinimalSchema() 

699 coaddInputs = afwImage.CoaddInputs(schema, schema) 

700 exposure1.getInfo().setCoaddInputs(coaddInputs) 

701 exposure2 = afwImage.ExposureF(exposure1, True) 

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

703 exposure2.writeFits(tmpFile) 

704 exposure3 = afwImage.ExposureF(tmpFile) 

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

706 

707 def testGetCutout(self): 

708 wcs = self.smallExposure.getWcs() 

709 

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

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

712 2*self.smallExposure.getDimensions()] 

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

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

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

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

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

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

719 for cutoutSize in dimensions: 

720 for label, cutoutCenter in locations: 

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

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

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

724 centerInPixels = wcs.skyToPixel(cutoutCenter) 

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

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

727 self._checkCutoutPixels( 

728 cutout, 

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

730 msg) 

731 

732 # Need a valid WCS 

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

734 self.exposureMiOnly.getCutout(cutoutCenter, cutoutSize) 

735 else: 

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

737 self.smallExposure.getCutout(cutoutCenter, cutoutSize) 

738 

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

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

741 

742 Parameters 

743 ---------- 

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

745 The cutout to test. 

746 size : `lsst.geom.Extent2I` 

747 The expected dimensions of ``cutout``. 

748 center : `lsst.geom.SpherePoint` 

749 The expected center of ``cutout``. 

750 precision : `lsst.geom.Angle` 

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

752 msg : `str` 

753 An error message suffix describing test parameters. 

754 """ 

755 newCenter = self._getExposureCenter(cutout) 

756 self.assertIsNotNone(cutout, msg=msg) 

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

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

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

760 

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

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

763 

764 Parameters 

765 ---------- 

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

767 The cutout to test. 

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

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

770 msg : `str` 

771 An error message suffix describing test parameters. 

772 """ 

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

774 edgeMask = mask.getPlaneBitMask("NO_DATA") 

775 

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

777 maskBitsSet = mask[corner] & edgeMask 

778 if corner in validCorners: 

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

780 else: 

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

782 

783 def _getExposureCenter(self, exposure): 

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

785 

786 Parameters 

787 ---------- 

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

789 The image whose center is desired. 

790 

791 Returns 

792 ------- 

793 center : `lsst.geom.SpherePoint` 

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

795 """ 

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

797 

798 def _getValidCorners(self, imageBox, cutoutBox): 

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

800 

801 Parameters 

802 ---------- 

803 imageBox: `lsst.geom.Extent2I` 

804 The bounding box of the original image. 

805 cutoutBox : `lsst.geom.Box2I` 

806 The bounding box of the cutout. 

807 

808 Returns 

809 ------- 

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

811 The corners that are drawn from the original image. 

812 """ 

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

814 

815 

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

817 def setUp(self): 

818 super().setUp() 

819 

820 afwImage.Filter.reset() 

821 afwImage.FilterProperty.reset() 

822 defineFilter("g", 470.0) 

823 

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

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

826 np.identity(2), 

827 ) 

828 self.photoCalib = afwImage.PhotoCalib(1.5) 

829 self.psf = DummyPsf(2.0) 

830 self.detector = DetectorWrapper().detector 

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

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

833 self.coaddInputs = afwImage.CoaddInputs() 

834 self.apCorrMap = afwImage.ApCorrMap() 

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

836 

837 self.exposureInfo = afwImage.ExposureInfo() 

838 gFilter = afwImage.Filter("g") 

839 self.exposureInfo.setFilter(gFilter) 

840 

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

842 self.assertFalse(has()) 

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

844 self.assertIsNone(get()) 

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

846 

847 self.exposureInfo.setComponent(key, value) 

848 self.assertTrue(has()) 

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

850 self.assertIsNotNone(get()) 

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

852 self.assertEqual(get(), value) 

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

854 

855 self.exposureInfo.removeComponent(key) 

856 self.assertFalse(has()) 

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

858 self.assertIsNone(get()) 

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

860 

861 def testAliases(self): 

862 cls = type(self.exposureInfo) 

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

864 self.exposureInfo.hasWcs, self.exposureInfo.getWcs) 

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

866 self.exposureInfo.hasPsf, self.exposureInfo.getPsf) 

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

868 self.exposureInfo.hasPhotoCalib, self.exposureInfo.getPhotoCalib) 

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

870 self.exposureInfo.hasDetector, self.exposureInfo.getDetector) 

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

872 self.exposureInfo.hasValidPolygon, self.exposureInfo.getValidPolygon) 

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

874 self.exposureInfo.hasCoaddInputs, self.exposureInfo.getCoaddInputs) 

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

876 self.exposureInfo.hasApCorrMap, self.exposureInfo.getApCorrMap) 

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

878 self.exposureInfo.hasTransmissionCurve, self.exposureInfo.getTransmissionCurve) 

879 

880 def testCopy(self): 

881 # Test that ExposureInfos have independently settable state 

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

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

884 

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

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

887 np.identity(2), 

888 ) 

889 copy.setWcs(newWcs) 

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

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

892 

893 

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

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

896 

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

898 """ 

899 def setUp(self): 

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

901 

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

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

904 nx = ny = 10 

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

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

907 mask = afwImage.MaskX(nx, ny) 

908 mask.array[5, 5] = 5 

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

910 

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

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

913 

914 def testReadUnversioned(self): 

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

916 """ 

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

918 exposure = afwImage.ExposureF.readFits(filename) 

919 

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

921 

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

923 

924 def testReadVersion0(self): 

925 """Test that we can read an version 0 file. 

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

927 is marked as ExposureInfo version 0 in the header. 

928 """ 

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

930 exposure = afwImage.ExposureF.readFits(filename) 

931 

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

933 

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

935 

936 # Check that the metadata reader parses the file correctly 

937 reader = afwImage.ExposureFitsReader(filename) 

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

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

940 

941 def testReadVersion1(self): 

942 """Test that we can read an version 1 file. 

943 Version 1 replaced Calib with PhotoCalib. 

944 """ 

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

946 exposure = afwImage.ExposureF.readFits(filename) 

947 

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

949 

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

951 

952 # Check that the metadata reader parses the file correctly 

953 reader = afwImage.ExposureFitsReader(filename) 

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

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

956 

957 

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

959 pass 

960 

961 

962def setup_module(module): 

963 lsst.utils.tests.init() 

964 

965 

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

967 lsst.utils.tests.init() 

968 unittest.main()