Coverage for tests/test_addToCoadd.py: 20%
173 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-15 02:51 -0700
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-15 02:51 -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#
23"""Test lsst.coadd.utils.addToCoadd
24"""
25import os
26import unittest
28import numpy as np
30import lsst.utils
31import lsst.utils.tests
32import lsst.geom as geom
33import lsst.afw.image as afwImage
34import lsst.afw.math as afwMath
35import lsst.afw.display.ds9 as ds9
36import lsst.pex.exceptions as pexExcept
37import lsst.coadd.utils as coaddUtils
38from lsst.log import Log
40try:
41 display
42except NameError:
43 display = False
45Log.getLogger("coadd.utils").setLevel(Log.INFO)
47try:
48 AfwdataDir = lsst.utils.getPackageDir('afwdata')
49except Exception:
50 AfwdataDir = None
51# path to a medium-sized MaskedImage, relative to afwdata package root
52MedMiSubpath = os.path.join("data", "med.fits")
55def slicesFromBox(box, image):
56 """Computes the numpy slice in x and y associated with a parent bounding box
57 given an image/maskedImage/exposure
58 """
59 startInd = (box.getMinX() - image.getX0(), box.getMinY() - image.getY0())
60 stopInd = (startInd[0] + box.getWidth(), startInd[1] + box.getHeight())
61# print "slicesFromBox: box=(min=%s, dim=%s), imxy0=%s, imdim=%s, startInd=%s, stopInd=%s" %\
62# (box.getMin(), box.getDimensions(), image.getXY0(), image.getDimensions(), startInd, stopInd)
63 return (
64 slice(startInd[0], stopInd[0]),
65 slice(startInd[1], stopInd[1]),
66 )
69def referenceAddToCoadd(coadd, weightMap, maskedImage, badPixelMask, weight):
70 """Reference implementation of lsst.coadd.utils.addToCoadd
72 Unlike lsst.coadd.utils.addToCoadd this one does not update the input coadd and weightMap,
73 but instead returns the new versions (as numpy arrays).
75 Inputs:
76 - coadd: coadd before adding maskedImage (a MaskedImage)
77 - weightMap: weight map before adding maskedImage (an Image)
78 - maskedImage: masked image to add to coadd (a MaskedImage)
79 - badPixelMask: mask of bad pixels to ignore (an int)
80 - weight: relative weight of this maskedImage (a float)
82 Returns three items:
83 - overlapBBox: the overlap region relative to the parent (geom.Box2I)
84 - coaddArrayList: new coadd as a list of image, mask, variance numpy arrays
85 - weightMapArray: new weight map, as a numpy array
86 """
87 overlapBBox = coadd.getBBox()
88 overlapBBox.clip(maskedImage.getBBox())
90 coaddArrayList = tuple(arr.copy() for arr in coadd.getArrays())
91 weightMapArray = weightMap.getArray().copy()
93 if overlapBBox.isEmpty():
94 return (overlapBBox, coaddArrayList, weightMapArray)
96 maskedImageArrayList = maskedImage.getArrays()
97 badMaskArr = (maskedImageArrayList[1] & badPixelMask) != 0
99 coaddSlices = slicesFromBox(overlapBBox, coadd)
100 imageSlices = slicesFromBox(overlapBBox, maskedImage)
102 badMaskView = badMaskArr[imageSlices[1], imageSlices[0]]
103 for ind in range(3):
104 coaddView = coaddArrayList[ind][coaddSlices[1], coaddSlices[0]]
105 maskedImageView = maskedImageArrayList[ind][imageSlices[1], imageSlices[0]]
106 if ind == 1: # mask plane
107 coaddView |= np.where(badMaskView, 0, maskedImageView)
108 else: # image or variance plane
109 if ind == 0: # image
110 weightFac = weight
111 else: # variance
112 weightFac = weight**2
113 coaddView += np.where(badMaskView, 0, maskedImageView)*weightFac
114 weightMapView = weightMapArray[coaddSlices[1], coaddSlices[0]]
115 weightMapView += np.where(badMaskView, 0, 1)*weight
116 return overlapBBox, coaddArrayList, weightMapArray
119class AddToCoaddTestCase(unittest.TestCase):
120 """A test case for addToCoadd
121 """
123 def _testAddToCoaddImpl(self, useMask, uniformWeight=True):
124 """Test coadd"""
126 trueImageValue = 10.0
127 imBBox = geom.Box2I(geom.Point2I(0, 0), geom.Extent2I(10, 20))
128 if useMask:
129 coadd = afwImage.MaskedImageF(imBBox)
130 weightMap = coadd.getImage().Factory(coadd.getBBox())
132 badBits = 0x1
133 badPixel = (float("NaN"), badBits, 0)
134 truth = (trueImageValue, 0x0, 0)
135 else:
136 coadd = afwImage.ImageF(imBBox)
137 weightMap = coadd.Factory(coadd.getBBox())
139 badPixel = float("NaN")
140 truth = trueImageValue
142 for i in range(0, 20, 3):
143 image = coadd.Factory(coadd.getDimensions())
144 image.set(badPixel)
146 subBBox = geom.Box2I(geom.Point2I(0, i),
147 image.getDimensions() - geom.Extent2I(0, i))
148 subImage = image.Factory(image, subBBox, afwImage.LOCAL)
149 subImage.set(truth)
150 del subImage
152 weight = 1.0 if uniformWeight else 1.0 + 0.1*i
153 if useMask:
154 coaddUtils.addToCoadd(coadd, weightMap, image, badBits, weight)
155 else:
156 coaddUtils.addToCoadd(coadd, weightMap, image, weight)
158 self.assertEqual(image[-1, -1, afwImage.LOCAL], truth)
160 coadd /= weightMap
162 if display:
163 ds9.mtv(image, title="image", frame=1)
164 ds9.mtv(coadd, title="coadd", frame=2)
165 ds9.mtv(weightMap, title="weightMap", frame=3)
167 stats = afwMath.makeStatistics(coadd, afwMath.MEAN | afwMath.STDEV)
169 return [trueImageValue, stats.getValue(afwMath.MEAN), 0.0, stats.getValue(afwMath.STDEV)]
171 def testAddToCoaddMask(self):
172 """Test coadded MaskedImages"""
174 for uniformWeight in (False, True):
175 truth_mean, mean, truth_stdev, stdev = self._testAddToCoaddImpl(True, uniformWeight)
177 self.assertEqual(truth_mean, mean)
178 self.assertEqual(truth_stdev, stdev)
180 def testAddToCoaddNaN(self):
181 """Test coadded Images with NaN"""
183 for uniformWeight in (False, True):
184 truth_mean, mean, truth_stdev, stdev = self._testAddToCoaddImpl(False, uniformWeight)
186 self.assertEqual(truth_mean, mean)
187 self.assertEqual(truth_stdev, stdev)
190class AddToCoaddAfwdataTestCase(unittest.TestCase):
191 """A test case for addToCoadd using afwdata
192 """
194 def referenceTest(self, coadd, weightMap, image, badPixelMask, weight):
195 """Compare lsst implemenation of addToCoadd to a reference implementation.
197 Returns the overlap bounding box
198 """
199 # this call leaves coadd and weightMap alone:
200 overlapBBox, refCoaddArrayList, refweightMapArray = \
201 referenceAddToCoadd(coadd, weightMap, image, badPixelMask, weight)
202 # this updates coadd and weightMap:
203 afwOverlapBox = coaddUtils.addToCoadd(coadd, weightMap, image, badPixelMask, weight)
204 self.assertEqual(overlapBBox, afwOverlapBox)
206 coaddArrayList = coadd.getArrays()
207 weightMapArray = weightMap.getArray()
209 for name, ind in (("image", 0), ("mask", 1), ("variance", 2)):
210 if not np.allclose(coaddArrayList[ind], refCoaddArrayList[ind]):
211 errMsgList = (
212 "Computed %s does not match reference for badPixelMask=%s:" % (name, badPixelMask),
213 "computed= %s" % (coaddArrayList[ind],),
214 "reference= %s" % (refCoaddArrayList[ind],),
215 )
216 errMsg = "\n".join(errMsgList)
217 self.fail(errMsg)
218 if not np.allclose(weightMapArray, refweightMapArray):
219 errMsgList = (
220 "Computed weight map does not match reference for badPixelMask=%s:" % (badPixelMask,),
221 "computed= %s" % (weightMapArray,),
222 "reference= %s" % (refweightMapArray,),
223 )
224 errMsg = "\n".join(errMsgList)
225 self.fail(errMsg)
226 return overlapBBox
228 @unittest.skipUnless(AfwdataDir, "afwdata not available")
229 def testMed(self):
230 """Test addToCoadd by adding an image with known bad pixels using varying masks
231 """
232 medBBox = geom.Box2I(geom.Point2I(130, 315), geom.Extent2I(20, 21))
233 medMIPath = os.path.join(AfwdataDir, MedMiSubpath)
234 maskedImage = afwImage.MaskedImageF(afwImage.MaskedImageF(medMIPath), medBBox)
235 coadd = afwImage.MaskedImageF(medBBox)
236 weightMap = afwImage.ImageF(medBBox)
237 weight = 0.9
238 for badPixelMask in (0x00, 0xFF):
239 self.referenceTest(coadd, weightMap, maskedImage, badPixelMask, weight)
241 @unittest.skipUnless(AfwdataDir, "afwdata not available")
242 def testMultSizes(self):
243 """Test addToCoadd by adding various subregions of the med image
244 to a coadd that's a slightly different shape
245 """
246 bbox = geom.Box2I(geom.Point2I(130, 315), geom.Extent2I(30, 31))
247 medMIPath = os.path.join(AfwdataDir, MedMiSubpath)
248 fullMaskedImage = afwImage.MaskedImageF(medMIPath)
249 maskedImage = afwImage.MaskedImageF(fullMaskedImage, bbox)
250 coaddBBox = geom.Box2I(
251 maskedImage.getXY0() + geom.Extent2I(-6, +4),
252 maskedImage.getDimensions() + geom.Extent2I(10, -10))
253 coadd = afwImage.MaskedImageF(coaddBBox)
254 weightMap = afwImage.ImageF(coaddBBox)
255 badPixelMask = 0x0
257 # add masked image that extends beyond coadd in y
258 overlapBBox = self.referenceTest(coadd, weightMap, maskedImage, badPixelMask, 0.5)
259 self.assertFalse(overlapBBox.isEmpty())
261 # add masked image that extends beyond coadd in x
262 bbox = geom.Box2I(geom.Point2I(120, 320), geom.Extent2I(50, 10))
263 maskedImage = afwImage.MaskedImageF(fullMaskedImage, bbox)
264 overlapBBox = self.referenceTest(coadd, weightMap, maskedImage, badPixelMask, 0.5)
265 self.assertFalse(overlapBBox.isEmpty())
267 # add masked image that is fully within the coadd
268 bbox = geom.Box2I(geom.Point2I(130, 320), geom.Extent2I(10, 10))
269 maskedImage = afwImage.MaskedImageF(fullMaskedImage, bbox)
270 overlapBBox = self.referenceTest(coadd, weightMap, maskedImage, badPixelMask, 0.5)
271 self.assertFalse(overlapBBox.isEmpty())
273 # add masked image that does not overlap coadd
274 bbox = geom.Box2I(geom.Point2I(0, 0), geom.Extent2I(10, 10))
275 maskedImage = afwImage.MaskedImageF(fullMaskedImage, bbox)
276 overlapBBox = self.referenceTest(coadd, weightMap, maskedImage, badPixelMask, 0.5)
277 self.assertTrue(overlapBBox.isEmpty())
279 def testAssertions(self):
280 """Test that addToCoadd requires coadd and weightMap to have the same dimensions and xy0"""
281 maskedImage = afwImage.MaskedImageF(geom.Extent2I(10, 10))
282 coadd = afwImage.MaskedImageF(geom.Extent2I(11, 11))
283 coadd.setXY0(5, 6)
284 for dw, dh in (1, 0), (0, 1), (-1, 0), (0, -1):
285 weightMapBBox = geom.Box2I(coadd.getXY0(), coadd.getDimensions() + geom.Extent2I(dw, dh))
286 weightMap = afwImage.ImageF(weightMapBBox)
287 weightMap.setXY0(coadd.getXY0())
288 try:
289 coaddUtils.addToCoadd(coadd, weightMap, maskedImage, 0x0, 0.1)
290 self.fail("should have raised exception")
291 except pexExcept.Exception:
292 pass
293 for dx0, dy0 in (1, 0), (0, 1), (-1, 0), (0, -1):
294 weightMapBBox = geom.Box2I(coadd.getXY0() + geom.Extent2I(dx0, dy0), coadd.getDimensions())
295 weightMap = afwImage.ImageF(weightMapBBox)
296 try:
297 coaddUtils.addToCoadd(coadd, weightMap, maskedImage, 0x0, 0.1)
298 self.fail("should have raised exception")
299 except pexExcept.Exception:
300 pass
303class MemoryTester(lsst.utils.tests.MemoryTestCase):
304 pass
307def setup_module(module):
308 lsst.utils.tests.init()
311if __name__ == "__main__": 311 ↛ 312line 311 didn't jump to line 312, because the condition on line 311 was never true
312 lsst.utils.tests.init()
313 unittest.main()