Coverage for tests/test_imageDifferenceOld.py: 11%
287 statements
« prev ^ index » next coverage.py v6.5.0, created at 2023-01-11 03:35 -0800
« prev ^ index » next coverage.py v6.5.0, created at 2023-01-11 03:35 -0800
1# This file is part of ip_diffim.
2#
3# LSST Data Management System
4# This product includes software developed by the
5# LSST Project (http://www.lsst.org/).
6# See COPYRIGHT file at the top of the source tree.
7#
8# This program is free software: you can redistribute it and/or modify
9# it under the terms of the GNU General Public License as published by
10# the Free Software Foundation, either version 3 of the License, or
11# (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU General Public License for more details.
17#
18# You should have received a copy of the LSST License Statement and
19# the GNU General Public License along with this program. If not,
20# see <https://www.lsstcorp.org/LegalNotices/>.
21#
23"""Tests of the old ImageDifferenceTask interface."""
25import numpy as np
27import lsst.afw.geom as afwGeom
28import lsst.afw.math as afwMath
29import lsst.daf.base as dafBase
30import lsst.geom as geom
31from lsst.meas.algorithms.testUtils import plantSources
32import lsst.utils.tests
34from lsst.ip.diffim.imageDecorrelation import DecorrelateALKernelTask, DecorrelateALKernelConfig
35from lsst.ip.diffim.imagePsfMatch import ImagePsfMatchTask, ImagePsfMatchConfig
36from lsst.ip.diffim.zogy import ZogyTask, ZogyConfig
39class ImageDifferenceTestBase(lsst.utils.tests.TestCase):
40 """A test case for comparing image differencing algorithms.
42 Attributes
43 ----------
44 bbox : `lsst.afw.geom.Box2I`
45 Bounding box of the test model.
46 bufferSize : `int`
47 Distance from the inner edge of the bounding box
48 to avoid placing test sources in the model images.
49 nRandIter : `int`
50 Number of iterations to repeat each test with random numbers.
51 statsCtrl : `lsst.afw.math.StatisticsControl`
52 Statistics control object.
53 """
55 def setUp(self):
56 """Define the filter, DCR parameters, and the bounding box for the tests.
57 """
58 self.nRandIter = 5 # Number of iterations to repeat each test with random numbers.
59 self.bufferSize = 5
60 xSize = 250
61 ySize = 260
62 x0 = 12345
63 y0 = 67890
64 self.bbox = geom.Box2I(geom.Point2I(x0, y0), geom.Extent2I(xSize, ySize))
65 self.statsCtrl = afwMath.StatisticsControl()
66 self.statsCtrl.setNumSigmaClip(3.)
67 self.statsCtrl.setNumIter(3)
69 def makeTestImages(self, seed=5, nSrc=5, psfSize=2., noiseLevel=5.,
70 fluxLevel=500., fluxRange=2.):
71 """Make reproduceable PSF-convolved masked images for testing.
73 Parameters
74 ----------
75 seed : `int`, optional
76 Seed value to initialize the random number generator.
77 nSrc : `int`, optional
78 Number of sources to simulate.
79 psfSize : `float`, optional
80 Width of the PSF of the simulated sources, in pixels.
81 noiseLevel : `float`, optional
82 Standard deviation of the noise to add to each pixel.
83 fluxLevel : `float`, optional
84 Reference flux of the simulated sources.
85 fluxRange : `float`, optional
86 Range in flux amplitude of the simulated sources.
88 Returns
89 -------
90 modelImages : `lsst.afw.image.ExposureF`
91 The model image, with the mask and variance planes.
92 sourceCat : `lsst.afw.table.SourceCatalog`
93 Catalog of sources detected on the model image.
94 """
95 rng = np.random.RandomState(seed)
96 x0, y0 = self.bbox.getBegin()
97 xSize, ySize = self.bbox.getDimensions()
98 xLoc = rng.rand(nSrc)*(xSize - 2*self.bufferSize) + self.bufferSize + x0
99 yLoc = rng.rand(nSrc)*(ySize - 2*self.bufferSize) + self.bufferSize + y0
101 flux = (rng.rand(nSrc)*(fluxRange - 1.) + 1.)*fluxLevel
102 sigmas = [psfSize for src in range(nSrc)]
103 coordList = list(zip(xLoc, yLoc, flux, sigmas))
104 kernelSize = int(xSize/2) # Need a careful explanation of this kernel size choice
105 skyLevel = 0
106 # Don't use the built in poisson noise: it modifies the global state of numpy random
107 model = plantSources(self.bbox, kernelSize, skyLevel, coordList, addPoissonNoise=False)
108 noise = rng.rand(ySize, xSize)*noiseLevel
109 model.image.array += noise
110 model.variance.array = (np.sqrt(np.abs(model.image.array)) + noiseLevel
111 - np.mean(np.sqrt(np.abs(noise))))
113 # Run source detection to set up the mask plane
114 psfMatchTask = ImagePsfMatchTask(config=ImagePsfMatchConfig())
115 sourceCat = psfMatchTask.getSelectSources(model)
117 model.setWcs(self._makeWcs())
118 return model, sourceCat
120 @staticmethod
121 def _makeWcs(offset=0):
122 """Make a fake Wcs.
124 Parameters
125 ----------
126 offset : `float`
127 offset the Wcs by this many pixels.
128 """
129 # taken from $AFW_DIR/tests/testMakeWcs.py
130 metadata = dafBase.PropertySet()
131 metadata.set("SIMPLE", "T")
132 metadata.set("BITPIX", -32)
133 metadata.set("NAXIS", 2)
134 metadata.set("NAXIS1", 1024)
135 metadata.set("NAXIS2", 1153)
136 metadata.set("RADESYS", 'FK5')
137 metadata.set("EQUINOX", 2000.)
138 metadata.setDouble("CRVAL1", 215.604025685476)
139 metadata.setDouble("CRVAL2", 53.1595451514076)
140 metadata.setDouble("CRPIX1", 1109.99981456774 + offset)
141 metadata.setDouble("CRPIX2", 560.018167811613 + offset)
142 metadata.set("CTYPE1", 'RA---SIN')
143 metadata.set("CTYPE2", 'DEC--SIN')
144 metadata.setDouble("CD1_1", 5.10808596133527E-05)
145 metadata.setDouble("CD1_2", 1.85579539217196E-07)
146 metadata.setDouble("CD2_2", -5.10281493481982E-05)
147 metadata.setDouble("CD2_1", -8.27440751733828E-07)
148 return afwGeom.makeSkyWcs(metadata)
150 def diffimMetricBasic(self, residual, sourceCat, radius=2, sigma=0.):
151 """Compute a basic metric based on the total number of positive and
152 negative pixels in a residual image.
154 Parameters
155 ----------
156 residual : `lsst.afw.image.ExposureF`
157 A residual image resulting from image differencing.
158 sourceCat : `lsst.afw.table.SourceCatalog`
159 Source catalog containing the locations to calculate the metric.
160 radius : `int`, optional
161 Radius in pixels to use around each source location for the metric.
162 sigma : `float`, optional
163 Threshold to include pixel values in the metric.
165 Returns
166 -------
167 `float`
168 Metric assessing the image differencing residual.
169 """
170 nNeg = 0
171 nPos = 0
172 threshold = sigma*self.computeExposureStddev(residual)
173 for src in sourceCat:
174 srcX = int(src.getX()) - residual.getBBox().getBeginX()
175 srcY = int(src.getY()) - residual.getBBox().getBeginY()
176 srcRes = residual.image.array[srcY - radius: srcY + radius + 1, srcX - radius: srcX + radius + 1]
177 nPos += np.sum(srcRes > threshold)
178 nNeg += np.sum(srcRes < -threshold)
180 if (nPos + nNeg) == 0:
181 metric = 0.
182 else:
183 metric = (nPos - nNeg)/(nPos + nNeg)
184 return metric
186 def computeExposureStddev(self, exposure):
187 """Compute the standard deviation of an exposure, using the mask plane.
189 Parameters
190 ----------
191 exposure : `lsst.afw.image.ExposureF`
192 The input exposure.
194 Returns
195 -------
196 `float`
197 The standard deviation of the unmasked pixels of the input image.
198 """
199 statObj = afwMath.makeStatistics(exposure.maskedImage.image,
200 exposure.maskedImage.mask,
201 afwMath.STDEVCLIP, self.statsCtrl)
202 var = statObj.getValue(afwMath.STDEVCLIP)
203 return var
205 @staticmethod
206 def wrapZogyDiffim(config, templateExposure, scienceExposure):
207 """Prepare and run ZOGY-style image differencing.
209 Parameters
210 ----------
211 config : `lsst.pex.config.Config`
212 The image differencing Task configuration settings.
213 templateExposure : `lsst.afw.image.ExposureF`
214 The reference image to subtract from the science image.
215 scienceExposure : `lsst.afw.image.ExposureF`
216 The science image.
218 Returns
219 -------
220 `lsst.afw.image.ExposureF`
221 The image difference.
222 """
223 config.scaleByCalibration = False
224 zogyTask = ZogyTask(config=config)
226 result = zogyTask.run(scienceExposure, templateExposure)
227 return result.diffExp
229 @staticmethod
230 def wrapAlDiffim(config, templateExposure, scienceExposure, convolveTemplate=True, returnKernel=False,
231 precomputeKernelCandidates=False):
232 """Prepare and run Alard&Lupton-style image differencing.
234 Parameters
235 ----------
236 config : `lsst.pex.config.Config`
237 The image differencing Task configuration settings.
238 templateExposure : `lsst.afw.image.ExposureF`
239 The reference image to subtract from the science image.
240 scienceExposure : `lsst.afw.image.ExposureF`
241 The science image.
242 convolveTemplate : `bool`, optional
243 Option to convolve the template or the science image.
244 returnKernel : `bool`, optional
245 Option to return the residual image or the matching kernel.
247 Returns
248 -------
249 `lsst.afw.image.ExposureF` or `lsst.afw.math.LinearCombinationKernel`
250 The image difference, or the PSF matching kernel.
251 """
252 alTask = ImagePsfMatchTask(config=config)
253 candidateList = None
254 if precomputeKernelCandidates:
255 if convolveTemplate:
256 candidateList = alTask.getSelectSources(scienceExposure.clone())
257 else:
258 candidateList = alTask.getSelectSources(templateExposure.clone())
259 templateFwhmPix = templateExposure.getPsf().getSigma()
260 scienceFwhmPix = scienceExposure.getPsf().getSigma()
261 result = alTask.subtractExposures(templateExposure, scienceExposure,
262 templateFwhmPix=templateFwhmPix,
263 scienceFwhmPix=scienceFwhmPix,
264 doWarping=False,
265 convolveTemplate=convolveTemplate,
266 candidateList=candidateList,
267 )
268 if returnKernel:
269 return result.psfMatchingKernel
270 else:
271 return result.subtractedExposure
274class ImageDifferenceTestVerification(ImageDifferenceTestBase):
276 def testModelImages(self):
277 """Check that the simulated images are useable.
278 """
279 sciPsf = 2.4
280 refPsf = 2.
281 sciNoise = 5.
282 refNoise = 1.5
283 fluxRatio = refPsf**2/sciPsf**2
284 sciIm, src = self.makeTestImages(psfSize=sciPsf, noiseLevel=sciNoise)
285 sciIm2, _ = self.makeTestImages(psfSize=sciPsf, noiseLevel=sciNoise)
286 refIm, _ = self.makeTestImages(psfSize=refPsf, noiseLevel=refNoise)
288 # Making the test images should be repeatable
289 self.assertFloatsAlmostEqual(sciIm.image.array, sciIm2.image.array)
291 diffIm = sciIm.clone()
292 diffIm.image.array -= refIm.image.array
294 # The "reference" image has a smaller PSF but the same source fluxes, so the peak should be greater.
295 self.assertGreater(np.max(refIm.image.array), np.max(sciIm.image.array))
296 # The difference image won't be zero since the two images have different PSFs,
297 # but the peak should be much lower.
298 sciPeak = np.max(sciIm.image.array)
299 residualPeak = np.sqrt(1 - fluxRatio)*sciPeak
300 self.assertGreater(residualPeak, np.max(abs(diffIm.image.array)))
302 # It should be possible to compute the diffim metric from the science and reference images
303 refMetric = self.diffimMetricBasic(refIm, src, sigma=3)
304 sciMetric = self.diffimMetricBasic(sciIm, src, sigma=3)
305 self.assertGreaterEqual(refMetric, -1)
306 self.assertGreaterEqual(sciMetric, -1)
308 def testSimDiffim(self):
309 "Basic smoke test to verify that the test code itself can run."
310 refPsf = 2.4
311 sciPsfBase = 2.
312 sciNoise = 5.
313 refNoise = 1.5
314 seed = 8
315 fluxLevel = 500
316 decorrelate = DecorrelateALKernelTask()
317 zogyConfig = ZogyConfig()
318 alConfig = ImagePsfMatchConfig()
320 for s in range(self.nRandIter):
321 sciPsf = sciPsfBase + s*0.2
322 ref, _ = self.makeTestImages(seed=seed + s, nSrc=20, psfSize=refPsf,
323 noiseLevel=refNoise, fluxLevel=fluxLevel)
324 sci, src = self.makeTestImages(seed=seed + s, nSrc=20, psfSize=sciPsf,
325 noiseLevel=sciNoise, fluxLevel=fluxLevel)
326 # The diffim tasks might modify the images,
327 # so make a deep copy to make sure they are independent
328 sci2 = sci.clone()
329 ref2 = ref.clone()
331 resAl = self.wrapAlDiffim(alConfig, ref, sci)
332 resZogy = self.wrapZogyDiffim(zogyConfig, ref2, sci2)
333 metricZogy = self.diffimMetricBasic(resZogy, src, sigma=3)
334 metricAl = self.diffimMetricBasic(resAl, src, sigma=3)
335 mKernel = self.wrapAlDiffim(alConfig, ref, sci, returnKernel=True)
336 resDecorr = decorrelate.run(sci, ref, resAl, mKernel).correctedExposure
337 metricDecorr = self.diffimMetricBasic(resDecorr, src, sigma=3)
338 self.assertGreaterEqual(metricZogy, -1)
339 self.assertGreaterEqual(metricAl, -1)
340 self.assertGreaterEqual(metricDecorr, -1)
343class ImageDifferenceTestAlardLupton(ImageDifferenceTestBase):
345 def testSimAlRefNotModified(self):
346 "Image differencing should not modify the original template image."
347 refPsf = 2.
348 sciPsfBase = 2.
349 sciNoise = 5.
350 refNoise = 1.5
351 seed = 37
352 fluxLevel = 500
353 rng = np.random.RandomState(seed)
354 alConfig = ImagePsfMatchConfig()
356 sciPsf = sciPsfBase + rng.random()*2.
357 refOriginal, _ = self.makeTestImages(seed=seed, nSrc=20, psfSize=refPsf,
358 noiseLevel=refNoise, fluxLevel=fluxLevel)
359 sciOriginal, src = self.makeTestImages(seed=seed, nSrc=20, psfSize=sciPsf,
360 noiseLevel=sciNoise, fluxLevel=fluxLevel)
361 # Make a deep copy of the images first
362 sciTest1 = sciOriginal.clone()
363 refTest1 = refOriginal.clone()
365 # Basic AL, but we don't care about the result.
366 self.wrapAlDiffim(alConfig, refTest1, sciTest1, convolveTemplate=False)
367 self.assertMaskedImagesEqual(refOriginal.maskedImage, refTest1.maskedImage)
369 # Basic AL, but we don't care about the result.
370 self.wrapAlDiffim(alConfig, refTest1, sciTest1, convolveTemplate=True)
371 self.assertMaskedImagesEqual(refOriginal.maskedImage, refTest1.maskedImage)
373 def testSimAlSciNotModified(self):
374 "Image differencing should not modify the original science image."
375 refPsf = 2.
376 sciPsfBase = 2.
377 sciNoise = 5.
378 refNoise = 1.5
379 seed = 37
380 fluxLevel = 500
381 rng = np.random.RandomState(seed)
382 alConfig = ImagePsfMatchConfig()
384 sciPsf = sciPsfBase + rng.random()*2.
385 refOriginal, _ = self.makeTestImages(seed=seed, nSrc=20, psfSize=refPsf,
386 noiseLevel=refNoise, fluxLevel=fluxLevel)
387 sciOriginal, src = self.makeTestImages(seed=seed, nSrc=20, psfSize=sciPsf,
388 noiseLevel=sciNoise, fluxLevel=fluxLevel)
389 # Make a deep copy of the images first
390 sciTest1 = sciOriginal.clone()
391 refTest1 = refOriginal.clone()
393 # Basic AL, but we don't care about the result.
394 # Note that selecting KernelCandidates *does* change the science image slightly
395 # because a background is subtracted before detection, then added back in.
396 # For this test, we separate out that known modification by precomputing the
397 # kernel candidates in wrapAlDiffim and using a deep copy of the science image.
398 # This test is therefore checking that there are no other, unknown, modifications
399 # of the science image.
400 self.wrapAlDiffim(alConfig, refTest1, sciTest1, convolveTemplate=True,
401 precomputeKernelCandidates=True)
403 self.assertMaskedImagesEqual(sciOriginal.maskedImage, sciTest1.maskedImage)
405 # Basic AL, but we don't care about the result.
406 self.wrapAlDiffim(alConfig, refTest1, sciTest1, convolveTemplate=False,
407 precomputeKernelCandidates=True)
409 self.assertMaskedImagesEqual(sciOriginal.maskedImage, sciTest1.maskedImage)
411 def testSimReverseAlNoDecorrEqualNoise(self):
412 refPsf = 2.
413 sciPsfBase = 2.
414 sciNoise = 5.
415 refNoise = 5
416 seed = 37
417 metricSigma = 0
418 fluxLevel = 500
419 rng = np.random.RandomState(seed)
420 alConfig = ImagePsfMatchConfig()
422 for s in range(self.nRandIter):
423 sciPsf = sciPsfBase + rng.random()*2.
424 ref, _ = self.makeTestImages(seed=seed + s, nSrc=20, psfSize=refPsf,
425 noiseLevel=refNoise, fluxLevel=fluxLevel)
426 sci, src = self.makeTestImages(seed=seed + s, nSrc=20, psfSize=sciPsf,
427 noiseLevel=sciNoise, fluxLevel=fluxLevel)
429 res = self.wrapAlDiffim(alConfig, ref, sci, convolveTemplate=True)
430 resR = self.wrapAlDiffim(alConfig, sci, ref, convolveTemplate=False)
432 metric = self.diffimMetricBasic(res, src, sigma=metricSigma)
433 metricR = self.diffimMetricBasic(resR, src, sigma=metricSigma)
434 # Alard&Lupton is not fully reversable, but the answers should be close.
435 # Partly this needs the decorrelation afterburner
436 # It might also be a difference in background subtraction
437 self.assertFloatsAlmostEqual(metric, -metricR, atol=.1, rtol=.1)
439 def testSimReverseAlNoDecorrUnequalNoise(self):
440 refPsf = 2.
441 sciPsfBase = 2.
442 sciNoise = 5.
443 refNoise = 1.5
444 seed = 37
445 metricSigma = 0
446 fluxLevel = 500
447 rng = np.random.RandomState(seed)
448 alConfig = ImagePsfMatchConfig()
450 for s in range(self.nRandIter):
451 sciPsf = sciPsfBase + rng.random()*2.
452 ref, _ = self.makeTestImages(seed=seed + s, nSrc=20, psfSize=refPsf,
453 noiseLevel=refNoise, fluxLevel=fluxLevel)
454 sci, src = self.makeTestImages(seed=seed + s, nSrc=20, psfSize=sciPsf,
455 noiseLevel=sciNoise, fluxLevel=fluxLevel)
457 res = self.wrapAlDiffim(alConfig, ref, sci, convolveTemplate=True)
458 resR = self.wrapAlDiffim(alConfig, sci, ref, convolveTemplate=False)
460 metric = self.diffimMetricBasic(res, src, sigma=metricSigma)
461 metricR = self.diffimMetricBasic(resR, src, sigma=metricSigma)
462 # Alard&Lupton is not fully reversable, but the answers should be close.
463 # Partly this needs the decorrelation afterburner
464 # It might also be a difference in background subtraction
465 self.assertFloatsAlmostEqual(metric, -metricR, atol=.1, rtol=.1)
468class ImageDifferenceTestZogy(ImageDifferenceTestBase):
470 def testSimZogySciRefNotModified(self):
471 "Image differencing should not modify the original images."
472 refPsf = 2.
473 sciPsfBase = 2.
474 sciNoise = 5.
475 refNoise = 1.5
476 seed = 37
477 fluxLevel = 500
478 rng = np.random.RandomState(seed)
479 zogyConfig = ZogyConfig()
481 sciPsf = sciPsfBase + rng.random()*2.
482 refOriginal, _ = self.makeTestImages(seed=seed, nSrc=20, psfSize=refPsf,
483 noiseLevel=refNoise, fluxLevel=fluxLevel)
484 sciOriginal, src = self.makeTestImages(seed=seed, nSrc=20, psfSize=sciPsf,
485 noiseLevel=sciNoise, fluxLevel=fluxLevel)
486 # Make a deep copy of the images first
487 sciTest1 = sciOriginal.clone()
488 refTest1 = refOriginal.clone()
490 # Basic ZOGY, but we don't care about the result.
491 self.wrapZogyDiffim(zogyConfig, refTest1, sciTest1)
492 self.assertMaskedImagesEqual(refOriginal.maskedImage, refTest1.maskedImage)
493 self.assertMaskedImagesEqual(sciOriginal.maskedImage, sciTest1.maskedImage)
495 def testSimReverseZogy(self):
496 refPsf = 2.
497 sciPsfBase = 2.
498 sciNoise = 5.
499 refNoise = 1.5
500 seed = 18
501 fluxLevel = 500
502 rng = np.random.RandomState(seed)
503 zogyConfig = ZogyConfig()
505 for s in range(self.nRandIter):
506 sciPsf = sciPsfBase + rng.random()*2.
507 ref, _ = self.makeTestImages(seed=seed + s, nSrc=20, psfSize=refPsf,
508 noiseLevel=refNoise, fluxLevel=fluxLevel)
509 sci, src = self.makeTestImages(seed=seed + s, nSrc=20, psfSize=sciPsf,
510 noiseLevel=sciNoise, fluxLevel=fluxLevel)
512 res = self.wrapZogyDiffim(zogyConfig, ref, sci)
513 resR = self.wrapZogyDiffim(zogyConfig, sci, ref)
514 metric = self.diffimMetricBasic(res, src, sigma=3)
515 metricR = self.diffimMetricBasic(resR, src, sigma=3)
516 self.assertFloatsAlmostEqual(metric, -metricR)
519class ImageDifferenceTestDecorrelation(ImageDifferenceTestBase):
521 def testSimAlDecorr(self):
522 refPsf = 2.
523 sciPsfBase = 2.
524 sciNoise = 5.
525 refNoise = 1.5
526 seed = 37
527 metricSigma = 0
528 fluxLevel = 500
529 rng = np.random.RandomState(seed)
530 decorrelateConfig = DecorrelateALKernelConfig()
531 decorrelate = DecorrelateALKernelTask(config=decorrelateConfig)
532 alConfig = ImagePsfMatchConfig()
534 for s in range(self.nRandIter):
535 sciPsf = sciPsfBase + rng.random()*2.
536 ref, _ = self.makeTestImages(seed=seed + s, nSrc=20, psfSize=refPsf,
537 noiseLevel=refNoise, fluxLevel=fluxLevel)
538 sci, src = self.makeTestImages(seed=seed + s, nSrc=20, psfSize=sciPsf,
539 noiseLevel=sciNoise, fluxLevel=fluxLevel)
540 # The diffim tasks can modify the images, so make a deep copy to make sure they are independent
541 sci2 = sci.clone()
542 ref2 = ref.clone()
544 # Basic AL
545 res = self.wrapAlDiffim(alConfig, ref, sci, convolveTemplate=True)
547 # Decorrelated AL
548 mKernel = self.wrapAlDiffim(alConfig, ref, sci, convolveTemplate=True, returnKernel=True)
549 resD = decorrelate.run(sci, ref, res, mKernel).correctedExposure
550 metricD = self.diffimMetricBasic(resD, src, sigma=metricSigma)
552 # Swap the "science" and "reference" images, and alse swap which image is convolved.
553 # The result is that the same image should be convolved as above
554 resR = self.wrapAlDiffim(alConfig, sci2, ref2, convolveTemplate=False)
556 # Swap the images as above, and also decorrelate.
557 mKernelR = self.wrapAlDiffim(alConfig, sci2, ref2, convolveTemplate=False, returnKernel=True)
558 resDR = decorrelate.run(ref2, sci2, resR, mKernelR).correctedExposure
559 metricDR = self.diffimMetricBasic(resDR, src, sigma=metricSigma)
561 self.assertFloatsAlmostEqual(metricD, -metricDR, atol=.1, rtol=0.1)