Coverage for tests/test_exposure.py : 12%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# This file is part of afw.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (https://www.lsst.org).
6# See the COPYRIGHT file at the top-level directory of this distribution
7# for details of code ownership.
8#
9# This program is free software: you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation, either version 3 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program. If not, see <https://www.gnu.org/licenses/>.
22"""
23Test lsst.afw.image.Exposure
24"""
26import os.path
27import unittest
29import numpy as np
30from numpy.testing import assert_allclose
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 gFilterLabel = afwImage.FilterLabel(band="g")
204 exposureInfo.setFilter(gFilter)
205 exposureInfo.setFilterLabel(gFilterLabel)
206 maskedImage = afwImage.MaskedImageF(inFilePathSmall)
207 exposure = afwImage.ExposureF(maskedImage, exposureInfo)
209 self.assertTrue(exposure.hasWcs())
210 self.assertEqual(exposure.getWcs().getPixelOrigin(),
211 self.wcs.getPixelOrigin())
212 self.assertEqual(exposure.getDetector().getName(),
213 self.detector.getName())
214 self.assertEqual(exposure.getDetector().getSerial(),
215 self.detector.getSerial())
216 self.assertEqual(exposure.getFilter(), gFilter)
217 self.assertEqual(exposure.getFilterLabel(), gFilterLabel)
219 self.assertTrue(exposure.getInfo().hasWcs())
220 self.assertEqual(exposure.getInfo().getWcs().getPixelOrigin(),
221 self.wcs.getPixelOrigin())
222 self.assertEqual(exposure.getInfo().getDetector().getName(),
223 self.detector.getName())
224 self.assertEqual(exposure.getInfo().getDetector().getSerial(),
225 self.detector.getSerial())
226 self.assertEqual(exposure.getInfo().getFilter(), gFilter)
227 self.assertEqual(exposure.getInfo().getFilterLabel(), gFilterLabel)
229 def testNullWcs(self):
230 """Test that an Exposure constructed with second argument None is usable
232 When the exposureInfo constructor was first added, trying to get a WCS
233 or other info caused a segfault because the ExposureInfo did not exist.
234 """
235 maskedImage = self.exposureMiOnly.getMaskedImage()
236 exposure = afwImage.ExposureF(maskedImage, None)
237 self.assertFalse(exposure.hasWcs())
238 self.assertFalse(exposure.hasPsf())
240 def testExposureInfoSetNone(self):
241 exposureInfo = afwImage.ExposureInfo()
242 exposureInfo.setDetector(None)
243 exposureInfo.setValidPolygon(None)
244 exposureInfo.setPsf(None)
245 exposureInfo.setWcs(None)
246 exposureInfo.setPhotoCalib(None)
247 exposureInfo.setCoaddInputs(None)
248 exposureInfo.setVisitInfo(None)
249 exposureInfo.setApCorrMap(None)
250 for key in self.extras:
251 exposureInfo.setComponent(key, None)
253 def testSetExposureInfo(self):
254 exposureInfo = afwImage.ExposureInfo()
255 exposureInfo.setWcs(self.wcs)
256 exposureInfo.setDetector(self.detector)
257 gFilter = afwImage.Filter("g")
258 gFilterLabel = afwImage.FilterLabel(band="g")
259 exposureInfo.setFilter(gFilter)
260 exposureInfo.setFilterLabel(gFilterLabel)
261 maskedImage = afwImage.MaskedImageF(inFilePathSmall)
262 exposure = afwImage.ExposureF(maskedImage)
263 self.assertFalse(exposure.hasWcs())
265 exposure.setInfo(exposureInfo)
267 self.assertTrue(exposure.hasWcs())
268 self.assertEqual(exposure.getWcs().getPixelOrigin(),
269 self.wcs.getPixelOrigin())
270 self.assertEqual(exposure.getDetector().getName(),
271 self.detector.getName())
272 self.assertEqual(exposure.getDetector().getSerial(),
273 self.detector.getSerial())
274 self.assertEqual(exposure.getFilter(), gFilter)
275 self.assertEqual(exposure.getFilterLabel(), gFilterLabel)
277 def testVisitInfoFitsPersistence(self):
278 """Test saving an exposure to FITS and reading it back in preserves (some) VisitInfo fields"""
279 exposureId = 5
280 exposureTime = 12.3
281 boresightRotAngle = 45.6 * lsst.geom.degrees
282 weather = Weather(1.1, 2.2, 0.3)
283 visitInfo = afwImage.VisitInfo(
284 exposureId=exposureId,
285 exposureTime=exposureTime,
286 boresightRotAngle=boresightRotAngle,
287 weather=weather,
288 )
289 photoCalib = afwImage.PhotoCalib(3.4, 5.6)
290 exposureInfo = afwImage.ExposureInfo()
291 exposureInfo.setVisitInfo(visitInfo)
292 exposureInfo.setPhotoCalib(photoCalib)
293 exposureInfo.setDetector(self.detector)
294 gFilter = afwImage.Filter("g")
295 gFilterLabel = afwImage.FilterLabel(band="g")
296 exposureInfo.setFilter(gFilter)
297 exposureInfo.setFilterLabel(gFilterLabel)
298 maskedImage = afwImage.MaskedImageF(inFilePathSmall)
299 exposure = afwImage.ExposureF(maskedImage, exposureInfo)
300 with lsst.utils.tests.getTempFilePath(".fits") as tmpFile:
301 exposure.writeFits(tmpFile)
302 rtExposure = afwImage.ExposureF(tmpFile)
303 rtVisitInfo = rtExposure.getInfo().getVisitInfo()
304 self.assertEqual(rtVisitInfo.getWeather(), weather)
305 self.assertEqual(rtExposure.getPhotoCalib(), photoCalib)
306 self.assertEqual(rtExposure.getFilter(), gFilter)
307 self.assertEqual(rtExposure.getFilterLabel(), gFilterLabel)
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"))
320 exposure.setFilterLabel(afwImage.FilterLabel(band="g"))
322 self.assertEqual(exposure.getDetector().getName(),
323 self.detector.getName())
324 self.assertEqual(exposure.getDetector().getSerial(),
325 self.detector.getSerial())
326 self.assertEqual(exposure.getFilter().getName(), "g")
327 self.assertEqual(exposure.getFilterLabel().bandLabel, "g")
328 self.assertEqual(exposure.getWcs(), self.wcs)
330 # The PhotoCalib tests are in test_photoCalib.py;
331 # here we just check that it's gettable and settable.
332 self.assertIsNone(exposure.getPhotoCalib())
334 photoCalib = afwImage.PhotoCalib(511.1, 44.4)
335 exposure.setPhotoCalib(photoCalib)
336 self.assertEqual(exposure.getPhotoCalib(), photoCalib)
338 # Psfs next
339 self.assertFalse(exposure.hasPsf())
340 exposure.setPsf(self.psf)
341 self.assertTrue(exposure.hasPsf())
343 exposure.setPsf(DummyPsf(1.0)) # we can reset the Psf
345 # extras next
346 info = exposure.getInfo()
347 for key, value in self.extras.items():
348 self.assertFalse(info.hasComponent(key))
349 self.assertIsNone(info.getComponent(key))
350 info.setComponent(key, value)
351 self.assertTrue(info.hasComponent(key))
352 self.assertEqual(info.getComponent(key), value)
353 info.removeComponent(key)
354 self.assertFalse(info.hasComponent(key))
356 # Test that we can set the MaskedImage and WCS of an Exposure
357 # that already has both
358 self.exposureMiWcs.setMaskedImage(maskedImage)
359 exposure.setWcs(self.wcs)
361 def testHasWcs(self):
362 """
363 Test if an Exposure has a WCS or not.
364 """
365 self.assertFalse(self.exposureBlank.hasWcs())
367 self.assertFalse(self.exposureMiOnly.hasWcs())
368 self.assertTrue(self.exposureMiWcs.hasWcs())
369 self.assertTrue(self.exposureCrWcs.hasWcs())
370 self.assertFalse(self.exposureCrOnly.hasWcs())
372 def testGetSubExposure(self):
373 """
374 Test that a subExposure of the original Exposure can be obtained.
376 The MaskedImage class should throw a
377 lsst::pex::exceptions::InvalidParameter if the requested
378 subRegion is not fully contained within the original
379 MaskedImage.
381 """
382 #
383 # This subExposure is valid
384 #
385 subBBox = lsst.geom.Box2I(lsst.geom.Point2I(40, 50),
386 lsst.geom.Extent2I(10, 10))
387 subExposure = self.exposureCrWcs.Factory(
388 self.exposureCrWcs, subBBox, afwImage.LOCAL)
390 self.checkWcs(self.exposureCrWcs, subExposure)
392 # this subRegion is not valid and should trigger an exception
393 # from the MaskedImage class and should trigger an exception
394 # from the WCS class for the MaskedImage 871034p_1_MI.
396 subRegion3 = lsst.geom.Box2I(lsst.geom.Point2I(100, 100),
397 lsst.geom.Extent2I(10, 10))
399 def getSubRegion():
400 self.exposureCrWcs.Factory(
401 self.exposureCrWcs, subRegion3, afwImage.LOCAL)
403 self.assertRaises(pexExcept.LengthError, getSubRegion)
405 # this subRegion is not valid and should trigger an exception
406 # from the MaskedImage class only for the MaskedImage small_MI.
407 # small_MI (cols, rows) = (256, 256)
409 subRegion4 = lsst.geom.Box2I(lsst.geom.Point2I(250, 250),
410 lsst.geom.Extent2I(10, 10))
412 def getSubRegion():
413 self.exposureCrWcs.Factory(
414 self.exposureCrWcs, subRegion4, afwImage.LOCAL)
416 self.assertRaises(pexExcept.LengthError, getSubRegion)
418 # check the sub- and parent- exposures are using the same Wcs
419 # transformation
420 subBBox = lsst.geom.Box2I(lsst.geom.Point2I(40, 50),
421 lsst.geom.Extent2I(10, 10))
422 subExposure = self.exposureCrWcs.Factory(
423 self.exposureCrWcs, subBBox, afwImage.LOCAL)
424 parentSkyPos = self.exposureCrWcs.getWcs().pixelToSky(0, 0)
426 subExpSkyPos = subExposure.getWcs().pixelToSky(0, 0)
428 self.assertSpherePointsAlmostEqual(parentSkyPos, subExpSkyPos, msg="Wcs in sub image has changed")
430 def testReadWriteFits(self):
431 """Test readFits and writeFits.
432 """
433 # This should pass without an exception
434 mainExposure = afwImage.ExposureF(inFilePathSmall)
435 mainExposure.setDetector(self.detector)
437 subBBox = lsst.geom.Box2I(lsst.geom.Point2I(10, 10),
438 lsst.geom.Extent2I(40, 50))
439 subExposure = mainExposure.Factory(
440 mainExposure, subBBox, afwImage.LOCAL)
441 self.checkWcs(mainExposure, subExposure)
442 det = subExposure.getDetector()
443 self.assertTrue(det)
445 subExposure = afwImage.ExposureF(
446 inFilePathSmall, subBBox, afwImage.LOCAL)
448 self.checkWcs(mainExposure, subExposure)
450 # This should throw an exception
451 def getExposure():
452 afwImage.ExposureF(inFilePathSmallImage)
454 self.assertRaises(FitsError, getExposure)
456 mainExposure.setPsf(self.psf)
458 # Make sure we can write without an exception
459 photoCalib = afwImage.PhotoCalib(1e-10, 1e-12)
460 mainExposure.setPhotoCalib(photoCalib)
462 mainInfo = mainExposure.getInfo()
463 for key, value in self.extras.items():
464 mainInfo.setComponent(key, value)
466 with lsst.utils.tests.getTempFilePath(".fits") as tmpFile:
467 mainExposure.writeFits(tmpFile)
469 readExposure = type(mainExposure)(tmpFile)
471 #
472 # Check the round-tripping
473 #
474 self.assertEqual(mainExposure.getFilter().getName(),
475 readExposure.getFilter().getName())
476 self.assertIsNotNone(mainExposure.getFilterLabel())
477 self.assertEqual(mainExposure.getFilterLabel(),
478 readExposure.getFilterLabel())
480 self.assertEqual(photoCalib, readExposure.getPhotoCalib())
482 readInfo = readExposure.getInfo()
483 for key, value in self.extras.items():
484 self.assertEqual(value, readInfo.getComponent(key))
486 psf = readExposure.getPsf()
487 self.assertIsNotNone(psf)
488 self.assertEqual(psf, self.psf)
490 def checkWcs(self, parentExposure, subExposure):
491 """Compare WCS at corner points of a sub-exposure and its parent exposure
492 By using the function indexToPosition, we should be able to convert the indices
493 (of the four corners (of the sub-exposure)) to positions and use the wcs
494 to get the same sky coordinates for each.
495 """
496 subMI = subExposure.getMaskedImage()
497 subDim = subMI.getDimensions()
499 # Note: pixel positions must be computed relative to XY0 when working
500 # with WCS
501 mainWcs = parentExposure.getWcs()
502 subWcs = subExposure.getWcs()
504 for xSubInd in (0, subDim.getX()-1):
505 for ySubInd in (0, subDim.getY()-1):
506 self.assertSpherePointsAlmostEqual(
507 mainWcs.pixelToSky(
508 afwImage.indexToPosition(xSubInd),
509 afwImage.indexToPosition(ySubInd),
510 ),
511 subWcs.pixelToSky(
512 afwImage.indexToPosition(xSubInd),
513 afwImage.indexToPosition(ySubInd),
514 ))
516 def cmpExposure(self, e1, e2):
517 self.assertEqual(e1.getDetector().getName(),
518 e2.getDetector().getName())
519 self.assertEqual(e1.getDetector().getSerial(),
520 e2.getDetector().getSerial())
521 self.assertEqual(e1.getFilter().getName(), e2.getFilter().getName())
522 self.assertEqual(e1.getFilterLabel(), e2.getFilterLabel())
523 xy = lsst.geom.Point2D(0, 0)
524 self.assertEqual(e1.getWcs().pixelToSky(xy)[0],
525 e2.getWcs().pixelToSky(xy)[0])
526 self.assertEqual(e1.getPhotoCalib(), e2.getPhotoCalib())
527 # check PSF identity
528 if not e1.getPsf():
529 self.assertFalse(e2.getPsf())
530 else:
531 self.assertEqual(e1.getPsf(), e2.getPsf())
532 # Check extra components
533 i1 = e1.getInfo()
534 i2 = e2.getInfo()
535 for key in self.extras:
536 self.assertEqual(i1.hasComponent(key), i2.hasComponent(key))
537 if i1.hasComponent(key):
538 self.assertEqual(i1.getComponent(key), i2.getComponent(key))
540 def testCopyExposure(self):
541 """Copy an Exposure (maybe changing type)"""
543 exposureU = afwImage.ExposureU(inFilePathSmall, allowUnsafe=True)
544 exposureU.setWcs(self.wcs)
545 exposureU.setDetector(self.detector)
546 exposureU.setFilter(afwImage.Filter("g"))
547 exposureU.setFilterLabel(afwImage.FilterLabel(band="g"))
548 exposureU.setPsf(DummyPsf(4.0))
549 infoU = exposureU.getInfo()
550 for key, value in self.extras.items():
551 infoU.setComponent(key, value)
553 exposureF = exposureU.convertF()
554 self.cmpExposure(exposureF, exposureU)
556 nexp = exposureF.Factory(exposureF, False)
557 self.cmpExposure(exposureF, nexp)
559 # Ensure that the copy was deep.
560 # (actually this test is invalid since getDetector() returns a shared_ptr)
561 # cen0 = exposureU.getDetector().getCenterPixel()
562 # x0,y0 = cen0
563 # det = exposureF.getDetector()
564 # det.setCenterPixel(lsst.geom.Point2D(999.0, 437.8))
565 # self.assertEqual(exposureU.getDetector().getCenterPixel()[0], x0)
566 # self.assertEqual(exposureU.getDetector().getCenterPixel()[1], y0)
568 def testDeepCopyData(self):
569 """Make sure a deep copy of an Exposure has its own data (ticket #2625)
570 """
571 exp = afwImage.ExposureF(6, 7)
572 mi = exp.getMaskedImage()
573 mi.getImage().set(100)
574 mi.getMask().set(5)
575 mi.getVariance().set(200)
577 expCopy = exp.clone()
578 miCopy = expCopy.getMaskedImage()
579 miCopy.getImage().set(-50)
580 miCopy.getMask().set(2)
581 miCopy.getVariance().set(175)
583 self.assertFloatsAlmostEqual(miCopy.getImage().getArray(), -50)
584 self.assertTrue(np.all(miCopy.getMask().getArray() == 2))
585 self.assertFloatsAlmostEqual(miCopy.getVariance().getArray(), 175)
587 self.assertFloatsAlmostEqual(mi.getImage().getArray(), 100)
588 self.assertTrue(np.all(mi.getMask().getArray() == 5))
589 self.assertFloatsAlmostEqual(mi.getVariance().getArray(), 200)
591 def testDeepCopySubData(self):
592 """Make sure a deep copy of a subregion of an Exposure has its own data (ticket #2625)
593 """
594 exp = afwImage.ExposureF(6, 7)
595 mi = exp.getMaskedImage()
596 mi.getImage().set(100)
597 mi.getMask().set(5)
598 mi.getVariance().set(200)
600 bbox = lsst.geom.Box2I(lsst.geom.Point2I(1, 0), lsst.geom.Extent2I(5, 4))
601 expCopy = exp.Factory(exp, bbox, afwImage.PARENT, True)
602 miCopy = expCopy.getMaskedImage()
603 miCopy.getImage().set(-50)
604 miCopy.getMask().set(2)
605 miCopy.getVariance().set(175)
607 self.assertFloatsAlmostEqual(miCopy.getImage().getArray(), -50)
608 self.assertTrue(np.all(miCopy.getMask().getArray() == 2))
609 self.assertFloatsAlmostEqual(miCopy.getVariance().getArray(), 175)
611 self.assertFloatsAlmostEqual(mi.getImage().getArray(), 100)
612 self.assertTrue(np.all(mi.getMask().getArray() == 5))
613 self.assertFloatsAlmostEqual(mi.getVariance().getArray(), 200)
615 def testDeepCopyMetadata(self):
616 """Make sure a deep copy of an Exposure has a deep copy of metadata (ticket #2568)
617 """
618 exp = afwImage.ExposureF(10, 10)
619 expMeta = exp.getMetadata()
620 expMeta.set("foo", 5)
621 expCopy = exp.clone()
622 expCopyMeta = expCopy.getMetadata()
623 expCopyMeta.set("foo", 6)
624 self.assertEqual(expCopyMeta.getScalar("foo"), 6)
625 # this will fail if the bug is present
626 self.assertEqual(expMeta.getScalar("foo"), 5)
628 def testDeepCopySubMetadata(self):
629 """Make sure a deep copy of a subregion of an Exposure has a deep copy of metadata (ticket #2568)
630 """
631 exp = afwImage.ExposureF(10, 10)
632 expMeta = exp.getMetadata()
633 expMeta.set("foo", 5)
634 bbox = lsst.geom.Box2I(lsst.geom.Point2I(1, 0), lsst.geom.Extent2I(5, 5))
635 expCopy = exp.Factory(exp, bbox, afwImage.PARENT, True)
636 expCopyMeta = expCopy.getMetadata()
637 expCopyMeta.set("foo", 6)
638 self.assertEqual(expCopyMeta.getScalar("foo"), 6)
639 # this will fail if the bug is present
640 self.assertEqual(expMeta.getScalar("foo"), 5)
642 def testMakeExposureLeaks(self):
643 """Test for memory leaks in makeExposure (the test is in lsst.utils.tests.MemoryTestCase)"""
644 afwImage.makeMaskedImage(afwImage.ImageU(lsst.geom.Extent2I(10, 20)))
645 afwImage.makeExposure(afwImage.makeMaskedImage(
646 afwImage.ImageU(lsst.geom.Extent2I(10, 20))))
648 def testImageSlices(self):
649 """Test image slicing, which generate sub-images using Box2I under the covers"""
650 exp = afwImage.ExposureF(10, 20)
651 mi = exp.getMaskedImage()
652 mi.image[9, 19] = 10
653 # N.b. Exposures don't support setting/getting the pixels so can't
654 # replicate e.g. Image's slice tests
655 sexp = exp[1:4, 6:10]
656 self.assertEqual(sexp.getDimensions(), lsst.geom.ExtentI(3, 4))
657 sexp = exp[:, -3:, afwImage.LOCAL]
658 self.assertEqual(sexp.getDimensions(),
659 lsst.geom.ExtentI(exp.getWidth(), 3))
660 self.assertEqual(sexp.maskedImage[-1, -1, afwImage.LOCAL],
661 exp.maskedImage[-1, -1, afwImage.LOCAL])
663 def testConversionToScalar(self):
664 """Test that even 1-pixel Exposures can't be converted to scalars"""
665 im = afwImage.ExposureF(10, 20)
667 # only single pixel images may be converted
668 self.assertRaises(TypeError, float, im)
669 # actually, can't convert (img, msk, var) to scalar
670 self.assertRaises(TypeError, float, im[0, 0])
672 def testReadMetadata(self):
673 with lsst.utils.tests.getTempFilePath(".fits") as tmpFile:
674 self.exposureCrWcs.getMetadata().set("FRAZZLE", True)
675 # This will write the main metadata (inc. FRAZZLE) to the primary HDU, and the
676 # WCS to subsequent HDUs, along with INHERIT=T.
677 self.exposureCrWcs.writeFits(tmpFile)
678 # This should read the first non-empty HDU (i.e. it skips the primary), but
679 # goes back and reads it if it finds INHERIT=T. That should let us read
680 # frazzle and the Wcs from the PropertySet returned by
681 # testReadMetadata.
682 md = readMetadata(tmpFile)
683 wcs = afwGeom.makeSkyWcs(md, False)
684 self.assertPairsAlmostEqual(wcs.getPixelOrigin(), self.wcs.getPixelOrigin())
685 self.assertSpherePointsAlmostEqual(wcs.getSkyOrigin(), self.wcs.getSkyOrigin())
686 assert_allclose(wcs.getCdMatrix(), self.wcs.getCdMatrix(), atol=1e-10)
687 frazzle = md.getScalar("FRAZZLE")
688 self.assertTrue(frazzle)
690 def testArchiveKeys(self):
691 with lsst.utils.tests.getTempFilePath(".fits") as tmpFile:
692 exposure1 = afwImage.ExposureF(100, 100, self.wcs)
693 exposure1.setPsf(self.psf)
694 exposure1.writeFits(tmpFile)
695 exposure2 = afwImage.ExposureF(tmpFile)
696 self.assertFalse(exposure2.getMetadata().exists("AR_ID"))
697 self.assertFalse(exposure2.getMetadata().exists("PSF_ID"))
698 self.assertFalse(exposure2.getMetadata().exists("WCS_ID"))
700 def testTicket2861(self):
701 with lsst.utils.tests.getTempFilePath(".fits") as tmpFile:
702 exposure1 = afwImage.ExposureF(100, 100, self.wcs)
703 exposure1.setPsf(self.psf)
704 schema = afwTable.ExposureTable.makeMinimalSchema()
705 coaddInputs = afwImage.CoaddInputs(schema, schema)
706 exposure1.getInfo().setCoaddInputs(coaddInputs)
707 exposure2 = afwImage.ExposureF(exposure1, True)
708 self.assertIsNotNone(exposure2.getInfo().getCoaddInputs())
709 exposure2.writeFits(tmpFile)
710 exposure3 = afwImage.ExposureF(tmpFile)
711 self.assertIsNotNone(exposure3.getInfo().getCoaddInputs())
713 def testGetCutout(self):
714 wcs = self.smallExposure.getWcs()
716 dimensions = [lsst.geom.Extent2I(100, 50), lsst.geom.Extent2I(15, 15), lsst.geom.Extent2I(0, 10),
717 lsst.geom.Extent2I(25, 30), lsst.geom.Extent2I(15, -5),
718 2*self.smallExposure.getDimensions()]
719 locations = [("center", self._getExposureCenter(self.smallExposure)),
720 ("edge", wcs.pixelToSky(lsst.geom.Point2D(0, 0))),
721 ("rounding test", wcs.pixelToSky(lsst.geom.Point2D(0.2, 0.7))),
722 ("just inside", wcs.pixelToSky(lsst.geom.Point2D(-0.5 + 1e-4, -0.5 + 1e-4))),
723 ("just outside", wcs.pixelToSky(lsst.geom.Point2D(-0.5 - 1e-4, -0.5 - 1e-4))),
724 ("outside", wcs.pixelToSky(lsst.geom.Point2D(-1000, -1000)))]
725 for cutoutSize in dimensions:
726 for label, cutoutCenter in locations:
727 msg = 'Cutout size = %s, location = %s' % (cutoutSize, label)
728 if "outside" not in label and all(cutoutSize.gt(0)):
729 cutout = self.smallExposure.getCutout(cutoutCenter, cutoutSize)
730 centerInPixels = wcs.skyToPixel(cutoutCenter)
731 precision = (1 + 1e-4)*np.sqrt(0.5)*wcs.getPixelScale(centerInPixels)
732 self._checkCutoutProperties(cutout, cutoutSize, cutoutCenter, precision, msg)
733 self._checkCutoutPixels(
734 cutout,
735 self._getValidCorners(self.smallExposure.getBBox(), cutout.getBBox()),
736 msg)
738 # Need a valid WCS
739 with self.assertRaises(pexExcept.LogicError, msg=msg):
740 self.exposureMiOnly.getCutout(cutoutCenter, cutoutSize)
741 else:
742 with self.assertRaises(pexExcept.InvalidParameterError, msg=msg):
743 self.smallExposure.getCutout(cutoutCenter, cutoutSize)
745 def _checkCutoutProperties(self, cutout, size, center, precision, msg):
746 """Test whether a cutout has the desired size and position.
748 Parameters
749 ----------
750 cutout : `lsst.afw.image.Exposure`
751 The cutout to test.
752 size : `lsst.geom.Extent2I`
753 The expected dimensions of ``cutout``.
754 center : `lsst.geom.SpherePoint`
755 The expected center of ``cutout``.
756 precision : `lsst.geom.Angle`
757 The precision to which ``center`` must match.
758 msg : `str`
759 An error message suffix describing test parameters.
760 """
761 newCenter = self._getExposureCenter(cutout)
762 self.assertIsNotNone(cutout, msg=msg)
763 self.assertSpherePointsAlmostEqual(newCenter, center, maxSep=precision, msg=msg)
764 self.assertEqual(cutout.getWidth(), size[0], msg=msg)
765 self.assertEqual(cutout.getHeight(), size[1], msg=msg)
767 def _checkCutoutPixels(self, cutout, validCorners, msg):
768 """Test whether a cutout has valid/empty pixels where expected.
770 Parameters
771 ----------
772 cutout : `lsst.afw.image.Exposure`
773 The cutout to test.
774 validCorners : iterable of `lsst.geom.Point2I`
775 The corners of ``cutout`` that should be drawn from the original image.
776 msg : `str`
777 An error message suffix describing test parameters.
778 """
779 mask = cutout.getMaskedImage().getMask()
780 edgeMask = mask.getPlaneBitMask("NO_DATA")
782 for corner in cutout.getBBox().getCorners():
783 maskBitsSet = mask[corner] & edgeMask
784 if corner in validCorners:
785 self.assertEqual(maskBitsSet, 0, msg=msg)
786 else:
787 self.assertEqual(maskBitsSet, edgeMask, msg=msg)
789 def _getExposureCenter(self, exposure):
790 """Return the sky coordinates of an Exposure's center.
792 Parameters
793 ----------
794 exposure : `lsst.afw.image.Exposure`
795 The image whose center is desired.
797 Returns
798 -------
799 center : `lsst.geom.SpherePoint`
800 The position at the center of ``exposure``.
801 """
802 return exposure.getWcs().pixelToSky(lsst.geom.Box2D(exposure.getBBox()).getCenter())
804 def _getValidCorners(self, imageBox, cutoutBox):
805 """Return the corners of a cutout that are constrained by the original image.
807 Parameters
808 ----------
809 imageBox: `lsst.geom.Extent2I`
810 The bounding box of the original image.
811 cutoutBox : `lsst.geom.Box2I`
812 The bounding box of the cutout.
814 Returns
815 -------
816 corners : iterable of `lsst.geom.Point2I`
817 The corners that are drawn from the original image.
818 """
819 return [corner for corner in cutoutBox.getCorners() if corner in imageBox]
822class ExposureInfoTestCase(lsst.utils.tests.TestCase):
823 def setUp(self):
824 super().setUp()
826 afwImage.Filter.reset()
827 afwImage.FilterProperty.reset()
828 defineFilter("g", 470.0)
830 self.wcs = afwGeom.makeSkyWcs(lsst.geom.Point2D(0.0, 0.0),
831 lsst.geom.SpherePoint(2.0, 34.0, lsst.geom.degrees),
832 np.identity(2),
833 )
834 self.photoCalib = afwImage.PhotoCalib(1.5)
835 self.psf = DummyPsf(2.0)
836 self.detector = DetectorWrapper().detector
837 self.polygon = afwGeom.Polygon(lsst.geom.Box2D(lsst.geom.Point2D(0.0, 0.0),
838 lsst.geom.Point2D(25.0, 20.0)))
839 self.coaddInputs = afwImage.CoaddInputs()
840 self.apCorrMap = afwImage.ApCorrMap()
841 self.transmissionCurve = afwImage.TransmissionCurve.makeIdentity()
843 self.exposureInfo = afwImage.ExposureInfo()
844 gFilter = afwImage.Filter("g")
845 gFilterLabel = afwImage.FilterLabel(band="g")
846 self.exposureInfo.setFilter(gFilter)
847 self.exposureInfo.setFilterLabel(gFilterLabel)
849 def _checkAlias(self, exposureInfo, key, value, has, get):
850 self.assertFalse(has())
851 self.assertFalse(exposureInfo.hasComponent(key))
852 self.assertIsNone(get())
853 self.assertIsNone(exposureInfo.getComponent(key))
855 self.exposureInfo.setComponent(key, value)
856 self.assertTrue(has())
857 self.assertTrue(exposureInfo.hasComponent(key))
858 self.assertIsNotNone(get())
859 self.assertIsNotNone(exposureInfo.getComponent(key))
860 self.assertEqual(get(), value)
861 self.assertEqual(exposureInfo.getComponent(key), value)
863 self.exposureInfo.removeComponent(key)
864 self.assertFalse(has())
865 self.assertFalse(exposureInfo.hasComponent(key))
866 self.assertIsNone(get())
867 self.assertIsNone(exposureInfo.getComponent(key))
869 def testAliases(self):
870 cls = type(self.exposureInfo)
871 self._checkAlias(self.exposureInfo, cls.KEY_WCS, self.wcs,
872 self.exposureInfo.hasWcs, self.exposureInfo.getWcs)
873 self._checkAlias(self.exposureInfo, cls.KEY_PSF, self.psf,
874 self.exposureInfo.hasPsf, self.exposureInfo.getPsf)
875 self._checkAlias(self.exposureInfo, cls.KEY_PHOTO_CALIB, self.photoCalib,
876 self.exposureInfo.hasPhotoCalib, self.exposureInfo.getPhotoCalib)
877 self._checkAlias(self.exposureInfo, cls.KEY_DETECTOR, self.detector,
878 self.exposureInfo.hasDetector, self.exposureInfo.getDetector)
879 self._checkAlias(self.exposureInfo, cls.KEY_VALID_POLYGON, self.polygon,
880 self.exposureInfo.hasValidPolygon, self.exposureInfo.getValidPolygon)
881 self._checkAlias(self.exposureInfo, cls.KEY_COADD_INPUTS, self.coaddInputs,
882 self.exposureInfo.hasCoaddInputs, self.exposureInfo.getCoaddInputs)
883 self._checkAlias(self.exposureInfo, cls.KEY_AP_CORR_MAP, self.apCorrMap,
884 self.exposureInfo.hasApCorrMap, self.exposureInfo.getApCorrMap)
885 self._checkAlias(self.exposureInfo, cls.KEY_TRANSMISSION_CURVE, self.transmissionCurve,
886 self.exposureInfo.hasTransmissionCurve, self.exposureInfo.getTransmissionCurve)
888 def testCopy(self):
889 # Test that ExposureInfos have independently settable state
890 copy = afwImage.ExposureInfo(self.exposureInfo, True)
891 self.assertEqual(self.exposureInfo.getWcs(), copy.getWcs())
893 newWcs = afwGeom.makeSkyWcs(lsst.geom.Point2D(-23.0, 8.0),
894 lsst.geom.SpherePoint(0.0, 0.0, lsst.geom.degrees),
895 np.identity(2),
896 )
897 copy.setWcs(newWcs)
898 self.assertEqual(copy.getWcs(), newWcs)
899 self.assertNotEqual(self.exposureInfo.getWcs(), copy.getWcs())
902class ExposureNoAfwdataTestCase(lsst.utils.tests.TestCase):
903 """Tests of Exposure that don't require afwdata.
905 These tests use the trivial exposures written to ``afw/tests/data``.
906 """
907 def setUp(self):
908 self.dataDir = os.path.join(os.path.split(__file__)[0], "data")
910 # Check the values below against what was written by comparing with
911 # the code in `afw/tests/data/makeTestExposure.py`
912 nx = ny = 10
913 image = afwImage.ImageF(np.arange(nx*ny, dtype='f').reshape(nx, ny))
914 variance = afwImage.ImageF(np.ones((nx, ny), dtype='f'))
915 mask = afwImage.MaskX(nx, ny)
916 mask.array[5, 5] = 5
917 self.maskedImage = afwImage.MaskedImageF(image, mask, variance)
919 self.v0PhotoCalib = afwImage.makePhotoCalibFromCalibZeroPoint(1e6, 2e4)
920 self.v1PhotoCalib = afwImage.PhotoCalib(1e6, 2e4)
921 self.v1FilterLabel = afwImage.FilterLabel(physical="ha")
922 self.v2FilterLabel = afwImage.FilterLabel(band="N656", physical="ha")
924 def testReadUnversioned(self):
925 """Test that we can read an unversioned (implicit verison 0) file.
926 """
927 filename = os.path.join(self.dataDir, "exposure-noversion.fits")
928 exposure = afwImage.ExposureF.readFits(filename)
930 self.assertMaskedImagesEqual(exposure.maskedImage, self.maskedImage)
932 self.assertEqual(exposure.getPhotoCalib(), self.v0PhotoCalib)
933 self.assertEqual(exposure.getFilterLabel(), self.v1FilterLabel)
935 def testReadVersion0(self):
936 """Test that we can read a version 0 file.
937 This file should be identical to the unversioned one, except that it
938 is marked as ExposureInfo version 0 in the header.
939 """
940 filename = os.path.join(self.dataDir, "exposure-version-0.fits")
941 exposure = afwImage.ExposureF.readFits(filename)
943 self.assertMaskedImagesEqual(exposure.maskedImage, self.maskedImage)
945 self.assertEqual(exposure.getPhotoCalib(), self.v0PhotoCalib)
946 self.assertEqual(exposure.getFilterLabel(), self.v1FilterLabel)
948 # Check that the metadata reader parses the file correctly
949 reader = afwImage.ExposureFitsReader(filename)
950 self.assertEqual(reader.readExposureInfo().getPhotoCalib(), self.v0PhotoCalib)
951 self.assertEqual(reader.readPhotoCalib(), self.v0PhotoCalib)
953 def testReadVersion1(self):
954 """Test that we can read a version 1 file.
955 Version 1 replaced Calib with PhotoCalib.
956 """
957 filename = os.path.join(self.dataDir, "exposure-version-1.fits")
958 exposure = afwImage.ExposureF.readFits(filename)
960 self.assertMaskedImagesEqual(exposure.maskedImage, self.maskedImage)
962 self.assertEqual(exposure.getPhotoCalib(), self.v1PhotoCalib)
963 self.assertEqual(exposure.getFilterLabel(), self.v1FilterLabel)
965 # Check that the metadata reader parses the file correctly
966 reader = afwImage.ExposureFitsReader(filename)
967 self.assertEqual(reader.readExposureInfo().getPhotoCalib(), self.v1PhotoCalib)
968 self.assertEqual(reader.readPhotoCalib(), self.v1PhotoCalib)
970 def testReadVersion2(self):
971 """Test that we can read a version 2 file.
972 Version 2 replaced Filter with FilterLabel.
973 """
974 filename = os.path.join(self.dataDir, "exposure-version-2.fits")
975 exposure = afwImage.ExposureF.readFits(filename)
977 self.assertMaskedImagesEqual(exposure.maskedImage, self.maskedImage)
979 self.assertEqual(exposure.getPhotoCalib(), self.v1PhotoCalib)
980 self.assertEqual(exposure.getFilterLabel(), self.v2FilterLabel)
982 # Check that the metadata reader parses the file correctly
983 reader = afwImage.ExposureFitsReader(filename)
984 self.assertEqual(reader.readExposureInfo().getPhotoCalib(), self.v1PhotoCalib)
985 self.assertEqual(reader.readPhotoCalib(), self.v1PhotoCalib)
988class MemoryTester(lsst.utils.tests.MemoryTestCase):
989 pass
992def setup_module(module):
993 lsst.utils.tests.init()
996if __name__ == "__main__": 996 ↛ 997line 996 didn't jump to line 997, because the condition on line 996 was never true
997 lsst.utils.tests.init()
998 unittest.main()