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 testDefaultFilter(self):
278 """Test that old convention of having a "default" filter replaced with `None`.
279 """
280 exposureInfo = afwImage.ExposureInfo()
281 noFilter = afwImage.Filter()
282 exposureInfo.setFilter(noFilter)
283 self.assertFalse(exposureInfo.hasFilterLabel())
284 self.assertIsNone(exposureInfo.getFilterLabel())
286 def testVisitInfoFitsPersistence(self):
287 """Test saving an exposure to FITS and reading it back in preserves (some) VisitInfo fields"""
288 exposureId = 5
289 exposureTime = 12.3
290 boresightRotAngle = 45.6 * lsst.geom.degrees
291 weather = Weather(1.1, 2.2, 0.3)
292 visitInfo = afwImage.VisitInfo(
293 exposureId=exposureId,
294 exposureTime=exposureTime,
295 boresightRotAngle=boresightRotAngle,
296 weather=weather,
297 )
298 photoCalib = afwImage.PhotoCalib(3.4, 5.6)
299 exposureInfo = afwImage.ExposureInfo()
300 exposureInfo.setVisitInfo(visitInfo)
301 exposureInfo.setPhotoCalib(photoCalib)
302 exposureInfo.setDetector(self.detector)
303 gFilter = afwImage.Filter("g")
304 gFilterLabel = afwImage.FilterLabel(band="g")
305 exposureInfo.setFilter(gFilter)
306 exposureInfo.setFilterLabel(gFilterLabel)
307 maskedImage = afwImage.MaskedImageF(inFilePathSmall)
308 exposure = afwImage.ExposureF(maskedImage, exposureInfo)
309 with lsst.utils.tests.getTempFilePath(".fits") as tmpFile:
310 exposure.writeFits(tmpFile)
311 rtExposure = afwImage.ExposureF(tmpFile)
312 rtVisitInfo = rtExposure.getInfo().getVisitInfo()
313 self.assertEqual(rtVisitInfo.getWeather(), weather)
314 self.assertEqual(rtExposure.getPhotoCalib(), photoCalib)
315 self.assertEqual(rtExposure.getFilter(), gFilter)
316 self.assertEqual(rtExposure.getFilterLabel(), gFilterLabel)
318 def testSetMembers(self):
319 """
320 Test that the MaskedImage and the WCS of an Exposure can be set.
321 """
322 exposure = afwImage.ExposureF()
324 maskedImage = afwImage.MaskedImageF(inFilePathSmall)
325 exposure.setMaskedImage(maskedImage)
326 exposure.setWcs(self.wcs)
327 exposure.setDetector(self.detector)
328 exposure.setFilter(afwImage.Filter("g"))
329 exposure.setFilterLabel(afwImage.FilterLabel(band="g"))
331 self.assertEqual(exposure.getDetector().getName(),
332 self.detector.getName())
333 self.assertEqual(exposure.getDetector().getSerial(),
334 self.detector.getSerial())
335 self.assertEqual(exposure.getFilter().getName(), "g")
336 self.assertEqual(exposure.getFilterLabel().bandLabel, "g")
337 self.assertEqual(exposure.getWcs(), self.wcs)
339 # The PhotoCalib tests are in test_photoCalib.py;
340 # here we just check that it's gettable and settable.
341 self.assertIsNone(exposure.getPhotoCalib())
343 photoCalib = afwImage.PhotoCalib(511.1, 44.4)
344 exposure.setPhotoCalib(photoCalib)
345 self.assertEqual(exposure.getPhotoCalib(), photoCalib)
347 # Psfs next
348 self.assertFalse(exposure.hasPsf())
349 exposure.setPsf(self.psf)
350 self.assertTrue(exposure.hasPsf())
352 exposure.setPsf(DummyPsf(1.0)) # we can reset the Psf
354 # extras next
355 info = exposure.getInfo()
356 for key, value in self.extras.items():
357 self.assertFalse(info.hasComponent(key))
358 self.assertIsNone(info.getComponent(key))
359 info.setComponent(key, value)
360 self.assertTrue(info.hasComponent(key))
361 self.assertEqual(info.getComponent(key), value)
362 info.removeComponent(key)
363 self.assertFalse(info.hasComponent(key))
365 # Test that we can set the MaskedImage and WCS of an Exposure
366 # that already has both
367 self.exposureMiWcs.setMaskedImage(maskedImage)
368 exposure.setWcs(self.wcs)
370 def testHasWcs(self):
371 """
372 Test if an Exposure has a WCS or not.
373 """
374 self.assertFalse(self.exposureBlank.hasWcs())
376 self.assertFalse(self.exposureMiOnly.hasWcs())
377 self.assertTrue(self.exposureMiWcs.hasWcs())
378 self.assertTrue(self.exposureCrWcs.hasWcs())
379 self.assertFalse(self.exposureCrOnly.hasWcs())
381 def testGetSubExposure(self):
382 """
383 Test that a subExposure of the original Exposure can be obtained.
385 The MaskedImage class should throw a
386 lsst::pex::exceptions::InvalidParameter if the requested
387 subRegion is not fully contained within the original
388 MaskedImage.
390 """
391 #
392 # This subExposure is valid
393 #
394 subBBox = lsst.geom.Box2I(lsst.geom.Point2I(40, 50),
395 lsst.geom.Extent2I(10, 10))
396 subExposure = self.exposureCrWcs.Factory(
397 self.exposureCrWcs, subBBox, afwImage.LOCAL)
399 self.checkWcs(self.exposureCrWcs, subExposure)
401 # this subRegion is not valid and should trigger an exception
402 # from the MaskedImage class and should trigger an exception
403 # from the WCS class for the MaskedImage 871034p_1_MI.
405 subRegion3 = lsst.geom.Box2I(lsst.geom.Point2I(100, 100),
406 lsst.geom.Extent2I(10, 10))
408 def getSubRegion():
409 self.exposureCrWcs.Factory(
410 self.exposureCrWcs, subRegion3, afwImage.LOCAL)
412 self.assertRaises(pexExcept.LengthError, getSubRegion)
414 # this subRegion is not valid and should trigger an exception
415 # from the MaskedImage class only for the MaskedImage small_MI.
416 # small_MI (cols, rows) = (256, 256)
418 subRegion4 = lsst.geom.Box2I(lsst.geom.Point2I(250, 250),
419 lsst.geom.Extent2I(10, 10))
421 def getSubRegion():
422 self.exposureCrWcs.Factory(
423 self.exposureCrWcs, subRegion4, afwImage.LOCAL)
425 self.assertRaises(pexExcept.LengthError, getSubRegion)
427 # check the sub- and parent- exposures are using the same Wcs
428 # transformation
429 subBBox = lsst.geom.Box2I(lsst.geom.Point2I(40, 50),
430 lsst.geom.Extent2I(10, 10))
431 subExposure = self.exposureCrWcs.Factory(
432 self.exposureCrWcs, subBBox, afwImage.LOCAL)
433 parentSkyPos = self.exposureCrWcs.getWcs().pixelToSky(0, 0)
435 subExpSkyPos = subExposure.getWcs().pixelToSky(0, 0)
437 self.assertSpherePointsAlmostEqual(parentSkyPos, subExpSkyPos, msg="Wcs in sub image has changed")
439 def testReadWriteFits(self):
440 """Test readFits and writeFits.
441 """
442 # This should pass without an exception
443 mainExposure = afwImage.ExposureF(inFilePathSmall)
444 mainExposure.setDetector(self.detector)
446 subBBox = lsst.geom.Box2I(lsst.geom.Point2I(10, 10),
447 lsst.geom.Extent2I(40, 50))
448 subExposure = mainExposure.Factory(
449 mainExposure, subBBox, afwImage.LOCAL)
450 self.checkWcs(mainExposure, subExposure)
451 det = subExposure.getDetector()
452 self.assertTrue(det)
454 subExposure = afwImage.ExposureF(
455 inFilePathSmall, subBBox, afwImage.LOCAL)
457 self.checkWcs(mainExposure, subExposure)
459 # This should throw an exception
460 def getExposure():
461 afwImage.ExposureF(inFilePathSmallImage)
463 self.assertRaises(FitsError, getExposure)
465 mainExposure.setPsf(self.psf)
467 # Make sure we can write without an exception
468 photoCalib = afwImage.PhotoCalib(1e-10, 1e-12)
469 mainExposure.setPhotoCalib(photoCalib)
471 mainInfo = mainExposure.getInfo()
472 for key, value in self.extras.items():
473 mainInfo.setComponent(key, value)
475 with lsst.utils.tests.getTempFilePath(".fits") as tmpFile:
476 mainExposure.writeFits(tmpFile)
478 readExposure = type(mainExposure)(tmpFile)
480 #
481 # Check the round-tripping
482 #
483 self.assertEqual(mainExposure.getFilter().getName(),
484 readExposure.getFilter().getName())
485 self.assertIsNotNone(mainExposure.getFilterLabel())
486 self.assertEqual(mainExposure.getFilterLabel(),
487 readExposure.getFilterLabel())
489 self.assertEqual(photoCalib, readExposure.getPhotoCalib())
491 readInfo = readExposure.getInfo()
492 for key, value in self.extras.items():
493 self.assertEqual(value, readInfo.getComponent(key))
495 psf = readExposure.getPsf()
496 self.assertIsNotNone(psf)
497 self.assertEqual(psf, self.psf)
499 def checkWcs(self, parentExposure, subExposure):
500 """Compare WCS at corner points of a sub-exposure and its parent exposure
501 By using the function indexToPosition, we should be able to convert the indices
502 (of the four corners (of the sub-exposure)) to positions and use the wcs
503 to get the same sky coordinates for each.
504 """
505 subMI = subExposure.getMaskedImage()
506 subDim = subMI.getDimensions()
508 # Note: pixel positions must be computed relative to XY0 when working
509 # with WCS
510 mainWcs = parentExposure.getWcs()
511 subWcs = subExposure.getWcs()
513 for xSubInd in (0, subDim.getX()-1):
514 for ySubInd in (0, subDim.getY()-1):
515 self.assertSpherePointsAlmostEqual(
516 mainWcs.pixelToSky(
517 afwImage.indexToPosition(xSubInd),
518 afwImage.indexToPosition(ySubInd),
519 ),
520 subWcs.pixelToSky(
521 afwImage.indexToPosition(xSubInd),
522 afwImage.indexToPosition(ySubInd),
523 ))
525 def cmpExposure(self, e1, e2):
526 self.assertEqual(e1.getDetector().getName(),
527 e2.getDetector().getName())
528 self.assertEqual(e1.getDetector().getSerial(),
529 e2.getDetector().getSerial())
530 self.assertEqual(e1.getFilter().getName(), e2.getFilter().getName())
531 self.assertEqual(e1.getFilterLabel(), e2.getFilterLabel())
532 xy = lsst.geom.Point2D(0, 0)
533 self.assertEqual(e1.getWcs().pixelToSky(xy)[0],
534 e2.getWcs().pixelToSky(xy)[0])
535 self.assertEqual(e1.getPhotoCalib(), e2.getPhotoCalib())
536 # check PSF identity
537 if not e1.getPsf():
538 self.assertFalse(e2.getPsf())
539 else:
540 self.assertEqual(e1.getPsf(), e2.getPsf())
541 # Check extra components
542 i1 = e1.getInfo()
543 i2 = e2.getInfo()
544 for key in self.extras:
545 self.assertEqual(i1.hasComponent(key), i2.hasComponent(key))
546 if i1.hasComponent(key):
547 self.assertEqual(i1.getComponent(key), i2.getComponent(key))
549 def testCopyExposure(self):
550 """Copy an Exposure (maybe changing type)"""
552 exposureU = afwImage.ExposureU(inFilePathSmall, allowUnsafe=True)
553 exposureU.setWcs(self.wcs)
554 exposureU.setDetector(self.detector)
555 exposureU.setFilter(afwImage.Filter("g"))
556 exposureU.setFilterLabel(afwImage.FilterLabel(band="g"))
557 exposureU.setPsf(DummyPsf(4.0))
558 infoU = exposureU.getInfo()
559 for key, value in self.extras.items():
560 infoU.setComponent(key, value)
562 exposureF = exposureU.convertF()
563 self.cmpExposure(exposureF, exposureU)
565 nexp = exposureF.Factory(exposureF, False)
566 self.cmpExposure(exposureF, nexp)
568 # Ensure that the copy was deep.
569 # (actually this test is invalid since getDetector() returns a shared_ptr)
570 # cen0 = exposureU.getDetector().getCenterPixel()
571 # x0,y0 = cen0
572 # det = exposureF.getDetector()
573 # det.setCenterPixel(lsst.geom.Point2D(999.0, 437.8))
574 # self.assertEqual(exposureU.getDetector().getCenterPixel()[0], x0)
575 # self.assertEqual(exposureU.getDetector().getCenterPixel()[1], y0)
577 def testDeepCopyData(self):
578 """Make sure a deep copy of an Exposure has its own data (ticket #2625)
579 """
580 exp = afwImage.ExposureF(6, 7)
581 mi = exp.getMaskedImage()
582 mi.getImage().set(100)
583 mi.getMask().set(5)
584 mi.getVariance().set(200)
586 expCopy = exp.clone()
587 miCopy = expCopy.getMaskedImage()
588 miCopy.getImage().set(-50)
589 miCopy.getMask().set(2)
590 miCopy.getVariance().set(175)
592 self.assertFloatsAlmostEqual(miCopy.getImage().getArray(), -50)
593 self.assertTrue(np.all(miCopy.getMask().getArray() == 2))
594 self.assertFloatsAlmostEqual(miCopy.getVariance().getArray(), 175)
596 self.assertFloatsAlmostEqual(mi.getImage().getArray(), 100)
597 self.assertTrue(np.all(mi.getMask().getArray() == 5))
598 self.assertFloatsAlmostEqual(mi.getVariance().getArray(), 200)
600 def testDeepCopySubData(self):
601 """Make sure a deep copy of a subregion of an Exposure has its own data (ticket #2625)
602 """
603 exp = afwImage.ExposureF(6, 7)
604 mi = exp.getMaskedImage()
605 mi.getImage().set(100)
606 mi.getMask().set(5)
607 mi.getVariance().set(200)
609 bbox = lsst.geom.Box2I(lsst.geom.Point2I(1, 0), lsst.geom.Extent2I(5, 4))
610 expCopy = exp.Factory(exp, bbox, afwImage.PARENT, True)
611 miCopy = expCopy.getMaskedImage()
612 miCopy.getImage().set(-50)
613 miCopy.getMask().set(2)
614 miCopy.getVariance().set(175)
616 self.assertFloatsAlmostEqual(miCopy.getImage().getArray(), -50)
617 self.assertTrue(np.all(miCopy.getMask().getArray() == 2))
618 self.assertFloatsAlmostEqual(miCopy.getVariance().getArray(), 175)
620 self.assertFloatsAlmostEqual(mi.getImage().getArray(), 100)
621 self.assertTrue(np.all(mi.getMask().getArray() == 5))
622 self.assertFloatsAlmostEqual(mi.getVariance().getArray(), 200)
624 def testDeepCopyMetadata(self):
625 """Make sure a deep copy of an Exposure has a deep copy of metadata (ticket #2568)
626 """
627 exp = afwImage.ExposureF(10, 10)
628 expMeta = exp.getMetadata()
629 expMeta.set("foo", 5)
630 expCopy = exp.clone()
631 expCopyMeta = expCopy.getMetadata()
632 expCopyMeta.set("foo", 6)
633 self.assertEqual(expCopyMeta.getScalar("foo"), 6)
634 # this will fail if the bug is present
635 self.assertEqual(expMeta.getScalar("foo"), 5)
637 def testDeepCopySubMetadata(self):
638 """Make sure a deep copy of a subregion of an Exposure has a deep copy of metadata (ticket #2568)
639 """
640 exp = afwImage.ExposureF(10, 10)
641 expMeta = exp.getMetadata()
642 expMeta.set("foo", 5)
643 bbox = lsst.geom.Box2I(lsst.geom.Point2I(1, 0), lsst.geom.Extent2I(5, 5))
644 expCopy = exp.Factory(exp, bbox, afwImage.PARENT, True)
645 expCopyMeta = expCopy.getMetadata()
646 expCopyMeta.set("foo", 6)
647 self.assertEqual(expCopyMeta.getScalar("foo"), 6)
648 # this will fail if the bug is present
649 self.assertEqual(expMeta.getScalar("foo"), 5)
651 def testMakeExposureLeaks(self):
652 """Test for memory leaks in makeExposure (the test is in lsst.utils.tests.MemoryTestCase)"""
653 afwImage.makeMaskedImage(afwImage.ImageU(lsst.geom.Extent2I(10, 20)))
654 afwImage.makeExposure(afwImage.makeMaskedImage(
655 afwImage.ImageU(lsst.geom.Extent2I(10, 20))))
657 def testImageSlices(self):
658 """Test image slicing, which generate sub-images using Box2I under the covers"""
659 exp = afwImage.ExposureF(10, 20)
660 mi = exp.getMaskedImage()
661 mi.image[9, 19] = 10
662 # N.b. Exposures don't support setting/getting the pixels so can't
663 # replicate e.g. Image's slice tests
664 sexp = exp[1:4, 6:10]
665 self.assertEqual(sexp.getDimensions(), lsst.geom.ExtentI(3, 4))
666 sexp = exp[:, -3:, afwImage.LOCAL]
667 self.assertEqual(sexp.getDimensions(),
668 lsst.geom.ExtentI(exp.getWidth(), 3))
669 self.assertEqual(sexp.maskedImage[-1, -1, afwImage.LOCAL],
670 exp.maskedImage[-1, -1, afwImage.LOCAL])
672 def testConversionToScalar(self):
673 """Test that even 1-pixel Exposures can't be converted to scalars"""
674 im = afwImage.ExposureF(10, 20)
676 # only single pixel images may be converted
677 self.assertRaises(TypeError, float, im)
678 # actually, can't convert (img, msk, var) to scalar
679 self.assertRaises(TypeError, float, im[0, 0])
681 def testReadMetadata(self):
682 with lsst.utils.tests.getTempFilePath(".fits") as tmpFile:
683 self.exposureCrWcs.getMetadata().set("FRAZZLE", True)
684 # This will write the main metadata (inc. FRAZZLE) to the primary HDU, and the
685 # WCS to subsequent HDUs, along with INHERIT=T.
686 self.exposureCrWcs.writeFits(tmpFile)
687 # This should read the first non-empty HDU (i.e. it skips the primary), but
688 # goes back and reads it if it finds INHERIT=T. That should let us read
689 # frazzle and the Wcs from the PropertySet returned by
690 # testReadMetadata.
691 md = readMetadata(tmpFile)
692 wcs = afwGeom.makeSkyWcs(md, False)
693 self.assertPairsAlmostEqual(wcs.getPixelOrigin(), self.wcs.getPixelOrigin())
694 self.assertSpherePointsAlmostEqual(wcs.getSkyOrigin(), self.wcs.getSkyOrigin())
695 assert_allclose(wcs.getCdMatrix(), self.wcs.getCdMatrix(), atol=1e-10)
696 frazzle = md.getScalar("FRAZZLE")
697 self.assertTrue(frazzle)
699 def testArchiveKeys(self):
700 with lsst.utils.tests.getTempFilePath(".fits") as tmpFile:
701 exposure1 = afwImage.ExposureF(100, 100, self.wcs)
702 exposure1.setPsf(self.psf)
703 exposure1.writeFits(tmpFile)
704 exposure2 = afwImage.ExposureF(tmpFile)
705 self.assertFalse(exposure2.getMetadata().exists("AR_ID"))
706 self.assertFalse(exposure2.getMetadata().exists("PSF_ID"))
707 self.assertFalse(exposure2.getMetadata().exists("WCS_ID"))
709 def testTicket2861(self):
710 with lsst.utils.tests.getTempFilePath(".fits") as tmpFile:
711 exposure1 = afwImage.ExposureF(100, 100, self.wcs)
712 exposure1.setPsf(self.psf)
713 schema = afwTable.ExposureTable.makeMinimalSchema()
714 coaddInputs = afwImage.CoaddInputs(schema, schema)
715 exposure1.getInfo().setCoaddInputs(coaddInputs)
716 exposure2 = afwImage.ExposureF(exposure1, True)
717 self.assertIsNotNone(exposure2.getInfo().getCoaddInputs())
718 exposure2.writeFits(tmpFile)
719 exposure3 = afwImage.ExposureF(tmpFile)
720 self.assertIsNotNone(exposure3.getInfo().getCoaddInputs())
722 def testGetCutout(self):
723 wcs = self.smallExposure.getWcs()
725 dimensions = [lsst.geom.Extent2I(100, 50), lsst.geom.Extent2I(15, 15), lsst.geom.Extent2I(0, 10),
726 lsst.geom.Extent2I(25, 30), lsst.geom.Extent2I(15, -5),
727 2*self.smallExposure.getDimensions()]
728 locations = [("center", self._getExposureCenter(self.smallExposure)),
729 ("edge", wcs.pixelToSky(lsst.geom.Point2D(0, 0))),
730 ("rounding test", wcs.pixelToSky(lsst.geom.Point2D(0.2, 0.7))),
731 ("just inside", wcs.pixelToSky(lsst.geom.Point2D(-0.5 + 1e-4, -0.5 + 1e-4))),
732 ("just outside", wcs.pixelToSky(lsst.geom.Point2D(-0.5 - 1e-4, -0.5 - 1e-4))),
733 ("outside", wcs.pixelToSky(lsst.geom.Point2D(-1000, -1000)))]
734 for cutoutSize in dimensions:
735 for label, cutoutCenter in locations:
736 msg = 'Cutout size = %s, location = %s' % (cutoutSize, label)
737 if "outside" not in label and all(cutoutSize.gt(0)):
738 cutout = self.smallExposure.getCutout(cutoutCenter, cutoutSize)
739 centerInPixels = wcs.skyToPixel(cutoutCenter)
740 precision = (1 + 1e-4)*np.sqrt(0.5)*wcs.getPixelScale(centerInPixels)
741 self._checkCutoutProperties(cutout, cutoutSize, cutoutCenter, precision, msg)
742 self._checkCutoutPixels(
743 cutout,
744 self._getValidCorners(self.smallExposure.getBBox(), cutout.getBBox()),
745 msg)
747 # Need a valid WCS
748 with self.assertRaises(pexExcept.LogicError, msg=msg):
749 self.exposureMiOnly.getCutout(cutoutCenter, cutoutSize)
750 else:
751 with self.assertRaises(pexExcept.InvalidParameterError, msg=msg):
752 self.smallExposure.getCutout(cutoutCenter, cutoutSize)
754 def _checkCutoutProperties(self, cutout, size, center, precision, msg):
755 """Test whether a cutout has the desired size and position.
757 Parameters
758 ----------
759 cutout : `lsst.afw.image.Exposure`
760 The cutout to test.
761 size : `lsst.geom.Extent2I`
762 The expected dimensions of ``cutout``.
763 center : `lsst.geom.SpherePoint`
764 The expected center of ``cutout``.
765 precision : `lsst.geom.Angle`
766 The precision to which ``center`` must match.
767 msg : `str`
768 An error message suffix describing test parameters.
769 """
770 newCenter = self._getExposureCenter(cutout)
771 self.assertIsNotNone(cutout, msg=msg)
772 self.assertSpherePointsAlmostEqual(newCenter, center, maxSep=precision, msg=msg)
773 self.assertEqual(cutout.getWidth(), size[0], msg=msg)
774 self.assertEqual(cutout.getHeight(), size[1], msg=msg)
776 def _checkCutoutPixels(self, cutout, validCorners, msg):
777 """Test whether a cutout has valid/empty pixels where expected.
779 Parameters
780 ----------
781 cutout : `lsst.afw.image.Exposure`
782 The cutout to test.
783 validCorners : iterable of `lsst.geom.Point2I`
784 The corners of ``cutout`` that should be drawn from the original image.
785 msg : `str`
786 An error message suffix describing test parameters.
787 """
788 mask = cutout.getMaskedImage().getMask()
789 edgeMask = mask.getPlaneBitMask("NO_DATA")
791 for corner in cutout.getBBox().getCorners():
792 maskBitsSet = mask[corner] & edgeMask
793 if corner in validCorners:
794 self.assertEqual(maskBitsSet, 0, msg=msg)
795 else:
796 self.assertEqual(maskBitsSet, edgeMask, msg=msg)
798 def _getExposureCenter(self, exposure):
799 """Return the sky coordinates of an Exposure's center.
801 Parameters
802 ----------
803 exposure : `lsst.afw.image.Exposure`
804 The image whose center is desired.
806 Returns
807 -------
808 center : `lsst.geom.SpherePoint`
809 The position at the center of ``exposure``.
810 """
811 return exposure.getWcs().pixelToSky(lsst.geom.Box2D(exposure.getBBox()).getCenter())
813 def _getValidCorners(self, imageBox, cutoutBox):
814 """Return the corners of a cutout that are constrained by the original image.
816 Parameters
817 ----------
818 imageBox: `lsst.geom.Extent2I`
819 The bounding box of the original image.
820 cutoutBox : `lsst.geom.Box2I`
821 The bounding box of the cutout.
823 Returns
824 -------
825 corners : iterable of `lsst.geom.Point2I`
826 The corners that are drawn from the original image.
827 """
828 return [corner for corner in cutoutBox.getCorners() if corner in imageBox]
831class ExposureInfoTestCase(lsst.utils.tests.TestCase):
832 def setUp(self):
833 super().setUp()
835 afwImage.Filter.reset()
836 afwImage.FilterProperty.reset()
837 defineFilter("g", 470.0)
839 self.wcs = afwGeom.makeSkyWcs(lsst.geom.Point2D(0.0, 0.0),
840 lsst.geom.SpherePoint(2.0, 34.0, lsst.geom.degrees),
841 np.identity(2),
842 )
843 self.photoCalib = afwImage.PhotoCalib(1.5)
844 self.psf = DummyPsf(2.0)
845 self.detector = DetectorWrapper().detector
846 self.summaryStats = afwImage.ExposureSummaryStats(ra=100.0)
847 self.polygon = afwGeom.Polygon(lsst.geom.Box2D(lsst.geom.Point2D(0.0, 0.0),
848 lsst.geom.Point2D(25.0, 20.0)))
849 self.coaddInputs = afwImage.CoaddInputs()
850 self.apCorrMap = afwImage.ApCorrMap()
851 self.transmissionCurve = afwImage.TransmissionCurve.makeIdentity()
853 self.exposureInfo = afwImage.ExposureInfo()
854 gFilter = afwImage.Filter("g")
855 gFilterLabel = afwImage.FilterLabel(band="g")
856 self.exposureInfo.setFilter(gFilter)
857 self.exposureInfo.setFilterLabel(gFilterLabel)
859 def _checkAlias(self, exposureInfo, key, value, has, get):
860 self.assertFalse(has())
861 self.assertFalse(exposureInfo.hasComponent(key))
862 self.assertIsNone(get())
863 self.assertIsNone(exposureInfo.getComponent(key))
865 self.exposureInfo.setComponent(key, value)
866 self.assertTrue(has())
867 self.assertTrue(exposureInfo.hasComponent(key))
868 self.assertIsNotNone(get())
869 self.assertIsNotNone(exposureInfo.getComponent(key))
870 self.assertEqual(get(), value)
871 self.assertEqual(exposureInfo.getComponent(key), value)
873 self.exposureInfo.removeComponent(key)
874 self.assertFalse(has())
875 self.assertFalse(exposureInfo.hasComponent(key))
876 self.assertIsNone(get())
877 self.assertIsNone(exposureInfo.getComponent(key))
879 def testAliases(self):
880 cls = type(self.exposureInfo)
881 self._checkAlias(self.exposureInfo, cls.KEY_WCS, self.wcs,
882 self.exposureInfo.hasWcs, self.exposureInfo.getWcs)
883 self._checkAlias(self.exposureInfo, cls.KEY_PSF, self.psf,
884 self.exposureInfo.hasPsf, self.exposureInfo.getPsf)
885 self._checkAlias(self.exposureInfo, cls.KEY_PHOTO_CALIB, self.photoCalib,
886 self.exposureInfo.hasPhotoCalib, self.exposureInfo.getPhotoCalib)
887 self._checkAlias(self.exposureInfo, cls.KEY_DETECTOR, self.detector,
888 self.exposureInfo.hasDetector, self.exposureInfo.getDetector)
889 self._checkAlias(self.exposureInfo, cls.KEY_VALID_POLYGON, self.polygon,
890 self.exposureInfo.hasValidPolygon, self.exposureInfo.getValidPolygon)
891 self._checkAlias(self.exposureInfo, cls.KEY_COADD_INPUTS, self.coaddInputs,
892 self.exposureInfo.hasCoaddInputs, self.exposureInfo.getCoaddInputs)
893 self._checkAlias(self.exposureInfo, cls.KEY_AP_CORR_MAP, self.apCorrMap,
894 self.exposureInfo.hasApCorrMap, self.exposureInfo.getApCorrMap)
895 self._checkAlias(self.exposureInfo, cls.KEY_TRANSMISSION_CURVE, self.transmissionCurve,
896 self.exposureInfo.hasTransmissionCurve, self.exposureInfo.getTransmissionCurve)
897 self._checkAlias(self.exposureInfo, cls.KEY_SUMMARY_STATS, self.summaryStats,
898 self.exposureInfo.hasSummaryStats, self.exposureInfo.getSummaryStats)
900 def testCopy(self):
901 # Test that ExposureInfos have independently settable state
902 copy = afwImage.ExposureInfo(self.exposureInfo, True)
903 self.assertEqual(self.exposureInfo.getWcs(), copy.getWcs())
905 newWcs = afwGeom.makeSkyWcs(lsst.geom.Point2D(-23.0, 8.0),
906 lsst.geom.SpherePoint(0.0, 0.0, lsst.geom.degrees),
907 np.identity(2),
908 )
909 copy.setWcs(newWcs)
910 self.assertEqual(copy.getWcs(), newWcs)
911 self.assertNotEqual(self.exposureInfo.getWcs(), copy.getWcs())
914class ExposureNoAfwdataTestCase(lsst.utils.tests.TestCase):
915 """Tests of Exposure that don't require afwdata.
917 These tests use the trivial exposures written to ``afw/tests/data``.
918 """
919 def setUp(self):
920 self.dataDir = os.path.join(os.path.split(__file__)[0], "data")
922 # Check the values below against what was written by comparing with
923 # the code in `afw/tests/data/makeTestExposure.py`
924 nx = ny = 10
925 image = afwImage.ImageF(np.arange(nx*ny, dtype='f').reshape(nx, ny))
926 variance = afwImage.ImageF(np.ones((nx, ny), dtype='f'))
927 mask = afwImage.MaskX(nx, ny)
928 mask.array[5, 5] = 5
929 self.maskedImage = afwImage.MaskedImageF(image, mask, variance)
931 self.v0PhotoCalib = afwImage.makePhotoCalibFromCalibZeroPoint(1e6, 2e4)
932 self.v1PhotoCalib = afwImage.PhotoCalib(1e6, 2e4)
933 self.v1FilterLabel = afwImage.FilterLabel(physical="ha")
934 self.v2FilterLabel = afwImage.FilterLabel(band="N656", physical="ha")
936 def testReadUnversioned(self):
937 """Test that we can read an unversioned (implicit verison 0) file.
938 """
939 filename = os.path.join(self.dataDir, "exposure-noversion.fits")
940 exposure = afwImage.ExposureF.readFits(filename)
942 self.assertMaskedImagesEqual(exposure.maskedImage, self.maskedImage)
944 self.assertEqual(exposure.getPhotoCalib(), self.v0PhotoCalib)
945 self.assertEqual(exposure.getFilterLabel(), self.v1FilterLabel)
947 def testReadVersion0(self):
948 """Test that we can read a version 0 file.
949 This file should be identical to the unversioned one, except that it
950 is marked as ExposureInfo version 0 in the header.
951 """
952 filename = os.path.join(self.dataDir, "exposure-version-0.fits")
953 exposure = afwImage.ExposureF.readFits(filename)
955 self.assertMaskedImagesEqual(exposure.maskedImage, self.maskedImage)
957 self.assertEqual(exposure.getPhotoCalib(), self.v0PhotoCalib)
958 self.assertEqual(exposure.getFilterLabel(), self.v1FilterLabel)
960 # Check that the metadata reader parses the file correctly
961 reader = afwImage.ExposureFitsReader(filename)
962 self.assertEqual(reader.readExposureInfo().getPhotoCalib(), self.v0PhotoCalib)
963 self.assertEqual(reader.readPhotoCalib(), self.v0PhotoCalib)
965 def testReadVersion1(self):
966 """Test that we can read a version 1 file.
967 Version 1 replaced Calib with PhotoCalib.
968 """
969 filename = os.path.join(self.dataDir, "exposure-version-1.fits")
970 exposure = afwImage.ExposureF.readFits(filename)
972 self.assertMaskedImagesEqual(exposure.maskedImage, self.maskedImage)
974 self.assertEqual(exposure.getPhotoCalib(), self.v1PhotoCalib)
975 self.assertEqual(exposure.getFilterLabel(), self.v1FilterLabel)
977 # Check that the metadata reader parses the file correctly
978 reader = afwImage.ExposureFitsReader(filename)
979 self.assertEqual(reader.readExposureInfo().getPhotoCalib(), self.v1PhotoCalib)
980 self.assertEqual(reader.readPhotoCalib(), self.v1PhotoCalib)
982 def testReadVersion2(self):
983 """Test that we can read a version 2 file.
984 Version 2 replaced Filter with FilterLabel.
985 """
986 filename = os.path.join(self.dataDir, "exposure-version-2.fits")
987 exposure = afwImage.ExposureF.readFits(filename)
989 self.assertMaskedImagesEqual(exposure.maskedImage, self.maskedImage)
991 self.assertEqual(exposure.getPhotoCalib(), self.v1PhotoCalib)
992 self.assertEqual(exposure.getFilterLabel(), self.v2FilterLabel)
994 # Check that the metadata reader parses the file correctly
995 reader = afwImage.ExposureFitsReader(filename)
996 self.assertEqual(reader.readExposureInfo().getPhotoCalib(), self.v1PhotoCalib)
997 self.assertEqual(reader.readPhotoCalib(), self.v1PhotoCalib)
1000class MemoryTester(lsst.utils.tests.MemoryTestCase):
1001 pass
1004def setup_module(module):
1005 lsst.utils.tests.init()
1008if __name__ == "__main__": 1008 ↛ 1009line 1008 didn't jump to line 1009, because the condition on line 1008 was never true
1009 lsst.utils.tests.init()
1010 unittest.main()