Coverage for tests/test_imageDecorrelation.py: 16%

268 statements  

« prev     ^ index     » next       coverage.py v6.4, created at 2022-05-25 12:29 +0000

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 

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 

33 

34from lsst.ip.diffim.imageDecorrelation import (DecorrelateALKernelTask, 

35 DecorrelateALKernelConfig, 

36 DecorrelateALKernelMapReduceConfig, 

37 DecorrelateALKernelSpatialConfig, 

38 DecorrelateALKernelSpatialTask) 

39from lsst.ip.diffim.imageMapReduce import ImageMapReduceTask 

40 

41try: 

42 type(verbose) 

43except NameError: 

44 verbose = False 

45 

46 

47def setup_module(module): 

48 lsst.utils.tests.init() 

49 

50 

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 

61 

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 

74 

75 

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. 

82 

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. 

110 

111 Returns 

112 ------- 

113 im1, im2 : `lsst.afw.image.Exposure` 

114 The science and template exposures. 

115 

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. 

122 

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. 

126 

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) 

133 

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) 

146 

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 

152 

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) 

157 

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

160 

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

165 

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 

175 

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) 

178 

179 def makeWcs(offset=0): 

180 """ Make a fake Wcs 

181 

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) 

207 

208 def makeExposure(imgArray, psfArray, imgVariance): 

209 """! Convert an image numpy.array and corresponding PSF numpy.array into an exposure. 

210 

211 Add the (constant) variance plane equal to `imgVariance`. 

212 

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 

234 

235 im1ex = makeExposure(im1, im1_psf, svar) # Science image 

236 im2ex = makeExposure(im2, im2_psf, tvar) # Template 

237 

238 return im1ex, im2ex 

239 

240 

241def estimatePixelCorrelation(B, nDist=40, convEdge=17): 

242 """Estimate correlation as a function of pixel distance in the image 

243 by sampling pixel pairs. 

244 

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. 

255 

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 

279 

280 

281class DiffimCorrectionTest(lsst.utils.tests.TestCase): 

282 """A test case for the diffim image decorrelation algorithm. 

283 """ 

284 

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 

288 

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

296 

297 def _setUpImages(self, svar=0.04, tvar=0.04, varyPsf=0.): 

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

299 """ 

300 

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

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

303 

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) 

307 

308 def _setUpSourcelessImages(self, svar, tvar): 

309 """Generate noise only template and science images. 

310 """ 

311 

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

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

314 

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) 

318 

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 

325 

326 def _computePixelVariance(self, maskedIm): 

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

328 self.statsControl) 

329 var = statObj.getValue(afwMath.VARIANCECLIP) 

330 return var 

331 

332 def tearDown(self): 

333 del self.im1ex 

334 del self.im2ex 

335 

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 psf1pos = self.im1ex.getPsf().getAveragePosition() 

342 psf2pos = self.im2ex.getPsf().getAveragePosition() 

343 psf1_sig = self.im1ex.getPsf().computeShape(psf1pos).getDeterminantRadius() 

344 psf2_sig = self.im2ex.getPsf().computeShape(psf2pos).getDeterminantRadius() 

345 sig_match = np.sqrt((psf1_sig**2. - psf2_sig**2.)) 

346 # Sanity check - make sure PSFs are correct. 

347 self.assertFloatsAlmostEqual(sig_match, np.sqrt((self.psf1_sigma**2. - self.psf2_sigma**2.)), 

348 rtol=2e-5) 

349 # mKernel = measAlg.SingleGaussianPsf(31, 31, sig_match) 

350 x0 = np.arange(-16, 16, 1) 

351 y0 = x0.copy() 

352 x0im, y0im = np.meshgrid(x0, y0) 

353 matchingKernel = singleGaussian2d(x0im, y0im, -1., -1., sigma_x=sig_match, sigma_y=sig_match) 

354 kernelImg = afwImage.ImageD(matchingKernel.shape[0], matchingKernel.shape[1]) 

355 kernelImg.getArray()[:, :] = matchingKernel 

356 mKernel = afwMath.FixedKernel(kernelImg) 

357 

358 # Create the matched template by convolving the template with the matchingKernel 

359 matched_im2ex = self.im2ex.clone() 

360 convCntrl = afwMath.ConvolutionControl(False, True, 0) 

361 afwMath.convolve(matched_im2ex.getMaskedImage(), self.im2ex.getMaskedImage(), mKernel, convCntrl) 

362 

363 # Expected (ideal) variance of difference image 

364 expected_var = self.svar + self.tvar 

365 if verbose: 

366 print('EXPECTED VARIANCE:', expected_var) 

367 

368 # Create the diffim (uncorrected) 

369 # Uncorrected diffim exposure - variance plane is wrong (too low) 

370 tmp_diffExp = self.im1ex.getMaskedImage().clone() 

371 tmp_diffExp -= matched_im2ex.getMaskedImage() 

372 var = self._computeVarianceMean(tmp_diffExp) 

373 self.assertLess(var, expected_var) 

374 

375 # Uncorrected diffim exposure - variance is wrong (too low) - same as above but on pixels 

