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 """Test that a WCS can be obtained from each Exposure created with 

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

187 """ 

188 # These exposures don't contain a WCS 

189 self.assertIsNone(self.exposureBlank.getWcs()) 

190 self.assertIsNone(self.exposureMiOnly.getWcs()) 

191 self.assertIsNone(self.exposureCrOnly.getWcs()) 

192 

193 # These exposures should contain a WCS 

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

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

196 

197 def testExposureInfoConstructor(self): 

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

199 exposureInfo = afwImage.ExposureInfo() 

200 exposureInfo.setWcs(self.wcs) 

201 exposureInfo.setDetector(self.detector) 

202 gFilter = afwImage.Filter("g") 

203 exposureInfo.setFilter(gFilter) 

204 maskedImage = afwImage.MaskedImageF(inFilePathSmall) 

205 exposure = afwImage.ExposureF(maskedImage, exposureInfo) 

206 

207 self.assertTrue(exposure.hasWcs()) 

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

209 self.wcs.getPixelOrigin()) 

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

211 self.detector.getName()) 

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

213 self.detector.getSerial()) 

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

215 

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

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

218 self.wcs.getPixelOrigin()) 

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

220 self.detector.getName()) 

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

222 self.detector.getSerial()) 

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

224 

225 def testNullWcs(self): 

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

227 

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

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

230 """ 

231 maskedImage = self.exposureMiOnly.getMaskedImage() 

232 exposure = afwImage.ExposureF(maskedImage, None) 

233 self.assertFalse(exposure.hasWcs()) 

234 self.assertFalse(exposure.hasPsf()) 

235 

236 def testExposureInfoSetNone(self): 

237 exposureInfo = afwImage.ExposureInfo() 

238 exposureInfo.setDetector(None) 

239 exposureInfo.setValidPolygon(None) 

240 exposureInfo.setPsf(None) 

241 exposureInfo.setWcs(None) 

242 exposureInfo.setPhotoCalib(None) 

243 exposureInfo.setCoaddInputs(None) 

244 exposureInfo.setVisitInfo(None) 

245 exposureInfo.setApCorrMap(None) 

246 for key in self.extras: 

247 exposureInfo.setComponent(key, None) 

248 

249 def testSetExposureInfo(self): 

250 exposureInfo = afwImage.ExposureInfo() 

251 exposureInfo.setWcs(self.wcs) 

252 exposureInfo.setDetector(self.detector) 

253 gFilter = afwImage.Filter("g") 

254 exposureInfo.setFilter(gFilter) 

255 maskedImage = afwImage.MaskedImageF(inFilePathSmall) 

256 exposure = afwImage.ExposureF(maskedImage) 

257 self.assertFalse(exposure.hasWcs()) 

258 

259 exposure.setInfo(exposureInfo) 

260 

261 self.assertTrue(exposure.hasWcs()) 

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

263 self.wcs.getPixelOrigin()) 

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

265 self.detector.getName()) 

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

267 self.detector.getSerial()) 

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

269 

270 def testVisitInfoFitsPersistence(self): 

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

272 exposureId = 5 

273 exposureTime = 12.3 

274 boresightRotAngle = 45.6 * lsst.geom.degrees 

275 weather = Weather(1.1, 2.2, 0.3) 

276 visitInfo = afwImage.VisitInfo( 

277 exposureId=exposureId, 

278 exposureTime=exposureTime, 

279 boresightRotAngle=boresightRotAngle, 

280 weather=weather, 

281 ) 

282 photoCalib = afwImage.PhotoCalib(3.4, 5.6) 

283 exposureInfo = afwImage.ExposureInfo() 

284 exposureInfo.setVisitInfo(visitInfo) 

285 exposureInfo.setPhotoCalib(photoCalib) 

286 exposureInfo.setDetector(self.detector) 

287 gFilter = afwImage.Filter("g") 

288 exposureInfo.setFilter(gFilter) 

289 maskedImage = afwImage.MaskedImageF(inFilePathSmall) 

290 exposure = afwImage.ExposureF(maskedImage, exposureInfo) 

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

292 exposure.writeFits(tmpFile) 

293 rtExposure = afwImage.ExposureF(tmpFile) 

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

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

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

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

298 

299 def testSetMembers(self): 

300 """ 

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

