Coverage for tests/test_subtractTask.py: 8%

354 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2023-01-13 11:47 +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 

23import numpy as np 

24 

25import lsst.afw.math as afwMath 

26import lsst.geom 

27import lsst.ip.diffim.imagePsfMatch 

28from lsst.ip.diffim import subtractImages 

29from lsst.ip.diffim.utils import getPsfFwhm, makeTestImage, makeStats, computeRobustStatistics 

30from lsst.pex.config import FieldValidationError 

31import lsst.utils.tests 

32 

33 

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

35 

36 def test_allowed_config_modes(self): 

37 """Verify the allowable modes for convolution. 

38 """ 

39 config = subtractImages.AlardLuptonSubtractTask.ConfigClass() 

40 config.mode = 'auto' 

41 config.mode = 'convolveScience' 

42 config.mode = 'convolveTemplate' 

43 

44 with self.assertRaises(FieldValidationError): 

45 config.mode = 'aotu' 

46 

47 def test_mismatched_template(self): 

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

49 does not fully contain the science image. 

50 """ 

51 xSize = 200 

52 ySize = 200 

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

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

55 config = subtractImages.AlardLuptonSubtractTask.ConfigClass() 

56 task = subtractImages.AlardLuptonSubtractTask(config=config) 

57 with self.assertRaises(AssertionError): 

58 task.run(template, science, sources) 

59 

60 def test_equal_images(self): 

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

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

63 """ 

64 noiseLevel = 1. 

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

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

67 templateBorderSize=20, doApplyCalibration=True) 

68 config = subtractImages.AlardLuptonSubtractTask.ConfigClass() 

69 config.doSubtractBackground = False 

70 task = subtractImages.AlardLuptonSubtractTask(config=config) 

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

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

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

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

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

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

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

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

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

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

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

82 makeStats(), statistic=afwMath.STDEV) 

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

84 

85 def test_auto_convolveTemplate(self): 

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

87 the template psf is the smaller. 

88 """ 

89 noiseLevel = 1. 

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

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

92 templateBorderSize=20, doApplyCalibration=True) 

93 config = subtractImages.AlardLuptonSubtractTask.ConfigClass() 

94 config.doSubtractBackground = False 

95 config.mode = "convolveTemplate" 

96 

97 task = subtractImages.AlardLuptonSubtractTask(config=config) 

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

99 

100 config.mode = "auto" 

101 task = subtractImages.AlardLuptonSubtractTask(config=config) 

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

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

104 

105 def test_auto_convolveScience(self): 

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

107 the science psf is the smaller. 

108 """ 

109 noiseLevel = 1. 

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

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

112 templateBorderSize=20, doApplyCalibration=True) 

113 config = subtractImages.AlardLuptonSubtractTask.ConfigClass() 

114 config.doSubtractBackground = False 

115 config.mode = "convolveScience" 

116 

117 task = subtractImages.AlardLuptonSubtractTask(config=config) 

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

119 

120 config.mode = "auto" 

121 task = subtractImages.AlardLuptonSubtractTask(config=config) 

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

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

124 

125 def test_science_better(self): 

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

127 with the science psf being smaller than the template. 

128 """ 

129 statsCtrl = makeStats() 

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

131 

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

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

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

135 templateBorderSize=20, doApplyCalibration=True) 

136 config = subtractImages.AlardLuptonSubtractTask.ConfigClass() 

137 config.doSubtractBackground = False 

138 config.mode = "convolveScience" 

139 task = subtractImages.AlardLuptonSubtractTask(config=config) 

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

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

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

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

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

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

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

147 statsCtrlDetect) 

148 

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

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

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

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

153 statsCtrl) 

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

155 statsCtrl, statistic=afwMath.STDEV) 

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

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

158 

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

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

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

162 

163 def test_template_better(self): 

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

165 with the template psf being smaller than the science. 

166 """ 

167 statsCtrl = makeStats() 

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

169 

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

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

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

173 templateBorderSize=20, doApplyCalibration=True) 

174 config = subtractImages.AlardLuptonSubtractTask.ConfigClass() 

175 config.doSubtractBackground = False 

176 task = subtractImages.AlardLuptonSubtractTask(config=config) 

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

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

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

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

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

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

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

184 

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

186 statsCtrlDetect) 

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

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

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

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

