Coverage for tests/test_kernelCandidateAndSolution.py: 17%
329 statements
« prev ^ index » next coverage.py v7.4.3, created at 2024-03-14 11:41 -0700
« prev ^ index » next coverage.py v7.4.3, created at 2024-03-14 11:41 -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 os
23import unittest
25import lsst.utils.tests
26import lsst.utils
27import lsst.afw.image as afwImage
28import lsst.afw.math as afwMath
29import lsst.geom as geom
30import lsst.ip.diffim as ipDiffim
31import lsst.pex.config as pexConfig
32import lsst.utils.logging as logUtils
33import lsst.afw.table as afwTable
35logUtils.trace_set_at("lsst.ip.diffim", 4)
37# known input images
38try:
39 defDataDir = lsst.utils.getPackageDir('afwdata')
40except Exception:
41 defDataDir = None
43try:
44 display
45 defDataDir
46except NameError:
47 display = False
48else:
49 import lsst.afw.display as afwDisplay
50 afwDisplay.setDefaultMaskTransparency(75)
53class DiffimTestCases(lsst.utils.tests.TestCase):
55 def setUp(self):
56 schema = afwTable.SourceTable.makeMinimalSchema()
57 afwTable.Point2DKey.addFields(schema, "Centroid", "input centroid", "pixel")
58 schema.addField("PsfFlux_instFlux", type=float)
59 schema.addField("PsfFlux_instFluxErr", type=float)
60 schema.addField("PsfFlux_flag", type="Flag")
61 self.table = afwTable.SourceTable.make(schema)
62 self.table.definePsfFlux("PsfFlux")
63 self.table.defineCentroid("Centroid")
64 self.ss = afwTable.SourceCatalog(self.table)
66 self.config = ipDiffim.PsfMatchConfigDF()
68 self.ps = pexConfig.makePropertySet(self.config)
69 self.ps['fitForBackground'] = True # we are testing known background recovery here
70 self.ps['checkConditionNumber'] = False # just in case
71 self.ps["useRegularization"] = False
73 if defDataDir:
74 defSciencePath = os.path.join(defDataDir, "DC3a-Sim", "sci", "v26-e0",
75 "v26-e0-c011-a10.sci.fits")
76 defTemplatePath = os.path.join(defDataDir, "DC3a-Sim", "sci", "v5-e0",
77 "v5-e0-c011-a10.sci.fits")
79 scienceExposure = afwImage.ExposureF(defSciencePath)
80 templateExposure = afwImage.ExposureF(defTemplatePath)
81 # set XY0 = 0
82 scienceExposure.setXY0(geom.Point2I(0, 0))
83 templateExposure.setXY0(geom.Point2I(0, 0))
84 # do the warping first so we don't have any masked pixels in the postage stamps
85 warper = afwMath.Warper.fromConfig(self.config.warpingConfig)
86 templateExposure = warper.warpExposure(scienceExposure.getWcs(), templateExposure,
87 destBBox=scienceExposure.getBBox())
89 # Change xy0
90 # Nice star at position 276, 717
91 # And should be at index 40, 40
92 # No masked pixels in this one
93 self.x02 = 276
94 self.y02 = 717
95 size = 40
96 bbox2 = geom.Box2I(geom.Point2I(self.x02 - size, self.y02 - size),
97 geom.Point2I(self.x02 + size, self.y02 + size))
98 self.scienceImage2 = afwImage.ExposureF(scienceExposure, bbox2, origin=afwImage.LOCAL)
99 self.templateExposure2 = afwImage.ExposureF(templateExposure, bbox2, origin=afwImage.LOCAL)
101 def addNoise(self, mi):
102 img = mi.image
103 seed = int(afwMath.makeStatistics(mi.getVariance(), afwMath.MEDIAN).getValue())
104 rdm = afwMath.Random(afwMath.Random.MT19937, seed)
105 rdmImage = img.Factory(img.getDimensions())
106 afwMath.randomGaussianImage(rdmImage, rdm)
107 img += rdmImage
108 return afwMath.makeStatistics(rdmImage, afwMath.MEAN).getValue(afwMath.MEAN)
110 def verifyDeltaFunctionSolution(self, solution, kSum=1.0, bg=0.0):
111 # when kSum = 1.0, this agrees to the default precision. when
112 # kSum != 1.0 I need to go to only 4 digits.
113 #
114 # -5.4640810225678728e-06 != 0.0 within 7 places
115 #
116 bgSolution = solution.getBackground()
117 self.assertAlmostEqual(bgSolution, bg, 4)
119 # again when kSum = 1.0 this agrees. otherwise
120 #
121 # 2.7000000605594079 != 2.7000000000000002 within 7 places
122 #
123 kSumSolution = solution.getKsum()
124 self.assertAlmostEqual(kSumSolution, kSum, 5)
126 kImage = solution.makeKernelImage()
127 for j in range(kImage.getHeight()):
128 for i in range(kImage.getWidth()):
130 if (i == kImage.getWidth() // 2) and (j == kImage.getHeight() // 2):
131 self.assertAlmostEqual(kImage[i, j, afwImage.LOCAL], kSum, 5)
132 else:
133 self.assertAlmostEqual(kImage[i, j, afwImage.LOCAL], 0., 5)
135 @unittest.skipIf(not defDataDir, "Warning: afwdata is not set up")
136 def testConstructor(self):
137 # Original and uninitialized
138 kc = ipDiffim.KernelCandidateF(self.x02, self.y02,
139 self.templateExposure2.getMaskedImage(),
140 self.scienceImage2.getMaskedImage(),
141 self.ps)
143 # Kernel not initialized
144 self.assertEqual(kc.isInitialized(), False)
146 # But this should be set on construction
147 try:
148 kc.getCandidateRating()
149 except Exception as e:
150 print(e)
151 self.fail()
153 # And these should be filled
154 try:
155 kc.getTemplateMaskedImage()
156 kc.getScienceMaskedImage()
157 except Exception as e:
158 print(e)
159 self.fail()
161 # And of the right type
162 self.assertEqual(type(kc.getTemplateMaskedImage()), afwImage.MaskedImageF)
163 self.assertEqual(type(kc.getScienceMaskedImage()), afwImage.MaskedImageF)
165 # None of these should work
166 for kType in (ipDiffim.KernelCandidateF.ORIG,
167 ipDiffim.KernelCandidateF.PCA,
168 ipDiffim.KernelCandidateF.RECENT):
169 for kMethod in (kc.getKernelSolution,
170 kc.getKernel,
171 kc.getBackground,
172 kc.getKsum,
173 kc.getKernelImage,
174 kc.getDifferenceImage):
175 try:
176 kMethod(kType)
177 except Exception:
178 pass
179 else:
180 self.fail()
181 try:
182 kc.getImage()
183 except Exception:
184 pass
185 else:
186 self.fail()
188 @unittest.skipIf(not defDataDir, "Warning: afwdata is not set up")
189 def testSourceStats(self):
190 source = self.ss.addNew()
191 source.setId(1)
192 source.set(self.table.getCentroidSlot().getMeasKey().getX(), 276)
193 source.set(self.table.getCentroidSlot().getMeasKey().getY(), 717)
194 source.set("slot_PsfFlux_instFlux", 1.)
196 kc = ipDiffim.KernelCandidateF(source,
197 self.templateExposure2.getMaskedImage(),
198 self.scienceImage2.getMaskedImage(),
199 self.ps)
200 kList = ipDiffim.makeKernelBasisList(self.config)
202 kc.build(kList)
203 self.assertEqual(kc.isInitialized(), True)
205 @unittest.skipIf(not defDataDir, "Warning: afwdata is not set up")
206 def testSourceConstructor(self):
207 source = self.ss.addNew()
208 source.setId(1)
209 source.set(self.table.getCentroidSlot().getMeasKey().getX(), 276)
210 source.set(self.table.getCentroidSlot().getMeasKey().getY(), 717)
211 source.set("slot_PsfFlux_instFlux", 1.)
213 kc = ipDiffim.KernelCandidateF(source,
214 self.templateExposure2.getMaskedImage(),
215 self.scienceImage2.getMaskedImage(),
216 self.ps)
218 # Kernel not initialized
219 self.assertEqual(kc.isInitialized(), False)
221 # Check that the source is set
222 self.assertEqual(kc.getSource(), source)
223 self.assertEqual(kc.getCandidateRating(), source.getPsfInstFlux())
225 # But this should be set on construction
226 try:
227 kc.getCandidateRating()
228 except Exception as e:
229 print(e)
230 self.fail()
232 # And these should be filled
233 try:
234 kc.getTemplateMaskedImage()
235 kc.getScienceMaskedImage()
236 except Exception as e:
237 print(e)
238 self.fail()
240 # And of the right type
241 self.assertEqual(type(kc.getTemplateMaskedImage()), afwImage.MaskedImageF)
242 self.assertEqual(type(kc.getScienceMaskedImage()), afwImage.MaskedImageF)
244 # None of these should work
245 for kType in (ipDiffim.KernelCandidateF.ORIG,
246 ipDiffim.KernelCandidateF.PCA,
247 ipDiffim.KernelCandidateF.RECENT):
248 for kMethod in (kc.getKernelSolution,
249 kc.getKernel,
250 kc.getBackground,
251 kc.getKsum,
252 kc.getKernelImage,
253 kc.getDifferenceImage):
254 try:
255 kMethod(kType)
256 except Exception:
257 pass
258 else:
259 self.fail()
260 try:
261 kc.getImage()
262 except Exception:
263 pass
264 else:
265 self.fail()
267 kList = ipDiffim.makeKernelBasisList(self.config)
269 kc.build(kList)
270 self.assertEqual(kc.isInitialized(), True)
272 @unittest.skipIf(not defDataDir, "Warning: afwdata is not set up")
273 def testDeltaFunctionScaled(self, scaling=2.7, bg=11.3):
274 sIm = afwImage.MaskedImageF(self.templateExposure2.getMaskedImage(), deep=True)
275 sIm *= scaling
276 kc = ipDiffim.KernelCandidateF(self.x02, self.y02,
277 self.templateExposure2.getMaskedImage(),
278 sIm,
279 self.ps)
281 kList = ipDiffim.makeKernelBasisList(self.config)
282 kc.build(kList)
283 self.verifyDeltaFunctionSolution(kc.getKernelSolution(ipDiffim.KernelCandidateF.RECENT),
284 kSum=scaling)
286 sIm = afwImage.MaskedImageF(self.templateExposure2.getMaskedImage(), deep=True)
287 sIm += bg
288 kc = ipDiffim.KernelCandidateF(self.x02, self.y02,
289 self.templateExposure2.getMaskedImage(),
290 sIm,
291 self.ps)
293 kList = ipDiffim.makeKernelBasisList(self.config)
294 kc.build(kList)
295 self.verifyDeltaFunctionSolution(kc.getKernelSolution(ipDiffim.KernelCandidateF.RECENT),
296 bg=bg)
298 @unittest.skipIf(not defDataDir, "Warning: afwdata is not set up")
299 def testDeltaFunction(self):
300 # Match an image to itself, with delta-function basis set
301 # No regularization
302 kc = ipDiffim.KernelCandidateF(self.x02, self.y02,
303 self.templateExposure2.getMaskedImage(),
304 self.templateExposure2.getMaskedImage(),
305 self.ps)
307 kList = ipDiffim.makeKernelBasisList(self.config)
309 kc.build(kList)
310 self.assertEqual(kc.isInitialized(), True)
312 # These should work
313 for kType in (ipDiffim.KernelCandidateF.ORIG,
314 ipDiffim.KernelCandidateF.RECENT):
315 for kMethod in (kc.getKernelSolution,
316 kc.getKernel,
317 kc.getBackground,
318 kc.getKsum,
319 kc.getKernelImage,
320 kc.getDifferenceImage):
321 try:
322 kMethod(kType)
323 except Exception as e:
324 print(kMethod, e)
325 self.fail()
326 else:
327 pass
328 try:
329 kc.getImage()
330 except Exception as e:
331 print(kMethod, e)
332 self.fail()
333 else:
334 pass
336 # None of these should work
337 for kType in (ipDiffim.KernelCandidateF.PCA,):
338 for kMethod in (kc.getKernelSolution,
339 kc.getKernel,
340 kc.getBackground,
341 kc.getKsum,
342 kc.getKernelImage,
343 kc.getImage,
344 kc.getDifferenceImage):
345 try:
346 kMethod(kType)
347 except Exception:
348 pass
349 else:
350 print(kMethod)
351 self.fail()
353 self.verifyDeltaFunctionSolution(kc.getKernelSolution(ipDiffim.KernelCandidateF.RECENT))
355 @unittest.skipIf(not defDataDir, "Warning: afwdata is not set up")
356 def testGaussianWithNoise(self):
357 # Convolve a real image with a gaussian and try and recover
358 # it. Add noise and perform the same test.
360 gsize = self.ps["kernelSize"]
361 gaussFunction = afwMath.GaussianFunction2D(2, 3)
362 gaussKernel = afwMath.AnalyticKernel(gsize, gsize, gaussFunction)
363 kImageIn = afwImage.ImageD(geom.Extent2I(gsize, gsize))
364 kSumIn = gaussKernel.computeImage(kImageIn, False)
366 imX, imY = self.templateExposure2.getMaskedImage().getDimensions()
367 smi = afwImage.MaskedImageF(geom.Extent2I(imX, imY))
369 convolutionControl = afwMath.ConvolutionControl()
370 convolutionControl.setDoNormalize(False)
371 afwMath.convolve(smi, self.templateExposure2.getMaskedImage(), gaussKernel, convolutionControl)
373 bbox = gaussKernel.shrinkBBox(smi.getBBox(afwImage.LOCAL))
375 tmi2 = afwImage.MaskedImageF(self.templateExposure2.getMaskedImage(), bbox, origin=afwImage.LOCAL)
376 smi2 = afwImage.MaskedImageF(smi, bbox, origin=afwImage.LOCAL)
378 kc = ipDiffim.KernelCandidateF(self.x02, self.y02, tmi2, smi2, self.ps)
379 kList = ipDiffim.makeKernelBasisList(self.config)
380 kc.build(kList)
381 self.assertEqual(kc.isInitialized(), True)
382 kImageOut = kc.getImage()
384 soln = kc.getKernelSolution(ipDiffim.KernelCandidateF.RECENT)
385 self.assertAlmostEqual(soln.getKsum(), kSumIn)
386 # 8.7499380640430563e-06 != 0.0 within 7 places
387 self.assertAlmostEqual(soln.getBackground(), 0.0, 4)
389 for j in range(kImageOut.getHeight()):
390 for i in range(kImageOut.getWidth()):
392 # in the outskirts of the kernel, the ratio can get screwed because of low S/N
393 # e.g. 7.45817359824e-09 vs. 1.18062529402e-08
394 # in the guts of the kernel it should look closer
395 if kImageIn[i, j, afwImage.LOCAL] > 1e-4:
396 # sigh, too bad this sort of thing fails..
397 # 0.99941584433815966 != 1.0 within 3 places
398 self.assertAlmostEqual(kImageOut[i, j, afwImage.LOCAL]/kImageIn[i, j, afwImage.LOCAL],
399 1.0, 2)
401 # now repeat with noise added; decrease precision of comparison
402 self.addNoise(smi2)
403 kc = ipDiffim.KernelCandidateF(self.x02, self.y02, tmi2, smi2, self.ps)
404 kList = ipDiffim.makeKernelBasisList(self.config)
405 kc.build(kList)
406 self.assertEqual(kc.isInitialized(), True)
407 kImageOut = kc.getImage()
409 soln = kc.getKernelSolution(ipDiffim.KernelCandidateF.RECENT)
410 self.assertAlmostEqual(soln.getKsum(), kSumIn, 3)
411 if not self.ps.get("fitForBackground"):
412 self.assertEqual(soln.getBackground(), 0.0)
414 for j in range(kImageOut.getHeight()):
415 for i in range(kImageOut.getWidth()):
416 if kImageIn[i, j, afwImage.LOCAL] > 1e-2:
417 self.assertAlmostEqual(kImageOut[i, j, afwImage.LOCAL],
418 kImageIn[i, j, afwImage.LOCAL], 2)
420 def testGaussian(self, imsize=50):
421 # Convolve a delta function with a known gaussian; try to
422 # recover using delta-function basis
424 gsize = self.ps["kernelSize"]
425 tsize = imsize + gsize
427 gaussFunction = afwMath.GaussianFunction2D(2, 3)
428 gaussKernel = afwMath.AnalyticKernel(gsize, gsize, gaussFunction)
429 kImageIn = afwImage.ImageD(geom.Extent2I(gsize, gsize))
430 gaussKernel.computeImage(kImageIn, False)
432 # template image with a single hot pixel in the exact center
433 tmi = afwImage.MaskedImageF(geom.Extent2I(tsize, tsize))
434 tmi.set(0, 0x0, 1e-4)
435 cpix = tsize // 2
436 tmi[cpix, cpix, afwImage.LOCAL] = (1, 0x0, 1)
438 # science image
439 smi = afwImage.MaskedImageF(tmi.getDimensions())
440 convolutionControl = afwMath.ConvolutionControl()
441 convolutionControl.setDoNormalize(False)
442 afwMath.convolve(smi, tmi, gaussKernel, convolutionControl)
444 # get the actual kernel sum (since the image is not infinite)
445 gscaling = afwMath.makeStatistics(smi, afwMath.SUM).getValue(afwMath.SUM)
447 # grab only the non-masked subregion
448 bbox = gaussKernel.shrinkBBox(smi.getBBox(afwImage.LOCAL))
450 tmi2 = afwImage.MaskedImageF(tmi, bbox, origin=afwImage.LOCAL)
451 smi2 = afwImage.MaskedImageF(smi, bbox, origin=afwImage.LOCAL)
453 # make sure its a valid subregion!
454 for j in range(tmi2.getHeight()):
455 for i in range(tmi2.getWidth()):
456 self.assertEqual(tmi2.mask[i, j, afwImage.LOCAL], 0)
457 self.assertEqual(smi2.mask[i, j, afwImage.LOCAL], 0)
459 kc = ipDiffim.KernelCandidateF(0.0, 0.0, tmi2, smi2, self.ps)
460 kList = ipDiffim.makeKernelBasisList(self.config)
461 kc.build(kList)
462 self.assertEqual(kc.isInitialized(), True)
463 kImageOut = kc.getImage()
465 soln = kc.getKernelSolution(ipDiffim.KernelCandidateF.RECENT)
466 self.assertAlmostEqual(soln.getKsum(), gscaling)
467 self.assertAlmostEqual(soln.getBackground(), 0.0)
469 for j in range(kImageOut.getHeight()):
470 for i in range(kImageOut.getWidth()):
471 self.assertAlmostEqual(kImageOut[i, j, afwImage.LOCAL]/kImageIn[i, j, afwImage.LOCAL],
472 1.0, 5)
474 def testZeroVariance(self, imsize=50):
475 gsize = self.ps["kernelSize"]
476 tsize = imsize + gsize
478 tmi = afwImage.MaskedImageF(geom.Extent2I(tsize, tsize))
479 tmi.set(0, 0x0, 1.0)
480 cpix = tsize // 2
481 tmi[cpix, cpix, afwImage.LOCAL] = (1, 0x0, 0.0)
482 smi = afwImage.MaskedImageF(geom.Extent2I(tsize, tsize))
483 smi.set(0, 0x0, 1.0)
484 smi[cpix, cpix, afwImage.LOCAL] = (1, 0x0, 0.0)
486 kList = ipDiffim.makeKernelBasisList(self.config)
487 self.ps["constantVarianceWeighting"] = False
488 kc = ipDiffim.KernelCandidateF(0.0, 0.0, tmi, smi, self.ps)
489 try:
490 kc.build(kList)
491 except Exception:
492 pass
493 else:
494 self.fail()
496 @unittest.skipIf(not defDataDir, "Warning: afwdata is not set up")
497 def testConstantWeighting(self):
498 self.ps["fitForBackground"] = False
499 self.testGaussian()
500 self.testGaussianWithNoise()
502 @unittest.skipIf(not defDataDir, "Warning: afwdata is not set up")
503 def testNoBackgroundFit(self):
504 self.ps["constantVarianceWeighting"] = True
505 self.testGaussian()
507 def testInsert(self):
508 mi = afwImage.MaskedImageF(geom.Extent2I(10, 10))
509 kc = ipDiffim.makeKernelCandidate(0., 0., mi, mi, self.ps)
510 kc.setStatus(afwMath.SpatialCellCandidate.GOOD)
512 sizeCellX = self.ps["sizeCellX"]
513 sizeCellY = self.ps["sizeCellY"]
514 kernelCellSet = afwMath.SpatialCellSet(geom.Box2I(geom.Point2I(0, 0), geom.Extent2I(1, 1)),
515 sizeCellX, sizeCellY)
516 kernelCellSet.insertCandidate(kc)
517 nSeen = 0
518 for cell in kernelCellSet.getCellList():
519 for cand in cell.begin(True):
520 self.assertEqual(cand.getStatus(), afwMath.SpatialCellCandidate.GOOD)
521 nSeen += 1
522 self.assertEqual(nSeen, 1)
524 @unittest.skipIf(not display, "display is None: skipping testDisp")
525 def testDisp(self):
526 afwDisplay.Display(frame=1).mtv(self.scienceImage2,
527 title=self._testMethodName + ": scienceImage2")
528 afwDisplay.Display(frame=2).mtv(self.templateExposure2,
529 title=self._testMethodName + ": templateExposure2")
531 def tearDown(self):
532 del self.ps
533 del self.table
534 del self.ss
535 if defDataDir:
536 del self.scienceImage2
537 del self.templateExposure2
539#####
542class TestMemory(lsst.utils.tests.MemoryTestCase):
543 pass
546def setup_module(module):
547 lsst.utils.tests.init()
550if __name__ == "__main__": 550 ↛ 551line 550 didn't jump to line 551, because the condition on line 550 was never true
551 lsst.utils.tests.init()
552 unittest.main()