Coverage for tests/test_subtractTask.py: 8%

542 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2023-03-08 10:15 +0000

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 

23 

24import lsst.afw.math as afwMath 

25import lsst.afw.table as afwTable 

26import lsst.geom 

27import lsst.ip.diffim.imagePsfMatch 

28import lsst.meas.algorithms as measAlg 

29from lsst.ip.diffim import subtractImages 

30from lsst.pex.config import FieldValidationError 

31import lsst.utils.tests 

32import numpy as np 

33from lsst.ip.diffim.utils import (computeRobustStatistics, computePSFNoiseEquivalentArea, 

34 evaluateMeanPsfFwhm, getPsfFwhm, makeStats, makeTestImage) 

35from lsst.pex.exceptions import InvalidParameterError 

36 

37 

38class CustomCoaddPsf(measAlg.CoaddPsf): 

39 """A custom CoaddPSF that overrides the getAveragePosition method. 

40 """ 

41 def getAveragePosition(self): 

42 return lsst.geom.Point2D(-10000, -10000) 

43 

44 

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

46 

47 def test_allowed_config_modes(self): 

48 """Verify the allowable modes for convolution. 

49 """ 

50 config = subtractImages.AlardLuptonSubtractTask.ConfigClass() 

51 config.mode = 'auto' 

52 config.mode = 'convolveScience' 

53 config.mode = 'convolveTemplate' 

54 

55 with self.assertRaises(FieldValidationError): 

56 config.mode = 'aotu' 

57 

58 def test_mismatched_template(self): 

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

60 does not fully contain the science image. 

61 """ 

62 xSize = 200 

63 ySize = 200 

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

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

66 config = subtractImages.AlardLuptonSubtractTask.ConfigClass() 

67 task = subtractImages.AlardLuptonSubtractTask(config=config) 

68 with self.assertRaises(AssertionError): 

69 task.run(template, science, sources) 

70 

71 def test_equal_images(self): 

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

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

74 """ 

75 noiseLevel = 1. 

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

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

78 templateBorderSize=20, doApplyCalibration=True) 

79 config = subtractImages.AlardLuptonSubtractTask.ConfigClass() 

80 config.doSubtractBackground = False 

81 task = subtractImages.AlardLuptonSubtractTask(config=config) 

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

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

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

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

86 meanError = noiseLevel/np.sqrt(output.difference.image.array.size) 

87 # Make sure to include pixels with the DETECTED mask bit set. 

88 statsCtrl = makeStats(badMaskPlanes=("EDGE", "BAD", "NO_DATA", "DETECTED", "DETECTED_NEGATIVE")) 

89 differenceMean = computeRobustStatistics(output.difference.image, output.difference.mask, statsCtrl) 

90 self.assertFloatsAlmostEqual(differenceMean, 0, atol=5*meanError) 

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

92 differenceStd = computeRobustStatistics(output.difference.image, output.difference.mask, 

93 makeStats(), statistic=afwMath.STDEV) 

94 self.assertFloatsAlmostEqual(differenceStd, np.sqrt(2)*noiseLevel, rtol=0.1) 

95 

96 def test_psf_size(self): 

97 """Test that the image subtract task runs without failing, if 

98 fwhmExposureBuffer and fwhmExposureGrid parameters are set. 

99 """ 

100 noiseLevel = 1. 

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

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

103 templateBorderSize=20, doApplyCalibration=True) 

104 

105 schema = afwTable.ExposureTable.makeMinimalSchema() 

106 weightKey = schema.addField("weight", type="D", doc="Coadd weight") 

107 exposureCatalog = afwTable.ExposureCatalog(schema) 

108 kernel = measAlg.DoubleGaussianPsf(7, 7, 2.0).getKernel() 

109 psf = measAlg.KernelPsf(kernel, template.getBBox().getCenter()) 

110 

111 record = exposureCatalog.addNew() 

112 record.setPsf(psf) 

113 record.setWcs(template.wcs) 

114 record.setD(weightKey, 1.0) 

115 record.setBBox(template.getBBox()) 

116 

117 customPsf = CustomCoaddPsf(exposureCatalog, template.wcs) 

118 template.setPsf(customPsf) 

119 

120 # Test that we get an exception if we simply get the FWHM at center. 

121 with self.assertRaises(InvalidParameterError): 

122 getPsfFwhm(template.psf, True) 

123 

124 with self.assertRaises(InvalidParameterError): 

125 getPsfFwhm(template.psf, False) 

126 

127 # Test that evaluateMeanPsfFwhm runs successfully on the template. 

128 evaluateMeanPsfFwhm(template, fwhmExposureBuffer=0.05, fwhmExposureGrid=10) 

129 

130 # Since the PSF is spatially invariant, the FWHM should be the same at 

131 # all points in the science image. 

132 fwhm1 = getPsfFwhm(science.psf, False) 

133 fwhm2 = evaluateMeanPsfFwhm(science, fwhmExposureBuffer=0.05, fwhmExposureGrid=10) 

134 self.assertAlmostEqual(fwhm1[0], fwhm2, places=13) 

135 self.assertAlmostEqual(fwhm1[1], fwhm2, places=13) 

136 

137 self.assertAlmostEqual(evaluateMeanPsfFwhm(science, fwhmExposureBuffer=0.05, 

138 fwhmExposureGrid=10), 

139 getPsfFwhm(science.psf, True), places=7 

140 ) 

