Coverage for tests/test_PsfexPsf.py : 16%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1#
2# LSST Data Management System
3#
4# Copyright 2008-2016 AURA/LSST.
5#
6# This product includes software developed by the
7# LSST Project (http://www.lsst.org/).
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 LSST License Statement and
20# the GNU General Public License along with this program. If not,
21# see <https://www.lsstcorp.org/LegalNotices/>.
22#
23import math
24import numpy as np
25import unittest
27import lsst.utils.tests
28import lsst.afw.image as afwImage
29import lsst.afw.detection as afwDetection
30import lsst.afw.geom as afwGeom
31import lsst.geom as geom
32import lsst.afw.math as afwMath
33import lsst.afw.table as afwTable
34import lsst.daf.base as dafBase
35import lsst.meas.algorithms as measAlg
36from lsst.meas.base import SingleFrameMeasurementTask
37# register the PSF determiner
38import lsst.meas.extensions.psfex.psfexPsfDeterminer
39assert lsst.meas.extensions.psfex.psfexPsfDeterminer # make pyflakes happy
41try:
42 display
43except NameError:
44 display = False
45else:
46 import lsst.afw.display as afwDisplay
47 afwDisplay.setDefaultMaskTransparency(75)
50def psfVal(ix, iy, x, y, sigma1, sigma2, b):
51 """Return the value at (ix, iy) of a double Gaussian
52 (N(0, sigma1^2) + b*N(0, sigma2^2))/(1 + b)
53 centered at (x, y)
54 """
55 dx, dy = x - ix, y - iy
56 theta = np.radians(30)
57 ab = 1.0/0.75 # axis ratio
58 c, s = np.cos(theta), np.sin(theta)
59 u, v = c*dx - s*dy, s*dx + c*dy
61 return (math.exp(-0.5*(u**2 + (v*ab)**2)/sigma1**2)
62 + b*math.exp(-0.5*(u**2 + (v*ab)**2)/sigma2**2))/(1 + b)
65class SpatialModelPsfTestCase(unittest.TestCase):
66 """A test case for SpatialModelPsf"""
68 def measure(self, footprintSet, exposure):
69 """Measure a set of Footprints, returning a SourceCatalog"""
70 catalog = afwTable.SourceCatalog(self.schema)
71 if display:
72 afwDisplay.Display(frame=0).mtv(exposure, title="Original")
74 footprintSet.makeSources(catalog)
76 self.measureSources.run(catalog, exposure)
77 return catalog
79 def setUp(self):
80 config = SingleFrameMeasurementTask.ConfigClass()
81 config.slots.apFlux = 'base_CircularApertureFlux_12_0'
82 self.schema = afwTable.SourceTable.makeMinimalSchema()
84 self.measureSources = SingleFrameMeasurementTask(self.schema, config=config)
86 width, height = 110, 301
88 self.mi = afwImage.MaskedImageF(geom.ExtentI(width, height))
89 self.mi.set(0)
90 sd = 3 # standard deviation of image
91 self.mi.getVariance().set(sd*sd)
92 self.mi.getMask().addMaskPlane("DETECTED")
94 self.ksize = 31 # size of desired kernel
96 sigma1 = 1.75
97 sigma2 = 2*sigma1
99 self.exposure = afwImage.makeExposure(self.mi)
100 self.exposure.setPsf(measAlg.DoubleGaussianPsf(self.ksize, self.ksize,
101 1.5*sigma1, 1, 0.1))
102 cdMatrix = np.array([1.0, 0.0, 0.0, 1.0])
103 cdMatrix.shape = (2, 2)
104 wcs = afwGeom.makeSkyWcs(crpix=geom.PointD(0, 0),
105 crval=geom.SpherePoint(0.0, 0.0, geom.degrees),
106 cdMatrix=cdMatrix)
107 self.exposure.setWcs(wcs)
109 #
110 # Make a kernel with the exactly correct basis functions.
111 # Useful for debugging
112 #
113 basisKernelList = []
114 for sigma in (sigma1, sigma2):
115 basisKernel = afwMath.AnalyticKernel(self.ksize, self.ksize,
116 afwMath.GaussianFunction2D(sigma, sigma))
117 basisImage = afwImage.ImageD(basisKernel.getDimensions())
118 basisKernel.computeImage(basisImage, True)
119 basisImage /= np.sum(basisImage.getArray())
121 if sigma == sigma1:
122 basisImage0 = basisImage
123 else:
124 basisImage -= basisImage0
126 basisKernelList.append(afwMath.FixedKernel(basisImage))
128 order = 1 # 1 => up to linear
129 spFunc = afwMath.PolynomialFunction2D(order)
131 exactKernel = afwMath.LinearCombinationKernel(basisKernelList, spFunc)
132 exactKernel.setSpatialParameters([[1.0, 0, 0],
133 [0.0, 0.5*1e-2, 0.2e-2]])
135 rand = afwMath.Random() # make these tests repeatable by setting seed
137 addNoise = True
139 if addNoise:
140 im = self.mi.getImage()
141 afwMath.randomGaussianImage(im, rand) # N(0, 1)
142 im *= sd # N(0, sd^2)
143 del im
145 xarr, yarr = [], []
147 for x, y in [(20, 20), (60, 20),
148 (30, 35),
149 (50, 50),
150 (20, 90), (70, 160), (25, 265), (75, 275), (85, 30),
151 (50, 120), (70, 80),
152 (60, 210), (20, 210),
153 ]:
154 xarr.append(x)
155 yarr.append(y)
157 for x, y in zip(xarr, yarr):
158 dx = rand.uniform() - 0.5 # random (centered) offsets
159 dy = rand.uniform() - 0.5
161 k = exactKernel.getSpatialFunction(1)(x, y) # functional variation of Kernel ...
162 b = (k*sigma1**2/((1 - k)*sigma2**2)) # ... converted double Gaussian's "b"
164 # flux = 80000 - 20*x - 10*(y/float(height))**2
165 flux = 80000*(1 + 0.1*(rand.uniform() - 0.5))
166 I0 = flux*(1 + b)/(2*np.pi*(sigma1**2 + b*sigma2**2))
167 for iy in range(y - self.ksize//2, y + self.ksize//2 + 1):
168 if iy < 0 or iy >= self.mi.getHeight():
169 continue
171 for ix in range(x - self.ksize//2, x + self.ksize//2 + 1):
172 if ix < 0 or ix >= self.mi.getWidth():
173 continue
175 II = I0*psfVal(ix, iy, x + dx, y + dy, sigma1, sigma2, b)
176 Isample = rand.poisson(II) if addNoise else II
177 self.mi.image[ix, iy, afwImage.LOCAL] += Isample
178 self.mi.variance[ix, iy, afwImage.LOCAL] += II
180 bbox = geom.BoxI(geom.PointI(0, 0), geom.ExtentI(width, height))
181 self.cellSet = afwMath.SpatialCellSet(bbox, 100)
183 self.footprintSet = afwDetection.FootprintSet(self.mi, afwDetection.Threshold(100), "DETECTED")
185 self.catalog = self.measure(self.footprintSet, self.exposure)
187 for source in self.catalog:
188 try:
189 cand = measAlg.makePsfCandidate(source, self.exposure)
190 self.cellSet.insertCandidate(cand)
192 except Exception as e:
193 print(e)
194 continue
196 def tearDown(self):
197 del self.cellSet
198 del self.exposure
199 del self.mi
200 del self.footprintSet
201 del self.catalog
202 del self.schema
203 del self.measureSources
205 def setupDeterminer(self, exposure):
206 """Setup the starSelector and psfDeterminer"""
207 starSelectorClass = measAlg.sourceSelectorRegistry["objectSize"]
208 starSelectorConfig = starSelectorClass.ConfigClass()
209 starSelectorConfig.sourceFluxField = "base_GaussianFlux_instFlux"
210 starSelectorConfig.badFlags = ["base_PixelFlags_flag_edge",
211 "base_PixelFlags_flag_interpolatedCenter",
212 "base_PixelFlags_flag_saturatedCenter",
213 "base_PixelFlags_flag_crCenter",
214 ]
215 starSelectorConfig.widthStdAllowed = 0.5 # Set to match when the tolerance of the test was set
217 self.starSelector = starSelectorClass(config=starSelectorConfig)
219 self.makePsfCandidates = measAlg.MakePsfCandidatesTask()
221 psfDeterminerClass = measAlg.psfDeterminerRegistry["psfex"]
222 psfDeterminerConfig = psfDeterminerClass.ConfigClass()
223 width, height = exposure.getMaskedImage().getDimensions()
224 psfDeterminerConfig.sizeCellX = width
225 psfDeterminerConfig.sizeCellY = height//3
226 psfDeterminerConfig.spatialOrder = 1
228 self.psfDeterminer = psfDeterminerClass(psfDeterminerConfig)
230 def subtractStars(self, exposure, catalog, chi_lim=-1):
231 """Subtract the exposure's PSF from all the sources in catalog"""
232 mi, psf = exposure.getMaskedImage(), exposure.getPsf()
234 subtracted = mi.Factory(mi, True)
235 for s in catalog:
236 xc, yc = s.getX(), s.getY()
237 bbox = subtracted.getBBox(afwImage.PARENT)
238 if bbox.contains(geom.PointI(int(xc), int(yc))):
239 try:
240 measAlg.subtractPsf(psf, subtracted, xc, yc)
241 except Exception:
242 pass
243 chi = subtracted.Factory(subtracted, True)
244 var = subtracted.getVariance()
245 np.sqrt(var.getArray(), var.getArray()) # inplace sqrt
246 chi /= var
248 if display:
249 afwDisplay.Display(frame=1).mtv(subtracted, title="Subtracted")
250 afwDisplay.Display(frame=2).mtv(chi, title="Chi")
251 xc, yc = exposure.getWidth()//2, exposure.getHeight()//2
252 afwDisplay.Display(frame=3).mtv(psf.computeImage(geom.Point2D(xc, yc)),
253 title="Psf %.1f,%.1f" % (xc, yc))
255 chi_min, chi_max = np.min(chi.getImage().getArray()), np.max(chi.getImage().getArray())
256 if False:
257 print(chi_min, chi_max)
259 if chi_lim > 0:
260 self.assertGreater(chi_min, -chi_lim)
261 self.assertLess(chi_max, chi_lim)
263 def testPsfexDeterminer(self):
264 """Test the (Psfex) psfDeterminer on subImages"""
266 self.setupDeterminer(self.exposure)
267 metadata = dafBase.PropertyList()
269 stars = self.starSelector.run(self.catalog, exposure=self.exposure)
270 psfCandidateList = self.makePsfCandidates.run(stars.sourceCat, exposure=self.exposure).psfCandidates
271 psf, cellSet = self.psfDeterminer.determinePsf(self.exposure, psfCandidateList, metadata)
272 self.exposure.setPsf(psf)
274 # Test how well we can subtract the PSF model
275 self.subtractStars(self.exposure, self.catalog, chi_lim=5.6)
277 # Test PsfexPsf.computeBBox
278 self.assertEqual(psf.computeBBox(), psf.computeKernelImage().getBBox())
279 self.assertEqual(psf.computeBBox(), psf.getKernel().getBBox())
282class TestMemory(lsst.utils.tests.MemoryTestCase):
283 pass
286def setup_module(module):
287 lsst.utils.tests.init()
290if __name__ == "__main__": 290 ↛ 291line 290 didn't jump to line 291, because the condition on line 290 was never true
291 lsst.utils.tests.init()
292 unittest.main()