Coverage for tests/test_warper.py: 38%
96 statements
« prev ^ index » next coverage.py v7.4.4, created at 2024-03-23 03:25 -0700
« prev ^ index » next coverage.py v7.4.4, created at 2024-03-23 03:25 -0700
1#
2# LSST Data Management System
3# Copyright 2008, 2009, 2010 LSST Corporation.
4#
5# This product includes software developed by the
6# LSST Project (http://www.lsst.org/).
7#
8# This program is free software: you can redistribute it and/or modify
9# it under the terms of the GNU General Public License as published by
10# the Free Software Foundation, either version 3 of the License, or
11# (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU General Public License for more details.
17#
18# You should have received a copy of the LSST License Statement and
19# the GNU General Public License along with this program. If not,
20# see <http://www.lsstcorp.org/LegalNotices/>.
21#
22"""Basic test of Warp (the warping algorithm is thoroughly tested in lsst.afw.math)
23"""
24import os
25import unittest
27import lsst.utils
28import lsst.utils.tests
29import lsst.geom
30import lsst.afw.geom as afwGeom
31import lsst.afw.image as afwImage
32import lsst.afw.math as afwMath
33from lsst.log import Log
35# Change the level to Log.DEBUG to see debug messages
36Log.getLogger("lsst.afw.image.Mask").setLevel(Log.INFO)
37Log.getLogger("TRACE3.lsst.afw.math.warp").setLevel(Log.INFO)
38Log.getLogger("TRACE4.lsst.afw.math.warp").setLevel(Log.INFO)
40try:
41 afwdataDir = lsst.utils.getPackageDir("afwdata")
42except LookupError:
43 afwdataDir = None
44 dataDir = None
45else:
46 dataDir = os.path.join(afwdataDir, "data")
47 originalExposureName = "medexp.fits"
48 originalExposurePath = os.path.join(dataDir, originalExposureName)
49 subExposureName = "medsub.fits"
50 subExposurePath = os.path.join(dataDir, originalExposureName)
51 originalFullExposureName = os.path.join(
52 "CFHT", "D4", "cal-53535-i-797722_1.fits")
53 originalFullExposurePath = os.path.join(dataDir, originalFullExposureName)
56class WarpExposureTestCase(lsst.utils.tests.TestCase):
57 """Test case for Warp
58 """
60 def testMatchSwarpLanczos2Exposure(self):
61 """Test that warpExposure matches swarp using a lanczos2 warping kernel.
62 """
63 self.compareToSwarp("lanczos2")
65 def testMatchSwarpLanczos2SubExposure(self):
66 """Test that warpExposure matches swarp using a lanczos2 warping kernel with a subexposure
67 """
68 for useDeepCopy in (False, True):
69 self.compareToSwarp("lanczos2", useSubregion=True,
70 useDeepCopy=useDeepCopy)
72 @unittest.skipIf(dataDir is None, "afwdata not setup")
73 def testBBox(self):
74 """Test that the default bounding box includes all warped pixels
75 """
76 kernelName = "lanczos2"
77 warper = afwMath.Warper(kernelName)
78 originalExposure, swarpedImage, swarpedWcs = self.getSwarpedImage(
79 kernelName=kernelName, useSubregion=True, useDeepCopy=False)
81 originalFilterLabel = afwImage.FilterLabel(band="i")
82 originalPhotoCalib = afwImage.PhotoCalib(1.0e5, 1.0e3)
83 originalExposure.setFilter(originalFilterLabel)
84 originalExposure.setPhotoCalib(originalPhotoCalib)
86 warpedExposure1 = warper.warpExposure(
87 destWcs=swarpedWcs, srcExposure=originalExposure)
88 # the default size must include all good pixels, so growing the bbox
89 # should not add any
90 warpedExposure2 = warper.warpExposure(
91 destWcs=swarpedWcs, srcExposure=originalExposure, border=1)
92 # a bit of excess border is allowed, but surely not as much as 10 (in
93 # fact it is approx. 5)
94 warpedExposure3 = warper.warpExposure(
95 destWcs=swarpedWcs, srcExposure=originalExposure, border=-10)
96 # assert that warpedExposure and warpedExposure2 have the same number of non-no_data pixels
97 # and that warpedExposure3 has fewer
98 noDataBitMask = afwImage.Mask.getPlaneBitMask("NO_DATA")
99 mask1Arr = warpedExposure1.getMaskedImage().getMask().getArray()
100 mask2Arr = warpedExposure2.getMaskedImage().getMask().getArray()
101 mask3Arr = warpedExposure3.getMaskedImage().getMask().getArray()
102 nGood1 = (mask1Arr & noDataBitMask == 0).sum()
103 nGood2 = (mask2Arr & noDataBitMask == 0).sum()
104 nGood3 = (mask3Arr & noDataBitMask == 0).sum()
105 self.assertEqual(nGood1, nGood2)
106 self.assertLess(nGood3, nGood1)
108 self.assertEqual(warpedExposure1.getFilter().bandLabel,
109 originalFilterLabel.bandLabel)
110 self.assertEqual(warpedExposure1.getPhotoCalib(), originalPhotoCalib)
112 @unittest.skipIf(dataDir is None, "afwdata not setup")
113 def testDestBBox(self):
114 """Test that the destBBox argument works
115 """
116 kernelName = "lanczos2"
117 warper = afwMath.Warper(kernelName)
118 originalExposure, swarpedImage, swarpedWcs = self.getSwarpedImage(
119 kernelName=kernelName, useSubregion=True, useDeepCopy=False)
121 bbox = lsst.geom.Box2I(lsst.geom.Point2I(100, 25), lsst.geom.Extent2I(3, 7))
122 warpedExposure = warper.warpExposure(
123 destWcs=swarpedWcs,
124 srcExposure=originalExposure,
125 destBBox=bbox,
126 # should be ignored
127 border=-2,
128 # should be ignored
129 maxBBox=lsst.geom.Box2I(lsst.geom.Point2I(1, 2),
130 lsst.geom.Extent2I(8, 9)),
131 )
132 self.assertEqual(bbox, warpedExposure.getBBox(afwImage.PARENT))
134 @unittest.skipIf(dataDir is None, "afwdata not setup")
135 def getSwarpedImage(self, kernelName, useSubregion=False, useDeepCopy=False):
136 """
137 Inputs:
138 - kernelName: name of kernel in the form used by afwImage.makeKernel
139 - useSubregion: if True then the original source exposure (from which the usual
140 test exposure was extracted) is read and the correct subregion extracted
141 - useDeepCopy: if True then the copy of the subimage is a deep copy,
142 else it is a shallow copy; ignored if useSubregion is False
144 Returns:
145 - originalExposure
146 - swarpedImage
147 - swarpedWcs
148 """
149 if useSubregion:
150 originalFullExposure = afwImage.ExposureF(originalExposurePath)
151 # "medsub" is a subregion of med starting at 0-indexed pixel (40, 150) of size 145 x 200
152 bbox = lsst.geom.Box2I(lsst.geom.Point2I(40, 150),
153 lsst.geom.Extent2I(145, 200))
154 originalExposure = afwImage.ExposureF(
155 originalFullExposure, bbox, afwImage.LOCAL, useDeepCopy)
156 swarpedImageName = f"medsubswarp1{kernelName}.fits"
157 else:
158 originalExposure = afwImage.ExposureF(originalExposurePath)
159 swarpedImageName = f"medswarp1{kernelName}.fits"
161 swarpedImagePath = os.path.join(dataDir, swarpedImageName)
162 swarpedDecoratedImage = afwImage.DecoratedImageF(swarpedImagePath)
163 swarpedImage = swarpedDecoratedImage.getImage()
164 swarpedMetadata = swarpedDecoratedImage.getMetadata()
165 swarpedWcs = afwGeom.makeSkyWcs(swarpedMetadata)
166 return (originalExposure, swarpedImage, swarpedWcs)
168 @unittest.skipIf(dataDir is None, "afwdata not setup")
169 def compareToSwarp(self, kernelName,
170 useSubregion=False, useDeepCopy=False,
171 interpLength=10, cacheSize=100000,
172 rtol=4e-05, atol=1e-2):
173 """Compare warpExposure to swarp for given warping kernel.
175 Note that swarp only warps the image plane, so only test that plane.
177 Inputs:
178 - kernelName: name of kernel in the form used by afwImage.makeKernel
179 - useSubregion: if True then the original source exposure (from which the usual
180 test exposure was extracted) is read and the correct subregion extracted
181 - useDeepCopy: if True then the copy of the subimage is a deep copy,
182 else it is a shallow copy; ignored if useSubregion is False
183 - interpLength: interpLength argument for lsst.afw.math.warpExposure
184 - cacheSize: cacheSize argument for lsst.afw.math.SeparableKernel.computeCache;
185 0 disables the cache
186 10000 gives some speed improvement but less accurate results (atol must be increased)
187 100000 gives better accuracy but no speed improvement in this test
188 - rtol: relative tolerance as used by numpy.allclose
189 - atol: absolute tolerance as used by numpy.allclose
190 """
191 warper = afwMath.Warper(kernelName)
193 originalExposure, swarpedImage, swarpedWcs = self.getSwarpedImage(
194 kernelName=kernelName, useSubregion=useSubregion, useDeepCopy=useDeepCopy)
195 maxBBox = lsst.geom.Box2I(
196 lsst.geom.Point2I(swarpedImage.getX0(), swarpedImage.getY0()),
197 lsst.geom.Extent2I(swarpedImage.getWidth(), swarpedImage.getHeight()))
199 # warning: this test assumes that the swarped image is smaller than it needs to be
200 # to hold all of the warped pixels
201 afwWarpedExposure = warper.warpExposure(
202 destWcs=swarpedWcs,
203 srcExposure=originalExposure,
204 maxBBox=maxBBox,
205 )
206 afwWarpedMaskedImage = afwWarpedExposure.getMaskedImage()
208 afwWarpedMask = afwWarpedMaskedImage.getMask()
209 noDataBitMask = afwImage.Mask.getPlaneBitMask("NO_DATA")
210 noDataMask = afwWarpedMask.getArray() & noDataBitMask
212 msg = "afw and swarp %s-warped %s (ignoring bad pixels)"
213 self.assertImagesAlmostEqual(afwWarpedMaskedImage.getImage(), swarpedImage,
214 skipMask=noDataMask, rtol=rtol, atol=atol, msg=msg)
217class MemoryTester(lsst.utils.tests.MemoryTestCase):
218 pass
221def setup_module(module):
222 lsst.utils.tests.init()
225if __name__ == "__main__": 225 ↛ 226line 225 didn't jump to line 226, because the condition on line 225 was never true
226 lsst.utils.tests.init()
227 unittest.main()