Coverage for tests/test_subtractTask.py: 11%

317 statements  

« prev     ^ index     » next       coverage.py v6.4, created at 2022-06-02 04:23 -0700

1# This file is part of ip_diffim. 

2# 

3# Developed for the LSST Data Management System. 

4# This product includes software developed by the LSST Project 

5# (https://www.lsst.org). 

6# See the COPYRIGHT file at the top-level directory of this distribution 

7# for details of code ownership. 

8# 

9# This program is free software: you can redistribute it and/or modify 

10# it under the terms of the GNU General Public License as published by 

11# the Free Software Foundation, either version 3 of the License, or 

12# (at your option) any later version. 

13# 

14# This program is distributed in the hope that it will be useful, 

15# but WITHOUT ANY WARRANTY; without even the implied warranty of 

16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

17# GNU General Public License for more details. 

18# 

19# You should have received a copy of the GNU General Public License 

20# along with this program. If not, see <https://www.gnu.org/licenses/>. 

21 

22import unittest 

23import numpy as np 

24 

25import lsst.afw.geom as afwGeom 

26from lsst.afw.image import PhotoCalib 

27from lsst.afw.math import Chebyshev1Function2D 

28import lsst.geom 

29from lsst.meas.algorithms.testUtils import plantSources 

30import lsst.ip.diffim.imagePsfMatch 

31from lsst.ip.diffim import subtractImages 

32from lsst.ip.diffim.utils import getPsfFwhm 

33from lsst.pex.config import FieldValidationError 

34import lsst.utils.tests 

35 

36 

37def makeFakeWcs(): 

38 """Make a fake, affine Wcs. 

39 """ 

40 crpix = lsst.geom.Point2D(123.45, 678.9) 

41 crval = lsst.geom.SpherePoint(0.1, 0.1, lsst.geom.degrees) 

42 cdMatrix = np.array([[5.19513851e-05, -2.81124812e-07], 

43 [-3.25186974e-07, -5.19112119e-05]]) 

44 return afwGeom.makeSkyWcs(crpix, crval, cdMatrix) 

45 

46 

47def makeTestImage(seed=5, nSrc=20, psfSize=2., noiseLevel=5., 

48 noiseSeed=6, fluxLevel=500., fluxRange=2., 

49 kernelSize=32, templateBorderSize=0, 

50 background=None, 

51 xSize=200, 

52 ySize=200, 

53 x0=12345, 

54 y0=67890, 

55 calibration=1., 

56 doApplyCalibration=False, 

57 ): 

58 """Make a reproduceable PSF-convolved exposure for testing. 

59 

60 Parameters 

61 ---------- 

62 seed : `int`, optional 

63 Seed value to initialize the random number generator for sources. 

64 nSrc : `int`, optional 

65 Number of sources to simulate. 

66 psfSize : `float`, optional 

67 Width of the PSF of the simulated sources, in pixels. 

68 noiseLevel : `float`, optional 

69 Standard deviation of the noise to add to each pixel. 

70 noiseSeed : `int`, optional 

71 Seed value to initialize the random number generator for noise. 

72 fluxLevel : `float`, optional 

73 Reference flux of the simulated sources. 

74 fluxRange : `float`, optional 

75 Range in flux amplitude of the simulated sources. 

76 kernelSize : `int`, optional 

77 Size in pixels of the kernel for simulating sources. 

78 templateBorderSize : `int`, optional 

79 Size in pixels of the image border used to pad the image. 

80 background : `lsst.afw.math.Chebyshev1Function2D`, optional 

81 Optional background to add to the output image. 

82 xSize, ySize : `int`, optional 

83 Size in pixels of the simulated image. 

84 x0, y0 : `int`, optional 

85 Origin of the image. 

86 calibration : `float`, optional 

87 Conversion factor between instFlux and nJy. 

88 doApplyCalibration : `bool`, optional 

89 Apply the photometric calibration and return the image in nJy? 

90 

91 Returns 

92 ------- 

93 modelExposure : `lsst.afw.image.Exposure` 

94 The model image, with the mask and variance planes. 

95 sourceCat : `lsst.afw.table.SourceCatalog` 

96 Catalog of sources detected on the model image. 

97 """ 

98 # Distance from the inner edge of the bounding box to avoid placing test 

99 # sources in the model images. 

100 bufferSize = kernelSize/2 + templateBorderSize + 1 

101 

