Coverage for tests/test_warpExposure.py : 16%

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