Coverage for tests / test_exposure.py: 12%
724 statements
« prev ^ index » next coverage.py v7.14.0, created at 2026-05-21 01:29 -0700
« prev ^ index » next coverage.py v7.14.0, created at 2026-05-21 01:29 -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/>.
22"""
23Test lsst.afw.image.Exposure
24"""
26import dataclasses
27import os.path
28import unittest
29import warnings
31import numpy as np
32from numpy.testing import assert_allclose
33import yaml
34import astropy.units as units
35import astropy.io.fits
37import lsst.utils
38import lsst.utils.tests
39import lsst.geom
40import lsst.afw.image as afwImage
41from lsst.afw.coord import Weather
42import lsst.afw.geom as afwGeom
43import lsst.afw.table as afwTable
44import lsst.pex.exceptions as pexExcept
45from lsst.afw.fits import readMetadata, FitsError
46from lsst.afw.cameraGeom.testUtils import DetectorWrapper
47from lsst.daf.base import PropertyList
48from lsst.log import Log
49from testTableArchivesLib import DummyPsf
51TESTDIR = os.path.abspath(os.path.dirname(__file__))
52Log.getLogger("lsst.afw.image.Mask").setLevel(Log.INFO)
54try:
55 dataDir = os.path.join(lsst.utils.getPackageDir("afwdata"), "data")
56except LookupError:
57 dataDir = None
58else:
59 InputMaskedImageName = "871034p_1_MI.fits"
60 InputMaskedImageNameSmall = "small_MI.fits"
61 InputImageNameSmall = "small"
62 OutputMaskedImageName = "871034p_1_MInew.fits"
64 currDir = os.path.abspath(os.path.dirname(__file__))
65 inFilePath = os.path.join(dataDir, InputMaskedImageName)
66 inFilePathSmall = os.path.join(dataDir, InputMaskedImageNameSmall)
67 inFilePathSmallImage = os.path.join(dataDir, InputImageNameSmall)
70@unittest.skipIf(dataDir is None, "afwdata not setup")
71class ExposureTestCase(lsst.utils.tests.TestCase):
72 """
73 A test case for the Exposure Class
74 """
76 def setUp(self):
77 maskedImage = afwImage.MaskedImageF(inFilePathSmall)
78 maskedImageMD = readMetadata(inFilePathSmall)
80 self.smallExposure = afwImage.ExposureF(inFilePathSmall)
81 self.width = maskedImage.getWidth()
82 self.height = maskedImage.getHeight()
83 self.wcs = afwGeom.makeSkyWcs(maskedImageMD, False)
84 self.md = maskedImageMD
85 self.psf = DummyPsf(2.0)
86 self.detector = DetectorWrapper().detector
87 self.id = 42
88 self.extras = {"MISC": DummyPsf(3.5)}
90 self.exposureBlank = afwImage.ExposureF()
91 self.exposureMiOnly = afwImage.makeExposure(maskedImage)
92 self.exposureMiWcs = afwImage.makeExposure(maskedImage, self.wcs)
93 # n.b. the (100, 100, ...) form
94 self.exposureCrWcs = afwImage.ExposureF(100, 100, self.wcs)
95 # test with ExtentI(100, 100) too
96 self.exposureCrOnly = afwImage.ExposureF(lsst.geom.ExtentI(100, 100))
98 def tearDown(self):
99 del self.smallExposure
100 del self.wcs
101 del self.psf
102 del self.detector
103 del self.extras
105 del self.exposureBlank
106 del self.exposureMiOnly
107 del self.exposureMiWcs
108 del self.exposureCrWcs
109 del self.exposureCrOnly
111 def testGetMaskedImage(self):
112 """
113 Test to ensure a MaskedImage can be obtained from each
114 Exposure. An Exposure is required to have a MaskedImage,
115 therefore each of the Exposures should return a MaskedImage.
117 MaskedImage class should throw appropriate
118 lsst::pex::exceptions::NotFound if the MaskedImage can not be
119 obtained.
120 """
121 maskedImageBlank = self.exposureBlank.getMaskedImage()
122 blankWidth = maskedImageBlank.getWidth()
123 blankHeight = maskedImageBlank.getHeight()
124 if blankWidth != blankHeight != 0:
125 self.fail(f"{blankWidth} = {blankHeight} != 0")
127 maskedImageMiOnly = self.exposureMiOnly.getMaskedImage()
128 miOnlyWidth = maskedImageMiOnly.getWidth()
129 miOnlyHeight = maskedImageMiOnly.getHeight()
130 self.assertAlmostEqual(miOnlyWidth, self.width)
131 self.assertAlmostEqual(miOnlyHeight, self.height)
133 # NOTE: Unittests for Exposures created from a MaskedImage and
134 # a WCS object are incomplete. No way to test the validity of
135 # the WCS being copied/created.
137 maskedImageMiWcs = self.exposureMiWcs.getMaskedImage()
138 miWcsWidth = maskedImageMiWcs.getWidth()
139 miWcsHeight = maskedImageMiWcs.getHeight()
140 self.assertAlmostEqual(miWcsWidth, self.width)
141 self.assertAlmostEqual(miWcsHeight, self.height)
143 maskedImageCrWcs = self.exposureCrWcs.getMaskedImage()
144 crWcsWidth = maskedImageCrWcs.getWidth()
145 crWcsHeight = maskedImageCrWcs.getHeight()
146 if crWcsWidth != crWcsHeight != 0:
147 self.fail(f"{crWcsWidth} != {crWcsHeight} != 0")
149 maskedImageCrOnly = self.exposureCrOnly.getMaskedImage()
150 crOnlyWidth = maskedImageCrOnly.getWidth()
151 crOnlyHeight = maskedImageCrOnly.getHeight()
152 if crOnlyWidth != crOnlyHeight != 0:
153 self.fail(f"{crOnlyWidth} != {crOnlyHeight} != 0")
155 # Check Exposure.getWidth() returns the MaskedImage's width
156 self.assertEqual(crOnlyWidth, self.exposureCrOnly.getWidth())
157 self.assertEqual(crOnlyHeight, self.exposureCrOnly.getHeight())
158 # check width/height properties
159 self.assertEqual(crOnlyWidth, self.exposureCrOnly.width)
160 self.assertEqual(crOnlyHeight, self.exposureCrOnly.height)
162 def testProperties(self):
163 self.assertMaskedImagesEqual(self.exposureMiOnly.maskedImage,
164 self.exposureMiOnly.getMaskedImage())
165 mi2 = afwImage.MaskedImageF(self.exposureMiOnly.getDimensions())
166 mi2.image.array[:] = 5.0
167 mi2.variance.array[:] = 3.0
168 mi2.mask.array[:] = 0x1
169 self.exposureMiOnly.maskedImage = mi2
170 self.assertMaskedImagesEqual(self.exposureMiOnly.maskedImage, mi2)
171 self.assertImagesEqual(self.exposureMiOnly.image,
172 self.exposureMiOnly.maskedImage.image)
174 image3 = afwImage.ImageF(self.exposureMiOnly.getDimensions())
175 image3.array[:] = 3.0
176 self.exposureMiOnly.image = image3
177 self.assertImagesEqual(self.exposureMiOnly.image, image3)
179 mask3 = afwImage.MaskX(self.exposureMiOnly.getDimensions())
180 mask3.array[:] = 0x2
181 self.exposureMiOnly.mask = mask3
182 self.assertMasksEqual(self.exposureMiOnly.mask, mask3)
184 var3 = afwImage.ImageF(self.exposureMiOnly.getDimensions())
185 var3.array[:] = 2.0
186 self.exposureMiOnly.variance = var3
187 self.assertImagesEqual(self.exposureMiOnly.variance, var3)
189 # Test the property getter for a null VisitInfo.
190 self.assertIsNone(self.exposureMiOnly.visitInfo)
192 def testGetWcs(self):
193 """Test that a WCS can be obtained from each Exposure created with
194 a WCS, and that an Exposure lacking a WCS returns None.
195 """
196 # These exposures don't contain a WCS
197 self.assertIsNone(self.exposureBlank.getWcs())
198 self.assertIsNone(self.exposureMiOnly.getWcs())
199 self.assertIsNone(self.exposureCrOnly.getWcs())
201 # These exposures should contain a WCS
202 self.assertEqual(self.wcs, self.exposureMiWcs.getWcs())
203 self.assertEqual(self.wcs, self.exposureCrWcs.getWcs())
205 def testExposureInfoConstructor(self):
206 """Test the Exposure(maskedImage, exposureInfo) constructor"""
207 exposureInfo = afwImage.ExposureInfo()
208 exposureInfo.setWcs(self.wcs)
209 exposureInfo.setDetector(self.detector)
210 gFilterLabel = afwImage.FilterLabel(band="g")
211 exposureInfo.setFilter(gFilterLabel)
212 maskedImage = afwImage.MaskedImageF(inFilePathSmall)
213 exposure = afwImage.ExposureF(maskedImage, exposureInfo)
215 self.assertTrue(exposure.hasWcs())
216 self.assertEqual(exposure.getWcs().getPixelOrigin(),
217 self.wcs.getPixelOrigin())
218 self.assertEqual(exposure.getDetector().getName(),
219 self.detector.getName())
220 self.assertEqual(exposure.getDetector().getSerial(),
221 self.detector.getSerial())
222 self.assertEqual(exposure.getFilter(), gFilterLabel)
224 self.assertTrue(exposure.getInfo().hasWcs())
225 # check the ExposureInfo property
226 self.assertTrue(exposure.info.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(), gFilterLabel)
235 def testNullWcs(self):
236 """Test that an Exposure constructed with second argument None is usable
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())
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)
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())
270 exposure.setInfo(exposureInfo)
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)
281 # test properties
282 self.assertEqual(exposure.detector.getName(), self.detector.getName())
283 self.assertEqual(exposure.filter, gFilterLabel)
284 self.assertEqual(exposure.wcs, self.wcs)
286 def testVisitInfoFitsPersistence(self):
287 """Test saving an exposure to FITS and reading it back in preserves (some) VisitInfo fields"""
288 exposureTime = 12.3
289 boresightRotAngle = 45.6 * lsst.geom.degrees
290 weather = Weather(1.1, 2.2, 0.3)
291 visitInfo = afwImage.VisitInfo(
292 exposureTime=exposureTime,
293 boresightRotAngle=boresightRotAngle,
294 weather=weather,
295 )
296 photoCalib = afwImage.PhotoCalib(3.4, 5.6)
297 exposureInfo = afwImage.ExposureInfo()
298 exposureInfo.setVisitInfo(visitInfo)
299 exposureInfo.setPhotoCalib(photoCalib)
300 exposureInfo.setDetector(self.detector)
301 gFilterLabel = afwImage.FilterLabel(band="g")
302 exposureInfo.setFilter(gFilterLabel)
303 maskedImage = afwImage.MaskedImageF(inFilePathSmall)
304 exposure = afwImage.ExposureF(maskedImage, exposureInfo)
305 with lsst.utils.tests.getTempFilePath(".fits") as tmpFile:
306 exposure.writeFits(tmpFile)
307 rtExposure = afwImage.ExposureF(tmpFile)
308 rtVisitInfo = rtExposure.getInfo().getVisitInfo()
309 self.assertEqual(rtVisitInfo.getWeather(), weather)
310 self.assertEqual(rtExposure.getPhotoCalib(), photoCalib)
311 self.assertEqual(rtExposure.getFilter(), gFilterLabel)
313 # Test property getters.
314 self.assertEqual(rtExposure.photoCalib, photoCalib)
315 self.assertEqual(rtExposure.filter, gFilterLabel)
316 # NOTE: we can't test visitInfo equality, because most fields are NaN.
317 self.assertIsNotNone(rtExposure.visitInfo)
319 def testSetMembers(self):
320 """
321 Test that the MaskedImage and the WCS of an Exposure can be set.
322 """
323 exposure = afwImage.ExposureF()
325 maskedImage = afwImage.MaskedImageF(inFilePathSmall)
326 exposure.setMaskedImage(maskedImage)
327 exposure.setWcs(self.wcs)
328 exposure.setDetector(self.detector)
329 exposure.setFilter(afwImage.FilterLabel(band="g"))
331 self.assertEqual(exposure.getDetector().getName(),
332 self.detector.getName())
333 self.assertEqual(exposure.getDetector().getSerial(),
334 self.detector.getSerial())
335 self.assertEqual(exposure.getFilter().bandLabel, "g")
336 self.assertEqual(exposure.getWcs(), self.wcs)
338 # The PhotoCalib tests are in test_photoCalib.py;
339 # here we just check that it's gettable and settable.
340 self.assertIsNone(exposure.getPhotoCalib())
342 photoCalib = afwImage.PhotoCalib(511.1, 44.4)
343 exposure.setPhotoCalib(photoCalib)
344 self.assertEqual(exposure.getPhotoCalib(), photoCalib)
346 # Psfs next
347 self.assertFalse(exposure.hasPsf())
348 exposure.setPsf(self.psf)
349 self.assertTrue(exposure.hasPsf())
351 exposure.setPsf(DummyPsf(1.0)) # we can reset the Psf
353 # extras next
354 info = exposure.getInfo()
355 for key, value in self.extras.items():
356 self.assertFalse(info.hasComponent(key))
357 self.assertIsNone(info.getComponent(key))
358 info.setComponent(key, value)
359 self.assertTrue(info.hasComponent(key))
360 self.assertEqual(info.getComponent(key), value)
361 info.removeComponent(key)
362 self.assertFalse(info.hasComponent(key))
364 # Test that we can set the MaskedImage and WCS of an Exposure
365 # that already has both
366 self.exposureMiWcs.setMaskedImage(maskedImage)
367 exposure.setWcs(self.wcs)
369 def testHasWcs(self):
370 """
371 Test if an Exposure has a WCS or not.
372 """
373 self.assertFalse(self.exposureBlank.hasWcs())
375 self.assertFalse(self.exposureMiOnly.hasWcs())
376 self.assertTrue(self.exposureMiWcs.hasWcs())
377 self.assertTrue(self.exposureCrWcs.hasWcs())
378 self.assertFalse(self.exposureCrOnly.hasWcs())
380 def testGetSubExposure(self):
381 """
382 Test that a subExposure of the original Exposure can be obtained.
384 The MaskedImage class should throw a
385 lsst::pex::exceptions::InvalidParameter if the requested
386 subRegion is not fully contained within the original
387 MaskedImage.
389 """
390 #
391 # This subExposure is valid
392 #
393 subBBox = lsst.geom.Box2I(lsst.geom.Point2I(40, 50),
394 lsst.geom.Extent2I(10, 10))
395 subExposure = self.exposureCrWcs.Factory(
396 self.exposureCrWcs, subBBox, afwImage.LOCAL)
398 self.checkWcs(self.exposureCrWcs, subExposure)
400 # this subRegion is not valid and should trigger an exception
401 # from the MaskedImage class and should trigger an exception
402 # from the WCS class for the MaskedImage 871034p_1_MI.
404 subRegion3 = lsst.geom.Box2I(lsst.geom.Point2I(100, 100),
405 lsst.geom.Extent2I(10, 10))
407 def getSubRegion():
408 self.exposureCrWcs.Factory(
409 self.exposureCrWcs, subRegion3, afwImage.LOCAL)
411 self.assertRaises(pexExcept.LengthError, getSubRegion)
413 # this subRegion is not valid and should trigger an exception
414 # from the MaskedImage class only for the MaskedImage small_MI.
415 # small_MI (cols, rows) = (256, 256)
417 subRegion4 = lsst.geom.Box2I(lsst.geom.Point2I(250, 250),
418 lsst.geom.Extent2I(10, 10))
420 def getSubRegion():
421 self.exposureCrWcs.Factory(
422 self.exposureCrWcs, subRegion4, afwImage.LOCAL)
424 self.assertRaises(pexExcept.LengthError, getSubRegion)
426 # check the sub- and parent- exposures are using the same Wcs
427 # transformation
428 subBBox = lsst.geom.Box2I(lsst.geom.Point2I(40, 50),
429 lsst.geom.Extent2I(10, 10))
430 subExposure = self.exposureCrWcs.Factory(
431 self.exposureCrWcs, subBBox, afwImage.LOCAL)
432 parentSkyPos = self.exposureCrWcs.getWcs().pixelToSky(0, 0)
434 subExpSkyPos = subExposure.getWcs().pixelToSky(0, 0)
436 self.assertSpherePointsAlmostEqual(parentSkyPos, subExpSkyPos, msg="Wcs in sub image has changed")
438 def testReadWriteFits(self):
439 """Test readFits and writeFits.
440 """
441 # This should pass without an exception
442 mainExposure = afwImage.ExposureF(inFilePathSmall)
443 mainExposure.info.setId(self.id)
444 mainExposure.setDetector(self.detector)
446 subBBox = lsst.geom.Box2I(lsst.geom.Point2I(10, 10),
447 lsst.geom.Extent2I(40, 50))
448 subExposure = mainExposure.Factory(
449 mainExposure, subBBox, afwImage.LOCAL)
450 self.checkWcs(mainExposure, subExposure)
451 det = subExposure.getDetector()
452 self.assertTrue(det)
454 subExposure = afwImage.ExposureF(
455 inFilePathSmall, subBBox, afwImage.LOCAL)
457 self.checkWcs(mainExposure, subExposure)
459 # This should throw an exception
460 def getExposure():
461 afwImage.ExposureF(inFilePathSmallImage)
463 self.assertRaises(FitsError, getExposure)
465 mainExposure.setPsf(self.psf)
467 # Make sure we can write without an exception
468 photoCalib = afwImage.PhotoCalib(1e-10, 1e-12)
469 mainExposure.setPhotoCalib(photoCalib)
471 mainInfo = mainExposure.getInfo()
472 for key, value in self.extras.items():
473 mainInfo.setComponent(key, value)
475 with lsst.utils.tests.getTempFilePath(".fits") as tmpFile:
476 mainExposure.writeFits(tmpFile)
478 readExposure = type(mainExposure)(tmpFile)
480 #
481 # Check the round-tripping
482 #
483 self.assertIsNotNone(mainExposure.getFilter())
484 self.assertEqual(mainExposure.getFilter(),
485 readExposure.getFilter())
487 self.assertEqual(photoCalib, readExposure.getPhotoCalib())
489 readInfo = readExposure.getInfo()
490 self.assertEqual(mainExposure.info.getId(), readInfo.id)
491 for key, value in self.extras.items():
492 self.assertEqual(value, readInfo.getComponent(key))
494 psf = readExposure.getPsf()
495 self.assertIsNotNone(psf)
496 self.assertEqual(psf, self.psf)
497 # check psf property getter
498 self.assertEqual(readExposure.psf, self.psf)
500 def checkWcs(self, parentExposure, subExposure):
501 """Compare WCS at corner points of a sub-exposure and its parent exposure
502 By using the function indexToPosition, we should be able to convert the indices
503 (of the four corners (of the sub-exposure)) to positions and use the wcs
504 to get the same sky coordinates for each.
505 """
506 subMI = subExposure.getMaskedImage()
507 subDim = subMI.getDimensions()
509 # Note: pixel positions must be computed relative to XY0 when working
510 # with WCS
511 mainWcs = parentExposure.getWcs()
512 subWcs = subExposure.getWcs()
514 for xSubInd in (0, subDim.getX()-1):
515 for ySubInd in (0, subDim.getY()-1):
516 self.assertSpherePointsAlmostEqual(
517 mainWcs.pixelToSky(
518 afwImage.indexToPosition(xSubInd),
519 afwImage.indexToPosition(ySubInd),
520 ),
521 subWcs.pixelToSky(
522 afwImage.indexToPosition(xSubInd),
523 afwImage.indexToPosition(ySubInd),
524 ))
526 def cmpExposure(self, e1, e2):
527 self.assertEqual(e1.getDetector().getName(),
528 e2.getDetector().getName())
529 self.assertEqual(e1.getDetector().getSerial(),
530 e2.getDetector().getSerial())
531 self.assertEqual(e1.getFilter(), e2.getFilter())
532 xy = lsst.geom.Point2D(0, 0)
533 self.assertEqual(e1.getWcs().pixelToSky(xy)[0],
534 e2.getWcs().pixelToSky(xy)[0])
535 self.assertEqual(e1.getPhotoCalib(), e2.getPhotoCalib())
536 # check PSF identity
537 if not e1.getPsf():
538 self.assertFalse(e2.getPsf())
539 else:
540 self.assertEqual(e1.getPsf(), e2.getPsf())
541 # Check extra components
542 i1 = e1.getInfo()
543 i2 = e2.getInfo()
544 for key in self.extras:
545 self.assertEqual(i1.hasComponent(key), i2.hasComponent(key))
546 if i1.hasComponent(key):
547 self.assertEqual(i1.getComponent(key), i2.getComponent(key))
549 def testCopyExposure(self):
550 """Copy an Exposure (maybe changing type)"""
552 exposureU = afwImage.ExposureU(inFilePathSmall, allowUnsafe=True)
553 exposureU.setWcs(self.wcs)
554 exposureU.setDetector(self.detector)
555 exposureU.setFilter(afwImage.FilterLabel(band="g"))
556 exposureU.setPsf(DummyPsf(4.0))
557 infoU = exposureU.getInfo()
558 for key, value in self.extras.items():
559 infoU.setComponent(key, value)
561 exposureF = exposureU.convertF()
562 self.cmpExposure(exposureF, exposureU)
564 nexp = exposureF.Factory(exposureF, False)
565 self.cmpExposure(exposureF, nexp)
567 # Ensure that the copy was deep.
568 # (actually this test is invalid since getDetector() returns a shared_ptr)
569 # cen0 = exposureU.getDetector().getCenterPixel()
570 # x0,y0 = cen0
571 # det = exposureF.getDetector()
572 # det.setCenterPixel(lsst.geom.Point2D(999.0, 437.8))
573 # self.assertEqual(exposureU.getDetector().getCenterPixel()[0], x0)
574 # self.assertEqual(exposureU.getDetector().getCenterPixel()[1], y0)
576 def testDeepCopyData(self):
577 """Make sure a deep copy of an Exposure has its own data (ticket #2625)
578 """
579 exp = afwImage.ExposureF(6, 7)
580 mi = exp.getMaskedImage()
581 mi.getImage().set(100)
582 mi.getMask().set(5)
583 mi.getVariance().set(200)
585 expCopy = exp.clone()
586 miCopy = expCopy.getMaskedImage()
587 miCopy.getImage().set(-50)
588 miCopy.getMask().set(2)
589 miCopy.getVariance().set(175)
591 self.assertFloatsAlmostEqual(miCopy.getImage().getArray(), -50)
592 self.assertTrue(np.all(miCopy.getMask().getArray() == 2))
593 self.assertFloatsAlmostEqual(miCopy.getVariance().getArray(), 175)
595 self.assertFloatsAlmostEqual(mi.getImage().getArray(), 100)
596 self.assertTrue(np.all(mi.getMask().getArray() == 5))
597 self.assertFloatsAlmostEqual(mi.getVariance().getArray(), 200)
599 def testDeepCopySubData(self):
600 """Make sure a deep copy of a subregion of an Exposure has its own data (ticket #2625)
601 """
602 exp = afwImage.ExposureF(6, 7)
603 mi = exp.getMaskedImage()
604 mi.getImage().set(100)
605 mi.getMask().set(5)
606 mi.getVariance().set(200)
608 bbox = lsst.geom.Box2I(lsst.geom.Point2I(1, 0), lsst.geom.Extent2I(5, 4))
609 expCopy = exp.Factory(exp, bbox, afwImage.PARENT, True)
610 miCopy = expCopy.getMaskedImage()
611 miCopy.getImage().set(-50)
612 miCopy.getMask().set(2)
613 miCopy.getVariance().set(175)
615 self.assertFloatsAlmostEqual(miCopy.getImage().getArray(), -50)
616 self.assertTrue(np.all(miCopy.getMask().getArray() == 2))
617 self.assertFloatsAlmostEqual(miCopy.getVariance().getArray(), 175)
619 self.assertFloatsAlmostEqual(mi.getImage().getArray(), 100)
620 self.assertTrue(np.all(mi.getMask().getArray() == 5))
621 self.assertFloatsAlmostEqual(mi.getVariance().getArray(), 200)
623 def testDeepCopyMetadata(self):
624 """Make sure a deep copy of an Exposure has a deep copy of metadata (ticket #2568)
625 """
626 exp = afwImage.ExposureF(10, 10)
627 expMeta = exp.getMetadata()
628 expMeta.set("foo", 5)
629 expCopy = exp.clone()
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)
636 def testDeepCopySubMetadata(self):
637 """Make sure a deep copy of a subregion of an Exposure has a deep copy of metadata (ticket #2568)
638 """
639 exp = afwImage.ExposureF(10, 10)
640 expMeta = exp.getMetadata()
641 expMeta.set("foo", 5)
642 bbox = lsst.geom.Box2I(lsst.geom.Point2I(1, 0), lsst.geom.Extent2I(5, 5))
643 expCopy = exp.Factory(exp, bbox, afwImage.PARENT, True)
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)
650 def testMakeExposureLeaks(self):
651 """Test for memory leaks in makeExposure (the test is in lsst.utils.tests.MemoryTestCase)"""
652 afwImage.makeMaskedImage(afwImage.ImageU(lsst.geom.Extent2I(10, 20)))
653 afwImage.makeExposure(afwImage.makeMaskedImage(
654 afwImage.ImageU(lsst.geom.Extent2I(10, 20))))
656 def testImageSlices(self):
657 """Test image slicing, which generate sub-images using Box2I under the covers"""
658 exp = afwImage.ExposureF(10, 20)
659 mi = exp.getMaskedImage()
660 mi.image[9, 19] = 10
661 # N.b. Exposures don't support setting/getting the pixels so can't
662 # replicate e.g. Image's slice tests
663 sexp = exp[1:4, 6:10]
664 self.assertEqual(sexp.getDimensions(), lsst.geom.ExtentI(3, 4))
665 sexp = exp[:, -3:, afwImage.LOCAL]
666 self.assertEqual(sexp.getDimensions(),
667 lsst.geom.ExtentI(exp.getWidth(), 3))
668 self.assertEqual(sexp.maskedImage[-1, -1, afwImage.LOCAL],
669 exp.maskedImage[-1, -1, afwImage.LOCAL])
671 def testConversionToScalar(self):
672 """Test that even 1-pixel Exposures can't be converted to scalars"""
673 im = afwImage.ExposureF(10, 20)
675 # only single pixel images may be converted
676 self.assertRaises(TypeError, float, im)
677 # actually, can't convert (img, msk, var) to scalar
678 self.assertRaises(TypeError, float, im[0, 0])
680 def testReadMetadata(self):
681 with lsst.utils.tests.getTempFilePath(".fits") as tmpFile:
682 self.exposureCrWcs.getMetadata().set("FRAZZLE", True)
683 # This will write the main metadata (inc. FRAZZLE) to the primary HDU, and the
684 # WCS to subsequent HDUs, along with INHERIT=T.
685 self.exposureCrWcs.writeFits(tmpFile)
686 # This should read the first non-empty HDU (i.e. it skips the primary), but
687 # goes back and reads it if it finds INHERIT=T. That should let us read
688 # frazzle and the Wcs from the PropertySet returned by
689 # testReadMetadata.
690 md = readMetadata(tmpFile)
691 wcs = afwGeom.makeSkyWcs(md, False)
692 self.assertPairsAlmostEqual(wcs.getPixelOrigin(), self.wcs.getPixelOrigin())
693 self.assertSpherePointsAlmostEqual(wcs.getSkyOrigin(), self.wcs.getSkyOrigin())
694 assert_allclose(wcs.getCdMatrix(), self.wcs.getCdMatrix(), atol=1e-10)
695 frazzle = md.getScalar("FRAZZLE")
696 self.assertTrue(frazzle)
698 def testArchiveKeys(self):
699 with lsst.utils.tests.getTempFilePath(".fits") as tmpFile:
700 exposure1 = afwImage.ExposureF(100, 100, self.wcs)
701 exposure1.setPsf(self.psf)
702 exposure1.writeFits(tmpFile)
703 exposure2 = afwImage.ExposureF(tmpFile)
704 self.assertFalse(exposure2.getMetadata().exists("AR_ID"))
705 self.assertFalse(exposure2.getMetadata().exists("PSF_ID"))
706 self.assertFalse(exposure2.getMetadata().exists("WCS_ID"))
708 def testTicket2861(self):
709 with lsst.utils.tests.getTempFilePath(".fits") as tmpFile:
710 exposure1 = afwImage.ExposureF(100, 100, self.wcs)
711 exposure1.setPsf(self.psf)
712 schema = afwTable.ExposureTable.makeMinimalSchema()
713 coaddInputs = afwImage.CoaddInputs(schema, schema)
714 exposure1.getInfo().setCoaddInputs(coaddInputs)
715 exposure2 = afwImage.ExposureF(exposure1, True)
716 self.assertIsNotNone(exposure2.getInfo().getCoaddInputs())
717 exposure2.writeFits(tmpFile)
718 exposure3 = afwImage.ExposureF(tmpFile)
719 self.assertIsNotNone(exposure3.getInfo().getCoaddInputs())
721 def testGetCutoutSky(self):
722 """Test we can get cutouts in sky coordinates, so long as there is a
723 valid WCS.
724 """
725 wcs = self.smallExposure.getWcs()
727 dimensions = [lsst.geom.Extent2I(100, 50), lsst.geom.Extent2I(15, 15), lsst.geom.Extent2I(0, 10),
728 lsst.geom.Extent2I(25, 30), lsst.geom.Extent2I(15, -5),
729 2*self.smallExposure.getDimensions()]
730 locations = [("center", self._getExposureCenter(self.smallExposure)),
731 ("edge", wcs.pixelToSky(lsst.geom.Point2D(0, 0))),
732 ("rounding test", wcs.pixelToSky(lsst.geom.Point2D(0.2, 0.7))),
733 ("just inside", wcs.pixelToSky(lsst.geom.Point2D(-0.5 + 1e-4, -0.5 + 1e-4))),
734 ("just outside", wcs.pixelToSky(lsst.geom.Point2D(-0.5 - 1e-4, -0.5 - 1e-4))),
735 ("outside", wcs.pixelToSky(lsst.geom.Point2D(-1000, -1000)))]
736 for cutoutSize in dimensions:
737 for label, cutoutCenter in locations:
738 msg = 'Cutout size = %s, location = %s' % (cutoutSize, label)
739 if "outside" not in label and all(cutoutSize.gt(0)):
740 cutout = self.smallExposure.getCutout(cutoutCenter, cutoutSize)
741 centerInPixels = wcs.skyToPixel(cutoutCenter)
742 precision = (1 + 1e-4)*np.sqrt(0.5)*wcs.getPixelScale(centerInPixels)
743 self._checkCutoutProperties(cutout, cutoutSize, cutoutCenter, precision, msg)
744 self._checkCutoutPixels(
745 cutout,
746 self._getValidCorners(self.smallExposure.getBBox(), cutout.getBBox()),
747 msg)
749 # Need a valid WCS
750 with self.assertRaises(pexExcept.LogicError, msg=msg):
751 self.exposureMiOnly.getCutout(cutoutCenter, cutoutSize)
752 else:
753 with self.assertRaises(pexExcept.InvalidParameterError, msg=msg):
754 self.smallExposure.getCutout(cutoutCenter, cutoutSize)
756 def testGetCutoutPixel(self):
757 """Test that we can get cutouts in pixel coordinates, even if the
758 extent is off the edge of the image, even if there is no WCS.
759 """
760 dimensions = [lsst.geom.Extent2I(100, 50), lsst.geom.Extent2I(15, 15), lsst.geom.Extent2I(0, 10),
761 lsst.geom.Extent2I(25, 30), lsst.geom.Extent2I(15, -5),
762 2*self.exposureMiOnly.getDimensions()]
763 locations = [("center", lsst.geom.Box2D(self.exposureMiOnly.getBBox()).getCenter()),
764 ("edge", lsst.geom.Point2D(0, 0)),
765 ("rounding test", lsst.geom.Point2D(0.2, 0.7)),
766 ("just inside", lsst.geom.Point2D(-0.5 + 1e-4, -0.5 + 1e-4)),
767 # These two should raise; center must be within image box.
768 ("just outside", lsst.geom.Point2D(-0.5 - 1e-4, -0.5 - 1e-4)),
769 ("outside", lsst.geom.Point2D(-1000, -1000))]
770 for cutoutSize in dimensions:
771 for label, cutoutCenter in locations:
772 msg = 'Cutout size = %s, location = %s' % (cutoutSize, label)
773 if "outside" not in label and all(cutoutSize.gt(0)):
774 cutout = self.exposureMiOnly.getCutout(cutoutCenter, cutoutSize)
775 self._checkCutoutPixels(
776 cutout,
777 self._getValidCorners(self.exposureMiOnly.getBBox(), cutout.getBBox()),
778 msg)
780 # Same result even if there is a wcs.
781 cutoutWithWcs = self.smallExposure.getCutout(cutoutCenter, cutoutSize)
782 self.assertMaskedImagesEqual(cutout.maskedImage, cutoutWithWcs.maskedImage)
784 # Getting a cutout with a bbox should produce the same result.
785 box = lsst.geom.Box2I.makeCenteredBox(cutoutCenter, lsst.geom.Extent2I(cutoutSize))
786 cutoutBox2I = self.exposureMiOnly.getCutout(box)
787 self.assertMaskedImagesEqual(cutout.maskedImage, cutoutBox2I.maskedImage)
788 else:
789 with self.assertRaises(pexExcept.InvalidParameterError, msg=msg):
790 self.exposureMiOnly.getCutout(cutoutCenter, cutoutSize)
792 def testGetConvexPolygon(self):
793 """Test the convex polygon."""
794 # Check that we do not have a convex polygon for the plain exposure.
795 self.assertIsNone(self.exposureMiOnly.convex_polygon)
797 # Check that all the points in the padded bounding box are in the polygon
798 bbox = self.exposureMiWcs.getBBox()
799 # Grow by the default padding.
800 bbox.grow(10)
801 x, y = np.meshgrid(np.arange(bbox.getBeginX(), bbox.getEndX(), dtype=np.float64),
802 np.arange(bbox.getBeginY(), bbox.getEndY(), dtype=np.float64))
803 wcs = self.exposureMiWcs.wcs
804 ra, dec = wcs.pixelToSkyArray(x.ravel(),
805 y.ravel())
807 poly = self.exposureMiWcs.convex_polygon
808 contains = poly.contains(ra, dec)
809 np.testing.assert_array_equal(contains, np.ones(len(contains), dtype=bool))
811 # Check that points one pixel outside of the bounding box are not in the polygon
812 bbox.grow(1)
814 ra, dec = wcs.pixelToSkyArray(
815 np.linspace(bbox.getBeginX(), bbox.getEndX(), 100),
816 np.full(100, bbox.getBeginY()))
817 contains = poly.contains(ra, dec)
818 np.testing.assert_array_equal(contains, np.zeros(len(contains), dtype=bool))
820 ra, dec = wcs.pixelToSkyArray(
821 np.linspace(bbox.getBeginX(), bbox.getEndX(), 100),
822 np.full(100, bbox.getEndY()))
823 contains = poly.contains(ra, dec)
824 np.testing.assert_array_equal(contains, np.zeros(len(contains), dtype=bool))
826 ra, dec = wcs.pixelToSkyArray(
827 np.full(100, bbox.getBeginX()),
828 np.linspace(bbox.getBeginY(), bbox.getEndY(), 100))
829 contains = poly.contains(ra, dec)
830 np.testing.assert_array_equal(contains, np.zeros(len(contains), dtype=bool))
832 ra, dec = wcs.pixelToSkyArray(
833 np.full(100, bbox.getEndX()),
834 np.linspace(bbox.getBeginY(), bbox.getEndY(), 100))
835 contains = poly.contains(ra, dec)
836 np.testing.assert_array_equal(contains, np.zeros(len(contains), dtype=bool))
838 def testContainsSkyCoords(self):
839 """Test the sky coord containment code."""
840 self.assertRaisesRegex(ValueError,
841 "Exposure does not have a valid WCS",
842 self.exposureMiOnly.containsSkyCoords,
843 0.0,
844 0.0)
846 # Check that all the points within the bounding box are contained
847 bbox = self.exposureMiWcs.getBBox()
848 x, y = np.meshgrid(np.arange(bbox.getBeginX() + 1, bbox.getEndX() - 1),
849 np.arange(bbox.getBeginY() + 1, bbox.getEndY() - 1))
850 wcs = self.exposureMiWcs.wcs
851 ra, dec = wcs.pixelToSkyArray(x.ravel().astype(np.float64),
852 y.ravel().astype(np.float64))
854 contains = self.exposureMiWcs.containsSkyCoords(ra*units.radian,
855 dec*units.radian)
856 np.testing.assert_array_equal(contains, np.ones(len(contains), dtype=bool))
858 # Same test, everything in degrees.
859 ra, dec = wcs.pixelToSkyArray(x.ravel().astype(np.float64),
860 y.ravel().astype(np.float64),
861 degrees=True)
863 contains = self.exposureMiWcs.containsSkyCoords(ra*units.degree,
864 dec*units.degree)
865 np.testing.assert_array_equal(contains, np.ones(len(contains), dtype=bool))
867 # Prepend and append some positions out of the box.
868 ra = np.concatenate(([300.0], ra, [180.]))
869 dec = np.concatenate(([50.0], dec, [50.0]))
871 # Bad NaN handling appears as a warning, not an error
872 with warnings.catch_warnings():
873 warnings.simplefilter("error", category=RuntimeWarning)
874 contains = self.exposureMiWcs.containsSkyCoords(ra*units.degree,
875 dec*units.degree)
876 compare = np.ones(len(contains), dtype=bool)
877 compare[0] = False
878 compare[-1] = False
879 np.testing.assert_array_equal(contains, compare)
881 def _checkCutoutProperties(self, cutout, size, center, precision, msg):
882 """Test whether a cutout has the desired size and position.
884 Parameters
885 ----------
886 cutout : `lsst.afw.image.Exposure`
887 The cutout to test.
888 size : `lsst.geom.Extent2I`
889 The expected dimensions of ``cutout``.
890 center : `lsst.geom.SpherePoint`
891 The expected center of ``cutout``.
892 precision : `lsst.geom.Angle`
893 The precision to which ``center`` must match.
894 msg : `str`
895 An error message suffix describing test parameters.
896 """
897 newCenter = self._getExposureCenter(cutout)
898 self.assertIsNotNone(cutout, msg=msg)
899 self.assertSpherePointsAlmostEqual(newCenter, center, maxSep=precision, msg=msg)
900 self.assertEqual(cutout.getWidth(), size[0], msg=msg)
901 self.assertEqual(cutout.getHeight(), size[1], msg=msg)
903 def _checkCutoutPixels(self, cutout, validCorners, msg):
904 """Test whether a cutout has valid/empty pixels where expected.
906 Parameters
907 ----------
908 cutout : `lsst.afw.image.Exposure`
909 The cutout to test.
910 validCorners : iterable of `lsst.geom.Point2I`
911 The corners of ``cutout`` that should be drawn from the original image.
912 msg : `str`
913 An error message suffix describing test parameters.
914 """
915 mask = cutout.getMaskedImage().getMask()
916 edgeMask = mask.getPlaneBitMask("NO_DATA")
918 for corner in cutout.getBBox().getCorners():
919 maskBitsSet = mask[corner] & edgeMask
920 if corner in validCorners:
921 self.assertEqual(maskBitsSet, 0, msg=msg)
922 else:
923 self.assertEqual(maskBitsSet, edgeMask, msg=msg)
925 def _getExposureCenter(self, exposure):
926 """Return the sky coordinates of an Exposure's center.
928 Parameters
929 ----------
930 exposure : `lsst.afw.image.Exposure`
931 The image whose center is desired.
933 Returns
934 -------
935 center : `lsst.geom.SpherePoint`
936 The position at the center of ``exposure``.
937 """
938 return exposure.getWcs().pixelToSky(lsst.geom.Box2D(exposure.getBBox()).getCenter())
940 def _getValidCorners(self, imageBox, cutoutBox):
941 """Return the corners of a cutout that are constrained by the original image.
943 Parameters
944 ----------
945 imageBox: `lsst.geom.Extent2I`
946 The bounding box of the original image.
947 cutoutBox : `lsst.geom.Box2I`
948 The bounding box of the cutout.
950 Returns
951 -------
952 corners : iterable of `lsst.geom.Point2I`
953 The corners that are drawn from the original image.
954 """
955 return [corner for corner in cutoutBox.getCorners() if corner in imageBox]
958class ExposureAfwDataNotNecessary(lsst.utils.tests.TestCase):
960 def testExposureUnits(self):
961 """Test that units round trip and end up in the right place."""
962 exposure = afwImage.ExposureF(os.path.join(TESTDIR, "data", "HSC-0908120-056-small.fits"))
964 # Set the units (there is no standard property in model at present
965 # so assume FITS standard).
966 units = "nJy"
967 exposure.metadata["BUNIT"] = units
969 with lsst.utils.tests.getTempFilePath(".fits") as tmpFile:
970 exposure.writeFits(tmpFile)
972 # Use astropy to read the individual HDUs.
973 with astropy.io.fits.open(tmpFile) as hdul:
974 # BUNIT should not be in primary.
975 self.assertNotIn("BUNIT", hdul[0].header)
976 for extname, expected in (("IMAGE", units), ("VARIANCE", f"{units}**2"), ("MASK", None)):
977 ind = hdul.index_of(extname)
978 hdu = hdul[ind]
979 self.assertEqual(hdu.header["BUNIT"], expected)
981 # Read it back in an BUNIT should still exist.
982 reread = afwImage.ExposureF(tmpFile)
983 self.assertEqual(reread.metadata["BUNIT"], units)
986class ExposureInfoTestCase(lsst.utils.tests.TestCase):
987 def setUp(self):
988 super().setUp()
990 self.wcs = afwGeom.makeSkyWcs(lsst.geom.Point2D(0.0, 0.0),
991 lsst.geom.SpherePoint(2.0, 34.0, lsst.geom.degrees),
992 np.identity(2),
993 )
994 self.photoCalib = afwImage.PhotoCalib(1.5)
995 self.psf = DummyPsf(2.0)
996 self.detector = DetectorWrapper().detector
997 self.summaryStats = afwImage.ExposureSummaryStats(ra=100.0)
998 self.polygon = afwGeom.Polygon(lsst.geom.Box2D(lsst.geom.Point2D(0.0, 0.0),
999 lsst.geom.Point2D(25.0, 20.0)))
1000 self.coaddInputs = afwImage.CoaddInputs()
1001 self.apCorrMap = afwImage.ApCorrMap()
1002 self.transmissionCurve = afwImage.TransmissionCurve.makeIdentity()
1004 self.exposureInfo = afwImage.ExposureInfo()
1005 self.gFilterLabel = afwImage.FilterLabel(band="g")
1006 self.exposureId = 42
1008 def _checkAlias(self, exposureInfo, key, value, has, get):
1009 self.assertFalse(has())
1010 self.assertFalse(exposureInfo.hasComponent(key))
1011 self.assertIsNone(get())
1012 self.assertIsNone(exposureInfo.getComponent(key))
1014 self.exposureInfo.setComponent(key, value)
1015 self.assertTrue(has())
1016 self.assertTrue(exposureInfo.hasComponent(key))
1017 self.assertIsNotNone(get())
1018 self.assertIsNotNone(exposureInfo.getComponent(key))
1019 self.assertEqual(get(), value)
1020 self.assertEqual(exposureInfo.getComponent(key), value)
1022 self.exposureInfo.removeComponent(key)
1023 self.assertFalse(has())
1024 self.assertFalse(exposureInfo.hasComponent(key))
1025 self.assertIsNone(get())
1026 self.assertIsNone(exposureInfo.getComponent(key))
1028 def testAliases(self):
1029 cls = type(self.exposureInfo)
1030 self._checkAlias(self.exposureInfo, cls.KEY_WCS, self.wcs,
1031 self.exposureInfo.hasWcs, self.exposureInfo.getWcs)
1032 self._checkAlias(self.exposureInfo, cls.KEY_PSF, self.psf,
1033 self.exposureInfo.hasPsf, self.exposureInfo.getPsf)
1034 self._checkAlias(self.exposureInfo, cls.KEY_PHOTO_CALIB, self.photoCalib,
1035 self.exposureInfo.hasPhotoCalib, self.exposureInfo.getPhotoCalib)
1036 self._checkAlias(self.exposureInfo, cls.KEY_DETECTOR, self.detector,
1037 self.exposureInfo.hasDetector, self.exposureInfo.getDetector)
1038 self._checkAlias(self.exposureInfo, cls.KEY_VALID_POLYGON, self.polygon,
1039 self.exposureInfo.hasValidPolygon, self.exposureInfo.getValidPolygon)
1040 self._checkAlias(self.exposureInfo, cls.KEY_COADD_INPUTS, self.coaddInputs,
1041 self.exposureInfo.hasCoaddInputs, self.exposureInfo.getCoaddInputs)
1042 self._checkAlias(self.exposureInfo, cls.KEY_AP_CORR_MAP, self.apCorrMap,
1043 self.exposureInfo.hasApCorrMap, self.exposureInfo.getApCorrMap)
1044 self._checkAlias(self.exposureInfo, cls.KEY_TRANSMISSION_CURVE, self.transmissionCurve,
1045 self.exposureInfo.hasTransmissionCurve, self.exposureInfo.getTransmissionCurve)
1046 self._checkAlias(self.exposureInfo, cls.KEY_SUMMARY_STATS, self.summaryStats,
1047 self.exposureInfo.hasSummaryStats, self.exposureInfo.getSummaryStats)
1048 self._checkAlias(self.exposureInfo, cls.KEY_FILTER, self.gFilterLabel,
1049 self.exposureInfo.hasFilter, self.exposureInfo.getFilter)
1051 def testId(self):
1052 self.exposureInfo.setVisitInfo(afwImage.VisitInfo())
1054 self.assertFalse(self.exposureInfo.hasId())
1055 self.assertIsNone(self.exposureInfo.getId())
1056 self.assertIsNone(self.exposureInfo.id)
1058 self.exposureInfo.setId(self.exposureId)
1059 self.assertTrue(self.exposureInfo.hasId())
1060 self.assertIsNotNone(self.exposureInfo.getId())
1061 self.assertIsNotNone(self.exposureInfo.id)
1062 self.assertEqual(self.exposureInfo.getId(), self.exposureId)
1063 self.assertEqual(self.exposureInfo.id, self.exposureId)
1065 self.exposureInfo.id = 99899
1066 self.assertEqual(self.exposureInfo.getId(), 99899)
1068 self.exposureInfo.id = None
1069 self.assertFalse(self.exposureInfo.hasId())
1070 self.assertIsNone(self.exposureInfo.getId())
1071 self.assertIsNone(self.exposureInfo.id)
1073 def testCopy(self):
1074 # Test that ExposureInfos have independently settable state
1075 copy = afwImage.ExposureInfo(self.exposureInfo, True)
1076 self.assertEqual(self.exposureInfo.getWcs(), copy.getWcs())
1078 newWcs = afwGeom.makeSkyWcs(lsst.geom.Point2D(-23.0, 8.0),
1079 lsst.geom.SpherePoint(0.0, 0.0, lsst.geom.degrees),
1080 np.identity(2),
1081 )
1082 copy.setWcs(newWcs)
1083 self.assertEqual(copy.getWcs(), newWcs)
1084 self.assertNotEqual(self.exposureInfo.getWcs(), copy.getWcs())
1086 def testMissingProperties(self):
1087 # Test that invalid properties return None instead of raising
1088 exposureInfo = afwImage.ExposureInfo()
1090 self.assertIsNone(exposureInfo.id)
1093class ExposureNoAfwdataTestCase(lsst.utils.tests.TestCase):
1094 """Tests of Exposure that don't require afwdata.
1096 These tests use the trivial exposures written to ``afw/tests/data``.
1097 """
1098 def setUp(self):
1099 self.dataDir = os.path.join(os.path.split(__file__)[0], "data")
1101 # Check the values below against what was written by comparing with
1102 # the code in `afw/tests/data/makeTestExposure.py`
1103 nx = ny = 10
1104 image = afwImage.ImageF(np.arange(nx*ny, dtype='f').reshape(nx, ny))
1105 variance = afwImage.ImageF(np.ones((nx, ny), dtype='f'))
1106 mask = afwImage.MaskX(nx, ny)
1107 mask.array[5, 5] = 5
1108 self.maskedImage = afwImage.MaskedImageF(image, mask, variance)
1109 self.exposureId = 12345
1111 self.v0PhotoCalib = afwImage.makePhotoCalibFromCalibZeroPoint(1e6, 2e4)
1112 self.v1PhotoCalib = afwImage.PhotoCalib(1e6, 2e4)
1113 self.v1FilterLabel = afwImage.FilterLabel(physical="ha")
1114 self.v2FilterLabel = afwImage.FilterLabel(band="N656", physical="ha")
1116 def testReadUnversioned(self):
1117 """Test that we can read an unversioned (implicit verison 0) file.
1118 """
1119 filename = os.path.join(self.dataDir, "exposure-noversion.fits")
1120 exposure = afwImage.ExposureF.readFits(filename)
1122 self.assertMaskedImagesEqual(exposure.maskedImage, self.maskedImage)
1124 self.assertEqual(exposure.info.id, self.exposureId)
1125 self.assertEqual(exposure.getPhotoCalib(), self.v0PhotoCalib)
1126 self.assertEqual(exposure.getFilter(), self.v1FilterLabel)
1128 def testReadVersion0(self):
1129 """Test that we can read a version 0 file.
1130 This file should be identical to the unversioned one, except that it
1131 is marked as ExposureInfo version 0 in the header.
1132 """
1133 filename = os.path.join(self.dataDir, "exposure-version-0.fits")
1134 exposure = afwImage.ExposureF.readFits(filename)
1136 self.assertMaskedImagesEqual(exposure.maskedImage, self.maskedImage)
1138 self.assertEqual(exposure.info.id, self.exposureId)
1139 self.assertEqual(exposure.getPhotoCalib(), self.v0PhotoCalib)
1140 self.assertEqual(exposure.getFilter(), self.v1FilterLabel)
1142 # Check that the metadata reader parses the file correctly
1143 reader = afwImage.ExposureFitsReader(filename)
1144 self.assertEqual(reader.readExposureInfo().getPhotoCalib(), self.v0PhotoCalib)
1145 self.assertEqual(reader.readPhotoCalib(), self.v0PhotoCalib)
1147 def testReadVersion1(self):
1148 """Test that we can read a version 1 file.
1149 Version 1 replaced Calib with PhotoCalib.
1150 """
1151 filename = os.path.join(self.dataDir, "exposure-version-1.fits")
1152 exposure = afwImage.ExposureF.readFits(filename)
1154 self.assertMaskedImagesEqual(exposure.maskedImage, self.maskedImage)
1156 self.assertEqual(exposure.info.id, self.exposureId)
1157 self.assertEqual(exposure.getPhotoCalib(), self.v1PhotoCalib)
1158 self.assertEqual(exposure.getFilter(), self.v1FilterLabel)
1160 # Check that the metadata reader parses the file correctly
1161 reader = afwImage.ExposureFitsReader(filename)
1162 self.assertEqual(reader.readExposureInfo().getPhotoCalib(), self.v1PhotoCalib)
1163 self.assertEqual(reader.readPhotoCalib(), self.v1PhotoCalib)
1165 def testReadVersion2(self):
1166 """Test that we can read a version 2 file.
1167 Version 2 replaced Filter with FilterLabel.
1168 """
1169 filename = os.path.join(self.dataDir, "exposure-version-2.fits")
1170 exposure = afwImage.ExposureF.readFits(filename)
1172 self.assertMaskedImagesEqual(exposure.maskedImage, self.maskedImage)
1174 self.assertEqual(exposure.info.id, self.exposureId)
1175 self.assertEqual(exposure.getPhotoCalib(), self.v1PhotoCalib)
1176 self.assertEqual(exposure.getFilter(), self.v2FilterLabel)
1178 # Check that the metadata reader parses the file correctly
1179 reader = afwImage.ExposureFitsReader(filename)
1180 self.assertEqual(reader.readExposureInfo().getPhotoCalib(), self.v1PhotoCalib)
1181 self.assertEqual(reader.readPhotoCalib(), self.v1PhotoCalib)
1183 def testReadDottedHeaderKey(self):
1184 """Test that we can read a file with a dot-delimited header key."""
1185 original = afwImage.ExposureF.readFits(os.path.join(self.dataDir, "exposure-version-2.fits"))
1186 original.metadata["x.y.z"] = "three"
1187 with lsst.utils.tests.getTempFilePath(".fits") as tmpFile:
1188 original.writeFits(tmpFile)
1189 roundtripped = afwImage.ExposureF(tmpFile)
1190 self.assertMaskedImagesEqual(original.maskedImage, roundtripped.maskedImage)
1192 def testExposureSummaryExtraComponents(self):
1193 """Test that we can read an exposure summary with extra components.
1194 """
1195 testDict = {'ra': 0.0,
1196 'dec': 0.0,
1197 'nonsense': 1.0}
1198 bytes = yaml.dump(testDict, encoding='utf-8')
1199 with self.assertWarns(FutureWarning):
1200 summaryStats = lsst.afw.image.ExposureSummaryStats._read(bytes)
1202 self.assertEqual(summaryStats.ra, testDict['ra'])
1203 self.assertEqual(summaryStats.dec, testDict['dec'])
1205 def testExposureSummaryForwardComponents(self):
1206 """Test that we can forward extra components (e.g. decl->dec).
1207 """
1208 testDict = {'ra': 10.0,
1209 'decl': 10.0}
1210 bytes = yaml.dump(testDict, encoding='utf-8')
1211 # Cleanly forwarded fields must not result in a warning.
1212 with warnings.catch_warnings():
1213 warnings.simplefilter("error")
1214 summaryStats = lsst.afw.image.ExposureSummaryStats._read(bytes)
1216 self.assertEqual(summaryStats.ra, testDict['ra'])
1217 self.assertEqual(summaryStats.dec, testDict['decl'])
1219 # And check if there are both listed, it should use the new dec value.
1220 testDict = {'ra': 10.0,
1221 'dec': 5.0,
1222 'decl': 10.0}
1223 bytes = yaml.dump(testDict, encoding='utf-8')
1224 with self.assertWarns(FutureWarning):
1225 summaryStats = lsst.afw.image.ExposureSummaryStats._read(bytes)
1227 self.assertEqual(summaryStats.ra, testDict['ra'])
1228 self.assertEqual(summaryStats.dec, testDict['dec'])
1230 def testExposureSummarySchema(self):
1231 """Test that we can make a schema for an exposure summary and populate
1232 records with that schema.
1233 """
1234 schema = afwTable.Schema()
1235 afwImage.ExposureSummaryStats.update_schema(schema)
1236 self.maxDiff = None
1237 self.assertEqual(
1238 {field.name for field in dataclasses.fields(afwImage.ExposureSummaryStats)},
1239 set(schema.getNames()) | {"version"},
1240 )
1241 catalog = afwTable.BaseCatalog(schema)
1242 summary1 = afwImage.ExposureSummaryStats()
1243 for n, field in enumerate(dataclasses.fields(afwImage.ExposureSummaryStats)):
1244 # Set fields to deterministic, distinct, but arbitrary values.
1245 if field.type == "float":
1246 setattr(summary1, field.name, float(0.5**n))
1247 elif field.type == "int":
1248 setattr(summary1, field.name, 10*n)
1249 elif field.type == "list[float]":
1250 setattr(summary1, field.name, [n + 0.1, n + 0.2, n + 0.3, n + 0.4])
1251 else:
1252 raise TypeError(f"Unexpected type: {field.type!r}.")
1253 record = catalog.addNew()
1254 summary1.update_record(record)
1255 summary2 = afwImage.ExposureSummaryStats.from_record(record)
1256 self.assertEqual(summary1, summary2)
1258 def testMetadataProperty(self):
1259 """Test that the metadata property works as expected.
1260 """
1261 exposure = afwImage.ExposureF(3, 4)
1262 self.assertFalse(exposure.metadata)
1263 self.assertIsNotNone(exposure.metadata)
1264 exposure.metadata = None
1265 self.assertIsNone(exposure.metadata)
1266 metadata = PropertyList()
1267 metadata["one"] = 1
1268 exposure.metadata = metadata
1269 self.assertEqual(exposure.metadata["one"], 1)
1272class MemoryTester(lsst.utils.tests.MemoryTestCase):
1273 pass
1276def setup_module(module):
1277 lsst.utils.tests.init()
1280if __name__ == "__main__": 1280 ↛ 1281line 1280 didn't jump to line 1281 because the condition on line 1280 was never true
1281 lsst.utils.tests.init()
1282 unittest.main()