102 bbox = lsst.geom.Box2I(lsst.geom.Point2I(x0, y0), lsst.geom.Extent2I(xSize, ySize)) 

103 if templateBorderSize > 0: 

104 bbox.grow(templateBorderSize) 

105 

106 rng = np.random.RandomState(seed) 

107 rngNoise = np.random.RandomState(noiseSeed) 

108 x0, y0 = bbox.getBegin() 

109 xSize, ySize = bbox.getDimensions() 

110 xLoc = rng.rand(nSrc)*(xSize - 2*bufferSize) + bufferSize + x0 

111 yLoc = rng.rand(nSrc)*(ySize - 2*bufferSize) + bufferSize + y0 

112 

113 flux = (rng.rand(nSrc)*(fluxRange - 1.) + 1.)*fluxLevel 

114 sigmas = [psfSize for src in range(nSrc)] 

115 coordList = list(zip(xLoc, yLoc, flux, sigmas)) 

116 skyLevel = 0 

117 # Don't use the built in poisson noise: it modifies the global state of numpy random 

118 modelExposure = plantSources(bbox, kernelSize, skyLevel, coordList, addPoissonNoise=False) 

119 modelExposure.setWcs(makeFakeWcs()) 

120 noise = rngNoise.randn(ySize, xSize)*noiseLevel 

121 modelExposure.image.array += noise 

122 modelExposure.variance.array = (np.sqrt(np.abs(modelExposure.image.array)) + noiseLevel 

123 - np.mean(np.sqrt(np.abs(noise)))) 

124 

125 # Run source detection to set up the mask plane 

126 psfMatchTask = lsst.ip.diffim.imagePsfMatch.ImagePsfMatchTask() 

127 sourceCat = psfMatchTask.getSelectSources(modelExposure) 

128 modelExposure.setPhotoCalib(PhotoCalib(calibration, 0., bbox)) 

129 if background is not None: 

130 modelExposure.image += background 

131 modelExposure.maskedImage /= calibration 

132 if doApplyCalibration: 

133 modelExposure.maskedImage = modelExposure.photoCalib.calibrateImage(modelExposure.maskedImage) 

134 

135 return modelExposure, sourceCat 

136 

137 

138class AlardLuptonSubtractTest(lsst.utils.tests.TestCase): 

139 

140 def test_allowed_config_modes(self): 

141 """Verify the allowable modes for convolution. 

142 """ 

143 config = subtractImages.AlardLuptonSubtractTask.ConfigClass() 

144 config.mode = 'auto' 

145 config.mode = 'convolveScience' 

146 config.mode = 'convolveTemplate' 

147 

148 with self.assertRaises(FieldValidationError): 

149 config.mode = 'aotu' 

150 

151 def test_mismatched_template(self): 

152 """Test that an error is raised if the template 

153 does not fully contain the science image. 

154 """ 

155 xSize = 200 

156 ySize = 200 

157 science, sources = makeTestImage(psfSize=2.4, xSize=xSize + 20, ySize=ySize + 20) 

158 template, _ = makeTestImage(psfSize=2.4, xSize=xSize, ySize=ySize, doApplyCalibration=True) 

159 config = subtractImages.AlardLuptonSubtractTask.ConfigClass() 

160 task = subtractImages.AlardLuptonSubtractTask(config=config) 

161 with self.assertRaises(AssertionError): 

162 task.run(template, science, sources) 

163 

164 def test_equal_images(self): 

165 """Test that running with enough sources produces reasonable output, 

166 with the same size psf in the template and science. 

167 """ 

168 noiseLevel = 1. 

169 science, sources = makeTestImage(psfSize=2.4, noiseLevel=noiseLevel, noiseSeed=6) 

170 template, _ = makeTestImage(psfSize=2.4, noiseLevel=noiseLevel, noiseSeed=7, 

171 templateBorderSize=20, doApplyCalibration=True) 

172 config = subtractImages.AlardLuptonSubtractTask.ConfigClass() 

173 config.doSubtractBackground = False 

174 task = subtractImages.AlardLuptonSubtractTask(config=config) 

175 output = task.run(template, science, sources) 

176 # There shoud be no NaN values in the difference image 

177 self.assertTrue(np.all(np.isfinite(output.difference.image.array))) 

178 # Mean of difference image should be close to zero. 

179 mean_error = noiseLevel/np.sqrt(output.difference.image.array.size) 

