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 """Deprecated. Use ``test_zogy : makeFakeImages``. DM-25115: This function is kept for 

78 numerical compatibility with existing test cases only. It has x,y axes handling 

79 and PSF centering bugs. 

80 

81 Parameters 

82 ---------- 

83 size : `tuple` of `int` 

84 Image pixel size (x,y). Pixel coordinates are set to 

85 (-size[0]//2:size[0]//2, -size[1]//2:size[1]//2) 

86 svar, tvar : `float`, optional 

87 Per pixel variance of the added noise. 

88 psf1, psf2 : `float`, optional 

89 std. dev. of (Gaussian) PSFs for the two images in x,y direction. Default is 

90 [3.3, 3.3] and [2.2, 2.2] for im1 and im2 respectively. 

91 offset : `float`, optional 

92 add a constant (pixel) astrometric offset between the two images. 

93 psf_yvary_factor : `float`, optional 

94 psf_yvary_factor vary the y-width of the PSF across the x-axis of the science image (zero, 

95 the default, means no variation) 

96 varSourceChange : `float`, optional 

97 varSourceChange add this amount of fractional flux to a single source closest to 

98 the center of the science image. 

99 theta1, theta2: `float`, optional 

100 PSF Gaussian rotation angles in degrees. 

101 n_sources : `int`, optional 

102 The number of sources to add to the images. If zero, no sources are 

103 generated just background noise. 

104 seed : `int`, optional 

105 Random number generator seed. 

106 verbose : `bool`, optional 

107 Print some actual values. 

108 

109 Returns 

110 ------- 

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

112 The science and template exposures. 

113 

114 Notes 

115 ----- 

116 If ``n_sources > 0`` and ``varSourceChange > 0.`` exactly one source, 

117 that is closest to the center, will have different fluxes in the two 

118 generated images. The flux on the science image will be higher by 

119 ``varSourceChange`` fraction. 

120 

121 Having sources near the edges really messes up the 

122 fitting (probably because of the convolution). So we make sure no 

123 sources are near the edge. 

124 

125 Also it seems that having the variable source with a large 

126 flux increase also messes up the fitting (seems to lead to 

127 overfitting -- perhaps to the source itself). This might be fixed by 

128 adding more constant sources. 

129 """ 

130 np.random.seed(seed) 

131 

132 psf1 = [3.3, 3.3] if psf1 is None else psf1 

133 if not hasattr(psf1, "__len__") and not isinstance(psf1, str): 

134 psf1 = [psf1, psf1] 

135 psf2 = [2.2, 2.2] if psf2 is None else psf2 

136 if not hasattr(psf2, "__len__") and not isinstance(psf2, str): 

137 psf2 = [psf2, psf2] 

138 offset = [0., 0.] if offset is None else offset # astrometric offset (pixels) between the two images 

139 if verbose: 

140 print('Science PSF:', psf1, theta1) 

141 print('Template PSF:', psf2, theta2) 

142 print(np.sqrt(psf1[0]**2 - psf2[0]**2)) 

143 print('Offset:', offset) 

144 

