Coverage for tests/test_addToCoadd.py: 21%

173 statements  

« prev     ^ index     » next       coverage.py v7.4.0, created at 2024-01-04 11:47 +0000

1# 

2# LSST Data Management System 

3# Copyright 2008, 2009, 2010 LSST Corporation. 

4# 

5# This product includes software developed by the 

6# LSST Project (http://www.lsst.org/). 

7# 

8# This program is free software: you can redistribute it and/or modify 

9# it under the terms of the GNU General Public License as published by 

10# the Free Software Foundation, either version 3 of the License, or 

11# (at your option) any later version. 

12# 

13# This program is distributed in the hope that it will be useful, 

14# but WITHOUT ANY WARRANTY; without even the implied warranty of 

15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

16# GNU General Public License for more details. 

17# 

18# You should have received a copy of the LSST License Statement and 

19# the GNU General Public License along with this program. If not, 

20# see <http://www.lsstcorp.org/LegalNotices/>. 

21# 

22 

23"""Test lsst.coadd.utils.addToCoadd 

24""" 

25import os 

26import unittest 

27 

28import numpy as np 

29 

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 

39 

40try: 

41 display 

42except NameError: 

43 display = False 

44 

45Log.getLogger("coadd.utils").setLevel(Log.INFO) 

46 

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") 

53 

54 

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 ) 

67 

68 

69def referenceAddToCoadd(coadd, weightMap, maskedImage, badPixelMask, weight): 

70 """Reference implementation of lsst.coadd.utils.addToCoadd 

71 

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). 

74 

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) 

81 

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()) 

89 

90 coaddArrayList = coadd.image.array.copy(), coadd.mask.array.copy(), coadd.variance.array.copy() 

91 weightMapArray = weightMap.array.copy() 

92 

93 if overlapBBox.isEmpty(): 

94 return (overlapBBox, coaddArrayList, weightMapArray) 

95 

96 maskedImageArrayList = maskedImage.image.array, maskedImage.mask.array, maskedImage.variance.array 

97 badMaskArr = (maskedImageArrayList[1] & badPixelMask) != 0 

98 

99 coaddSlices = slicesFromBox(overlapBBox, coadd) 

100 imageSlices = slicesFromBox(overlapBBox, maskedImage) 

101 

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 

117 

118 

119class AddToCoaddTestCase(unittest.TestCase): 

120 """A test case for addToCoadd 

121 """ 

122 

123 def _testAddToCoaddImpl(self, useMask, uniformWeight=True): 

124 """Test coadd""" 

125 

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()) 

131 

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()) 

138 

139 badPixel = float("NaN") 

140 truth = trueImageValue 

141 

142 for i in range(0, 20, 3): 

143 image = coadd.Factory(coadd.getDimensions()) 

144 image.set(badPixel) 

145 

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 

151 

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) 

157 

158 self.assertEqual(image[-1, -1, afwImage.LOCAL], truth) 

159 

160 coadd /= weightMap 

161 

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) 

166 

167 stats = afwMath.makeStatistics(coadd, afwMath.MEAN | afwMath.STDEV) 

168 

169 return [trueImageValue, stats.getValue(afwMath.MEAN), 0.0, stats.getValue(afwMath.STDEV)] 

170 

171 def testAddToCoaddMask(self): 

172 """Test coadded MaskedImages""" 

173 

174 for uniformWeight in (False, True): 

175 truth_mean, mean, truth_stdev, stdev = self._testAddToCoaddImpl(True, uniformWeight) 

176 

177 self.assertEqual(truth_mean, mean) 

178 self.assertEqual(truth_stdev, stdev) 

179 

180 def testAddToCoaddNaN(self): 

181 """Test coadded Images with NaN""" 

182 

183 for uniformWeight in (False, True): 

184 truth_mean, mean, truth_stdev, stdev = self._testAddToCoaddImpl(False, uniformWeight) 

185 

186 self.assertEqual(truth_mean, mean) 

187 self.assertEqual(truth_stdev, stdev) 

188 

189 

190class AddToCoaddAfwdataTestCase(unittest.TestCase): 

191 """A test case for addToCoadd using afwdata 

192 """ 

193 

194 def referenceTest(self, coadd, weightMap, image, badPixelMask, weight): 

195 """Compare lsst implemenation of addToCoadd to a reference implementation. 

196 

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) 

205 

206 coaddArrayList = coadd.image.array, coadd.mask.array, coadd.variance.array 

207 weightMapArray = weightMap.array 

208 

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 

227 

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) 

240 

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 

256 

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()) 

260 

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()) 

266 

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()) 

272 

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()) 

278 

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 

301 

302 

303class MemoryTester(lsst.utils.tests.MemoryTestCase): 

304 pass 

305 

306 

307def setup_module(module): 

308 lsst.utils.tests.init() 

309 

310 

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()