141 

142 # Test that the image subtraction task runs successfully. 

143 config = subtractImages.AlardLuptonSubtractTask.ConfigClass() 

144 config.doSubtractBackground = False 

145 task = subtractImages.AlardLuptonSubtractTask(config=config) 

146 

147 # Test that the task runs if we take the mean FWHM on a grid. 

148 with self.assertLogs(level="INFO") as cm: 

149 task.run(template, science, sources) 

150 

151 # Check that evaluateMeanPsfFwhm was called. 

152 # This tests that getPsfFwhm failed raising InvalidParameterError, 

153 # that is caught and handled appropriately. 

154 logMessage = ("INFO:lsst.alardLuptonSubtract:Unable to evaluate PSF at the average position. " 

155 "Evaluting PSF on a grid of points." 

156 ) 

157 self.assertIn(logMessage, cm.output) 

158 

159 def test_auto_convolveTemplate(self): 

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

161 the template psf is the smaller. 

162 """ 

163 noiseLevel = 1. 

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

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

166 templateBorderSize=20, doApplyCalibration=True) 

167 config = subtractImages.AlardLuptonSubtractTask.ConfigClass() 

168 config.doSubtractBackground = False 

169 config.mode = "convolveTemplate" 

170 

171 task = subtractImages.AlardLuptonSubtractTask(config=config) 

172 output = task.run(template.clone(), science.clone(), sources) 

173 

174 config.mode = "auto" 

175 task = subtractImages.AlardLuptonSubtractTask(config=config) 

176 outputAuto = task.run(template, science, sources) 

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

178 

179 def test_auto_convolveScience(self): 

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

181 the science psf is the smaller. 

182 """ 

183 noiseLevel = 1. 

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

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

186 templateBorderSize=20, doApplyCalibration=True) 

187 config = subtractImages.AlardLuptonSubtractTask.ConfigClass() 

188 config.doSubtractBackground = False 

189 config.mode = "convolveScience" 

190 

191 task = subtractImages.AlardLuptonSubtractTask(config=config) 

192 output = task.run(template.clone(), science.clone(), sources) 

193 

194 config.mode = "auto" 

195 task = subtractImages.AlardLuptonSubtractTask(config=config) 

196 outputAuto = task.run(template, science, sources) 

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

198 

199 def test_science_better(self): 

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

201 with the science psf being smaller than the template. 

202 """ 

203 statsCtrl = makeStats() 

204 statsCtrlDetect = makeStats(badMaskPlanes=("EDGE", "BAD", "NO_DATA")) 

205 

206 def _run_and_check_images(statsCtrl, statsCtrlDetect, scienceNoiseLevel, templateNoiseLevel): 

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

208 template, _ = makeTestImage(psfSize=3.0, noiseLevel=templateNoiseLevel, noiseSeed=7, 

209 templateBorderSize=20, doApplyCalibration=True) 

210 config = subtractImages.AlardLuptonSubtractTask.ConfigClass() 

211 config.doSubtractBackground = False 

212 config.mode = "convolveScience" 

213 task = subtractImages.AlardLuptonSubtractTask(config=config) 

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

215 self.assertFloatsAlmostEqual(task.metadata["scaleTemplateVarianceFactor"], 1., atol=.05) 

216 self.assertFloatsAlmostEqual(task.metadata["scaleScienceVarianceFactor"], 1., atol=.05) 

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

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

219 meanError = (scienceNoiseLevel + templateNoiseLevel)/np.sqrt(nGoodPix) 

220 diffimMean = computeRobustStatistics(output.difference.image, output.difference.mask, 

221 statsCtrlDetect) 

222 

223 self.assertFloatsAlmostEqual(diffimMean, 0, atol=5*meanError) 

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

225 noiseLevel = np.sqrt(scienceNoiseLevel**2 + templateNoiseLevel**2) 

226 varianceMean = computeRobustStatistics(output.difference.variance, output.difference.mask, 

227 statsCtrl) 

228 diffimStd = computeRobustStatistics(output.difference.image, output.difference.mask, 

229 statsCtrl, statistic=afwMath.STDEV) 

230 self.assertFloatsAlmostEqual(varianceMean, noiseLevel**2, rtol=0.1) 

231 self.assertFloatsAlmostEqual(diffimStd, noiseLevel, rtol=0.1) 

232 

233 _run_and_check_images(statsCtrl, statsCtrlDetect, scienceNoiseLevel=1., templateNoiseLevel=1.) 

234 _run_and_check_images(statsCtrl, statsCtrlDetect, scienceNoiseLevel=1., templateNoiseLevel=.1) 

235 _run_and_check_images(statsCtrl, statsCtrlDetect, scienceNoiseLevel=.1, templateNoiseLevel=.1) 

236 

237 def test_template_better(self): 

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

239 with the template psf being smaller than the science. 

240 """ 

241 statsCtrl = makeStats() 

242 statsCtrlDetect = makeStats(badMaskPlanes=("EDGE", "BAD", "NO_DATA")) 

243 

244 def _run_and_check_images(statsCtrl, statsCtrlDetect, scienceNoiseLevel, templateNoiseLevel): 

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

246 template, _ = makeTestImage(psfSize=2.0, noiseLevel=templateNoiseLevel, noiseSeed=7, 

247 templateBorderSize=20, doApplyCalibration=True) 