180 self.assertFloatsAlmostEqual(np.mean(output.difference.image.array), 0, atol=5*mean_error) 

181 # stddev of difference image should be close to expected value. 

182 self.assertFloatsAlmostEqual(np.std(output.difference.image.array), np.sqrt(2)*noiseLevel, rtol=0.1) 

183 

184 def test_auto_convolveTemplate(self): 

185 """Test that auto mode gives the same result as convolveTemplate when 

186 the template psf is the smaller. 

187 """ 

188 noiseLevel = 1. 

189 science, sources = makeTestImage(psfSize=3.0, noiseLevel=noiseLevel, noiseSeed=6) 

190 scienceOrig = science.clone() 

191 template, _ = makeTestImage(psfSize=2.0, noiseLevel=noiseLevel, noiseSeed=7, 

192 templateBorderSize=20, doApplyCalibration=True) 

193 config = subtractImages.AlardLuptonSubtractTask.ConfigClass() 

194 config.doSubtractBackground = False 

195 config.mode = "convolveTemplate" 

196 

197 task = subtractImages.AlardLuptonSubtractTask(config=config) 

198 output = task.run(template, science, sources) 

199 

200 config.mode = "auto" 

201 task = subtractImages.AlardLuptonSubtractTask(config=config) 

202 outputAuto = task.run(template, scienceOrig, sources) 

203 self.assertMaskedImagesEqual(output.difference.maskedImage, outputAuto.difference.maskedImage) 

204 

205 def test_auto_convolveScience(self): 

206 """Test that auto mode gives the same result as convolveScience when 

207 the science psf is the smaller. 

208 """ 

209 noiseLevel = 1. 

210 science, sources = makeTestImage(psfSize=2.0, noiseLevel=noiseLevel, noiseSeed=6) 

211 scienceOrig = science.clone() 

212 template, _ = makeTestImage(psfSize=3.0, noiseLevel=noiseLevel, noiseSeed=7, 

213 templateBorderSize=20, doApplyCalibration=True) 

214 config = subtractImages.AlardLuptonSubtractTask.ConfigClass() 

215 config.doSubtractBackground = False 

216 config.mode = "convolveScience" 

217 

218 task = subtractImages.AlardLuptonSubtractTask(config=config) 

219 output = task.run(template, science, sources) 

220 

221 config.mode = "auto" 

222 task = subtractImages.AlardLuptonSubtractTask(config=config) 

223 outputAuto = task.run(template, scienceOrig, sources) 

224 self.assertMaskedImagesEqual(output.difference.maskedImage, outputAuto.difference.maskedImage) 

225 

226 def test_science_better(self): 

227 """Test that running with enough sources produces reasonable output, 

228 with the science psf being smaller than the template. 

229 """ 

230 noiseLevel = 1. 

231 science, sources = makeTestImage(psfSize=2.0, noiseLevel=noiseLevel, noiseSeed=6) 

232 template, _ = makeTestImage(psfSize=3.0, noiseLevel=noiseLevel, noiseSeed=7, 

233 templateBorderSize=20, doApplyCalibration=True) 

234 config = subtractImages.AlardLuptonSubtractTask.ConfigClass() 

235 config.doSubtractBackground = False 

236 task = subtractImages.AlardLuptonSubtractTask(config=config) 

237 output = task.run(template, science, sources) 

238 # Mean of difference image should be close to zero. 

239 nGoodPix = np.sum(np.isfinite(output.difference.image.array)) 

240 mean_error = noiseLevel/np.sqrt(nGoodPix) 

241 self.assertFloatsAlmostEqual(np.nanmean(output.difference.image.array), 0, atol=5*mean_error) 

242 # stddev of difference image should be close to expected value. 

243 self.assertFloatsAlmostEqual(np.nanstd(output.difference.image.array), 

244 np.sqrt(2)*noiseLevel, rtol=0.1) 

245 

246 def test_template_better(self): 

247 """Test that running with enough sources produces reasonable output, 

248 with the template psf being smaller than the science. 

249 """ 

250 noiseLevel = 1. 

251 science, sources = makeTestImage(psfSize=3.0, noiseLevel=noiseLevel, noiseSeed=6) 

252 template, _ = makeTestImage(psfSize=2.0, noiseLevel=noiseLevel, noiseSeed=7, 

253 templateBorderSize=20, doApplyCalibration=True) 