376 diffExp = self.im1ex.clone() 

377 tmp = diffExp.getMaskedImage() 

378 tmp -= matched_im2ex.getMaskedImage() 

379 var = self._computePixelVariance(diffExp.getMaskedImage()) 

380 self.assertLess(var, expected_var) 

381 

382 # Uncorrected diffim exposure - variance plane is wrong (too low) 

383 mn = self._computeVarianceMean(diffExp.getMaskedImage()) 

384 self.assertLess(mn, expected_var) 

385 if verbose: 

386 print('UNCORRECTED VARIANCE:', var, mn) 

387 

388 return diffExp, mKernel, expected_var 

389 

390 def _runDecorrelationTask(self, diffExp, mKernel, config=None): 

391 """ Run the decorrelation task on the given diffim with the given matching kernel 

392 """ 

393 task = DecorrelateALKernelTask(config=config) 

394 decorrResult = task.run(self.im1ex, self.im2ex, diffExp, mKernel) 

395 corrected_diffExp = decorrResult.correctedExposure 

396 return corrected_diffExp 

397 

398 def _testDecorrelation(self, expected_var, corrected_diffExp): 

399 """ Check that the variance of the corrected diffim matches the theoretical value. 

400 """ 

401 # Corrected diffim - variance should be close to expected. 

402 # We set the tolerance a bit higher here since the simulated images have many bright stars 

403 var = self._computePixelVariance(corrected_diffExp.getMaskedImage()) 

404 self.assertFloatsAlmostEqual(var, expected_var, rtol=0.05) 

405 

406 # Check statistics of variance plane in corrected diffim 

407 mn = self._computeVarianceMean(corrected_diffExp.getMaskedImage()) 

408 if verbose: 

409 print('CORRECTED VARIANCE:', var, mn) 

410 self.assertFloatsAlmostEqual(mn, expected_var, rtol=0.02) 

411 self.assertFloatsAlmostEqual(var, mn, rtol=0.05) 

412 return var, mn 

413 

414 def _testDiffimCorrection(self, svar, tvar, config): 

415 """ Run decorrelation and check the variance of the corrected diffim. 

416 """ 

417 self._setUpImages(svar=svar, tvar=tvar) 

418 diffExp, mKernel, expected_var = self._makeAndTestUncorrectedDiffim() 

419 corrected_diffExp = self._runDecorrelationTask(diffExp, mKernel, config) 

420 self._testDecorrelation(expected_var, corrected_diffExp) 

421 

422 @methodParameters(completeVarPlanePropagation=[False, True]) 

423 def testDiffimCorrection(self, completeVarPlanePropagation): 

424 """Test decorrelated diffim from images with different combinations of variances. 

425 """ 

426 config = DecorrelateALKernelConfig() 

427 config.completeVarPlanePropagation = completeVarPlanePropagation 

428 # Same variance 

429 self._testDiffimCorrection(svar=0.04, tvar=0.04, config=config) 

430 # Science image variance is higher than that of the template. 

431 self._testDiffimCorrection(svar=0.08, tvar=0.04, config=config) 

432 # Template variance is higher than that of the science img. 

433 self._testDiffimCorrection(svar=0.04, tvar=0.08, config=config) 

434 

435 def testNoiseDiffimCorrection(self): 

436 """Test correction by estimating correlation directly on a noise difference image. 

437 

438 Notes 

439 ------ 

440 

441 See `lsst-dm/diffimTests` notebook `DM-24371_correlation_estimate.ipynb` 

442 for further details of how the correlation looks like in the uncorrected 

443 and corrected cases and where the tolerance numbers come from. 

444 """ 

445 svar = 1. 

446 tvar = 100. 

447 # Based on DM-24371_correlation_estimate.ipynb 

448 someCorrelationThreshold = 0.2 

449 

450 self._setUpSourcelessImages(svar=svar, tvar=tvar) 

451 diffExp, mKernel, expected_var = self._makeAndTestUncorrectedDiffim() 

452 corrected_diffExp = self._runDecorrelationTask(diffExp.clone(), mKernel) 

453 

454 rho_sci = estimatePixelCorrelation(self.im1ex.getImage().getArray()) 

455 rho_rawdiff = estimatePixelCorrelation(diffExp.getImage().getArray()) 

456 rho_corrdiff = estimatePixelCorrelation(corrected_diffExp.getImage().getArray()) 

457 

458 # Autocorrelation sanity check 

459 self.assertFloatsAlmostEqual(rho_sci[0], 1., atol=0.1, rtol=None) 

460 self.assertFloatsAlmostEqual(rho_rawdiff[0], 1., atol=0.1, rtol=None) 

461 self.assertFloatsAlmostEqual(rho_corrdiff[0], 1., atol=0.1, rtol=None) 

462 

463 # Uncorrelated input check 

464 self.assertFloatsAlmostEqual(rho_sci[1:], 0., atol=0.1, rtol=None) 

465 

466 # Without correction there should be correlation up to a few pixel distance 

467 self.assertGreater(rho_rawdiff[1], someCorrelationThreshold) 