248 config = subtractImages.AlardLuptonSubtractTask.ConfigClass() 

249 config.doSubtractBackground = False 

250 task = subtractImages.AlardLuptonSubtractTask(config=config) 

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

252 self.assertFloatsAlmostEqual(task.metadata["scaleTemplateVarianceFactor"], 1., atol=.05) 

253 self.assertFloatsAlmostEqual(task.metadata["scaleScienceVarianceFactor"], 1., atol=.05) 

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

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

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

257 meanError = (scienceNoiseLevel + templateNoiseLevel)/np.sqrt(output.difference.image.array.size) 

258 

259 diffimMean = computeRobustStatistics(output.difference.image, output.difference.mask, 

260 statsCtrlDetect) 

261 self.assertFloatsAlmostEqual(diffimMean, 0, atol=5*meanError) 

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

263 noiseLevel = np.sqrt(scienceNoiseLevel**2 + templateNoiseLevel**2) 

264 varianceMean = computeRobustStatistics(output.difference.variance, output.difference.mask, 

265 statsCtrl) 

266 diffimStd = computeRobustStatistics(output.difference.image, output.difference.mask, 

267 statsCtrl, statistic=afwMath.STDEV) 

268 self.assertFloatsAlmostEqual(varianceMean, noiseLevel**2, rtol=0.1) 

269 self.assertFloatsAlmostEqual(diffimStd, noiseLevel, rtol=0.1) 

270 

271 _run_and_check_images(statsCtrl, statsCtrlDetect, scienceNoiseLevel=1., templateNoiseLevel=1.) 

272 _run_and_check_images(statsCtrl, statsCtrlDetect, scienceNoiseLevel=1., templateNoiseLevel=.1) 

273 _run_and_check_images(statsCtrl, statsCtrlDetect, scienceNoiseLevel=.1, templateNoiseLevel=.1) 

274 

275 def test_symmetry(self): 

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

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

278 should be nearly the same. 

279 """ 

280 noiseLevel = 1. 

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

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

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

284 noiseSeed=6, templateBorderSize=0) 

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

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

287 config = subtractImages.AlardLuptonSubtractTask.ConfigClass() 

288 config.mode = 'auto' 

289 config.doSubtractBackground = False 

290 task = subtractImages.AlardLuptonSubtractTask(config=config) 

291 

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

293 science_better = task.run(template.clone(), science.clone(), sources) 

294 template_better = task.run(science, template, sources) 

295 

296 delta = template_better.difference.clone() 

297 delta.image -= science_better.difference.image 

298 delta.variance -= science_better.difference.variance 

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

300 

301 statsCtrl = makeStats() 

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

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

304 meanError = 2*noiseLevel/np.sqrt(nGoodPix) 

305 deltaMean = computeRobustStatistics(delta.image, delta.mask, statsCtrl) 

306 deltaStd = computeRobustStatistics(delta.image, delta.mask, statsCtrl, statistic=afwMath.STDEV) 

307 self.assertFloatsAlmostEqual(deltaMean, 0, atol=5*meanError) 

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

309 self.assertFloatsAlmostEqual(deltaStd, 2*np.sqrt(2)*noiseLevel, rtol=.1) 

310 

311 def test_few_sources(self): 

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

313 """ 

314 xSize = 256 

315 ySize = 256 

316 science, sources = makeTestImage(psfSize=2.4, nSrc=10, xSize=xSize, ySize=ySize) 

317 template, _ = makeTestImage(psfSize=2.0, nSrc=10, xSize=xSize, ySize=ySize, doApplyCalibration=True) 

318 config = subtractImages.AlardLuptonSubtractTask.ConfigClass() 

319 task = subtractImages.AlardLuptonSubtractTask(config=config) 

320 sources = sources[0:1] 

321 with self.assertRaisesRegex(RuntimeError, 

322 "Cannot compute PSF matching kernel: too few sources selected."): 

323 task.run(template, science, sources) 

324 

325 def test_order_equal_images(self): 

326 """Verify that the result is the same regardless of convolution mode 

327 if the images are equivalent. 

328 """ 

329 noiseLevel = .1 

330 seed1 = 6 

331 seed2 = 7 

332 science1, sources1 = makeTestImage(psfSize=2.0, noiseLevel=noiseLevel, noiseSeed=seed1) 

333 template1, _ = makeTestImage(psfSize=2.0, noiseLevel=noiseLevel, noiseSeed=seed2, 

334 templateBorderSize=0, doApplyCalibration=True) 

335 config1 = subtractImages.AlardLuptonSubtractTask.ConfigClass() 

336 config1.mode = "convolveTemplate" 

337 config1.doSubtractBackground = False 

338 task1 = subtractImages.AlardLuptonSubtractTask(config=config1) 

339 results_convolveTemplate = task1.run(template1, science1, sources1) 

340 

341 science2, sources2 = makeTestImage(psfSize=2.0, noiseLevel=noiseLevel, noiseSeed=seed1) 

342 template2, _ = makeTestImage(psfSize=2.0, noiseLevel=noiseLevel, noiseSeed=seed2, 

343 templateBorderSize=0, doApplyCalibration=True) 

344 config2 = subtractImages.AlardLuptonSubtractTask.ConfigClass() 

345 config2.mode = "convolveScience" 

346 config2.doSubtractBackground = False 

347 task2 = subtractImages.AlardLuptonSubtractTask(config=config2) 