254 config = subtractImages.AlardLuptonSubtractTask.ConfigClass() 

255 config.doSubtractBackground = False 

256 task = subtractImages.AlardLuptonSubtractTask(config=config) 

257 output = task.run(template, science, sources) 

258 # There should be no NaNs in the image if we convolve the template with a buffer 

259 self.assertTrue(np.all(np.isfinite(output.difference.image.array))) 

260 # Mean of difference image should be close to zero. 

261 mean_error = noiseLevel/np.sqrt(output.difference.image.array.size) 

262 self.assertFloatsAlmostEqual(np.mean(output.difference.image.array), 0, atol=5*mean_error) 

263 # stddev of difference image should be close to expected value. 

264 self.assertFloatsAlmostEqual(np.std(output.difference.image.array), np.sqrt(2)*noiseLevel, rtol=0.1) 

265 

266 def test_symmetry(self): 

267 """Test that convolving the science and convolving the template are 

268 symmetric: if the psfs are switched between them, the difference image 

269 should be nearly the same. 

270 """ 

271 noiseLevel = 1. 

272 # Don't include a border for the template, in order to make the results 

273 # comparable when we swap which image is treated as the "science" image. 

274 science, sources = makeTestImage(psfSize=2.0, noiseLevel=noiseLevel, 

275 noiseSeed=6, templateBorderSize=0) 

276 template, _ = makeTestImage(psfSize=3.0, noiseLevel=noiseLevel, 

277 noiseSeed=7, templateBorderSize=0, doApplyCalibration=True) 

278 scienceOrig = science.clone() 

279 config = subtractImages.AlardLuptonSubtractTask.ConfigClass() 

280 config.mode = 'auto' 

281 config.doSubtractBackground = False 

282 task = subtractImages.AlardLuptonSubtractTask(config=config) 

283 

284 # The science image will be modified in place, so use a copy for the second run. 

285 science_better = task.run(template, science, sources) 

286 template_better = task.run(scienceOrig, template, sources) 

287 

288 delta = template_better.difference.clone() 

289 delta.image -= science_better.difference.image 

290 delta.variance -= science_better.difference.variance 

291 delta.mask.array -= science_better.difference.mask.array 

292 

293 # Mean of delta should be very close to zero. 

294 nGoodPix = np.sum(np.isfinite(delta.image.array)) 

295 mean_error = 2*noiseLevel/np.sqrt(nGoodPix) 

296 self.assertFloatsAlmostEqual(np.nanmean(delta.image.array), 0, atol=5*mean_error) 

297 # stddev of difference image should be close to expected value 

298 self.assertFloatsAlmostEqual(np.nanstd(delta.image.array), 2*np.sqrt(2)*noiseLevel, rtol=.1) 

299 

300 def test_few_sources(self): 

301 """Test with only 1 source, to check that we get a useful error. 

302 """ 

303 xSize = 256 

304 ySize = 256 

305 science, sources = makeTestImage(psfSize=2.4, nSrc=1, xSize=xSize, ySize=ySize) 

306 template, _ = makeTestImage(psfSize=2.0, nSrc=1, xSize=xSize, ySize=ySize, doApplyCalibration=True) 

307 config = subtractImages.AlardLuptonSubtractTask.ConfigClass() 

308 task = subtractImages.AlardLuptonSubtractTask(config=config) 

309 with self.assertRaisesRegex(lsst.pex.exceptions.Exception, 

310 'Unable to determine kernel sum; 0 candidates'): 

311 task.run(template, science, sources) 

312 

313 def test_images_unmodified(self): 

314 """Verify that image subtraction does not change the input images. 

315 """ 

316 noiseLevel = 1. 

317 science, sources = makeTestImage(psfSize=2.0, noiseLevel=noiseLevel, noiseSeed=6) 

318 template, _ = makeTestImage(psfSize=2.0, noiseLevel=noiseLevel, noiseSeed=7, 

319 templateBorderSize=20, doApplyCalibration=True) 

320 scienceOrig = science.clone() 

321 templateOrig = template.clone() 

322 config = subtractImages.AlardLuptonSubtractTask.ConfigClass() 

323 config.mode = "convolveTemplate" 

324 task = subtractImages.AlardLuptonSubtractTask(config=config) 

325 task.run(template, science, sources) 

326 self.assertMaskedImagesEqual(template.maskedImage, templateOrig.maskedImage) 