302 """ 

303 exposure = afwImage.ExposureF() 

304 

305 maskedImage = afwImage.MaskedImageF(inFilePathSmall) 

306 exposure.setMaskedImage(maskedImage) 

307 exposure.setWcs(self.wcs) 

308 exposure.setDetector(self.detector) 

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

310 

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

312 self.detector.getName()) 

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

314 self.detector.getSerial()) 

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

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

317 

318 # The PhotoCalib tests are in test_photoCalib.py; 

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

320 self.assertIsNone(exposure.getPhotoCalib()) 

321 

322 photoCalib = afwImage.PhotoCalib(511.1, 44.4) 

323 exposure.setPhotoCalib(photoCalib) 

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

325 

326 # Psfs next 

327 self.assertFalse(exposure.hasPsf()) 

328 exposure.setPsf(self.psf) 

329 self.assertTrue(exposure.hasPsf()) 

330 

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

332 

333 # extras next 

334 info = exposure.getInfo() 

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

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

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

338 info.setComponent(key, value) 

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

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

341 info.removeComponent(key) 

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

343 

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

345 # that already has both 

346 self.exposureMiWcs.setMaskedImage(maskedImage) 

347 exposure.setWcs(self.wcs) 

348 

349 def testHasWcs(self): 

350 """ 

351 Test if an Exposure has a WCS or not. 

352 """ 

353 self.assertFalse(self.exposureBlank.hasWcs()) 

354 

355 self.assertFalse(self.exposureMiOnly.hasWcs()) 

356 self.assertTrue(self.exposureMiWcs.hasWcs()) 

357 self.assertTrue(self.exposureCrWcs.hasWcs()) 

358 self.assertFalse(self.exposureCrOnly.hasWcs()) 

359 

360 def testGetSubExposure(self): 

361 """ 

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

363 

364 The MaskedImage class should throw a 

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

366 subRegion is not fully contained within the original 

367 MaskedImage. 

368 

369 """ 

370 # 

371 # This subExposure is valid 

372 # 

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

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

375 subExposure = self.exposureCrWcs.Factory( 

376 self.exposureCrWcs, subBBox, afwImage.LOCAL) 

377 

378 self.checkWcs(self.exposureCrWcs, subExposure) 

379 

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

381 # from the MaskedImage class and should trigger an exception 

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

383 

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

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

386 

387 def getSubRegion(): 

388 self.exposureCrWcs.Factory( 

389 self.exposureCrWcs, subRegion3, afwImage.LOCAL) 

390 

391 self.assertRaises(pexExcept.LengthError, getSubRegion) 

392 

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

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

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

396 

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

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

399 

400 def getSubRegion(): 

401 self.exposureCrWcs.Factory( 

402 self.exposureCrWcs, subRegion4, afwImage.LOCAL) 

403 

404 self.assertRaises(pexExcept.LengthError, getSubRegion) 

405 

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

407 # transformation 

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

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

410 subExposure = self.exposureCrWcs.Factory( 

411 self.exposureCrWcs, subBBox, afwImage.LOCAL) 

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

413 

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

415 

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

417 

418 def testReadWriteFits(self): 

419 """Test readFits and writeFits. 