348 results_convolveScience = task2.run(template2, science2, sources2) 

349 bbox = results_convolveTemplate.difference.getBBox().clippedTo( 

350 results_convolveScience.difference.getBBox()) 

351 diff1 = science1.maskedImage.clone()[bbox] 

352 diff1 -= template1.maskedImage[bbox] 

353 diff2 = science2.maskedImage.clone()[bbox] 

354 diff2 -= template2.maskedImage[bbox] 

355 self.assertFloatsAlmostEqual(results_convolveTemplate.difference[bbox].image.array, 

356 diff1.image.array, 

357 atol=noiseLevel*5.) 

358 self.assertFloatsAlmostEqual(results_convolveScience.difference[bbox].image.array, 

359 diff2.image.array, 

360 atol=noiseLevel*5.) 

361 diffErr = noiseLevel*2 

362 self.assertMaskedImagesAlmostEqual(results_convolveTemplate.difference[bbox].maskedImage, 

363 results_convolveScience.difference[bbox].maskedImage, 

364 atol=diffErr*5.) 

365 

366 def test_background_subtraction(self): 

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

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

369 """ 

370 noiseLevel = 1. 

371 xSize = 512 

372 ySize = 512 

373 x0 = 123 

374 y0 = 456 

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

376 templateBorderSize=20, 

377 xSize=xSize, ySize=ySize, x0=x0, y0=y0, 

378 doApplyCalibration=True) 

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

380 

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

382 background_model = afwMath.Chebyshev1Function2D(params, bbox2D) 

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

384 background=background_model, 

385 xSize=xSize, ySize=ySize, x0=x0, y0=y0) 

386 config = subtractImages.AlardLuptonSubtractTask.ConfigClass() 

387 config.doSubtractBackground = True 

388 

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

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

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

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

393 statsCtrl = makeStats() 

394 

395 def _run_and_check_images(config, statsCtrl, mode): 

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

397 """ 

398 config.mode = mode 

399 task = subtractImages.AlardLuptonSubtractTask(config=config) 

400 output = task.run(template.clone(), science.clone(), sources) 

401 

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

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

404 

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

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

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

408 

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

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

411 stdVal = computeRobustStatistics(output.difference.image, output.difference.mask, 

412 statsCtrl, statistic=afwMath.STDEV) 

413 self.assertFloatsAlmostEqual(stdVal, np.sqrt(2)*noiseLevel, rtol=0.1) 

414 

415 _run_and_check_images(config, statsCtrl, "convolveTemplate") 

416 _run_and_check_images(config, statsCtrl, "convolveScience") 

417 

418 def test_scale_variance_convolve_template(self): 

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

420 """ 

421 scienceNoiseLevel = 4. 

422 templateNoiseLevel = 2. 

423 scaleFactor = 1.345 

424 # Make sure to include pixels with the DETECTED mask bit set. 

425 statsCtrl = makeStats(badMaskPlanes=("EDGE", "BAD", "NO_DATA")) 

426 

427 def _run_and_check_images(science, template, sources, statsCtrl, 

428 doDecorrelation, doScaleVariance, scaleFactor=1.): 

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

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

431 """ 

432 

433 config = subtractImages.AlardLuptonSubtractTask.ConfigClass() 

434 config.doSubtractBackground = False 

435 config.doDecorrelation = doDecorrelation 

436 config.doScaleVariance = doScaleVariance 

437 task = subtractImages.AlardLuptonSubtractTask(config=config) 

438 output = task.run(template.clone(), science.clone(), sources) 

439 if doScaleVariance: 

440 self.assertFloatsAlmostEqual(task.metadata["scaleTemplateVarianceFactor"], 

441 scaleFactor, atol=0.05) 

442 self.assertFloatsAlmostEqual(task.metadata["scaleScienceVarianceFactor"], 

443 scaleFactor, atol=0.05) 

444 

445 scienceNoise = computeRobustStatistics(science.variance, science.mask, statsCtrl) 

446 if doDecorrelation: 

447 templateNoise = computeRobustStatistics(template.variance, template.mask, statsCtrl) 

448 else: 

449 templateNoise = computeRobustStatistics(output.matchedTemplate.variance, 

450 output.matchedTemplate.mask, 

451 statsCtrl) 

452 

453 if doScaleVariance: 

454 templateNoise *= scaleFactor 

455 scienceNoise *= scaleFactor 

456 varMean = computeRobustStatistics(output.difference.variance, output.difference.mask, statsCtrl) 

457 self.assertFloatsAlmostEqual(varMean, scienceNoise + templateNoise, rtol=0.1) 

458 

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

460 template, _ = makeTestImage(psfSize=2.0, noiseLevel=templateNoiseLevel, noiseSeed=7, 

461 templateBorderSize=20, doApplyCalibration=True) 

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

463 # when the template and science variance planes are correct 

464 _run_and_check_images(science, template, sources, statsCtrl, 

465 doDecorrelation=True, doScaleVariance=True) 

466 _run_and_check_images(science, template, sources, statsCtrl, 

467 doDecorrelation=True, doScaleVariance=False) 

468 _run_and_check_images(science, template, sources, statsCtrl, 

469 doDecorrelation=False, doScaleVariance=True) 

470 _run_and_check_images(science, template, sources, statsCtrl, 

471 doDecorrelation=False, doScaleVariance=False) 

472 

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