327 # The science image will have its photometric calibration applied. 

328 self.assertMaskedImagesEqual(science.maskedImage, scienceOrig.maskedImage) 

329 

330 # Reset the science image for the second run 

331 science = scienceOrig.clone() 

332 config.mode = "convolveScience" 

333 task = subtractImages.AlardLuptonSubtractTask(config=config) 

334 task.run(template, science, sources) 

335 self.assertMaskedImagesEqual(template.maskedImage, templateOrig.maskedImage) 

336 

337 self.assertMaskedImagesEqual(science.maskedImage, 

338 scienceOrig.maskedImage) 

339 

340 def test_background_subtraction(self): 

341 """Check that we can recover the background, 

342 and that it is subtracted correctly in the difference image. 

343 """ 

344 noiseLevel = 1. 

345 xSize = 512 

346 ySize = 512 

347 x0 = 123 

348 y0 = 456 

349 template, _ = makeTestImage(psfSize=2.0, noiseLevel=noiseLevel, noiseSeed=7, 

350 templateBorderSize=20, 

351 xSize=xSize, ySize=ySize, x0=x0, y0=y0, 

352 doApplyCalibration=True) 

353 params = [2.2, 2.1, 2.0, 1.2, 1.1, 1.0] 

354 

355 bbox2D = lsst.geom.Box2D(lsst.geom.Point2D(x0, y0), lsst.geom.Extent2D(xSize, ySize)) 

356 background_model = Chebyshev1Function2D(params, bbox2D) 

357 science, sources = makeTestImage(psfSize=2.0, noiseLevel=noiseLevel, noiseSeed=6, 

358 background=background_model, 

359 xSize=xSize, ySize=ySize, x0=x0, y0=y0) 

360 config = subtractImages.AlardLuptonSubtractTask.ConfigClass() 

361 config.doSubtractBackground = True 

362 

363 config.makeKernel.kernel.name = "AL" 

364 config.makeKernel.kernel.active.fitForBackground = True 

365 config.makeKernel.kernel.active.spatialKernelOrder = 1 

366 config.makeKernel.kernel.active.spatialBgOrder = 2 

367 

368 def _run_and_check_images(config, mode): 

369 """Check that the fit background matches the input model. 

370 """ 

371 config.mode = mode 

372 task = subtractImages.AlardLuptonSubtractTask(config=config) 

373 output = task.run(template, science, sources) 

374 

375 # We should be fitting the same number of parameters as were in the input model 

376 self.assertEqual(output.backgroundModel.getNParameters(), background_model.getNParameters()) 

377 

378 # The parameters of the background fit should be close to the input model 

379 self.assertFloatsAlmostEqual(np.array(output.backgroundModel.getParameters()), 

380 np.array(params), rtol=0.3) 

381 

382 # stddev of difference image should be close to expected value. 

383 # This will fail if we have mis-subtracted the background. 

384 self.assertFloatsAlmostEqual(np.nanstd(output.difference.image.array), 

385 np.sqrt(2)*noiseLevel, rtol=0.1) 

386 

387 _run_and_check_images(config, "convolveTemplate") 

388 _run_and_check_images(config, "convolveScience") 

389 

390 def test_scale_variance(self): 

391 """Check variance scaling of the image difference. 

392 """ 

393 noiseLevel = 1. 

394 scaleFactor = 2.345 

395 science, sources = makeTestImage(psfSize=3.0, noiseLevel=noiseLevel, noiseSeed=6) 

396 template, _ = makeTestImage(psfSize=2.0, noiseLevel=noiseLevel, noiseSeed=7, 

397 templateBorderSize=20, doApplyCalibration=True) 

398 config = subtractImages.AlardLuptonSubtractTask.ConfigClass() 

399 config.doSubtractBackground = False 

400 scienceVarianceOrig = science.variance.array[...] 

401 templateVarianceOrig = template.variance.array[...] 

402 

403 def _run_and_check_images(doDecorrelation, doScaleVariance, scaleFactor=1.): 

404 """Check that the variance plane matches the expected value for 

405 different configurations of ``doDecorrelation`` and ``doScaleVariance``. 

406 """ 

407 scienceVarMean = np.mean(science.variance.array) 

408 templateVarMean = np.mean(template.variance.array) 

409 config.doDecorrelation = doDecorrelation 

410 config.doScaleVariance = doScaleVariance 

