Coverage for tests/test_exposure.py: 11%
699 statements
« prev ^ index » next coverage.py v7.4.4, created at 2024-04-06 04:03 -0700
« prev ^ index » next coverage.py v7.4.4, created at 2024-04-06 04:03 -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
36import lsst.utils
37import lsst.utils.tests
38import lsst.geom
39import lsst.afw.image as afwImage
40from lsst.afw.coord import Weather
41import lsst.afw.geom as afwGeom
42import lsst.afw.table as afwTable
43import lsst.pex.exceptions as pexExcept
44from lsst.afw.fits import readMetadata, FitsError
45from lsst.afw.cameraGeom.testUtils import DetectorWrapper
46from lsst.daf.base import PropertyList
47from lsst.log import Log
48from testTableArchivesLib import DummyPsf
50Log.getLogger("lsst.afw.image.Mask").setLevel(Log.INFO)
52try:
53 dataDir = os.path.join(lsst.utils.getPackageDir("afwdata"), "data")
54except LookupError:
55 dataDir = None
56else:
57 InputMaskedImageName = "871034p_1_MI.fits"
58 InputMaskedImageNameSmall = "small_MI.fits"
59 InputImageNameSmall = "small"
60 OutputMaskedImageName = "871034p_1_MInew.fits"
62 currDir = os.path.abspath(os.path.dirname(__file__))
63 inFilePath = os.path.join(dataDir, InputMaskedImageName)
64 inFilePathSmall = os.path.join(dataDir, InputMaskedImageNameSmall)
65 inFilePathSmallImage = os.path.join(dataDir, InputImageNameSmall)
68@unittest.skipIf(dataDir is None, "afwdata not setup")
69class ExposureTestCase(lsst.utils.tests.TestCase):
70 """
71 A test case for the Exposure Class
72 """
74 def setUp(self):
75 maskedImage = afwImage.MaskedImageF(inFilePathSmall)
76 maskedImageMD = readMetadata(inFilePathSmall)
78 self.smallExposure = afwImage.ExposureF(inFilePathSmall)
79 self.width = maskedImage.getWidth()
80 self.height = maskedImage.getHeight()
81 self.wcs = afwGeom.makeSkyWcs(maskedImageMD, False)
82 self.md = maskedImageMD
83 self.psf = DummyPsf(2.0)
84 self.detector = DetectorWrapper().detector
85 self.id = 42
86 self.extras = {"MISC": DummyPsf(3.5)}
88 self.exposureBlank = afwImage.ExposureF()
89 self.exposureMiOnly = afwImage.makeExposure(maskedImage)
90 self.exposureMiWcs = afwImage.makeExposure(maskedImage, self.wcs)
91 # n.b. the (100, 100, ...) form
92 self.exposureCrWcs = afwImage.ExposureF(100, 100, self.wcs)
93 # test with ExtentI(100, 100) too
94 self.exposureCrOnly = afwImage.ExposureF(lsst.geom.ExtentI(100, 100))
96 def tearDown(self):
97 del self.smallExposure
98 del self.wcs
99 del self.psf
100 del self.detector
101 del self.extras
103 del self.exposureBlank
104 del self.exposureMiOnly
105 del self.exposureMiWcs
106 del self.exposureCrWcs
107 del self.exposureCrOnly
109 def testGetMaskedImage(self):
110 """
111 Test to ensure a MaskedImage can be obtained from each
112 Exposure. An Exposure is required to have a MaskedImage,
113 therefore each of the Exposures should return a MaskedImage.
115 MaskedImage class should throw appropriate
116 lsst::pex::exceptions::NotFound if the MaskedImage can not be
117 obtained.
118 """
119 maskedImageBlank = self.exposureBlank.getMaskedImage()
120 blankWidth = maskedImageBlank.getWidth()
121 blankHeight = maskedImageBlank.getHeight()
122 if blankWidth != blankHeight != 0:
123 self.fail(f"{blankWidth} = {blankHeight} != 0")
125 maskedImageMiOnly = self.exposureMiOnly.getMaskedImage()
126 miOnlyWidth = maskedImageMiOnly.getWidth()
127 miOnlyHeight = maskedImageMiOnly.getHeight()
128 self.assertAlmostEqual(miOnlyWidth, self.width)
129 self.assertAlmostEqual(miOnlyHeight, self.height)
131 # NOTE: Unittests for Exposures created from a MaskedImage and
132 # a WCS object are incomplete. No way to test the validity of
133 # the WCS being copied/created.
135 maskedImageMiWcs = self.exposureMiWcs.getMaskedImage()
136 miWcsWidth = maskedImageMiWcs.getWidth()
137 miWcsHeight = maskedImageMiWcs.getHeight()
138 self.assertAlmostEqual(miWcsWidth, self.width)
139 self.assertAlmostEqual(miWcsHeight, self.height)
141 maskedImageCrWcs = self.exposureCrWcs.getMaskedImage()
142 crWcsWidth = maskedImageCrWcs.getWidth()
143 crWcsHeight = maskedImageCrWcs.getHeight()
144 if crWcsWidth != crWcsHeight != 0:
145 self.fail(f"{crWcsWidth} != {crWcsHeight} != 0")
147 maskedImageCrOnly = self.exposureCrOnly.getMaskedImage()
148 crOnlyWidth = maskedImageCrOnly.getWidth()
149 crOnlyHeight = maskedImageCrOnly.getHeight()
150 if crOnlyWidth != crOnlyHeight != 0:
151 self.fail(f"{crOnlyWidth} != {crOnlyHeight} != 0")
153 # Check Exposure.getWidth() returns the MaskedImage's width
154 self.assertEqual(crOnlyWidth, self.exposureCrOnly.getWidth())
155 self.assertEqual(crOnlyHeight, self.exposureCrOnly.getHeight())
156 # check width/height properties
157 self.assertEqual(crOnlyWidth, self.exposureCrOnly.width)
158 self.assertEqual(crOnlyHeight, self.exposureCrOnly.height)
160 def testProperties(self):
161 self.assertMaskedImagesEqual(self.exposureMiOnly.maskedImage,
162 self.exposureMiOnly.getMaskedImage())
163 mi2 = afwImage.MaskedImageF(self.exposureMiOnly.getDimensions())
164 mi2.image.array[:] = 5.0
165 mi2.variance.array[:] = 3.0
166 mi2.mask.array[:] = 0x1
167 self.exposureMiOnly.maskedImage = mi2
168 self.assertMaskedImagesEqual(self.exposureMiOnly.maskedImage, mi2)
169 self.assertImagesEqual(self.exposureMiOnly.image,
170 self.exposureMiOnly.maskedImage.image)
172 image3 = afwImage.ImageF(self.exposureMiOnly.getDimensions())
173 image3.array[:] = 3.0
174 self.exposureMiOnly.image = image3
175 self.assertImagesEqual(self.exposureMiOnly.image, image3)
177 mask3 = afwImage.MaskX(self.exposureMiOnly.getDimensions())
178 mask3.array[:] = 0x2
179 self.exposureMiOnly.mask = mask3
180 self.assertMasksEqual(self.exposureMiOnly.mask, mask3)
182 var3 = afwImage.ImageF(self.exposureMiOnly.getDimensions())
183 var3.array[:] = 2.0
184 self.exposureMiOnly.variance = var3
185 self.assertImagesEqual(self.exposureMiOnly.variance, var3)
187 # Test the property getter for a null VisitInfo.
188 self.assertIsNone(self.exposureMiOnly.visitInfo)
190 def testGetWcs(self):
191 """Test that a WCS can be obtained from each Exposure created with
192 a WCS, and that an Exposure lacking a WCS returns None.
193 """
194 # These exposures don't contain a WCS
195 self.assertIsNone(self.exposureBlank.getWcs())
196 self.assertIsNone(self.exposureMiOnly.getWcs())
197 self.assertIsNone(self.exposureCrOnly.getWcs())
199 # These exposures should contain a WCS
200 self.assertEqual(self.wcs, self.exposureMiWcs.getWcs())
201 self.assertEqual(self.wcs, self.exposureCrWcs.getWcs())
203 def testExposureInfoConstructor(self):
204 """Test the Exposure(maskedImage, exposureInfo) constructor"""
205 exposureInfo = afwImage.ExposureInfo()
206 exposureInfo.setWcs(self.wcs)
207 exposureInfo.setDetector(self.detector)
208 gFilterLabel = afwImage.FilterLabel(band="g")
209 exposureInfo.setFilter(gFilterLabel)
210 maskedImage = afwImage.MaskedImageF(inFilePathSmall)
211 exposure = afwImage.ExposureF(maskedImage, exposureInfo)
213 self.assertTrue(exposure.hasWcs())
214 self.assertEqual(exposure.getWcs().getPixelOrigin(),
215 self.wcs.getPixelOrigin())
216 self.assertEqual(exposure.getDetector().getName(),
217 self.detector.getName())
218 self.assertEqual(exposure.getDetector().getSerial(),
219 self.detector.getSerial())
220 self.assertEqual(exposure.getFilter(), 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)
233 def testNullWcs(self):
234 """Test that an Exposure constructed with second argument None is usable
236 When the exposureInfo constructor was first added, trying to get a WCS
237 or other info caused a segfault because the ExposureInfo did not exist.
238 """
239 maskedImage = self.exposureMiOnly.getMaskedImage()
240 exposure = afwImage.ExposureF(maskedImage, None)
241 self.assertFalse(exposure.hasWcs())
242 self.assertFalse(exposure.hasPsf())
244 def testExposureInfoSetNone(self):
245 exposureInfo = afwImage.ExposureInfo()
246 exposureInfo.setDetector(None)
247 exposureInfo.setValidPolygon(None)
248 exposureInfo.setPsf(None)
249 exposureInfo.setWcs(None)
250 exposureInfo.setPhotoCalib(None)
251 exposureInfo.setCoaddInputs(None)
252 exposureInfo.setVisitInfo(None)
253 exposureInfo.setApCorrMap(None)
254 for key in self.extras:
255 exposureInfo.setComponent(key, None)
257 def testSetExposureInfo(self):
258 exposureInfo = afwImage.ExposureInfo()
259 exposureInfo.setWcs(self.wcs)
260 exposureInfo.setDetector(self.detector)
261 gFilterLabel = afwImage.FilterLabel(band="g")
262 exposureInfo.setFilter(gFilterLabel)
263 exposureInfo.setId(self.id)
264 maskedImage = afwImage.MaskedImageF(inFilePathSmall)
265 exposure = afwImage.ExposureF(maskedImage)
266 self.assertFalse(exposure.hasWcs())
268 exposure.setInfo(exposureInfo)
270 self.assertTrue(exposure.hasWcs())
271 self.assertEqual(exposure.getWcs().getPixelOrigin(),
272 self.wcs.getPixelOrigin())
273 self.assertEqual(exposure.getDetector().getName(),
274 self.detector.getName())
275 self.assertEqual(exposure.getDetector().getSerial(),
276 self.detector.getSerial())
277 self.assertEqual(exposure.getFilter(), gFilterLabel)
279 # test properties
280 self.assertEqual(exposure.detector.getName(), self.detector.getName())
281 self.assertEqual(exposure.filter, gFilterLabel)
282 self.assertEqual(exposure.wcs, self.wcs)
284 def testVisitInfoFitsPersistence(self):
285 """Test saving an exposure to FITS and reading it back in preserves (some) VisitInfo fields"""
286 exposureTime = 12.3
287 boresightRotAngle = 45.6 * lsst.geom.degrees
288 weather = Weather(1.1, 2.2, 0.3)
289 visitInfo = afwImage.VisitInfo(
290 exposureTime=exposureTime,
291 boresightRotAngle=boresightRotAngle,
292 weather=weather,
293 )
294 photoCalib = afwImage.PhotoCalib(3.4, 5.6)
295 exposureInfo = afwImage.ExposureInfo()
296 exposureInfo.setVisitInfo(visitInfo)
297 exposureInfo.setPhotoCalib(photoCalib)
298 exposureInfo.setDetector(self.detector)
299 gFilterLabel = afwImage.FilterLabel(band="g")
300 exposureInfo.setFilter(gFilterLabel)
301 maskedImage = afwImage.MaskedImageF(inFilePathSmall)
302 exposure = afwImage.ExposureF(maskedImage, exposureInfo)
303 with lsst.utils.tests.getTempFilePath(".fits") as tmpFile:
304 exposure.writeFits(tmpFile)
305 rtExposure = afwImage.ExposureF(tmpFile)
306 rtVisitInfo = rtExposure.getInfo().getVisitInfo()
307 self.assertEqual(rtVisitInfo.getWeather(), weather)
308 self.assertEqual(rtExposure.getPhotoCalib(), photoCalib)
309 self.assertEqual(rtExposure.getFilter(), gFilterLabel)
311 # Test property getters.
312 self.assertEqual(rtExposure.photoCalib, photoCalib)
313 self.assertEqual(rtExposure.filter, gFilterLabel)
314 # NOTE: we can't test visitInfo equality, because most fields are NaN.
315 self.assertIsNotNone(rtExposure.visitInfo)
317 def testSetMembers(self):
318 """
319 Test that the MaskedImage and the WCS of an Exposure can be set.
320 """
321 exposure = afwImage.ExposureF()
323 maskedImage = afwImage.MaskedImageF(inFilePathSmall)
324 exposure.setMaskedImage(maskedImage)
325 exposure.setWcs(self.wcs)
326 exposure.setDetector(self.detector)
327 exposure.setFilter(afwImage.FilterLabel(band="g"))
329 self.assertEqual(exposure.getDetector().getName(),
330 self.detector.getName())
331 self.assertEqual(exposure.getDetector().getSerial(),
332 self.detector.getSerial())
333 self.assertEqual(exposure.getFilter().bandLabel, "g")
334 self.assertEqual(exposure.getWcs(), self.wcs)
336 # The PhotoCalib tests are in test_photoCalib.py;
337 # here we just check that it's gettable and settable.
338 self.assertIsNone(exposure.getPhotoCalib())
340 photoCalib = afwImage.PhotoCalib(511.1, 44.4)
341 exposure.setPhotoCalib(photoCalib)
342 self.assertEqual(exposure.getPhotoCalib(), photoCalib)
344 # Psfs next
345 self.assertFalse(exposure.hasPsf())
346 exposure.setPsf(self.psf)
347 self.assertTrue(exposure.hasPsf())
349 exposure.setPsf(DummyPsf(1.0)) # we can reset the Psf
351 # extras next
352 info = exposure.getInfo()
353 for key, value in self.extras.items():
354 self.assertFalse(info.hasComponent(key))
355 self.assertIsNone(info.getComponent(key))
356 info.setComponent(key, value)
357 self.assertTrue(info.hasComponent(key))
358 self.assertEqual(info.getComponent(key), value)
359 info.removeComponent(key)
360 self.assertFalse(info.hasComponent(key))
362 # Test that we can set the MaskedImage and WCS of an Exposure
363 # that already has both
364 self.exposureMiWcs.setMaskedImage(maskedImage)
365 exposure.setWcs(self.wcs)
367 def testHasWcs(self):
368 """
369 Test if an Exposure has a WCS or not.
370 """
371 self.assertFalse(self.exposureBlank.hasWcs())
373 self.assertFalse(self.exposureMiOnly.hasWcs())
374 self.assertTrue(self.exposureMiWcs.hasWcs())
375 self.assertTrue(self.exposureCrWcs.hasWcs())
376 self.assertFalse(self.exposureCrOnly.hasWcs())
378 def testGetSubExposure(self):
379 """
380 Test that a subExposure of the original Exposure can be obtained.
382 The MaskedImage class should throw a
383 lsst::pex::exceptions::InvalidParameter if the requested
384 subRegion is not fully contained within the original
385 MaskedImage.
387 """
388 #
389 # This subExposure is valid
390 #
391 subBBox = lsst.geom.Box2I(lsst.geom.Point2I(40, 50),
392 lsst.geom.Extent2I(10, 10))
393 subExposure = self.exposureCrWcs.Factory(
394 self.exposureCrWcs, subBBox, afwImage.LOCAL)
396 self.checkWcs(self.exposureCrWcs, subExposure)
398 # this subRegion is not valid and should trigger an exception
399 # from the MaskedImage class and should trigger an exception
400 # from the WCS class for the MaskedImage 871034p_1_MI.
402 subRegion3 = lsst.geom.Box2I(lsst.geom.Point2I(100, 100),
403 lsst.geom.Extent2I(10, 10))
405 def getSubRegion():
406 self.exposureCrWcs.Factory(
407 self.exposureCrWcs, subRegion3, afwImage.LOCAL)
409 self.assertRaises(pexExcept.LengthError, getSubRegion)
411 # this subRegion is not valid and should trigger an exception
412 # from the MaskedImage class only for the MaskedImage small_MI.
413 # small_MI (cols, rows) = (256, 256)
415 subRegion4 = lsst.geom.Box2I(lsst.geom.Point2I(250, 250),
416 lsst.geom.Extent2I(10, 10))
418 def getSubRegion():
419 self.exposureCrWcs.Factory(
420 self.exposureCrWcs, subRegion4, afwImage.LOCAL)
422 self.assertRaises(pexExcept.LengthError, getSubRegion)
424 # check the sub- and parent- exposures are using the same Wcs
425 # transformation
426 subBBox = lsst.geom.Box2I(lsst.geom.Point2I(40, 50),
427 lsst.geom.Extent2I(10, 10))
428 subExposure = self.exposureCrWcs.Factory(
429 self.exposureCrWcs, subBBox, afwImage.LOCAL)
430 parentSkyPos = self.exposureCrWcs.getWcs().pixelToSky(0, 0)
432 subExpSkyPos = subExposure.getWcs().pixelToSky(0, 0)
434 self.assertSpherePointsAlmostEqual(parentSkyPos, subExpSkyPos, msg="Wcs in sub image has changed")
436 def testReadWriteFits(self):
437 """Test readFits and writeFits.
438 """
439 # This should pass without an exception
440 mainExposure = afwImage.ExposureF(inFilePathSmall)
441 mainExposure.info.setId(self.id)
442 mainExposure.setDetector(self.detector)
444 subBBox = lsst.geom.Box2I(lsst.geom.Point2I(10, 10),
445 lsst.geom.Extent2I(40, 50))
446 subExposure = mainExposure.Factory(
447 mainExposure, subBBox, afwImage.LOCAL)
448 self.checkWcs(mainExposure, subExposure)
449 det = subExposure.getDetector()
450 self.assertTrue(det)
452 subExposure = afwImage.ExposureF(
453 inFilePathSmall, subBBox, afwImage.LOCAL)
455 self.checkWcs(mainExposure, subExposure)
457 # This should throw an exception
458 def getExposure():
459 afwImage.ExposureF(inFilePathSmallImage)
461 self.assertRaises(FitsError, getExposure)
463 mainExposure.setPsf(self.psf)
465 # Make sure we can write without an exception
466 photoCalib = afwImage.PhotoCalib(1e-10, 1e-12)
467 mainExposure.setPhotoCalib(photoCalib)
469 mainInfo = mainExposure.getInfo()
470 for key, value in self.extras.items():
471 mainInfo.setComponent(key, value)
473 with lsst.utils.tests.getTempFilePath(".fits") as tmpFile:
474 mainExposure.writeFits(tmpFile)
476 readExposure = type(mainExposure)(tmpFile)
478 #
479 # Check the round-tripping
480 #
481 self.assertIsNotNone(mainExposure.getFilter())
482 self.assertEqual(mainExposure.getFilter(),
483 readExposure.getFilter())
485 self.assertEqual(photoCalib, readExposure.getPhotoCalib())
487 readInfo = readExposure.getInfo()
488 self.assertEqual(mainExposure.info.getId(), readInfo.id)
489 for key, value in self.extras.items():
490 self.assertEqual(value, readInfo.getComponent(key))
492 psf = readExposure.getPsf()
493 self.assertIsNotNone(psf)
494 self.assertEqual(psf, self.psf)
495 # check psf property getter
496 self.assertEqual(readExposure.psf, self.psf)
498 def checkWcs(self, parentExposure, subExposure):
499 """Compare WCS at corner points of a sub-exposure and its parent exposure
500 By using the function indexToPosition, we should be able to convert the indices
501 (of the four corners (of the sub-exposure)) to positions and use the wcs
502 to get the same sky coordinates for each.
503 """
504 subMI = subExposure.getMaskedImage()
505 subDim = subMI.getDimensions()
507 # Note: pixel positions must be computed relative to XY0 when working
508 # with WCS
509 mainWcs = parentExposure.getWcs()
510 subWcs = subExposure.getWcs()
512 for xSubInd in (0, subDim.getX()-1):
513 for ySubInd in (0, subDim.getY()-1):
514 self.assertSpherePointsAlmostEqual(
515 mainWcs.pixelToSky(
516 afwImage.indexToPosition(xSubInd),
517 afwImage.indexToPosition(ySubInd),
518 ),
519 subWcs.pixelToSky(
520 afwImage.indexToPosition(xSubInd),
521 afwImage.indexToPosition(ySubInd),
522 ))
524 def cmpExposure(self, e1, e2):
525 self.assertEqual(e1.getDetector().getName(),
526 e2.getDetector().getName())
527 self.assertEqual(e1.getDetector().getSerial(),
528 e2.getDetector().getSerial())
529 self.assertEqual(e1.getFilter(), e2.getFilter())
530 xy = lsst.geom.Point2D(0, 0)
531 self.assertEqual(e1.getWcs().pixelToSky(xy)[0],
532 e2.getWcs().pixelToSky(xy)[0])
533 self.assertEqual(e1.getPhotoCalib(), e2.getPhotoCalib())
534 # check PSF identity
535 if not e1.getPsf():
536 self.assertFalse(e2.getPsf())
537 else:
538 self.assertEqual(e1.getPsf(), e2.getPsf())
539 # Check extra components
540 i1 = e1.getInfo()
541 i2 = e2.getInfo()
542 for key in self.extras:
543 self.assertEqual(i1.hasComponent(key), i2.hasComponent(key))
544 if i1.hasComponent(key):
545 self.assertEqual(i1.getComponent(key), i2.getComponent(key))
547 def testCopyExposure(self):
548 """Copy an Exposure (maybe changing type)"""
550 exposureU = afwImage.ExposureU(inFilePathSmall, allowUnsafe=True)
551 exposureU.setWcs(self.wcs)
552 exposureU.setDetector(self.detector)
553 exposureU.setFilter(afwImage.FilterLabel(band="g"))
554 exposureU.setPsf(DummyPsf(4.0))
555 infoU = exposureU.getInfo()
556 for key, value in self.extras.items():
557 infoU.setComponent(key, value)
559 exposureF = exposureU.convertF()
560 self.cmpExposure(exposureF, exposureU)
562 nexp = exposureF.Factory(exposureF, False)
563 self.cmpExposure(exposureF, nexp)
565 # Ensure that the copy was deep.
566 # (actually this test is invalid since getDetector() returns a shared_ptr)
567 # cen0 = exposureU.getDetector().getCenterPixel()
568 # x0,y0 = cen0
569 # det = exposureF.getDetector()
570 # det.setCenterPixel(lsst.geom.Point2D(999.0, 437.8))
571 # self.assertEqual(exposureU.getDetector().getCenterPixel()[0], x0)
572 # self.assertEqual(exposureU.getDetector().getCenterPixel()[1], y0)
574 def testDeepCopyData(self):
575 """Make sure a deep copy of an Exposure has its own data (ticket #2625)
576 """
577 exp = afwImage.ExposureF(6, 7)
578 mi = exp.getMaskedImage()
579 mi.getImage().set(100)
580 mi.getMask().set(5)
581 mi.getVariance().set(200)
583 expCopy = exp.clone()
584 miCopy = expCopy.getMaskedImage()
585 miCopy.getImage().set(-50)
586 miCopy.getMask().set(2)
587 miCopy.getVariance().set(175)
589 self.assertFloatsAlmostEqual(miCopy.getImage().getArray(), -50)
590 self.assertTrue(np.all(miCopy.getMask().getArray() == 2))
591 self.assertFloatsAlmostEqual(miCopy.getVariance().getArray(), 175)
593 self.assertFloatsAlmostEqual(mi.getImage().getArray(), 100)
594 self.assertTrue(np.all(mi.getMask().getArray() == 5))
595 self.assertFloatsAlmostEqual(mi.getVariance().getArray(), 200)
597 def testDeepCopySubData(self):
598 """Make sure a deep copy of a subregion of an Exposure has its own data (ticket #2625)
599 """
600 exp = afwImage.ExposureF(6, 7)
601 mi = exp.getMaskedImage()
602 mi.getImage().set(100)
603 mi.getMask().set(5)
604 mi.getVariance().set(200)
606 bbox = lsst.geom.Box2I(lsst.geom.Point2I(1, 0), lsst.geom.Extent2I(5, 4))
607 expCopy = exp.Factory(exp, bbox, afwImage.PARENT, True)
608 miCopy = expCopy.getMaskedImage()
609 miCopy.getImage().set(-50)
610 miCopy.getMask().set(2)
611 miCopy.getVariance().set(175)
613 self.assertFloatsAlmostEqual(miCopy.getImage().getArray(), -50)
614 self.assertTrue(np.all(miCopy.getMask().getArray() == 2))
615 self.assertFloatsAlmostEqual(miCopy.getVariance().getArray(), 175)
617 self.assertFloatsAlmostEqual(mi.getImage().getArray(), 100)
618 self.assertTrue(np.all(mi.getMask().getArray() == 5))
619 self.assertFloatsAlmostEqual(mi.getVariance().getArray(), 200)
621 def testDeepCopyMetadata(self):
622 """Make sure a deep copy of an Exposure has a deep copy of metadata (ticket #2568)
623 """
624 exp = afwImage.ExposureF(10, 10)
625 expMeta = exp.getMetadata()
626 expMeta.set("foo", 5)
627 expCopy = exp.clone()
628 expCopyMeta = expCopy.getMetadata()
629 expCopyMeta.set("foo", 6)
630 self.assertEqual(expCopyMeta.getScalar("foo"), 6)
631 # this will fail if the bug is present
632 self.assertEqual(expMeta.getScalar("foo"), 5)
634 def testDeepCopySubMetadata(self):
635 """Make sure a deep copy of a subregion of an Exposure has a deep copy of metadata (ticket #2568)
636 """
637 exp = afwImage.ExposureF(10, 10)
638 expMeta = exp.getMetadata()
639 expMeta.set("foo", 5)
640 bbox = lsst.geom.Box2I(lsst.geom.Point2I(1, 0), lsst.geom.Extent2I(5, 5))
641 expCopy = exp.Factory(exp, bbox, afwImage.PARENT, True)
642 expCopyMeta = expCopy.getMetadata()
643 expCopyMeta.set("foo", 6)
644 self.assertEqual(expCopyMeta.getScalar("foo"), 6)
645 # this will fail if the bug is present
646 self.assertEqual(expMeta.getScalar("foo"), 5)
648 def testMakeExposureLeaks(self):
649 """Test for memory leaks in makeExposure (the test is in lsst.utils.tests.MemoryTestCase)"""
650 afwImage.makeMaskedImage(afwImage.ImageU(lsst.geom.Extent2I(10, 20)))
651 afwImage.makeExposure(afwImage.makeMaskedImage(
652 afwImage.ImageU(lsst.geom.Extent2I(10, 20))))
654 def testImageSlices(self):
655 """Test image slicing, which generate sub-images using Box2I under the covers"""
656 exp = afwImage.ExposureF(10, 20)
657 mi = exp.getMaskedImage()
658 mi.image[9, 19] = 10
659 # N.b. Exposures don't support setting/getting the pixels so can't
660 # replicate e.g. Image's slice tests
661 sexp = exp[1:4, 6:10]
662 self.assertEqual(sexp.getDimensions(), lsst.geom.ExtentI(3, 4))
663 sexp = exp[:, -3:, afwImage.LOCAL]
664 self.assertEqual(sexp.getDimensions(),
665 lsst.geom.ExtentI(exp.getWidth(), 3))
666 self.assertEqual(sexp.maskedImage[-1, -1, afwImage.LOCAL],
667 exp.maskedImage[-1, -1, afwImage.LOCAL])
669 def testConversionToScalar(self):
670 """Test that even 1-pixel Exposures can't be converted to scalars"""
671 im = afwImage.ExposureF(10, 20)
673 # only single pixel images may be converted
674 self.assertRaises(TypeError, float, im)
675 # actually, can't convert (img, msk, var) to scalar
676 self.assertRaises(TypeError, float, im[0, 0])
678 def testReadMetadata(self):
679 with lsst.utils.tests.getTempFilePath(".fits") as tmpFile:
680 self.exposureCrWcs.getMetadata().set("FRAZZLE", True)
681 # This will write the main metadata (inc. FRAZZLE) to the primary HDU, and the
682 # WCS to subsequent HDUs, along with INHERIT=T.
683 self.exposureCrWcs.writeFits(tmpFile)
684 # This should read the first non-empty HDU (i.e. it skips the primary), but
685 # goes back and reads it if it finds INHERIT=T. That should let us read
686 # frazzle and the Wcs from the PropertySet returned by
687 # testReadMetadata.
688 md = readMetadata(tmpFile)
689 wcs = afwGeom.makeSkyWcs(md, False)
690 self.assertPairsAlmostEqual(wcs.getPixelOrigin(), self.wcs.getPixelOrigin())
691 self.assertSpherePointsAlmostEqual(wcs.getSkyOrigin(), self.wcs.getSkyOrigin())
692 assert_allclose(wcs.getCdMatrix(), self.wcs.getCdMatrix(), atol=1e-10)
693 frazzle = md.getScalar("FRAZZLE")
694 self.assertTrue(frazzle)
696 def testArchiveKeys(self):
697 with lsst.utils.tests.getTempFilePath(".fits") as tmpFile:
698 exposure1 = afwImage.ExposureF(100, 100, self.wcs)
699 exposure1.setPsf(self.psf)
700 exposure1.writeFits(tmpFile)
701 exposure2 = afwImage.ExposureF(tmpFile)
702 self.assertFalse(exposure2.getMetadata().exists("AR_ID"))
703 self.assertFalse(exposure2.getMetadata().exists("PSF_ID"))
704 self.assertFalse(exposure2.getMetadata().exists("WCS_ID"))
706 def testTicket2861(self):
707 with lsst.utils.tests.getTempFilePath(".fits") as tmpFile:
708 exposure1 = afwImage.ExposureF(100, 100, self.wcs)
709 exposure1.setPsf(self.psf)
710 schema = afwTable.ExposureTable.makeMinimalSchema()
711 coaddInputs = afwImage.CoaddInputs(schema, schema)
712 exposure1.getInfo().setCoaddInputs(coaddInputs)
713 exposure2 = afwImage.ExposureF(exposure1, True)
714 self.assertIsNotNone(exposure2.getInfo().getCoaddInputs())
715 exposure2.writeFits(tmpFile)
716 exposure3 = afwImage.ExposureF(tmpFile)
717 self.assertIsNotNone(exposure3.getInfo().getCoaddInputs())
719 def testGetCutoutSky(self):
720 """Test we can get cutouts in sky coordinates, so long as there is a
721 valid WCS.
722 """
723 wcs = self.smallExposure.getWcs()
725 dimensions = [lsst.geom.Extent2I(100, 50), lsst.geom.Extent2I(15, 15), lsst.geom.Extent2I(0, 10),
726 lsst.geom.Extent2I(25, 30), lsst.geom.Extent2I(15, -5),
727 2*self.smallExposure.getDimensions()]
728 locations = [("center", self._getExposureCenter(self.smallExposure)),
729 ("edge", wcs.pixelToSky(lsst.geom.Point2D(0, 0))),
730 ("rounding test", wcs.pixelToSky(lsst.geom.Point2D(0.2, 0.7))),
731 ("just inside", wcs.pixelToSky(lsst.geom.Point2D(-0.5 + 1e-4, -0.5 + 1e-4))),
732 ("just outside", wcs.pixelToSky(lsst.geom.Point2D(-0.5 - 1e-4, -0.5 - 1e-4))),
733 ("outside", wcs.pixelToSky(lsst.geom.Point2D(-1000, -1000)))]
734 for cutoutSize in dimensions:
735 for label, cutoutCenter in locations:
736 msg = 'Cutout size = %s, location = %s' % (cutoutSize, label)
737 if "outside" not in label and all(cutoutSize.gt(0)):
738 cutout = self.smallExposure.getCutout(cutoutCenter, cutoutSize)
739 centerInPixels = wcs.skyToPixel(cutoutCenter)
740 precision = (1 + 1e-4)*np.sqrt(0.5)*wcs.getPixelScale(centerInPixels)
741 self._checkCutoutProperties(cutout, cutoutSize, cutoutCenter, precision, msg)
742 self._checkCutoutPixels(
743 cutout,
744 self._getValidCorners(self.smallExposure.getBBox(), cutout.getBBox()),
745 msg)
747 # Need a valid WCS
748 with self.assertRaises(pexExcept.LogicError, msg=msg):
749 self.exposureMiOnly.getCutout(cutoutCenter, cutoutSize)
750 else:
751 with self.assertRaises(pexExcept.InvalidParameterError, msg=msg):
752 self.smallExposure.getCutout(cutoutCenter, cutoutSize)
754 def testGetCutoutPixel(self):
755 """Test that we can get cutouts in pixel coordinates, even if the
756 extent is off the edge of the image, even if there is no WCS.
757 """
758 dimensions = [lsst.geom.Extent2I(100, 50), lsst.geom.Extent2I(15, 15), lsst.geom.Extent2I(0, 10),
759 lsst.geom.Extent2I(25, 30), lsst.geom.Extent2I(15, -5),
760 2*self.exposureMiOnly.getDimensions()]
761 locations = [("center", lsst.geom.Box2D(self.exposureMiOnly.getBBox()).getCenter()),
762 ("edge", lsst.geom.Point2D(0, 0)),
763 ("rounding test", lsst.geom.Point2D(0.2, 0.7)),
764 ("just inside", lsst.geom.Point2D(-0.5 + 1e-4, -0.5 + 1e-4)),
765 # These two should raise; center must be within image box.
766 ("just outside", lsst.geom.Point2D(-0.5 - 1e-4, -0.5 - 1e-4)),
767 ("outside", lsst.geom.Point2D(-1000, -1000))]
768 for cutoutSize in dimensions:
769 for label, cutoutCenter in locations:
770 msg = 'Cutout size = %s, location = %s' % (cutoutSize, label)
771 if "outside" not in label and all(cutoutSize.gt(0)):
772 cutout = self.exposureMiOnly.getCutout(cutoutCenter, cutoutSize)
773 self._checkCutoutPixels(
774 cutout,
775 self._getValidCorners(self.exposureMiOnly.getBBox(), cutout.getBBox()),
776 msg)
778 # Same result even if there is a wcs.
779 cutoutWithWcs = self.smallExposure.getCutout(cutoutCenter, cutoutSize)
780 self.assertMaskedImagesEqual(cutout.maskedImage, cutoutWithWcs.maskedImage)
782 # Getting a cutout with a bbox should produce the same result.
783 box = lsst.geom.Box2I.makeCenteredBox(cutoutCenter, lsst.geom.Extent2I(cutoutSize))
784 cutoutBox2I = self.exposureMiOnly.getCutout(box)
785 self.assertMaskedImagesEqual(cutout.maskedImage, cutoutBox2I.maskedImage)
786 else:
787 with self.assertRaises(pexExcept.InvalidParameterError, msg=msg):
788 self.exposureMiOnly.getCutout(cutoutCenter, cutoutSize)
790 def testGetConvexPolygon(self):
791 """Test the convex polygon."""
792 # Check that we do not have a convex polygon for the plain exposure.
793 self.assertIsNone(self.exposureMiOnly.convex_polygon)
795 # Check that all the points in the padded bounding box are in the polygon
796 bbox = self.exposureMiWcs.getBBox()
797 # Grow by the default padding.
798 bbox.grow(10)
799 x, y = np.meshgrid(np.arange(bbox.getBeginX(), bbox.getEndX(), dtype=np.float64),
800 np.arange(bbox.getBeginY(), bbox.getEndY(), dtype=np.float64))
801 wcs = self.exposureMiWcs.wcs
802 ra, dec = wcs.pixelToSkyArray(x.ravel(),
803 y.ravel())
805 poly = self.exposureMiWcs.convex_polygon
806 contains = poly.contains(ra, dec)
807 np.testing.assert_array_equal(contains, np.ones(len(contains), dtype=bool))
809 # Check that points one pixel outside of the bounding box are not in the polygon
810 bbox.grow(1)
812 ra, dec = wcs.pixelToSkyArray(
813 np.linspace(bbox.getBeginX(), bbox.getEndX(), 100),
814 np.full(100, bbox.getBeginY()))
815 contains = poly.contains(ra, dec)
816 np.testing.assert_array_equal(contains, np.zeros(len(contains), dtype=bool))
818 ra, dec = wcs.pixelToSkyArray(
819 np.linspace(bbox.getBeginX(), bbox.getEndX(), 100),
820 np.full(100, bbox.getEndY()))
821 contains = poly.contains(ra, dec)
822 np.testing.assert_array_equal(contains, np.zeros(len(contains), dtype=bool))
824 ra, dec = wcs.pixelToSkyArray(
825 np.full(100, bbox.getBeginX()),
826 np.linspace(bbox.getBeginY(), bbox.getEndY(), 100))
827 contains = poly.contains(ra, dec)
828 np.testing.assert_array_equal(contains, np.zeros(len(contains), dtype=bool))
830 ra, dec = wcs.pixelToSkyArray(
831 np.full(100, bbox.getEndX()),
832 np.linspace(bbox.getBeginY(), bbox.getEndY(), 100))
833 contains = poly.contains(ra, dec)
834 np.testing.assert_array_equal(contains, np.zeros(len(contains), dtype=bool))
836 def testContainsSkyCoords(self):
837 """Test the sky coord containment code."""
838 self.assertRaisesRegex(ValueError,
839 "Exposure does not have a valid WCS",
840 self.exposureMiOnly.containsSkyCoords,
841 0.0,
842 0.0)
844 # Check that all the points within the bounding box are contained
845 bbox = self.exposureMiWcs.getBBox()
846 x, y = np.meshgrid(np.arange(bbox.getBeginX() + 1, bbox.getEndX() - 1),
847 np.arange(bbox.getBeginY() + 1, bbox.getEndY() - 1))
848 wcs = self.exposureMiWcs.wcs
849 ra, dec = wcs.pixelToSkyArray(x.ravel().astype(np.float64),
850 y.ravel().astype(np.float64))
852 contains = self.exposureMiWcs.containsSkyCoords(ra*units.radian,
853 dec*units.radian)
854 np.testing.assert_array_equal(contains, np.ones(len(contains), dtype=bool))
856 # Same test, everything in degrees.
857 ra, dec = wcs.pixelToSkyArray(x.ravel().astype(np.float64),
858 y.ravel().astype(np.float64),
859 degrees=True)
861 contains = self.exposureMiWcs.containsSkyCoords(ra*units.degree,
862 dec*units.degree)
863 np.testing.assert_array_equal(contains, np.ones(len(contains), dtype=bool))
865 # Prepend and append some positions out of the box.
866 ra = np.concatenate(([300.0], ra, [180.]))
867 dec = np.concatenate(([50.0], dec, [50.0]))
869 contains = self.exposureMiWcs.containsSkyCoords(ra*units.degree,
870 dec*units.degree)
871 compare = np.ones(len(contains), dtype=bool)
872 compare[0] = False
873 compare[-1] = False
874 np.testing.assert_array_equal(contains, compare)
876 def _checkCutoutProperties(self, cutout, size, center, precision, msg):
877 """Test whether a cutout has the desired size and position.
879 Parameters
880 ----------
881 cutout : `lsst.afw.image.Exposure`
882 The cutout to test.
883 size : `lsst.geom.Extent2I`
884 The expected dimensions of ``cutout``.
885 center : `lsst.geom.SpherePoint`
886 The expected center of ``cutout``.
887 precision : `lsst.geom.Angle`
888 The precision to which ``center`` must match.
889 msg : `str`
890 An error message suffix describing test parameters.
891 """
892 newCenter = self._getExposureCenter(cutout)
893 self.assertIsNotNone(cutout, msg=msg)
894 self.assertSpherePointsAlmostEqual(newCenter, center, maxSep=precision, msg=msg)
895 self.assertEqual(cutout.getWidth(), size[0], msg=msg)
896 self.assertEqual(cutout.getHeight(), size[1], msg=msg)
898 def _checkCutoutPixels(self, cutout, validCorners, msg):
899 """Test whether a cutout has valid/empty pixels where expected.
901 Parameters
902 ----------
903 cutout : `lsst.afw.image.Exposure`
904 The cutout to test.
905 validCorners : iterable of `lsst.geom.Point2I`
906 The corners of ``cutout`` that should be drawn from the original image.
907 msg : `str`
908 An error message suffix describing test parameters.
909 """
910 mask = cutout.getMaskedImage().getMask()
911 edgeMask = mask.getPlaneBitMask("NO_DATA")
913 for corner in cutout.getBBox().getCorners():
914 maskBitsSet = mask[corner] & edgeMask
915 if corner in validCorners:
916 self.assertEqual(maskBitsSet, 0, msg=msg)
917 else:
918 self.assertEqual(maskBitsSet, edgeMask, msg=msg)
920 def _getExposureCenter(self, exposure):
921 """Return the sky coordinates of an Exposure's center.
923 Parameters
924 ----------
925 exposure : `lsst.afw.image.Exposure`
926 The image whose center is desired.
928 Returns
929 -------
930 center : `lsst.geom.SpherePoint`
931 The position at the center of ``exposure``.
932 """
933 return exposure.getWcs().pixelToSky(lsst.geom.Box2D(exposure.getBBox()).getCenter())
935 def _getValidCorners(self, imageBox, cutoutBox):
936 """Return the corners of a cutout that are constrained by the original image.
938 Parameters
939 ----------
940 imageBox: `lsst.geom.Extent2I`
941 The bounding box of the original image.
942 cutoutBox : `lsst.geom.Box2I`
943 The bounding box of the cutout.
945 Returns
946 -------
947 corners : iterable of `lsst.geom.Point2I`
948 The corners that are drawn from the original image.
949 """
950 return [corner for corner in cutoutBox.getCorners() if corner in imageBox]
953class ExposureInfoTestCase(lsst.utils.tests.TestCase):
954 def setUp(self):
955 super().setUp()
957 self.wcs = afwGeom.makeSkyWcs(lsst.geom.Point2D(0.0, 0.0),
958 lsst.geom.SpherePoint(2.0, 34.0, lsst.geom.degrees),
959 np.identity(2),
960 )
961 self.photoCalib = afwImage.PhotoCalib(1.5)
962 self.psf = DummyPsf(2.0)
963 self.detector = DetectorWrapper().detector
964 self.summaryStats = afwImage.ExposureSummaryStats(ra=100.0)
965 self.polygon = afwGeom.Polygon(lsst.geom.Box2D(lsst.geom.Point2D(0.0, 0.0),
966 lsst.geom.Point2D(25.0, 20.0)))
967 self.coaddInputs = afwImage.CoaddInputs()
968 self.apCorrMap = afwImage.ApCorrMap()
969 self.transmissionCurve = afwImage.TransmissionCurve.makeIdentity()
971 self.exposureInfo = afwImage.ExposureInfo()
972 self.gFilterLabel = afwImage.FilterLabel(band="g")
973 self.exposureId = 42
975 def _checkAlias(self, exposureInfo, key, value, has, get):
976 self.assertFalse(has())
977 self.assertFalse(exposureInfo.hasComponent(key))
978 self.assertIsNone(get())
979 self.assertIsNone(exposureInfo.getComponent(key))
981 self.exposureInfo.setComponent(key, value)
982 self.assertTrue(has())
983 self.assertTrue(exposureInfo.hasComponent(key))
984 self.assertIsNotNone(get())
985 self.assertIsNotNone(exposureInfo.getComponent(key))
986 self.assertEqual(get(), value)
987 self.assertEqual(exposureInfo.getComponent(key), value)
989 self.exposureInfo.removeComponent(key)
990 self.assertFalse(has())
991 self.assertFalse(exposureInfo.hasComponent(key))
992 self.assertIsNone(get())
993 self.assertIsNone(exposureInfo.getComponent(key))
995 def testAliases(self):
996 cls = type(self.exposureInfo)
997 self._checkAlias(self.exposureInfo, cls.KEY_WCS, self.wcs,
998 self.exposureInfo.hasWcs, self.exposureInfo.getWcs)
999 self._checkAlias(self.exposureInfo, cls.KEY_PSF, self.psf,
1000 self.exposureInfo.hasPsf, self.exposureInfo.getPsf)
1001 self._checkAlias(self.exposureInfo, cls.KEY_PHOTO_CALIB, self.photoCalib,
1002 self.exposureInfo.hasPhotoCalib, self.exposureInfo.getPhotoCalib)
1003 self._checkAlias(self.exposureInfo, cls.KEY_DETECTOR, self.detector,
1004 self.exposureInfo.hasDetector, self.exposureInfo.getDetector)
1005 self._checkAlias(self.exposureInfo, cls.KEY_VALID_POLYGON, self.polygon,
1006 self.exposureInfo.hasValidPolygon, self.exposureInfo.getValidPolygon)
1007 self._checkAlias(self.exposureInfo, cls.KEY_COADD_INPUTS, self.coaddInputs,
1008 self.exposureInfo.hasCoaddInputs, self.exposureInfo.getCoaddInputs)
1009 self._checkAlias(self.exposureInfo, cls.KEY_AP_CORR_MAP, self.apCorrMap,
1010 self.exposureInfo.hasApCorrMap, self.exposureInfo.getApCorrMap)
1011 self._checkAlias(self.exposureInfo, cls.KEY_TRANSMISSION_CURVE, self.transmissionCurve,
1012 self.exposureInfo.hasTransmissionCurve, self.exposureInfo.getTransmissionCurve)
1013 self._checkAlias(self.exposureInfo, cls.KEY_SUMMARY_STATS, self.summaryStats,
1014 self.exposureInfo.hasSummaryStats, self.exposureInfo.getSummaryStats)
1015 self._checkAlias(self.exposureInfo, cls.KEY_FILTER, self.gFilterLabel,
1016 self.exposureInfo.hasFilter, self.exposureInfo.getFilter)
1018 def testId(self):
1019 self.exposureInfo.setVisitInfo(afwImage.VisitInfo())
1021 self.assertFalse(self.exposureInfo.hasId())
1022 self.assertIsNone(self.exposureInfo.getId())
1023 self.assertIsNone(self.exposureInfo.id)
1025 self.exposureInfo.setId(self.exposureId)
1026 self.assertTrue(self.exposureInfo.hasId())
1027 self.assertIsNotNone(self.exposureInfo.getId())
1028 self.assertIsNotNone(self.exposureInfo.id)
1029 self.assertEqual(self.exposureInfo.getId(), self.exposureId)
1030 self.assertEqual(self.exposureInfo.id, self.exposureId)
1032 self.exposureInfo.id = 99899
1033 self.assertEqual(self.exposureInfo.getId(), 99899)
1035 self.exposureInfo.id = None
1036 self.assertFalse(self.exposureInfo.hasId())
1037 self.assertIsNone(self.exposureInfo.getId())
1038 self.assertIsNone(self.exposureInfo.id)
1040 def testCopy(self):
1041 # Test that ExposureInfos have independently settable state
1042 copy = afwImage.ExposureInfo(self.exposureInfo, True)
1043 self.assertEqual(self.exposureInfo.getWcs(), copy.getWcs())
1045 newWcs = afwGeom.makeSkyWcs(lsst.geom.Point2D(-23.0, 8.0),
1046 lsst.geom.SpherePoint(0.0, 0.0, lsst.geom.degrees),
1047 np.identity(2),
1048 )
1049 copy.setWcs(newWcs)
1050 self.assertEqual(copy.getWcs(), newWcs)
1051 self.assertNotEqual(self.exposureInfo.getWcs(), copy.getWcs())
1053 def testMissingProperties(self):
1054 # Test that invalid properties return None instead of raising
1055 exposureInfo = afwImage.ExposureInfo()
1057 self.assertIsNone(exposureInfo.id)
1060class ExposureNoAfwdataTestCase(lsst.utils.tests.TestCase):
1061 """Tests of Exposure that don't require afwdata.
1063 These tests use the trivial exposures written to ``afw/tests/data``.
1064 """
1065 def setUp(self):
1066 self.dataDir = os.path.join(os.path.split(__file__)[0], "data")
1068 # Check the values below against what was written by comparing with
1069 # the code in `afw/tests/data/makeTestExposure.py`
1070 nx = ny = 10
1071 image = afwImage.ImageF(np.arange(nx*ny, dtype='f').reshape(nx, ny))
1072 variance = afwImage.ImageF(np.ones((nx, ny), dtype='f'))
1073 mask = afwImage.MaskX(nx, ny)
1074 mask.array[5, 5] = 5
1075 self.maskedImage = afwImage.MaskedImageF(image, mask, variance)
1076 self.exposureId = 12345
1078 self.v0PhotoCalib = afwImage.makePhotoCalibFromCalibZeroPoint(1e6, 2e4)
1079 self.v1PhotoCalib = afwImage.PhotoCalib(1e6, 2e4)
1080 self.v1FilterLabel = afwImage.FilterLabel(physical="ha")
1081 self.v2FilterLabel = afwImage.FilterLabel(band="N656", physical="ha")
1083 def testReadUnversioned(self):
1084 """Test that we can read an unversioned (implicit verison 0) file.
1085 """
1086 filename = os.path.join(self.dataDir, "exposure-noversion.fits")
1087 exposure = afwImage.ExposureF.readFits(filename)
1089 self.assertMaskedImagesEqual(exposure.maskedImage, self.maskedImage)
1091 self.assertEqual(exposure.info.id, self.exposureId)
1092 self.assertEqual(exposure.getPhotoCalib(), self.v0PhotoCalib)
1093 self.assertEqual(exposure.getFilter(), self.v1FilterLabel)
1095 def testReadVersion0(self):
1096 """Test that we can read a version 0 file.
1097 This file should be identical to the unversioned one, except that it
1098 is marked as ExposureInfo version 0 in the header.
1099 """
1100 filename = os.path.join(self.dataDir, "exposure-version-0.fits")
1101 exposure = afwImage.ExposureF.readFits(filename)
1103 self.assertMaskedImagesEqual(exposure.maskedImage, self.maskedImage)
1105 self.assertEqual(exposure.info.id, self.exposureId)
1106 self.assertEqual(exposure.getPhotoCalib(), self.v0PhotoCalib)
1107 self.assertEqual(exposure.getFilter(), self.v1FilterLabel)
1109 # Check that the metadata reader parses the file correctly
1110 reader = afwImage.ExposureFitsReader(filename)
1111 self.assertEqual(reader.readExposureInfo().getPhotoCalib(), self.v0PhotoCalib)
1112 self.assertEqual(reader.readPhotoCalib(), self.v0PhotoCalib)
1114 def testReadVersion1(self):
1115 """Test that we can read a version 1 file.
1116 Version 1 replaced Calib with PhotoCalib.
1117 """
1118 filename = os.path.join(self.dataDir, "exposure-version-1.fits")
1119 exposure = afwImage.ExposureF.readFits(filename)
1121 self.assertMaskedImagesEqual(exposure.maskedImage, self.maskedImage)
1123 self.assertEqual(exposure.info.id, self.exposureId)
1124 self.assertEqual(exposure.getPhotoCalib(), self.v1PhotoCalib)
1125 self.assertEqual(exposure.getFilter(), self.v1FilterLabel)
1127 # Check that the metadata reader parses the file correctly
1128 reader = afwImage.ExposureFitsReader(filename)
1129 self.assertEqual(reader.readExposureInfo().getPhotoCalib(), self.v1PhotoCalib)
1130 self.assertEqual(reader.readPhotoCalib(), self.v1PhotoCalib)
1132 def testReadVersion2(self):
1133 """Test that we can read a version 2 file.
1134 Version 2 replaced Filter with FilterLabel.
1135 """
1136 filename = os.path.join(self.dataDir, "exposure-version-2.fits")
1137 exposure = afwImage.ExposureF.readFits(filename)
1139 self.assertMaskedImagesEqual(exposure.maskedImage, self.maskedImage)
1141 self.assertEqual(exposure.info.id, self.exposureId)
1142 self.assertEqual(exposure.getPhotoCalib(), self.v1PhotoCalib)
1143 self.assertEqual(exposure.getFilter(), self.v2FilterLabel)
1145 # Check that the metadata reader parses the file correctly
1146 reader = afwImage.ExposureFitsReader(filename)
1147 self.assertEqual(reader.readExposureInfo().getPhotoCalib(), self.v1PhotoCalib)
1148 self.assertEqual(reader.readPhotoCalib(), self.v1PhotoCalib)
1150 def testExposureSummaryExtraComponents(self):
1151 """Test that we can read an exposure summary with extra components.
1152 """
1153 testDict = {'ra': 0.0,
1154 'dec': 0.0,
1155 'nonsense': 1.0}
1156 bytes = yaml.dump(testDict, encoding='utf-8')
1157 with self.assertWarns(FutureWarning):
1158 summaryStats = lsst.afw.image.ExposureSummaryStats._read(bytes)
1160 self.assertEqual(summaryStats.ra, testDict['ra'])
1161 self.assertEqual(summaryStats.dec, testDict['dec'])
1163 def testExposureSummaryForwardComponents(self):
1164 """Test that we can forward extra components (e.g. decl->dec).
1165 """
1166 testDict = {'ra': 10.0,
1167 'decl': 10.0}
1168 bytes = yaml.dump(testDict, encoding='utf-8')
1169 # Cleanly forwarded fields must not result in a warning.
1170 with warnings.catch_warnings():
1171 warnings.simplefilter("error")
1172 summaryStats = lsst.afw.image.ExposureSummaryStats._read(bytes)
1174 self.assertEqual(summaryStats.ra, testDict['ra'])
1175 self.assertEqual(summaryStats.dec, testDict['decl'])
1177 # And check if there are both listed, it should use the new dec value.
1178 testDict = {'ra': 10.0,
1179 'dec': 5.0,
1180 'decl': 10.0}
1181 bytes = yaml.dump(testDict, encoding='utf-8')
1182 with self.assertWarns(FutureWarning):
1183 summaryStats = lsst.afw.image.ExposureSummaryStats._read(bytes)
1185 self.assertEqual(summaryStats.ra, testDict['ra'])
1186 self.assertEqual(summaryStats.dec, testDict['dec'])
1188 def testExposureSummarySchema(self):
1189 """Test that we can make a schema for an exposure summary and populate
1190 records with that schema.
1191 """
1192 schema = afwTable.Schema()
1193 afwImage.ExposureSummaryStats.update_schema(schema)
1194 self.maxDiff = None
1195 self.assertEqual(
1196 {field.name for field in dataclasses.fields(afwImage.ExposureSummaryStats)},
1197 set(schema.getNames()) | {"version"},
1198 )
1199 catalog = afwTable.BaseCatalog(schema)
1200 summary1 = afwImage.ExposureSummaryStats()
1201 for n, field in enumerate(dataclasses.fields(afwImage.ExposureSummaryStats)):
1202 # Set fields to deterministic, distinct, but arbitrary values.
1203 if field.type == "float":
1204 setattr(summary1, field.name, float(0.5**n))
1205 elif field.type == "int":
1206 setattr(summary1, field.name, 10*n)
1207 elif field.type == "list[float]":
1208 setattr(summary1, field.name, [n + 0.1, n + 0.2, n + 0.3, n + 0.4])
1209 else:
1210 raise TypeError(f"Unexpected type: {field.type!r}.")
1211 record = catalog.addNew()
1212 summary1.update_record(record)
1213 summary2 = afwImage.ExposureSummaryStats.from_record(record)
1214 self.assertEqual(summary1, summary2)
1216 def testMetadataProperty(self):
1217 """Test that the metadata property works as expected.
1218 """
1219 exposure = afwImage.ExposureF(3, 4)
1220 self.assertFalse(exposure.metadata)
1221 self.assertIsNotNone(exposure.metadata)
1222 exposure.metadata = None
1223 self.assertIsNone(exposure.metadata)
1224 metadata = PropertyList()
1225 metadata["one"] = 1
1226 exposure.metadata = metadata
1227 self.assertEqual(exposure.metadata["one"], 1)
1230class MemoryTester(lsst.utils.tests.MemoryTestCase):
1231 pass
1234def setup_module(module):
1235 lsst.utils.tests.init()
1238if __name__ == "__main__": 1238 ↛ 1239line 1238 didn't jump to line 1239, because the condition on line 1238 was never true
1239 lsst.utils.tests.init()
1240 unittest.main()