Hide keyboard shortcuts

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# 

2# LSST Data Management System 

3# Copyright 2016-2017 AURA/LSST. 

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 <https://www.lsstcorp.org/LegalNotices/>. 

21import unittest 

22 

23import numpy as np 

24 

25import lsst.utils.tests 

26import lsst.afw.image as afwImage 

27import lsst.afw.math as afwMath 

28import lsst.geom as geom 

29import lsst.meas.algorithms as measAlg 

30 

31from test_imageDecorrelation import makeFakeImages 

32 

33from lsst.ip.diffim.zogy import ZogyTask, ZogyConfig, ZogyMapReduceConfig, \ 

34 ZogyImagePsfMatchConfig, ZogyImagePsfMatchTask 

35from lsst.ip.diffim.imageMapReduce import ImageMapReduceTask 

36 

37try: 

38 type(verbose) 

39except NameError: 

40 verbose = False 

41 

42 

43def setup_module(module): 

44 lsst.utils.tests.init() 

45 

46 

47class ZogyTest(lsst.utils.tests.TestCase): 

48 """A test case for the Zogy task. 

49 """ 

50 

51 def setUp(self): 

52 self.psf1_sigma = 3.3 # sigma of psf of science image 

53 self.psf2_sigma = 2.2 # sigma of psf of template image 

54 

55 self.statsControl = afwMath.StatisticsControl() 

56 self.statsControl.setNumSigmaClip(3.) 

57 self.statsControl.setNumIter(3) 

58 self.statsControl.setAndMask(afwImage.Mask 

59 .getPlaneBitMask(["INTRP", "EDGE", "SAT", "CR", 

60 "DETECTED", "BAD", 

61 "NO_DATA", "DETECTED_NEGATIVE"])) 

62 

63 def _setUpImages(self, svar=100., tvar=100., varyPsf=0.): 

64 """Generate a fake aligned template and science image. 

65 """ 

66 self.svar = svar # variance of noise in science image 

67 self.tvar = tvar # variance of noise in template image 

68 

69 seed = 666 

70 self.im1ex, self.im2ex \ 

71 = makeFakeImages(size=(255, 257), svar=self.svar, tvar=self.tvar, 

72 psf1=self.psf1_sigma, psf2=self.psf2_sigma, 

73 n_sources=10, psf_yvary_factor=varyPsf, 

74 seed=seed, verbose=False) 

75 # Create an array corresponding to the "expected" subtraction (noise only) 

76 np.random.seed(seed) 

77 self.expectedSubtraction = np.random.normal(scale=np.sqrt(svar), size=self.im1ex.getDimensions()) 

78 self.expectedSubtraction -= np.random.normal(scale=np.sqrt(tvar), size=self.im2ex.getDimensions()) 

79 self.expectedVar = np.var(self.expectedSubtraction) 

80 self.expectedMean = np.mean(self.expectedSubtraction) 

81 

82 def _computeVarianceMean(self, maskedIm): 

83 statObj = afwMath.makeStatistics(maskedIm.getVariance(), 

84 maskedIm.getMask(), afwMath.MEANCLIP, 

85 self.statsControl) 

86 mn = statObj.getValue(afwMath.MEANCLIP) 

87 return mn 

88 

89 def _computePixelVariance(self, maskedIm): 

90 statObj = afwMath.makeStatistics(maskedIm, afwMath.VARIANCECLIP, 

91 self.statsControl) 

92 var = statObj.getValue(afwMath.VARIANCECLIP) 

93 return var 

94 

95 def _computePixelMean(self, maskedIm): 

96 statObj = afwMath.makeStatistics(maskedIm, afwMath.MEANCLIP, 

97 self.statsControl) 

98 var = statObj.getValue(afwMath.MEANCLIP) 

99 return var 

100 

101 def tearDown(self): 

102 del self.im1ex 

103 del self.im2ex 

104 

105 def _compareExposures(self, D_F, D_R, Scorr=False, tol=0.02): 

106 """Tests to compare the two images (diffim's or Scorr's). 

107 

108 See below. Also compare the diffim pixels with the "expected" 

109 pixels statistics. Only do the latter if Scorr==False. 

110 """ 

111 D_F.getMaskedImage().getMask()[:, :] = D_R.getMaskedImage().getMask() 

112 varMean_F = self._computeVarianceMean(D_F.getMaskedImage()) 

113 varMean_R = self._computeVarianceMean(D_R.getMaskedImage()) 

114 pixMean_F = self._computePixelMean(D_F.getMaskedImage()) 

115 pixMean_R = self._computePixelMean(D_R.getMaskedImage()) 

116 pixVar_F = self._computePixelVariance(D_F.getMaskedImage()) 

117 pixVar_R = self._computePixelVariance(D_R.getMaskedImage()) 

118 

119 if not Scorr: 

120 self.assertFloatsAlmostEqual(varMean_F, varMean_R, rtol=tol) 

121 self.assertFloatsAlmostEqual(pixMean_F, self.expectedMean, atol=tol*2.) 

