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