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
« 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/>.
22import unittest
23import numpy as np
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
34class AlardLuptonSubtractTest(lsst.utils.tests.TestCase):
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'
44 with self.assertRaises(FieldValidationError):
45 config.mode = 'aotu'
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)
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)
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"
97 task = subtractImages.AlardLuptonSubtractTask(config=config)
98 output = task.run(template.clone(), science.clone(), sources)
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)
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"
117 task = subtractImages.AlardLuptonSubtractTask(config=config)
118 output = task.run(template.clone(), science.clone(), sources)
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)
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"))
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)
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)
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)
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"))
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)
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)
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)
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)
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)
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
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)
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)
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)
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.)
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]
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
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()
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)
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())
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)
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)
339 _run_and_check_images(config, statsCtrl, "convolveTemplate")
340 _run_and_check_images(config, statsCtrl, "convolveScience")
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"))
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 """
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)
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)
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)
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)
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)
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"))
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 """
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)
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)
446 if doScaleVariance:
447 templateNoise *= scaleFactor
448 scienceNoise *= scaleFactor
450 varMean = computeRobustStatistics(output.difference.variance, output.difference.mask, statsCtrl)
451 self.assertFloatsAlmostEqual(varMean, scienceNoise + templateNoise, rtol=0.1)
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)
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)
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)
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)
501 config = subtractImages.AlardLuptonSubtractTask.ConfigClass()
502 config.mode = "convolveTemplate"
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)
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)
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)
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)
550 config = subtractImages.AlardLuptonSubtractTask.ConfigClass()
551 config.mode = "convolveScience"
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)
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)
576 _run_and_check_images(doDecorrelation=True)
577 _run_and_check_images(doDecorrelation=False)
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).
583 This function is taken from ``ApCorrMapTestCase`` in afw/tests/.
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)
599def setup_module(module):
600 lsst.utils.tests.init()
603class MemoryTestCase(lsst.utils.tests.MemoryTestCase):
604 pass
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()