122 self.assertFloatsAlmostEqual(pixMean_R, self.expectedMean, atol=tol*2.) 

123 self.assertFloatsAlmostEqual(pixVar_F, pixVar_R, rtol=tol) 

124 self.assertFloatsAlmostEqual(pixVar_F, self.expectedVar, rtol=tol*2.) 

125 self.assertFloatsAlmostEqual(pixVar_R, self.expectedVar, rtol=tol*2.) 

126 else: 

127 self.assertFloatsAlmostEqual(varMean_F, varMean_R, atol=tol) # nearly zero so need to use atol 

128 self.assertFloatsAlmostEqual(pixVar_F, pixVar_R, atol=tol) 

129 

130 self.assertFloatsAlmostEqual(pixMean_F, pixMean_R, atol=tol*2.) # nearly zero so need to use atol 

131 

132 def testZogyDiffim(self): 

133 """Compute Zogy diffims using Fourier- and Real-space methods. 

134 

135 Compare the images. They are not identical but should be 

136 similar (within ~2%). 

137 """ 

138 self._setUpImages() 

139 config = ZogyConfig() 

140 task = ZogyTask(templateExposure=self.im2ex, scienceExposure=self.im1ex, config=config) 

141 D_F = task.computeDiffim(inImageSpace=False) 

142 D_R = task.computeDiffim(inImageSpace=True) 

143 # Fourier-space and image-space versions are not identical, so up the tolerance. 

144 # This is a known issue with the image-space version. 

145 self._compareExposures(D_F.D, D_R.D, tol=0.03) 

146 

147 def _testZogyScorr(self, varAst=0.): 

148 """Compute Zogy likelihood images (Scorr) using Fourier- and Real-space methods. 

149 

150 Compare the images. They are not identical but should be similar (within ~2%). 

151 """ 

152 config = ZogyConfig() 

153 task = ZogyTask(templateExposure=self.im2ex, scienceExposure=self.im1ex, config=config) 

154 D_F = task.computeScorr(inImageSpace=False, xVarAst=varAst, yVarAst=varAst) 

155 D_R = task.computeScorr(inImageSpace=True, xVarAst=varAst, yVarAst=varAst) 

156 self._compareExposures(D_F.S, D_R.S, Scorr=True) 

157 

158 def testZogyScorr(self): 

159 """Compute Zogy likelihood images (Scorr) using Fourier- and Real-space methods. 

160 

161 Do the computation with "astrometric variance" both zero and non-zero. 

162 Compare the images. They are not identical but should be similar (within ~2%). 

163 """ 

164 self._setUpImages() 

165 self._testZogyScorr() 

166 self._testZogyScorr(varAst=0.1) 

167 

168 def _testZogyDiffimMapReduced(self, inImageSpace=False, doScorr=False, **kwargs): 

169 """Test running Zogy using ImageMapReduceTask framework. 

170 

171 Compare map-reduced version with non-map-reduced version. 

172 Do it for pure Fourier-based calc. and also for real-space. 

173 Also for computing pure diffim D and corrected likelihood image Scorr. 

174 """ 

175 config = ZogyMapReduceConfig() 

176 config.gridStepX = config.gridStepY = 9 

177 config.borderSizeX = config.borderSizeY = 3 

178 if inImageSpace: 

179 config.gridStepX = config.gridStepY = 8 

180 config.borderSizeX = config.borderSizeY = 6 # need larger border size for image-space run 

181 config.reducer.reduceOperation = 'average' 

182 task = ImageMapReduceTask(config=config) 

183 D_mapReduced = task.run(self.im1ex, template=self.im2ex, inImageSpace=inImageSpace, 

184 doScorr=doScorr, forceEvenSized=False, **kwargs).exposure 

185 

186 config = ZogyConfig() 

187 task = ZogyTask(templateExposure=self.im2ex, scienceExposure=self.im1ex, config=config) 

188 if not doScorr: 

189 D = task.computeDiffim(inImageSpace=inImageSpace, **kwargs).D 

190 else: 

191 D = task.computeScorr(inImageSpace=inImageSpace, **kwargs).S 

192 

193 self._compareExposures(D_mapReduced, D, tol=0.04, Scorr=doScorr) 

194 

195 def testZogyDiffimMapReduced(self): 

196 """Test running Zogy using ImageMapReduceTask framework. 

197 

198 Compare map-reduced version with non-map-reduced version. 

199 Do it for pure Fourier-based calc. and also for real-space. 

200 Do it for ZOGY diffim and corrected likelihood image Scorr. 

201 For Scorr, do it for zero and non-zero astrometric variance. 

202 """ 

203 self._setUpImages() 

204 self._testZogyDiffimMapReduced(inImageSpace=False) 

205 self._testZogyDiffimMapReduced(inImageSpace=True) 

206 self._testZogyDiffimMapReduced(inImageSpace=False, doScorr=True) 

207 self._testZogyDiffimMapReduced(inImageSpace=True, doScorr=True) 

208 self._testZogyDiffimMapReduced(inImageSpace=False, doScorr=True, xVarAst=0.1, yVarAst=0.1) 