468 self.assertGreater(rho_rawdiff[2], someCorrelationThreshold) 

469 self.assertGreater(rho_rawdiff[3], someCorrelationThreshold) 

470 

471 # Uncorrelated corrected image check 

472 self.assertFloatsAlmostEqual(rho_corrdiff[1:], 0., atol=0.1, rtol=None) 

473 

474 def _runDecorrelationTaskMapReduced(self, diffExp, mKernel): 

475 """ Run decorrelation using the imageMapReducer. 

476 """ 

477 config = DecorrelateALKernelMapReduceConfig() 

478 config.borderSizeX = config.borderSizeY = 3 

479 config.reducer.reduceOperation = 'average' 

480 task = ImageMapReduceTask(config=config) 

481 decorrResult = task.run(diffExp, template=self.im2ex, science=self.im1ex, 

482 psfMatchingKernel=mKernel, forceEvenSized=True) 

483 corrected_diffExp = decorrResult.exposure 

484 return corrected_diffExp 

485 

486 def _testDiffimCorrection_mapReduced(self, svar, tvar, varyPsf=0.0): 

487 """ Run decorrelation using the imageMapReduce task, and check the variance of 

488 the corrected diffim. 

489 """ 

490 self._setUpImages(svar=svar, tvar=tvar, varyPsf=varyPsf) 

491 diffExp, mKernel, expected_var = self._makeAndTestUncorrectedDiffim() 

492 corrected_diffExp = self._runDecorrelationTaskMapReduced(diffExp, mKernel) 

493 self._testDecorrelation(expected_var, corrected_diffExp) 

494 # Also compare the diffim generated here vs. the non-ImageMapReduce one 

495 corrected_diffExp_OLD = self._runDecorrelationTask(diffExp, mKernel) 

496 self.assertMaskedImagesAlmostEqual(corrected_diffExp.getMaskedImage(), 

497 corrected_diffExp_OLD.getMaskedImage()) 

498 

499 @unittest.skip("DM-21868 ImageMapReduce usage is not yet supported") 

500 def testDiffimCorrection_mapReduced(self): 

501 """ Test decorrelated diffim when using the imageMapReduce task. 

502 Compare results with those from the original DecorrelateALKernelTask. 

503 """ 

504 # Same variance 

505 self._testDiffimCorrection_mapReduced(svar=0.04, tvar=0.04) 

506 # Science image variance is higher than that of the template. 

507 self._testDiffimCorrection_mapReduced(svar=0.04, tvar=0.08) 

508 # Template variance is higher than that of the science img. 

509 self._testDiffimCorrection_mapReduced(svar=0.08, tvar=0.04) 

510 

511 def _runDecorrelationSpatialTask(self, diffExp, mKernel, spatiallyVarying=False): 

512 """ Run decorrelation using the DecorrelateALKernelSpatialTask. 

513 """ 

514 config = DecorrelateALKernelSpatialConfig() 

515 task = DecorrelateALKernelSpatialTask(config=config) 

516 decorrResult = task.run(scienceExposure=self.im1ex, templateExposure=self.im2ex, 

517 subtractedExposure=diffExp, psfMatchingKernel=mKernel, 

518 spatiallyVarying=spatiallyVarying) 

519 corrected_diffExp = decorrResult.correctedExposure 

520 return corrected_diffExp 

521 

522 def _testDiffimCorrection_spatialTask(self, svar, tvar, varyPsf=0.0): 

523 """Run decorrelation using the DecorrelateALKernelSpatialTask, and 

524 check the variance of the corrected diffim. Do it for `spatiallyVarying` both 

525 True and False. Also compare the variances between the two `spatiallyVarying` 

526 cases. 

527 """ 

528 self._setUpImages(svar=svar, tvar=tvar, varyPsf=varyPsf) 

529 diffExp, mKernel, expected_var = self._makeAndTestUncorrectedDiffim() 

530 variances = [] 

531 for spatiallyVarying in [False, True]: 

532 corrected_diffExp = self._runDecorrelationSpatialTask(diffExp.clone(), mKernel, 

533 spatiallyVarying) 

534 var, mn = self._testDecorrelation(expected_var, corrected_diffExp) 

535 variances.append(var) 

536 self.assertFloatsAlmostEqual(variances[0], variances[1], rtol=0.03) 

537 

538 def testDiffimCorrection_spatialTask(self): 

539 """Test decorrelated diffim when using the DecorrelateALKernelSpatialTask. 

540 Compare results with those from the original DecorrelateALKernelTask. 

541 """ 

542 # Same variance 

543 self._testDiffimCorrection_spatialTask(svar=0.04, tvar=0.04) 

544 # Science image variance is higher than that of the template. 

545 self._testDiffimCorrection_spatialTask(svar=0.04, tvar=0.08) 

546 # Template variance is higher than that of the science img. 

547 self._testDiffimCorrection_spatialTask(svar=0.08, tvar=0.04) 

548 

549 

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

551 pass 

552 

553 

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

555 lsst.utils.tests.init() 

556 unittest.main()