420 """ 

421 # This should pass without an exception 

422 mainExposure = afwImage.ExposureF(inFilePathSmall) 

423 mainExposure.setDetector(self.detector) 

424 

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

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

427 subExposure = mainExposure.Factory( 

428 mainExposure, subBBox, afwImage.LOCAL) 

429 self.checkWcs(mainExposure, subExposure) 

430 det = subExposure.getDetector() 

431 self.assertTrue(det) 

432 

433 subExposure = afwImage.ExposureF( 

434 inFilePathSmall, subBBox, afwImage.LOCAL) 

435 

436 self.checkWcs(mainExposure, subExposure) 

437 

438 # This should throw an exception 

439 def getExposure(): 

440 afwImage.ExposureF(inFilePathSmallImage) 

441 

442 self.assertRaises(FitsError, getExposure) 

443 

444 mainExposure.setPsf(self.psf) 

445 

446 # Make sure we can write without an exception 

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

448 mainExposure.setPhotoCalib(photoCalib) 

449 

450 mainInfo = mainExposure.getInfo() 

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

452 mainInfo.setComponent(key, value) 

453 

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

455 mainExposure.writeFits(tmpFile) 

456 

457 readExposure = type(mainExposure)(tmpFile) 

458 

459 # 

460 # Check the round-tripping 

461 # 

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

463 readExposure.getFilter().getName()) 

464 

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

466 

467 readInfo = readExposure.getInfo() 

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

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

470 

471 psf = readExposure.getPsf() 

472 self.assertIsNotNone(psf) 

473 self.assertEqual(psf, self.psf) 

474 

475 def checkWcs(self, parentExposure, subExposure): 

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

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

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

479 to get the same sky coordinates for each. 

480 """ 

481 subMI = subExposure.getMaskedImage() 

482 subDim = subMI.getDimensions() 

483 

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

485 # with WCS 

486 mainWcs = parentExposure.getWcs() 

487 subWcs = subExposure.getWcs() 

488 

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

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

491 self.assertSpherePointsAlmostEqual( 

492 mainWcs.pixelToSky( 

493 afwImage.indexToPosition(xSubInd), 

494 afwImage.indexToPosition(ySubInd), 

495 ), 

496 subWcs.pixelToSky( 

497 afwImage.indexToPosition(xSubInd), 

498 afwImage.indexToPosition(ySubInd), 

499 )) 

500 

501 def cmpExposure(self, e1, e2): 

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

503 e2.getDetector().getName()) 

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

505 e2.getDetector().getSerial()) 

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

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

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

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

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

511 # check PSF identity 

512 if not e1.getPsf(): 

513 self.assertFalse(e2.getPsf()) 

514 else: 

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

516 # Check extra components 

517 i1 = e1.getInfo() 

518 i2 = e2.getInfo() 

519 for key in self.extras: 

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

521 if i1.hasComponent(key): 

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

523 

524 def testCopyExposure(self): 

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

526 

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

528 exposureU.setWcs(self.wcs) 

529 exposureU.setDetector(self.detector) 

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

531 exposureU.setPsf(DummyPsf(4.0)) 

532 infoU = exposureU.getInfo() 

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

534 infoU.setComponent(key, value) 

535 

536 exposureF = exposureU.convertF() 

537 self.cmpExposure(exposureF, exposureU) 

538 

539 nexp = exposureF.Factory(exposureF, False) 

540 self.cmpExposure(exposureF, nexp) 

541 

542 # Ensure that the copy was deep. 

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

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

545 # x0,y0 = cen0 

546 # det = exposureF.getDetector() 

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

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

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

550 

551 def testDeepCopyData(self): 

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

553 """ 

554 exp = afwImage.ExposureF(6, 7) 

555 mi = exp.getMaskedImage() 

556 mi.getImage().set(100) 

557 mi.getMask().set(5) 

558 mi.getVariance().set(200) 

559 

560 expCopy = exp.clone() 

561 miCopy = expCopy.getMaskedImage() 

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

563 miCopy.getMask().set(2) 

564 miCopy.getVariance().set(175) 

565 

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

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

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

569 

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

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

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

573 

574 def testDeepCopySubData(self): 

575 """Make sure a deep copy of a subregion 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 bbox = lsst.geom.Box2I(lsst.geom.Point2I(1, 0), lsst.geom.Extent2I(5, 4)) 

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

585 miCopy = expCopy.getMaskedImage() 

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

587 miCopy.getMask().set(2) 

588 miCopy.getVariance().set(175) 

589 

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

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

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

593 

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

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

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

597 

598 def testDeepCopyMetadata(self): 

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

600 """ 

601 exp = afwImage.ExposureF(10, 10) 

602 expMeta = exp.getMetadata() 

603 expMeta.set("foo", 5) 

604 expCopy = exp.clone() 

605 expCopyMeta = expCopy.getMetadata() 

606 expCopyMeta.set("foo", 6) 

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

608 # this will fail if the bug is present 

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

610 

611 def testDeepCopySubMetadata(self): 

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

613 """ 

