Coverage for tests/test_exposure.py : 13%

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
32import lsst.utils
33import lsst.utils.tests
34import lsst.geom
35import lsst.afw.image as afwImage
36from lsst.afw.image.utils import defineFilter
37from lsst.afw.coord import Weather
38import lsst.afw.geom as afwGeom
39import lsst.afw.table as afwTable
40import lsst.pex.exceptions as pexExcept
41from lsst.afw.fits import readMetadata, FitsError
42from lsst.afw.cameraGeom.testUtils import DetectorWrapper
43from lsst.log import Log
44from testTableArchivesLib import DummyPsf
46Log.getLogger("afw.image.Mask").setLevel(Log.INFO)
48try:
49 dataDir = os.path.join(lsst.utils.getPackageDir("afwdata"), "data")
50except pexExcept.NotFoundError:
51 dataDir = None
52else:
53 InputMaskedImageName = "871034p_1_MI.fits"
54 InputMaskedImageNameSmall = "small_MI.fits"
55 InputImageNameSmall = "small"
56 OutputMaskedImageName = "871034p_1_MInew.fits"
58 currDir = os.path.abspath(os.path.dirname(__file__))
59 inFilePath = os.path.join(dataDir, InputMaskedImageName)
60 inFilePathSmall = os.path.join(dataDir, InputMaskedImageNameSmall)
61 inFilePathSmallImage = os.path.join(dataDir, InputImageNameSmall)
64@unittest.skipIf(dataDir is None, "afwdata not setup")
65class ExposureTestCase(lsst.utils.tests.TestCase):
66 """
67 A test case for the Exposure Class
68 """
70 def setUp(self):
71 maskedImage = afwImage.MaskedImageF(inFilePathSmall)
72 maskedImageMD = readMetadata(inFilePathSmall)
74 self.smallExposure = afwImage.ExposureF(inFilePathSmall)
75 self.width = maskedImage.getWidth()
76 self.height = maskedImage.getHeight()
77 self.wcs = afwGeom.makeSkyWcs(maskedImageMD, False)
78 self.md = maskedImageMD
79 self.psf = DummyPsf(2.0)
80 self.detector = DetectorWrapper().detector
81 self.extras = {"MISC": DummyPsf(3.5)}
83 self.exposureBlank = afwImage.ExposureF()
84 self.exposureMiOnly = afwImage.makeExposure(maskedImage)
85 self.exposureMiWcs = afwImage.makeExposure(maskedImage, self.wcs)
86 # n.b. the (100, 100, ...) form
87 self.exposureCrWcs = afwImage.ExposureF(100, 100, self.wcs)
88 # test with ExtentI(100, 100) too
89 self.exposureCrOnly = afwImage.ExposureF(lsst.geom.ExtentI(100, 100))
91 afwImage.Filter.reset()
92 afwImage.FilterProperty.reset()
94 defineFilter("g", 470.0)
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())
157 def testProperties(self):
158 self.assertMaskedImagesEqual(self.exposureMiOnly.maskedImage,
159 self.exposureMiOnly.getMaskedImage())
160 mi2 = afwImage.MaskedImageF(self.exposureMiOnly.getDimensions())
161 mi2.image.array[:] = 5.0
162 mi2.variance.array[:] = 3.0
163 mi2.mask.array[:] = 0x1
164 self.exposureMiOnly.maskedImage = mi2
165 self.assertMaskedImagesEqual(self.exposureMiOnly.maskedImage, mi2)
166 self.assertImagesEqual(self.exposureMiOnly.image,
167 self.exposureMiOnly.maskedImage.image)
169 image3 = afwImage.ImageF(self.exposureMiOnly.getDimensions())
170 image3.array[:] = 3.0
171 self.exposureMiOnly.image = image3
172 self.assertImagesEqual(self.exposureMiOnly.image, image3)
174 mask3 = afwImage.MaskX(self.exposureMiOnly.getDimensions())
175 mask3.array[:] = 0x2
176 self.exposureMiOnly.mask = mask3
177 self.assertMasksEqual(self.exposureMiOnly.mask, mask3)
179 var3 = afwImage.ImageF(self.exposureMiOnly.getDimensions())
180 var3.array[:] = 2.0
181 self.exposureMiOnly.variance = var3
182 self.assertImagesEqual(self.exposureMiOnly.variance, var3)
184 def testGetWcs(self):
185 """
186 Test if a WCS can be obtained from each Exposure created with
187 a WCS.
189 Test that appropriate exceptions are thrown if a WCS is
190 requested from an Exposure that was not created with a WCS.
191 Python turns the pex::exceptions in the Exposure and
192 MaskedImage classes into IndexErrors.
194 The exposureBlank, exposureMiOnly, and exposureCrOnly
195 Exposures should throw a lsst::pex::exceptions::NotFound.
196 """
198 self.assertFalse(self.exposureBlank.getWcs())
199 self.assertFalse(self.exposureMiOnly.getWcs())
201 # These two should pass
202 self.exposureMiWcs.getWcs()
203 self.exposureCrWcs.getWcs()
205 self.assertFalse(self.exposureCrOnly.getWcs())
207 def testExposureInfoConstructor(self):
208 """Test the Exposure(maskedImage, exposureInfo) constructor"""
209 exposureInfo = afwImage.ExposureInfo()
210 exposureInfo.setWcs(self.wcs)
211 exposureInfo.setDetector(self.detector)
212 gFilter = afwImage.Filter("g")
213 exposureInfo.setFilter(gFilter)
214 maskedImage = afwImage.MaskedImageF(inFilePathSmall)
215 exposure = afwImage.ExposureF(maskedImage, exposureInfo)
217 self.assertTrue(exposure.hasWcs())
218 self.assertEqual(exposure.getWcs().getPixelOrigin(),
219 self.wcs.getPixelOrigin())
220 self.assertEqual(exposure.getDetector().getName(),
221 self.detector.getName())
222 self.assertEqual(exposure.getDetector().getSerial(),
223 self.detector.getSerial())
224 self.assertEqual(exposure.getFilter(), gFilter)
226 self.assertTrue(exposure.getInfo().hasWcs())
227 self.assertEqual(exposure.getInfo().getWcs().getPixelOrigin(),
228 self.wcs.getPixelOrigin())
229 self.assertEqual(exposure.getInfo().getDetector().getName(),
230 self.detector.getName())
231 self.assertEqual(exposure.getInfo().getDetector().getSerial(),
232 self.detector.getSerial())
233 self.assertEqual(exposure.getInfo().getFilter(), gFilter)
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 gFilter = afwImage.Filter("g")
264 exposureInfo.setFilter(gFilter)
265 maskedImage = afwImage.MaskedImageF(inFilePathSmall)
266 exposure = afwImage.ExposureF(maskedImage)
267 self.assertFalse(exposure.hasWcs())
269 exposure.setInfo(exposureInfo)
271 self.assertTrue(exposure.hasWcs())
272 self.assertEqual(exposure.getWcs().getPixelOrigin(),
273 self.wcs.getPixelOrigin())
274 self.assertEqual(exposure.getDetector().getName(),
275 self.detector.getName())
276 self.assertEqual(exposure.getDetector().getSerial(),
277 self.detector.getSerial())
278 self.assertEqual(exposure.getFilter(), gFilter)
280 def testVisitInfoFitsPersistence(self):
281 """Test saving an exposure to FITS and reading it back in preserves (some) VisitInfo fields"""
282 exposureId = 5
283 exposureTime = 12.3
284 boresightRotAngle = 45.6 * lsst.geom.degrees
285 weather = Weather(1.1, 2.2, 0.3)
286 visitInfo = afwImage.VisitInfo(
287 exposureId=exposureId,
288 exposureTime=exposureTime,
289 boresightRotAngle=boresightRotAngle,
290 weather=weather,
291 )
292 photoCalib = afwImage.PhotoCalib(3.4, 5.6)
293 exposureInfo = afwImage.ExposureInfo()
294 exposureInfo.setVisitInfo(visitInfo)
295 exposureInfo.setPhotoCalib(photoCalib)
296 exposureInfo.setDetector(self.detector)
297 gFilter = afwImage.Filter("g")
298 exposureInfo.setFilter(gFilter)
299 maskedImage = afwImage.MaskedImageF(inFilePathSmall)
300 exposure = afwImage.ExposureF(maskedImage, exposureInfo)
301 with lsst.utils.tests.getTempFilePath(".fits") as tmpFile:
302 exposure.writeFits(tmpFile)
303 rtExposure = afwImage.ExposureF(tmpFile)
304 rtVisitInfo = rtExposure.getInfo().getVisitInfo()
305 self.assertEqual(rtVisitInfo.getWeather(), weather)
306 self.assertEqual(rtExposure.getPhotoCalib(), photoCalib)
307 self.assertEqual(rtExposure.getFilter(), gFilter)
309 def testSetMembers(self):
310 """
311 Test that the MaskedImage and the WCS of an Exposure can be set.
312 """
313 exposure = afwImage.ExposureF()
315 maskedImage = afwImage.MaskedImageF(inFilePathSmall)
316 exposure.setMaskedImage(maskedImage)
317 exposure.setWcs(self.wcs)
318 exposure.setDetector(self.detector)
319 exposure.setFilter(afwImage.Filter("g"))
321 self.assertEqual(exposure.getDetector().getName(),
322 self.detector.getName())
323 self.assertEqual(exposure.getDetector().getSerial(),
324 self.detector.getSerial())
325 self.assertEqual(exposure.getFilter().getName(), "g")
326 self.assertEqual(exposure.getWcs(), self.wcs)
328 # The PhotoCalib tests are in test_photoCalib.py;
329 # here we just check that it's gettable and settable.
330 self.assertIsNone(exposure.getPhotoCalib())
332 photoCalib = afwImage.PhotoCalib(511.1, 44.4)
333 exposure.setPhotoCalib(photoCalib)
334 self.assertEqual(exposure.getPhotoCalib(), photoCalib)
336 # Psfs next
337 self.assertFalse(exposure.hasPsf())
338 exposure.setPsf(self.psf)
339 self.assertTrue(exposure.hasPsf())
341 exposure.setPsf(DummyPsf(1.0)) # we can reset the Psf
343 # extras next
344 info = exposure.getInfo()
345 for key, value in self.extras.items():
346 self.assertFalse(info.hasComponent(key))
347 self.assertIsNone(info.getComponent(key))
348 info.setComponent(key, value)
349 self.assertTrue(info.hasComponent(key))
350 self.assertEqual(info.getComponent(key), value)
351 info.removeComponent(key)
352 self.assertFalse(info.hasComponent(key))
354 # Test that we can set the MaskedImage and WCS of an Exposure
355 # that already has both
356 self.exposureMiWcs.setMaskedImage(maskedImage)
357 exposure.setWcs(self.wcs)
359 def testHasWcs(self):
360 """
361 Test if an Exposure has a WCS or not.
362 """
363 self.assertFalse(self.exposureBlank.hasWcs())
365 self.assertFalse(self.exposureMiOnly.hasWcs())
366 self.assertTrue(self.exposureMiWcs.hasWcs())
367 self.assertTrue(self.exposureCrWcs.hasWcs())
368 self.assertFalse(self.exposureCrOnly.hasWcs())
370 def testGetSubExposure(self):
371 """
372 Test that a subExposure of the original Exposure can be obtained.
374 The MaskedImage class should throw a
375 lsst::pex::exceptions::InvalidParameter if the requested
376 subRegion is not fully contained within the original
377 MaskedImage.
379 """
380 #
381 # This subExposure is valid
382 #
383 subBBox = lsst.geom.Box2I(lsst.geom.Point2I(40, 50),
384 lsst.geom.Extent2I(10, 10))
385 subExposure = self.exposureCrWcs.Factory(
386 self.exposureCrWcs, subBBox, afwImage.LOCAL)
388 self.checkWcs(self.exposureCrWcs, subExposure)
390 # this subRegion is not valid and should trigger an exception
391 # from the MaskedImage class and should trigger an exception
392 # from the WCS class for the MaskedImage 871034p_1_MI.
394 subRegion3 = lsst.geom.Box2I(lsst.geom.Point2I(100, 100),
395 lsst.geom.Extent2I(10, 10))
397 def getSubRegion():
398 self.exposureCrWcs.Factory(
399 self.exposureCrWcs, subRegion3, afwImage.LOCAL)
401 self.assertRaises(pexExcept.LengthError, getSubRegion)
403 # this subRegion is not valid and should trigger an exception
404 # from the MaskedImage class only for the MaskedImage small_MI.
405 # small_MI (cols, rows) = (256, 256)
407 subRegion4 = lsst.geom.Box2I(lsst.geom.Point2I(250, 250),
408 lsst.geom.Extent2I(10, 10))
410 def getSubRegion():
411 self.exposureCrWcs.Factory(
412 self.exposureCrWcs, subRegion4, afwImage.LOCAL)
414 self.assertRaises(pexExcept.LengthError, getSubRegion)
416 # check the sub- and parent- exposures are using the same Wcs
417 # transformation
418 subBBox = lsst.geom.Box2I(lsst.geom.Point2I(40, 50),
419 lsst.geom.Extent2I(10, 10))
420 subExposure = self.exposureCrWcs.Factory(
421 self.exposureCrWcs, subBBox, afwImage.LOCAL)
422 parentSkyPos = self.exposureCrWcs.getWcs().pixelToSky(0, 0)
424 subExpSkyPos = subExposure.getWcs().pixelToSky(0, 0)
426 self.assertSpherePointsAlmostEqual(parentSkyPos, subExpSkyPos, msg="Wcs in sub image has changed")
428 def testReadWriteFits(self):
429 """Test readFits and writeFits.
430 """
431 # This should pass without an exception
432 mainExposure = afwImage.ExposureF(inFilePathSmall)
433 mainExposure.setDetector(self.detector)
435 subBBox = lsst.geom.Box2I(lsst.geom.Point2I(10, 10),
436 lsst.geom.Extent2I(40, 50))
437 subExposure = mainExposure.Factory(
438 mainExposure, subBBox, afwImage.LOCAL)
439 self.checkWcs(mainExposure, subExposure)
440 det = subExposure.getDetector()
441 self.assertTrue(det)
443 subExposure = afwImage.ExposureF(
444 inFilePathSmall, subBBox, afwImage.LOCAL)
446 self.checkWcs(mainExposure, subExposure)
448 # This should throw an exception
449 def getExposure():
450 afwImage.ExposureF(inFilePathSmallImage)
452 self.assertRaises(FitsError, getExposure)
454 mainExposure.setPsf(self.psf)
456 # Make sure we can write without an exception
457 photoCalib = afwImage.PhotoCalib(1e-10, 1e-12)
458 mainExposure.setPhotoCalib(photoCalib)
460 mainInfo = mainExposure.getInfo()
461 for key, value in self.extras.items():
462 mainInfo.setComponent(key, value)
464 with lsst.utils.tests.getTempFilePath(".fits") as tmpFile:
465 mainExposure.writeFits(tmpFile)
467 readExposure = type(mainExposure)(tmpFile)
469 #
470 # Check the round-tripping
471 #
472 self.assertEqual(mainExposure.getFilter().getName(),
473 readExposure.getFilter().getName())
475 self.assertEqual(photoCalib, readExposure.getPhotoCalib())
477 readInfo = readExposure.getInfo()
478 for key, value in self.extras.items():
479 self.assertEqual(value, readInfo.getComponent(key))
481 psf = readExposure.getPsf()
482 self.assertIsNotNone(psf)
483 self.assertEqual(psf, self.psf)
485 def checkWcs(self, parentExposure, subExposure):
486 """Compare WCS at corner points of a sub-exposure and its parent exposure
487 By using the function indexToPosition, we should be able to convert the indices
488 (of the four corners (of the sub-exposure)) to positions and use the wcs
489 to get the same sky coordinates for each.
490 """
491 subMI = subExposure.getMaskedImage()
492 subDim = subMI.getDimensions()
494 # Note: pixel positions must be computed relative to XY0 when working
495 # with WCS
496 mainWcs = parentExposure.getWcs()
497 subWcs = subExposure.getWcs()
499 for xSubInd in (0, subDim.getX()-1):
500 for ySubInd in (0, subDim.getY()-1):
501 self.assertSpherePointsAlmostEqual(
502 mainWcs.pixelToSky(
503 afwImage.indexToPosition(xSubInd),
504 afwImage.indexToPosition(ySubInd),
505 ),
506 subWcs.pixelToSky(
507 afwImage.indexToPosition(xSubInd),
508 afwImage.indexToPosition(ySubInd),
509 ))
511 def cmpExposure(self, e1, e2):
512 self.assertEqual(e1.getDetector().getName(),
513 e2.getDetector().getName())
514 self.assertEqual(e1.getDetector().getSerial(),
515 e2.getDetector().getSerial())
516 self.assertEqual(e1.getFilter().getName(), e2.getFilter().getName())
517 xy = lsst.geom.Point2D(0, 0)
518 self.assertEqual(e1.getWcs().pixelToSky(xy)[0],
519 e2.getWcs().pixelToSky(xy)[0])
520 self.assertEqual(e1.getPhotoCalib(), e2.getPhotoCalib())
521 # check PSF identity
522 if not e1.getPsf():
523 self.assertFalse(e2.getPsf())
524 else:
525 self.assertEqual(e1.getPsf(), e2.getPsf())
526 # Check extra components
527 i1 = e1.getInfo()
528 i2 = e2.getInfo()
529 for key in self.extras:
530 self.assertEqual(i1.hasComponent(key), i2.hasComponent(key))
531 if i1.hasComponent(key):
532 self.assertEqual(i1.getComponent(key), i2.getComponent(key))
534 def testCopyExposure(self):
535 """Copy an Exposure (maybe changing type)"""
537 exposureU = afwImage.ExposureU(inFilePathSmall, allowUnsafe=True)
538 exposureU.setWcs(self.wcs)
539 exposureU.setDetector(self.detector)
540 exposureU.setFilter(afwImage.Filter("g"))
541 exposureU.setPsf(DummyPsf(4.0))
542 infoU = exposureU.getInfo()
543 for key, value in self.extras.items():
544 infoU.setComponent(key, value)
546 exposureF = exposureU.convertF()
547 self.cmpExposure(exposureF, exposureU)
549 nexp = exposureF.Factory(exposureF, False)
550 self.cmpExposure(exposureF, nexp)
552 # Ensure that the copy was deep.
553 # (actually this test is invalid since getDetector() returns a shared_ptr)
554 # cen0 = exposureU.getDetector().getCenterPixel()
555 # x0,y0 = cen0
556 # det = exposureF.getDetector()
557 # det.setCenterPixel(lsst.geom.Point2D(999.0, 437.8))
558 # self.assertEqual(exposureU.getDetector().getCenterPixel()[0], x0)
559 # self.assertEqual(exposureU.getDetector().getCenterPixel()[1], y0)
561 def testDeepCopyData(self):
562 """Make sure a deep copy of an Exposure has its own data (ticket #2625)
563 """
564 exp = afwImage.ExposureF(6, 7)
565 mi = exp.getMaskedImage()
566 mi.getImage().set(100)
567 mi.getMask().set(5)
568 mi.getVariance().set(200)
570 expCopy = exp.clone()
571 miCopy = expCopy.getMaskedImage()
572 miCopy.getImage().set(-50)
573 miCopy.getMask().set(2)
574 miCopy.getVariance().set(175)
576 self.assertFloatsAlmostEqual(miCopy.getImage().getArray(), -50)
577 self.assertTrue(np.all(miCopy.getMask().getArray() == 2))
578 self.assertFloatsAlmostEqual(miCopy.getVariance().getArray(), 175)
580 self.assertFloatsAlmostEqual(mi.getImage().getArray(), 100)
581 self.assertTrue(np.all(mi.getMask().getArray() == 5))
582 self.assertFloatsAlmostEqual(mi.getVariance().getArray(), 200)
584 def testDeepCopySubData(self):
585 """Make sure a deep copy of a subregion of an Exposure has its own data (ticket #2625)
586 """
587 exp = afwImage.ExposureF(6, 7)
588 mi = exp.getMaskedImage()
589 mi.getImage().set(100)
590 mi.getMask().set(5)
591 mi.getVariance().set(200)
593 bbox = lsst.geom.Box2I(lsst.geom.Point2I(1, 0), lsst.geom.Extent2I(5, 4))
594 expCopy = exp.Factory(exp, bbox, afwImage.PARENT, True)
595 miCopy = expCopy.getMaskedImage()
596 miCopy.getImage().set(-50)
597 miCopy.getMask().set(2)
598 miCopy.getVariance().set(175)
600 self.assertFloatsAlmostEqual(miCopy.getImage().getArray(), -50)
601 self.assertTrue(np.all(miCopy.getMask().getArray() == 2))
602 self.assertFloatsAlmostEqual(miCopy.getVariance().getArray(), 175)
604 self.assertFloatsAlmostEqual(mi.getImage().getArray(), 100)
605 self.assertTrue(np.all(mi.getMask().getArray() == 5))
606 self.assertFloatsAlmostEqual(mi.getVariance().getArray(), 200)
608 def testDeepCopyMetadata(self):
609 """Make sure a deep copy of an Exposure has a deep copy of metadata (ticket #2568)
610 """
611 exp = afwImage.ExposureF(10, 10)
612 expMeta = exp.getMetadata()
613 expMeta.set("foo", 5)
614 expCopy = exp.clone()
615 expCopyMeta = expCopy.getMetadata()
616 expCopyMeta.set("foo", 6)
617 self.assertEqual(expCopyMeta.getScalar("foo"), 6)
618 # this will fail if the bug is present
619 self.assertEqual(expMeta.getScalar("foo"), 5)
621 def testDeepCopySubMetadata(self):
622 """Make sure a deep copy of a subregion 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 bbox = lsst.geom.Box2I(lsst.geom.Point2I(1, 0), lsst.geom.Extent2I(5, 5))
628 expCopy = exp.Factory(exp, bbox, afwImage.PARENT, True)
629 expCopyMeta = expCopy.getMetadata()
630 expCopyMeta.set("foo", 6)
631 self.assertEqual(expCopyMeta.getScalar("foo"), 6)
632 # this will fail if the bug is present
633 self.assertEqual(expMeta.getScalar("foo"), 5)
635 def testMakeExposureLeaks(self):
636 """Test for memory leaks in makeExposure (the test is in lsst.utils.tests.MemoryTestCase)"""
637 afwImage.makeMaskedImage(afwImage.ImageU(lsst.geom.Extent2I(10, 20)))
638 afwImage.makeExposure(afwImage.makeMaskedImage(
639 afwImage.ImageU(lsst.geom.Extent2I(10, 20))))
641 def testImageSlices(self):
642 """Test image slicing, which generate sub-images using Box2I under the covers"""
643 exp = afwImage.ExposureF(10, 20)
644 mi = exp.getMaskedImage()
645 mi.image[9, 19] = 10
646 # N.b. Exposures don't support setting/getting the pixels so can't
647 # replicate e.g. Image's slice tests
648 sexp = exp[1:4, 6:10]
649 self.assertEqual(sexp.getDimensions(), lsst.geom.ExtentI(3, 4))
650 sexp = exp[:, -3:, afwImage.LOCAL]
651 self.assertEqual(sexp.getDimensions(),
652 lsst.geom.ExtentI(exp.getWidth(), 3))
653 self.assertEqual(sexp.maskedImage[-1, -1, afwImage.LOCAL],
654 exp.maskedImage[-1, -1, afwImage.LOCAL])
656 def testConversionToScalar(self):
657 """Test that even 1-pixel Exposures can't be converted to scalars"""
658 im = afwImage.ExposureF(10, 20)
660 # only single pixel images may be converted
661 self.assertRaises(TypeError, float, im)
662 # actually, can't convert (img, msk, var) to scalar
663 self.assertRaises(TypeError, float, im[0, 0])
665 def testReadMetadata(self):
666 with lsst.utils.tests.getTempFilePath(".fits") as tmpFile:
667 self.exposureCrWcs.getMetadata().set("FRAZZLE", True)
668 # This will write the main metadata (inc. FRAZZLE) to the primary HDU, and the
669 # WCS to subsequent HDUs, along with INHERIT=T.
670 self.exposureCrWcs.writeFits(tmpFile)
671 # This should read the first non-empty HDU (i.e. it skips the primary), but
672 # goes back and reads it if it finds INHERIT=T. That should let us read
673 # frazzle and the Wcs from the PropertySet returned by
674 # testReadMetadata.
675 md = readMetadata(tmpFile)
676 wcs = afwGeom.makeSkyWcs(md, False)
677 self.assertPairsAlmostEqual(wcs.getPixelOrigin(), self.wcs.getPixelOrigin())
678 self.assertSpherePointsAlmostEqual(wcs.getSkyOrigin(), self.wcs.getSkyOrigin())
679 assert_allclose(wcs.getCdMatrix(), self.wcs.getCdMatrix(), atol=1e-10)
680 frazzle = md.getScalar("FRAZZLE")
681 self.assertTrue(frazzle)
683 def testArchiveKeys(self):
684 with lsst.utils.tests.getTempFilePath(".fits") as tmpFile:
685 exposure1 = afwImage.ExposureF(100, 100, self.wcs)
686 exposure1.setPsf(self.psf)
687 exposure1.writeFits(tmpFile)
688 exposure2 = afwImage.ExposureF(tmpFile)
689 self.assertFalse(exposure2.getMetadata().exists("AR_ID"))
690 self.assertFalse(exposure2.getMetadata().exists("PSF_ID"))
691 self.assertFalse(exposure2.getMetadata().exists("WCS_ID"))
693 def testTicket2861(self):
694 with lsst.utils.tests.getTempFilePath(".fits") as tmpFile:
695 exposure1 = afwImage.ExposureF(100, 100, self.wcs)
696 exposure1.setPsf(self.psf)
697 schema = afwTable.ExposureTable.makeMinimalSchema()
698 coaddInputs = afwImage.CoaddInputs(schema, schema)
699 exposure1.getInfo().setCoaddInputs(coaddInputs)
700 exposure2 = afwImage.ExposureF(exposure1, True)
701 self.assertIsNotNone(exposure2.getInfo().getCoaddInputs())
702 exposure2.writeFits(tmpFile)
703 exposure3 = afwImage.ExposureF(tmpFile)
704 self.assertIsNotNone(exposure3.getInfo().getCoaddInputs())
706 def testGetCutout(self):
707 wcs = self.smallExposure.getWcs()
709 dimensions = [lsst.geom.Extent2I(100, 50), lsst.geom.Extent2I(15, 15), lsst.geom.Extent2I(0, 10),
710 lsst.geom.Extent2I(25, 30), lsst.geom.Extent2I(15, -5),
711 2*self.smallExposure.getDimensions()]
712 locations = [("center", self._getExposureCenter(self.smallExposure)),
713 ("edge", wcs.pixelToSky(lsst.geom.Point2D(0, 0))),
714 ("rounding test", wcs.pixelToSky(lsst.geom.Point2D(0.2, 0.7))),
715 ("just inside", wcs.pixelToSky(lsst.geom.Point2D(-0.5 + 1e-4, -0.5 + 1e-4))),
716 ("just outside", wcs.pixelToSky(lsst.geom.Point2D(-0.5 - 1e-4, -0.5 - 1e-4))),
717 ("outside", wcs.pixelToSky(lsst.geom.Point2D(-1000, -1000)))]
718 for cutoutSize in dimensions:
719 for label, cutoutCenter in locations:
720 msg = 'Cutout size = %s, location = %s' % (cutoutSize, label)
721 if "outside" not in label and all(cutoutSize.gt(0)):
722 cutout = self.smallExposure.getCutout(cutoutCenter, cutoutSize)
723 centerInPixels = wcs.skyToPixel(cutoutCenter)
724 precision = (1 + 1e-4)*np.sqrt(0.5)*wcs.getPixelScale(centerInPixels)
725 self._checkCutoutProperties(cutout, cutoutSize, cutoutCenter, precision, msg)
726 self._checkCutoutPixels(
727 cutout,
728 self._getValidCorners(self.smallExposure.getBBox(), cutout.getBBox()),
729 msg)
731 # Need a valid WCS
732 with self.assertRaises(pexExcept.LogicError, msg=msg):
733 self.exposureMiOnly.getCutout(cutoutCenter, cutoutSize)
734 else:
735 with self.assertRaises(pexExcept.InvalidParameterError, msg=msg):
736 self.smallExposure.getCutout(cutoutCenter, cutoutSize)
738 def _checkCutoutProperties(self, cutout, size, center, precision, msg):
739 """Test whether a cutout has the desired size and position.
741 Parameters
742 ----------
743 cutout : `lsst.afw.image.Exposure`
744 The cutout to test.
745 size : `lsst.geom.Extent2I`
746 The expected dimensions of ``cutout``.
747 center : `lsst.geom.SpherePoint`
748 The expected center of ``cutout``.
749 precision : `lsst.geom.Angle`
750 The precision to which ``center`` must match.
751 msg : `str`
752 An error message suffix describing test parameters.
753 """
754 newCenter = self._getExposureCenter(cutout)
755 self.assertIsNotNone(cutout, msg=msg)
756 self.assertSpherePointsAlmostEqual(newCenter, center, maxSep=precision, msg=msg)
757 self.assertEqual(cutout.getWidth(), size[0], msg=msg)
758 self.assertEqual(cutout.getHeight(), size[1], msg=msg)
760 def _checkCutoutPixels(self, cutout, validCorners, msg):
761 """Test whether a cutout has valid/empty pixels where expected.
763 Parameters
764 ----------
765 cutout : `lsst.afw.image.Exposure`
766 The cutout to test.
767 validCorners : iterable of `lsst.geom.Point2I`
768 The corners of ``cutout`` that should be drawn from the original image.
769 msg : `str`
770 An error message suffix describing test parameters.
771 """
772 mask = cutout.getMaskedImage().getMask()
773 edgeMask = mask.getPlaneBitMask("NO_DATA")
775 for corner in cutout.getBBox().getCorners():
776 maskBitsSet = mask[corner] & edgeMask
777 if corner in validCorners:
778 self.assertEqual(maskBitsSet, 0, msg=msg)
779 else:
780 self.assertEqual(maskBitsSet, edgeMask, msg=msg)
782 def _getExposureCenter(self, exposure):
783 """Return the sky coordinates of an Exposure's center.
785 Parameters
786 ----------
787 exposure : `lsst.afw.image.Exposure`
788 The image whose center is desired.
790 Returns
791 -------
792 center : `lsst.geom.SpherePoint`
793 The position at the center of ``exposure``.
794 """
795 return exposure.getWcs().pixelToSky(lsst.geom.Box2D(exposure.getBBox()).getCenter())
797 def _getValidCorners(self, imageBox, cutoutBox):
798 """Return the corners of a cutout that are constrained by the original image.
800 Parameters
801 ----------
802 imageBox: `lsst.geom.Extent2I`
803 The bounding box of the original image.
804 cutoutBox : `lsst.geom.Box2I`
805 The bounding box of the cutout.
807 Returns
808 -------
809 corners : iterable of `lsst.geom.Point2I`
810 The corners that are drawn from the original image.
811 """
812 return [corner for corner in cutoutBox.getCorners() if corner in imageBox]
815class ExposureInfoTestCase(lsst.utils.tests.TestCase):
816 def setUp(self):
817 super().setUp()
819 afwImage.Filter.reset()
820 afwImage.FilterProperty.reset()
821 defineFilter("g", 470.0)
823 self.wcs = afwGeom.makeSkyWcs(lsst.geom.Point2D(0.0, 0.0),
824 lsst.geom.SpherePoint(2.0, 34.0, lsst.geom.degrees),
825 np.identity(2),
826 )
827 self.photoCalib = afwImage.PhotoCalib(1.5)
828 self.psf = DummyPsf(2.0)
829 self.detector = DetectorWrapper().detector
830 self.polygon = afwGeom.Polygon(lsst.geom.Box2D(lsst.geom.Point2D(0.0, 0.0),
831 lsst.geom.Point2D(25.0, 20.0)))
832 self.coaddInputs = afwImage.CoaddInputs()
833 self.apCorrMap = afwImage.ApCorrMap()
834 self.transmissionCurve = afwImage.TransmissionCurve.makeIdentity()
836 self.exposureInfo = afwImage.ExposureInfo()
837 gFilter = afwImage.Filter("g")
838 self.exposureInfo.setFilter(gFilter)
840 def _checkAlias(self, exposureInfo, key, value, has, get):
841 self.assertFalse(has())
842 self.assertFalse(exposureInfo.hasComponent(key))
843 self.assertIsNone(get())
844 self.assertIsNone(exposureInfo.getComponent(key))
846 self.exposureInfo.setComponent(key, value)
847 self.assertTrue(has())
848 self.assertTrue(exposureInfo.hasComponent(key))
849 self.assertIsNotNone(get())
850 self.assertIsNotNone(exposureInfo.getComponent(key))
851 self.assertEqual(get(), value)
852 self.assertEqual(exposureInfo.getComponent(key), value)
854 self.exposureInfo.removeComponent(key)
855 self.assertFalse(has())
856 self.assertFalse(exposureInfo.hasComponent(key))
857 self.assertIsNone(get())
858 self.assertIsNone(exposureInfo.getComponent(key))
860 def testAliases(self):
861 cls = type(self.exposureInfo)
862 self._checkAlias(self.exposureInfo, cls.KEY_WCS, self.wcs,
863 self.exposureInfo.hasWcs, self.exposureInfo.getWcs)
864 self._checkAlias(self.exposureInfo, cls.KEY_PSF, self.psf,
865 self.exposureInfo.hasPsf, self.exposureInfo.getPsf)
866 self._checkAlias(self.exposureInfo, cls.KEY_PHOTO_CALIB, self.photoCalib,
867 self.exposureInfo.hasPhotoCalib, self.exposureInfo.getPhotoCalib)
868 self._checkAlias(self.exposureInfo, cls.KEY_DETECTOR, self.detector,
869 self.exposureInfo.hasDetector, self.exposureInfo.getDetector)
870 self._checkAlias(self.exposureInfo, cls.KEY_VALID_POLYGON, self.polygon,
871 self.exposureInfo.hasValidPolygon, self.exposureInfo.getValidPolygon)
872 self._checkAlias(self.exposureInfo, cls.KEY_COADD_INPUTS, self.coaddInputs,
873 self.exposureInfo.hasCoaddInputs, self.exposureInfo.getCoaddInputs)
874 self._checkAlias(self.exposureInfo, cls.KEY_AP_CORR_MAP, self.apCorrMap,
875 self.exposureInfo.hasApCorrMap, self.exposureInfo.getApCorrMap)
876 self._checkAlias(self.exposureInfo, cls.KEY_TRANSMISSION_CURVE, self.transmissionCurve,
877 self.exposureInfo.hasTransmissionCurve, self.exposureInfo.getTransmissionCurve)
879 def testCopy(self):
880 # Test that ExposureInfos have independently settable state
881 copy = afwImage.ExposureInfo(self.exposureInfo, True)
882 self.assertEqual(self.exposureInfo.getWcs(), copy.getWcs())
884 newWcs = afwGeom.makeSkyWcs(lsst.geom.Point2D(-23.0, 8.0),
885 lsst.geom.SpherePoint(0.0, 0.0, lsst.geom.degrees),
886 np.identity(2),
887 )
888 copy.setWcs(newWcs)
889 self.assertEqual(copy.getWcs(), newWcs)
890 self.assertNotEqual(self.exposureInfo.getWcs(), copy.getWcs())
893class ExposureNoAfwdataTestCase(lsst.utils.tests.TestCase):
894 """Tests of Exposure that don't require afwdata.
896 These tests use the trivial exposures written to ``afw/tests/data``.
897 """
898 def setUp(self):
899 self.dataDir = os.path.join(os.path.split(__file__)[0], "data")
901 # Check the values below against what was written by comparing with
902 # the code in `afw/tests/data/makeTestExposure.py`
903 nx = ny = 10
904 image = afwImage.ImageF(np.arange(nx*ny, dtype='f').reshape(nx, ny))
905 variance = afwImage.ImageF(np.ones((nx, ny), dtype='f'))
906 mask = afwImage.MaskX(nx, ny)
907 mask.array[5, 5] = 5
908 self.maskedImage = afwImage.MaskedImageF(image, mask, variance)
910 self.v0PhotoCalib = afwImage.makePhotoCalibFromCalibZeroPoint(1e6, 2e4)
911 self.v1PhotoCalib = afwImage.PhotoCalib(1e6, 2e4)
913 def testReadUnversioned(self):
914 """Test that we can read an unversioned (implicit verison 0) file.
915 """
916 filename = os.path.join(self.dataDir, "exposure-noversion.fits")
917 exposure = afwImage.ExposureF.readFits(filename)
919 self.assertMaskedImagesEqual(exposure.maskedImage, self.maskedImage)
921 self.assertEqual(exposure.getPhotoCalib(), self.v0PhotoCalib)
923 def testReadVersion0(self):
924 """Test that we can read an version 0 file.
925 This file should be identical to the unversioned one, except that it
926 is marked as ExposureInfo version 0 in the header.
927 """
928 filename = os.path.join(self.dataDir, "exposure-version-0.fits")
929 exposure = afwImage.ExposureF.readFits(filename)
931 self.assertMaskedImagesEqual(exposure.maskedImage, self.maskedImage)
933 self.assertEqual(exposure.getPhotoCalib(), self.v0PhotoCalib)
935 # Check that the metadata reader parses the file correctly
936 reader = afwImage.ExposureFitsReader(filename)
937 self.assertEqual(reader.readExposureInfo().getPhotoCalib(), self.v0PhotoCalib)
938 self.assertEqual(reader.readPhotoCalib(), self.v0PhotoCalib)
940 def testReadVersion1(self):
941 """Test that we can read an version 1 file.
942 Version 1 replaced Calib with PhotoCalib.
943 """
944 filename = os.path.join(self.dataDir, "exposure-version-1.fits")
945 exposure = afwImage.ExposureF.readFits(filename)
947 self.assertMaskedImagesEqual(exposure.maskedImage, self.maskedImage)
949 self.assertEqual(exposure.getPhotoCalib(), self.v1PhotoCalib)
951 # Check that the metadata reader parses the file correctly
952 reader = afwImage.ExposureFitsReader(filename)
953 self.assertEqual(reader.readExposureInfo().getPhotoCalib(), self.v1PhotoCalib)
954 self.assertEqual(reader.readPhotoCalib(), self.v1PhotoCalib)
957class MemoryTester(lsst.utils.tests.MemoryTestCase):
958 pass
961def setup_module(module):
962 lsst.utils.tests.init()
965if __name__ == "__main__": 965 ↛ 966line 965 didn't jump to line 966, because the condition on line 965 was never true
966 lsst.utils.tests.init()
967 unittest.main()