191 statsCtrl) 

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

193 statsCtrl, statistic=afwMath.STDEV) 

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

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

196 

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

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

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

200 

201 def test_symmetry(self): 

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

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

204 should be nearly the same. 

205 """ 

206 noiseLevel = 1. 

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

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

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

210 noiseSeed=6, templateBorderSize=0) 

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

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

213 config = subtractImages.AlardLuptonSubtractTask.ConfigClass() 

214 config.mode = 'auto' 

215 config.doSubtractBackground = False 

216 task = subtractImages.AlardLuptonSubtractTask(config=config) 

217 

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

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

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

221 

222 delta = template_better.difference.clone() 

223 delta.image -= science_better.difference.image 

224 delta.variance -= science_better.difference.variance 

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

226 

227 statsCtrl = makeStats() 

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

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

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

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

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

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

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

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

236 

237 def test_few_sources(self): 

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

239 """ 

240 xSize = 256 

241 ySize = 256 

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

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

244 config = subtractImages.AlardLuptonSubtractTask.ConfigClass() 

245 task = subtractImages.AlardLuptonSubtractTask(config=config) 

246 sources = sources[0:1] 

247 with self.assertRaisesRegex(RuntimeError, 

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

249 task.run(template, science, sources) 

250 

251 def test_order_equal_images(self): 

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

253 if the images are equivalent. 

254 """ 

255 noiseLevel = .1 

256 seed1 = 6 

257 seed2 = 7 

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

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

260 templateBorderSize=0, doApplyCalibration=True) 

261 config1 = subtractImages.AlardLuptonSubtractTask.ConfigClass() 

262 config1.mode = "convolveTemplate" 

263 config1.doSubtractBackground = False 

264 task1 = subtractImages.AlardLuptonSubtractTask(config=config1) 

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

266 

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

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

269 templateBorderSize=0, doApplyCalibration=True) 

270 config2 = subtractImages.AlardLuptonSubtractTask.ConfigClass() 

271 config2.mode = "convolveScience" 

272 config2.doSubtractBackground = False 

273 task2 = subtractImages.AlardLuptonSubtractTask(config=config2) 

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

275 diff1 = science1.maskedImage.clone() 

276 diff1 -= template1.maskedImage 

277 diff2 = science2.maskedImage.clone() 

278 diff2 -= template2.maskedImage 

279 self.assertFloatsAlmostEqual(results_convolveTemplate.difference.image.array, 

280 diff1.image.array, 

281 atol=noiseLevel*5.) 

282 self.assertFloatsAlmostEqual(results_convolveScience.difference.image.array, 

283 diff2.image.array, 

284 atol=noiseLevel*5.) 

285 diffErr = noiseLevel*2 

286 self.assertMaskedImagesAlmostEqual(results_convolveTemplate.difference.maskedImage, 

287 results_convolveScience.difference.maskedImage, 

288 atol=diffErr*5.) 

289 

290 def test_background_subtraction(self): 

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

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

293 """ 

294 noiseLevel = 1. 

295 xSize = 512 

296 ySize = 512 

297 x0 = 123 

298 y0 = 456 

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

300 templateBorderSize=20, 

301 xSize=xSize, ySize=ySize, x0=x0, y0=y0, 

302 doApplyCalibration=True) 

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

304 

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

306 background_model = afwMath.Chebyshev1Function2D(params, bbox2D) 

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

308 background=background_model, 

309 xSize=xSize, ySize=ySize, x0=x0, y0=y0) 

310 config = subtractImages.AlardLuptonSubtractTask.ConfigClass() 

311 config.doSubtractBackground = True 

312 

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

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

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

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

317 statsCtrl = makeStats() 

318 

319 def _run_and_check_images(config, statsCtrl, mode): 

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

321 """ 

322 config.mode = mode 

323 task = subtractImages.AlardLuptonSubtractTask(config=config) 

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

325 

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

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

328 

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

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

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

332 

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

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

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

336 statsCtrl, statistic=afwMath.STDEV) 

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

338 

339 _run_and_check_images(config, statsCtrl, "convolveTemplate") 

340 _run_and_check_images(config, statsCtrl, "convolveScience") 

341 

342 def test_scale_variance_convolve_template(self): 

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

344 """ 

345 scienceNoiseLevel = 4. 