614 exp = afwImage.ExposureF(10, 10) 

615 expMeta = exp.getMetadata() 

616 expMeta.set("foo", 5) 

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

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

619 expCopyMeta = expCopy.getMetadata() 

620 expCopyMeta.set("foo", 6) 

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

622 # this will fail if the bug is present 

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

624 

625 def testMakeExposureLeaks(self): 

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

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

628 afwImage.makeExposure(afwImage.makeMaskedImage( 

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

630 

631 def testImageSlices(self): 

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

633 exp = afwImage.ExposureF(10, 20) 

634 mi = exp.getMaskedImage() 

635 mi.image[9, 19] = 10 

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

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

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

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

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

641 self.assertEqual(sexp.getDimensions(), 

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

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

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

645 

646 def testConversionToScalar(self): 

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

648 im = afwImage.ExposureF(10, 20) 

649 

650 # only single pixel images may be converted 

651 self.assertRaises(TypeError, float, im) 

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

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

654 

655 def testReadMetadata(self): 

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

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

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

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

660 self.exposureCrWcs.writeFits(tmpFile) 

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

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

663 # frazzle and the Wcs from the PropertySet returned by 

664 # testReadMetadata. 

665 md = readMetadata(tmpFile) 

666 wcs = afwGeom.makeSkyWcs(md, False) 

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

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

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

670 frazzle = md.getScalar("FRAZZLE") 

671 self.assertTrue(frazzle) 

672 

673 def testArchiveKeys(self): 

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

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

676 exposure1.setPsf(self.psf) 

677 exposure1.writeFits(tmpFile) 

678 exposure2 = afwImage.ExposureF(tmpFile) 

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

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

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

682 

683 def testTicket2861(self): 

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

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

686 exposure1.setPsf(self.psf) 

687 schema = afwTable.ExposureTable.makeMinimalSchema() 

688 coaddInputs = afwImage.CoaddInputs(schema, schema) 

689 exposure1.getInfo().setCoaddInputs(coaddInputs) 

690 exposure2 = afwImage.ExposureF(exposure1, True) 

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

692 exposure2.writeFits(tmpFile) 

693 exposure3 = afwImage.ExposureF(tmpFile) 

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

695 

696 def testGetCutout(self): 

697 wcs = self.smallExposure.getWcs() 

698 

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

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

701 2*self.smallExposure.getDimensions()] 

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

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

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

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

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

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

708 for cutoutSize in dimensions: 

709 for label, cutoutCenter in locations: 

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

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

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

713 centerInPixels = wcs.skyToPixel(cutoutCenter) 

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

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

716 self._checkCutoutPixels( 

717 cutout, 

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

719 msg) 

720 

721 # Need a valid WCS 

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

723 self.exposureMiOnly.getCutout(cutoutCenter, cutoutSize) 

724 else: 

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

726 self.smallExposure.getCutout(cutoutCenter, cutoutSize) 

727 

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

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

730 

731 Parameters 

732 ---------- 

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

734 The cutout to test. 

735 size : `lsst.geom.Extent2I` 

736 The expected dimensions of ``cutout``. 

737 center : `lsst.geom.SpherePoint` 

738 The expected center of ``cutout``. 

739 precision : `lsst.geom.Angle` 

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

741 msg : `str` 

742 An error message suffix describing test parameters. 

743 """ 

744 newCenter = self._getExposureCenter(cutout) 

745 self.assertIsNotNone(cutout, msg=msg) 

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

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

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

749 

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

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

752 

753 Parameters 

754 ---------- 

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

756 The cutout to test. 

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

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

759 msg : `str` 

760 An error message suffix describing test parameters. 

761 """ 

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

763 edgeMask = mask.getPlaneBitMask("NO_DATA") 

764 

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

766 maskBitsSet = mask[corner] & edgeMask 

767 if corner in validCorners: 

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

769 else: 

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

771 

772 def _getExposureCenter(self, exposure): 

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

774 

775 Parameters 

776 ---------- 

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

778 The image whose center is desired. 

779 

780 Returns 

781 ------- 

782 center : `lsst.geom.SpherePoint` 

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

784 """ 

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

786 

787 def _getValidCorners(self, imageBox, cutoutBox): 

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

789 

790 Parameters 

791 ---------- 

792 imageBox: `lsst.geom.Extent2I` 

793 The bounding box of the original image. 

794 cutoutBox : `lsst.geom.Box2I` 

795 The bounding box of the cutout. 

796 

797 Returns 

798 ------- 

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

800 The corners that are drawn from the original image. 

801 """ 

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

803 

804 

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

806 def setUp(self): 

807 super().setUp() 

808 

809 afwImage.Filter.reset() 

810 afwImage.FilterProperty.reset() 

811 defineFilter("g", 470.0) 

812 

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

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

815 np.identity(2), 

816 ) 

817 self.photoCalib = afwImage.PhotoCalib(1.5) 

818 self.psf = DummyPsf(2.0) 

819 self.detector = DetectorWrapper().detector 

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

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

822 self.coaddInputs = afwImage.CoaddInputs() 

823 self.apCorrMap = afwImage.ApCorrMap() 

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

825 

826 self.exposureInfo = afwImage.ExposureInfo() 

827 gFilter = afwImage.Filter("g") 

828 self.exposureInfo.setFilter(gFilter) 

829 

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

831 self.assertFalse(has()) 

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

833 self.assertIsNone(get()) 

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

835 

836 self.exposureInfo.setComponent(key, value) 

837 self.assertTrue(has()) 

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

839 self.assertIsNotNone(get()) 

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

841 self.assertEqual(get(), value) 

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

843 

844 self.exposureInfo.removeComponent(key) 

845 self.assertFalse(has()) 

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

847 self.assertIsNone(get()) 

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

849 

850 def testAliases(self): 

851 cls = type(self.exposureInfo) 

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

853 self.exposureInfo.hasWcs, self.exposureInfo.getWcs) 

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