411 task = subtractImages.AlardLuptonSubtractTask(config=config) 

412 output = task.run(template, science, sources) 

413 if doDecorrelation: 

414 if doScaleVariance: 

415 templateNoise = templateVarMean*scaleFactor 

416 scienceNoise = scienceVarMean*scaleFactor 

417 else: 

418 templateNoise = templateVarMean 

419 scienceNoise = scienceVarMean 

420 else: 

421 if doScaleVariance: 

422 templateNoise = np.mean(output.matchedTemplate.variance.array)*scaleFactor 

423 scienceNoise = scienceVarMean*scaleFactor 

424 else: 

425 templateNoise = np.mean(output.matchedTemplate.variance.array)*scaleFactor 

426 scienceNoise = scienceVarMean 

427 self.assertFloatsAlmostEqual(np.mean(output.difference.variance.array), 

428 scienceNoise + templateNoise, rtol=0.1) 

429 

430 # Verify that the variance plane of the difference image is correct 

431 # when the template and science variance planes are correct 

432 _run_and_check_images(doDecorrelation=True, doScaleVariance=True) 

433 _run_and_check_images(doDecorrelation=True, doScaleVariance=False) 

434 _run_and_check_images(doDecorrelation=False, doScaleVariance=True) 

435 _run_and_check_images(doDecorrelation=False, doScaleVariance=False) 

436 

437 # Verify that the variance plane of the difference image is correct 

438 # when the template and science variance planes are incorrect 

439 science.variance.array[...] = scienceVarianceOrig/scaleFactor 

440 template.variance.array[...] = templateVarianceOrig/scaleFactor 

441 _run_and_check_images(doDecorrelation=True, doScaleVariance=True, scaleFactor=scaleFactor) 

442 _run_and_check_images(doDecorrelation=True, doScaleVariance=False, scaleFactor=scaleFactor) 

443 _run_and_check_images(doDecorrelation=False, doScaleVariance=True, scaleFactor=scaleFactor) 

444 _run_and_check_images(doDecorrelation=False, doScaleVariance=False, scaleFactor=scaleFactor) 

445 

446 def test_exposure_properties_convolve_template(self): 

447 """Check that all necessary exposure metadata is included 

448 when the template is convolved. 

449 """ 

450 noiseLevel = 1. 

451 seed = 37 

452 rng = np.random.RandomState(seed) 

453 science, sources = makeTestImage(psfSize=3.0, noiseLevel=noiseLevel, noiseSeed=6) 

454 psf = science.psf 

455 psfAvgPos = psf.getAveragePosition() 

456 psfSize = getPsfFwhm(science.psf) 

457 psfImg = psf.computeKernelImage(psfAvgPos) 

458 template, _ = makeTestImage(psfSize=2.0, noiseLevel=noiseLevel, noiseSeed=7, 

459 templateBorderSize=20, doApplyCalibration=True) 

460 

461 # Generate a random aperture correction map 

462 apCorrMap = lsst.afw.image.ApCorrMap() 

463 for name in ("a", "b", "c"): 

464 apCorrMap.set(name, lsst.afw.math.ChebyshevBoundedField(science.getBBox(), rng.randn(3, 3))) 

465 science.info.setApCorrMap(apCorrMap) 

466 

467 config = subtractImages.AlardLuptonSubtractTask.ConfigClass() 

468 config.mode = "convolveTemplate" 

469 

470 def _run_and_check_images(doDecorrelation): 

471 """Check that the metadata is correct with or without decorrelation. 

472 """ 

473 config.doDecorrelation = doDecorrelation 

474 task = subtractImages.AlardLuptonSubtractTask(config=config) 

475 output = task.run(template, science, sources) 

476 psfOut = output.difference.psf 

477 psfAvgPos = psfOut.getAveragePosition() 

478 if doDecorrelation: 

479 # Decorrelation requires recalculating the PSF, 

480 # so it will not be the same as the input 

481 psfOutSize = getPsfFwhm(science.psf) 

482 self.assertFloatsAlmostEqual(psfSize, psfOutSize) 

483 else: 

484 psfOutImg = psfOut.computeKernelImage(psfAvgPos) 

485 self.assertImagesAlmostEqual(psfImg, psfOutImg) 

486 

487 # check PSF, WCS, bbox, filterLabel, photoCalib, aperture correction 

488 self._compare_apCorrMaps(apCorrMap, output.difference.info.getApCorrMap()) 