474 # when the template variance plane is incorrect 

475 template.variance.array /= scaleFactor 

476 science.variance.array /= scaleFactor 

477 _run_and_check_images(science, template, sources, statsCtrl, 

478 doDecorrelation=True, doScaleVariance=True, scaleFactor=scaleFactor) 

479 _run_and_check_images(science, template, sources, statsCtrl, 

480 doDecorrelation=True, doScaleVariance=False, scaleFactor=scaleFactor) 

481 _run_and_check_images(science, template, sources, statsCtrl, 

482 doDecorrelation=False, doScaleVariance=True, scaleFactor=scaleFactor) 

483 _run_and_check_images(science, template, sources, statsCtrl, 

484 doDecorrelation=False, doScaleVariance=False, scaleFactor=scaleFactor) 

485 

486 def test_scale_variance_convolve_science(self): 

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

488 """ 

489 scienceNoiseLevel = 4. 

490 templateNoiseLevel = 2. 

491 scaleFactor = 1.345 

492 # Make sure to include pixels with the DETECTED mask bit set. 

493 statsCtrl = makeStats(badMaskPlanes=("EDGE", "BAD", "NO_DATA")) 

494 

495 def _run_and_check_images(science, template, sources, statsCtrl, 

496 doDecorrelation, doScaleVariance, scaleFactor=1.): 

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

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

499 """ 

500 

501 config = subtractImages.AlardLuptonSubtractTask.ConfigClass() 

502 config.mode = "convolveScience" 

503 config.doSubtractBackground = False 

504 config.doDecorrelation = doDecorrelation 

505 config.doScaleVariance = doScaleVariance 

506 task = subtractImages.AlardLuptonSubtractTask(config=config) 

507 output = task.run(template.clone(), science.clone(), sources) 

508 if doScaleVariance: 

509 self.assertFloatsAlmostEqual(task.metadata["scaleTemplateVarianceFactor"], 

510 scaleFactor, atol=0.05) 

511 self.assertFloatsAlmostEqual(task.metadata["scaleScienceVarianceFactor"], 

512 scaleFactor, atol=0.05) 

513 

514 templateNoise = computeRobustStatistics(template.variance, template.mask, statsCtrl) 

515 if doDecorrelation: 

516 scienceNoise = computeRobustStatistics(science.variance, science.mask, statsCtrl) 

517 else: 

518 scienceNoise = computeRobustStatistics(output.matchedScience.variance, 

519 output.matchedScience.mask, 

520 statsCtrl) 

521 

522 if doScaleVariance: 

523 templateNoise *= scaleFactor 

524 scienceNoise *= scaleFactor 

525 

526 varMean = computeRobustStatistics(output.difference.variance, output.difference.mask, statsCtrl) 

527 self.assertFloatsAlmostEqual(varMean, scienceNoise + templateNoise, rtol=0.1) 

528 

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

530 template, _ = makeTestImage(psfSize=3.0, noiseLevel=templateNoiseLevel, noiseSeed=7, 

531 templateBorderSize=20, doApplyCalibration=True) 

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

533 # when the template and science variance planes are correct 

534 _run_and_check_images(science, template, sources, statsCtrl, 

535 doDecorrelation=True, doScaleVariance=True) 

536 _run_and_check_images(science, template, sources, statsCtrl, 

537 doDecorrelation=True, doScaleVariance=False) 

538 _run_and_check_images(science, template, sources, statsCtrl, 

539 doDecorrelation=False, doScaleVariance=True) 

540 _run_and_check_images(science, template, sources, statsCtrl, 

541 doDecorrelation=False, doScaleVariance=False) 

542 

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

544 # when the template and science variance planes are incorrect 

545 science.variance.array /= scaleFactor 

546 template.variance.array /= scaleFactor 

547 _run_and_check_images(science, template, sources, statsCtrl, 

548 doDecorrelation=True, doScaleVariance=True, scaleFactor=scaleFactor) 

549 _run_and_check_images(science, template, sources, statsCtrl, 

550 doDecorrelation=True, doScaleVariance=False, scaleFactor=scaleFactor) 

551 _run_and_check_images(science, template, sources, statsCtrl, 

552 doDecorrelation=False, doScaleVariance=True, scaleFactor=scaleFactor) 

553 _run_and_check_images(science, template, sources, statsCtrl, 

554 doDecorrelation=False, doScaleVariance=False, scaleFactor=scaleFactor) 

555 

556 def test_exposure_properties_convolve_template(self): 

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

558 when the template is convolved. 

559 """ 

560 noiseLevel = 1. 

561 seed = 37 

562 rng = np.random.RandomState(seed) 

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

564 psf = science.psf 

565 psfAvgPos = psf.getAveragePosition() 

566 psfSize = getPsfFwhm(science.psf) 

567 psfImg = psf.computeKernelImage(psfAvgPos) 

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

569 templateBorderSize=20, doApplyCalibration=True) 

570 

571 # Generate a random aperture correction map 

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

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

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

575 science.info.setApCorrMap(apCorrMap) 

576 

577 config = subtractImages.AlardLuptonSubtractTask.ConfigClass() 

578 config.mode = "convolveTemplate" 

579 

580 def _run_and_check_images(doDecorrelation): 

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

582 """ 

583 config.doDecorrelation = doDecorrelation 

584 task = subtractImages.AlardLuptonSubtractTask(config=config) 

