Coverage for tests/test_imageDecorrelation.py : 15%

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.geom as afwGeom
28import lsst.afw.math as afwMath
29import lsst.geom as geom
30import lsst.meas.algorithms as measAlg
31import lsst.daf.base as dafBase
33from lsst.ip.diffim.imageDecorrelation import (DecorrelateALKernelTask,
34 DecorrelateALKernelMapReduceConfig,
35 DecorrelateALKernelSpatialConfig,
36 DecorrelateALKernelSpatialTask)
37from lsst.ip.diffim.imageMapReduce import ImageMapReduceTask
39try:
40 type(verbose)
41except NameError:
42 verbose = False
45def setup_module(module):
46 lsst.utils.tests.init()
49def singleGaussian2d(x, y, xc, yc, sigma_x=1., sigma_y=1., theta=0., ampl=1.):
50 """! Generate a 2-d Gaussian, possibly elongated and rotated, on a grid of pixel
51 coordinates given by x,y.
52 @param x,y each a 1-d numpy.array containing x- and y- coordinates for independent variables,
53 for example `np.arange(-16, 15)`.
54 @param xc,yc each a float giving the centroid of the gaussian
55 @param sigma_x,sigma_y each a float giving the sigma of the gaussian
56 @param theta a float giving the rotation of the gaussian (degrees)
57 @param ampl a float giving the amplitude of the gaussian
58 @return a 2-d numpy.array containing the normalized 2-d Gaussian
60 @Note this can be done in `astropy.modeling` but for now we have it explicitly here.
61 """
62 theta = (theta/180.) * np.pi
63 cos_theta2, sin_theta2 = np.cos(theta)**2., np.sin(theta)**2.
64 sigma_x2, sigma_y2 = sigma_x**2., sigma_y**2.
65 a = cos_theta2/(2.*sigma_x2) + sin_theta2/(2.*sigma_y2)
66 b = -(np.sin(2.*theta))/(4.*sigma_x2) + (np.sin(2.*theta))/(4.*sigma_y2)
67 c = sin_theta2/(2.*sigma_x2) + cos_theta2/(2.*sigma_y2)
68 xxc, yyc = x-xc, y-yc
69 out = np.exp(-(a*(xxc**2.) + 2.*b*xxc*yyc + c*(yyc**2.)))
70 out /= out.sum()
71 return out
74def makeFakeImages(size=(256, 256), svar=0.04, tvar=0.04, psf1=3.3, psf2=2.2, offset=None,
75 psf_yvary_factor=0., varSourceChange=1/50., theta1=0., theta2=0.,
76 n_sources=500, seed=66, verbose=False):
77 """Make two exposures: science and template pair with flux sources and random noise.
78 In all cases below, index (1) is the science image, and (2) is the template.
80 Parameters
81 ----------
82 size : `tuple` of `int`
83 Image pixel size (x,y). Pixel coordinates are set to
84 (-size[0]//2:size[0]//2, -size[1]//2:size[1]//2)
85 svar, tvar : `float`, optional
86 Per pixel variance of the added noise.
87 psf1, psf2 : `float`, optional
88 std. dev. of (Gaussian) PSFs for the two images in x,y direction. Default is
89 [3.3, 3.3] and [2.2, 2.2] for im1 and im2 respectively.
90 offset : `float`, optional
91 add a constant (pixel) astrometric offset between the two images.
92 psf_yvary_factor : `float`, optional
93 psf_yvary_factor vary the y-width of the PSF across the x-axis of the science image (zero,
94 the default, means no variation)
95 varSourceChange : `float`, optional
96 varSourceChange add this amount of fractional flux to a single source closest to
97 the center of the science image.
98 theta1, theta2: `float`, optional
99 PSF Gaussian rotation angles in degrees.
100 n_sources : `int`, optional
101 The number of sources to add to the images. If zero, no sources are
102 generated just background noise.
103 seed : `int`, optional
104 Random number generator seed.
105 verbose : `bool`, optional
106 Print some actual values.
108 Returns
109 -------
110 im1, im2 : `lsst.afw.image.Exposure`
111 The science and template exposures.
113 Notes
114 -----
115 If ``n_sources > 0`` and ``varSourceChange > 0.`` exactly one source,
116 that is closest to the center, will have different fluxes in the two
117 generated images. The flux on the science image will be higher by
118 ``varSourceChange`` fraction.
120 Having sources near the edges really messes up the
121 fitting (probably because of the convolution). So we make sure no
122 sources are near the edge.
124 Also it seems that having the variable source with a large
125 flux increase also messes up the fitting (seems to lead to
126 overfitting -- perhaps to the source itself). This might be fixed by
127 adding more constant sources.
128 """
129 np.random.seed(seed)
131 psf1 = [3.3, 3.3] if psf1 is None else psf1
132 if not hasattr(psf1, "__len__") and not isinstance(psf1, str):
133 psf1 = [psf1, psf1]
134 psf2 = [2.2, 2.2] if psf2 is None else psf2
135 if not hasattr(psf2, "__len__") and not isinstance(psf2, str):
136 psf2 = [psf2, psf2]
137 offset = [0., 0.] if offset is None else offset # astrometric offset (pixels) between the two images
138 if verbose:
139 print('Science PSF:', psf1, theta1)
140 print('Template PSF:', psf2, theta2)
141 print(np.sqrt(psf1[0]**2 - psf2[0]**2))
142 print('Offset:', offset)
144 xim = np.arange(-size[0]//2, size[0]//2, 1)
145 yim = np.arange(-size[1]//2, size[1]//2, 1)
146 x0im, y0im = np.meshgrid(yim, xim)
147 im1 = np.random.normal(scale=np.sqrt(svar), size=x0im.shape) # variance of science image
148 im2 = np.random.normal(scale=np.sqrt(tvar), size=x0im.shape) # variance of template
150 if n_sources > 0:
151 fluxes = np.random.uniform(50, 30000, n_sources)
152 xposns = np.random.uniform(xim.min()+16, xim.max()-5, n_sources)
153 yposns = np.random.uniform(yim.min()+16, yim.max()-5, n_sources)
155 # Make the source closest to the center of the image the one that increases in flux
156 ind = np.argmin(xposns**2. + yposns**2.)
158 # vary the y-width of psf across x-axis of science image (zero means no variation):
159 psf1_yvary = psf_yvary_factor * (yim.mean() - yposns) / yim.max()
160 if verbose:
161 print('PSF y spatial-variation:', psf1_yvary.min(), psf1_yvary.max())
163 for i in range(n_sources):
164 flux = fluxes[i]
165 tmp = flux * singleGaussian2d(x0im, y0im, xposns[i], yposns[i], psf2[0], psf2[1], theta=theta2)
166 im2 += tmp
167 if i == ind:
168 flux += flux * varSourceChange
169 tmp = flux * singleGaussian2d(x0im, y0im, xposns[i]+offset[0], yposns[i]+offset[1],
170 psf1[0], psf1[1]+psf1_yvary[i], theta=theta1)
171 im1 += tmp
173 im1_psf = singleGaussian2d(x0im, y0im, 0, 0, psf1[0], psf1[1], theta=theta1)
174 im2_psf = singleGaussian2d(x0im, y0im, offset[0], offset[1], psf2[0], psf2[1], theta=theta2)
176 def makeWcs(offset=0):
177 """ Make a fake Wcs
179 Parameters
180 ----------
181 offset : float
182 offset the Wcs by this many pixels.
183 """
184 # taken from $AFW_DIR/tests/testMakeWcs.py
185 metadata = dafBase.PropertySet()
186 metadata.set("SIMPLE", "T")
187 metadata.set("BITPIX", -32)
188 metadata.set("NAXIS", 2)
189 metadata.set("NAXIS1", 1024)
190 metadata.set("NAXIS2", 1153)
191 metadata.set("RADESYS", 'FK5')
192 metadata.set("EQUINOX", 2000.)
193 metadata.setDouble("CRVAL1", 215.604025685476)
194 metadata.setDouble("CRVAL2", 53.1595451514076)
195 metadata.setDouble("CRPIX1", 1109.99981456774 + offset)
196 metadata.setDouble("CRPIX2", 560.018167811613 + offset)
197 metadata.set("CTYPE1", 'RA---SIN')
198 metadata.set("CTYPE2", 'DEC--SIN')
199 metadata.setDouble("CD1_1", 5.10808596133527E-05)
200 metadata.setDouble("CD1_2", 1.85579539217196E-07)
201 metadata.setDouble("CD2_2", -5.10281493481982E-05)
202 metadata.setDouble("CD2_1", -8.27440751733828E-07)
203 return afwGeom.makeSkyWcs(metadata)
205 def makeExposure(imgArray, psfArray, imgVariance):
206 """! Convert an image numpy.array and corresponding PSF numpy.array into an exposure.
208 Add the (constant) variance plane equal to `imgVariance`.
210 @param imgArray 2-d numpy.array containing the image
211 @param psfArray 2-d numpy.array containing the PSF image
212 @param imgVariance variance of input image
213 @return a new exposure containing the image, PSF and desired variance plane
214 """
215 # All this code to convert the template image array/psf array into an exposure.
216 bbox = geom.Box2I(geom.Point2I(0, 0), geom.Point2I(imgArray.shape[1]-1, imgArray.shape[0]-1))
217 im1ex = afwImage.ExposureD(bbox)
218 im1ex.getMaskedImage().getImage().getArray()[:, :] = imgArray
219 im1ex.getMaskedImage().getVariance().getArray()[:, :] = imgVariance
220 psfBox = geom.Box2I(geom.Point2I(-12, -12), geom.Point2I(12, 12)) # a 25x25 pixel psf
221 psf = afwImage.ImageD(psfBox)
222 psfBox.shift(geom.Extent2I(size[0]//2, size[1]//2))
223 im1_psf_sub = psfArray[psfBox.getMinX():psfBox.getMaxX()+1, psfBox.getMinY():psfBox.getMaxY()+1]
224 psf.getArray()[:, :] = im1_psf_sub
225 psfK = afwMath.FixedKernel(psf)
226 psfNew = measAlg.KernelPsf(psfK)
227 im1ex.setPsf(psfNew)
228 wcs = makeWcs()
229 im1ex.setWcs(wcs)
230 return im1ex
232 im1ex = makeExposure(im1, im1_psf, svar) # Science image
233 im2ex = makeExposure(im2, im2_psf, tvar) # Template
235 return im1ex, im2ex
238def estimatePixelCorrelation(B, nDist=40, convEdge=17):
239 """Estimate correlation as a function of pixel distance in the image
240 by sampling pixel pairs.
242 Parameters
243 ----------
244 B : `numpy.ndarray` of N x N `float` elements
245 Noise only image with zero pixel expectation value and identical variance
246 in all pixels. Must have equal dimensions.
247 nDist : `int`, optional
248 Estimated distances goes from 0 to nDist-1.
249 nDist must be smaller than the half dimensions of B.
250 convEdge : `int`, optional
251 Edge width where convolution did not happen.
253 Returns
254 -------
255 S : `numpy.ndarray` of nDist `float` elements
256 Correlation from 0 to nDist-1 pix distance. Pixels are normed by their
257 variance estimation. S[0], the autocorrelation, should be close to 1.
258 """
259 S = np.zeros(nDist, dtype=float)
260 nSample = 10000
261 # Cannot use nDist wide edge, otherwise 2nd pixel can go off the image.
262 # Don't bother with it.
263 A = B/np.sqrt(np.mean(B[convEdge:-convEdge, convEdge:-convEdge]
264 * B[convEdge:-convEdge, convEdge:-convEdge]))
265 lEdge = nDist + convEdge
266 rEdge = B.shape[0] - lEdge
267 for r in range(nDist):
268 ind1 = np.random.randint(lEdge, rEdge, (2, nSample))
269 ind2 = np.copy(ind1)
270 # generate delta x,y in random directions uniformly
271 c_dxy = np.exp(2.j*np.pi*np.random.random(nSample))
272 ind2[0] += np.around(np.real(c_dxy)*r).astype(int)
273 ind2[1] += np.around(np.imag(c_dxy)*r).astype(int)
274 S[r] = np.sum(A[ind1[0], ind1[1]] * A[ind2[0], ind2[1]])/nSample
275 return S
278class DiffimCorrectionTest(lsst.utils.tests.TestCase):
279 """A test case for the diffim image decorrelation algorithm.
280 """
282 def setUp(self):
283 self.psf1_sigma = 3.3 # sigma of psf of science image
284 self.psf2_sigma = 2.2 # sigma of psf of template image
286 self.statsControl = afwMath.StatisticsControl()
287 self.statsControl.setNumSigmaClip(3.)
288 self.statsControl.setNumIter(3)
289 self.statsControl.setAndMask(afwImage.Mask
290 .getPlaneBitMask(["INTRP", "EDGE", "SAT", "CR",
291 "DETECTED", "BAD",
292 "NO_DATA", "DETECTED_NEGATIVE"]))
294 def _setUpImages(self, svar=0.04, tvar=0.04, varyPsf=0.):
295 """Generate a fake aligned template and science image.
296 """
298 self.svar = svar # variance of noise in science image
299 self.tvar = tvar # variance of noise in template image
301 self.im1ex, self.im2ex \
302 = makeFakeImages(svar=self.svar, tvar=self.tvar, psf1=self.psf1_sigma, psf2=self.psf2_sigma,
303 n_sources=50, psf_yvary_factor=varyPsf, verbose=False)
305 def _setUpSourcelessImages(self, svar, tvar):
306 """Generate noise only template and science images.
307 """
309 self.svar = svar # variance of noise in science image
310 self.tvar = tvar # variance of noise in template image
312 self.im1ex, self.im2ex = makeFakeImages(
313 svar=self.svar, tvar=self.tvar, psf1=self.psf1_sigma, psf2=self.psf2_sigma,
314 n_sources=0, seed=22, varSourceChange=0, psf_yvary_factor=0)
316 def _computeVarianceMean(self, maskedIm):
317 statObj = afwMath.makeStatistics(maskedIm.getVariance(),
318 maskedIm.getMask(), afwMath.MEANCLIP,
319 self.statsControl)
320 mn = statObj.getValue(afwMath.MEANCLIP)
321 return mn
323 def _computePixelVariance(self, maskedIm):
324 statObj = afwMath.makeStatistics(maskedIm, afwMath.VARIANCECLIP,
325 self.statsControl)
326 var = statObj.getValue(afwMath.VARIANCECLIP)
327 return var
329 def tearDown(self):
330 del self.im1ex
331 del self.im2ex
333 def _makeAndTestUncorrectedDiffim(self):
334 """Create the (un-decorrelated) diffim, and verify that its variance is too low.
335 """
336 # Create the matching kernel. We used Gaussian PSFs for im1 and im2, so we can compute the "expected"
337 # matching kernel sigma.
338 psf1_sig = self.im1ex.getPsf().computeShape().getDeterminantRadius()
339 psf2_sig = self.im2ex.getPsf().computeShape().getDeterminantRadius()
340 sig_match = np.sqrt((psf1_sig**2. - psf2_sig**2.))
341 # Sanity check - make sure PSFs are correct.
342 self.assertFloatsAlmostEqual(sig_match, np.sqrt((self.psf1_sigma**2. - self.psf2_sigma**2.)),
343 rtol=2e-5)
344 # mKernel = measAlg.SingleGaussianPsf(31, 31, sig_match)
345 x0 = np.arange(-16, 16, 1)
346 y0 = x0.copy()
347 x0im, y0im = np.meshgrid(x0, y0)
348 matchingKernel = singleGaussian2d(x0im, y0im, -1., -1., sigma_x=sig_match, sigma_y=sig_match)
349 kernelImg = afwImage.ImageD(matchingKernel.shape[0], matchingKernel.shape[1])
350 kernelImg.getArray()[:, :] = matchingKernel
351 mKernel = afwMath.FixedKernel(kernelImg)
353 # Create the matched template by convolving the template with the matchingKernel
354 matched_im2ex = self.im2ex.clone()
355 convCntrl = afwMath.ConvolutionControl(False, True, 0)
356 afwMath.convolve(matched_im2ex.getMaskedImage(), self.im2ex.getMaskedImage(), mKernel, convCntrl)
358 # Expected (ideal) variance of difference image
359 expected_var = self.svar + self.tvar
360 if verbose:
361 print('EXPECTED VARIANCE:', expected_var)
363 # Create the diffim (uncorrected)
364 # Uncorrected diffim exposure - variance plane is wrong (too low)
365 tmp_diffExp = self.im1ex.getMaskedImage().clone()
366 tmp_diffExp -= matched_im2ex.getMaskedImage()
367 var = self._computeVarianceMean(tmp_diffExp)
368 self.assertLess(var, expected_var)
370 # Uncorrected diffim exposure - variance is wrong (too low) - same as above but on pixels
371 diffExp = self.im1ex.clone()
372 tmp = diffExp.getMaskedImage()
373 tmp -= matched_im2ex.getMaskedImage()
374 var = self._computePixelVariance(diffExp.getMaskedImage())
375 self.assertLess(var, expected_var)
377 # Uncorrected diffim exposure - variance plane is wrong (too low)
378 mn = self._computeVarianceMean(diffExp.getMaskedImage())
379 self.assertLess(mn, expected_var)
380 if verbose:
381 print('UNCORRECTED VARIANCE:', var, mn)
383 return diffExp, mKernel, expected_var
385 def _runDecorrelationTask(self, diffExp, mKernel):
386 """ Run the decorrelation task on the given diffim with the given matching kernel
387 """
388 task = DecorrelateALKernelTask()
389 decorrResult = task.run(self.im1ex, self.im2ex, diffExp, mKernel)
390 corrected_diffExp = decorrResult.correctedExposure
391 return corrected_diffExp
393 def _testDecorrelation(self, expected_var, corrected_diffExp):
394 """ Check that the variance of the corrected diffim matches the theoretical value.
395 """
396 # Corrected diffim - variance should be close to expected.
397 # We set the tolerance a bit higher here since the simulated images have many bright stars
398 var = self._computePixelVariance(corrected_diffExp.getMaskedImage())
399 self.assertFloatsAlmostEqual(var, expected_var, rtol=0.05)
401 # Check statistics of variance plane in corrected diffim
402 mn = self._computeVarianceMean(corrected_diffExp.getMaskedImage())
403 if verbose:
404 print('CORRECTED VARIANCE:', var, mn)
405 self.assertFloatsAlmostEqual(mn, expected_var, rtol=0.02)
406 self.assertFloatsAlmostEqual(var, mn, rtol=0.05)
407 return var, mn
409 def _testDiffimCorrection(self, svar, tvar):
410 """ Run decorrelation and check the variance of the corrected diffim.
411 """
412 self._setUpImages(svar=svar, tvar=tvar)
413 diffExp, mKernel, expected_var = self._makeAndTestUncorrectedDiffim()
414 corrected_diffExp = self._runDecorrelationTask(diffExp, mKernel)
415 self._testDecorrelation(expected_var, corrected_diffExp)
417 def testDiffimCorrection(self):
418 """Test decorrelated diffim from images with different combinations of variances.
419 """
420 # Same variance
421 self._testDiffimCorrection(svar=0.04, tvar=0.04)
422 # Science image variance is higher than that of the template.
423 self._testDiffimCorrection(svar=0.08, tvar=0.04)
424 # Template variance is higher than that of the science img.
425 self._testDiffimCorrection(svar=0.04, tvar=0.08)
427 def testNoiseDiffimCorrection(self):
428 """Test correction by estimating correlation directly on a noise difference image.
430 Notes
431 ------
433 See `lsst-dm/diffimTests` notebook `DM-24371_correlation_estimate.ipynb`
434 for further details of how the correlation looks like in the uncorrected
435 and corrected cases and where the tolerance numbers come from.
436 """
437 svar = 1.
438 tvar = 100.
439 # Based on DM-24371_correlation_estimate.ipynb
440 someCorrelationThreshold = 0.2
442 self._setUpSourcelessImages(svar=svar, tvar=tvar)
443 diffExp, mKernel, expected_var = self._makeAndTestUncorrectedDiffim()
444 corrected_diffExp = self._runDecorrelationTask(diffExp, mKernel)
446 rho_sci = estimatePixelCorrelation(self.im1ex.getImage().getArray())
447 rho_rawdiff = estimatePixelCorrelation(diffExp.getImage().getArray())
448 rho_corrdiff = estimatePixelCorrelation(corrected_diffExp.getImage().getArray())
450 # Autocorrelation sanity check
451 self.assertFloatsAlmostEqual(rho_sci[0], 1., atol=0.1, rtol=None)
452 self.assertFloatsAlmostEqual(rho_rawdiff[0], 1., atol=0.1, rtol=None)
453 self.assertFloatsAlmostEqual(rho_corrdiff[0], 1., atol=0.1, rtol=None)
455 # Uncorrelated input check
456 self.assertFloatsAlmostEqual(rho_sci[1:], 0., atol=0.1, rtol=None)
458 # Without correction there should be correlation up to a few pixel distance
459 self.assertGreater(rho_rawdiff[1], someCorrelationThreshold)
460 self.assertGreater(rho_rawdiff[2], someCorrelationThreshold)
461 self.assertGreater(rho_rawdiff[3], someCorrelationThreshold)
463 # Uncorrelated corrected image check
464 self.assertFloatsAlmostEqual(rho_corrdiff[1:], 0., atol=0.1, rtol=None)
466 def _runDecorrelationTaskMapReduced(self, diffExp, mKernel):
467 """ Run decorrelation using the imageMapReducer.
468 """
469 config = DecorrelateALKernelMapReduceConfig()
470 config.borderSizeX = config.borderSizeY = 3
471 config.reducer.reduceOperation = 'average'
472 task = ImageMapReduceTask(config=config)
473 decorrResult = task.run(diffExp, template=self.im2ex, science=self.im1ex,
474 psfMatchingKernel=mKernel, forceEvenSized=True)
475 corrected_diffExp = decorrResult.exposure
476 return corrected_diffExp
478 def _testDiffimCorrection_mapReduced(self, svar, tvar, varyPsf=0.0):
479 """ Run decorrelation using the imageMapReduce task, and check the variance of
480 the corrected diffim.
481 """
482 self._setUpImages(svar=svar, tvar=tvar, varyPsf=varyPsf)
483 diffExp, mKernel, expected_var = self._makeAndTestUncorrectedDiffim()
484 corrected_diffExp = self._runDecorrelationTaskMapReduced(diffExp, mKernel)
485 self._testDecorrelation(expected_var, corrected_diffExp)
486 # Also compare the diffim generated here vs. the non-ImageMapReduce one
487 corrected_diffExp_OLD = self._runDecorrelationTask(diffExp, mKernel)
488 self.assertMaskedImagesAlmostEqual(corrected_diffExp.getMaskedImage(),
489 corrected_diffExp_OLD.getMaskedImage())
491 @unittest.skip("DM-21868 ImageMapReduce usage is not yet supported")
492 def testDiffimCorrection_mapReduced(self):
493 """ Test decorrelated diffim when using the imageMapReduce task.
494 Compare results with those from the original DecorrelateALKernelTask.
495 """
496 # Same variance
497 self._testDiffimCorrection_mapReduced(svar=0.04, tvar=0.04)
498 # Science image variance is higher than that of the template.
499 self._testDiffimCorrection_mapReduced(svar=0.04, tvar=0.08)
500 # Template variance is higher than that of the science img.
501 self._testDiffimCorrection_mapReduced(svar=0.08, tvar=0.04)
503 def _runDecorrelationSpatialTask(self, diffExp, mKernel, spatiallyVarying=False):
504 """ Run decorrelation using the DecorrelateALKernelSpatialTask.
505 """
506 config = DecorrelateALKernelSpatialConfig()
507 task = DecorrelateALKernelSpatialTask(config=config)
508 decorrResult = task.run(scienceExposure=self.im1ex, templateExposure=self.im2ex,
509 subtractedExposure=diffExp, psfMatchingKernel=mKernel,
510 spatiallyVarying=spatiallyVarying)
511 corrected_diffExp = decorrResult.correctedExposure
512 return corrected_diffExp
514 def _testDiffimCorrection_spatialTask(self, svar, tvar, varyPsf=0.0):
515 """Run decorrelation using the DecorrelateALKernelSpatialTask, and
516 check the variance of the corrected diffim. Do it for `spatiallyVarying` both
517 True and False. Also compare the variances between the two `spatiallyVarying`
518 cases.
519 """
520 self._setUpImages(svar=svar, tvar=tvar, varyPsf=varyPsf)
521 diffExp, mKernel, expected_var = self._makeAndTestUncorrectedDiffim()
522 variances = []
523 for spatiallyVarying in [False, True]:
524 corrected_diffExp = self._runDecorrelationSpatialTask(diffExp, mKernel,
525 spatiallyVarying)
526 var, mn = self._testDecorrelation(expected_var, corrected_diffExp)
527 variances.append(var)
528 self.assertFloatsAlmostEqual(variances[0], variances[1], rtol=0.03)
530 def testDiffimCorrection_spatialTask(self):
531 """Test decorrelated diffim when using the DecorrelateALKernelSpatialTask.
532 Compare results with those from the original DecorrelateALKernelTask.
533 """
534 # Same variance
535 self._testDiffimCorrection_spatialTask(svar=0.04, tvar=0.04)
536 # Science image variance is higher than that of the template.
537 self._testDiffimCorrection_spatialTask(svar=0.04, tvar=0.08)
538 # Template variance is higher than that of the science img.
539 self._testDiffimCorrection_spatialTask(svar=0.08, tvar=0.04)
542class MemoryTester(lsst.utils.tests.MemoryTestCase):
543 pass
546if __name__ == "__main__": 546 ↛ 547line 546 didn't jump to line 547, because the condition on line 546 was never true
547 lsst.utils.tests.init()
548 unittest.main()