145 xim = np.arange(-size[0]//2, size[0]//2, 1) 

146 yim = np.arange(-size[1]//2, size[1]//2, 1) 

147 x0im, y0im = np.meshgrid(yim, xim) 

148 im1 = np.random.normal(scale=np.sqrt(svar), size=x0im.shape) # variance of science image 

149 im2 = np.random.normal(scale=np.sqrt(tvar), size=x0im.shape) # variance of template 

150 

151 if n_sources > 0: 

152 fluxes = np.random.uniform(50, 30000, n_sources) 

153 xposns = np.random.uniform(xim.min()+16, xim.max()-5, n_sources) 

154 yposns = np.random.uniform(yim.min()+16, yim.max()-5, n_sources) 

155 

156 # Make the source closest to the center of the image the one that increases in flux 

157 ind = np.argmin(xposns**2. + yposns**2.) 

158 

159 # vary the y-width of psf across x-axis of science image (zero means no variation): 

160 psf1_yvary = psf_yvary_factor * (yim.mean() - yposns) / yim.max() 

161 if verbose: 

162 print('PSF y spatial-variation:', psf1_yvary.min(), psf1_yvary.max()) 

163 

164 for i in range(n_sources): 

165 flux = fluxes[i] 

166 tmp = flux * singleGaussian2d(x0im, y0im, xposns[i], yposns[i], psf2[0], psf2[1], theta=theta2) 

167 im2 += tmp 

168 if i == ind: 

169 flux += flux * varSourceChange 

170 tmp = flux * singleGaussian2d(x0im, y0im, xposns[i]+offset[0], yposns[i]+offset[1], 

171 psf1[0], psf1[1]+psf1_yvary[i], theta=theta1) 

172 im1 += tmp 

173 

174 im1_psf = singleGaussian2d(x0im, y0im, 0, 0, psf1[0], psf1[1], theta=theta1) 

175 im2_psf = singleGaussian2d(x0im, y0im, offset[0], offset[1], psf2[0], psf2[1], theta=theta2) 

176 

177 def makeWcs(offset=0): 

178 """ Make a fake Wcs 

179 

180 Parameters 

181 ---------- 

182 offset : float 

183 offset the Wcs by this many pixels. 

184 """ 

185 # taken from $AFW_DIR/tests/testMakeWcs.py 

186 metadata = dafBase.PropertySet() 

187 metadata.set("SIMPLE", "T") 

188 metadata.set("BITPIX", -32) 

189 metadata.set("NAXIS", 2) 

190 metadata.set("NAXIS1", 1024) 

191 metadata.set("NAXIS2", 1153) 

192 metadata.set("RADESYS", 'FK5') 

193 metadata.set("EQUINOX", 2000.) 

194 metadata.setDouble("CRVAL1", 215.604025685476) 

195 metadata.setDouble("CRVAL2", 53.1595451514076) 

196 metadata.setDouble("CRPIX1", 1109.99981456774 + offset) 

197 metadata.setDouble("CRPIX2", 560.018167811613 + offset) 

198 metadata.set("CTYPE1", 'RA---SIN') 

199 metadata.set("CTYPE2", 'DEC--SIN') 

200 metadata.setDouble("CD1_1", 5.10808596133527E-05) 

201 metadata.setDouble("CD1_2", 1.85579539217196E-07) 

202 metadata.setDouble("CD2_2", -5.10281493481982E-05) 

203 metadata.setDouble("CD2_1", -8.27440751733828E-07) 

204 return afwGeom.makeSkyWcs(metadata) 

205 

206 def makeExposure(imgArray, psfArray, imgVariance): 

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

208 

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

210 

211 @param imgArray 2-d numpy.array containing the image 

212 @param psfArray 2-d numpy.array containing the PSF image 

213 @param imgVariance variance of input image 

214 @return a new exposure containing the image, PSF and desired variance plane 

215 """ 

216 # All this code to convert the template image array/psf array into an exposure. 

217 bbox = geom.Box2I(geom.Point2I(0, 0), geom.Point2I(imgArray.shape[1]-1, imgArray.shape[0]-1)) 

218 im1ex = afwImage.ExposureD(bbox) 

219 im1ex.getMaskedImage().getImage().getArray()[:, :] = imgArray 

220 im1ex.getMaskedImage().getVariance().getArray()[:, :] = imgVariance 

221 psfBox = geom.Box2I(geom.Point2I(-12, -12), geom.Point2I(12, 12)) # a 25x25 pixel psf 

222 psf = afwImage.ImageD(psfBox) 

223 psfBox.shift(geom.Extent2I(size[0]//2, size[1]//2)) 

224 im1_psf_sub = psfArray[psfBox.getMinX():psfBox.getMaxX()+1, psfBox.getMinY():psfBox.getMaxY()+1] 

225 psf.getArray()[:, :] = im1_psf_sub 

226 psfK = afwMath.FixedKernel(psf) 

227 psfNew = measAlg.KernelPsf(psfK) 

228 im1ex.setPsf(psfNew) 

229 wcs = makeWcs() 

230 im1ex.setWcs(wcs) 

231 return im1ex 

232 

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

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

235 

236 return im1ex, im2ex 

237 

238 

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

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

241 by sampling pixel pairs. 

242 

243 Parameters 

244 ---------- 

245 B : `numpy.ndarray` of N x N `float` elements 

246 Noise only image with zero pixel expectation value and identical variance 

247 in all pixels. Must have equal dimensions. 

248 nDist : `int`, optional 

249 Estimated distances goes from 0 to nDist-1. 

250 nDist must be smaller than the half dimensions of B. 

251 convEdge : `int`, optional 

252 Edge width where convolution did not happen. 

253 

254 Returns 

255 ------- 

256 S : `numpy.ndarray` of nDist `float` elements 

257 Correlation from 0 to nDist-1 pix distance. Pixels are normed by their 

258 variance estimation. S[0], the autocorrelation, should be close to 1. 

259 """ 

260 S = np.zeros(nDist, dtype=float) 

261 nSample = 10000 

262 # Cannot use nDist wide edge, otherwise 2nd pixel can go off the image. 

263 # Don't bother with it. 

264 A = B/np.sqrt(np.mean(B[convEdge:-convEdge, convEdge:-convEdge] 

265 * B[convEdge:-convEdge, convEdge:-convEdge])) 

266 lEdge = nDist + convEdge 

267 rEdge = B.shape[0] - lEdge 

268 for r in range(nDist): 

269 ind1 = np.random.randint(lEdge, rEdge, (2, nSample)) 

270 ind2 = np.copy(ind1) 

271 # generate delta x,y in random directions uniformly 

272 c_dxy = np.exp(2.j*np.pi*np.random.random(nSample)) 

273 ind2[0] += np.around(np.real(c_dxy)*r).astype(int) 

274 ind2[1] += np.around(np.imag(c_dxy)*r).astype(int) 

275 S[r] = np.sum(A[ind1[0], ind1[1]] * A[ind2[0], ind2[1]])/nSample 

276 return S 

277 

278 

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

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

281 """ 

282 

283 def setUp(self): 

284 self.psf1_sigma = 3.3 # sigma of psf of science image 

285 self.psf2_sigma = 2.2 # sigma of psf of template image 

286 

287 self.statsControl = afwMath.StatisticsControl() 

288 self.statsControl.setNumSigmaClip(3.) 

289 self.statsControl.setNumIter(3) 

290 self.statsControl.setAndMask(afwImage.Mask 

291 .getPlaneBitMask(["INTRP", "EDGE", "SAT", "CR", 

292 "DETECTED", "BAD", 

293 "NO_DATA", "DETECTED_NEGATIVE"])) 

294 

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

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

297 """ 

298 

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

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

301 

302 self.im1ex, self.im2ex \ 

303 = makeFakeImages(svar=self.svar, tvar=self.tvar, psf1=self.psf1_sigma, psf2=self.psf2_sigma, 

304 n_sources=50, psf_yvary_factor=varyPsf, verbose=False) 

305 

306 def _setUpSourcelessImages(self, svar, tvar): 

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

308 """ 

309 

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

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

312 

313 self.im1ex, self.im2ex = makeFakeImages( 

314 svar=self.svar, tvar=self.tvar, psf1=self.psf1_sigma, psf2=self.psf2_sigma, 

315 n_sources=0, seed=22, varSourceChange=0, psf_yvary_factor=0) 

316 

317 def _computeVarianceMean(self, maskedIm): 

318 statObj = afwMath.makeStatistics(maskedIm.getVariance(), 

319 maskedIm.getMask(), afwMath.MEANCLIP, 

320 self.statsControl) 

321 mn = statObj.getValue(afwMath.MEANCLIP) 

322 return mn 

323 

324 def _computePixelVariance(self, maskedIm): 

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

326 self.statsControl) 

327 var = statObj.getValue(afwMath.VARIANCECLIP) 

328 return var 

329 

330 def tearDown(self): 

331 del self.im1ex 

332 del self.im2ex 

333 

334 def _makeAndTestUncorrectedDiffim(self): 

335 """Create the (un-decorrelated) diffim, and verify that its variance is too low. 

336 """ 

337 # Create the matching kernel. We used Gaussian PSFs for im1 and im2, so we can compute the "expected" 

338 # matching kernel sigma. 

339 psf1_sig = self.im1ex.getPsf().computeShape().getDeterminantRadius() 

340 psf2_sig = self.im2ex.getPsf().computeShape().getDeterminantRadius() 

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

342 # Sanity check - make sure PSFs are correct. 

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

344 rtol=2e-5) 

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

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

347 y0 = x0.copy() 

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

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

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

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

352 mKernel = afwMath.FixedKernel(kernelImg) 

353 

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

355 matched_im2ex = self.im2ex.clone() 

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

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

358 

359 # Expected (ideal) variance of difference image 

360 expected_var = self.svar + self.tvar 

361 if verbose: 

362 print('EXPECTED VARIANCE:', expected_var) 

363 

364 # Create the diffim (uncorrected) 

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

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

367 tmp_diffExp -= matched_im2ex.getMaskedImage() 

368 var = self._computeVarianceMean(tmp_diffExp) 

369 self.assertLess(var, expected_var) 

370 

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

372 diffExp = self.im1ex.clone() 

373 tmp = diffExp.getMaskedImage() 

374 tmp -= matched_im2ex.getMaskedImage() 

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

376 self.assertLess(var, expected_var) 

377 

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

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

380 self.assertLess(mn, expected_var) 

381 if verbose: 

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

383 

384 return diffExp, mKernel, expected_var 

385 

386 def _runDecorrelationTask(self, diffExp, mKernel): 

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

388 """ 

389 task = DecorrelateALKernelTask() 

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

391 corrected_diffExp = decorrResult.correctedExposure 

392 return corrected_diffExp 

393 

394 def _testDecorrelation(self, expected_var, corrected_diffExp): 

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

396 """ 

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

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

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

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

401 

402 # Check statistics of variance plane in corrected diffim 

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

404 if verbose: 

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

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

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

408 return var, mn 

409 

410 def _testDiffimCorrection(self, svar, tvar): 

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

412 """ 

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

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

415 corrected_diffExp = self._runDecorrelationTask(diffExp, mKernel) 

416 self._testDecorrelation(expected_var, corrected_diffExp) 

417 

418 def testDiffimCorrection(self): 

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

420 """ 

421 # Same variance 

422 self._testDiffimCorrection(svar=0.04, tvar=0.04) 

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

424 self._testDiffimCorrection(svar=0.08, tvar=0.04) 

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

426 self._testDiffimCorrection(svar=0.04, tvar=0.08) 

427 

428 def testNoiseDiffimCorrection(self): 

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

430 

431 Notes 

432 ------ 

433 

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

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

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

437 """ 

438 svar = 1. 

439 tvar = 100. 

440 # Based on DM-24371_correlation_estimate.ipynb 

441 someCorrelationThreshold = 0.2 

442 

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

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

445 corrected_diffExp = self._runDecorrelationTask(diffExp, mKernel) 

446 

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

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

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

450 

451 # Autocorrelation sanity check 

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

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

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

455 

456 # Uncorrelated input check 

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

458 

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

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

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

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

463 

464 # Uncorrelated corrected image check 

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

466 

467 def _runDecorrelationTaskMapReduced(self, diffExp, mKernel): 

468 """ Run decorrelation using the imageMapReducer. 

469 """ 

470 config = DecorrelateALKernelMapReduceConfig() 

471 config.borderSizeX = config.borderSizeY = 3 

472 config.reducer.reduceOperation = 'average' 

473 task = ImageMapReduceTask(config=config) 

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

475 psfMatchingKernel=mKernel, forceEvenSized=True) 

476 corrected_diffExp = decorrResult.exposure 

477 return corrected_diffExp 

478 

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

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

481 the corrected diffim. 

482 """ 

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

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

485 corrected_diffExp = self._runDecorrelationTaskMapReduced(diffExp, mKernel) 

486 self._testDecorrelation(expected_var, corrected_diffExp) 

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

488 corrected_diffExp_OLD = self._runDecorrelationTask(diffExp, mKernel) 

489 self.assertMaskedImagesAlmostEqual(corrected_diffExp.getMaskedImage(), 

490 corrected_diffExp_OLD.getMaskedImage()) 

491 

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

493 def testDiffimCorrection_mapReduced(self): 

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

495 Compare results with those from the original DecorrelateALKernelTask. 

496 """ 

497 # Same variance 

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

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

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

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

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

503 

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

505 """ Run decorrelation using the DecorrelateALKernelSpatialTask. 

506 """ 

507 config = DecorrelateALKernelSpatialConfig() 

508 task = DecorrelateALKernelSpatialTask(config=config) 

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

510 subtractedExposure=diffExp, psfMatchingKernel=mKernel, 

511 spatiallyVarying=spatiallyVarying) 

512 corrected_diffExp = decorrResult.correctedExposure 

513 return corrected_diffExp 

514 

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

516 """Run decorrelation using the DecorrelateALKernelSpatialTask, and 

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

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

519 cases. 

520 """ 

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

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

523 variances = [] 

524 for spatiallyVarying in [False, True]: 

525 corrected_diffExp = self._runDecorrelationSpatialTask(diffExp, mKernel, 

526 spatiallyVarying) 

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

528 variances.append(var) 

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

530 

531 def testDiffimCorrection_spatialTask(self): 

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

533 Compare results with those from the original DecorrelateALKernelTask. 

534 """ 

535 # Same variance 

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

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

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

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

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

541 

542 

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

544 pass 

545 

546 

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

548 lsst.utils.tests.init() 

549 unittest.main()