585 output = task.run(template.clone(), science.clone(), sources) 

586 psfOut = output.difference.psf 

587 psfAvgPos = psfOut.getAveragePosition() 

588 if doDecorrelation: 

589 # Decorrelation requires recalculating the PSF, 

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

591 psfOutSize = getPsfFwhm(science.psf) 

592 self.assertFloatsAlmostEqual(psfSize, psfOutSize) 

593 else: 

594 psfOutImg = psfOut.computeKernelImage(psfAvgPos) 

595 self.assertImagesAlmostEqual(psfImg, psfOutImg) 

596 

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

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

599 self.assertWcsAlmostEqualOverBBox(science.wcs, output.difference.wcs, science.getBBox()) 

600 self.assertEqual(science.filter, output.difference.filter) 

601 self.assertEqual(science.photoCalib, output.difference.photoCalib) 

602 _run_and_check_images(doDecorrelation=True) 

603 _run_and_check_images(doDecorrelation=False) 

604 

605 def test_exposure_properties_convolve_science(self): 

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

607 when the science image is convolved. 

608 """ 

609 noiseLevel = 1. 

610 seed = 37 

611 rng = np.random.RandomState(seed) 

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

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

614 templateBorderSize=20, doApplyCalibration=True) 

615 psf = template.psf 

616 psfAvgPos = psf.getAveragePosition() 

617 psfSize = getPsfFwhm(template.psf) 

618 psfImg = psf.computeKernelImage(psfAvgPos) 

619 

620 # Generate a random aperture correction map 

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

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

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

624 science.info.setApCorrMap(apCorrMap) 

625 

626 config = subtractImages.AlardLuptonSubtractTask.ConfigClass() 

627 config.mode = "convolveScience" 

628 

629 def _run_and_check_images(doDecorrelation): 

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

631 """ 

632 config.doDecorrelation = doDecorrelation 

633 task = subtractImages.AlardLuptonSubtractTask(config=config) 

634 output = task.run(template.clone(), science.clone(), sources) 

635 if doDecorrelation: 

636 # Decorrelation requires recalculating the PSF, 

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

638 psfOutSize = getPsfFwhm(template.psf) 

639 self.assertFloatsAlmostEqual(psfSize, psfOutSize) 

640 else: 

641 psfOut = output.difference.psf 

642 psfAvgPos = psfOut.getAveragePosition() 

643 psfOutImg = psfOut.computeKernelImage(psfAvgPos) 

644 self.assertImagesAlmostEqual(psfImg, psfOutImg) 

645 

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

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

648 self.assertWcsAlmostEqualOverBBox(science.wcs, output.difference.wcs, science.getBBox()) 

649 self.assertEqual(science.filter, output.difference.filter) 

650 self.assertEqual(science.photoCalib, output.difference.photoCalib) 

651 

652 _run_and_check_images(doDecorrelation=True) 

653 _run_and_check_images(doDecorrelation=False) 

654 

655 def _compare_apCorrMaps(self, a, b): 

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

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

658 

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

660 

661 Parameters 

662 ---------- 

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

664 The two aperture correction maps to compare. 

665 """ 

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

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

668 value2 = b.get(name) 

669 self.assertIsNotNone(value2) 

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

671 self.assertFloatsAlmostEqual( 

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

673 

674 

675class AlardLuptonPreconvolveSubtractTest(lsst.utils.tests.TestCase): 

676 

677 def test_mismatched_template(self): 

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

679 does not fully contain the science image. 

680 """ 

681 xSize = 200 

682 ySize = 200 

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

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

685 config = subtractImages.AlardLuptonPreconvolveSubtractTask.ConfigClass() 

686 task = subtractImages.AlardLuptonPreconvolveSubtractTask(config=config) 

687 with self.assertRaises(AssertionError): 

688 task.run(template, science, sources) 

689 

690 def test_equal_images(self): 

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

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

693 """ 

694 noiseLevel = 1. 

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

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

697 templateBorderSize=20, doApplyCalibration=True) 

698 config = subtractImages.AlardLuptonPreconvolveSubtractTask.ConfigClass() 

699 config.doSubtractBackground = False 

700 task = subtractImages.AlardLuptonPreconvolveSubtractTask(config=config) 

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

702 # There shoud be no NaN values in the Score image 

703 self.assertTrue(np.all(np.isfinite(output.scoreExposure.image.array))) 

704 # Mean of Score image should be close to zero. 

705 meanError = noiseLevel/np.sqrt(output.scoreExposure.image.array.size) 

706 # Make sure to include pixels with the DETECTED mask bit set. 

707 statsCtrl = makeStats(badMaskPlanes=("EDGE", "BAD", "NO_DATA")) 

708 scoreMean = computeRobustStatistics(output.scoreExposure.image, 

709 output.scoreExposure.mask, 

710 statsCtrl) 

711 self.assertFloatsAlmostEqual(scoreMean, 0, atol=5*meanError) 

712 nea = computePSFNoiseEquivalentArea(science.psf) 

713 # stddev of Score image should be close to expected value. 

714 scoreStd = computeRobustStatistics(output.scoreExposure.image, output.scoreExposure.mask, 

715 statsCtrl=statsCtrl, statistic=afwMath.STDEV) 

716 self.assertFloatsAlmostEqual(scoreStd, np.sqrt(2)*noiseLevel/np.sqrt(nea), rtol=0.1) 

717 

718 def test_agnostic_template_psf(self): 

719 """Test that the Score image is the same whether the template PSF is 

