Coverage for tests/test_warpExposure.py: 17%
337 statements
« prev ^ index » next coverage.py v7.5.0, created at 2024-05-01 15:48 -0700
« prev ^ index » next coverage.py v7.5.0, created at 2024-05-01 15:48 -0700
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"""Test warpExposure
23"""
24import os
25import unittest
27import numpy as np
29import lsst.utils
30import lsst.utils.tests
31import lsst.daf.base as dafBase
32from lsst.afw.coord import Observatory, Weather
33import lsst.geom
34import lsst.afw.geom as afwGeom
35import lsst.afw.image as afwImage
36import lsst.afw.math as afwMath
37import lsst.pex.exceptions as pexExcept
38import lsst.afw.display as afwDisplay
39from lsst.log import Log
41# Change the level to Log.DEBUG to see debug messages
42Log.getLogger("lsst.afw.image.Mask").setLevel(Log.INFO)
43Log.getLogger("TRACE2.lsst.afw.math.warp").setLevel(Log.INFO)
44Log.getLogger("TRACE3.lsst.afw.math.warp").setLevel(Log.INFO)
46afwDisplay.setDefaultMaskTransparency(75)
48display = False
49# set True to save afw-warped images as FITS files
50SAVE_FITS_FILES = False
51# set True to save failed afw-warped images as FITS files even if
52# SAVE_FITS_FILES is False
53SAVE_FAILED_FITS_FILES = True
55try:
56 afwdataDir = lsst.utils.getPackageDir("afwdata")
57except LookupError:
58 afwdataDir = None
59else:
60 dataDir = os.path.join(afwdataDir, "data")
62 originalExposureName = "medexp.fits"
63 originalExposurePath = os.path.join(dataDir, originalExposureName)
64 subExposureName = "medsub.fits"
65 subExposurePath = os.path.join(dataDir, originalExposureName)
66 originalFullExposureName = os.path.join(
67 "CFHT", "D4", "cal-53535-i-797722_1.fits")
68 originalFullExposurePath = os.path.join(dataDir, originalFullExposureName)
71def makeVisitInfo():
72 """Return a non-NaN visitInfo."""
73 return afwImage.VisitInfo(exposureTime=10.01,
74 darkTime=11.02,
75 date=dafBase.DateTime(65321.1, dafBase.DateTime.MJD, dafBase.DateTime.TAI),
76 ut1=12345.1,
77 era=45.1*lsst.geom.degrees,
78 boresightRaDec=lsst.geom.SpherePoint(23.1, 73.2, lsst.geom.degrees),
79 boresightAzAlt=lsst.geom.SpherePoint(134.5, 33.3, lsst.geom.degrees),
80 boresightAirmass=1.73,
81 boresightRotAngle=73.2*lsst.geom.degrees,
82 rotType=afwImage.RotType.SKY,
83 observatory=Observatory(11.1*lsst.geom.degrees, 22.2*lsst.geom.degrees, 0.333),
84 weather=Weather(1.1, 2.2, 34.5),
85 )
88class WarpExposureTestCase(lsst.utils.tests.TestCase):
89 """Test case for warpExposure
90 """
92 def setUp(self):
93 np.random.seed(0)
95 @unittest.skipIf(afwdataDir is None, "afwdata not setup")
96 def testNullWarpExposure(self, interpLength=10):
97 """Test that warpExposure maps an image onto itself.
99 Note:
100 - NO_DATA and off-CCD pixels must be ignored
101 - bad mask pixels get smeared out so we have to excluded all bad mask pixels
102 from the output image when comparing masks.
103 """
104 originalExposure = afwImage.ExposureF(originalExposurePath)
105 originalExposure.getInfo().setId(10313423)
106 originalExposure.getInfo().setVisitInfo(makeVisitInfo())
107 originalFilterLabel = afwImage.FilterLabel(band="i")
108 originalPhotoCalib = afwImage.PhotoCalib(1.0e5, 1.0e3)
109 originalExposure.setFilter(originalFilterLabel)
110 originalExposure.setPhotoCalib(originalPhotoCalib)
111 afwWarpedExposure = afwImage.ExposureF(
112 originalExposure.getBBox(),
113 originalExposure.getWcs())
114 warpingControl = afwMath.WarpingControl(
115 "lanczos4", "", 0, interpLength)
116 afwMath.warpExposure(
117 afwWarpedExposure, originalExposure, warpingControl)
118 if SAVE_FITS_FILES:
119 afwWarpedExposure.writeFits("afwWarpedExposureNull.fits")
121 self.assertEqual(afwWarpedExposure.getFilter().bandLabel,
122 originalFilterLabel.bandLabel)
123 self.assertEqual(afwWarpedExposure.getPhotoCalib(), originalPhotoCalib)
124 self.assertEqual(afwWarpedExposure.getInfo().getVisitInfo(),
125 originalExposure.getInfo().getVisitInfo())
127 afwWarpedMaskedImage = afwWarpedExposure.getMaskedImage()
128 afwWarpedMask = afwWarpedMaskedImage.getMask()
129 noDataBitMask = afwWarpedMask.getPlaneBitMask("NO_DATA")
131 # compare all non-DATA pixels of image and variance, but relax specs a bit
132 # because of minor noise introduced by bad pixels
133 noDataMaskArr = afwWarpedMaskedImage.mask.array & noDataBitMask
134 msg = "afw null-warped MaskedImage (all pixels, relaxed tolerance)"
135 self.assertMaskedImagesAlmostEqual(afwWarpedMaskedImage, originalExposure.getMaskedImage(),
136 doMask=False, skipMask=noDataMaskArr, atol=1e-5, msg=msg)
138 # compare good pixels (mask=0) of image, mask and variance using full
139 # tolerance
140 msg = "afw null-warped MaskedImage (good pixels, max tolerance)"
141 self.assertMaskedImagesAlmostEqual(afwWarpedMaskedImage, originalExposure.getMaskedImage(),
142 skipMask=afwWarpedMask, msg=msg)
144 @unittest.skipIf(afwdataDir is None, "afwdata not setup")
145 def testNullWarpImage(self, interpLength=10):
146 """Test that warpImage maps an image onto itself.
147 """
148 originalExposure = afwImage.ExposureF(originalExposurePath)
149 afwWarpedExposure = afwImage.ExposureF(originalExposurePath)
150 originalImage = originalExposure.getMaskedImage().getImage()
151 afwWarpedImage = afwWarpedExposure.getMaskedImage().getImage()
152 originalWcs = originalExposure.getWcs()
153 afwWarpedWcs = afwWarpedExposure.getWcs()
154 warpingControl = afwMath.WarpingControl(
155 "lanczos4", "", 0, interpLength)
156 afwMath.warpImage(afwWarpedImage, afwWarpedWcs,
157 originalImage, originalWcs, warpingControl)
158 if SAVE_FITS_FILES:
159 afwWarpedImage.writeFits("afwWarpedImageNull.fits")
160 afwWarpedImageArr = afwWarpedImage.getArray()
161 noDataMaskArr = np.isnan(afwWarpedImageArr)
162 # relax specs a bit because of minor noise introduced by bad pixels
163 msg = "afw null-warped Image"
164 self.assertImagesAlmostEqual(originalImage, afwWarpedImage, skipMask=noDataMaskArr,
165 atol=1e-5, msg=msg)
167 @unittest.skipIf(afwdataDir is None, "afwdata not setup")
168 def testNullWcs(self, interpLength=10):
169 """Cannot warp from or into an exposure without a Wcs.
170 """
171 exposureWithWcs = afwImage.ExposureF(originalExposurePath)
172 mi = exposureWithWcs.getMaskedImage()
173 exposureWithoutWcs = afwImage.ExposureF(mi.getDimensions())
174 warpingControl = afwMath.WarpingControl(
175 "bilinear", "", 0, interpLength)
177 with self.assertRaises(pexExcept.InvalidParameterError):
178 afwMath.warpExposure(exposureWithWcs, exposureWithoutWcs, warpingControl)
180 with self.assertRaises(pexExcept.InvalidParameterError):
181 afwMath.warpExposure(exposureWithoutWcs, exposureWithWcs, warpingControl)
183 def testWarpIntoSelf(self, interpLength=10):
184 """Cannot warp in-place
185 """
186 wcs = afwGeom.makeSkyWcs(
187 crpix=lsst.geom.Point2D(0, 0),
188 crval=lsst.geom.SpherePoint(359, 0, lsst.geom.degrees),
189 cdMatrix=afwGeom.makeCdMatrix(1.0e-8*lsst.geom.degrees),
190 )
191 exposure = afwImage.ExposureF(lsst.geom.Extent2I(100, 100), wcs)
192 maskedImage = exposure.getMaskedImage()
193 warpingControl = afwMath.WarpingControl(
194 "bilinear", "", 0, interpLength)
196 with self.assertRaises(pexExcept.InvalidParameterError):
197 afwMath.warpExposure(exposure, exposure, warpingControl)
199 with self.assertRaises(pexExcept.InvalidParameterError):
200 afwMath.warpImage(maskedImage, wcs, maskedImage, wcs, warpingControl)
202 with self.assertRaises(pexExcept.InvalidParameterError):
203 afwMath.warpImage(maskedImage.getImage(), wcs, maskedImage.getImage(), wcs, warpingControl)
205 def testWarpingControl(self):
206 """Test the basic mechanics of WarpingControl
207 """
208 for interpLength in (0, 1, 52):
209 wc = afwMath.WarpingControl("lanczos3", "", 0, interpLength)
210 self.assertFalse(wc.hasMaskWarpingKernel())
211 self.assertEqual(wc.getInterpLength(), interpLength)
212 for newInterpLength in (3, 7, 9):
213 wc.setInterpLength(newInterpLength)
214 self.assertEqual(wc.getInterpLength(), newInterpLength)
216 for cacheSize in (0, 100):
217 wc = afwMath.WarpingControl("lanczos3", "bilinear", cacheSize)
218 self.assertTrue(wc.hasMaskWarpingKernel())
219 self.assertEqual(wc.getCacheSize(), cacheSize)
220 self.assertEqual(wc.getWarpingKernel().getCacheSize(), cacheSize)
221 self.assertEqual(
222 wc.getMaskWarpingKernel().getCacheSize(), cacheSize)
223 for newCacheSize in (1, 50):
224 wc.setCacheSize(newCacheSize)
225 self.assertEqual(wc.getCacheSize(), newCacheSize)
226 self.assertEqual(
227 wc.getWarpingKernel().getCacheSize(), newCacheSize)
228 self.assertEqual(
229 wc.getMaskWarpingKernel().getCacheSize(), newCacheSize)
231 wc = afwMath.WarpingControl("lanczos4", "nearest", 64, 7, 42)
232 self.assertTrue(wc.isPersistable())
233 with lsst.utils.tests.getTempFilePath(".fits", expectOutput=True) as tempFile:
234 wc.writeFits(tempFile)
235 wc2 = afwMath.WarpingControl.readFits(tempFile)
236 self.assertEqual(wc.getCacheSize(), wc2.getCacheSize())
237 self.assertEqual(wc.getInterpLength(), wc2.getInterpLength())
238 self.assertEqual(wc.getWarpingKernel().getBBox(), wc2.getWarpingKernel().getBBox())
239 self.assertEqual(wc.getWarpingKernel().getKernelParameters(),
240 wc2.getWarpingKernel().getKernelParameters())
241 self.assertEqual(wc.hasMaskWarpingKernel(), wc2.hasMaskWarpingKernel())
242 self.assertEqual(wc.getMaskWarpingKernel().getBBox(), wc2.getMaskWarpingKernel().getBBox())
243 self.assertEqual(wc.getMaskWarpingKernel().getKernelParameters(),
244 wc2.getMaskWarpingKernel().getKernelParameters())
245 self.assertEqual(wc.getGrowFullMask(), wc2.getGrowFullMask())
247 def testWarpingControlError(self):
248 """Test error handling of WarpingControl
249 """
250 # error: mask kernel smaller than main kernel
251 for kernelName, maskKernelName in (
252 ("bilinear", "lanczos3"),
253 ("bilinear", "lanczos4"),
254 ("lanczos3", "lanczos4"),
255 ):
256 with self.assertRaises(pexExcept.InvalidParameterError):
257 afwMath.WarpingControl(kernelName, maskKernelName)
259 # error: new mask kernel larger than main kernel
260 warpingControl = afwMath.WarpingControl("bilinear")
261 for maskKernelName in ("lanczos3", "lanczos4"):
262 with self.assertRaises(pexExcept.InvalidParameterError):
263 warpingControl.setMaskWarpingKernelName(maskKernelName)
265 # error: new kernel smaller than mask kernel
266 warpingControl = afwMath.WarpingControl("lanczos4", "lanczos4")
267 for kernelName in ("bilinear", "lanczos3"):
268 with self.assertRaises(pexExcept.InvalidParameterError):
269 warpingControl.setWarpingKernelName(kernelName)
271 # OK: main kernel at least as big as mask kernel
272 for kernelName, maskKernelName in (
273 ("bilinear", "bilinear"),
274 ("lanczos3", "lanczos3"),
275 ("lanczos3", "bilinear"),
276 ("lanczos4", "lanczos3"),
277 ):
278 # this should not raise any exception
279 afwMath.WarpingControl(kernelName, maskKernelName)
281 # invalid kernel names
282 for kernelName, maskKernelName in (
283 ("badname", ""),
284 ("lanczos", ""), # no digit after lanczos
285 ("lanczos3", "badname"),
286 ("lanczos3", "lanczos"),
287 ):
288 with self.assertRaises(pexExcept.InvalidParameterError):
289 afwMath.WarpingControl(kernelName, maskKernelName)
291 def testWarpMask(self):
292 """Test that warping the mask plane with a different kernel does the right thing
293 """
294 for kernelName, maskKernelName in (
295 ("bilinear", "bilinear"),
296 ("lanczos3", "lanczos3"),
297 ("lanczos3", "bilinear"),
298 ("lanczos4", "lanczos3"),
299 ):
300 for growFullMask in (0, 1, 3, 0xFFFF):
301 self.verifyMaskWarp(
302 kernelName=kernelName,
303 maskKernelName=maskKernelName,
304 growFullMask=growFullMask,
305 )
307 def testMatchSwarpBilinearImage(self):
308 """Test that warpExposure matches swarp using a bilinear warping kernel
309 """
310 self.compareToSwarp("bilinear", useWarpExposure=False, atol=0.15)
312 def testMatchSwarpBilinearExposure(self):
313 """Test that warpExposure matches swarp using a bilinear warping kernel
314 """
315 self.compareToSwarp("bilinear", useWarpExposure=True,
316 useSubregion=False, useDeepCopy=True)
318 def testMatchSwarpLanczos2Image(self):
319 """Test that warpExposure matches swarp using a lanczos2 warping kernel
320 """
321 self.compareToSwarp("lanczos2", useWarpExposure=False)
323 def testMatchSwarpLanczos2Exposure(self):
324 """Test that warpExposure matches swarp using a lanczos2 warping kernel.
325 """
326 self.compareToSwarp("lanczos2", useWarpExposure=True)
328 def testMatchSwarpLanczos2SubExposure(self):
329 """Test that warpExposure matches swarp using a lanczos2 warping kernel with a subexposure
330 """
331 for useDeepCopy in (False, True):
332 self.compareToSwarp("lanczos2", useWarpExposure=True,
333 useSubregion=True, useDeepCopy=useDeepCopy)
335 def testMatchSwarpLanczos3Image(self):
336 """Test that warpExposure matches swarp using a lanczos2 warping kernel
337 """
338 self.compareToSwarp("lanczos3", useWarpExposure=False)
340 def testMatchSwarpLanczos3(self):
341 """Test that warpExposure matches swarp using a lanczos4 warping kernel.
342 """
343 self.compareToSwarp("lanczos3", useWarpExposure=True)
345 def testMatchSwarpLanczos4Image(self):
346 """Test that warpExposure matches swarp using a lanczos2 warping kernel
347 """
348 self.compareToSwarp("lanczos4", useWarpExposure=False)
350 def testMatchSwarpLanczos4(self):
351 """Test that warpExposure matches swarp using a lanczos4 warping kernel.
352 """
353 self.compareToSwarp("lanczos4", useWarpExposure=True)
355 def testMatchSwarpNearestExposure(self):
356 """Test that warpExposure matches swarp using a nearest neighbor warping kernel
357 """
358 self.compareToSwarp("nearest", useWarpExposure=True, atol=60)
360 @unittest.skipIf(afwdataDir is None, "afwdata not setup")
361 def testTransformBasedWarp(self):
362 """Test warping using TransformPoint2ToPoint2
363 """
364 for interpLength in (0, 1, 2, 4):
365 kernelName = "lanczos3"
366 rtol = 4e-5
367 atol = 1e-2
368 warpingControl = afwMath.WarpingControl(
369 warpingKernelName=kernelName,
370 interpLength=interpLength,
371 )
373 originalExposure = afwImage.ExposureF(originalExposurePath)
374 originalMetadata = afwImage.DecoratedImageF(originalExposurePath).getMetadata()
375 originalSkyWcs = afwGeom.makeSkyWcs(originalMetadata)
377 swarpedImageName = f"medswarp1{kernelName}.fits"
378 swarpedImagePath = os.path.join(dataDir, swarpedImageName)
379 swarpedDecoratedImage = afwImage.DecoratedImageF(swarpedImagePath)
380 swarpedImage = swarpedDecoratedImage.getImage()
382 swarpedMetadata = swarpedDecoratedImage.getMetadata()
383 warpedSkyWcs = afwGeom.makeSkyWcs(swarpedMetadata)
385 # original image is source, warped image is destination
386 srcToDest = afwGeom.makeWcsPairTransform(originalSkyWcs, warpedSkyWcs)
388 afwWarpedMaskedImage = afwImage.MaskedImageF(swarpedImage.getDimensions())
389 originalMaskedImage = originalExposure.getMaskedImage()
391 numGoodPix = afwMath.warpImage(afwWarpedMaskedImage, originalMaskedImage,
392 srcToDest, warpingControl)
393 self.assertGreater(numGoodPix, 50)
395 afwWarpedImage = afwWarpedMaskedImage.getImage()
396 afwWarpedImageArr = afwWarpedImage.getArray()
397 noDataMaskArr = np.isnan(afwWarpedImageArr)
398 self.assertImagesAlmostEqual(afwWarpedImage, swarpedImage,
399 skipMask=noDataMaskArr, rtol=rtol, atol=atol)
401 def testTicket2441(self):
402 """Test ticket 2441: warpExposure sometimes mishandles zero-extent dest exposures"""
403 fromWcs = afwGeom.makeSkyWcs(
404 crpix=lsst.geom.Point2D(0, 0),
405 crval=lsst.geom.SpherePoint(359, 0, lsst.geom.degrees),
406 cdMatrix=afwGeom.makeCdMatrix(scale=1.0e-8*lsst.geom.degrees),
407 )
408 fromExp = afwImage.ExposureF(afwImage.MaskedImageF(10, 10), fromWcs)
410 toWcs = afwGeom.makeSkyWcs(
411 crpix=lsst.geom.Point2D(410000, 11441),
412 crval=lsst.geom.SpherePoint(45, 0, lsst.geom.degrees),
413 cdMatrix=afwGeom.makeCdMatrix(scale=0.00011*lsst.geom.degrees, flipX=True),
414 projection="CEA",
415 )
416 toExp = afwImage.ExposureF(afwImage.MaskedImageF(0, 0), toWcs)
418 warpControl = afwMath.WarpingControl("lanczos3")
419 # if a bug described in ticket #2441 is present, this will raise an
420 # exception:
421 numGoodPix = afwMath.warpExposure(toExp, fromExp, warpControl)
422 self.assertEqual(numGoodPix, 0)
424 def testTicketDM4063(self):
425 """Test that a uint16 array can be cast to a bool array, to avoid DM-4063
426 """
427 a = np.array([0, 1, 0, 23], dtype=np.uint16)
428 b = np.array([True, True, False, False], dtype=bool)
429 acast = np.array(a != 0, dtype=bool)
430 orArr = acast | b
431 desOrArr = np.array([True, True, False, True], dtype=bool)
432 # Note: assertEqual(bool arr, bool arr) fails with:
433 # ValueError: The truth value of an array with more than one element is
434 # ambiguous
435 try:
436 self.assertTrue(np.all(orArr == desOrArr))
437 except Exception as e:
438 print(f"Failed: {orArr!r} != {desOrArr!r}: {e}")
439 raise
441 def testSmallSrc(self):
442 """Verify that a source image that is too small will not raise an exception
444 This tests another bug that was fixed in ticket #2441
445 """
446 fromWcs = afwGeom.makeSkyWcs(
447 crpix=lsst.geom.Point2D(0, 0),
448 crval=lsst.geom.SpherePoint(359, 0, lsst.geom.degrees),
449 cdMatrix=afwGeom.makeCdMatrix(scale=1.0e-8*lsst.geom.degrees),
450 )
451 fromExp = afwImage.ExposureF(afwImage.MaskedImageF(1, 1), fromWcs)
453 toWcs = afwGeom.makeSkyWcs(
454 crpix=lsst.geom.Point2D(0, 0),
455 crval=lsst.geom.SpherePoint(358, 0, lsst.geom.degrees),
456 cdMatrix=afwGeom.makeCdMatrix(scale=1.1e-8*lsst.geom.degrees),
457 )
458 toExp = afwImage.ExposureF(afwImage.MaskedImageF(10, 10), toWcs)
460 warpControl = afwMath.WarpingControl("lanczos3")
461 # if a bug described in ticket #2441 is present, this will raise an
462 # exception:
463 numGoodPix = afwMath.warpExposure(toExp, fromExp, warpControl)
464 self.assertEqual(numGoodPix, 0)
465 self.assertTrue(np.all(np.isnan(toExp.image.array)))
466 self.assertTrue(np.all(np.isinf(toExp.variance.array)))
467 noDataBitMask = afwImage.Mask.getPlaneBitMask("NO_DATA")
468 self.assertTrue(np.all(toExp.mask.array == noDataBitMask))
470 def verifyMaskWarp(self, kernelName, maskKernelName, growFullMask, interpLength=10, cacheSize=100000,
471 rtol=4e-05, atol=1e-2):
472 """Verify that using a separate mask warping kernel produces the correct results
474 Inputs:
475 - kernelName: name of warping kernel in the form used by afwImage.makeKernel
476 - maskKernelName: name of mask warping kernel in the form used by afwImage.makeKernel
477 - interpLength: interpLength argument for lsst.afw.math.WarpingControl
478 - cacheSize: cacheSize argument for lsst.afw.math.WarpingControl;
479 0 disables the cache
480 10000 gives some speed improvement but less accurate results (atol must be increased)
481 100000 gives better accuracy but no speed improvement in this test
482 - rtol: relative tolerance as used by np.allclose
483 - atol: absolute tolerance as used by np.allclose
484 """
485 srcWcs = afwGeom.makeSkyWcs(
486 crpix=lsst.geom.Point2D(10, 11),
487 crval=lsst.geom.SpherePoint(41.7, 32.9, lsst.geom.degrees),
488 cdMatrix=afwGeom.makeCdMatrix(scale=0.2*lsst.geom.degrees),
489 )
490 destWcs = afwGeom.makeSkyWcs(
491 crpix=lsst.geom.Point2D(9, 10),
492 crval=lsst.geom.SpherePoint(41.65, 32.95, lsst.geom.degrees),
493 cdMatrix=afwGeom.makeCdMatrix(scale=0.17*lsst.geom.degrees),
494 )
496 srcMaskedImage = afwImage.MaskedImageF(100, 101)
497 srcExposure = afwImage.ExposureF(srcMaskedImage, srcWcs)
499 shape = srcMaskedImage.image.array.shape
500 srcMaskedImage.image.array[:] = np.random.normal(10000, 1000, size=shape)
501 srcMaskedImage.variance.array[:] = np.random.normal(9000, 900, size=shape)
502 srcMaskedImage.mask.array[:] = np.reshape(
503 np.arange(0, shape[0] * shape[1], 1, dtype=np.uint16), shape)
505 warpControl = afwMath.WarpingControl(
506 kernelName,
507 maskKernelName,
508 cacheSize,
509 interpLength,
510 growFullMask
511 )
512 destMaskedImage = afwImage.MaskedImageF(110, 121)
513 destExposure = afwImage.ExposureF(destMaskedImage, destWcs)
514 afwMath.warpExposure(destExposure, srcExposure, warpControl)
516 # now compute with two separate mask planes
517 warpControl.setGrowFullMask(0)
518 narrowMaskedImage = afwImage.MaskedImageF(110, 121)
519 narrowExposure = afwImage.ExposureF(narrowMaskedImage, destWcs)
520 afwMath.warpExposure(narrowExposure, srcExposure, warpControl)
522 warpControl.setMaskWarpingKernelName("")
523 broadMaskedImage = afwImage.MaskedImageF(110, 121)
524 broadExposure = afwImage.ExposureF(broadMaskedImage, destWcs)
525 afwMath.warpExposure(broadExposure, srcExposure, warpControl)
527 if (kernelName != maskKernelName) and (growFullMask != 0xFFFF):
528 # we expect the mask planes to differ
529 if np.all(narrowExposure.mask.array == broadExposure.mask.array):
530 self.fail("No difference between broad and narrow mask")
532 predMask = (broadExposure.mask.array & growFullMask) | (
533 narrowExposure.mask.array & ~growFullMask).astype(np.uint16)
534 predArraySet = (broadExposure.image.array, predMask, broadExposure.variance.array)
535 predExposure = afwImage.makeMaskedImageFromArrays(*predArraySet)
537 msg = f"Separate mask warping failed; warpingKernel={kernelName}; maskWarpingKernel={maskKernelName}"
538 self.assertMaskedImagesAlmostEqual(destExposure.getMaskedImage(), predExposure,
539 doImage=True, doMask=True, doVariance=True,
540 rtol=rtol, atol=atol, msg=msg)
542 @unittest.skipIf(afwdataDir is None, "afwdata not setup")
543 def compareToSwarp(self, kernelName,
544 useWarpExposure=True, useSubregion=False, useDeepCopy=False,
545 interpLength=10, cacheSize=100000,
546 rtol=4e-05, atol=1e-2):
547 """Compare warpExposure to swarp for given warping kernel.
549 Note that swarp only warps the image plane, so only test that plane.
551 Inputs:
552 - kernelName: name of kernel in the form used by afwImage.makeKernel
553 - useWarpExposure: if True, call warpExposure to warp an ExposureF,
554 else call warpImage to warp an ImageF and also call the Transform version
555 - useSubregion: if True then the original source exposure (from which the usual
556 test exposure was extracted) is read and the correct subregion extracted
557 - useDeepCopy: if True then the copy of the subimage is a deep copy,
558 else it is a shallow copy; ignored if useSubregion is False
559 - interpLength: interpLength argument for lsst.afw.math.WarpingControl
560 - cacheSize: cacheSize argument for lsst.afw.math.WarpingControl;
561 0 disables the cache
562 10000 gives some speed improvement but less accurate results (atol must be increased)
563 100000 gives better accuracy but no speed improvement in this test
564 - rtol: relative tolerance as used by np.allclose
565 - atol: absolute tolerance as used by np.allclose
566 """
567 warpingControl = afwMath.WarpingControl(
568 kernelName,
569 "", # there is no point to a separate mask kernel since we aren't testing the mask plane
570 cacheSize,
571 interpLength,
572 )
573 if useSubregion:
574 originalFullExposure = afwImage.ExposureF(originalExposurePath)
575 # "medsub" is a subregion of med starting at 0-indexed pixel (40, 150) of size 145 x 200
576 bbox = lsst.geom.Box2I(lsst.geom.Point2I(40, 150),
577 lsst.geom.Extent2I(145, 200))
578 originalExposure = afwImage.ExposureF(
579 originalFullExposure, bbox, afwImage.LOCAL, useDeepCopy)
580 swarpedImageName = f"medsubswarp1{kernelName}.fits"
581 else:
582 originalExposure = afwImage.ExposureF(originalExposurePath)
583 swarpedImageName = f"medswarp1{kernelName}.fits"
585 swarpedImagePath = os.path.join(dataDir, swarpedImageName)
586 swarpedDecoratedImage = afwImage.DecoratedImageF(swarpedImagePath)
587 swarpedImage = swarpedDecoratedImage.getImage()
588 swarpedMetadata = swarpedDecoratedImage.getMetadata()
589 warpedWcs = afwGeom.makeSkyWcs(swarpedMetadata)
591 if useWarpExposure:
592 # path for saved afw-warped image
593 afwWarpedImagePath = f"afwWarpedExposure1{kernelName}.fits"
595 afwWarpedMaskedImage = afwImage.MaskedImageF(
596 swarpedImage.getDimensions())
597 afwWarpedExposure = afwImage.ExposureF(
598 afwWarpedMaskedImage, warpedWcs)
599 afwMath.warpExposure(
600 afwWarpedExposure, originalExposure, warpingControl)
601 afwWarpedMask = afwWarpedMaskedImage.getMask()
602 if SAVE_FITS_FILES:
603 afwWarpedExposure.writeFits(afwWarpedImagePath)
604 if display:
605 afwDisplay.Display(frame=1).mtv(afwWarpedExposure, title="Warped")
607 swarpedMaskedImage = afwImage.MaskedImageF(swarpedImage)
609 if display:
610 afwDisplay.Display(frame=2).mtv(swarpedMaskedImage, title="SWarped")
612 msg = f"afw and swarp {kernelName}-warped differ (ignoring bad pixels)"
613 try:
614 self.assertMaskedImagesAlmostEqual(afwWarpedMaskedImage, swarpedMaskedImage,
615 doImage=True, doMask=False, doVariance=False,
616 skipMask=afwWarpedMask, rtol=rtol, atol=atol, msg=msg)
617 except Exception:
618 if SAVE_FAILED_FITS_FILES:
619 afwWarpedExposure.writeFits(afwWarpedImagePath)
620 print(f"Saved failed afw-warped exposure as: {afwWarpedImagePath}")
621 raise
622 else:
623 # path for saved afw-warped image
624 afwWarpedImagePath = f"afwWarpedImage1{kernelName}.fits"
625 afwWarpedImage2Path = f"afwWarpedImage1{kernelName}_xyTransform.fits"
627 afwWarpedImage = afwImage.ImageF(swarpedImage.getDimensions())
628 originalImage = originalExposure.getMaskedImage().getImage()
629 originalWcs = originalExposure.getWcs()
630 afwMath.warpImage(afwWarpedImage, warpedWcs, originalImage,
631 originalWcs, warpingControl)
632 if display:
633 afwDisplay.Display(frame=1).mtv(afwWarpedImage, title="Warped")
634 afwDisplay.Display(frame=2).mtv(swarpedImage, title="SWarped")
635 diff = swarpedImage.Factory(swarpedImage, True)
636 diff -= afwWarpedImage
637 afwDisplay.Display(frame=3).mtv(diff, title="swarp - afw")
638 if SAVE_FITS_FILES:
639 afwWarpedImage.writeFits(afwWarpedImagePath)
641 afwWarpedImageArr = afwWarpedImage.getArray()
642 noDataMaskArr = np.isnan(afwWarpedImageArr)
643 msg = f"afw and swarp {kernelName}-warped images do not match (ignoring NaN pixels)"
644 try:
645 self.assertImagesAlmostEqual(afwWarpedImage, swarpedImage,
646 skipMask=noDataMaskArr, rtol=rtol, atol=atol, msg=msg)
647 except Exception:
648 if SAVE_FAILED_FITS_FILES:
649 # save the image anyway
650 afwWarpedImage.writeFits(afwWarpedImagePath)
651 print(f"Saved failed afw-warped image as: {afwWarpedImagePath}")
652 raise
654 afwWarpedImage2 = afwImage.ImageF(swarpedImage.getDimensions())
655 srcToDest = afwGeom.makeWcsPairTransform(originalWcs, warpedWcs)
656 afwMath.warpImage(afwWarpedImage2, originalImage,
657 srcToDest, warpingControl)
658 msg = f"afw transform-based and WCS-based {kernelName}-warped images do not match"
659 try:
660 self.assertImagesAlmostEqual(afwWarpedImage2, afwWarpedImage,
661 rtol=rtol, atol=atol, msg=msg)
662 except Exception:
663 if SAVE_FAILED_FITS_FILES:
664 # save the image anyway
665 afwWarpedImage.writeFits(afwWarpedImage2)
666 print(f"Saved failed afw-warped image as: {afwWarpedImage2Path}")
667 raise
670class MemoryTester(lsst.utils.tests.MemoryTestCase):
671 pass
674def setup_module(module):
675 lsst.utils.tests.init()
678if __name__ == "__main__": 678 ↛ 679line 678 didn't jump to line 679, because the condition on line 678 was never true
679 lsst.utils.tests.init()
680 unittest.main()