Coverage for tests/test_exposure.py : 12%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# This file is part of afw.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (https://www.lsst.org).
6# See the COPYRIGHT file at the top-level directory of this distribution
7# for details of code ownership.
8#
9# This program is free software: you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation, either version 3 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program. If not, see <https://www.gnu.org/licenses/>.
22"""
23Test lsst.afw.image.Exposure
24"""
26import os.path
27import unittest
29import numpy as np
30from numpy.testing import assert_allclose
31import yaml
33import lsst.utils
34import lsst.utils.tests
35import lsst.geom
36import lsst.afw.image as afwImage
37from lsst.afw.image.utils import defineFilter
38from lsst.afw.coord import Weather
39import lsst.afw.geom as afwGeom
40import lsst.afw.table as afwTable
41import lsst.pex.exceptions as pexExcept
42from lsst.afw.fits import readMetadata, FitsError
43from lsst.afw.cameraGeom.testUtils import DetectorWrapper
44from lsst.log import Log
45from testTableArchivesLib import DummyPsf
47Log.getLogger("afw.image.Mask").setLevel(Log.INFO)
49try:
50 dataDir = os.path.join(lsst.utils.getPackageDir("afwdata"), "data")
51except LookupError:
52 dataDir = None
53else:
54 InputMaskedImageName = "871034p_1_MI.fits"
55 InputMaskedImageNameSmall = "small_MI.fits"
56 InputImageNameSmall = "small"
57 OutputMaskedImageName = "871034p_1_MInew.fits"
59 currDir = os.path.abspath(os.path.dirname(__file__))
60 inFilePath = os.path.join(dataDir, InputMaskedImageName)
61 inFilePathSmall = os.path.join(dataDir, InputMaskedImageNameSmall)
62 inFilePathSmallImage = os.path.join(dataDir, InputImageNameSmall)
65@unittest.skipIf(dataDir is None, "afwdata not setup")
66class ExposureTestCase(lsst.utils.tests.TestCase):
67 """
68 A test case for the Exposure Class
69 """
71 def setUp(self):
72 maskedImage = afwImage.MaskedImageF(inFilePathSmall)
73 maskedImageMD = readMetadata(inFilePathSmall)
75 self.smallExposure = afwImage.ExposureF(inFilePathSmall)
76 self.width = maskedImage.getWidth()
77 self.height = maskedImage.getHeight()
78 self.wcs = afwGeom.makeSkyWcs(maskedImageMD, False)
79 self.md = maskedImageMD
80 self.psf = DummyPsf(2.0)
81 self.detector = DetectorWrapper().detector
82 self.extras = {"MISC": DummyPsf(3.5)}
84 self.exposureBlank = afwImage.ExposureF()
85 self.exposureMiOnly = afwImage.makeExposure(maskedImage)
86 self.exposureMiWcs = afwImage.makeExposure(maskedImage, self.wcs)
87 # n.b. the (100, 100, ...) form
88 self.exposureCrWcs = afwImage.ExposureF(100, 100, self.wcs)
89 # test with ExtentI(100, 100) too
90 self.exposureCrOnly = afwImage.ExposureF(lsst.geom.ExtentI(100, 100))
92 afwImage.Filter.reset()
93 afwImage.FilterProperty.reset()
95 defineFilter("g", 470.0)
97 def tearDown(self):
98 del self.smallExposure
99 del self.wcs
100 del self.psf
101 del self.detector
102 del self.extras
104 del self.exposureBlank
105 del self.exposureMiOnly
106 del self.exposureMiWcs
107 del self.exposureCrWcs
108 del self.exposureCrOnly
110 def testGetMaskedImage(self):
111 """
112 Test to ensure a MaskedImage can be obtained from each
113 Exposure. An Exposure is required to have a MaskedImage,
114 therefore each of the Exposures should return a MaskedImage.
116 MaskedImage class should throw appropriate
117 lsst::pex::exceptions::NotFound if the MaskedImage can not be
118 obtained.
119 """
120 maskedImageBlank = self.exposureBlank.getMaskedImage()
121 blankWidth = maskedImageBlank.getWidth()
122 blankHeight = maskedImageBlank.getHeight()
123 if blankWidth != blankHeight != 0:
124 self.fail(f"{blankWidth} = {blankHeight} != 0")
126 maskedImageMiOnly = self.exposureMiOnly.getMaskedImage()
127 miOnlyWidth = maskedImageMiOnly.getWidth()
128 miOnlyHeight = maskedImageMiOnly.getHeight()
129 self.assertAlmostEqual(miOnlyWidth, self.width)
130 self.assertAlmostEqual(miOnlyHeight, self.height)
132 # NOTE: Unittests for Exposures created from a MaskedImage and
133 # a WCS object are incomplete. No way to test the validity of
134 # the WCS being copied/created.
136 maskedImageMiWcs = self.exposureMiWcs.getMaskedImage()
137 miWcsWidth = maskedImageMiWcs.getWidth()
138 miWcsHeight = maskedImageMiWcs.getHeight()
139 self.assertAlmostEqual(miWcsWidth, self.width)
140 self.assertAlmostEqual(miWcsHeight, self.height)
142 maskedImageCrWcs = self.exposureCrWcs.getMaskedImage()
143 crWcsWidth = maskedImageCrWcs.getWidth()
144 crWcsHeight = maskedImageCrWcs.getHeight()
145 if crWcsWidth != crWcsHeight != 0:
146 self.fail(f"{crWcsWidth} != {crWcsHeight} != 0")
148 maskedImageCrOnly = self.exposureCrOnly.getMaskedImage()
149 crOnlyWidth = maskedImageCrOnly.getWidth()
150 crOnlyHeight = maskedImageCrOnly.getHeight()
151 if crOnlyWidth != crOnlyHeight != 0:
152 self.fail(f"{crOnlyWidth} != {crOnlyHeight} != 0")
154 # Check Exposure.getWidth() returns the MaskedImage's width
155 self.assertEqual(crOnlyWidth, self.exposureCrOnly.getWidth())
156 self.assertEqual(crOnlyHeight, self.exposureCrOnly.getHeight())
157 # check width/height properties
158 self.assertEqual(crOnlyWidth, self.exposureCrOnly.width)
159 self.assertEqual(crOnlyHeight, self.exposureCrOnly.height)
161 def testProperties(self):
162 self.assertMaskedImagesEqual(self.exposureMiOnly.maskedImage,
163 self.exposureMiOnly.getMaskedImage())
164 mi2 = afwImage.MaskedImageF(self.exposureMiOnly.getDimensions())
165 mi2.image.array[:] = 5.0
166 mi2.variance.array[:] = 3.0
167 mi2.mask.array[:] = 0x1
168 self.exposureMiOnly.maskedImage = mi2
169 self.assertMaskedImagesEqual(self.exposureMiOnly.maskedImage, mi2)
170 self.assertImagesEqual(self.exposureMiOnly.image,
171 self.exposureMiOnly.maskedImage.image)
173 image3 = afwImage.ImageF(self.exposureMiOnly.getDimensions())
174 image3.array[:] = 3.0
175 self.exposureMiOnly.image = image3
176 self.assertImagesEqual(self.exposureMiOnly.image, image3)
178 mask3 = afwImage.MaskX(self.exposureMiOnly.getDimensions())
179 mask3.array[:] = 0x2
180 self.exposureMiOnly.mask = mask3
181 self.assertMasksEqual(self.exposureMiOnly.mask, mask3)
183 var3 = afwImage.ImageF(self.exposureMiOnly.getDimensions())
184 var3.array[:] = 2.0
185 self.exposureMiOnly.variance = var3
186 self.assertImagesEqual(self.exposureMiOnly.variance, var3)
188 # Test the property getter for a null VisitInfo.
189 self.assertIsNone(self.exposureMiOnly.visitInfo)
191 def testGetWcs(self):
192 """Test that a WCS can be obtained from each Exposure created with
193 a WCS, and that an Exposure lacking a WCS returns None.
194 """
195 # These exposures don't contain a WCS
196 self.assertIsNone(self.exposureBlank.getWcs())
197 self.assertIsNone(self.exposureMiOnly.getWcs())
198 self.assertIsNone(self.exposureCrOnly.getWcs())
200 # These exposures should contain a WCS
201 self.assertEqual(self.wcs, self.exposureMiWcs.getWcs())
202 self.assertEqual(self.wcs, self.exposureCrWcs.getWcs())
204 def testExposureInfoConstructor(self):
205 """Test the Exposure(maskedImage, exposureInfo) constructor"""
206 exposureInfo = afwImage.ExposureInfo()
207 exposureInfo.setWcs(self.wcs)
208 exposureInfo.setDetector(self.detector)
209 gFilter = afwImage.Filter("g")
210 gFilterLabel = afwImage.FilterLabel(band="g")
211 exposureInfo.setFilter(gFilter)
212 exposureInfo.setFilterLabel(gFilterLabel)
213 maskedImage = afwImage.MaskedImageF(inFilePathSmall)
214 exposure = afwImage.ExposureF(maskedImage, exposureInfo)
216 self.assertTrue(exposure.hasWcs())
217 self.assertEqual(exposure.getWcs().getPixelOrigin(),
218 self.wcs.getPixelOrigin())
219 self.assertEqual(exposure.getDetector().getName(),
220 self.detector.getName())
221 self.assertEqual(exposure.getDetector().getSerial(),
222 self.detector.getSerial())
223 self.assertEqual(exposure.getFilter(), gFilter)
224 self.assertEqual(exposure.getFilterLabel(), gFilterLabel)
226 self.assertTrue(exposure.getInfo().hasWcs())
227 # check the ExposureInfo property
228 self.assertTrue(exposure.info.hasWcs())
229 self.assertEqual(exposure.getInfo().getWcs().getPixelOrigin(),
230 self.wcs.getPixelOrigin())
231 self.assertEqual(exposure.getInfo().getDetector().getName(),
232 self.detector.getName())
233 self.assertEqual(exposure.getInfo().getDetector().getSerial(),
234 self.detector.getSerial())
235 self.assertEqual(exposure.getInfo().getFilter(), gFilter)
236 self.assertEqual(exposure.getInfo().getFilterLabel(), gFilterLabel)
238 def testNullWcs(self):
239 """Test that an Exposure constructed with second argument None is usable
241 When the exposureInfo constructor was first added, trying to get a WCS
242 or other info caused a segfault because the ExposureInfo did not exist.
243 """
244 maskedImage = self.exposureMiOnly.getMaskedImage()
245 exposure = afwImage.ExposureF(maskedImage, None)
246 self.assertFalse(exposure.hasWcs())
247 self.assertFalse(exposure.hasPsf())
249 def testExposureInfoSetNone(self):
250 exposureInfo = afwImage.ExposureInfo()
251 exposureInfo.setDetector(None)
252 exposureInfo.setValidPolygon(None)
253 exposureInfo.setPsf(None)
254 exposureInfo.setWcs(None)
255 exposureInfo.setPhotoCalib(None)
256 exposureInfo.setCoaddInputs(None)
257 exposureInfo.setVisitInfo(None)
258 exposureInfo.setApCorrMap(None)
259 for key in self.extras:
260 exposureInfo.setComponent(key, None)
262 def testSetExposureInfo(self):
263 exposureInfo = afwImage.ExposureInfo()
264 exposureInfo.setWcs(self.wcs)
265 exposureInfo.setDetector(self.detector)
266 gFilter = afwImage.Filter("g")
267 gFilterLabel = afwImage.FilterLabel(band="g")
268 exposureInfo.setFilter(gFilter)
269 exposureInfo.setFilterLabel(gFilterLabel)
270 maskedImage = afwImage.MaskedImageF(inFilePathSmall)
271 exposure = afwImage.ExposureF(maskedImage)
272 self.assertFalse(exposure.hasWcs())
274 exposure.setInfo(exposureInfo)
276 self.assertTrue(exposure.hasWcs())
277 self.assertEqual(exposure.getWcs().getPixelOrigin(),
278 self.wcs.getPixelOrigin())
279 self.assertEqual(exposure.getDetector().getName(),
280 self.detector.getName())
281 self.assertEqual(exposure.getDetector().getSerial(),
282 self.detector.getSerial())
283 self.assertEqual(exposure.getFilter(), gFilter)
284 self.assertEqual(exposure.getFilterLabel(), gFilterLabel)
286 # test properties
287 self.assertEqual(exposure.detector.getName(), self.detector.getName())
288 self.assertEqual(exposure.filterLabel, gFilterLabel)
289 self.assertEqual(exposure.wcs, self.wcs)
291 def testDefaultFilter(self):
292 """Test that old convention of having a "default" filter replaced with `None`.
293 """
294 exposureInfo = afwImage.ExposureInfo()
295 noFilter = afwImage.Filter()
296 exposureInfo.setFilter(noFilter)
297 self.assertFalse(exposureInfo.hasFilterLabel())
298 self.assertIsNone(exposureInfo.getFilterLabel())
300 def testVisitInfoFitsPersistence(self):
301 """Test saving an exposure to FITS and reading it back in preserves (some) VisitInfo fields"""
302 exposureId = 5
303 exposureTime = 12.3
304 boresightRotAngle = 45.6 * lsst.geom.degrees
305 weather = Weather(1.1, 2.2, 0.3)
306 visitInfo = afwImage.VisitInfo(
307 exposureId=exposureId,
308 exposureTime=exposureTime,
309 boresightRotAngle=boresightRotAngle,
310 weather=weather,
311 )
312 photoCalib = afwImage.PhotoCalib(3.4, 5.6)
313 exposureInfo = afwImage.ExposureInfo()
314 exposureInfo.setVisitInfo(visitInfo)
315 exposureInfo.setPhotoCalib(photoCalib)
316 exposureInfo.setDetector(self.detector)
317 gFilter = afwImage.Filter("g")
318 gFilterLabel = afwImage.FilterLabel(band="g")
319 exposureInfo.setFilter(gFilter)
320 exposureInfo.setFilterLabel(gFilterLabel)
321 maskedImage = afwImage.MaskedImageF(inFilePathSmall)
322 exposure = afwImage.ExposureF(maskedImage, exposureInfo)
323 with lsst.utils.tests.getTempFilePath(".fits") as tmpFile:
324 exposure.writeFits(tmpFile)
325 rtExposure = afwImage.ExposureF(tmpFile)
326 rtVisitInfo = rtExposure.getInfo().getVisitInfo()
327 self.assertEqual(rtVisitInfo.getWeather(), weather)
328 self.assertEqual(rtExposure.getPhotoCalib(), photoCalib)
329 self.assertEqual(rtExposure.getFilter(), gFilter)
330 self.assertEqual(rtExposure.getFilterLabel(), gFilterLabel)
332 # Test property getters.
333 self.assertEqual(rtExposure.photoCalib, photoCalib)
334 self.assertEqual(rtExposure.filterLabel, gFilterLabel)
335 # NOTE: we can't test visitInfo equality, because most fields are NaN.
336 self.assertIsNotNone(rtExposure.visitInfo)
338 def testSetMembers(self):
339 """
340 Test that the MaskedImage and the WCS of an Exposure can be set.
341 """
342 exposure = afwImage.ExposureF()
344 maskedImage = afwImage.MaskedImageF(inFilePathSmall)
345 exposure.setMaskedImage(maskedImage)
346 exposure.setWcs(self.wcs)
347 exposure.setDetector(self.detector)
348 exposure.setFilter(afwImage.Filter("g"))
349 exposure.setFilterLabel(afwImage.FilterLabel(band="g"))
351 self.assertEqual(exposure.getDetector().getName(),
352 self.detector.getName())
353 self.assertEqual(exposure.getDetector().getSerial(),
354 self.detector.getSerial())
355 self.assertEqual(exposure.getFilter().getName(), "g")
356 self.assertEqual(exposure.getFilterLabel().bandLabel, "g")
357 self.assertEqual(exposure.getWcs(), self.wcs)
359 # The PhotoCalib tests are in test_photoCalib.py;
360 # here we just check that it's gettable and settable.
361 self.assertIsNone(exposure.getPhotoCalib())
363 photoCalib = afwImage.PhotoCalib(511.1, 44.4)
364 exposure.setPhotoCalib(photoCalib)
365 self.assertEqual(exposure.getPhotoCalib(), photoCalib)
367 # Psfs next
368 self.assertFalse(exposure.hasPsf())
369 exposure.setPsf(self.psf)
370 self.assertTrue(exposure.hasPsf())
372 exposure.setPsf(DummyPsf(1.0)) # we can reset the Psf
374 # extras next
375 info = exposure.getInfo()
376 for key, value in self.extras.items():
377 self.assertFalse(info.hasComponent(key))
378 self.assertIsNone(info.getComponent(key))
379 info.setComponent(key, value)
380 self.assertTrue(info.hasComponent(key))
381 self.assertEqual(info.getComponent(key), value)
382 info.removeComponent(key)
383 self.assertFalse(info.hasComponent(key))
385 # Test that we can set the MaskedImage and WCS of an Exposure
386 # that already has both
387 self.exposureMiWcs.setMaskedImage(maskedImage)
388 exposure.setWcs(self.wcs)
390 def testHasWcs(self):
391 """
392 Test if an Exposure has a WCS or not.
393 """
394 self.assertFalse(self.exposureBlank.hasWcs())
396 self.assertFalse(self.exposureMiOnly.hasWcs())
397 self.assertTrue(self.exposureMiWcs.hasWcs())
398 self.assertTrue(self.exposureCrWcs.hasWcs())
399 self.assertFalse(self.exposureCrOnly.hasWcs())
401 def testGetSubExposure(self):
402 """
403 Test that a subExposure of the original Exposure can be obtained.
405 The MaskedImage class should throw a
406 lsst::pex::exceptions::InvalidParameter if the requested
407 subRegion is not fully contained within the original
408 MaskedImage.
410 """
411 #
412 # This subExposure is valid
413 #
414 subBBox = lsst.geom.Box2I(lsst.geom.Point2I(40, 50),
415 lsst.geom.Extent2I(10, 10))
416 subExposure = self.exposureCrWcs.Factory(
417 self.exposureCrWcs, subBBox, afwImage.LOCAL)
419 self.checkWcs(self.exposureCrWcs, subExposure)
421 # this subRegion is not valid and should trigger an exception
422 # from the MaskedImage class and should trigger an exception
423 # from the WCS class for the MaskedImage 871034p_1_MI.
425 subRegion3 = lsst.geom.Box2I(lsst.geom.Point2I(100, 100),
426 lsst.geom.Extent2I(10, 10))
428 def getSubRegion():
429 self.exposureCrWcs.Factory(
430 self.exposureCrWcs, subRegion3, afwImage.LOCAL)
432 self.assertRaises(pexExcept.LengthError, getSubRegion)
434 # this subRegion is not valid and should trigger an exception
435 # from the MaskedImage class only for the MaskedImage small_MI.
436 # small_MI (cols, rows) = (256, 256)
438 subRegion4 = lsst.geom.Box2I(lsst.geom.Point2I(250, 250),
439 lsst.geom.Extent2I(10, 10))
441 def getSubRegion():
442 self.exposureCrWcs.Factory(
443 self.exposureCrWcs, subRegion4, afwImage.LOCAL)
445 self.assertRaises(pexExcept.LengthError, getSubRegion)
447 # check the sub- and parent- exposures are using the same Wcs
448 # transformation
449 subBBox = lsst.geom.Box2I(lsst.geom.Point2I(40, 50),
450 lsst.geom.Extent2I(10, 10))
451 subExposure = self.exposureCrWcs.Factory(
452 self.exposureCrWcs, subBBox, afwImage.LOCAL)
453 parentSkyPos = self.exposureCrWcs.getWcs().pixelToSky(0, 0)
455 subExpSkyPos = subExposure.getWcs().pixelToSky(0, 0)
457 self.assertSpherePointsAlmostEqual(parentSkyPos, subExpSkyPos, msg="Wcs in sub image has changed")
459 def testReadWriteFits(self):
460 """Test readFits and writeFits.
461 """
462 # This should pass without an exception
463 mainExposure = afwImage.ExposureF(inFilePathSmall)
464 mainExposure.setDetector(self.detector)
466 subBBox = lsst.geom.Box2I(lsst.geom.Point2I(10, 10),
467 lsst.geom.Extent2I(40, 50))
468 subExposure = mainExposure.Factory(
469 mainExposure, subBBox, afwImage.LOCAL)
470 self.checkWcs(mainExposure, subExposure)
471 det = subExposure.getDetector()
472 self.assertTrue(det)
474 subExposure = afwImage.ExposureF(
475 inFilePathSmall, subBBox, afwImage.LOCAL)
477 self.checkWcs(mainExposure, subExposure)
479 # This should throw an exception
480 def getExposure():
481 afwImage.ExposureF(inFilePathSmallImage)
483 self.assertRaises(FitsError, getExposure)
485 mainExposure.setPsf(self.psf)
487 # Make sure we can write without an exception
488 photoCalib = afwImage.PhotoCalib(1e-10, 1e-12)
489 mainExposure.setPhotoCalib(photoCalib)
491 mainInfo = mainExposure.getInfo()
492 for key, value in self.extras.items():
493 mainInfo.setComponent(key, value)
495 with lsst.utils.tests.getTempFilePath(".fits") as tmpFile:
496 mainExposure.writeFits(tmpFile)
498 readExposure = type(mainExposure)(tmpFile)
500 #
501 # Check the round-tripping
502 #
503 self.assertEqual(mainExposure.getFilter().getName(),
504 readExposure.getFilter().getName())
505 self.assertIsNotNone(mainExposure.getFilterLabel())
506 self.assertEqual(mainExposure.getFilterLabel(),
507 readExposure.getFilterLabel())
509 self.assertEqual(photoCalib, readExposure.getPhotoCalib())
511 readInfo = readExposure.getInfo()
512 for key, value in self.extras.items():
513 self.assertEqual(value, readInfo.getComponent(key))
515 psf = readExposure.getPsf()
516 self.assertIsNotNone(psf)
517 self.assertEqual(psf, self.psf)
518 # check psf property getter
519 self.assertEqual(readExposure.psf, self.psf)
521 def checkWcs(self, parentExposure, subExposure):
522 """Compare WCS at corner points of a sub-exposure and its parent exposure
523 By using the function indexToPosition, we should be able to convert the indices
524 (of the four corners (of the sub-exposure)) to positions and use the wcs
525 to get the same sky coordinates for each.
526 """
527 subMI = subExposure.getMaskedImage()
528 subDim = subMI.getDimensions()
530 # Note: pixel positions must be computed relative to XY0 when working
531 # with WCS
532 mainWcs = parentExposure.getWcs()
533 subWcs = subExposure.getWcs()
535 for xSubInd in (0, subDim.getX()-1):
536 for ySubInd in (0, subDim.getY()-1):
537 self.assertSpherePointsAlmostEqual(
538 mainWcs.pixelToSky(
539 afwImage.indexToPosition(xSubInd),
540 afwImage.indexToPosition(ySubInd),
541 ),
542 subWcs.pixelToSky(
543 afwImage.indexToPosition(xSubInd),
544 afwImage.indexToPosition(ySubInd),
545 ))
547 def cmpExposure(self, e1, e2):
548 self.assertEqual(e1.getDetector().getName(),
549 e2.getDetector().getName())
550 self.assertEqual(e1.getDetector().getSerial(),
551 e2.getDetector().getSerial())
552 self.assertEqual(e1.getFilter().getName(), e2.getFilter().getName())
553 self.assertEqual(e1.getFilterLabel(), e2.getFilterLabel())
554 xy = lsst.geom.Point2D(0, 0)
555 self.assertEqual(e1.getWcs().pixelToSky(xy)[0],
556 e2.getWcs().pixelToSky(xy)[0])
557 self.assertEqual(e1.getPhotoCalib(), e2.getPhotoCalib())
558 # check PSF identity
559 if not e1.getPsf():
560 self.assertFalse(e2.getPsf())
561 else:
562 self.assertEqual(e1.getPsf(), e2.getPsf())
563 # Check extra components
564 i1 = e1.getInfo()
565 i2 = e2.getInfo()
566 for key in self.extras:
567 self.assertEqual(i1.hasComponent(key), i2.hasComponent(key))
568 if i1.hasComponent(key):
569 self.assertEqual(i1.getComponent(key), i2.getComponent(key))
571 def testCopyExposure(self):
572 """Copy an Exposure (maybe changing type)"""
574 exposureU = afwImage.ExposureU(inFilePathSmall, allowUnsafe=True)
575 exposureU.setWcs(self.wcs)
576 exposureU.setDetector(self.detector)
577 exposureU.setFilter(afwImage.Filter("g"))
578 exposureU.setFilterLabel(afwImage.FilterLabel(band="g"))
579 exposureU.setPsf(DummyPsf(4.0))
580 infoU = exposureU.getInfo()
581 for key, value in self.extras.items():
582 infoU.setComponent(key, value)
584 exposureF = exposureU.convertF()
585 self.cmpExposure(exposureF, exposureU)
587 nexp = exposureF.Factory(exposureF, False)
588 self.cmpExposure(exposureF, nexp)
590 # Ensure that the copy was deep.
591 # (actually this test is invalid since getDetector() returns a shared_ptr)
592 # cen0 = exposureU.getDetector().getCenterPixel()
593 # x0,y0 = cen0
594 # det = exposureF.getDetector()
595 # det.setCenterPixel(lsst.geom.Point2D(999.0, 437.8))
596 # self.assertEqual(exposureU.getDetector().getCenterPixel()[0], x0)
597 # self.assertEqual(exposureU.getDetector().getCenterPixel()[1], y0)
599 def testDeepCopyData(self):
600 """Make sure a deep copy of an Exposure has its own data (ticket #2625)
601 """
602 exp = afwImage.ExposureF(6, 7)
603 mi = exp.getMaskedImage()
604 mi.getImage().set(100)
605 mi.getMask().set(5)
606 mi.getVariance().set(200)
608 expCopy = exp.clone()
609 miCopy = expCopy.getMaskedImage()
610 miCopy.getImage().set(-50)
611 miCopy.getMask().set(2)
612 miCopy.getVariance().set(175)
614 self.assertFloatsAlmostEqual(miCopy.getImage().getArray(), -50)
615 self.assertTrue(np.all(miCopy.getMask().getArray() == 2))
616 self.assertFloatsAlmostEqual(miCopy.getVariance().getArray(), 175)
618 self.assertFloatsAlmostEqual(mi.getImage().getArray(), 100)
619 self.assertTrue(np.all(mi.getMask().getArray() == 5))
620 self.assertFloatsAlmostEqual(mi.getVariance().getArray(), 200)
622 def testDeepCopySubData(self):
623 """Make sure a deep copy of a subregion of an Exposure has its own data (ticket #2625)
624 """
625 exp = afwImage.ExposureF(6, 7)
626 mi = exp.getMaskedImage()
627 mi.getImage().set(100)
628 mi.getMask().set(5)
629 mi.getVariance().set(200)
631 bbox = lsst.geom.Box2I(lsst.geom.Point2I(1, 0), lsst.geom.Extent2I(5, 4))
632 expCopy = exp.Factory(exp, bbox, afwImage.PARENT, True)
633 miCopy = expCopy.getMaskedImage()
634 miCopy.getImage().set(-50)
635 miCopy.getMask().set(2)
636 miCopy.getVariance().set(175)
638 self.assertFloatsAlmostEqual(miCopy.getImage().getArray(), -50)
639 self.assertTrue(np.all(miCopy.getMask().getArray() == 2))
640 self.assertFloatsAlmostEqual(miCopy.getVariance().getArray(), 175)
642 self.assertFloatsAlmostEqual(mi.getImage().getArray(), 100)
643 self.assertTrue(np.all(mi.getMask().getArray() == 5))
644 self.assertFloatsAlmostEqual(mi.getVariance().getArray(), 200)
646 def testDeepCopyMetadata(self):
647 """Make sure a deep copy of an Exposure has a deep copy of metadata (ticket #2568)
648 """
649 exp = afwImage.ExposureF(10, 10)
650 expMeta = exp.getMetadata()
651 expMeta.set("foo", 5)
652 expCopy = exp.clone()
653 expCopyMeta = expCopy.getMetadata()
654 expCopyMeta.set("foo", 6)
655 self.assertEqual(expCopyMeta.getScalar("foo"), 6)
656 # this will fail if the bug is present
657 self.assertEqual(expMeta.getScalar("foo"), 5)
659 def testDeepCopySubMetadata(self):
660 """Make sure a deep copy of a subregion of an Exposure has a deep copy of metadata (ticket #2568)
661 """
662 exp = afwImage.ExposureF(10, 10)
663 expMeta = exp.getMetadata()
664 expMeta.set("foo", 5)
665 bbox = lsst.geom.Box2I(lsst.geom.Point2I(1, 0), lsst.geom.Extent2I(5, 5))
666 expCopy = exp.Factory(exp, bbox, afwImage.PARENT, True)
667 expCopyMeta = expCopy.getMetadata()
668 expCopyMeta.set("foo", 6)
669 self.assertEqual(expCopyMeta.getScalar("foo"), 6)
670 # this will fail if the bug is present
671 self.assertEqual(expMeta.getScalar("foo"), 5)
673 def testMakeExposureLeaks(self):
674 """Test for memory leaks in makeExposure (the test is in lsst.utils.tests.MemoryTestCase)"""
675 afwImage.makeMaskedImage(afwImage.ImageU(lsst.geom.Extent2I(10, 20)))
676 afwImage.makeExposure(afwImage.makeMaskedImage(
677 afwImage.ImageU(lsst.geom.Extent2I(10, 20))))
679 def testImageSlices(self):
680 """Test image slicing, which generate sub-images using Box2I under the covers"""
681 exp = afwImage.ExposureF(10, 20)
682 mi = exp.getMaskedImage()
683 mi.image[9, 19] = 10
684 # N.b. Exposures don't support setting/getting the pixels so can't
685 # replicate e.g. Image's slice tests
686 sexp = exp[1:4, 6:10]
687 self.assertEqual(sexp.getDimensions(), lsst.geom.ExtentI(3, 4))
688 sexp = exp[:, -3:, afwImage.LOCAL]
689 self.assertEqual(sexp.getDimensions(),
690 lsst.geom.ExtentI(exp.getWidth(), 3))
691 self.assertEqual(sexp.maskedImage[-1, -1, afwImage.LOCAL],
692 exp.maskedImage[-1, -1, afwImage.LOCAL])
694 def testConversionToScalar(self):
695 """Test that even 1-pixel Exposures can't be converted to scalars"""
696 im = afwImage.ExposureF(10, 20)
698 # only single pixel images may be converted
699 self.assertRaises(TypeError, float, im)
700 # actually, can't convert (img, msk, var) to scalar
701 self.assertRaises(TypeError, float, im[0, 0])
703 def testReadMetadata(self):
704 with lsst.utils.tests.getTempFilePath(".fits") as tmpFile:
705 self.exposureCrWcs.getMetadata().set("FRAZZLE", True)
706 # This will write the main metadata (inc. FRAZZLE) to the primary HDU, and the
707 # WCS to subsequent HDUs, along with INHERIT=T.
708 self.exposureCrWcs.writeFits(tmpFile)
709 # This should read the first non-empty HDU (i.e. it skips the primary), but
710 # goes back and reads it if it finds INHERIT=T. That should let us read
711 # frazzle and the Wcs from the PropertySet returned by
712 # testReadMetadata.
713 md = readMetadata(tmpFile)
714 wcs = afwGeom.makeSkyWcs(md, False)
715 self.assertPairsAlmostEqual(wcs.getPixelOrigin(), self.wcs.getPixelOrigin())
716 self.assertSpherePointsAlmostEqual(wcs.getSkyOrigin(), self.wcs.getSkyOrigin())
717 assert_allclose(wcs.getCdMatrix(), self.wcs.getCdMatrix(), atol=1e-10)
718 frazzle = md.getScalar("FRAZZLE")
719 self.assertTrue(frazzle)
721 def testArchiveKeys(self):
722 with lsst.utils.tests.getTempFilePath(".fits") as tmpFile:
723 exposure1 = afwImage.ExposureF(100, 100, self.wcs)
724 exposure1.setPsf(self.psf)
725 exposure1.writeFits(tmpFile)
726 exposure2 = afwImage.ExposureF(tmpFile)
727 self.assertFalse(exposure2.getMetadata().exists("AR_ID"))
728 self.assertFalse(exposure2.getMetadata().exists("PSF_ID"))
729 self.assertFalse(exposure2.getMetadata().exists("WCS_ID"))
731 def testTicket2861(self):
732 with lsst.utils.tests.getTempFilePath(".fits") as tmpFile:
733 exposure1 = afwImage.ExposureF(100, 100, self.wcs)
734 exposure1.setPsf(self.psf)
735 schema = afwTable.ExposureTable.makeMinimalSchema()
736 coaddInputs = afwImage.CoaddInputs(schema, schema)
737 exposure1.getInfo().setCoaddInputs(coaddInputs)
738 exposure2 = afwImage.ExposureF(exposure1, True)
739 self.assertIsNotNone(exposure2.getInfo().getCoaddInputs())
740 exposure2.writeFits(tmpFile)
741 exposure3 = afwImage.ExposureF(tmpFile)
742 self.assertIsNotNone(exposure3.getInfo().getCoaddInputs())
744 def testGetCutout(self):
745 wcs = self.smallExposure.getWcs()
747 dimensions = [lsst.geom.Extent2I(100, 50), lsst.geom.Extent2I(15, 15), lsst.geom.Extent2I(0, 10),
748 lsst.geom.Extent2I(25, 30), lsst.geom.Extent2I(15, -5),
749 2*self.smallExposure.getDimensions()]
750 locations = [("center", self._getExposureCenter(self.smallExposure)),
751 ("edge", wcs.pixelToSky(lsst.geom.Point2D(0, 0))),
752 ("rounding test", wcs.pixelToSky(lsst.geom.Point2D(0.2, 0.7))),
753 ("just inside", wcs.pixelToSky(lsst.geom.Point2D(-0.5 + 1e-4, -0.5 + 1e-4))),
754 ("just outside", wcs.pixelToSky(lsst.geom.Point2D(-0.5 - 1e-4, -0.5 - 1e-4))),
755 ("outside", wcs.pixelToSky(lsst.geom.Point2D(-1000, -1000)))]
756 for cutoutSize in dimensions:
757 for label, cutoutCenter in locations:
758 msg = 'Cutout size = %s, location = %s' % (cutoutSize, label)
759 if "outside" not in label and all(cutoutSize.gt(0)):
760 cutout = self.smallExposure.getCutout(cutoutCenter, cutoutSize)
761 centerInPixels = wcs.skyToPixel(cutoutCenter)
762 precision = (1 + 1e-4)*np.sqrt(0.5)*wcs.getPixelScale(centerInPixels)
763 self._checkCutoutProperties(cutout, cutoutSize, cutoutCenter, precision, msg)
764 self._checkCutoutPixels(
765 cutout,
766 self._getValidCorners(self.smallExposure.getBBox(), cutout.getBBox()),
767 msg)
769 # Need a valid WCS
770 with self.assertRaises(pexExcept.LogicError, msg=msg):
771 self.exposureMiOnly.getCutout(cutoutCenter, cutoutSize)
772 else:
773 with self.assertRaises(pexExcept.InvalidParameterError, msg=msg):
774 self.smallExposure.getCutout(cutoutCenter, cutoutSize)
776 def _checkCutoutProperties(self, cutout, size, center, precision, msg):
777 """Test whether a cutout has the desired size and position.
779 Parameters
780 ----------
781 cutout : `lsst.afw.image.Exposure`
782 The cutout to test.
783 size : `lsst.geom.Extent2I`
784 The expected dimensions of ``cutout``.
785 center : `lsst.geom.SpherePoint`
786 The expected center of ``cutout``.
787 precision : `lsst.geom.Angle`
788 The precision to which ``center`` must match.
789 msg : `str`
790 An error message suffix describing test parameters.
791 """
792 newCenter = self._getExposureCenter(cutout)
793 self.assertIsNotNone(cutout, msg=msg)
794 self.assertSpherePointsAlmostEqual(newCenter, center, maxSep=precision, msg=msg)
795 self.assertEqual(cutout.getWidth(), size[0], msg=msg)
796 self.assertEqual(cutout.getHeight(), size[1], msg=msg)
798 def _checkCutoutPixels(self, cutout, validCorners, msg):
799 """Test whether a cutout has valid/empty pixels where expected.
801 Parameters
802 ----------
803 cutout : `lsst.afw.image.Exposure`
804 The cutout to test.
805 validCorners : iterable of `lsst.geom.Point2I`
806 The corners of ``cutout`` that should be drawn from the original image.
807 msg : `str`
808 An error message suffix describing test parameters.
809 """
810 mask = cutout.getMaskedImage().getMask()
811 edgeMask = mask.getPlaneBitMask("NO_DATA")
813 for corner in cutout.getBBox().getCorners():
814 maskBitsSet = mask[corner] & edgeMask
815 if corner in validCorners:
816 self.assertEqual(maskBitsSet, 0, msg=msg)
817 else:
818 self.assertEqual(maskBitsSet, edgeMask, msg=msg)
820 def _getExposureCenter(self, exposure):
821 """Return the sky coordinates of an Exposure's center.
823 Parameters
824 ----------
825 exposure : `lsst.afw.image.Exposure`
826 The image whose center is desired.
828 Returns
829 -------
830 center : `lsst.geom.SpherePoint`
831 The position at the center of ``exposure``.
832 """
833 return exposure.getWcs().pixelToSky(lsst.geom.Box2D(exposure.getBBox()).getCenter())
835 def _getValidCorners(self, imageBox, cutoutBox):
836 """Return the corners of a cutout that are constrained by the original image.
838 Parameters
839 ----------
840 imageBox: `lsst.geom.Extent2I`
841 The bounding box of the original image.
842 cutoutBox : `lsst.geom.Box2I`
843 The bounding box of the cutout.
845 Returns
846 -------
847 corners : iterable of `lsst.geom.Point2I`
848 The corners that are drawn from the original image.
849 """
850 return [corner for corner in cutoutBox.getCorners() if corner in imageBox]
853class ExposureInfoTestCase(lsst.utils.tests.TestCase):
854 def setUp(self):
855 super().setUp()
857 afwImage.Filter.reset()
858 afwImage.FilterProperty.reset()
859 defineFilter("g", 470.0)
861 self.wcs = afwGeom.makeSkyWcs(lsst.geom.Point2D(0.0, 0.0),
862 lsst.geom.SpherePoint(2.0, 34.0, lsst.geom.degrees),
863 np.identity(2),
864 )
865 self.photoCalib = afwImage.PhotoCalib(1.5)
866 self.psf = DummyPsf(2.0)
867 self.detector = DetectorWrapper().detector
868 self.summaryStats = afwImage.ExposureSummaryStats(ra=100.0)
869 self.polygon = afwGeom.Polygon(lsst.geom.Box2D(lsst.geom.Point2D(0.0, 0.0),
870 lsst.geom.Point2D(25.0, 20.0)))
871 self.coaddInputs = afwImage.CoaddInputs()
872 self.apCorrMap = afwImage.ApCorrMap()
873 self.transmissionCurve = afwImage.TransmissionCurve.makeIdentity()
875 self.exposureInfo = afwImage.ExposureInfo()
876 gFilter = afwImage.Filter("g")
877 gFilterLabel = afwImage.FilterLabel(band="g")
878 self.exposureInfo.setFilter(gFilter)
879 self.exposureInfo.setFilterLabel(gFilterLabel)
881 def _checkAlias(self, exposureInfo, key, value, has, get):
882 self.assertFalse(has())
883 self.assertFalse(exposureInfo.hasComponent(key))
884 self.assertIsNone(get())
885 self.assertIsNone(exposureInfo.getComponent(key))
887 self.exposureInfo.setComponent(key, value)
888 self.assertTrue(has())
889 self.assertTrue(exposureInfo.hasComponent(key))
890 self.assertIsNotNone(get())
891 self.assertIsNotNone(exposureInfo.getComponent(key))
892 self.assertEqual(get(), value)
893 self.assertEqual(exposureInfo.getComponent(key), value)
895 self.exposureInfo.removeComponent(key)
896 self.assertFalse(has())
897 self.assertFalse(exposureInfo.hasComponent(key))
898 self.assertIsNone(get())
899 self.assertIsNone(exposureInfo.getComponent(key))
901 def testAliases(self):
902 cls = type(self.exposureInfo)
903 self._checkAlias(self.exposureInfo, cls.KEY_WCS, self.wcs,
904 self.exposureInfo.hasWcs, self.exposureInfo.getWcs)
905 self._checkAlias(self.exposureInfo, cls.KEY_PSF, self.psf,
906 self.exposureInfo.hasPsf, self.exposureInfo.getPsf)
907 self._checkAlias(self.exposureInfo, cls.KEY_PHOTO_CALIB, self.photoCalib,
908 self.exposureInfo.hasPhotoCalib, self.exposureInfo.getPhotoCalib)
909 self._checkAlias(self.exposureInfo, cls.KEY_DETECTOR, self.detector,
910 self.exposureInfo.hasDetector, self.exposureInfo.getDetector)
911 self._checkAlias(self.exposureInfo, cls.KEY_VALID_POLYGON, self.polygon,
912 self.exposureInfo.hasValidPolygon, self.exposureInfo.getValidPolygon)
913 self._checkAlias(self.exposureInfo, cls.KEY_COADD_INPUTS, self.coaddInputs,
914 self.exposureInfo.hasCoaddInputs, self.exposureInfo.getCoaddInputs)
915 self._checkAlias(self.exposureInfo, cls.KEY_AP_CORR_MAP, self.apCorrMap,
916 self.exposureInfo.hasApCorrMap, self.exposureInfo.getApCorrMap)
917 self._checkAlias(self.exposureInfo, cls.KEY_TRANSMISSION_CURVE, self.transmissionCurve,
918 self.exposureInfo.hasTransmissionCurve, self.exposureInfo.getTransmissionCurve)
919 self._checkAlias(self.exposureInfo, cls.KEY_SUMMARY_STATS, self.summaryStats,
920 self.exposureInfo.hasSummaryStats, self.exposureInfo.getSummaryStats)
922 def testCopy(self):
923 # Test that ExposureInfos have independently settable state
924 copy = afwImage.ExposureInfo(self.exposureInfo, True)
925 self.assertEqual(self.exposureInfo.getWcs(), copy.getWcs())
927 newWcs = afwGeom.makeSkyWcs(lsst.geom.Point2D(-23.0, 8.0),
928 lsst.geom.SpherePoint(0.0, 0.0, lsst.geom.degrees),
929 np.identity(2),
930 )
931 copy.setWcs(newWcs)
932 self.assertEqual(copy.getWcs(), newWcs)
933 self.assertNotEqual(self.exposureInfo.getWcs(), copy.getWcs())
936class ExposureNoAfwdataTestCase(lsst.utils.tests.TestCase):
937 """Tests of Exposure that don't require afwdata.
939 These tests use the trivial exposures written to ``afw/tests/data``.
940 """
941 def setUp(self):
942 self.dataDir = os.path.join(os.path.split(__file__)[0], "data")
944 # Check the values below against what was written by comparing with
945 # the code in `afw/tests/data/makeTestExposure.py`
946 nx = ny = 10
947 image = afwImage.ImageF(np.arange(nx*ny, dtype='f').reshape(nx, ny))
948 variance = afwImage.ImageF(np.ones((nx, ny), dtype='f'))
949 mask = afwImage.MaskX(nx, ny)
950 mask.array[5, 5] = 5
951 self.maskedImage = afwImage.MaskedImageF(image, mask, variance)
953 self.v0PhotoCalib = afwImage.makePhotoCalibFromCalibZeroPoint(1e6, 2e4)
954 self.v1PhotoCalib = afwImage.PhotoCalib(1e6, 2e4)
955 self.v1FilterLabel = afwImage.FilterLabel(physical="ha")
956 self.v2FilterLabel = afwImage.FilterLabel(band="N656", physical="ha")
958 def testReadUnversioned(self):
959 """Test that we can read an unversioned (implicit verison 0) file.
960 """
961 filename = os.path.join(self.dataDir, "exposure-noversion.fits")
962 exposure = afwImage.ExposureF.readFits(filename)
964 self.assertMaskedImagesEqual(exposure.maskedImage, self.maskedImage)
966 self.assertEqual(exposure.getPhotoCalib(), self.v0PhotoCalib)
967 self.assertEqual(exposure.getFilterLabel(), self.v1FilterLabel)
969 def testReadVersion0(self):
970 """Test that we can read a version 0 file.
971 This file should be identical to the unversioned one, except that it
972 is marked as ExposureInfo version 0 in the header.
973 """
974 filename = os.path.join(self.dataDir, "exposure-version-0.fits")
975 exposure = afwImage.ExposureF.readFits(filename)
977 self.assertMaskedImagesEqual(exposure.maskedImage, self.maskedImage)
979 self.assertEqual(exposure.getPhotoCalib(), self.v0PhotoCalib)
980 self.assertEqual(exposure.getFilterLabel(), self.v1FilterLabel)
982 # Check that the metadata reader parses the file correctly
983 reader = afwImage.ExposureFitsReader(filename)
984 self.assertEqual(reader.readExposureInfo().getPhotoCalib(), self.v0PhotoCalib)
985 self.assertEqual(reader.readPhotoCalib(), self.v0PhotoCalib)
987 def testReadVersion1(self):
988 """Test that we can read a version 1 file.
989 Version 1 replaced Calib with PhotoCalib.
990 """
991 filename = os.path.join(self.dataDir, "exposure-version-1.fits")
992 exposure = afwImage.ExposureF.readFits(filename)
994 self.assertMaskedImagesEqual(exposure.maskedImage, self.maskedImage)
996 self.assertEqual(exposure.getPhotoCalib(), self.v1PhotoCalib)
997 self.assertEqual(exposure.getFilterLabel(), self.v1FilterLabel)
999 # Check that the metadata reader parses the file correctly
1000 reader = afwImage.ExposureFitsReader(filename)
1001 self.assertEqual(reader.readExposureInfo().getPhotoCalib(), self.v1PhotoCalib)
1002 self.assertEqual(reader.readPhotoCalib(), self.v1PhotoCalib)
1004 def testReadVersion2(self):
1005 """Test that we can read a version 2 file.
1006 Version 2 replaced Filter with FilterLabel.
1007 """
1008 filename = os.path.join(self.dataDir, "exposure-version-2.fits")
1009 exposure = afwImage.ExposureF.readFits(filename)
1011 self.assertMaskedImagesEqual(exposure.maskedImage, self.maskedImage)
1013 self.assertEqual(exposure.getPhotoCalib(), self.v1PhotoCalib)
1014 self.assertEqual(exposure.getFilterLabel(), self.v2FilterLabel)
1016 # Check that the metadata reader parses the file correctly
1017 reader = afwImage.ExposureFitsReader(filename)
1018 self.assertEqual(reader.readExposureInfo().getPhotoCalib(), self.v1PhotoCalib)
1019 self.assertEqual(reader.readPhotoCalib(), self.v1PhotoCalib)
1021 def testExposureSummaryExtraComponents(self):
1022 """Test that we can read an exposure summary with extra components.
1023 """
1024 testDict = {'ra': 0.0,
1025 'decl': 0.0,
1026 'nonsense': 1.0}
1027 bytes = yaml.dump(testDict, encoding='utf-8')
1028 with self.assertWarns(FutureWarning):
1029 summaryStats = lsst.afw.image.ExposureSummaryStats._read(bytes)
1031 self.assertEqual(summaryStats.ra, testDict['ra'])
1032 self.assertEqual(summaryStats.decl, testDict['decl'])
1035class MemoryTester(lsst.utils.tests.MemoryTestCase):
1036 pass
1039def setup_module(module):
1040 lsst.utils.tests.init()
1043if __name__ == "__main__": 1043 ↛ 1044line 1043 didn't jump to line 1044, because the condition on line 1043 was never true
1044 lsst.utils.tests.init()
1045 unittest.main()