Coverage for tests/test_zogy.py : 20%

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
23import numpy as np
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
31from test_imageDecorrelation import makeFakeImages
33from lsst.ip.diffim.zogy import ZogyTask, ZogyConfig, ZogyMapReduceConfig, \
34 ZogyImagePsfMatchConfig, ZogyImagePsfMatchTask
35from lsst.ip.diffim.imageMapReduce import ImageMapReduceTask
37try:
38 type(verbose)
39except NameError:
40 verbose = False
43def setup_module(module):
44 lsst.utils.tests.init()
47class ZogyTest(lsst.utils.tests.TestCase):
48 """A test case for the Zogy task.
49 """
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
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"]))
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
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)
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
89 def _computePixelVariance(self, maskedIm):
90 statObj = afwMath.makeStatistics(maskedIm, afwMath.VARIANCECLIP,
91 self.statsControl)
92 var = statObj.getValue(afwMath.VARIANCECLIP)
93 return var
95 def _computePixelMean(self, maskedIm):
96 statObj = afwMath.makeStatistics(maskedIm, afwMath.MEANCLIP,
97 self.statsControl)
98 var = statObj.getValue(afwMath.MEANCLIP)
99 return var
101 def tearDown(self):
102 del self.im1ex
103 del self.im2ex
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).
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())
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)
130 self.assertFloatsAlmostEqual(pixMean_F, pixMean_R, atol=tol*2.) # nearly zero so need to use atol
132 def testZogyDiffim(self):
133 """Compute Zogy diffims using Fourier- and Real-space methods.
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)
147 def _testZogyScorr(self, varAst=0.):
148 """Compute Zogy likelihood images (Scorr) using Fourier- and Real-space methods.
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)
158 def testZogyScorr(self):
159 """Compute Zogy likelihood images (Scorr) using Fourier- and Real-space methods.
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)
168 def _testZogyDiffimMapReduced(self, inImageSpace=False, doScorr=False, **kwargs):
169 """Test running Zogy using ImageMapReduceTask framework.
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
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
193 self._compareExposures(D_mapReduced, D, tol=0.04, Scorr=doScorr)
195 def testZogyDiffimMapReduced(self):
196 """Test running Zogy using ImageMapReduceTask framework.
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)
211 def _testZogyImagePsfMatchTask(self, spatiallyVarying=False, inImageSpace=False,
212 doScorr=False, **kwargs):
213 """Test running Zogy using ZogyImagePsfMatchTask framework.
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
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)
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)
242 def testZogyImagePsfMatchTaskDifferentPsfSizes(self):
243 """Test running ZogyTask both with and without the spatiallyVarying option.
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 """
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
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)
273 # Try a range of PSF size combinations...
274 self._setUpImages()
275 self.im1ex = _growPsf(self.im1ex, (2, 3))
276 _runAllTests()
278 self.im2ex = _growPsf(self.im2ex, (3, 2))
279 _runAllTests()
281 self._setUpImages()
282 self.im2ex = _growPsf(self.im2ex, (1, 0))
283 _runAllTests()
285 self.im2ex = _growPsf(self.im2ex, (3, 6))
286 _runAllTests()
288 self.im1ex = _growPsf(self.im1ex, (5, 6))
289 _runAllTests()
292class MemoryTester(lsst.utils.tests.MemoryTestCase):
293 pass
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()