489 self.assertWcsAlmostEqualOverBBox(science.getWcs(), output.difference.getWcs(), science.getBBox()) 

490 self.assertEqual(science.getFilterLabel(), output.difference.getFilterLabel()) 

491 self.assertEqual(science.getPhotoCalib(), output.difference.getPhotoCalib()) 

492 _run_and_check_images(doDecorrelation=True) 

493 _run_and_check_images(doDecorrelation=False) 

494 

495 def test_exposure_properties_convolve_science(self): 

496 """Check that all necessary exposure metadata is included 

497 when the science image is convolved. 

498 """ 

499 noiseLevel = 1. 

500 seed = 37 

501 rng = np.random.RandomState(seed) 

502 science, sources = makeTestImage(psfSize=2.0, noiseLevel=noiseLevel, noiseSeed=6) 

503 template, _ = makeTestImage(psfSize=3.0, noiseLevel=noiseLevel, noiseSeed=7, 

504 templateBorderSize=20, doApplyCalibration=True) 

505 psf = template.psf 

506 psfAvgPos = psf.getAveragePosition() 

507 psfSize = getPsfFwhm(template.psf) 

508 psfImg = psf.computeKernelImage(psfAvgPos) 

509 

510 # Generate a random aperture correction map 

511 apCorrMap = lsst.afw.image.ApCorrMap() 

512 for name in ("a", "b", "c"): 

513 apCorrMap.set(name, lsst.afw.math.ChebyshevBoundedField(science.getBBox(), rng.randn(3, 3))) 

514 science.info.setApCorrMap(apCorrMap) 

515 

516 config = subtractImages.AlardLuptonSubtractTask.ConfigClass() 

517 config.mode = "convolveScience" 

518 

519 def _run_and_check_images(doDecorrelation): 

520 """Check that the metadata is correct with or without decorrelation. 

521 """ 

522 config.doDecorrelation = doDecorrelation 

523 task = subtractImages.AlardLuptonSubtractTask(config=config) 

524 output = task.run(template, science, sources) 

525 if doDecorrelation: 

526 # Decorrelation requires recalculating the PSF, 

527 # so it will not be the same as the input 

528 psfOutSize = getPsfFwhm(template.psf) 

529 self.assertFloatsAlmostEqual(psfSize, psfOutSize) 

530 else: 

531 psfOut = output.difference.psf 

532 psfAvgPos = psfOut.getAveragePosition() 

533 psfOutImg = psfOut.computeKernelImage(psfAvgPos) 

534 self.assertImagesAlmostEqual(psfImg, psfOutImg) 

535 

536 # check PSF, WCS, bbox, filterLabel, photoCalib, aperture correction 

537 self._compare_apCorrMaps(apCorrMap, output.difference.info.getApCorrMap()) 

538 self.assertWcsAlmostEqualOverBBox(science.getWcs(), output.difference.getWcs(), science.getBBox()) 

539 self.assertEqual(science.getFilterLabel(), output.difference.getFilterLabel()) 

540 self.assertEqual(science.getPhotoCalib(), output.difference.getPhotoCalib()) 

541 

542 _run_and_check_images(doDecorrelation=True) 

543 _run_and_check_images(doDecorrelation=False) 

544 

545 def _compare_apCorrMaps(self, a, b): 

546 """Compare two ApCorrMaps for equality, without assuming that their BoundedFields have the 

547 same addresses (i.e. so we can compare after serialization). 

548 

549 This function is taken from ``ApCorrMapTestCase`` in afw/tests/. 

550 

551 Parameters 

552 ---------- 

553 a, b : `lsst.afw.image.ApCorrMap` 

554 The two aperture correction maps to compare. 

555 """ 

556 self.assertEqual(len(a), len(b)) 

557 for name, value in list(a.items()): 

558 value2 = b.get(name) 

559 self.assertIsNotNone(value2) 

560 self.assertEqual(value.getBBox(), value2.getBBox()) 

561 self.assertFloatsAlmostEqual( 

562 value.getCoefficients(), value2.getCoefficients(), rtol=0.0) 

563 

564 

565def setup_module(module): 

566 lsst.utils.tests.init() 

567 

568 

569class MemoryTestCase(lsst.utils.tests.MemoryTestCase): 

570 pass 

571 

572 

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

574 lsst.utils.tests.init() 

575 unittest.main()