Coverage for tests/test_exposure.py: 10%
697 statements
« prev ^ index » next coverage.py v7.5.0, created at 2024-04-25 00:01 -0700
« prev ^ index » next coverage.py v7.5.0, created at 2024-04-25 00:01 -0700
1# This file is part of afw.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (https://www.lsst.org).
6# See the COPYRIGHT file at the top-level directory of this distribution
7# for details of code ownership.
8#
9# This program is free software: you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation, either version 3 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program. If not, see <https://www.gnu.org/licenses/>.
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)
219 with self.assertWarns(FutureWarning):
220 self.assertEqual(exposure.getFilterLabel(), gFilterLabel)
222 self.assertTrue(exposure.getInfo().hasWcs())
223 # check the ExposureInfo property
224 self.assertTrue(exposure.info.hasWcs())
225 self.assertEqual(exposure.getInfo().getWcs().getPixelOrigin(),
226 self.wcs.getPixelOrigin())
227 self.assertEqual(exposure.getInfo().getDetector().getName(),
228 self.detector.getName())
229 self.assertEqual(exposure.getInfo().getDetector().getSerial(),
230 self.detector.getSerial())
231 self.assertEqual(exposure.getInfo().getFilter(), gFilterLabel)
232 with self.assertWarns(FutureWarning):
233 self.assertEqual(exposure.getInfo().getFilterLabel(), gFilterLabel)
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)
280 with self.assertWarns(FutureWarning):
281 self.assertEqual(exposure.getFilterLabel(), gFilterLabel)
283 # test properties
284 self.assertEqual(exposure.detector.getName(), self.detector.getName())
285 self.assertEqual(exposure.filter, gFilterLabel)
286 self.assertEqual(exposure.wcs, self.wcs)
288 def testVisitInfoFitsPersistence(self):
289 """Test saving an exposure to FITS and reading it back in preserves (some) VisitInfo fields"""
290 exposureId = 5
291 exposureTime = 12.3
292 boresightRotAngle = 45.6 * lsst.geom.degrees
293 weather = Weather(1.1, 2.2, 0.3)
294 visitInfo = afwImage.VisitInfo(
295 exposureId=exposureId,
296 exposureTime=exposureTime,
297 boresightRotAngle=boresightRotAngle,
298 weather=weather,
299 )
300 photoCalib = afwImage.PhotoCalib(3.4, 5.6)
301 exposureInfo = afwImage.ExposureInfo()
302 exposureInfo.setVisitInfo(visitInfo)
303 exposureInfo.setPhotoCalib(photoCalib)
304 exposureInfo.setDetector(self.detector)
305 gFilterLabel = afwImage.FilterLabel(band="g")
306 exposureInfo.setFilter(gFilterLabel)
307 maskedImage = afwImage.MaskedImageF(inFilePathSmall)
308 exposure = afwImage.ExposureF(maskedImage, exposureInfo)
309 with lsst.utils.tests.getTempFilePath(".fits") as tmpFile:
310 exposure.writeFits(tmpFile)
311 rtExposure = afwImage.ExposureF(tmpFile)
312 rtVisitInfo = rtExposure.getInfo().getVisitInfo()
313 self.assertEqual(rtVisitInfo.getWeather(), weather)
314 self.assertEqual(rtExposure.getPhotoCalib(), photoCalib)
315 self.assertEqual(rtExposure.getFilter(), gFilterLabel)
316 with self.assertWarns(FutureWarning):
317 self.assertEqual(rtExposure.getFilterLabel(), gFilterLabel)
319 # Test property getters.
320 self.assertEqual(rtExposure.photoCalib, photoCalib)
321 self.assertEqual(rtExposure.filter, gFilterLabel)
322 # NOTE: we can't test visitInfo equality, because most fields are NaN.
323 self.assertIsNotNone(rtExposure.visitInfo)
325 def testSetMembers(self):
326 """
327 Test that the MaskedImage and the WCS of an Exposure can be set.
328 """
329 exposure = afwImage.ExposureF()
331 maskedImage = afwImage.MaskedImageF(inFilePathSmall)
332 exposure.setMaskedImage(maskedImage)
333 exposure.setWcs(self.wcs)
334 exposure.setDetector(self.detector)
335 exposure.setFilter(afwImage.FilterLabel(band="g"))
337 self.assertEqual(exposure.getDetector().getName(),
338 self.detector.getName())
339 self.assertEqual(exposure.getDetector().getSerial(),
340 self.detector.getSerial())
341 self.assertEqual(exposure.getFilter().bandLabel, "g")
342 with self.assertWarns(FutureWarning):
343 self.assertEqual(exposure.getFilterLabel().bandLabel, "g")
344 self.assertEqual(exposure.getWcs(), self.wcs)
346 # The PhotoCalib tests are in test_photoCalib.py;
347 # here we just check that it's gettable and settable.
348 self.assertIsNone(exposure.getPhotoCalib())
350 photoCalib = afwImage.PhotoCalib(511.1, 44.4)
351 exposure.setPhotoCalib(photoCalib)
352 self.assertEqual(exposure.getPhotoCalib(), photoCalib)
354 # Psfs next
355 self.assertFalse(exposure.hasPsf())
356 exposure.setPsf(self.psf)
357 self.assertTrue(exposure.hasPsf())
359 exposure.setPsf(DummyPsf(1.0)) # we can reset the Psf
361 # extras next
362 info = exposure.getInfo()
363 for key, value in self.extras.items():
364 self.assertFalse(info.hasComponent(key))
365 self.assertIsNone(info.getComponent(key))
366 info.setComponent(key, value)
367 self.assertTrue(info.hasComponent(key))
368 self.assertEqual(info.getComponent(key), value)
369 info.removeComponent(key)
370 self.assertFalse(info.hasComponent(key))
372 # Test that we can set the MaskedImage and WCS of an Exposure
373 # that already has both
374 self.exposureMiWcs.setMaskedImage(maskedImage)
375 exposure.setWcs(self.wcs)
377 def testHasWcs(self):
378 """
379 Test if an Exposure has a WCS or not.
380 """
381 self.assertFalse(self.exposureBlank.hasWcs())
383 self.assertFalse(self.exposureMiOnly.hasWcs())
384 self.assertTrue(self.exposureMiWcs.hasWcs())
385 self.assertTrue(self.exposureCrWcs.hasWcs())
386 self.assertFalse(self.exposureCrOnly.hasWcs())
388 def testGetSubExposure(self):
389 """
390 Test that a subExposure of the original Exposure can be obtained.
392 The MaskedImage class should throw a
393 lsst::pex::exceptions::InvalidParameter if the requested
394 subRegion is not fully contained within the original
395 MaskedImage.
397 """
398 #
399 # This subExposure is valid
400 #
401 subBBox = lsst.geom.Box2I(lsst.geom.Point2I(40, 50),
402 lsst.geom.Extent2I(10, 10))
403 subExposure = self.exposureCrWcs.Factory(
404 self.exposureCrWcs, subBBox, afwImage.LOCAL)
406 self.checkWcs(self.exposureCrWcs, subExposure)
408 # this subRegion is not valid and should trigger an exception
409 # from the MaskedImage class and should trigger an exception
410 # from the WCS class for the MaskedImage 871034p_1_MI.
412 subRegion3 = lsst.geom.Box2I(lsst.geom.Point2I(100, 100),
413 lsst.geom.Extent2I(10, 10))
415 def getSubRegion():
416 self.exposureCrWcs.Factory(
417 self.exposureCrWcs, subRegion3, afwImage.LOCAL)
419 self.assertRaises(pexExcept.LengthError, getSubRegion)
421 # this subRegion is not valid and should trigger an exception
422 # from the MaskedImage class only for the MaskedImage small_MI.
423 # small_MI (cols, rows) = (256, 256)
425 subRegion4 = lsst.geom.Box2I(lsst.geom.Point2I(250, 250),
426 lsst.geom.Extent2I(10, 10))
428 def getSubRegion():
429 self.exposureCrWcs.Factory(
430 self.exposureCrWcs, subRegion4, afwImage.LOCAL)
432 self.assertRaises(pexExcept.LengthError, getSubRegion)
434 # check the sub- and parent- exposures are using the same Wcs
435 # transformation
436 subBBox = lsst.geom.Box2I(lsst.geom.Point2I(40, 50),
437 lsst.geom.Extent2I(10, 10))
438 subExposure = self.exposureCrWcs.Factory(
439 self.exposureCrWcs, subBBox, afwImage.LOCAL)
440 parentSkyPos = self.exposureCrWcs.getWcs().pixelToSky(0, 0)
442 subExpSkyPos = subExposure.getWcs().pixelToSky(0, 0)
444 self.assertSpherePointsAlmostEqual(parentSkyPos, subExpSkyPos, msg="Wcs in sub image has changed")
446 def testReadWriteFits(self):
447 """Test readFits and writeFits.
448 """
449 # This should pass without an exception
450 mainExposure = afwImage.ExposureF(inFilePathSmall)
451 mainExposure.info.setId(self.id)
452 mainExposure.setDetector(self.detector)
454 subBBox = lsst.geom.Box2I(lsst.geom.Point2I(10, 10),
455 lsst.geom.Extent2I(40, 50))
456 subExposure = mainExposure.Factory(
457 mainExposure, subBBox, afwImage.LOCAL)
458 self.checkWcs(mainExposure, subExposure)
459 det = subExposure.getDetector()
460 self.assertTrue(det)
462 subExposure = afwImage.ExposureF(
463 inFilePathSmall, subBBox, afwImage.LOCAL)
465 self.checkWcs(mainExposure, subExposure)
467 # This should throw an exception
468 def getExposure():
469 afwImage.ExposureF(inFilePathSmallImage)
471 self.assertRaises(FitsError, getExposure)
473 mainExposure.setPsf(self.psf)
475 # Make sure we can write without an exception
476 photoCalib = afwImage.PhotoCalib(1e-10, 1e-12)
477 mainExposure.setPhotoCalib(photoCalib)
479 mainInfo = mainExposure.getInfo()
480 for key, value in self.extras.items():
481 mainInfo.setComponent(key, value)
483 with lsst.utils.tests.getTempFilePath(".fits") as tmpFile:
484 mainExposure.writeFits(tmpFile)
486 readExposure = type(mainExposure)(tmpFile)
488 #
489 # Check the round-tripping
490 #
491 self.assertIsNotNone(mainExposure.getFilter())
492 self.assertEqual(mainExposure.getFilter(),
493 readExposure.getFilter())
494 with self.assertWarns(FutureWarning):
495 self.assertIsNotNone(mainExposure.getFilterLabel())
496 self.assertEqual(mainExposure.getFilterLabel(),
497 readExposure.getFilterLabel())
499 self.assertEqual(photoCalib, readExposure.getPhotoCalib())
501 readInfo = readExposure.getInfo()
502 self.assertEqual(mainExposure.info.getId(), readInfo.id)
503 for key, value in self.extras.items():
504 self.assertEqual(value, readInfo.getComponent(key))
506 psf = readExposure.getPsf()
507 self.assertIsNotNone(psf)
508 self.assertEqual(psf, self.psf)
509 # check psf property getter
510 self.assertEqual(readExposure.psf, self.psf)
512 def checkWcs(self, parentExposure, subExposure):
513 """Compare WCS at corner points of a sub-exposure and its parent exposure
514 By using the function indexToPosition, we should be able to convert the indices
515 (of the four corners (of the sub-exposure)) to positions and use the wcs
516 to get the same sky coordinates for each.
517 """
518 subMI = subExposure.getMaskedImage()
519 subDim = subMI.getDimensions()
521 # Note: pixel positions must be computed relative to XY0 when working
522 # with WCS
523 mainWcs = parentExposure.getWcs()
524 subWcs = subExposure.getWcs()
526 for xSubInd in (0, subDim.getX()-1):
527 for ySubInd in (0, subDim.getY()-1):
528 self.assertSpherePointsAlmostEqual(
529 mainWcs.pixelToSky(
530 afwImage.indexToPosition(xSubInd),
531 afwImage.indexToPosition(ySubInd),
532 ),
533 subWcs.pixelToSky(
534 afwImage.indexToPosition(xSubInd),
535 afwImage.indexToPosition(ySubInd),
536 ))
538 def cmpExposure(self, e1, e2):
539 self.assertEqual(e1.getDetector().getName(),
540 e2.getDetector().getName())
541 self.assertEqual(e1.getDetector().getSerial(),
542 e2.getDetector().getSerial())
543 self.assertEqual(e1.getFilter(), e2.getFilter())
544 with self.assertWarns(FutureWarning):
545 self.assertEqual(e1.getFilterLabel(), e2.getFilterLabel())
546 xy = lsst.geom.Point2D(0, 0)
547 self.assertEqual(e1.getWcs().pixelToSky(xy)[0],
548 e2.getWcs().pixelToSky(xy)[0])
549 self.assertEqual(e1.getPhotoCalib(), e2.getPhotoCalib())
550 # check PSF identity
551 if not e1.getPsf():
552 self.assertFalse(e2.getPsf())
553 else:
554 self.assertEqual(e1.getPsf(), e2.getPsf())
555 # Check extra components
556 i1 = e1.getInfo()
557 i2 = e2.getInfo()
558 for key in self.extras:
559 self.assertEqual(i1.hasComponent(key), i2.hasComponent(key))
560 if i1.hasComponent(key):
561 self.assertEqual(i1.getComponent(key), i2.getComponent(key))
563 def testCopyExposure(self):
564 """Copy an Exposure (maybe changing type)"""
566 exposureU = afwImage.ExposureU(inFilePathSmall, allowUnsafe=True)
567 exposureU.setWcs(self.wcs)
568 exposureU.setDetector(self.detector)
569 exposureU.setFilter(afwImage.FilterLabel(band="g"))
570 exposureU.setPsf(DummyPsf(4.0))
571 infoU = exposureU.getInfo()
572 for key, value in self.extras.items():
573 infoU.setComponent(key, value)
575 exposureF = exposureU.convertF()
576 self.cmpExposure(exposureF, exposureU)
578 nexp = exposureF.Factory(exposureF, False)
579 self.cmpExposure(exposureF, nexp)
581 # Ensure that the copy was deep.
582 # (actually this test is invalid since getDetector() returns a shared_ptr)
583 # cen0 = exposureU.getDetector().getCenterPixel()
584 # x0,y0 = cen0
585 # det = exposureF.getDetector()
586 # det.setCenterPixel(lsst.geom.Point2D(999.0, 437.8))
587 # self.assertEqual(exposureU.getDetector().getCenterPixel()[0], x0)
588 # self.assertEqual(exposureU.getDetector().getCenterPixel()[1], y0)
590 def testDeepCopyData(self):
591 """Make sure a deep copy of an Exposure has its own data (ticket #2625)
592 """
593 exp = afwImage.ExposureF(6, 7)
594 mi = exp.getMaskedImage()
595 mi.getImage().set(100)
596 mi.getMask().set(5)
597 mi.getVariance().set(200)
599 expCopy = exp.clone()
600 miCopy = expCopy.getMaskedImage()
601 miCopy.getImage().set(-50)
602 miCopy.getMask().set(2)
603 miCopy.getVariance().set(175)
605 self.assertFloatsAlmostEqual(miCopy.getImage().getArray(), -50)
606 self.assertTrue(np.all(miCopy.getMask().getArray() == 2))
607 self.assertFloatsAlmostEqual(miCopy.getVariance().getArray(), 175)
609 self.assertFloatsAlmostEqual(mi.getImage().getArray(), 100)
610 self.assertTrue(np.all(mi.getMask().getArray() == 5))
611 self.assertFloatsAlmostEqual(mi.getVariance().getArray(), 200)
613 def testDeepCopySubData(self):
614 """Make sure a deep copy of a subregion of an Exposure has its own data (ticket #2625)
615 """
616 exp = afwImage.ExposureF(6, 7)
617 mi = exp.getMaskedImage()
618 mi.getImage().set(100)
619 mi.getMask().set(5)
620 mi.getVariance().set(200)
622 bbox = lsst.geom.Box2I(lsst.geom.Point2I(1, 0), lsst.geom.Extent2I(5, 4))
623 expCopy = exp.Factory(exp, bbox, afwImage.PARENT, True)
624 miCopy = expCopy.getMaskedImage()
625 miCopy.getImage().set(-50)
626 miCopy.getMask().set(2)
627 miCopy.getVariance().set(175)
629 self.assertFloatsAlmostEqual(miCopy.getImage().getArray(), -50)
630 self.assertTrue(np.all(miCopy.getMask().getArray() == 2))
631 self.assertFloatsAlmostEqual(miCopy.getVariance().getArray(), 175)
633 self.assertFloatsAlmostEqual(mi.getImage().getArray(), 100)
634 self.assertTrue(np.all(mi.getMask().getArray() == 5))
635 self.assertFloatsAlmostEqual(mi.getVariance().getArray(), 200)
637 def testDeepCopyMetadata(self):
638 """Make sure a deep copy of an Exposure has a deep copy of metadata (ticket #2568)
639 """
640 exp = afwImage.ExposureF(10, 10)
641 expMeta = exp.getMetadata()
642 expMeta.set("foo", 5)
643 expCopy = exp.clone()
644 expCopyMeta = expCopy.getMetadata()
645 expCopyMeta.set("foo", 6)
646 self.assertEqual(expCopyMeta.getScalar("foo"), 6)
647 # this will fail if the bug is present
648 self.assertEqual(expMeta.getScalar("foo"), 5)
650 def testDeepCopySubMetadata(self):
651 """Make sure a deep copy of a subregion of an Exposure has a deep copy of metadata (ticket #2568)
652 """
653 exp = afwImage.ExposureF(10, 10)
654 expMeta = exp.getMetadata()
655 expMeta.set("foo", 5)
656 bbox = lsst.geom.Box2I(lsst.geom.Point2I(1, 0), lsst.geom.Extent2I(5, 5))
657 expCopy = exp.Factory(exp, bbox, afwImage.PARENT, True)
658 expCopyMeta = expCopy.getMetadata()
659 expCopyMeta.set("foo", 6)
660 self.assertEqual(expCopyMeta.getScalar("foo"), 6)
661 # this will fail if the bug is present
662 self.assertEqual(expMeta.getScalar("foo"), 5)
664 def testMakeExposureLeaks(self):
665 """Test for memory leaks in makeExposure (the test is in lsst.utils.tests.MemoryTestCase)"""
666 afwImage.makeMaskedImage(afwImage.ImageU(lsst.geom.Extent2I(10, 20)))
667 afwImage.makeExposure(afwImage.makeMaskedImage(
668 afwImage.ImageU(lsst.geom.Extent2I(10, 20))))
670 def testImageSlices(self):
671 """Test image slicing, which generate sub-images using Box2I under the covers"""
672 exp = afwImage.ExposureF(10, 20)
673 mi = exp.getMaskedImage()
674 mi.image[9, 19] = 10
675 # N.b. Exposures don't support setting/getting the pixels so can't
676 # replicate e.g. Image's slice tests
677 sexp = exp[1:4, 6:10]
678 self.assertEqual(sexp.getDimensions(), lsst.geom.ExtentI(3, 4))
679 sexp = exp[:, -3:, afwImage.LOCAL]
680 self.assertEqual(sexp.getDimensions(),
681 lsst.geom.ExtentI(exp.getWidth(), 3))
682 self.assertEqual(sexp.maskedImage[-1, -1, afwImage.LOCAL],
683 exp.maskedImage[-1, -1, afwImage.LOCAL])
685 def testConversionToScalar(self):
686 """Test that even 1-pixel Exposures can't be converted to scalars"""
687 im = afwImage.ExposureF(10, 20)
689 # only single pixel images may be converted
690 self.assertRaises(TypeError, float, im)
691 # actually, can't convert (img, msk, var) to scalar
692 self.assertRaises(TypeError, float, im[0, 0])
694 def testReadMetadata(self):
695 with lsst.utils.tests.getTempFilePath(".fits") as tmpFile:
696 self.exposureCrWcs.getMetadata().set("FRAZZLE", True)
697 # This will write the main metadata (inc. FRAZZLE) to the primary HDU, and the
698 # WCS to subsequent HDUs, along with INHERIT=T.
699 self.exposureCrWcs.writeFits(tmpFile)
700 # This should read the first non-empty HDU (i.e. it skips the primary), but
701 # goes back and reads it if it finds INHERIT=T. That should let us read
702 # frazzle and the Wcs from the PropertySet returned by
703 # testReadMetadata.
704 md = readMetadata(tmpFile)
705 wcs = afwGeom.makeSkyWcs(md, False)
706 self.assertPairsAlmostEqual(wcs.getPixelOrigin(), self.wcs.getPixelOrigin())
707 self.assertSpherePointsAlmostEqual(wcs.getSkyOrigin(), self.wcs.getSkyOrigin())
708 assert_allclose(wcs.getCdMatrix(), self.wcs.getCdMatrix(), atol=1e-10)
709 frazzle = md.getScalar("FRAZZLE")
710 self.assertTrue(frazzle)
712 def testArchiveKeys(self):
713 with lsst.utils.tests.getTempFilePath(".fits") as tmpFile:
714 exposure1 = afwImage.ExposureF(100, 100, self.wcs)
715 exposure1.setPsf(self.psf)
716 exposure1.writeFits(tmpFile)
717 exposure2 = afwImage.ExposureF(tmpFile)
718 self.assertFalse(exposure2.getMetadata().exists("AR_ID"))
719 self.assertFalse(exposure2.getMetadata().exists("PSF_ID"))
720 self.assertFalse(exposure2.getMetadata().exists("WCS_ID"))
722 def testTicket2861(self):
723 with lsst.utils.tests.getTempFilePath(".fits") as tmpFile:
724 exposure1 = afwImage.ExposureF(100, 100, self.wcs)
725 exposure1.setPsf(self.psf)
726 schema = afwTable.ExposureTable.makeMinimalSchema()
727 coaddInputs = afwImage.CoaddInputs(schema, schema)
728 exposure1.getInfo().setCoaddInputs(coaddInputs)
729 exposure2 = afwImage.ExposureF(exposure1, True)
730 self.assertIsNotNone(exposure2.getInfo().getCoaddInputs())
731 exposure2.writeFits(tmpFile)
732 exposure3 = afwImage.ExposureF(tmpFile)
733 self.assertIsNotNone(exposure3.getInfo().getCoaddInputs())
735 def testGetCutout(self):
736 wcs = self.smallExposure.getWcs()
738 dimensions = [lsst.geom.Extent2I(100, 50), lsst.geom.Extent2I(15, 15), lsst.geom.Extent2I(0, 10),
739 lsst.geom.Extent2I(25, 30), lsst.geom.Extent2I(15, -5),
740 2*self.smallExposure.getDimensions()]
741 locations = [("center", self._getExposureCenter(self.smallExposure)),
742 ("edge", wcs.pixelToSky(lsst.geom.Point2D(0, 0))),
743 ("rounding test", wcs.pixelToSky(lsst.geom.Point2D(0.2, 0.7))),
744 ("just inside", wcs.pixelToSky(lsst.geom.Point2D(-0.5 + 1e-4, -0.5 + 1e-4))),
745 ("just outside", wcs.pixelToSky(lsst.geom.Point2D(-0.5 - 1e-4, -0.5 - 1e-4))),
746 ("outside", wcs.pixelToSky(lsst.geom.Point2D(-1000, -1000)))]
747 for cutoutSize in dimensions:
748 for label, cutoutCenter in locations:
749 msg = 'Cutout size = %s, location = %s' % (cutoutSize, label)
750 if "outside" not in label and all(cutoutSize.gt(0)):
751 cutout = self.smallExposure.getCutout(cutoutCenter, cutoutSize)
752 centerInPixels = wcs.skyToPixel(cutoutCenter)
753 precision = (1 + 1e-4)*np.sqrt(0.5)*wcs.getPixelScale(centerInPixels)
754 self._checkCutoutProperties(cutout, cutoutSize, cutoutCenter, precision, msg)
755 self._checkCutoutPixels(
756 cutout,
757 self._getValidCorners(self.smallExposure.getBBox(), cutout.getBBox()),
758 msg)
760 # Need a valid WCS
761 with self.assertRaises(pexExcept.LogicError, msg=msg):
762 self.exposureMiOnly.getCutout(cutoutCenter, cutoutSize)
763 else:
764 with self.assertRaises(pexExcept.InvalidParameterError, msg=msg):
765 self.smallExposure.getCutout(cutoutCenter, cutoutSize)
767 def testGetConvexPolygon(self):
768 """Test the convex polygon."""
769 # Check that we do not have a convex polygon for the plain exposure.
770 self.assertIsNone(self.exposureMiOnly.convex_polygon)
772 # Check that all the points in the padded bounding box are in the polygon
773 bbox = self.exposureMiWcs.getBBox()
774 # Grow by the default padding.
775 bbox.grow(10)
776 x, y = np.meshgrid(np.arange(bbox.getBeginX(), bbox.getEndX(), dtype=np.float64),
777 np.arange(bbox.getBeginY(), bbox.getEndY(), dtype=np.float64))
778 wcs = self.exposureMiWcs.wcs
779 ra, dec = wcs.pixelToSkyArray(x.ravel(),
780 y.ravel())
782 poly = self.exposureMiWcs.convex_polygon
783 contains = poly.contains(ra, dec)
784 np.testing.assert_array_equal(contains, np.ones(len(contains), dtype=bool))
786 # Check that points one pixel outside of the bounding box are not in the polygon
787 bbox.grow(1)
789 ra, dec = wcs.pixelToSkyArray(
790 np.linspace(bbox.getBeginX(), bbox.getEndX(), 100),
791 np.full(100, bbox.getBeginY()))
792 contains = poly.contains(ra, dec)
793 np.testing.assert_array_equal(contains, np.zeros(len(contains), dtype=bool))
795 ra, dec = wcs.pixelToSkyArray(
796 np.linspace(bbox.getBeginX(), bbox.getEndX(), 100),
797 np.full(100, bbox.getEndY()))
798 contains = poly.contains(ra, dec)
799 np.testing.assert_array_equal(contains, np.zeros(len(contains), dtype=bool))
801 ra, dec = wcs.pixelToSkyArray(
802 np.full(100, bbox.getBeginX()),
803 np.linspace(bbox.getBeginY(), bbox.getEndY(), 100))
804 contains = poly.contains(ra, dec)
805 np.testing.assert_array_equal(contains, np.zeros(len(contains), dtype=bool))
807 ra, dec = wcs.pixelToSkyArray(
808 np.full(100, bbox.getEndX()),
809 np.linspace(bbox.getBeginY(), bbox.getEndY(), 100))
810 contains = poly.contains(ra, dec)
811 np.testing.assert_array_equal(contains, np.zeros(len(contains), dtype=bool))
813 def testContainsSkyCoords(self):
814 """Test the sky coord containment code."""
815 self.assertRaisesRegex(ValueError,
816 "Exposure does not have a valid WCS",
817 self.exposureMiOnly.containsSkyCoords,
818 0.0,
819 0.0)
821 # Check that all the points within the bounding box are contained
822 bbox = self.exposureMiWcs.getBBox()
823 x, y = np.meshgrid(np.arange(bbox.getBeginX() + 1, bbox.getEndX() - 1),
824 np.arange(bbox.getBeginY() + 1, bbox.getEndY() - 1))
825 wcs = self.exposureMiWcs.wcs
826 ra, dec = wcs.pixelToSkyArray(x.ravel().astype(np.float64),
827 y.ravel().astype(np.float64))
829 contains = self.exposureMiWcs.containsSkyCoords(ra*units.radian,
830 dec*units.radian)
831 np.testing.assert_array_equal(contains, np.ones(len(contains), dtype=bool))
833 # Same test, everything in degrees.
834 ra, dec = wcs.pixelToSkyArray(x.ravel().astype(np.float64),
835 y.ravel().astype(np.float64),
836 degrees=True)
838 contains = self.exposureMiWcs.containsSkyCoords(ra*units.degree,
839 dec*units.degree)
840 np.testing.assert_array_equal(contains, np.ones(len(contains), dtype=bool))
842 # Prepend and append some positions out of the box.
843 ra = np.concatenate(([300.0], ra, [180.]))
844 dec = np.concatenate(([50.0], dec, [50.0]))
846 contains = self.exposureMiWcs.containsSkyCoords(ra*units.degree,
847 dec*units.degree)
848 compare = np.ones(len(contains), dtype=bool)
849 compare[0] = False
850 compare[-1] = False
851 np.testing.assert_array_equal(contains, compare)
853 def _checkCutoutProperties(self, cutout, size, center, precision, msg):
854 """Test whether a cutout has the desired size and position.
856 Parameters
857 ----------
858 cutout : `lsst.afw.image.Exposure`
859 The cutout to test.
860 size : `lsst.geom.Extent2I`
861 The expected dimensions of ``cutout``.
862 center : `lsst.geom.SpherePoint`
863 The expected center of ``cutout``.
864 precision : `lsst.geom.Angle`
865 The precision to which ``center`` must match.
866 msg : `str`
867 An error message suffix describing test parameters.
868 """
869 newCenter = self._getExposureCenter(cutout)
870 self.assertIsNotNone(cutout, msg=msg)
871 self.assertSpherePointsAlmostEqual(newCenter, center, maxSep=precision, msg=msg)
872 self.assertEqual(cutout.getWidth(), size[0], msg=msg)
873 self.assertEqual(cutout.getHeight(), size[1], msg=msg)
875 def _checkCutoutPixels(self, cutout, validCorners, msg):
876 """Test whether a cutout has valid/empty pixels where expected.
878 Parameters
879 ----------
880 cutout : `lsst.afw.image.Exposure`
881 The cutout to test.
882 validCorners : iterable of `lsst.geom.Point2I`
883 The corners of ``cutout`` that should be drawn from the original image.
884 msg : `str`
885 An error message suffix describing test parameters.
886 """
887 mask = cutout.getMaskedImage().getMask()
888 edgeMask = mask.getPlaneBitMask("NO_DATA")
890 for corner in cutout.getBBox().getCorners():
891 maskBitsSet = mask[corner] & edgeMask
892 if corner in validCorners:
893 self.assertEqual(maskBitsSet, 0, msg=msg)
894 else:
895 self.assertEqual(maskBitsSet, edgeMask, msg=msg)
897 def _getExposureCenter(self, exposure):
898 """Return the sky coordinates of an Exposure's center.
900 Parameters
901 ----------
902 exposure : `lsst.afw.image.Exposure`
903 The image whose center is desired.
905 Returns
906 -------
907 center : `lsst.geom.SpherePoint`
908 The position at the center of ``exposure``.
909 """
910 return exposure.getWcs().pixelToSky(lsst.geom.Box2D(exposure.getBBox()).getCenter())
912 def _getValidCorners(self, imageBox, cutoutBox):
913 """Return the corners of a cutout that are constrained by the original image.
915 Parameters
916 ----------
917 imageBox: `lsst.geom.Extent2I`
918 The bounding box of the original image.
919 cutoutBox : `lsst.geom.Box2I`
920 The bounding box of the cutout.
922 Returns
923 -------
924 corners : iterable of `lsst.geom.Point2I`
925 The corners that are drawn from the original image.
926 """
927 return [corner for corner in cutoutBox.getCorners() if corner in imageBox]
930class ExposureInfoTestCase(lsst.utils.tests.TestCase):
931 def setUp(self):
932 super().setUp()
934 self.wcs = afwGeom.makeSkyWcs(lsst.geom.Point2D(0.0, 0.0),
935 lsst.geom.SpherePoint(2.0, 34.0, lsst.geom.degrees),
936 np.identity(2),
937 )
938 self.photoCalib = afwImage.PhotoCalib(1.5)
939 self.psf = DummyPsf(2.0)
940 self.detector = DetectorWrapper().detector
941 self.summaryStats = afwImage.ExposureSummaryStats(ra=100.0)
942 self.polygon = afwGeom.Polygon(lsst.geom.Box2D(lsst.geom.Point2D(0.0, 0.0),
943 lsst.geom.Point2D(25.0, 20.0)))
944 self.coaddInputs = afwImage.CoaddInputs()
945 self.apCorrMap = afwImage.ApCorrMap()
946 self.transmissionCurve = afwImage.TransmissionCurve.makeIdentity()
948 self.exposureInfo = afwImage.ExposureInfo()
949 self.gFilterLabel = afwImage.FilterLabel(band="g")
950 self.exposureId = 42
952 def _checkAlias(self, exposureInfo, key, value, has, get):
953 self.assertFalse(has())
954 self.assertFalse(exposureInfo.hasComponent(key))
955 self.assertIsNone(get())
956 self.assertIsNone(exposureInfo.getComponent(key))
958 self.exposureInfo.setComponent(key, value)
959 self.assertTrue(has())
960 self.assertTrue(exposureInfo.hasComponent(key))
961 self.assertIsNotNone(get())
962 self.assertIsNotNone(exposureInfo.getComponent(key))
963 self.assertEqual(get(), value)
964 self.assertEqual(exposureInfo.getComponent(key), value)
966 self.exposureInfo.removeComponent(key)
967 self.assertFalse(has())
968 self.assertFalse(exposureInfo.hasComponent(key))
969 self.assertIsNone(get())
970 self.assertIsNone(exposureInfo.getComponent(key))
972 def testAliases(self):
973 cls = type(self.exposureInfo)
974 self._checkAlias(self.exposureInfo, cls.KEY_WCS, self.wcs,
975 self.exposureInfo.hasWcs, self.exposureInfo.getWcs)
976 self._checkAlias(self.exposureInfo, cls.KEY_PSF, self.psf,
977 self.exposureInfo.hasPsf, self.exposureInfo.getPsf)
978 self._checkAlias(self.exposureInfo, cls.KEY_PHOTO_CALIB, self.photoCalib,
979 self.exposureInfo.hasPhotoCalib, self.exposureInfo.getPhotoCalib)
980 self._checkAlias(self.exposureInfo, cls.KEY_DETECTOR, self.detector,
981 self.exposureInfo.hasDetector, self.exposureInfo.getDetector)
982 self._checkAlias(self.exposureInfo, cls.KEY_VALID_POLYGON, self.polygon,
983 self.exposureInfo.hasValidPolygon, self.exposureInfo.getValidPolygon)
984 self._checkAlias(self.exposureInfo, cls.KEY_COADD_INPUTS, self.coaddInputs,
985 self.exposureInfo.hasCoaddInputs, self.exposureInfo.getCoaddInputs)
986 self._checkAlias(self.exposureInfo, cls.KEY_AP_CORR_MAP, self.apCorrMap,
987 self.exposureInfo.hasApCorrMap, self.exposureInfo.getApCorrMap)
988 self._checkAlias(self.exposureInfo, cls.KEY_TRANSMISSION_CURVE, self.transmissionCurve,
989 self.exposureInfo.hasTransmissionCurve, self.exposureInfo.getTransmissionCurve)
990 self._checkAlias(self.exposureInfo, cls.KEY_SUMMARY_STATS, self.summaryStats,
991 self.exposureInfo.hasSummaryStats, self.exposureInfo.getSummaryStats)
992 self._checkAlias(self.exposureInfo, cls.KEY_FILTER, self.gFilterLabel,
993 self.exposureInfo.hasFilter, self.exposureInfo.getFilter)
994 with self.assertWarns(FutureWarning):
995 self._checkAlias(self.exposureInfo, cls.KEY_FILTER, self.gFilterLabel,
996 self.exposureInfo.hasFilterLabel, self.exposureInfo.getFilterLabel)
998 def testId(self):
999 self.exposureInfo.setVisitInfo(afwImage.VisitInfo())
1001 self.assertFalse(self.exposureInfo.hasId())
1002 self.assertIsNone(self.exposureInfo.getId())
1003 with self.assertWarns(FutureWarning):
1004 self.assertEqual(self.exposureInfo.getVisitInfo().getExposureId(), 0)
1005 self.assertIsNone(self.exposureInfo.id)
1007 self.exposureInfo.setId(self.exposureId)
1008 self.assertTrue(self.exposureInfo.hasId())
1009 self.assertIsNotNone(self.exposureInfo.getId())
1010 self.assertIsNotNone(self.exposureInfo.id)
1011 self.assertEqual(self.exposureInfo.getId(), self.exposureId)
1012 with self.assertWarns(FutureWarning):
1013 self.assertEqual(self.exposureInfo.getVisitInfo().getExposureId(), self.exposureId)
1014 self.assertEqual(self.exposureInfo.id, self.exposureId)
1016 self.exposureInfo.id = 99899
1017 self.assertEqual(self.exposureInfo.getId(), 99899)
1019 self.exposureInfo.setVisitInfo(afwImage.VisitInfo(exposureId=12321))
1020 self.assertEqual(self.exposureInfo.id, 12321)
1022 self.exposureInfo.id = None
1023 self.assertFalse(self.exposureInfo.hasId())
1024 self.assertIsNone(self.exposureInfo.getId())
1025 self.assertIsNone(self.exposureInfo.id)
1027 def testCopy(self):
1028 # Test that ExposureInfos have independently settable state
1029 copy = afwImage.ExposureInfo(self.exposureInfo, True)
1030 self.assertEqual(self.exposureInfo.getWcs(), copy.getWcs())
1032 newWcs = afwGeom.makeSkyWcs(lsst.geom.Point2D(-23.0, 8.0),
1033 lsst.geom.SpherePoint(0.0, 0.0, lsst.geom.degrees),
1034 np.identity(2),
1035 )
1036 copy.setWcs(newWcs)
1037 self.assertEqual(copy.getWcs(), newWcs)
1038 self.assertNotEqual(self.exposureInfo.getWcs(), copy.getWcs())
1040 def testMissingProperties(self):
1041 # Test that invalid properties return None instead of raising
1042 exposureInfo = afwImage.ExposureInfo()
1044 self.assertIsNone(exposureInfo.id)
1047class ExposureNoAfwdataTestCase(lsst.utils.tests.TestCase):
1048 """Tests of Exposure that don't require afwdata.
1050 These tests use the trivial exposures written to ``afw/tests/data``.
1051 """
1052 def setUp(self):
1053 self.dataDir = os.path.join(os.path.split(__file__)[0], "data")
1055 # Check the values below against what was written by comparing with
1056 # the code in `afw/tests/data/makeTestExposure.py`
1057 nx = ny = 10
1058 image = afwImage.ImageF(np.arange(nx*ny, dtype='f').reshape(nx, ny))
1059 variance = afwImage.ImageF(np.ones((nx, ny), dtype='f'))
1060 mask = afwImage.MaskX(nx, ny)
1061 mask.array[5, 5] = 5
1062 self.maskedImage = afwImage.MaskedImageF(image, mask, variance)
1063 self.exposureId = 12345
1065 self.v0PhotoCalib = afwImage.makePhotoCalibFromCalibZeroPoint(1e6, 2e4)
1066 self.v1PhotoCalib = afwImage.PhotoCalib(1e6, 2e4)
1067 self.v1FilterLabel = afwImage.FilterLabel(physical="ha")
1068 self.v2FilterLabel = afwImage.FilterLabel(band="N656", physical="ha")
1070 def testReadUnversioned(self):
1071 """Test that we can read an unversioned (implicit verison 0) file.
1072 """
1073 filename = os.path.join(self.dataDir, "exposure-noversion.fits")
1074 exposure = afwImage.ExposureF.readFits(filename)
1076 self.assertMaskedImagesEqual(exposure.maskedImage, self.maskedImage)
1078 self.assertEqual(exposure.info.id, self.exposureId)
1079 with self.assertWarns(FutureWarning):
1080 self.assertEqual(exposure.info.getVisitInfo().getExposureId(), self.exposureId)
1081 self.assertEqual(exposure.getPhotoCalib(), self.v0PhotoCalib)
1082 self.assertEqual(exposure.getFilter(), self.v1FilterLabel)
1083 with self.assertWarns(FutureWarning):
1084 self.assertEqual(exposure.getFilterLabel(), self.v1FilterLabel)
1086 def testReadVersion0(self):
1087 """Test that we can read a version 0 file.
1088 This file should be identical to the unversioned one, except that it
1089 is marked as ExposureInfo version 0 in the header.
1090 """
1091 filename = os.path.join(self.dataDir, "exposure-version-0.fits")
1092 exposure = afwImage.ExposureF.readFits(filename)
1094 self.assertMaskedImagesEqual(exposure.maskedImage, self.maskedImage)
1096 self.assertEqual(exposure.info.id, self.exposureId)
1097 with self.assertWarns(FutureWarning):
1098 self.assertEqual(exposure.info.getVisitInfo().getExposureId(), self.exposureId)
1099 self.assertEqual(exposure.getPhotoCalib(), self.v0PhotoCalib)
1100 self.assertEqual(exposure.getFilter(), self.v1FilterLabel)
1101 with self.assertWarns(FutureWarning):
1102 self.assertEqual(exposure.getFilterLabel(), self.v1FilterLabel)
1104 # Check that the metadata reader parses the file correctly
1105 reader = afwImage.ExposureFitsReader(filename)
1106 self.assertEqual(reader.readExposureInfo().getPhotoCalib(), self.v0PhotoCalib)
1107 self.assertEqual(reader.readPhotoCalib(), self.v0PhotoCalib)
1109 def testReadVersion1(self):
1110 """Test that we can read a version 1 file.
1111 Version 1 replaced Calib with PhotoCalib.
1112 """
1113 filename = os.path.join(self.dataDir, "exposure-version-1.fits")
1114 exposure = afwImage.ExposureF.readFits(filename)
1116 self.assertMaskedImagesEqual(exposure.maskedImage, self.maskedImage)
1118 self.assertEqual(exposure.info.id, self.exposureId)
1119 with self.assertWarns(FutureWarning):
1120 self.assertEqual(exposure.info.getVisitInfo().getExposureId(), self.exposureId)
1121 self.assertEqual(exposure.getPhotoCalib(), self.v1PhotoCalib)
1122 self.assertEqual(exposure.getFilter(), self.v1FilterLabel)
1123 with self.assertWarns(FutureWarning):
1124 self.assertEqual(exposure.getFilterLabel(), self.v1FilterLabel)
1126 # Check that the metadata reader parses the file correctly
1127 reader = afwImage.ExposureFitsReader(filename)
1128 self.assertEqual(reader.readExposureInfo().getPhotoCalib(), self.v1PhotoCalib)
1129 self.assertEqual(reader.readPhotoCalib(), self.v1PhotoCalib)
1131 def testReadVersion2(self):
1132 """Test that we can read a version 2 file.
1133 Version 2 replaced Filter with FilterLabel.
1134 """
1135 filename = os.path.join(self.dataDir, "exposure-version-2.fits")
1136 exposure = afwImage.ExposureF.readFits(filename)
1138 self.assertMaskedImagesEqual(exposure.maskedImage, self.maskedImage)
1140 self.assertEqual(exposure.info.id, self.exposureId)
1141 with self.assertWarns(FutureWarning):
1142 self.assertEqual(exposure.info.getVisitInfo().getExposureId(), self.exposureId)
1143 self.assertEqual(exposure.getPhotoCalib(), self.v1PhotoCalib)
1144 self.assertEqual(exposure.getFilter(), self.v2FilterLabel)
1145 with self.assertWarns(FutureWarning):
1146 self.assertEqual(exposure.getFilterLabel(), self.v2FilterLabel)
1148 # Check that the metadata reader parses the file correctly
1149 reader = afwImage.ExposureFitsReader(filename)
1150 self.assertEqual(reader.readExposureInfo().getPhotoCalib(), self.v1PhotoCalib)
1151 self.assertEqual(reader.readPhotoCalib(), self.v1PhotoCalib)
1153 def testExposureSummaryExtraComponents(self):
1154 """Test that we can read an exposure summary with extra components.
1155 """
1156 testDict = {'ra': 0.0,
1157 'decl': 0.0,
1158 'nonsense': 1.0}
1159 bytes = yaml.dump(testDict, encoding='utf-8')
1160 with self.assertWarns(FutureWarning):
1161 summaryStats = lsst.afw.image.ExposureSummaryStats._read(bytes)
1163 self.assertEqual(summaryStats.ra, testDict['ra'])
1164 self.assertEqual(summaryStats.decl, testDict['decl'])
1166 def testExposureSummarySchema(self):
1167 """Test that we can make a schema for an exposure summary and populate
1168 records with that schema.
1169 """
1170 schema = afwTable.Schema()
1171 afwImage.ExposureSummaryStats.update_schema(schema)
1172 self.maxDiff = None
1173 self.assertEqual(
1174 {field.name for field in dataclasses.fields(afwImage.ExposureSummaryStats)},
1175 set(schema.getNames()) | {"version"},
1176 )
1177 catalog = afwTable.BaseCatalog(schema)
1178 summary1 = afwImage.ExposureSummaryStats()
1179 for n, field in enumerate(dataclasses.fields(afwImage.ExposureSummaryStats)):
1180 # Set fields to deterministic, distinct, but arbitrary values.
1181 if field.type == "float":
1182 setattr(summary1, field.name, float(0.5**n))
1183 elif field.type == "int":
1184 setattr(summary1, field.name, 10*n)
1185 elif field.type == "list[float]":
1186 setattr(summary1, field.name, [n + 0.1, n + 0.2, n + 0.3, n + 0.4])
1187 else:
1188 raise TypeError(f"Unexpected type: {field.type!r}.")
1189 record = catalog.addNew()
1190 summary1.update_record(record)
1191 summary2 = afwImage.ExposureSummaryStats.from_record(record)
1192 self.assertEqual(summary1, summary2)
1195class MemoryTester(lsst.utils.tests.MemoryTestCase):
1196 pass
1199def setup_module(module):
1200 lsst.utils.tests.init()
1203if __name__ == "__main__": 1203 ↛ 1204line 1203 didn't jump to line 1204, because the condition on line 1203 was never true
1204 lsst.utils.tests.init()
1205 unittest.main()