720 larger or smaller than the science image PSF. 

721 """ 

722 noiseLevel = .3 

723 science, sources = makeTestImage(psfSize=2.4, noiseLevel=noiseLevel, 

724 noiseSeed=6, templateBorderSize=0) 

725 template1, _ = makeTestImage(psfSize=3.0, noiseLevel=noiseLevel, 

726 noiseSeed=7, doApplyCalibration=True) 

727 template2, _ = makeTestImage(psfSize=2.0, noiseLevel=noiseLevel, 

728 noiseSeed=8, doApplyCalibration=True) 

729 config = subtractImages.AlardLuptonPreconvolveSubtractTask.ConfigClass() 

730 config.doSubtractBackground = False 

731 task = subtractImages.AlardLuptonPreconvolveSubtractTask(config=config) 

732 

733 science_better = task.run(template1, science.clone(), sources) 

734 template_better = task.run(template2, science, sources) 

735 bbox = science_better.scoreExposure.getBBox().clippedTo(template_better.scoreExposure.getBBox()) 

736 

737 delta = template_better.scoreExposure[bbox].clone() 

738 delta.image -= science_better.scoreExposure[bbox].image 

739 delta.variance -= science_better.scoreExposure[bbox].variance 

740 delta.mask.array &= science_better.scoreExposure[bbox].mask.array 

741 

742 statsCtrl = makeStats(badMaskPlanes=("EDGE", "BAD", "NO_DATA")) 

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

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

745 meanError = 2*noiseLevel/np.sqrt(nGoodPix) 

746 deltaMean = computeRobustStatistics(delta.image, delta.mask, statsCtrl) 

747 deltaStd = computeRobustStatistics(delta.image, delta.mask, statsCtrl, 

748 statistic=afwMath.STDEV) 

749 self.assertFloatsAlmostEqual(deltaMean, 0, atol=5*meanError) 

750 nea = computePSFNoiseEquivalentArea(science.psf) 

751 # stddev of Score image should be close to expected value 

752 self.assertFloatsAlmostEqual(deltaStd, np.sqrt(2)*noiseLevel/np.sqrt(nea), rtol=.1) 

753 

754 def test_few_sources(self): 

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

756 """ 

757 xSize = 256 

758 ySize = 256 

759 science, sources = makeTestImage(psfSize=2.4, nSrc=10, xSize=xSize, ySize=ySize) 

760 template, _ = makeTestImage(psfSize=2.0, nSrc=10, xSize=xSize, ySize=ySize, doApplyCalibration=True) 

761 config = subtractImages.AlardLuptonPreconvolveSubtractTask.ConfigClass() 

762 task = subtractImages.AlardLuptonPreconvolveSubtractTask(config=config) 

763 sources = sources[0:1] 

764 with self.assertRaisesRegex(RuntimeError, 

765 "Cannot compute PSF matching kernel: too few sources selected."): 

766 task.run(template, science, sources) 

767 

768 def test_background_subtraction(self): 

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

770 and that it is subtracted correctly in the Score image. 

771 """ 

772 noiseLevel = 1. 

773 xSize = 512 

774 ySize = 512 

775 x0 = 123 

776 y0 = 456 

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

778 templateBorderSize=20, 

779 xSize=xSize, ySize=ySize, x0=x0, y0=y0, 

780 doApplyCalibration=True) 

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

782 

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

784 background_model = afwMath.Chebyshev1Function2D(params, bbox2D) 

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

786 background=background_model, 

787 xSize=xSize, ySize=ySize, x0=x0, y0=y0) 

788 config = subtractImages.AlardLuptonPreconvolveSubtractTask.ConfigClass() 

789 config.doSubtractBackground = True 

790 

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

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

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

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

795 statsCtrl = makeStats(badMaskPlanes=("EDGE", "BAD", "NO_DATA")) 

796 

797 task = subtractImages.AlardLuptonPreconvolveSubtractTask(config=config) 

798 output = task.run(template.clone(), science.clone(), sources) 

799 

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

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

802 

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

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

805 np.array(params), rtol=0.2) 

806 

807 # stddev of Score image should be close to expected value. 

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

809 stdVal = computeRobustStatistics(output.scoreExposure.image, output.scoreExposure.mask, 

810 statsCtrl, statistic=afwMath.STDEV) 

811 # get the img psf Noise Equivalent Area value 

812 nea = computePSFNoiseEquivalentArea(science.psf) 

813 self.assertFloatsAlmostEqual(stdVal, np.sqrt(2)*noiseLevel/np.sqrt(nea), rtol=0.1) 

814 

815 def test_scale_variance(self): 

816 """Check variance scaling of the Score image. 

817 """ 

818 scienceNoiseLevel = 4. 

819 templateNoiseLevel = 2. 

820 scaleFactor = 1.345 

821 # Make sure to include pixels with the DETECTED mask bit set. 

822 statsCtrl = makeStats(badMaskPlanes=("EDGE", "BAD", "NO_DATA")) 

823 

824 def _run_and_check_images(science, template, sources, statsCtrl, 

825 doDecorrelation, doScaleVariance, scaleFactor=1.): 

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

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

828 """ 

829 

830 config = subtractImages.AlardLuptonPreconvolveSubtractTask.ConfigClass() 

