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 """Test that a WCS can be obtained from each Exposure created with
186 a WCS, and that an Exposure lacking a WCS returns None.
187 """
188 # These exposures don't contain a WCS
189 self.assertIsNone(self.exposureBlank.getWcs())
190 self.assertIsNone(self.exposureMiOnly.getWcs())
191 self.assertIsNone(self.exposureCrOnly.getWcs())
193 # These exposures should contain a WCS
194 self.assertEqual(self.wcs, self.exposureMiWcs.getWcs())
195 self.assertEqual(self.wcs, self.exposureCrWcs.getWcs())
197 def testExposureInfoConstructor(self):
198 """Test the Exposure(maskedImage, exposureInfo) constructor"""
199 exposureInfo = afwImage.ExposureInfo()
200 exposureInfo.setWcs(self.wcs)
201 exposureInfo.setDetector(self.detector)
202 gFilter = afwImage.Filter("g")
203 exposureInfo.setFilter(gFilter)
204 maskedImage = afwImage.MaskedImageF(inFilePathSmall)
205 exposure = afwImage.ExposureF(maskedImage, exposureInfo)
207 self.assertTrue(exposure.hasWcs())
208 self.assertEqual(exposure.getWcs().getPixelOrigin(),
209 self.wcs.getPixelOrigin())
210 self.assertEqual(exposure.getDetector().getName(),
211 self.detector.getName())
212 self.assertEqual(exposure.getDetector().getSerial(),
213 self.detector.getSerial())
214 self.assertEqual(exposure.getFilter(), gFilter)
216 self.assertTrue(exposure.getInfo().hasWcs())
217 self.assertEqual(exposure.getInfo().getWcs().getPixelOrigin(),
218 self.wcs.getPixelOrigin())
219 self.assertEqual(exposure.getInfo().getDetector().getName(),
220 self.detector.getName())
221 self.assertEqual(exposure.getInfo().getDetector().getSerial(),
222 self.detector.getSerial())
223 self.assertEqual(exposure.getInfo().getFilter(), gFilter)
225 def testNullWcs(self):
226 """Test that an Exposure constructed with second argument None is usable
228 When the exposureInfo constructor was first added, trying to get a WCS
229 or other info caused a segfault because the ExposureInfo did not exist.
230 """
231 maskedImage = self.exposureMiOnly.getMaskedImage()
232 exposure = afwImage.ExposureF(maskedImage, None)
233 self.assertFalse(exposure.hasWcs())
234 self.assertFalse(exposure.hasPsf())
236 def testExposureInfoSetNone(self):
237 exposureInfo = afwImage.ExposureInfo()
238 exposureInfo.setDetector(None)
239 exposureInfo.setValidPolygon(None)
240 exposureInfo.setPsf(None)
241 exposureInfo.setWcs(None)
242 exposureInfo.setPhotoCalib(None)
243 exposureInfo.setCoaddInputs(None)
244 exposureInfo.setVisitInfo(None)
245 exposureInfo.setApCorrMap(None)
246 for key in self.extras:
247 exposureInfo.setComponent(key, None)
249 def testSetExposureInfo(self):
250 exposureInfo = afwImage.ExposureInfo()
251 exposureInfo.setWcs(self.wcs)
252 exposureInfo.setDetector(self.detector)
253 gFilter = afwImage.Filter("g")
254 exposureInfo.setFilter(gFilter)
255 maskedImage = afwImage.MaskedImageF(inFilePathSmall)
256 exposure = afwImage.ExposureF(maskedImage)
257 self.assertFalse(exposure.hasWcs())
259 exposure.setInfo(exposureInfo)
261 self.assertTrue(exposure.hasWcs())
262 self.assertEqual(exposure.getWcs().getPixelOrigin(),
263 self.wcs.getPixelOrigin())
264 self.assertEqual(exposure.getDetector().getName(),
265 self.detector.getName())
266 self.assertEqual(exposure.getDetector().getSerial(),
267 self.detector.getSerial())
268 self.assertEqual(exposure.getFilter(), gFilter)
270 def testVisitInfoFitsPersistence(self):
271 """Test saving an exposure to FITS and reading it back in preserves (some) VisitInfo fields"""
272 exposureId = 5
273 exposureTime = 12.3
274 boresightRotAngle = 45.6 * lsst.geom.degrees
275 weather = Weather(1.1, 2.2, 0.3)
276 visitInfo = afwImage.VisitInfo(
277 exposureId=exposureId,
278 exposureTime=exposureTime,
279 boresightRotAngle=boresightRotAngle,
280 weather=weather,
281 )
282 photoCalib = afwImage.PhotoCalib(3.4, 5.6)
283 exposureInfo = afwImage.ExposureInfo()
284 exposureInfo.setVisitInfo(visitInfo)
285 exposureInfo.setPhotoCalib(photoCalib)
286 exposureInfo.setDetector(self.detector)
287 gFilter = afwImage.Filter("g")
288 exposureInfo.setFilter(gFilter)
289 maskedImage = afwImage.MaskedImageF(inFilePathSmall)
290 exposure = afwImage.ExposureF(maskedImage, exposureInfo)
291 with lsst.utils.tests.getTempFilePath(".fits") as tmpFile:
292 exposure.writeFits(tmpFile)
293 rtExposure = afwImage.ExposureF(tmpFile)
294 rtVisitInfo = rtExposure.getInfo().getVisitInfo()
295 self.assertEqual(rtVisitInfo.getWeather(), weather)
296 self.assertEqual(rtExposure.getPhotoCalib(), photoCalib)
297 self.assertEqual(rtExposure.getFilter(), gFilter)
299 def testSetMembers(self):
300 """
301 Test that the MaskedImage and the WCS of an Exposure can be set.
302 """
303 exposure = afwImage.ExposureF()
305 maskedImage = afwImage.MaskedImageF(inFilePathSmall)
306 exposure.setMaskedImage(maskedImage)
307 exposure.setWcs(self.wcs)
308 exposure.setDetector(self.detector)
309 exposure.setFilter(afwImage.Filter("g"))
311 self.assertEqual(exposure.getDetector().getName(),
312 self.detector.getName())
313 self.assertEqual(exposure.getDetector().getSerial(),
314 self.detector.getSerial())
315 self.assertEqual(exposure.getFilter().getName(), "g")
316 self.assertEqual(exposure.getWcs(), self.wcs)
318 # The PhotoCalib tests are in test_photoCalib.py;
319 # here we just check that it's gettable and settable.
320 self.assertIsNone(exposure.getPhotoCalib())
322 photoCalib = afwImage.PhotoCalib(511.1, 44.4)
323 exposure.setPhotoCalib(photoCalib)
324 self.assertEqual(exposure.getPhotoCalib(), photoCalib)
326 # Psfs next
327 self.assertFalse(exposure.hasPsf())
328 exposure.setPsf(self.psf)
329 self.assertTrue(exposure.hasPsf())
331 exposure.setPsf(DummyPsf(1.0)) # we can reset the Psf
333 # extras next
334 info = exposure.getInfo()
335 for key, value in self.extras.items():
336 self.assertFalse(info.hasComponent(key))
337 self.assertIsNone(info.getComponent(key))
338 info.setComponent(key, value)
339 self.assertTrue(info.hasComponent(key))
340 self.assertEqual(info.getComponent(key), value)
341 info.removeComponent(key)
342 self.assertFalse(info.hasComponent(key))
344 # Test that we can set the MaskedImage and WCS of an Exposure
345 # that already has both
346 self.exposureMiWcs.setMaskedImage(maskedImage)
347 exposure.setWcs(self.wcs)
349 def testHasWcs(self):
350 """
351 Test if an Exposure has a WCS or not.
352 """
353 self.assertFalse(self.exposureBlank.hasWcs())
355 self.assertFalse(self.exposureMiOnly.hasWcs())
356 self.assertTrue(self.exposureMiWcs.hasWcs())
357 self.assertTrue(self.exposureCrWcs.hasWcs())
358 self.assertFalse(self.exposureCrOnly.hasWcs())
360 def testGetSubExposure(self):
361 """
362 Test that a subExposure of the original Exposure can be obtained.
364 The MaskedImage class should throw a
365 lsst::pex::exceptions::InvalidParameter if the requested
366 subRegion is not fully contained within the original
367 MaskedImage.
369 """
370 #
371 # This subExposure is valid
372 #
373 subBBox = lsst.geom.Box2I(lsst.geom.Point2I(40, 50),
374 lsst.geom.Extent2I(10, 10))
375 subExposure = self.exposureCrWcs.Factory(
376 self.exposureCrWcs, subBBox, afwImage.LOCAL)
378 self.checkWcs(self.exposureCrWcs, subExposure)
380 # this subRegion is not valid and should trigger an exception
381 # from the MaskedImage class and should trigger an exception
382 # from the WCS class for the MaskedImage 871034p_1_MI.
384 subRegion3 = lsst.geom.Box2I(lsst.geom.Point2I(100, 100),
385 lsst.geom.Extent2I(10, 10))
387 def getSubRegion():
388 self.exposureCrWcs.Factory(
389 self.exposureCrWcs, subRegion3, afwImage.LOCAL)
391 self.assertRaises(pexExcept.LengthError, getSubRegion)
393 # this subRegion is not valid and should trigger an exception
394 # from the MaskedImage class only for the MaskedImage small_MI.
395 # small_MI (cols, rows) = (256, 256)
397 subRegion4 = lsst.geom.Box2I(lsst.geom.Point2I(250, 250),
398 lsst.geom.Extent2I(10, 10))
400 def getSubRegion():
401 self.exposureCrWcs.Factory(
402 self.exposureCrWcs, subRegion4, afwImage.LOCAL)
404 self.assertRaises(pexExcept.LengthError, getSubRegion)
406 # check the sub- and parent- exposures are using the same Wcs
407 # transformation
408 subBBox = lsst.geom.Box2I(lsst.geom.Point2I(40, 50),
409 lsst.geom.Extent2I(10, 10))
410 subExposure = self.exposureCrWcs.Factory(
411 self.exposureCrWcs, subBBox, afwImage.LOCAL)
412 parentSkyPos = self.exposureCrWcs.getWcs().pixelToSky(0, 0)
414 subExpSkyPos = subExposure.getWcs().pixelToSky(0, 0)
416 self.assertSpherePointsAlmostEqual(parentSkyPos, subExpSkyPos, msg="Wcs in sub image has changed")
418 def testReadWriteFits(self):
419 """Test readFits and writeFits.
420 """
421 # This should pass without an exception
422 mainExposure = afwImage.ExposureF(inFilePathSmall)
423 mainExposure.setDetector(self.detector)
425 subBBox = lsst.geom.Box2I(lsst.geom.Point2I(10, 10),
426 lsst.geom.Extent2I(40, 50))
427 subExposure = mainExposure.Factory(
428 mainExposure, subBBox, afwImage.LOCAL)
429 self.checkWcs(mainExposure, subExposure)
430 det = subExposure.getDetector()
431 self.assertTrue(det)
433 subExposure = afwImage.ExposureF(
434 inFilePathSmall, subBBox, afwImage.LOCAL)
436 self.checkWcs(mainExposure, subExposure)
438 # This should throw an exception
439 def getExposure():
440 afwImage.ExposureF(inFilePathSmallImage)
442 self.assertRaises(FitsError, getExposure)
444 mainExposure.setPsf(self.psf)
446 # Make sure we can write without an exception
447 photoCalib = afwImage.PhotoCalib(1e-10, 1e-12)
448 mainExposure.setPhotoCalib(photoCalib)
450 mainInfo = mainExposure.getInfo()
451 for key, value in self.extras.items():
452 mainInfo.setComponent(key, value)
454 with lsst.utils.tests.getTempFilePath(".fits") as tmpFile:
455 mainExposure.writeFits(tmpFile)
457 readExposure = type(mainExposure)(tmpFile)
459 #
460 # Check the round-tripping
461 #
462 self.assertEqual(mainExposure.getFilter().getName(),
463 readExposure.getFilter().getName())
465 self.assertEqual(photoCalib, readExposure.getPhotoCalib())
467 readInfo = readExposure.getInfo()
468 for key, value in self.extras.items():
469 self.assertEqual(value, readInfo.getComponent(key))
471 psf = readExposure.getPsf()
472 self.assertIsNotNone(psf)
473 self.assertEqual(psf, self.psf)
475 def checkWcs(self, parentExposure, subExposure):
476 """Compare WCS at corner points of a sub-exposure and its parent exposure
477 By using the function indexToPosition, we should be able to convert the indices
478 (of the four corners (of the sub-exposure)) to positions and use the wcs
479 to get the same sky coordinates for each.
480 """
481 subMI = subExposure.getMaskedImage()
482 subDim = subMI.getDimensions()
484 # Note: pixel positions must be computed relative to XY0 when working
485 # with WCS
486 mainWcs = parentExposure.getWcs()
487 subWcs = subExposure.getWcs()
489 for xSubInd in (0, subDim.getX()-1):
490 for ySubInd in (0, subDim.getY()-1):
491 self.assertSpherePointsAlmostEqual(
492 mainWcs.pixelToSky(
493 afwImage.indexToPosition(xSubInd),
494 afwImage.indexToPosition(ySubInd),
495 ),
496 subWcs.pixelToSky(
497 afwImage.indexToPosition(xSubInd),
498 afwImage.indexToPosition(ySubInd),
499 ))
501 def cmpExposure(self, e1, e2):
502 self.assertEqual(e1.getDetector().getName(),
503 e2.getDetector().getName())
504 self.assertEqual(e1.getDetector().getSerial(),
505 e2.getDetector().getSerial())
506 self.assertEqual(e1.getFilter().getName(), e2.getFilter().getName())
507 xy = lsst.geom.Point2D(0, 0)
508 self.assertEqual(e1.getWcs().pixelToSky(xy)[0],
509 e2.getWcs().pixelToSky(xy)[0])
510 self.assertEqual(e1.getPhotoCalib(), e2.getPhotoCalib())
511 # check PSF identity
512 if not e1.getPsf():
513 self.assertFalse(e2.getPsf())
514 else:
515 self.assertEqual(e1.getPsf(), e2.getPsf())
516 # Check extra components
517 i1 = e1.getInfo()
518 i2 = e2.getInfo()
519 for key in self.extras:
520 self.assertEqual(i1.hasComponent(key), i2.hasComponent(key))
521 if i1.hasComponent(key):
522 self.assertEqual(i1.getComponent(key), i2.getComponent(key))
524 def testCopyExposure(self):
525 """Copy an Exposure (maybe changing type)"""
527 exposureU = afwImage.ExposureU(inFilePathSmall, allowUnsafe=True)
528 exposureU.setWcs(self.wcs)
529 exposureU.setDetector(self.detector)
530 exposureU.setFilter(afwImage.Filter("g"))
531 exposureU.setPsf(DummyPsf(4.0))
532 infoU = exposureU.getInfo()
533 for key, value in self.extras.items():
534 infoU.setComponent(key, value)
536 exposureF = exposureU.convertF()
537 self.cmpExposure(exposureF, exposureU)
539 nexp = exposureF.Factory(exposureF, False)
540 self.cmpExposure(exposureF, nexp)
542 # Ensure that the copy was deep.
543 # (actually this test is invalid since getDetector() returns a shared_ptr)
544 # cen0 = exposureU.getDetector().getCenterPixel()
545 # x0,y0 = cen0
546 # det = exposureF.getDetector()
547 # det.setCenterPixel(lsst.geom.Point2D(999.0, 437.8))
548 # self.assertEqual(exposureU.getDetector().getCenterPixel()[0], x0)
549 # self.assertEqual(exposureU.getDetector().getCenterPixel()[1], y0)
551 def testDeepCopyData(self):
552 """Make sure a deep copy of an Exposure has its own data (ticket #2625)
553 """
554 exp = afwImage.ExposureF(6, 7)
555 mi = exp.getMaskedImage()
556 mi.getImage().set(100)
557 mi.getMask().set(5)
558 mi.getVariance().set(200)
560 expCopy = exp.clone()
561 miCopy = expCopy.getMaskedImage()
562 miCopy.getImage().set(-50)
563 miCopy.getMask().set(2)
564 miCopy.getVariance().set(175)
566 self.assertFloatsAlmostEqual(miCopy.getImage().getArray(), -50)
567 self.assertTrue(np.all(miCopy.getMask().getArray() == 2))
568 self.assertFloatsAlmostEqual(miCopy.getVariance().getArray(), 175)
570 self.assertFloatsAlmostEqual(mi.getImage().getArray(), 100)
571 self.assertTrue(np.all(mi.getMask().getArray() == 5))
572 self.assertFloatsAlmostEqual(mi.getVariance().getArray(), 200)
574 def testDeepCopySubData(self):
575 """Make sure a deep copy of a subregion of an Exposure has its own data (ticket #2625)
576 """
577 exp = afwImage.ExposureF(6, 7)
578 mi = exp.getMaskedImage()
579 mi.getImage().set(100)
580 mi.getMask().set(5)
581 mi.getVariance().set(200)
583 bbox = lsst.geom.Box2I(lsst.geom.Point2I(1, 0), lsst.geom.Extent2I(5, 4))
584 expCopy = exp.Factory(exp, bbox, afwImage.PARENT, True)
585 miCopy = expCopy.getMaskedImage()
586 miCopy.getImage().set(-50)
587 miCopy.getMask().set(2)
588 miCopy.getVariance().set(175)
590 self.assertFloatsAlmostEqual(miCopy.getImage().getArray(), -50)
591 self.assertTrue(np.all(miCopy.getMask().getArray() == 2))
592 self.assertFloatsAlmostEqual(miCopy.getVariance().getArray(), 175)
594 self.assertFloatsAlmostEqual(mi.getImage().getArray(), 100)
595 self.assertTrue(np.all(mi.getMask().getArray() == 5))
596 self.assertFloatsAlmostEqual(mi.getVariance().getArray(), 200)
598 def testDeepCopyMetadata(self):
599 """Make sure a deep copy of an Exposure has a deep copy of metadata (ticket #2568)
600 """
601 exp = afwImage.ExposureF(10, 10)
602 expMeta = exp.getMetadata()
603 expMeta.set("foo", 5)
604 expCopy = exp.clone()
605 expCopyMeta = expCopy.getMetadata()
606 expCopyMeta.set("foo", 6)
607 self.assertEqual(expCopyMeta.getScalar("foo"), 6)
608 # this will fail if the bug is present
609 self.assertEqual(expMeta.getScalar("foo"), 5)
611 def testDeepCopySubMetadata(self):
612 """Make sure a deep copy of a subregion of an Exposure has a deep copy of metadata (ticket #2568)
613 """
614 exp = afwImage.ExposureF(10, 10)
615 expMeta = exp.getMetadata()
616 expMeta.set("foo", 5)
617 bbox = lsst.geom.Box2I(lsst.geom.Point2I(1, 0), lsst.geom.Extent2I(5, 5))
618 expCopy = exp.Factory(exp, bbox, afwImage.PARENT, True)
619 expCopyMeta = expCopy.getMetadata()
620 expCopyMeta.set("foo", 6)
621 self.assertEqual(expCopyMeta.getScalar("foo"), 6)
622 # this will fail if the bug is present
623 self.assertEqual(expMeta.getScalar("foo"), 5)
625 def testMakeExposureLeaks(self):
626 """Test for memory leaks in makeExposure (the test is in lsst.utils.tests.MemoryTestCase)"""
627 afwImage.makeMaskedImage(afwImage.ImageU(lsst.geom.Extent2I(10, 20)))
628 afwImage.makeExposure(afwImage.makeMaskedImage(
629 afwImage.ImageU(lsst.geom.Extent2I(10, 20))))
631 def testImageSlices(self):
632 """Test image slicing, which generate sub-images using Box2I under the covers"""
633 exp = afwImage.ExposureF(10, 20)
634 mi = exp.getMaskedImage()
635 mi.image[9, 19] = 10
636 # N.b. Exposures don't support setting/getting the pixels so can't
637 # replicate e.g. Image's slice tests
638 sexp = exp[1:4, 6:10]
639 self.assertEqual(sexp.getDimensions(), lsst.geom.ExtentI(3, 4))
640 sexp = exp[:, -3:, afwImage.LOCAL]
641 self.assertEqual(sexp.getDimensions(),
642 lsst.geom.ExtentI(exp.getWidth(), 3))
643 self.assertEqual(sexp.maskedImage[-1, -1, afwImage.LOCAL],
644 exp.maskedImage[-1, -1, afwImage.LOCAL])
646 def testConversionToScalar(self):
647 """Test that even 1-pixel Exposures can't be converted to scalars"""
648 im = afwImage.ExposureF(10, 20)
650 # only single pixel images may be converted
651 self.assertRaises(TypeError, float, im)
652 # actually, can't convert (img, msk, var) to scalar
653 self.assertRaises(TypeError, float, im[0, 0])
655 def testReadMetadata(self):
656 with lsst.utils.tests.getTempFilePath(".fits") as tmpFile:
657 self.exposureCrWcs.getMetadata().set("FRAZZLE", True)
658 # This will write the main metadata (inc. FRAZZLE) to the primary HDU, and the
659 # WCS to subsequent HDUs, along with INHERIT=T.
660 self.exposureCrWcs.writeFits(tmpFile)
661 # This should read the first non-empty HDU (i.e. it skips the primary), but
662 # goes back and reads it if it finds INHERIT=T. That should let us read
663 # frazzle and the Wcs from the PropertySet returned by
664 # testReadMetadata.
665 md = readMetadata(tmpFile)
666 wcs = afwGeom.makeSkyWcs(md, False)
667 self.assertPairsAlmostEqual(wcs.getPixelOrigin(), self.wcs.getPixelOrigin())
668 self.assertSpherePointsAlmostEqual(wcs.getSkyOrigin(), self.wcs.getSkyOrigin())
669 assert_allclose(wcs.getCdMatrix(), self.wcs.getCdMatrix(), atol=1e-10)
670 frazzle = md.getScalar("FRAZZLE")
671 self.assertTrue(frazzle)
673 def testArchiveKeys(self):
674 with lsst.utils.tests.getTempFilePath(".fits") as tmpFile:
675 exposure1 = afwImage.ExposureF(100, 100, self.wcs)
676 exposure1.setPsf(self.psf)
677 exposure1.writeFits(tmpFile)
678 exposure2 = afwImage.ExposureF(tmpFile)
679 self.assertFalse(exposure2.getMetadata().exists("AR_ID"))
680 self.assertFalse(exposure2.getMetadata().exists("PSF_ID"))
681 self.assertFalse(exposure2.getMetadata().exists("WCS_ID"))
683 def testTicket2861(self):
684 with lsst.utils.tests.getTempFilePath(".fits") as tmpFile:
685 exposure1 = afwImage.ExposureF(100, 100, self.wcs)
686 exposure1.setPsf(self.psf)
687 schema = afwTable.ExposureTable.makeMinimalSchema()
688 coaddInputs = afwImage.CoaddInputs(schema, schema)
689 exposure1.getInfo().setCoaddInputs(coaddInputs)
690 exposure2 = afwImage.ExposureF(exposure1, True)
691 self.assertIsNotNone(exposure2.getInfo().getCoaddInputs())
692 exposure2.writeFits(tmpFile)
693 exposure3 = afwImage.ExposureF(tmpFile)
694 self.assertIsNotNone(exposure3.getInfo().getCoaddInputs())
696 def testGetCutout(self):
697 wcs = self.smallExposure.getWcs()
699 dimensions = [lsst.geom.Extent2I(100, 50), lsst.geom.Extent2I(15, 15), lsst.geom.Extent2I(0, 10),
700 lsst.geom.Extent2I(25, 30), lsst.geom.Extent2I(15, -5),
701 2*self.smallExposure.getDimensions()]
702 locations = [("center", self._getExposureCenter(self.smallExposure)),
703 ("edge", wcs.pixelToSky(lsst.geom.Point2D(0, 0))),
704 ("rounding test", wcs.pixelToSky(lsst.geom.Point2D(0.2, 0.7))),
705 ("just inside", wcs.pixelToSky(lsst.geom.Point2D(-0.5 + 1e-4, -0.5 + 1e-4))),
706 ("just outside", wcs.pixelToSky(lsst.geom.Point2D(-0.5 - 1e-4, -0.5 - 1e-4))),
707 ("outside", wcs.pixelToSky(lsst.geom.Point2D(-1000, -1000)))]
708 for cutoutSize in dimensions:
709 for label, cutoutCenter in locations:
710 msg = 'Cutout size = %s, location = %s' % (cutoutSize, label)
711 if "outside" not in label and all(cutoutSize.gt(0)):
712 cutout = self.smallExposure.getCutout(cutoutCenter, cutoutSize)
713 centerInPixels = wcs.skyToPixel(cutoutCenter)
714 precision = (1 + 1e-4)*np.sqrt(0.5)*wcs.getPixelScale(centerInPixels)
715 self._checkCutoutProperties(cutout, cutoutSize, cutoutCenter, precision, msg)
716 self._checkCutoutPixels(
717 cutout,
718 self._getValidCorners(self.smallExposure.getBBox(), cutout.getBBox()),
719 msg)
721 # Need a valid WCS
722 with self.assertRaises(pexExcept.LogicError, msg=msg):
723 self.exposureMiOnly.getCutout(cutoutCenter, cutoutSize)
724 else:
725 with self.assertRaises(pexExcept.InvalidParameterError, msg=msg):
726 self.smallExposure.getCutout(cutoutCenter, cutoutSize)
728 def _checkCutoutProperties(self, cutout, size, center, precision, msg):
729 """Test whether a cutout has the desired size and position.
731 Parameters
732 ----------
733 cutout : `lsst.afw.image.Exposure`
734 The cutout to test.
735 size : `lsst.geom.Extent2I`
736 The expected dimensions of ``cutout``.
737 center : `lsst.geom.SpherePoint`
738 The expected center of ``cutout``.
739 precision : `lsst.geom.Angle`
740 The precision to which ``center`` must match.
741 msg : `str`
742 An error message suffix describing test parameters.
743 """
744 newCenter = self._getExposureCenter(cutout)
745 self.assertIsNotNone(cutout, msg=msg)
746 self.assertSpherePointsAlmostEqual(newCenter, center, maxSep=precision, msg=msg)
747 self.assertEqual(cutout.getWidth(), size[0], msg=msg)
748 self.assertEqual(cutout.getHeight(), size[1], msg=msg)
750 def _checkCutoutPixels(self, cutout, validCorners, msg):
751 """Test whether a cutout has valid/empty pixels where expected.
753 Parameters
754 ----------
755 cutout : `lsst.afw.image.Exposure`
756 The cutout to test.
757 validCorners : iterable of `lsst.geom.Point2I`
758 The corners of ``cutout`` that should be drawn from the original image.
759 msg : `str`
760 An error message suffix describing test parameters.
761 """
762 mask = cutout.getMaskedImage().getMask()
763 edgeMask = mask.getPlaneBitMask("NO_DATA")
765 for corner in cutout.getBBox().getCorners():
766 maskBitsSet = mask[corner] & edgeMask
767 if corner in validCorners:
768 self.assertEqual(maskBitsSet, 0, msg=msg)
769 else:
770 self.assertEqual(maskBitsSet, edgeMask, msg=msg)
772 def _getExposureCenter(self, exposure):
773 """Return the sky coordinates of an Exposure's center.
775 Parameters
776 ----------
777 exposure : `lsst.afw.image.Exposure`
778 The image whose center is desired.
780 Returns
781 -------
782 center : `lsst.geom.SpherePoint`
783 The position at the center of ``exposure``.
784 """
785 return exposure.getWcs().pixelToSky(lsst.geom.Box2D(exposure.getBBox()).getCenter())
787 def _getValidCorners(self, imageBox, cutoutBox):
788 """Return the corners of a cutout that are constrained by the original image.
790 Parameters
791 ----------
792 imageBox: `lsst.geom.Extent2I`
793 The bounding box of the original image.
794 cutoutBox : `lsst.geom.Box2I`
795 The bounding box of the cutout.
797 Returns
798 -------
799 corners : iterable of `lsst.geom.Point2I`
800 The corners that are drawn from the original image.
801 """
802 return [corner for corner in cutoutBox.getCorners() if corner in imageBox]
805class ExposureInfoTestCase(lsst.utils.tests.TestCase):
806 def setUp(self):
807 super().setUp()
809 afwImage.Filter.reset()
810 afwImage.FilterProperty.reset()
811 defineFilter("g", 470.0)
813 self.wcs = afwGeom.makeSkyWcs(lsst.geom.Point2D(0.0, 0.0),
814 lsst.geom.SpherePoint(2.0, 34.0, lsst.geom.degrees),
815 np.identity(2),
816 )
817 self.photoCalib = afwImage.PhotoCalib(1.5)
818 self.psf = DummyPsf(2.0)
819 self.detector = DetectorWrapper().detector
820 self.polygon = afwGeom.Polygon(lsst.geom.Box2D(lsst.geom.Point2D(0.0, 0.0),
821 lsst.geom.Point2D(25.0, 20.0)))
822 self.coaddInputs = afwImage.CoaddInputs()
823 self.apCorrMap = afwImage.ApCorrMap()
824 self.transmissionCurve = afwImage.TransmissionCurve.makeIdentity()
826 self.exposureInfo = afwImage.ExposureInfo()
827 gFilter = afwImage.Filter("g")
828 self.exposureInfo.setFilter(gFilter)
830 def _checkAlias(self, exposureInfo, key, value, has, get):
831 self.assertFalse(has())
832 self.assertFalse(exposureInfo.hasComponent(key))
833 self.assertIsNone(get())
834 self.assertIsNone(exposureInfo.getComponent(key))
836 self.exposureInfo.setComponent(key, value)
837 self.assertTrue(has())
838 self.assertTrue(exposureInfo.hasComponent(key))
839 self.assertIsNotNone(get())
840 self.assertIsNotNone(exposureInfo.getComponent(key))
841 self.assertEqual(get(), value)
842 self.assertEqual(exposureInfo.getComponent(key), value)
844 self.exposureInfo.removeComponent(key)
845 self.assertFalse(has())
846 self.assertFalse(exposureInfo.hasComponent(key))
847 self.assertIsNone(get())
848 self.assertIsNone(exposureInfo.getComponent(key))
850 def testAliases(self):
851 cls = type(self.exposureInfo)
852 self._checkAlias(self.exposureInfo, cls.KEY_WCS, self.wcs,
853 self.exposureInfo.hasWcs, self.exposureInfo.getWcs)
854 self._checkAlias(self.exposureInfo, cls.KEY_PSF, self.psf,
855 self.exposureInfo.hasPsf, self.exposureInfo.getPsf)
856 self._checkAlias(self.exposureInfo, cls.KEY_PHOTO_CALIB, self.photoCalib,
857 self.exposureInfo.hasPhotoCalib, self.exposureInfo.getPhotoCalib)
858 self._checkAlias(self.exposureInfo, cls.KEY_DETECTOR, self.detector,
859 self.exposureInfo.hasDetector, self.exposureInfo.getDetector)
860 self._checkAlias(self.exposureInfo, cls.KEY_VALID_POLYGON, self.polygon,
861 self.exposureInfo.hasValidPolygon, self.exposureInfo.getValidPolygon)
862 self._checkAlias(self.exposureInfo, cls.KEY_COADD_INPUTS, self.coaddInputs,
863 self.exposureInfo.hasCoaddInputs, self.exposureInfo.getCoaddInputs)
864 self._checkAlias(self.exposureInfo, cls.KEY_AP_CORR_MAP, self.apCorrMap,
865 self.exposureInfo.hasApCorrMap, self.exposureInfo.getApCorrMap)
866 self._checkAlias(self.exposureInfo, cls.KEY_TRANSMISSION_CURVE, self.transmissionCurve,
867 self.exposureInfo.hasTransmissionCurve, self.exposureInfo.getTransmissionCurve)
869 def testCopy(self):
870 # Test that ExposureInfos have independently settable state
871 copy = afwImage.ExposureInfo(self.exposureInfo, True)
872 self.assertEqual(self.exposureInfo.getWcs(), copy.getWcs())
874 newWcs = afwGeom.makeSkyWcs(lsst.geom.Point2D(-23.0, 8.0),
875 lsst.geom.SpherePoint(0.0, 0.0, lsst.geom.degrees),
876 np.identity(2),
877 )
878 copy.setWcs(newWcs)
879 self.assertEqual(copy.getWcs(), newWcs)
880 self.assertNotEqual(self.exposureInfo.getWcs(), copy.getWcs())
883class ExposureNoAfwdataTestCase(lsst.utils.tests.TestCase):
884 """Tests of Exposure that don't require afwdata.
886 These tests use the trivial exposures written to ``afw/tests/data``.
887 """
888 def setUp(self):
889 self.dataDir = os.path.join(os.path.split(__file__)[0], "data")
891 # Check the values below against what was written by comparing with
892 # the code in `afw/tests/data/makeTestExposure.py`
893 nx = ny = 10
894 image = afwImage.ImageF(np.arange(nx*ny, dtype='f').reshape(nx, ny))
895 variance = afwImage.ImageF(np.ones((nx, ny), dtype='f'))
896 mask = afwImage.MaskX(nx, ny)
897 mask.array[5, 5] = 5
898 self.maskedImage = afwImage.MaskedImageF(image, mask, variance)
900 self.v0PhotoCalib = afwImage.makePhotoCalibFromCalibZeroPoint(1e6, 2e4)
901 self.v1PhotoCalib = afwImage.PhotoCalib(1e6, 2e4)
903 def testReadUnversioned(self):
904 """Test that we can read an unversioned (implicit verison 0) file.
905 """
906 filename = os.path.join(self.dataDir, "exposure-noversion.fits")
907 exposure = afwImage.ExposureF.readFits(filename)
909 self.assertMaskedImagesEqual(exposure.maskedImage, self.maskedImage)
911 self.assertEqual(exposure.getPhotoCalib(), self.v0PhotoCalib)
913 def testReadVersion0(self):
914 """Test that we can read an version 0 file.
915 This file should be identical to the unversioned one, except that it
916 is marked as ExposureInfo version 0 in the header.
917 """
918 filename = os.path.join(self.dataDir, "exposure-version-0.fits")
919 exposure = afwImage.ExposureF.readFits(filename)
921 self.assertMaskedImagesEqual(exposure.maskedImage, self.maskedImage)
923 self.assertEqual(exposure.getPhotoCalib(), self.v0PhotoCalib)
925 # Check that the metadata reader parses the file correctly
926 reader = afwImage.ExposureFitsReader(filename)
927 self.assertEqual(reader.readExposureInfo().getPhotoCalib(), self.v0PhotoCalib)
928 self.assertEqual(reader.readPhotoCalib(), self.v0PhotoCalib)
930 def testReadVersion1(self):
931 """Test that we can read an version 1 file.
932 Version 1 replaced Calib with PhotoCalib.
933 """
934 filename = os.path.join(self.dataDir, "exposure-version-1.fits")
935 exposure = afwImage.ExposureF.readFits(filename)
937 self.assertMaskedImagesEqual(exposure.maskedImage, self.maskedImage)
939 self.assertEqual(exposure.getPhotoCalib(), self.v1PhotoCalib)
941 # Check that the metadata reader parses the file correctly
942 reader = afwImage.ExposureFitsReader(filename)
943 self.assertEqual(reader.readExposureInfo().getPhotoCalib(), self.v1PhotoCalib)
944 self.assertEqual(reader.readPhotoCalib(), self.v1PhotoCalib)
947class MemoryTester(lsst.utils.tests.MemoryTestCase):
948 pass
951def setup_module(module):
952 lsst.utils.tests.init()
955if __name__ == "__main__": 955 ↛ 956line 955 didn't jump to line 956, because the condition on line 955 was never true
956 lsst.utils.tests.init()
957 unittest.main()