346 templateNoiseLevel = 2. 

347 scaleFactor = 1.345 

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

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

350 

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

352 doDecorrelation, doScaleVariance, scaleFactor=1.): 

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

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

355 """ 

356 

357 config = subtractImages.AlardLuptonSubtractTask.ConfigClass() 

358 config.doSubtractBackground = False 

359 config.doDecorrelation = doDecorrelation 

360 config.doScaleVariance = doScaleVariance 

361 task = subtractImages.AlardLuptonSubtractTask(config=config) 

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

363 if doScaleVariance: 

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

365 scaleFactor, atol=0.05) 

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

367 scaleFactor, atol=0.05) 

368 

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

370 if doDecorrelation: 

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

372 else: 

373 templateNoise = computeRobustStatistics(output.matchedTemplate.variance, 

374 output.matchedTemplate.mask, 

375 statsCtrl) 

376 

377 if doScaleVariance: 

378 templateNoise *= scaleFactor 

379 scienceNoise *= scaleFactor 

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

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

382 

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

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

385 templateBorderSize=20, doApplyCalibration=True) 

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

387 # when the template and science variance planes are correct 

388 _run_and_check_images(science, template, sources, statsCtrl, 

389 doDecorrelation=True, doScaleVariance=True) 

390 _run_and_check_images(science, template, sources, statsCtrl, 

391 doDecorrelation=True, doScaleVariance=False) 

392 _run_and_check_images(science, template, sources, statsCtrl, 

393 doDecorrelation=False, doScaleVariance=True) 

394 _run_and_check_images(science, template, sources, statsCtrl, 

395 doDecorrelation=False, doScaleVariance=False) 

396 

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

398 # when the template variance plane is incorrect 

399 template.variance.array /= scaleFactor 

400 science.variance.array /= scaleFactor 

401 _run_and_check_images(science, template, sources, statsCtrl, 

402 doDecorrelation=True, doScaleVariance=True, scaleFactor=scaleFactor) 

403 _run_and_check_images(science, template, sources, statsCtrl, 

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

405 _run_and_check_images(science, template, sources, statsCtrl, 

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

407 _run_and_check_images(science, template, sources, statsCtrl, 

408 doDecorrelation=False, doScaleVariance=False, scaleFactor=scaleFactor) 

409 

410 def test_scale_variance_convolve_science(self): 

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

412 """ 

413 scienceNoiseLevel = 4. 

414 templateNoiseLevel = 2. 

415 scaleFactor = 1.345 

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

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

418 

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

420 doDecorrelation, doScaleVariance, scaleFactor=1.): 

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

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

423 """ 

424 

425 config = subtractImages.AlardLuptonSubtractTask.ConfigClass() 

426 config.mode = "convolveScience" 

427 config.doSubtractBackground = False 

428 config.doDecorrelation = doDecorrelation 

429 config.doScaleVariance = doScaleVariance 

430 task = subtractImages.AlardLuptonSubtractTask(config=config) 

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

432 if doScaleVariance: 

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

434 scaleFactor, atol=0.05) 

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

436 scaleFactor, atol=0.05) 

437 

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

439 if doDecorrelation: 

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

441 else: 

442 scienceNoise = computeRobustStatistics(output.matchedScience.variance, 

443 output.matchedScience.mask, 

444 statsCtrl) 

445 

446 if doScaleVariance: 

447 templateNoise *= scaleFactor 

448 scienceNoise *= scaleFactor 

449 

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

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

452 

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

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

455 templateBorderSize=20, doApplyCalibration=True) 

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

457 # when the template and science variance planes are correct 

458 _run_and_check_images(science, template, sources, statsCtrl, 

459 doDecorrelation=True, doScaleVariance=True) 

460 _run_and_check_images(science, template, sources, statsCtrl, 

461 doDecorrelation=True, doScaleVariance=False) 

462 _run_and_check_images(science, template, sources, statsCtrl, 

463 doDecorrelation=False, doScaleVariance=True) 

464 _run_and_check_images(science, template, sources, statsCtrl, 

465 doDecorrelation=False, doScaleVariance=False) 

466 

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

468 # when the template and science variance planes are incorrect 

469 science.variance.array /= scaleFactor 

470 template.variance.array /= scaleFactor 

471 _run_and_check_images(science, template, sources, statsCtrl, 

472 doDecorrelation=True, doScaleVariance=True, scaleFactor=scaleFactor) 

473 _run_and_check_images(science, template, sources, statsCtrl, 

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

475 _run_and_check_images(science, template, sources, statsCtrl, 

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

477 _run_and_check_images(science, template, sources, statsCtrl, 

478 doDecorrelation=False, doScaleVariance=False, scaleFactor=scaleFactor) 

479 

480 def test_exposure_properties_convolve_template(self): 

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

482 when the template is convolved. 

483 """ 