831 config.doSubtractBackground = False 

832 config.doDecorrelation = doDecorrelation 

833 config.doScaleVariance = doScaleVariance 

834 task = subtractImages.AlardLuptonPreconvolveSubtractTask(config=config) 

835 output = task.run(template.clone(), science.clone(), sources) 

836 if doScaleVariance: 

837 self.assertFloatsAlmostEqual(task.metadata["scaleTemplateVarianceFactor"], 

838 scaleFactor, atol=0.05) 

839 self.assertFloatsAlmostEqual(task.metadata["scaleScienceVarianceFactor"], 

840 scaleFactor, atol=0.05) 

841 

842 scienceNoise = computeRobustStatistics(science.variance, science.mask, statsCtrl) 

843 # get the img psf Noise Equivalent Area value 

844 nea = computePSFNoiseEquivalentArea(science.psf) 

845 scienceNoise /= nea 

846 if doDecorrelation: 

847 templateNoise = computeRobustStatistics(template.variance, template.mask, statsCtrl) 

848 templateNoise /= nea 

849 else: 

850 # Don't divide by NEA in this case, since the template is convolved 

851 # and in the same units as the Score exposure. 

852 templateNoise = computeRobustStatistics(output.matchedTemplate.variance, 

853 output.matchedTemplate.mask, 

854 statsCtrl) 

855 if doScaleVariance: 

856 templateNoise *= scaleFactor 

857 scienceNoise *= scaleFactor 

858 varMean = computeRobustStatistics(output.scoreExposure.variance, 

859 output.scoreExposure.mask, 

860 statsCtrl) 

861 self.assertFloatsAlmostEqual(varMean, scienceNoise + templateNoise, rtol=0.1) 

862 

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

864 template, _ = makeTestImage(psfSize=2.0, noiseLevel=templateNoiseLevel, noiseSeed=7, 

865 templateBorderSize=20, doApplyCalibration=True) 

866 # Verify that the variance plane of the Score image is correct 

867 # when the template and science variance planes are correct 

868 _run_and_check_images(science, template, sources, statsCtrl, 

869 doDecorrelation=True, doScaleVariance=True) 

870 _run_and_check_images(science, template, sources, statsCtrl, 

871 doDecorrelation=True, doScaleVariance=False) 

872 _run_and_check_images(science, template, sources, statsCtrl, 

873 doDecorrelation=False, doScaleVariance=True) 

874 _run_and_check_images(science, template, sources, statsCtrl, 

875 doDecorrelation=False, doScaleVariance=False) 

876 

877 # Verify that the variance plane of the Score image is correct 

878 # when the template variance plane is incorrect 

879 template.variance.array /= scaleFactor 

880 science.variance.array /= scaleFactor 

881 _run_and_check_images(science, template, sources, statsCtrl, 

882 doDecorrelation=True, doScaleVariance=True, scaleFactor=scaleFactor) 

883 _run_and_check_images(science, template, sources, statsCtrl, 

884 doDecorrelation=True, doScaleVariance=False, scaleFactor=scaleFactor) 

885 _run_and_check_images(science, template, sources, statsCtrl, 

886 doDecorrelation=False, doScaleVariance=True, scaleFactor=scaleFactor) 

887 _run_and_check_images(science, template, sources, statsCtrl, 

888 doDecorrelation=False, doScaleVariance=False, scaleFactor=scaleFactor) 

889 

890 def test_exposure_properties(self): 

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

892 with the Score image. 

893 """ 

894 noiseLevel = 1. 

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

896 psf = science.psf 

897 psfAvgPos = psf.getAveragePosition() 

898 psfSize = getPsfFwhm(science.psf) 

899 psfImg = psf.computeKernelImage(psfAvgPos) 

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

901 templateBorderSize=20, doApplyCalibration=True) 

902 

903 config = subtractImages.AlardLuptonPreconvolveSubtractTask.ConfigClass() 

904 

905 def _run_and_check_images(doDecorrelation): 

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

907 """ 

908 config.doDecorrelation = doDecorrelation 

909 task = subtractImages.AlardLuptonPreconvolveSubtractTask(config=config) 

910 output = task.run(template.clone(), science.clone(), sources) 

911 psfOut = output.scoreExposure.psf 

912 psfAvgPos = psfOut.getAveragePosition() 

913 if doDecorrelation: 

914 # Decorrelation requires recalculating the PSF, 

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

916 psfOutSize = getPsfFwhm(science.psf) 

917 self.assertFloatsAlmostEqual(psfSize, psfOutSize) 

918 else: 

919 psfOutImg = psfOut.computeKernelImage(psfAvgPos) 

920 self.assertImagesAlmostEqual(psfImg, psfOutImg) 

921 

922 # check PSF, WCS, bbox, filterLabel, photoCalib 

923 self.assertWcsAlmostEqualOverBBox(science.wcs, output.scoreExposure.wcs, science.getBBox()) 

924 self.assertEqual(science.filter, output.scoreExposure.filter) 

925 self.assertEqual(science.photoCalib, output.scoreExposure.photoCalib) 

926 _run_and_check_images(doDecorrelation=True) 

927 _run_and_check_images(doDecorrelation=False) 

928 

929 

930def setup_module(module): 

931 lsst.utils.tests.init() 

932 

933 

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

935 pass 

936 

937 

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

939 lsst.utils.tests.init() 

940 unittest.main()