Coverage for tests/test_psfIO.py: 16%
229 statements
« prev ^ index » next coverage.py v6.5.0, created at 2022-10-18 01:46 -0700
« prev ^ index » next coverage.py v6.5.0, created at 2022-10-18 01:46 -0700
1# This file is part of meas_algorithms.
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 math
24import unittest
25import tempfile
27import numpy as np
29import lsst.utils.tests
30import lsst.geom
31import lsst.afw.image as afwImage
32import lsst.afw.detection as afwDetection
33import lsst.afw.math as afwMath
34import lsst.afw.table as afwTable
35from lsst.log import Log
36import lsst.meas.base as measBase
37import lsst.meas.algorithms as algorithms
39try:
40 type(display)
41except NameError:
42 display = False
43else:
44 import lsst.afw.display as afwDisplay
45 afwDisplay.setDefaultMaskTransparency(75)
47# Change the level to Log.DEBUG or Log.TRACE to see debug messages
48Log.getLogger("lsst.measurement").setLevel(Log.INFO)
51def roundTripPsf(key, psf):
52 with tempfile.NamedTemporaryFile() as f:
53 psf.writeFits(f.name)
54 psf2 = type(psf).readFits(f.name)
56 return psf2
59class SpatialModelPsfTestCase(lsst.utils.tests.TestCase):
60 """A test case for SpatialModelPsf"""
62 def setUp(self):
63 width, height = 100, 300
64 self.mi = afwImage.MaskedImageF(lsst.geom.ExtentI(width, height))
65 self.mi.set(0)
66 self.mi.getVariance().set(10)
67 self.mi.getMask().addMaskPlane("DETECTED")
69 self.FWHM = 5
70 self.ksize = 25 # size of desired kernel
72 self.exposure = afwImage.makeExposure(self.mi)
74 psf = roundTripPsf(2, algorithms.DoubleGaussianPsf(self.ksize, self.ksize,
75 self.FWHM/(2*math.sqrt(2*math.log(2))), 1, 0.1))
76 self.exposure.setPsf(psf)
78 for x, y in [(20, 20),
79 # (30, 35), (50, 50),
80 (60, 20), (60, 210), (20, 210)]:
82 flux = 10000 - 0*x - 10*y
84 sigma = 3 + 0.01*(y - self.mi.getHeight()/2)
85 psf = roundTripPsf(3, algorithms.DoubleGaussianPsf(self.ksize, self.ksize, sigma, 1, 0.1))
86 im = psf.computeImage(psf.getAveragePosition()).convertF()
87 im *= flux
88 x0y0 = lsst.geom.PointI(x - self.ksize//2, y - self.ksize//2)
89 smi = self.mi.getImage().Factory(self.mi.getImage(),
90 lsst.geom.BoxI(x0y0, lsst.geom.ExtentI(self.ksize)),
91 afwImage.LOCAL)
93 if False: # Test subtraction with non-centered psfs
94 im = afwMath.offsetImage(im, 0.5, 0.5)
96 smi += im
97 del psf
98 del im
99 del smi
101 roundTripPsf(4, algorithms.DoubleGaussianPsf(self.ksize, self.ksize,
102 self.FWHM/(2*math.sqrt(2*math.log(2))), 1, 0.1))
104 self.cellSet = afwMath.SpatialCellSet(lsst.geom.BoxI(lsst.geom.PointI(0, 0),
105 lsst.geom.ExtentI(width, height)), 100)
106 ds = afwDetection.FootprintSet(self.mi, afwDetection.Threshold(10), "DETECTED")
107 #
108 # Prepare to measure
109 #
110 schema = afwTable.SourceTable.makeMinimalSchema()
111 sfm_config = measBase.SingleFrameMeasurementConfig()
112 sfm_config.plugins = ["base_SdssCentroid", "base_CircularApertureFlux", "base_PsfFlux",
113 "base_SdssShape", "base_GaussianFlux", "base_PixelFlags"]
114 sfm_config.slots.centroid = "base_SdssCentroid"
115 sfm_config.slots.shape = "base_SdssShape"
116 sfm_config.slots.psfFlux = "base_PsfFlux"
117 sfm_config.slots.gaussianFlux = None
118 sfm_config.slots.apFlux = "base_CircularApertureFlux_3_0"
119 sfm_config.slots.modelFlux = "base_GaussianFlux"
120 sfm_config.slots.calibFlux = None
121 sfm_config.plugins["base_SdssShape"].maxShift = 10.0
122 sfm_config.plugins["base_CircularApertureFlux"].radii = [3.0]
123 task = measBase.SingleFrameMeasurementTask(schema, config=sfm_config)
124 measCat = afwTable.SourceCatalog(schema)
125 # detect the sources and run with the measurement task
126 ds.makeSources(measCat)
127 task.run(measCat, self.exposure)
128 for source in measCat:
129 self.cellSet.insertCandidate(algorithms.makePsfCandidate(source, self.exposure))
131 def tearDown(self):
132 del self.exposure
133 del self.cellSet
134 del self.mi
135 del self.ksize
136 del self.FWHM
138 def testGetPcaKernel(self):
139 """Convert our cellSet to a LinearCombinationKernel"""
141 nEigenComponents = 2
142 spatialOrder = 1
143 kernelSize = 21
144 nStarPerCell = 2
145 nStarPerCellSpatialFit = 2
146 tolerance = 1e-5
148 if display:
149 disp = afwDisplay.Display(frame=0)
150 disp.mtv(self.mi, title=self._testMethodName + ": image")
151 #
152 # Show the candidates we're using
153 #
154 for cell in self.cellSet.getCellList():
155 i = 0
156 for cand in cell:
157 i += 1
158 source = cand.getSource()
159 xc, yc = source.getX() - self.mi.getX0(), source.getY() - self.mi.getY0()
160 if i <= nStarPerCell:
161 disp.dot("o", xc, yc, ctype=afwDisplay.GREEN)
162 else:
163 disp.dot("o", xc, yc, ctype=afwDisplay.YELLOW)
165 pair = algorithms.createKernelFromPsfCandidates(self.cellSet, self.exposure.getDimensions(),
166 self.exposure.getXY0(), nEigenComponents,
167 spatialOrder, kernelSize, nStarPerCell)
169 kernel, eigenValues = pair[0], pair[1]
170 del pair
172 print("lambda", " ".join(["%g" % val for val in eigenValues]))
174 pair = algorithms.fitSpatialKernelFromPsfCandidates(kernel, self.cellSet, nStarPerCellSpatialFit,
175 tolerance)
176 status, chi2 = pair[0], pair[1]
177 del pair
178 print("Spatial fit: %s chi^2 = %.2g" % (status, chi2))
180 psf = roundTripPsf(5, algorithms.PcaPsf(kernel)) # Hurrah!
182 self.assertIsNotNone(psf.getKernel())
184 self.checkTablePersistence(psf)
186 if display:
187 # print psf.getKernel().toString()
189 eImages = []
190 for k in psf.getKernel().getKernelList():
191 im = afwImage.ImageD(k.getDimensions())
192 k.computeImage(im, False)
193 eImages.append(im)
195 mos = afwDisplay.utils.Mosaic()
196 disp = afwDisplay.Display(frame=3)
197 disp.mtv(mos.makeMosaic(eImages), title=self._testMethodName + ": mosaic")
198 disp.dot("Eigen Images", 0, 0)
199 #
200 # Make a mosaic of PSF candidates
201 #
202 stamps = []
203 stampInfo = []
205 for cell in self.cellSet.getCellList():
206 for cand in cell:
207 s = cand.getSource()
208 im = cand.getMaskedImage()
210 stamps.append(im)
211 stampInfo.append("[%d 0x%x]" % (s.getId(), s["base_PixelFlags_flag"]))
213 mos = afwDisplay.utils.Mosaic()
214 disp = afwDisplay.Display(frame=1)
215 disp.mtv(mos.makeMosaic(stamps), title=self._testMethodName + ": PSF candidates")
216 for i in range(len(stampInfo)):
217 disp.dot(stampInfo[i], mos.getBBox(i).getMinX(), mos.getBBox(i).getMinY(),
218 ctype=afwDisplay.RED)
220 psfImages = []
221 labels = []
222 if False:
223 nx, ny = 3, 4
224 for iy in range(ny):
225 for ix in range(nx):
226 x = int((ix + 0.5)*self.mi.getWidth()/nx)
227 y = int((iy + 0.5)*self.mi.getHeight()/ny)
229 im = psf.getImage(x, y)
230 psfImages.append(im.Factory(im, True))
231 labels.append("PSF(%d,%d)" % (int(x), int(y)))
233 if True:
234 print((x, y, "PSF parameters:", psf.getKernel().getKernelParameters()))
235 else:
236 nx, ny = 2, 2
237 for x, y in [(20, 20), (60, 20),
238 (60, 210), (20, 210)]:
240 im = psf.computeImage(lsst.geom.PointD(x, y))
241 psfImages.append(im.Factory(im, True))
242 labels.append("PSF(%d,%d)" % (int(x), int(y)))
244 if True:
245 print(x, y, "PSF parameters:", psf.getKernel().getKernelParameters())
246 mos = afwDisplay.utils.Mosaic()
247 disp = afwDisplay.Display(frame=2)
248 mos.makeMosaic(psfImages, display=disp, mode=nx)
249 mos.drawLabels(labels, display=disp)
251 if display:
252 disp = afwDisplay.Display(frame=0)
253 disp.mtv(self.mi, title=self._testMethodName + ": image")
255 psfImages = []
256 labels = []
257 if False:
258 nx, ny = 3, 4
259 for iy in range(ny):
260 for ix in range(nx):
261 x = int((ix + 0.5)*self.mi.getWidth()/nx)
262 y = int((iy + 0.5)*self.mi.getHeight()/ny)
264 algorithms.subtractPsf(psf, self.mi, x, y)
265 else:
266 nx, ny = 2, 2
267 for x, y in [(20, 20), (60, 20),
268 (60, 210), (20, 210)]:
270 if False: # Test subtraction with non-centered psfs
271 x += 0.5
272 y -= 0.5
274 # algorithms.subtractPsf(psf, self.mi, x, y)
276 afwDisplay.Display(frame=1).mtv(self.mi, title=self._testMethodName + ": image")
278 def testCandidateList(self):
279 if False and display:
280 disp = afwDisplay.Display(frame=0)
281 disp.mtv(self.mi, title=self._testMethodName + ": image")
283 for cell in self.cellSet.getCellList():
284 x0, y0, x1, y1 = (
285 cell.getBBox().getX0(), cell.getBBox().getY0(),
286 cell.getBBox().getX1(), cell.getBBox().getY1())
287 print((x0, y0, " ", x1, y1))
288 x0 -= 0.5
289 y0 -= 0.5
290 x1 += 0.5
291 y1 += 0.5
293 disp.line([(x0, y0), (x1, y0), (x1, y1), (x0, y1), (x0, y0)], ctype=afwDisplay.RED)
295 self.assertFalse(self.cellSet.getCellList()[0].empty())
296 self.assertTrue(self.cellSet.getCellList()[1].empty())
297 self.assertFalse(self.cellSet.getCellList()[2].empty())
299 stamps = []
300 for cell in self.cellSet.getCellList():
301 for cand in cell:
302 cand = cell[0]
303 width, height = 15, 17
304 cand.setWidth(width)
305 cand.setHeight(height)
307 im = cand.getMaskedImage()
308 stamps.append(im)
310 self.assertEqual(im.getWidth(), width)
311 self.assertEqual(im.getHeight(), height)
313 if display:
314 mos = afwDisplay.utils.Mosaic()
315 afwDisplay.Display(frame=1).mtv(mos.makeMosaic(stamps), title=self._testMethodName + ": image")
317 def checkTablePersistence(self, psf1):
318 """Called by testGetPcaKernel to test table-based persistence; it's a pain to
319 build a PcaPsf, so we don't want to repeat it all for each test case.
321 We just verify here that we get a LinearCombinationKernel; all the details of
322 testing that we get the *right* one are tested more thoroughly in afw.
323 """
324 print("Testing PcaPsf!")
325 filename = "PcaPsf.fits"
326 psf1.writeFits(filename)
327 psf2 = algorithms.PcaPsf.readFits(filename)
328 self.assertIsNotNone(psf2)
329 self.assertIsNotNone(psf2.getKernel())
330 os.remove(filename)
333class SingleGaussianPsfTestCase(unittest.TestCase):
335 def testTablePersistence(self):
336 filename = "SingleGaussianPsf.fits"
337 psf1 = algorithms.SingleGaussianPsf(5, 7, 4.2)
338 psf1.writeFits(filename)
339 psf2 = algorithms.SingleGaussianPsf.readFits(filename)
340 self.assertEqual(psf1.getSigma(), psf2.getSigma())
341 os.remove(filename)
344class DoubleGaussianPsfTestCase(unittest.TestCase):
345 """A test case for DoubleGaussianPsf"""
347 def comparePsfs(self, psf1, psf2):
348 self.assertTrue(isinstance(psf1, algorithms.DoubleGaussianPsf))
349 self.assertTrue(isinstance(psf2, algorithms.DoubleGaussianPsf))
350 self.assertEqual(psf1.getKernel().getWidth(), psf2.getKernel().getWidth())
351 self.assertEqual(psf1.getKernel().getHeight(), psf2.getKernel().getHeight())
352 self.assertEqual(psf1.getSigma1(), psf2.getSigma1())
353 self.assertEqual(psf1.getSigma2(), psf2.getSigma2())
354 self.assertEqual(psf1.getB(), psf2.getB())
356 def setUp(self):
357 self.ksize = 25 # size of desired kernel
358 FWHM = 5
359 self.sigma1 = FWHM/(2*np.sqrt(2*np.log(2)))
360 self.sigma2 = 2*self.sigma1
361 self.b = 0.1
363 def tearDown(self):
364 del self.ksize
365 del self.sigma1
366 del self.sigma2
367 del self.b
369 def testBoostPersistence(self):
370 psf1 = algorithms.DoubleGaussianPsf(self.ksize, self.ksize, self.sigma1, self.sigma2, self.b)
371 psf2 = roundTripPsf(1, psf1)
372 psf3 = roundTripPsf(1, psf1)
373 self.comparePsfs(psf1, psf2)
374 self.comparePsfs(psf1, psf3)
376 def testFitsPersistence(self):
377 psf1 = algorithms.DoubleGaussianPsf(self.ksize, self.ksize, self.sigma1, self.sigma2, self.b)
378 filename = "tests/data/psf1-1.fits"
379 psf1.writeFits(filename)
380 psf2 = algorithms.DoubleGaussianPsf.readFits(filename)
381 self.comparePsfs(psf1, psf2)
384class TestMemory(lsst.utils.tests.MemoryTestCase):
385 pass
388def setup_module(module):
389 lsst.utils.tests.init()
392if __name__ == "__main__": 392 ↛ 393line 392 didn't jump to line 393, because the condition on line 392 was never true
393 lsst.utils.tests.init()
394 unittest.main()