484 noiseLevel = 1. 

485 seed = 37 

486 rng = np.random.RandomState(seed) 

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

488 psf = science.psf 

489 psfAvgPos = psf.getAveragePosition() 

490 psfSize = getPsfFwhm(science.psf) 

491 psfImg = psf.computeKernelImage(psfAvgPos) 

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

493 templateBorderSize=20, doApplyCalibration=True) 

494 

495 # Generate a random aperture correction map 

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

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

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

499 science.info.setApCorrMap(apCorrMap) 

500 

501 config = subtractImages.AlardLuptonSubtractTask.ConfigClass() 

502 config.mode = "convolveTemplate" 

503 

504 def _run_and_check_images(doDecorrelation): 

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

506 """ 

507 config.doDecorrelation = doDecorrelation 

508 task = subtractImages.AlardLuptonSubtractTask(config=config) 

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

510 psfOut = output.difference.psf 

511 psfAvgPos = psfOut.getAveragePosition() 

512 if doDecorrelation: 

513 # Decorrelation requires recalculating the PSF, 

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

515 psfOutSize = getPsfFwhm(science.psf) 

516 self.assertFloatsAlmostEqual(psfSize, psfOutSize) 

517 else: 

518 psfOutImg = psfOut.computeKernelImage(psfAvgPos) 

519 self.assertImagesAlmostEqual(psfImg, psfOutImg) 

520 

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

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

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

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

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

526 _run_and_check_images(doDecorrelation=True) 

527 _run_and_check_images(doDecorrelation=False) 

528 

529 def test_exposure_properties_convolve_science(self): 

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

531 when the science image is convolved. 

532 """ 

533 noiseLevel = 1. 

534 seed = 37 

535 rng = np.random.RandomState(seed) 

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

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

538 templateBorderSize=20, doApplyCalibration=True) 

539 psf = template.psf 

540 psfAvgPos = psf.getAveragePosition() 

541 psfSize = getPsfFwhm(template.psf) 

542 psfImg = psf.computeKernelImage(psfAvgPos) 

543 

544 # Generate a random aperture correction map 

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

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

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

548 science.info.setApCorrMap(apCorrMap) 

549 

550 config = subtractImages.AlardLuptonSubtractTask.ConfigClass() 

551 config.mode = "convolveScience" 

552 

553 def _run_and_check_images(doDecorrelation): 

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

555 """ 

556 config.doDecorrelation = doDecorrelation 

557 task = subtractImages.AlardLuptonSubtractTask(config=config) 

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

559 if doDecorrelation: 

560 # Decorrelation requires recalculating the PSF, 

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

562 psfOutSize = getPsfFwhm(template.psf) 

563 self.assertFloatsAlmostEqual(psfSize, psfOutSize) 

564 else: 

565 psfOut = output.difference.psf 

566 psfAvgPos = psfOut.getAveragePosition() 

567 psfOutImg = psfOut.computeKernelImage(psfAvgPos) 

568 self.assertImagesAlmostEqual(psfImg, psfOutImg) 

569 

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

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

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

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

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

575 

576 _run_and_check_images(doDecorrelation=True) 

577 _run_and_check_images(doDecorrelation=False) 

578 

579 def _compare_apCorrMaps(self, a, b): 

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

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

582 

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

584 

585 Parameters 

586 ---------- 

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

588 The two aperture correction maps to compare. 

589 """ 

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

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

592 value2 = b.get(name) 

593 self.assertIsNotNone(value2) 

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

595 self.assertFloatsAlmostEqual( 

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

597 

598 

599def setup_module(module): 

600 lsst.utils.tests.init() 

601 

602 

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

604 pass 

605 

606 

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

608 lsst.utils.tests.init() 

609 unittest.main()