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 psf = readExposure.getPsf()
478 self.assertIsNotNone(psf)
479 self.assertEqual(psf, self.psf)
481 # FIXME: This does not pass with conda dependencies.
482 # readInfo = readExposure.getInfo()
483 # for key, value in self.extras.items():
484 # self.assertEqual(value, readInfo.getComponent(key))
486 def checkWcs(self, parentExposure, subExposure):
487 """Compare WCS at corner points of a sub-exposure and its parent exposure
488 By using the function indexToPosition, we should be able to convert the indices
489 (of the four corners (of the sub-exposure)) to positions and use the wcs
490 to get the same sky coordinates for each.
491 """
492 subMI = subExposure.getMaskedImage()
493 subDim = subMI.getDimensions()
495 # Note: pixel positions must be computed relative to XY0 when working
496 # with WCS
497 mainWcs = parentExposure.getWcs()
498 subWcs = subExposure.getWcs()
500 for xSubInd in (0, subDim.getX()-1):
501 for ySubInd in (0, subDim.getY()-1):
502 self.assertSpherePointsAlmostEqual(
503 mainWcs.pixelToSky(
504 afwImage.indexToPosition(xSubInd),
505 afwImage.indexToPosition(ySubInd),
506 ),
507 subWcs.pixelToSky(
508 afwImage.indexToPosition(xSubInd),
509 afwImage.indexToPosition(ySubInd),
510 ))
512 def cmpExposure(self, e1, e2):
513 self.assertEqual(e1.getDetector().getName(),
514 e2.getDetector().getName())
515 self.assertEqual(e1.getDetector().getSerial(),
516 e2.getDetector().getSerial())
517 self.assertEqual(e1.getFilter().getName(), e2.getFilter().getName())
518 xy = lsst.geom.Point2D(0, 0)
519 self.assertEqual(e1.getWcs().pixelToSky(xy)[0],
520 e2.getWcs().pixelToSky(xy)[0])
521 self.assertEqual(e1.getPhotoCalib(), e2.getPhotoCalib())
522 # check PSF identity
523 if not e1.getPsf():
524 self.assertFalse(e2.getPsf())
525 else:
526 self.assertEqual(e1.getPsf(), e2.getPsf())
527 # Check extra components
528 i1 = e1.getInfo()
529 i2 = e2.getInfo()
530 for key in self.extras:
531 self.assertEqual(i1.hasComponent(key), i2.hasComponent(key))
532 if i1.hasComponent(key):
533 self.assertEqual(i1.getComponent(key), i2.getComponent(key))
535 def testCopyExposure(self):
536 """Copy an Exposure (maybe changing type)"""
538 exposureU = afwImage.ExposureU(inFilePathSmall, allowUnsafe=True)
539 exposureU.setWcs(self.wcs)
540 exposureU.setDetector(self.detector)
541 exposureU.setFilter(afwImage.Filter("g"))
542 exposureU.setPsf(DummyPsf(4.0))
543 infoU = exposureU.getInfo()
544 for key, value in self.extras.items():
545 infoU.setComponent(key, value)
547 exposureF = exposureU.convertF()
548 self.cmpExposure(exposureF, exposureU)
550 nexp = exposureF.Factory(exposureF, False)
551 self.cmpExposure(exposureF, nexp)
553 # Ensure that the copy was deep.
554 # (actually this test is invalid since getDetector() returns a shared_ptr)
555 # cen0 = exposureU.getDetector().getCenterPixel()
556 # x0,y0 = cen0
557 # det = exposureF.getDetector()
558 # det.setCenterPixel(lsst.geom.Point2D(999.0, 437.8))
559 # self.assertEqual(exposureU.getDetector().getCenterPixel()[0], x0)
560 # self.assertEqual(exposureU.getDetector().getCenterPixel()[1], y0)
562 def testDeepCopyData(self):
563 """Make sure a deep copy of an Exposure has its own data (ticket #2625)
564 """
565 exp = afwImage.ExposureF(6, 7)
566 mi = exp.getMaskedImage()
567 mi.getImage().set(100)
568 mi.getMask().set(5)
569 mi.getVariance().set(200)
571 expCopy = exp.clone()
572 miCopy = expCopy.getMaskedImage()
573 miCopy.getImage().set(-50)
574 miCopy.getMask().set(2)
575 miCopy.getVariance().set(175)
577 self.assertFloatsAlmostEqual(miCopy.getImage().getArray(), -50)
578 self.assertTrue(np.all(miCopy.getMask().getArray() == 2))
579 self.assertFloatsAlmostEqual(miCopy.getVariance().getArray(), 175)
581 self.assertFloatsAlmostEqual(mi.getImage().getArray(), 100)
582 self.assertTrue(np.all(mi.getMask().getArray() == 5))
583 self.assertFloatsAlmostEqual(mi.getVariance().getArray(), 200)
585 def testDeepCopySubData(self):
586 """Make sure a deep copy of a subregion of an Exposure has its own data (ticket #2625)
587 """
588 exp = afwImage.ExposureF(6, 7)
589 mi = exp.getMaskedImage()
590 mi.getImage().set(100)
591 mi.getMask().set(5)
592 mi.getVariance().set(200)
594 bbox = lsst.geom.Box2I(lsst.geom.Point2I(1, 0), lsst.geom.Extent2I(5, 4))
595 expCopy = exp.Factory(exp, bbox, afwImage.PARENT, True)
596 miCopy = expCopy.getMaskedImage()
597 miCopy.getImage().set(-50)
598 miCopy.getMask().set(2)
599 miCopy.getVariance().set(175)
601 self.assertFloatsAlmostEqual(miCopy.getImage().getArray(), -50)
602 self.assertTrue(np.all(miCopy.getMask().getArray() == 2))
603 self.assertFloatsAlmostEqual(miCopy.getVariance().getArray(), 175)
605 self.assertFloatsAlmostEqual(mi.getImage().getArray(), 100)
606 self.assertTrue(np.all(mi.getMask().getArray() == 5))
607 self.assertFloatsAlmostEqual(mi.getVariance().getArray(), 200)
609 def testDeepCopyMetadata(self):
610 """Make sure a deep copy of an Exposure has a deep copy of metadata (ticket #2568)
611 """
612 exp = afwImage.ExposureF(10, 10)
613 expMeta = exp.getMetadata()
614 expMeta.set("foo", 5)
615 expCopy = exp.clone()
616 expCopyMeta = expCopy.getMetadata()
617 expCopyMeta.set("foo", 6)
618 self.assertEqual(expCopyMeta.getScalar("foo"), 6)
619 # this will fail if the bug is present
620 self.assertEqual(expMeta.getScalar("foo"), 5)
622 def testDeepCopySubMetadata(self):
623 """Make sure a deep copy of a subregion of an Exposure has a deep copy of metadata (ticket #2568)
624 """
625 exp = afwImage.ExposureF(10, 10)
626 expMeta = exp.getMetadata()
627 expMeta.set("foo", 5)
628 bbox = lsst.geom.Box2I(lsst.geom.Point2I(1, 0), lsst.geom.Extent2I(5, 5))
629 expCopy = exp.Factory(exp, bbox, afwImage.PARENT, True)
630 expCopyMeta = expCopy.getMetadata()
631 expCopyMeta.set("foo", 6)
632 self.assertEqual(expCopyMeta.getScalar("foo"), 6)
633 # this will fail if the bug is present
634 self.assertEqual(expMeta.getScalar("foo"), 5)
636 def testMakeExposureLeaks(self):
637 """Test for memory leaks in makeExposure (the test is in lsst.utils.tests.MemoryTestCase)"""
638 afwImage.makeMaskedImage(afwImage.ImageU(lsst.geom.Extent2I(10, 20)))
639 afwImage.makeExposure(afwImage.makeMaskedImage(
640 afwImage.ImageU(lsst.geom.Extent2I(10, 20))))
642 def testImageSlices(self):
643 """Test image slicing, which generate sub-images using Box2I under the covers"""
644 exp = afwImage.ExposureF(10, 20)
645 mi = exp.getMaskedImage()
646 mi.image[9, 19] = 10
647 # N.b. Exposures don't support setting/getting the pixels so can't
648 # replicate e.g. Image's slice tests
649 sexp = exp[1:4, 6:10]
650 self.assertEqual(sexp.getDimensions(), lsst.geom.ExtentI(3, 4))
651 sexp = exp[:, -3:, afwImage.LOCAL]
652 self.assertEqual(sexp.getDimensions(),
653 lsst.geom.ExtentI(exp.getWidth(), 3))
654 self.assertEqual(sexp.maskedImage[-1, -1, afwImage.LOCAL],
655 exp.maskedImage[-1, -1, afwImage.LOCAL])
657 def testConversionToScalar(self):
658 """Test that even 1-pixel Exposures can't be converted to scalars"""
659 im = afwImage.ExposureF(10, 20)
661 # only single pixel images may be converted
662 self.assertRaises(TypeError, float, im)
663 # actually, can't convert (img, msk, var) to scalar
664 self.assertRaises(TypeError, float, im[0, 0])
666 def testReadMetadata(self):
667 with lsst.utils.tests.getTempFilePath(".fits") as tmpFile:
668 self.exposureCrWcs.getMetadata().set("FRAZZLE", True)
669 # This will write the main metadata (inc. FRAZZLE) to the primary HDU, and the
670 # WCS to subsequent HDUs, along with INHERIT=T.
671 self.exposureCrWcs.writeFits(tmpFile)
672 # This should read the first non-empty HDU (i.e. it skips the primary), but
673 # goes back and reads it if it finds INHERIT=T. That should let us read
674 # frazzle and the Wcs from the PropertySet returned by
675 # testReadMetadata.
676 md = readMetadata(tmpFile)
677 wcs = afwGeom.makeSkyWcs(md, False)
678 self.assertPairsAlmostEqual(wcs.getPixelOrigin(), self.wcs.getPixelOrigin())
679 self.assertSpherePointsAlmostEqual(wcs.getSkyOrigin(), self.wcs.getSkyOrigin())
680 assert_allclose(wcs.getCdMatrix(), self.wcs.getCdMatrix(), atol=1e-10)
681 frazzle = md.getScalar("FRAZZLE")
682 self.assertTrue(frazzle)
684 def testArchiveKeys(self):
685 with lsst.utils.tests.getTempFilePath(".fits") as tmpFile:
686 exposure1 = afwImage.ExposureF(100, 100, self.wcs)
687 exposure1.setPsf(self.psf)
688 exposure1.writeFits(tmpFile)
689 exposure2 = afwImage.ExposureF(tmpFile)
690 self.assertFalse(exposure2.getMetadata().exists("AR_ID"))
691 self.assertFalse(exposure2.getMetadata().exists("PSF_ID"))
692 self.assertFalse(exposure2.getMetadata().exists("WCS_ID"))
694 def testTicket2861(self):
695 with lsst.utils.tests.getTempFilePath(".fits") as tmpFile:
696 exposure1 = afwImage.ExposureF(100, 100, self.wcs)
697 exposure1.setPsf(self.psf)
698 schema = afwTable.ExposureTable.makeMinimalSchema()
699 coaddInputs = afwImage.CoaddInputs(schema, schema)
700 exposure1.getInfo().setCoaddInputs(coaddInputs)
701 exposure2 = afwImage.ExposureF(exposure1, True)
702 self.assertIsNotNone(exposure2.getInfo().getCoaddInputs())
703 exposure2.writeFits(tmpFile)
704 exposure3 = afwImage.ExposureF(tmpFile)
705 self.assertIsNotNone(exposure3.getInfo().getCoaddInputs())
707 def testGetCutout(self):
708 wcs = self.smallExposure.getWcs()
710 dimensions = [lsst.geom.Extent2I(100, 50), lsst.geom.Extent2I(15, 15), lsst.geom.Extent2I(0, 10),
711 lsst.geom.Extent2I(25, 30), lsst.geom.Extent2I(15, -5),
712 2*self.smallExposure.getDimensions()]
713 locations = [("center", self._getExposureCenter(self.smallExposure)),
714 ("edge", wcs.pixelToSky(lsst.geom.Point2D(0, 0))),
715 ("rounding test", wcs.pixelToSky(lsst.geom.Point2D(0.2, 0.7))),
716 ("just inside", wcs.pixelToSky(lsst.geom.Point2D(-0.5 + 1e-4, -0.5 + 1e-4))),
717 ("just outside", wcs.pixelToSky(lsst.geom.Point2D(-0.5 - 1e-4, -0.5 - 1e-4))),
718 ("outside", wcs.pixelToSky(lsst.geom.Point2D(-1000, -1000)))]
719 for cutoutSize in dimensions:
720 for label, cutoutCenter in locations:
721 msg = 'Cutout size = %s, location = %s' % (cutoutSize, label)
722 if "outside" not in label and all(cutoutSize.gt(0)):
723 cutout = self.smallExposure.getCutout(cutoutCenter, cutoutSize)
724 centerInPixels = wcs.skyToPixel(cutoutCenter)
725 precision = (1 + 1e-4)*np.sqrt(0.5)*wcs.getPixelScale(centerInPixels)
726 self._checkCutoutProperties(cutout, cutoutSize, cutoutCenter, precision, msg)
727 self._checkCutoutPixels(
728 cutout,
729 self._getValidCorners(self.smallExposure.getBBox(), cutout.getBBox()),
730 msg)
732 # Need a valid WCS
733 with self.assertRaises(pexExcept.LogicError, msg=msg):
734 self.exposureMiOnly.getCutout(cutoutCenter, cutoutSize)
735 else:
736 with self.assertRaises(pexExcept.InvalidParameterError, msg=msg):
737 self.smallExposure.getCutout(cutoutCenter, cutoutSize)
739 def _checkCutoutProperties(self, cutout, size, center, precision, msg):
740 """Test whether a cutout has the desired size and position.
742 Parameters
743 ----------
744 cutout : `lsst.afw.image.Exposure`
745 The cutout to test.
746 size : `lsst.geom.Extent2I`
747 The expected dimensions of ``cutout``.
748 center : `lsst.geom.SpherePoint`
749 The expected center of ``cutout``.
750 precision : `lsst.geom.Angle`
751 The precision to which ``center`` must match.
752 msg : `str`
753 An error message suffix describing test parameters.
754 """
755 newCenter = self._getExposureCenter(cutout)
756 self.assertIsNotNone(cutout, msg=msg)
757 self.assertSpherePointsAlmostEqual(newCenter, center, maxSep=precision, msg=msg)
758 self.assertEqual(cutout.getWidth(), size[0], msg=msg)
759 self.assertEqual(cutout.getHeight(), size[1], msg=msg)
761 def _checkCutoutPixels(self, cutout, validCorners, msg):
762 """Test whether a cutout has valid/empty pixels where expected.
764 Parameters
765 ----------
766 cutout : `lsst.afw.image.Exposure`
767 The cutout to test.
768 validCorners : iterable of `lsst.geom.Point2I`
769 The corners of ``cutout`` that should be drawn from the original image.
770 msg : `str`
771 An error message suffix describing test parameters.
772 """
773 mask = cutout.getMaskedImage().getMask()
774 edgeMask = mask.getPlaneBitMask("NO_DATA")
776 for corner in cutout.getBBox().getCorners():
777 maskBitsSet = mask[corner] & edgeMask
778 if corner in validCorners:
779 self.assertEqual(maskBitsSet, 0, msg=msg)
780 else:
781 self.assertEqual(maskBitsSet, edgeMask, msg=msg)
783 def _getExposureCenter(self, exposure):
784 """Return the sky coordinates of an Exposure's center.
786 Parameters
787 ----------
788 exposure : `lsst.afw.image.Exposure`
789 The image whose center is desired.
791 Returns
792 -------
793 center : `lsst.geom.SpherePoint`
794 The position at the center of ``exposure``.
795 """
796 return exposure.getWcs().pixelToSky(lsst.geom.Box2D(exposure.getBBox()).getCenter())
798 def _getValidCorners(self, imageBox, cutoutBox):
799 """Return the corners of a cutout that are constrained by the original image.
801 Parameters
802 ----------
803 imageBox: `lsst.geom.Extent2I`
804 The bounding box of the original image.
805 cutoutBox : `lsst.geom.Box2I`
806 The bounding box of the cutout.
808 Returns
809 -------
810 corners : iterable of `lsst.geom.Point2I`
811 The corners that are drawn from the original image.
812 """
813 return [corner for corner in cutoutBox.getCorners() if corner in imageBox]
816class ExposureInfoTestCase(lsst.utils.tests.TestCase):
817 def setUp(self):
818 super().setUp()
820 afwImage.Filter.reset()
821 afwImage.FilterProperty.reset()
822 defineFilter("g", 470.0)
824 self.wcs = afwGeom.makeSkyWcs(lsst.geom.Point2D(0.0, 0.0),
825 lsst.geom.SpherePoint(2.0, 34.0, lsst.geom.degrees),
826 np.identity(2),
827 )
828 self.photoCalib = afwImage.PhotoCalib(1.5)
829 self.psf = DummyPsf(2.0)
830 self.detector = DetectorWrapper().detector
831 self.polygon = afwGeom.Polygon(lsst.geom.Box2D(lsst.geom.Point2D(0.0, 0.0),
832 lsst.geom.Point2D(25.0, 20.0)))
833 self.coaddInputs = afwImage.CoaddInputs()
834 self.apCorrMap = afwImage.ApCorrMap()
835 self.transmissionCurve = afwImage.TransmissionCurve.makeIdentity()
837 self.exposureInfo = afwImage.ExposureInfo()
838 gFilter = afwImage.Filter("g")
839 self.exposureInfo.setFilter(gFilter)
841 def _checkAlias(self, exposureInfo, key, value, has, get):
842 self.assertFalse(has())
843 self.assertFalse(exposureInfo.hasComponent(key))
844 self.assertIsNone(get())
845 self.assertIsNone(exposureInfo.getComponent(key))
847 self.exposureInfo.setComponent(key, value)
848 self.assertTrue(has())
849 self.assertTrue(exposureInfo.hasComponent(key))
850 self.assertIsNotNone(get())
851 self.assertIsNotNone(exposureInfo.getComponent(key))
852 self.assertEqual(get(), value)
853 self.assertEqual(exposureInfo.getComponent(key), value)
855 self.exposureInfo.removeComponent(key)
856 self.assertFalse(has())
857 self.assertFalse(exposureInfo.hasComponent(key))
858 self.assertIsNone(get())
859 self.assertIsNone(exposureInfo.getComponent(key))
861 def testAliases(self):
862 cls = type(self.exposureInfo)
863 self._checkAlias(self.exposureInfo, cls.KEY_WCS, self.wcs,
864 self.exposureInfo.hasWcs, self.exposureInfo.getWcs)
865 self._checkAlias(self.exposureInfo, cls.KEY_PSF, self.psf,
866 self.exposureInfo.hasPsf, self.exposureInfo.getPsf)
867 self._checkAlias(self.exposureInfo, cls.KEY_PHOTO_CALIB, self.photoCalib,
868 self.exposureInfo.hasPhotoCalib, self.exposureInfo.getPhotoCalib)
869 self._checkAlias(self.exposureInfo, cls.KEY_DETECTOR, self.detector,
870 self.exposureInfo.hasDetector, self.exposureInfo.getDetector)
871 self._checkAlias(self.exposureInfo, cls.KEY_VALID_POLYGON, self.polygon,
872 self.exposureInfo.hasValidPolygon, self.exposureInfo.getValidPolygon)
873 self._checkAlias(self.exposureInfo, cls.KEY_COADD_INPUTS, self.coaddInputs,
874 self.exposureInfo.hasCoaddInputs, self.exposureInfo.getCoaddInputs)
875 self._checkAlias(self.exposureInfo, cls.KEY_AP_CORR_MAP, self.apCorrMap,
876 self.exposureInfo.hasApCorrMap, self.exposureInfo.getApCorrMap)
877 self._checkAlias(self.exposureInfo, cls.KEY_TRANSMISSION_CURVE, self.transmissionCurve,
878 self.exposureInfo.hasTransmissionCurve, self.exposureInfo.getTransmissionCurve)
880 def testCopy(self):
881 # Test that ExposureInfos have independently settable state
882 copy = afwImage.ExposureInfo(self.exposureInfo, True)
883 self.assertEqual(self.exposureInfo.getWcs(), copy.getWcs())
885 newWcs = afwGeom.makeSkyWcs(lsst.geom.Point2D(-23.0, 8.0),
886 lsst.geom.SpherePoint(0.0, 0.0, lsst.geom.degrees),
887 np.identity(2),
888 )
889 copy.setWcs(newWcs)
890 self.assertEqual(copy.getWcs(), newWcs)
891 self.assertNotEqual(self.exposureInfo.getWcs(), copy.getWcs())
894class ExposureNoAfwdataTestCase(lsst.utils.tests.TestCase):
895 """Tests of Exposure that don't require afwdata.
897 These tests use the trivial exposures written to ``afw/tests/data``.
898 """
899 def setUp(self):
900 self.dataDir = os.path.join(os.path.split(__file__)[0], "data")
902 # Check the values below against what was written by comparing with
903 # the code in `afw/tests/data/makeTestExposure.py`
904 nx = ny = 10
905 image = afwImage.ImageF(np.arange(nx*ny, dtype='f').reshape(nx, ny))
906 variance = afwImage.ImageF(np.ones((nx, ny), dtype='f'))
907 mask = afwImage.MaskX(nx, ny)
908 mask.array[5, 5] = 5
909 self.maskedImage = afwImage.MaskedImageF(image, mask, variance)
911 self.v0PhotoCalib = afwImage.makePhotoCalibFromCalibZeroPoint(1e6, 2e4)
912 self.v1PhotoCalib = afwImage.PhotoCalib(1e6, 2e4)
914 def testReadUnversioned(self):
915 """Test that we can read an unversioned (implicit verison 0) file.
916 """
917 filename = os.path.join(self.dataDir, "exposure-noversion.fits")
918 exposure = afwImage.ExposureF.readFits(filename)
920 self.assertMaskedImagesEqual(exposure.maskedImage, self.maskedImage)
922 self.assertEqual(exposure.getPhotoCalib(), self.v0PhotoCalib)
924 def testReadVersion0(self):
925 """Test that we can read an version 0 file.
926 This file should be identical to the unversioned one, except that it
927 is marked as ExposureInfo version 0 in the header.
928 """
929 filename = os.path.join(self.dataDir, "exposure-version-0.fits")
930 exposure = afwImage.ExposureF.readFits(filename)
932 self.assertMaskedImagesEqual(exposure.maskedImage, self.maskedImage)
934 self.assertEqual(exposure.getPhotoCalib(), self.v0PhotoCalib)
936 # Check that the metadata reader parses the file correctly
937 reader = afwImage.ExposureFitsReader(filename)
938 self.assertEqual(reader.readExposureInfo().getPhotoCalib(), self.v0PhotoCalib)
939 self.assertEqual(reader.readPhotoCalib(), self.v0PhotoCalib)
941 def testReadVersion1(self):
942 """Test that we can read an version 1 file.
943 Version 1 replaced Calib with PhotoCalib.
944 """
945 filename = os.path.join(self.dataDir, "exposure-version-1.fits")
946 exposure = afwImage.ExposureF.readFits(filename)
948 self.assertMaskedImagesEqual(exposure.maskedImage, self.maskedImage)
950 self.assertEqual(exposure.getPhotoCalib(), self.v1PhotoCalib)
952 # Check that the metadata reader parses the file correctly
953 reader = afwImage.ExposureFitsReader(filename)
954 self.assertEqual(reader.readExposureInfo().getPhotoCalib(), self.v1PhotoCalib)
955 self.assertEqual(reader.readPhotoCalib(), self.v1PhotoCalib)
958class MemoryTester(lsst.utils.tests.MemoryTestCase):
959 pass
962def setup_module(module):
963 lsst.utils.tests.init()
966if __name__ == "__main__": 966 ↛ 967line 966 didn't jump to line 967, because the condition on line 966 was never true
967 lsst.utils.tests.init()
968 unittest.main()