Coverage for tests/test_subtractTask.py: 11%
317 statements
« prev ^ index » next coverage.py v6.4.1, created at 2022-06-09 03:32 -0700
« prev ^ index » next coverage.py v6.4.1, created at 2022-06-09 03:32 -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/>.
22import unittest
23import numpy as np
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
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)
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=256,
52 ySize=256,
53 x0=12345,
54 y0=67890,
55 calibration=1.,
56 doApplyCalibration=False,
57 ):
58 """Make a reproduceable PSF-convolved exposure for testing.
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?
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
102 bbox = lsst.geom.Box2I(lsst.geom.Point2I(x0, y0), lsst.geom.Extent2I(xSize, ySize))
103 if templateBorderSize > 0:
104 bbox.grow(templateBorderSize)
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
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))))
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)
135 return modelExposure, sourceCat
138class AlardLuptonSubtractTest(lsst.utils.tests.TestCase):
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'
148 with self.assertRaises(FieldValidationError):
149 config.mode = 'aotu'
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)
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)
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"
197 task = subtractImages.AlardLuptonSubtractTask(config=config)
198 output = task.run(template, science, sources)
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)
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"
218 task = subtractImages.AlardLuptonSubtractTask(config=config)
219 output = task.run(template, science, sources)
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)
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)
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)
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)
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)
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
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)
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)
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)
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)
337 self.assertMaskedImagesEqual(science.maskedImage,
338 scienceOrig.maskedImage)
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]
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
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
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)
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())
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)
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)
387 _run_and_check_images(config, "convolveTemplate")
388 _run_and_check_images(config, "convolveScience")
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[...]
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)
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)
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)
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)
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)
467 config = subtractImages.AlardLuptonSubtractTask.ConfigClass()
468 config.mode = "convolveTemplate"
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)
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.getFilter(), output.difference.getFilter())
491 self.assertEqual(science.getPhotoCalib(), output.difference.getPhotoCalib())
492 _run_and_check_images(doDecorrelation=True)
493 _run_and_check_images(doDecorrelation=False)
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)
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)
516 config = subtractImages.AlardLuptonSubtractTask.ConfigClass()
517 config.mode = "convolveScience"
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)
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.getFilter(), output.difference.getFilter())
540 self.assertEqual(science.getPhotoCalib(), output.difference.getPhotoCalib())
542 _run_and_check_images(doDecorrelation=True)
543 _run_and_check_images(doDecorrelation=False)
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).
549 This function is taken from ``ApCorrMapTestCase`` in afw/tests/.
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)
565def setup_module(module):
566 lsst.utils.tests.init()
569class MemoryTestCase(lsst.utils.tests.MemoryTestCase):
570 pass
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()