855 self.exposureInfo.hasPsf, self.exposureInfo.getPsf) 

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

857 self.exposureInfo.hasPhotoCalib, self.exposureInfo.getPhotoCalib) 

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

859 self.exposureInfo.hasDetector, self.exposureInfo.getDetector) 

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

861 self.exposureInfo.hasValidPolygon, self.exposureInfo.getValidPolygon) 

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

863 self.exposureInfo.hasCoaddInputs, self.exposureInfo.getCoaddInputs) 

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

865 self.exposureInfo.hasApCorrMap, self.exposureInfo.getApCorrMap) 

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

867 self.exposureInfo.hasTransmissionCurve, self.exposureInfo.getTransmissionCurve) 

868 

869 def testCopy(self): 

870 # Test that ExposureInfos have independently settable state 

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

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

873 

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

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

876 np.identity(2), 

877 ) 

878 copy.setWcs(newWcs) 

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

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

881 

882 

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

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

885 

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

887 """ 

888 def setUp(self): 

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

890 

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

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

893 nx = ny = 10 

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

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

896 mask = afwImage.MaskX(nx, ny) 

897 mask.array[5, 5] = 5 

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

899 

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

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

902 

903 def testReadUnversioned(self): 

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

905 """ 

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

907 exposure = afwImage.ExposureF.readFits(filename) 

908 

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

910 

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

912 

913 def testReadVersion0(self): 

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

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

916 is marked as ExposureInfo version 0 in the header. 

917 """ 

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

919 exposure = afwImage.ExposureF.readFits(filename) 

920 

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

922 

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

924 

925 # Check that the metadata reader parses the file correctly 

926 reader = afwImage.ExposureFitsReader(filename) 

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

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

929 

930 def testReadVersion1(self): 

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

932 Version 1 replaced Calib with PhotoCalib. 

933 """ 

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

935 exposure = afwImage.ExposureF.readFits(filename) 

936 

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

938 

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

940 

941 # Check that the metadata reader parses the file correctly 

942 reader = afwImage.ExposureFitsReader(filename) 

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

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

945 

946 

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

948 pass 

949 

950 

951def setup_module(module): 

952 lsst.utils.tests.init() 

953 

954 

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

956 lsst.utils.tests.init() 

957 unittest.main()