Coverage for tests/test_exposure.py: 11%
657 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-07-08 03:13 -0700
« prev ^ index » next coverage.py v7.2.7, created at 2023-07-08 03:13 -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
30import numpy as np
31from numpy.testing import assert_allclose
32import yaml
33import astropy.units as units
35import lsst.utils
36import lsst.utils.tests
37import lsst.geom
38import lsst.afw.image as afwImage
39from lsst.afw.coord import Weather
40import lsst.afw.geom as afwGeom
41import lsst.afw.table as afwTable
42import lsst.pex.exceptions as pexExcept
43from lsst.afw.fits import readMetadata, FitsError
44from lsst.afw.cameraGeom.testUtils import DetectorWrapper
45from lsst.log import Log
46from testTableArchivesLib import DummyPsf
48Log.getLogger("lsst.afw.image.Mask").setLevel(Log.INFO)
50try:
51 dataDir = os.path.join(lsst.utils.getPackageDir("afwdata"), "data")
52except LookupError:
53 dataDir = None
54else:
55 InputMaskedImageName = "871034p_1_MI.fits"
56 InputMaskedImageNameSmall = "small_MI.fits"
57 InputImageNameSmall = "small"
58 OutputMaskedImageName = "871034p_1_MInew.fits"
60 currDir = os.path.abspath(os.path.dirname(__file__))
61 inFilePath = os.path.join(dataDir, InputMaskedImageName)
62 inFilePathSmall = os.path.join(dataDir, InputMaskedImageNameSmall)
63 inFilePathSmallImage = os.path.join(dataDir, InputImageNameSmall)
66@unittest.skipIf(dataDir is None, "afwdata not setup")
67class ExposureTestCase(lsst.utils.tests.TestCase):
68 """
69 A test case for the Exposure Class
70 """
72 def setUp(self):
73 maskedImage = afwImage.MaskedImageF(inFilePathSmall)
74 maskedImageMD = readMetadata(inFilePathSmall)
76 self.smallExposure = afwImage.ExposureF(inFilePathSmall)
77 self.width = maskedImage.getWidth()
78 self.height = maskedImage.getHeight()
79 self.wcs = afwGeom.makeSkyWcs(maskedImageMD, False)
80 self.md = maskedImageMD
81 self.psf = DummyPsf(2.0)
82 self.detector = DetectorWrapper().detector
83 self.id = 42
84 self.extras = {"MISC": DummyPsf(3.5)}
86 self.exposureBlank = afwImage.ExposureF()
87 self.exposureMiOnly = afwImage.makeExposure(maskedImage)
88 self.exposureMiWcs = afwImage.makeExposure(maskedImage, self.wcs)
89 # n.b. the (100, 100, ...) form
90 self.exposureCrWcs = afwImage.ExposureF(100, 100, self.wcs)
91 # test with ExtentI(100, 100) too
92 self.exposureCrOnly = afwImage.ExposureF(lsst.geom.ExtentI(100, 100))
94 def tearDown(self):
95 del self.smallExposure
96 del self.wcs
97 del self.psf
98 del self.detector
99 del self.extras
101 del self.exposureBlank
102 del self.exposureMiOnly
103 del self.exposureMiWcs
104 del self.exposureCrWcs
105 del self.exposureCrOnly
107 def testGetMaskedImage(self):
108 """
109 Test to ensure a MaskedImage can be obtained from each
110 Exposure. An Exposure is required to have a MaskedImage,
111 therefore each of the Exposures should return a MaskedImage.
113 MaskedImage class should throw appropriate
114 lsst::pex::exceptions::NotFound if the MaskedImage can not be
115 obtained.
116 """
117 maskedImageBlank = self.exposureBlank.getMaskedImage()
118 blankWidth = maskedImageBlank.getWidth()
119 blankHeight = maskedImageBlank.getHeight()
120 if blankWidth != blankHeight != 0:
121 self.fail(f"{blankWidth} = {blankHeight} != 0")
123 maskedImageMiOnly = self.exposureMiOnly.getMaskedImage()
124 miOnlyWidth = maskedImageMiOnly.getWidth()
125 miOnlyHeight = maskedImageMiOnly.getHeight()
126 self.assertAlmostEqual(miOnlyWidth, self.width)
127 self.assertAlmostEqual(miOnlyHeight, self.height)
129 # NOTE: Unittests for Exposures created from a MaskedImage and
130 # a WCS object are incomplete. No way to test the validity of
131 # the WCS being copied/created.
133 maskedImageMiWcs = self.exposureMiWcs.getMaskedImage()
134 miWcsWidth = maskedImageMiWcs.getWidth()
135 miWcsHeight = maskedImageMiWcs.getHeight()
136 self.assertAlmostEqual(miWcsWidth, self.width)
137 self.assertAlmostEqual(miWcsHeight, self.height)
139 maskedImageCrWcs = self.exposureCrWcs.getMaskedImage()
140 crWcsWidth = maskedImageCrWcs.getWidth()
141 crWcsHeight = maskedImageCrWcs.getHeight()
142 if crWcsWidth != crWcsHeight != 0:
143 self.fail(f"{crWcsWidth} != {crWcsHeight} != 0")
145 maskedImageCrOnly = self.exposureCrOnly.getMaskedImage()
146 crOnlyWidth = maskedImageCrOnly.getWidth()
147 crOnlyHeight = maskedImageCrOnly.getHeight()
148 if crOnlyWidth != crOnlyHeight != 0:
149 self.fail(f"{crOnlyWidth} != {crOnlyHeight} != 0")
151 # Check Exposure.getWidth() returns the MaskedImage's width
152 self.assertEqual(crOnlyWidth, self.exposureCrOnly.getWidth())
153 self.assertEqual(crOnlyHeight, self.exposureCrOnly.getHeight())
154 # check width/height properties
155 self.assertEqual(crOnlyWidth, self.exposureCrOnly.width)
156 self.assertEqual(crOnlyHeight, self.exposureCrOnly.height)
158 def testProperties(self):
159 self.assertMaskedImagesEqual(self.exposureMiOnly.maskedImage,
160 self.exposureMiOnly.getMaskedImage())
161 mi2 = afwImage.MaskedImageF(self.exposureMiOnly.getDimensions())
162 mi2.image.array[:] = 5.0
163 mi2.variance.array[:] = 3.0
164 mi2.mask.array[:] = 0x1
165 self.exposureMiOnly.maskedImage = mi2
166 self.assertMaskedImagesEqual(self.exposureMiOnly.maskedImage, mi2)
167 self.assertImagesEqual(self.exposureMiOnly.image,
168 self.exposureMiOnly.maskedImage.image)
170 image3 = afwImage.ImageF(self.exposureMiOnly.getDimensions())
171 image3.array[:] = 3.0
172 self.exposureMiOnly.image = image3
173 self.assertImagesEqual(self.exposureMiOnly.image, image3)
175 mask3 = afwImage.MaskX(self.exposureMiOnly.getDimensions())
176 mask3.array[:] = 0x2
177 self.exposureMiOnly.mask = mask3
178 self.assertMasksEqual(self.exposureMiOnly.mask, mask3)
180 var3 = afwImage.ImageF(self.exposureMiOnly.getDimensions())
181 var3.array[:] = 2.0
182 self.exposureMiOnly.variance = var3
183 self.assertImagesEqual(self.exposureMiOnly.variance, var3)
185 # Test the property getter for a null VisitInfo.
186 self.assertIsNone(self.exposureMiOnly.visitInfo)
188 def testGetWcs(self):
189 """Test that a WCS can be obtained from each Exposure created with
190 a WCS, and that an Exposure lacking a WCS returns None.
191 """
192 # These exposures don't contain a WCS
193 self.assertIsNone(self.exposureBlank.getWcs())
194 self.assertIsNone(self.exposureMiOnly.getWcs())
195 self.assertIsNone(self.exposureCrOnly.getWcs())
197 # These exposures should contain a WCS
198 self.assertEqual(self.wcs, self.exposureMiWcs.getWcs())
199 self.assertEqual(self.wcs, self.exposureCrWcs.getWcs())
201 def testExposureInfoConstructor(self):
202 """Test the Exposure(maskedImage, exposureInfo) constructor"""
203 exposureInfo = afwImage.ExposureInfo()
204 exposureInfo.setWcs(self.wcs)
205 exposureInfo.setDetector(self.detector)
206 gFilterLabel = afwImage.FilterLabel(band="g")
207 exposureInfo.setFilter(gFilterLabel)
208 maskedImage = afwImage.MaskedImageF(inFilePathSmall)
209 exposure = afwImage.ExposureF(maskedImage, exposureInfo)
211 self.assertTrue(exposure.hasWcs())
212 self.assertEqual(exposure.getWcs().getPixelOrigin(),
213 self.wcs.getPixelOrigin())
214 self.assertEqual(exposure.getDetector().getName(),
215 self.detector.getName())
216 self.assertEqual(exposure.getDetector().getSerial(),
217 self.detector.getSerial())
218 self.assertEqual(exposure.getFilter(), gFilterLabel)
220 self.assertTrue(exposure.getInfo().hasWcs())
221 # check the ExposureInfo property
222 self.assertTrue(exposure.info.hasWcs())
223 self.assertEqual(exposure.getInfo().getWcs().getPixelOrigin(),
224 self.wcs.getPixelOrigin())
225 self.assertEqual(exposure.getInfo().getDetector().getName(),
226 self.detector.getName())
227 self.assertEqual(exposure.getInfo().getDetector().getSerial(),
228 self.detector.getSerial())
229 self.assertEqual(exposure.getInfo().getFilter(), gFilterLabel)
231 def testNullWcs(self):
232 """Test that an Exposure constructed with second argument None is usable
234 When the exposureInfo constructor was first added, trying to get a WCS
235 or other info caused a segfault because the ExposureInfo did not exist.
236 """
237 maskedImage = self.exposureMiOnly.getMaskedImage()
238 exposure = afwImage.ExposureF(maskedImage, None)
239 self.assertFalse(exposure.hasWcs())
240 self.assertFalse(exposure.hasPsf())
242 def testExposureInfoSetNone(self):
243 exposureInfo = afwImage.ExposureInfo()
244 exposureInfo.setDetector(None)
245 exposureInfo.setValidPolygon(None)
246 exposureInfo.setPsf(None)
247 exposureInfo.setWcs(None)
248 exposureInfo.setPhotoCalib(None)
249 exposureInfo.setCoaddInputs(None)
250 exposureInfo.setVisitInfo(None)
251 exposureInfo.setApCorrMap(None)
252 for key in self.extras:
253 exposureInfo.setComponent(key, None)
255 def testSetExposureInfo(self):
256 exposureInfo = afwImage.ExposureInfo()
257 exposureInfo.setWcs(self.wcs)
258 exposureInfo.setDetector(self.detector)
259 gFilterLabel = afwImage.FilterLabel(band="g")
260 exposureInfo.setFilter(gFilterLabel)
261 exposureInfo.setId(self.id)
262 maskedImage = afwImage.MaskedImageF(inFilePathSmall)
263 exposure = afwImage.ExposureF(maskedImage)
264 self.assertFalse(exposure.hasWcs())
266 exposure.setInfo(exposureInfo)
268 self.assertTrue(exposure.hasWcs())
269 self.assertEqual(exposure.getWcs().getPixelOrigin(),
270 self.wcs.getPixelOrigin())
271 self.assertEqual(exposure.getDetector().getName(),
272 self.detector.getName())
273 self.assertEqual(exposure.getDetector().getSerial(),
274 self.detector.getSerial())
275 self.assertEqual(exposure.getFilter(), gFilterLabel)
277 # test properties
278 self.assertEqual(exposure.detector.getName(), self.detector.getName())
279 self.assertEqual(exposure.filter, gFilterLabel)
280 self.assertEqual(exposure.wcs, self.wcs)
282 def testVisitInfoFitsPersistence(self):
283 """Test saving an exposure to FITS and reading it back in preserves (some) VisitInfo fields"""
284 exposureTime = 12.3
285 boresightRotAngle = 45.6 * lsst.geom.degrees
286 weather = Weather(1.1, 2.2, 0.3)
287 visitInfo = afwImage.VisitInfo(
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 gFilterLabel = afwImage.FilterLabel(band="g")
298 exposureInfo.setFilter(gFilterLabel)
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(), gFilterLabel)
309 # Test property getters.
310 self.assertEqual(rtExposure.photoCalib, photoCalib)
311 self.assertEqual(rtExposure.filter, gFilterLabel)
312 # NOTE: we can't test visitInfo equality, because most fields are NaN.
313 self.assertIsNotNone(rtExposure.visitInfo)
315 def testSetMembers(self):
316 """
317 Test that the MaskedImage and the WCS of an Exposure can be set.
318 """
319 exposure = afwImage.ExposureF()
321 maskedImage = afwImage.MaskedImageF(inFilePathSmall)
322 exposure.setMaskedImage(maskedImage)
323 exposure.setWcs(self.wcs)
324 exposure.setDetector(self.detector)
325 exposure.setFilter(afwImage.FilterLabel(band="g"))
327 self.assertEqual(exposure.getDetector().getName(),
328 self.detector.getName())
329 self.assertEqual(exposure.getDetector().getSerial(),
330 self.detector.getSerial())
331 self.assertEqual(exposure.getFilter().bandLabel, "g")
332 self.assertEqual(exposure.getWcs(), self.wcs)
334 # The PhotoCalib tests are in test_photoCalib.py;
335 # here we just check that it's gettable and settable.
336 self.assertIsNone(exposure.getPhotoCalib())
338 photoCalib = afwImage.PhotoCalib(511.1, 44.4)
339 exposure.setPhotoCalib(photoCalib)
340 self.assertEqual(exposure.getPhotoCalib(), photoCalib)
342 # Psfs next
343 self.assertFalse(exposure.hasPsf())
344 exposure.setPsf(self.psf)
345 self.assertTrue(exposure.hasPsf())
347 exposure.setPsf(DummyPsf(1.0)) # we can reset the Psf
349 # extras next
350 info = exposure.getInfo()
351 for key, value in self.extras.items():
352 self.assertFalse(info.hasComponent(key))
353 self.assertIsNone(info.getComponent(key))
354 info.setComponent(key, value)
355 self.assertTrue(info.hasComponent(key))
356 self.assertEqual(info.getComponent(key), value)
357 info.removeComponent(key)
358 self.assertFalse(info.hasComponent(key))
360 # Test that we can set the MaskedImage and WCS of an Exposure
361 # that already has both
362 self.exposureMiWcs.setMaskedImage(maskedImage)
363 exposure.setWcs(self.wcs)
365 def testHasWcs(self):
366 """
367 Test if an Exposure has a WCS or not.
368 """
369 self.assertFalse(self.exposureBlank.hasWcs())
371 self.assertFalse(self.exposureMiOnly.hasWcs())
372 self.assertTrue(self.exposureMiWcs.hasWcs())
373 self.assertTrue(self.exposureCrWcs.hasWcs())
374 self.assertFalse(self.exposureCrOnly.hasWcs())
376 def testGetSubExposure(self):
377 """
378 Test that a subExposure of the original Exposure can be obtained.
380 The MaskedImage class should throw a
381 lsst::pex::exceptions::InvalidParameter if the requested
382 subRegion is not fully contained within the original
383 MaskedImage.
385 """
386 #
387 # This subExposure is valid
388 #
389 subBBox = lsst.geom.Box2I(lsst.geom.Point2I(40, 50),
390 lsst.geom.Extent2I(10, 10))
391 subExposure = self.exposureCrWcs.Factory(
392 self.exposureCrWcs, subBBox, afwImage.LOCAL)
394 self.checkWcs(self.exposureCrWcs, subExposure)
396 # this subRegion is not valid and should trigger an exception
397 # from the MaskedImage class and should trigger an exception
398 # from the WCS class for the MaskedImage 871034p_1_MI.
400 subRegion3 = lsst.geom.Box2I(lsst.geom.Point2I(100, 100),
401 lsst.geom.Extent2I(10, 10))
403 def getSubRegion():
404 self.exposureCrWcs.Factory(
405 self.exposureCrWcs, subRegion3, afwImage.LOCAL)
407 self.assertRaises(pexExcept.LengthError, getSubRegion)
409 # this subRegion is not valid and should trigger an exception
410 # from the MaskedImage class only for the MaskedImage small_MI.
411 # small_MI (cols, rows) = (256, 256)
413 subRegion4 = lsst.geom.Box2I(lsst.geom.Point2I(250, 250),
414 lsst.geom.Extent2I(10, 10))
416 def getSubRegion():
417 self.exposureCrWcs.Factory(
418 self.exposureCrWcs, subRegion4, afwImage.LOCAL)
420 self.assertRaises(pexExcept.LengthError, getSubRegion)
422 # check the sub- and parent- exposures are using the same Wcs
423 # transformation
424 subBBox = lsst.geom.Box2I(lsst.geom.Point2I(40, 50),
425 lsst.geom.Extent2I(10, 10))
426 subExposure = self.exposureCrWcs.Factory(
427 self.exposureCrWcs, subBBox, afwImage.LOCAL)
428 parentSkyPos = self.exposureCrWcs.getWcs().pixelToSky(0, 0)
430 subExpSkyPos = subExposure.getWcs().pixelToSky(0, 0)
432 self.assertSpherePointsAlmostEqual(parentSkyPos, subExpSkyPos, msg="Wcs in sub image has changed")
434 def testReadWriteFits(self):
435 """Test readFits and writeFits.
436 """
437 # This should pass without an exception
438 mainExposure = afwImage.ExposureF(inFilePathSmall)
439 mainExposure.info.setId(self.id)
440 mainExposure.setDetector(self.detector)
442 subBBox = lsst.geom.Box2I(lsst.geom.Point2I(10, 10),
443 lsst.geom.Extent2I(40, 50))
444 subExposure = mainExposure.Factory(
445 mainExposure, subBBox, afwImage.LOCAL)
446 self.checkWcs(mainExposure, subExposure)
447 det = subExposure.getDetector()
448 self.assertTrue(det)
450 subExposure = afwImage.ExposureF(
451 inFilePathSmall, subBBox, afwImage.LOCAL)
453 self.checkWcs(mainExposure, subExposure)
455 # This should throw an exception
456 def getExposure():
457 afwImage.ExposureF(inFilePathSmallImage)
459 self.assertRaises(FitsError, getExposure)
461 mainExposure.setPsf(self.psf)
463 # Make sure we can write without an exception
464 photoCalib = afwImage.PhotoCalib(1e-10, 1e-12)
465 mainExposure.setPhotoCalib(photoCalib)
467 mainInfo = mainExposure.getInfo()
468 for key, value in self.extras.items():
469 mainInfo.setComponent(key, value)
471 with lsst.utils.tests.getTempFilePath(".fits") as tmpFile:
472 mainExposure.writeFits(tmpFile)
474 readExposure = type(mainExposure)(tmpFile)
476 #
477 # Check the round-tripping
478 #
479 self.assertIsNotNone(mainExposure.getFilter())
480 self.assertEqual(mainExposure.getFilter(),
481 readExposure.getFilter())
483 self.assertEqual(photoCalib, readExposure.getPhotoCalib())
485 readInfo = readExposure.getInfo()
486 self.assertEqual(mainExposure.info.getId(), readInfo.id)
487 for key, value in self.extras.items():
488 self.assertEqual(value, readInfo.getComponent(key))
490 psf = readExposure.getPsf()
491 self.assertIsNotNone(psf)
492 self.assertEqual(psf, self.psf)
493 # check psf property getter
494 self.assertEqual(readExposure.psf, self.psf)
496 def checkWcs(self, parentExposure, subExposure):
497 """Compare WCS at corner points of a sub-exposure and its parent exposure
498 By using the function indexToPosition, we should be able to convert the indices
499 (of the four corners (of the sub-exposure)) to positions and use the wcs
500 to get the same sky coordinates for each.
501 """
502 subMI = subExposure.getMaskedImage()
503 subDim = subMI.getDimensions()
505 # Note: pixel positions must be computed relative to XY0 when working
506 # with WCS
507 mainWcs = parentExposure.getWcs()
508 subWcs = subExposure.getWcs()
510 for xSubInd in (0, subDim.getX()-1):
511 for ySubInd in (0, subDim.getY()-1):
512 self.assertSpherePointsAlmostEqual(
513 mainWcs.pixelToSky(
514 afwImage.indexToPosition(xSubInd),
515 afwImage.indexToPosition(ySubInd),
516 ),
517 subWcs.pixelToSky(
518 afwImage.indexToPosition(xSubInd),
519 afwImage.indexToPosition(ySubInd),
520 ))
522 def cmpExposure(self, e1, e2):
523 self.assertEqual(e1.getDetector().getName(),
524 e2.getDetector().getName())
525 self.assertEqual(e1.getDetector().getSerial(),
526 e2.getDetector().getSerial())
527 self.assertEqual(e1.getFilter(), e2.getFilter())
528 xy = lsst.geom.Point2D(0, 0)
529 self.assertEqual(e1.getWcs().pixelToSky(xy)[0],
530 e2.getWcs().pixelToSky(xy)[0])
531 self.assertEqual(e1.getPhotoCalib(), e2.getPhotoCalib())
532 # check PSF identity
533 if not e1.getPsf():
534 self.assertFalse(e2.getPsf())
535 else:
536 self.assertEqual(e1.getPsf(), e2.getPsf())
537 # Check extra components
538 i1 = e1.getInfo()
539 i2 = e2.getInfo()
540 for key in self.extras:
541 self.assertEqual(i1.hasComponent(key), i2.hasComponent(key))
542 if i1.hasComponent(key):
543 self.assertEqual(i1.getComponent(key), i2.getComponent(key))
545 def testCopyExposure(self):
546 """Copy an Exposure (maybe changing type)"""
548 exposureU = afwImage.ExposureU(inFilePathSmall, allowUnsafe=True)
549 exposureU.setWcs(self.wcs)
550 exposureU.setDetector(self.detector)
551 exposureU.setFilter(afwImage.FilterLabel(band="g"))
552 exposureU.setPsf(DummyPsf(4.0))
553 infoU = exposureU.getInfo()
554 for key, value in self.extras.items():
555 infoU.setComponent(key, value)
557 exposureF = exposureU.convertF()
558 self.cmpExposure(exposureF, exposureU)
560 nexp = exposureF.Factory(exposureF, False)
561 self.cmpExposure(exposureF, nexp)
563 # Ensure that the copy was deep.
564 # (actually this test is invalid since getDetector() returns a shared_ptr)
565 # cen0 = exposureU.getDetector().getCenterPixel()
566 # x0,y0 = cen0
567 # det = exposureF.getDetector()
568 # det.setCenterPixel(lsst.geom.Point2D(999.0, 437.8))
569 # self.assertEqual(exposureU.getDetector().getCenterPixel()[0], x0)
570 # self.assertEqual(exposureU.getDetector().getCenterPixel()[1], y0)
572 def testDeepCopyData(self):
573 """Make sure a deep copy of an Exposure has its own data (ticket #2625)
574 """
575 exp = afwImage.ExposureF(6, 7)
576 mi = exp.getMaskedImage()
577 mi.getImage().set(100)
578 mi.getMask().set(5)
579 mi.getVariance().set(200)
581 expCopy = exp.clone()
582 miCopy = expCopy.getMaskedImage()
583 miCopy.getImage().set(-50)
584 miCopy.getMask().set(2)
585 miCopy.getVariance().set(175)
587 self.assertFloatsAlmostEqual(miCopy.getImage().getArray(), -50)
588 self.assertTrue(np.all(miCopy.getMask().getArray() == 2))
589 self.assertFloatsAlmostEqual(miCopy.getVariance().getArray(), 175)
591 self.assertFloatsAlmostEqual(mi.getImage().getArray(), 100)
592 self.assertTrue(np.all(mi.getMask().getArray() == 5))
593 self.assertFloatsAlmostEqual(mi.getVariance().getArray(), 200)
595 def testDeepCopySubData(self):
596 """Make sure a deep copy of a subregion of an Exposure has its own data (ticket #2625)
597 """
598 exp = afwImage.ExposureF(6, 7)
599 mi = exp.getMaskedImage()
600 mi.getImage().set(100)
601 mi.getMask().set(5)
602 mi.getVariance().set(200)
604 bbox = lsst.geom.Box2I(lsst.geom.Point2I(1, 0), lsst.geom.Extent2I(5, 4))
605 expCopy = exp.Factory(exp, bbox, afwImage.PARENT, True)
606 miCopy = expCopy.getMaskedImage()
607 miCopy.getImage().set(-50)
608 miCopy.getMask().set(2)
609 miCopy.getVariance().set(175)
611 self.assertFloatsAlmostEqual(miCopy.getImage().getArray(), -50)
612 self.assertTrue(np.all(miCopy.getMask().getArray() == 2))
613 self.assertFloatsAlmostEqual(miCopy.getVariance().getArray(), 175)
615 self.assertFloatsAlmostEqual(mi.getImage().getArray(), 100)
616 self.assertTrue(np.all(mi.getMask().getArray() == 5))
617 self.assertFloatsAlmostEqual(mi.getVariance().getArray(), 200)
619 def testDeepCopyMetadata(self):
620 """Make sure a deep copy of an Exposure has a deep copy of metadata (ticket #2568)
621 """
622 exp = afwImage.ExposureF(10, 10)
623 expMeta = exp.getMetadata()
624 expMeta.set("foo", 5)
625 expCopy = exp.clone()
626 expCopyMeta = expCopy.getMetadata()
627 expCopyMeta.set("foo", 6)
628 self.assertEqual(expCopyMeta.getScalar("foo"), 6)
629 # this will fail if the bug is present
630 self.assertEqual(expMeta.getScalar("foo"), 5)
632 def testDeepCopySubMetadata(self):
633 """Make sure a deep copy of a subregion of an Exposure has a deep copy of metadata (ticket #2568)
634 """
635 exp = afwImage.ExposureF(10, 10)
636 expMeta = exp.getMetadata()
637 expMeta.set("foo", 5)
638 bbox = lsst.geom.Box2I(lsst.geom.Point2I(1, 0), lsst.geom.Extent2I(5, 5))
639 expCopy = exp.Factory(exp, bbox, afwImage.PARENT, True)
640 expCopyMeta = expCopy.getMetadata()
641 expCopyMeta.set("foo", 6)
642 self.assertEqual(expCopyMeta.getScalar("foo"), 6)
643 # this will fail if the bug is present
644 self.assertEqual(expMeta.getScalar("foo"), 5)
646 def testMakeExposureLeaks(self):
647 """Test for memory leaks in makeExposure (the test is in lsst.utils.tests.MemoryTestCase)"""
648 afwImage.makeMaskedImage(afwImage.ImageU(lsst.geom.Extent2I(10, 20)))
649 afwImage.makeExposure(afwImage.makeMaskedImage(
650 afwImage.ImageU(lsst.geom.Extent2I(10, 20))))
652 def testImageSlices(self):
653 """Test image slicing, which generate sub-images using Box2I under the covers"""
654 exp = afwImage.ExposureF(10, 20)
655 mi = exp.getMaskedImage()
656 mi.image[9, 19] = 10
657 # N.b. Exposures don't support setting/getting the pixels so can't
658 # replicate e.g. Image's slice tests
659 sexp = exp[1:4, 6:10]
660 self.assertEqual(sexp.getDimensions(), lsst.geom.ExtentI(3, 4))
661 sexp = exp[:, -3:, afwImage.LOCAL]
662 self.assertEqual(sexp.getDimensions(),
663 lsst.geom.ExtentI(exp.getWidth(), 3))
664 self.assertEqual(sexp.maskedImage[-1, -1, afwImage.LOCAL],
665 exp.maskedImage[-1, -1, afwImage.LOCAL])
667 def testConversionToScalar(self):
668 """Test that even 1-pixel Exposures can't be converted to scalars"""
669 im = afwImage.ExposureF(10, 20)
671 # only single pixel images may be converted
672 self.assertRaises(TypeError, float, im)
673 # actually, can't convert (img, msk, var) to scalar
674 self.assertRaises(TypeError, float, im[0, 0])
676 def testReadMetadata(self):
677 with lsst.utils.tests.getTempFilePath(".fits") as tmpFile:
678 self.exposureCrWcs.getMetadata().set("FRAZZLE", True)
679 # This will write the main metadata (inc. FRAZZLE) to the primary HDU, and the
680 # WCS to subsequent HDUs, along with INHERIT=T.
681 self.exposureCrWcs.writeFits(tmpFile)
682 # This should read the first non-empty HDU (i.e. it skips the primary), but
683 # goes back and reads it if it finds INHERIT=T. That should let us read
684 # frazzle and the Wcs from the PropertySet returned by
685 # testReadMetadata.
686 md = readMetadata(tmpFile)
687 wcs = afwGeom.makeSkyWcs(md, False)
688 self.assertPairsAlmostEqual(wcs.getPixelOrigin(), self.wcs.getPixelOrigin())
689 self.assertSpherePointsAlmostEqual(wcs.getSkyOrigin(), self.wcs.getSkyOrigin())
690 assert_allclose(wcs.getCdMatrix(), self.wcs.getCdMatrix(), atol=1e-10)
691 frazzle = md.getScalar("FRAZZLE")
692 self.assertTrue(frazzle)
694 def testArchiveKeys(self):
695 with lsst.utils.tests.getTempFilePath(".fits") as tmpFile:
696 exposure1 = afwImage.ExposureF(100, 100, self.wcs)
697 exposure1.setPsf(self.psf)
698 exposure1.writeFits(tmpFile)
699 exposure2 = afwImage.ExposureF(tmpFile)
700 self.assertFalse(exposure2.getMetadata().exists("AR_ID"))
701 self.assertFalse(exposure2.getMetadata().exists("PSF_ID"))
702 self.assertFalse(exposure2.getMetadata().exists("WCS_ID"))
704 def testTicket2861(self):
705 with lsst.utils.tests.getTempFilePath(".fits") as tmpFile:
706 exposure1 = afwImage.ExposureF(100, 100, self.wcs)
707 exposure1.setPsf(self.psf)
708 schema = afwTable.ExposureTable.makeMinimalSchema()
709 coaddInputs = afwImage.CoaddInputs(schema, schema)
710 exposure1.getInfo().setCoaddInputs(coaddInputs)
711 exposure2 = afwImage.ExposureF(exposure1, True)
712 self.assertIsNotNone(exposure2.getInfo().getCoaddInputs())
713 exposure2.writeFits(tmpFile)
714 exposure3 = afwImage.ExposureF(tmpFile)
715 self.assertIsNotNone(exposure3.getInfo().getCoaddInputs())
717 def testGetCutout(self):
718 wcs = self.smallExposure.getWcs()
720 dimensions = [lsst.geom.Extent2I(100, 50), lsst.geom.Extent2I(15, 15), lsst.geom.Extent2I(0, 10),
721 lsst.geom.Extent2I(25, 30), lsst.geom.Extent2I(15, -5),
722 2*self.smallExposure.getDimensions()]
723 locations = [("center", self._getExposureCenter(self.smallExposure)),
724 ("edge", wcs.pixelToSky(lsst.geom.Point2D(0, 0))),
725 ("rounding test", wcs.pixelToSky(lsst.geom.Point2D(0.2, 0.7))),
726 ("just inside", wcs.pixelToSky(lsst.geom.Point2D(-0.5 + 1e-4, -0.5 + 1e-4))),
727 ("just outside", wcs.pixelToSky(lsst.geom.Point2D(-0.5 - 1e-4, -0.5 - 1e-4))),
728 ("outside", wcs.pixelToSky(lsst.geom.Point2D(-1000, -1000)))]
729 for cutoutSize in dimensions:
730 for label, cutoutCenter in locations:
731 msg = 'Cutout size = %s, location = %s' % (cutoutSize, label)
732 if "outside" not in label and all(cutoutSize.gt(0)):
733 cutout = self.smallExposure.getCutout(cutoutCenter, cutoutSize)
734 centerInPixels = wcs.skyToPixel(cutoutCenter)
735 precision = (1 + 1e-4)*np.sqrt(0.5)*wcs.getPixelScale(centerInPixels)
736 self._checkCutoutProperties(cutout, cutoutSize, cutoutCenter, precision, msg)
737 self._checkCutoutPixels(
738 cutout,
739 self._getValidCorners(self.smallExposure.getBBox(), cutout.getBBox()),
740 msg)
742 # Need a valid WCS
743 with self.assertRaises(pexExcept.LogicError, msg=msg):
744 self.exposureMiOnly.getCutout(cutoutCenter, cutoutSize)
745 else:
746 with self.assertRaises(pexExcept.InvalidParameterError, msg=msg):
747 self.smallExposure.getCutout(cutoutCenter, cutoutSize)
749 def testGetConvexPolygon(self):
750 """Test the convex polygon."""
751 # Check that we do not have a convex polygon for the plain exposure.
752 self.assertIsNone(self.exposureMiOnly.convex_polygon)
754 # Check that all the points in the padded bounding box are in the polygon
755 bbox = self.exposureMiWcs.getBBox()
756 # Grow by the default padding.
757 bbox.grow(10)
758 x, y = np.meshgrid(np.arange(bbox.getBeginX(), bbox.getEndX(), dtype=np.float64),
759 np.arange(bbox.getBeginY(), bbox.getEndY(), dtype=np.float64))
760 wcs = self.exposureMiWcs.wcs
761 ra, dec = wcs.pixelToSkyArray(x.ravel(),
762 y.ravel())
764 poly = self.exposureMiWcs.convex_polygon
765 contains = poly.contains(ra, dec)
766 np.testing.assert_array_equal(contains, np.ones(len(contains), dtype=bool))
768 # Check that points one pixel outside of the bounding box are not in the polygon
769 bbox.grow(1)
771 ra, dec = wcs.pixelToSkyArray(
772 np.linspace(bbox.getBeginX(), bbox.getEndX(), 100),
773 np.full(100, bbox.getBeginY()))
774 contains = poly.contains(ra, dec)
775 np.testing.assert_array_equal(contains, np.zeros(len(contains), dtype=bool))
777 ra, dec = wcs.pixelToSkyArray(
778 np.linspace(bbox.getBeginX(), bbox.getEndX(), 100),
779 np.full(100, bbox.getEndY()))
780 contains = poly.contains(ra, dec)
781 np.testing.assert_array_equal(contains, np.zeros(len(contains), dtype=bool))
783 ra, dec = wcs.pixelToSkyArray(
784 np.full(100, bbox.getBeginX()),
785 np.linspace(bbox.getBeginY(), bbox.getEndY(), 100))
786 contains = poly.contains(ra, dec)
787 np.testing.assert_array_equal(contains, np.zeros(len(contains), dtype=bool))
789 ra, dec = wcs.pixelToSkyArray(
790 np.full(100, bbox.getEndX()),
791 np.linspace(bbox.getBeginY(), bbox.getEndY(), 100))
792 contains = poly.contains(ra, dec)
793 np.testing.assert_array_equal(contains, np.zeros(len(contains), dtype=bool))
795 def testContainsSkyCoords(self):
796 """Test the sky coord containment code."""
797 self.assertRaisesRegex(ValueError,
798 "Exposure does not have a valid WCS",
799 self.exposureMiOnly.containsSkyCoords,
800 0.0,
801 0.0)
803 # Check that all the points within the bounding box are contained
804 bbox = self.exposureMiWcs.getBBox()
805 x, y = np.meshgrid(np.arange(bbox.getBeginX() + 1, bbox.getEndX() - 1),
806 np.arange(bbox.getBeginY() + 1, bbox.getEndY() - 1))
807 wcs = self.exposureMiWcs.wcs
808 ra, dec = wcs.pixelToSkyArray(x.ravel().astype(np.float64),
809 y.ravel().astype(np.float64))
811 contains = self.exposureMiWcs.containsSkyCoords(ra*units.radian,
812 dec*units.radian)
813 np.testing.assert_array_equal(contains, np.ones(len(contains), dtype=bool))
815 # Same test, everything in degrees.
816 ra, dec = wcs.pixelToSkyArray(x.ravel().astype(np.float64),
817 y.ravel().astype(np.float64),
818 degrees=True)
820 contains = self.exposureMiWcs.containsSkyCoords(ra*units.degree,
821 dec*units.degree)
822 np.testing.assert_array_equal(contains, np.ones(len(contains), dtype=bool))
824 # Prepend and append some positions out of the box.
825 ra = np.concatenate(([300.0], ra, [180.]))
826 dec = np.concatenate(([50.0], dec, [50.0]))
828 contains = self.exposureMiWcs.containsSkyCoords(ra*units.degree,
829 dec*units.degree)
830 compare = np.ones(len(contains), dtype=bool)
831 compare[0] = False
832 compare[-1] = False
833 np.testing.assert_array_equal(contains, compare)
835 def _checkCutoutProperties(self, cutout, size, center, precision, msg):
836 """Test whether a cutout has the desired size and position.
838 Parameters
839 ----------
840 cutout : `lsst.afw.image.Exposure`
841 The cutout to test.
842 size : `lsst.geom.Extent2I`
843 The expected dimensions of ``cutout``.
844 center : `lsst.geom.SpherePoint`
845 The expected center of ``cutout``.
846 precision : `lsst.geom.Angle`
847 The precision to which ``center`` must match.
848 msg : `str`
849 An error message suffix describing test parameters.
850 """
851 newCenter = self._getExposureCenter(cutout)
852 self.assertIsNotNone(cutout, msg=msg)
853 self.assertSpherePointsAlmostEqual(newCenter, center, maxSep=precision, msg=msg)
854 self.assertEqual(cutout.getWidth(), size[0], msg=msg)
855 self.assertEqual(cutout.getHeight(), size[1], msg=msg)
857 def _checkCutoutPixels(self, cutout, validCorners, msg):
858 """Test whether a cutout has valid/empty pixels where expected.
860 Parameters
861 ----------
862 cutout : `lsst.afw.image.Exposure`
863 The cutout to test.
864 validCorners : iterable of `lsst.geom.Point2I`
865 The corners of ``cutout`` that should be drawn from the original image.
866 msg : `str`
867 An error message suffix describing test parameters.
868 """
869 mask = cutout.getMaskedImage().getMask()
870 edgeMask = mask.getPlaneBitMask("NO_DATA")
872 for corner in cutout.getBBox().getCorners():
873 maskBitsSet = mask[corner] & edgeMask
874 if corner in validCorners:
875 self.assertEqual(maskBitsSet, 0, msg=msg)
876 else:
877 self.assertEqual(maskBitsSet, edgeMask, msg=msg)
879 def _getExposureCenter(self, exposure):
880 """Return the sky coordinates of an Exposure's center.
882 Parameters
883 ----------
884 exposure : `lsst.afw.image.Exposure`
885 The image whose center is desired.
887 Returns
888 -------
889 center : `lsst.geom.SpherePoint`
890 The position at the center of ``exposure``.
891 """
892 return exposure.getWcs().pixelToSky(lsst.geom.Box2D(exposure.getBBox()).getCenter())
894 def _getValidCorners(self, imageBox, cutoutBox):
895 """Return the corners of a cutout that are constrained by the original image.
897 Parameters
898 ----------
899 imageBox: `lsst.geom.Extent2I`
900 The bounding box of the original image.
901 cutoutBox : `lsst.geom.Box2I`
902 The bounding box of the cutout.
904 Returns
905 -------
906 corners : iterable of `lsst.geom.Point2I`
907 The corners that are drawn from the original image.
908 """
909 return [corner for corner in cutoutBox.getCorners() if corner in imageBox]
912class ExposureInfoTestCase(lsst.utils.tests.TestCase):
913 def setUp(self):
914 super().setUp()
916 self.wcs = afwGeom.makeSkyWcs(lsst.geom.Point2D(0.0, 0.0),
917 lsst.geom.SpherePoint(2.0, 34.0, lsst.geom.degrees),
918 np.identity(2),
919 )
920 self.photoCalib = afwImage.PhotoCalib(1.5)
921 self.psf = DummyPsf(2.0)
922 self.detector = DetectorWrapper().detector
923 self.summaryStats = afwImage.ExposureSummaryStats(ra=100.0)
924 self.polygon = afwGeom.Polygon(lsst.geom.Box2D(lsst.geom.Point2D(0.0, 0.0),
925 lsst.geom.Point2D(25.0, 20.0)))
926 self.coaddInputs = afwImage.CoaddInputs()
927 self.apCorrMap = afwImage.ApCorrMap()
928 self.transmissionCurve = afwImage.TransmissionCurve.makeIdentity()
930 self.exposureInfo = afwImage.ExposureInfo()
931 self.gFilterLabel = afwImage.FilterLabel(band="g")
932 self.exposureId = 42
934 def _checkAlias(self, exposureInfo, key, value, has, get):
935 self.assertFalse(has())
936 self.assertFalse(exposureInfo.hasComponent(key))
937 self.assertIsNone(get())
938 self.assertIsNone(exposureInfo.getComponent(key))
940 self.exposureInfo.setComponent(key, value)
941 self.assertTrue(has())
942 self.assertTrue(exposureInfo.hasComponent(key))
943 self.assertIsNotNone(get())
944 self.assertIsNotNone(exposureInfo.getComponent(key))
945 self.assertEqual(get(), value)
946 self.assertEqual(exposureInfo.getComponent(key), value)
948 self.exposureInfo.removeComponent(key)
949 self.assertFalse(has())
950 self.assertFalse(exposureInfo.hasComponent(key))
951 self.assertIsNone(get())
952 self.assertIsNone(exposureInfo.getComponent(key))
954 def testAliases(self):
955 cls = type(self.exposureInfo)
956 self._checkAlias(self.exposureInfo, cls.KEY_WCS, self.wcs,
957 self.exposureInfo.hasWcs, self.exposureInfo.getWcs)
958 self._checkAlias(self.exposureInfo, cls.KEY_PSF, self.psf,
959 self.exposureInfo.hasPsf, self.exposureInfo.getPsf)
960 self._checkAlias(self.exposureInfo, cls.KEY_PHOTO_CALIB, self.photoCalib,
961 self.exposureInfo.hasPhotoCalib, self.exposureInfo.getPhotoCalib)
962 self._checkAlias(self.exposureInfo, cls.KEY_DETECTOR, self.detector,
963 self.exposureInfo.hasDetector, self.exposureInfo.getDetector)
964 self._checkAlias(self.exposureInfo, cls.KEY_VALID_POLYGON, self.polygon,
965 self.exposureInfo.hasValidPolygon, self.exposureInfo.getValidPolygon)
966 self._checkAlias(self.exposureInfo, cls.KEY_COADD_INPUTS, self.coaddInputs,
967 self.exposureInfo.hasCoaddInputs, self.exposureInfo.getCoaddInputs)
968 self._checkAlias(self.exposureInfo, cls.KEY_AP_CORR_MAP, self.apCorrMap,
969 self.exposureInfo.hasApCorrMap, self.exposureInfo.getApCorrMap)
970 self._checkAlias(self.exposureInfo, cls.KEY_TRANSMISSION_CURVE, self.transmissionCurve,
971 self.exposureInfo.hasTransmissionCurve, self.exposureInfo.getTransmissionCurve)
972 self._checkAlias(self.exposureInfo, cls.KEY_SUMMARY_STATS, self.summaryStats,
973 self.exposureInfo.hasSummaryStats, self.exposureInfo.getSummaryStats)
974 self._checkAlias(self.exposureInfo, cls.KEY_FILTER, self.gFilterLabel,
975 self.exposureInfo.hasFilter, self.exposureInfo.getFilter)
977 def testId(self):
978 self.exposureInfo.setVisitInfo(afwImage.VisitInfo())
980 self.assertFalse(self.exposureInfo.hasId())
981 self.assertIsNone(self.exposureInfo.getId())
982 self.assertIsNone(self.exposureInfo.id)
984 self.exposureInfo.setId(self.exposureId)
985 self.assertTrue(self.exposureInfo.hasId())
986 self.assertIsNotNone(self.exposureInfo.getId())
987 self.assertIsNotNone(self.exposureInfo.id)
988 self.assertEqual(self.exposureInfo.getId(), self.exposureId)
989 self.assertEqual(self.exposureInfo.id, self.exposureId)
991 self.exposureInfo.id = 99899
992 self.assertEqual(self.exposureInfo.getId(), 99899)
994 self.exposureInfo.id = None
995 self.assertFalse(self.exposureInfo.hasId())
996 self.assertIsNone(self.exposureInfo.getId())
997 self.assertIsNone(self.exposureInfo.id)
999 def testCopy(self):
1000 # Test that ExposureInfos have independently settable state
1001 copy = afwImage.ExposureInfo(self.exposureInfo, True)
1002 self.assertEqual(self.exposureInfo.getWcs(), copy.getWcs())
1004 newWcs = afwGeom.makeSkyWcs(lsst.geom.Point2D(-23.0, 8.0),
1005 lsst.geom.SpherePoint(0.0, 0.0, lsst.geom.degrees),
1006 np.identity(2),
1007 )
1008 copy.setWcs(newWcs)
1009 self.assertEqual(copy.getWcs(), newWcs)
1010 self.assertNotEqual(self.exposureInfo.getWcs(), copy.getWcs())
1012 def testMissingProperties(self):
1013 # Test that invalid properties return None instead of raising
1014 exposureInfo = afwImage.ExposureInfo()
1016 self.assertIsNone(exposureInfo.id)
1019class ExposureNoAfwdataTestCase(lsst.utils.tests.TestCase):
1020 """Tests of Exposure that don't require afwdata.
1022 These tests use the trivial exposures written to ``afw/tests/data``.
1023 """
1024 def setUp(self):
1025 self.dataDir = os.path.join(os.path.split(__file__)[0], "data")
1027 # Check the values below against what was written by comparing with
1028 # the code in `afw/tests/data/makeTestExposure.py`
1029 nx = ny = 10
1030 image = afwImage.ImageF(np.arange(nx*ny, dtype='f').reshape(nx, ny))
1031 variance = afwImage.ImageF(np.ones((nx, ny), dtype='f'))
1032 mask = afwImage.MaskX(nx, ny)
1033 mask.array[5, 5] = 5
1034 self.maskedImage = afwImage.MaskedImageF(image, mask, variance)
1035 self.exposureId = 12345
1037 self.v0PhotoCalib = afwImage.makePhotoCalibFromCalibZeroPoint(1e6, 2e4)
1038 self.v1PhotoCalib = afwImage.PhotoCalib(1e6, 2e4)
1039 self.v1FilterLabel = afwImage.FilterLabel(physical="ha")
1040 self.v2FilterLabel = afwImage.FilterLabel(band="N656", physical="ha")
1042 def testReadUnversioned(self):
1043 """Test that we can read an unversioned (implicit verison 0) file.
1044 """
1045 filename = os.path.join(self.dataDir, "exposure-noversion.fits")
1046 exposure = afwImage.ExposureF.readFits(filename)
1048 self.assertMaskedImagesEqual(exposure.maskedImage, self.maskedImage)
1050 self.assertEqual(exposure.info.id, self.exposureId)
1051 self.assertEqual(exposure.getPhotoCalib(), self.v0PhotoCalib)
1052 self.assertEqual(exposure.getFilter(), self.v1FilterLabel)
1054 def testReadVersion0(self):
1055 """Test that we can read a version 0 file.
1056 This file should be identical to the unversioned one, except that it
1057 is marked as ExposureInfo version 0 in the header.
1058 """
1059 filename = os.path.join(self.dataDir, "exposure-version-0.fits")
1060 exposure = afwImage.ExposureF.readFits(filename)
1062 self.assertMaskedImagesEqual(exposure.maskedImage, self.maskedImage)
1064 self.assertEqual(exposure.info.id, self.exposureId)
1065 self.assertEqual(exposure.getPhotoCalib(), self.v0PhotoCalib)
1066 self.assertEqual(exposure.getFilter(), self.v1FilterLabel)
1068 # Check that the metadata reader parses the file correctly
1069 reader = afwImage.ExposureFitsReader(filename)
1070 self.assertEqual(reader.readExposureInfo().getPhotoCalib(), self.v0PhotoCalib)
1071 self.assertEqual(reader.readPhotoCalib(), self.v0PhotoCalib)
1073 def testReadVersion1(self):
1074 """Test that we can read a version 1 file.
1075 Version 1 replaced Calib with PhotoCalib.
1076 """
1077 filename = os.path.join(self.dataDir, "exposure-version-1.fits")
1078 exposure = afwImage.ExposureF.readFits(filename)
1080 self.assertMaskedImagesEqual(exposure.maskedImage, self.maskedImage)
1082 self.assertEqual(exposure.info.id, self.exposureId)
1083 self.assertEqual(exposure.getPhotoCalib(), self.v1PhotoCalib)
1084 self.assertEqual(exposure.getFilter(), self.v1FilterLabel)
1086 # Check that the metadata reader parses the file correctly
1087 reader = afwImage.ExposureFitsReader(filename)
1088 self.assertEqual(reader.readExposureInfo().getPhotoCalib(), self.v1PhotoCalib)
1089 self.assertEqual(reader.readPhotoCalib(), self.v1PhotoCalib)
1091 def testReadVersion2(self):
1092 """Test that we can read a version 2 file.
1093 Version 2 replaced Filter with FilterLabel.
1094 """
1095 filename = os.path.join(self.dataDir, "exposure-version-2.fits")
1096 exposure = afwImage.ExposureF.readFits(filename)
1098 self.assertMaskedImagesEqual(exposure.maskedImage, self.maskedImage)
1100 self.assertEqual(exposure.info.id, self.exposureId)
1101 self.assertEqual(exposure.getPhotoCalib(), self.v1PhotoCalib)
1102 self.assertEqual(exposure.getFilter(), self.v2FilterLabel)
1104 # Check that the metadata reader parses the file correctly
1105 reader = afwImage.ExposureFitsReader(filename)
1106 self.assertEqual(reader.readExposureInfo().getPhotoCalib(), self.v1PhotoCalib)
1107 self.assertEqual(reader.readPhotoCalib(), self.v1PhotoCalib)
1109 def testExposureSummaryExtraComponents(self):
1110 """Test that we can read an exposure summary with extra components.
1111 """
1112 testDict = {'ra': 0.0,
1113 'dec': 0.0,
1114 'nonsense': 1.0}
1115 bytes = yaml.dump(testDict, encoding='utf-8')
1116 with self.assertWarns(FutureWarning):
1117 summaryStats = lsst.afw.image.ExposureSummaryStats._read(bytes)
1119 self.assertEqual(summaryStats.ra, testDict['ra'])
1120 self.assertEqual(summaryStats.dec, testDict['dec'])
1122 def testExposureSummarySchema(self):
1123 """Test that we can make a schema for an exposure summary and populate
1124 records with that schema.
1125 """
1126 schema = afwTable.Schema()
1127 afwImage.ExposureSummaryStats.update_schema(schema)
1128 self.maxDiff = None
1129 self.assertEqual(
1130 {field.name for field in dataclasses.fields(afwImage.ExposureSummaryStats)},
1131 set(schema.getNames()) | {"version"},
1132 )
1133 catalog = afwTable.BaseCatalog(schema)
1134 summary1 = afwImage.ExposureSummaryStats()
1135 for n, field in enumerate(dataclasses.fields(afwImage.ExposureSummaryStats)):
1136 # Set fields to deterministic, distinct, but arbitrary values.
1137 if field.type == "float":
1138 setattr(summary1, field.name, float(0.5**n))
1139 elif field.type == "int":
1140 setattr(summary1, field.name, 10*n)
1141 elif field.type == "list[float]":
1142 setattr(summary1, field.name, [n + 0.1, n + 0.2, n + 0.3, n + 0.4])
1143 else:
1144 raise TypeError(f"Unexpected type: {field.type!r}.")
1145 record = catalog.addNew()
1146 summary1.update_record(record)
1147 summary2 = afwImage.ExposureSummaryStats.from_record(record)
1148 self.assertEqual(summary1, summary2)
1151class MemoryTester(lsst.utils.tests.MemoryTestCase):
1152 pass
1155def setup_module(module):
1156 lsst.utils.tests.init()
1159if __name__ == "__main__": 1159 ↛ 1160line 1159 didn't jump to line 1160, because the condition on line 1159 was never true
1160 lsst.utils.tests.init()
1161 unittest.main()