209 self._testZogyDiffimMapReduced(inImageSpace=True, doScorr=True, xVarAst=0.1, yVarAst=0.1) 

210 

211 def _testZogyImagePsfMatchTask(self, spatiallyVarying=False, inImageSpace=False, 

212 doScorr=False, **kwargs): 

213 """Test running Zogy using ZogyImagePsfMatchTask framework. 

214 

215 Compare resulting diffim version with original, non-spatially-varying version. 

216 """ 

217 config = ZogyImagePsfMatchConfig() 

218 config.zogyMapReduceConfig.gridStepX = config.zogyMapReduceConfig.gridStepY = 9 

219 config.zogyMapReduceConfig.borderSizeX = config.zogyMapReduceConfig.borderSizeY = 3 

220 if inImageSpace: # need larger border size for image-space run 

221 config.zogyMapReduceConfig.gridStepX = config.zogyMapReduceConfig.gridStepY = 8 

222 config.zogyMapReduceConfig.borderSizeX = config.zogyMapReduceConfig.borderSizeY = 6 

223 task = ZogyImagePsfMatchTask(config=config) 

224 result = task.subtractExposures(self.im2ex, self.im1ex, inImageSpace=inImageSpace, 

225 doWarping=False, spatiallyVarying=spatiallyVarying) 

226 D_fromTask = result.subtractedExposure 

227 

228 config = ZogyConfig() 

229 task = ZogyTask(templateExposure=self.im2ex, scienceExposure=self.im1ex, config=config) 

230 D = task.computeDiffim(inImageSpace=inImageSpace, **kwargs).D 

231 self._compareExposures(D_fromTask, D, tol=0.04, Scorr=doScorr) 

232 

233 def testZogyImagePsfMatchTask(self): 

234 """Test running ZogyTask both with and without the spatiallyVarying option. 

235 """ 

236 self._setUpImages() 

237 self._testZogyImagePsfMatchTask(inImageSpace=False) 

238 self._testZogyImagePsfMatchTask(inImageSpace=True) 

239 self._testZogyImagePsfMatchTask(inImageSpace=False, spatiallyVarying=True) 

240 self._testZogyImagePsfMatchTask(inImageSpace=True, spatiallyVarying=True) 

241 

242 def testZogyImagePsfMatchTaskDifferentPsfSizes(self): 

243 """Test running ZogyTask both with and without the spatiallyVarying option. 

244 

245 Here we artificially set the two images to have PSFs with different dimensions 

246 to ensure this edge case passes. This also tests cases where one of the PSFs 

247 is not square. 

248 """ 

249 

250 # All this to grow the PSF of im1ex by a few pixels: 

251 def _growPsf(exp, extraPix=(2, 3)): 

252 bbox = exp.getBBox() 

253 center = ((bbox.getBeginX() + bbox.getEndX()) // 2., (bbox.getBeginY() + bbox.getEndY()) // 2.) 

254 center = geom.Point2D(center[0], center[1]) 

255 kern = exp.getPsf().computeKernelImage(center).convertF() 

256 kernSize = kern.getDimensions() 

257 paddedKern = afwImage.ImageF(kernSize[0] + extraPix[0], kernSize[1] + extraPix[1]) 

258 bboxToPlace = geom.Box2I(geom.Point2I((kernSize[0] + extraPix[0] - kern.getWidth()) // 2, 

259 (kernSize[1] + extraPix[1] - kern.getHeight()) // 2), 

260 kern.getDimensions()) 

261 paddedKern.assign(kern, bboxToPlace) 

262 fixedKern = afwMath.FixedKernel(paddedKern.convertD()) 

263 psfNew = measAlg.KernelPsf(fixedKern, center) 

264 exp.setPsf(psfNew) 

265 return exp 

266 

267 def _runAllTests(): 

268 self._testZogyImagePsfMatchTask(inImageSpace=False) 

269 self._testZogyImagePsfMatchTask(inImageSpace=True) 

270 self._testZogyImagePsfMatchTask(inImageSpace=False, spatiallyVarying=True) 

271 self._testZogyImagePsfMatchTask(inImageSpace=True, spatiallyVarying=True) 

272 

273 # Try a range of PSF size combinations... 

274 self._setUpImages() 

275 self.im1ex = _growPsf(self.im1ex, (2, 3)) 

276 _runAllTests() 

277 

278 self.im2ex = _growPsf(self.im2ex, (3, 2)) 

279 _runAllTests() 

280 

281 self._setUpImages() 

282 self.im2ex = _growPsf(self.im2ex, (1, 0)) 

283 _runAllTests() 

284 

285 self.im2ex = _growPsf(self.im2ex, (3, 6)) 

286 _runAllTests() 

287 

288 self.im1ex = _growPsf(self.im1ex, (5, 6)) 

289 _runAllTests() 

290 

291 

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

293 pass 

294 

295 

296if __name__ == "__main__": 296 ↛ 297line 296 didn't jump to line 297, because the condition on line 296 was never true

297 lsst.utils.tests.init() 

298 unittest.main()