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

32 

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

34 DecorrelateALKernelMapReduceConfig, 

35 DecorrelateALKernelSpatialConfig, 

36 DecorrelateALKernelSpatialTask) 

37from lsst.ip.diffim.imageMapReduce import ImageMapReduceTask 

38 

39try: 

40 type(verbose) 

41except NameError: 

42 verbose = False 

43 

44 

45def setup_module(module): 

46 lsst.utils.tests.init() 

47 

48 

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 

59 

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 

72 

73 

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. 

79 

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. 

107 

108 Returns 

109 ------- 

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

111 The science and template exposures. 

112 

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. 

119 

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. 

123 

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) 

130 

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) 

143 

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 

149 

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) 

154 

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

157 

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

162 

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 

172 

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) 

175 

176 def makeWcs(offset=0): 

177 """ Make a fake Wcs 

178 

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) 

204 

205 def makeExposure(imgArray, psfArray, imgVariance): 

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

207 

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

209 

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 

231 

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

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

234 

235 return im1ex, im2ex 

236 

237 

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

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

240 by sampling pixel pairs. 

241 

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. 

252 

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 

276 

277 

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

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

280 """ 

281 

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 

285 

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

293 

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

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

296 """ 

297 

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

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

300 

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) 

304 

305 def _setUpSourcelessImages(self, svar, tvar): 

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

307 """ 

308 

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

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

311 

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) 

315 

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 

322 

323 def _computePixelVariance(self, maskedIm): 

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

325 self.statsControl) 

326 var = statObj.getValue(afwMath.VARIANCECLIP) 

327 return var 

328 

329 def tearDown(self): 

330 del self.im1ex 

331 del self.im2ex 

332 

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) 

352 

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) 

357 

358 # Expected (ideal) variance of difference image 

359 expected_var = self.svar + self.tvar 

360 if verbose: 

361 print('EXPECTED VARIANCE:', expected_var) 

362 

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) 

369 

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) 

376 

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) 

382 

383 return diffExp, mKernel, expected_var 

384 

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 

392 

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) 

400 

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 

408 

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) 

416 

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) 

426 

427 def testNoiseDiffimCorrection(self): 

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

429 

430 Notes 

431 ------ 

432 

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 

441 

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

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

444 corrected_diffExp = self._runDecorrelationTask(diffExp, mKernel) 

445 

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

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

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

449 

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) 

454 

455 # Uncorrelated input check 

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

457 

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) 

462 

463 # Uncorrelated corrected image check 

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

465 

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 

477 

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

490 

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) 

502 

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 

513 

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) 

529 

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) 

540 

541 

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

543 pass 

544 

545 

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