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