Coverage for tests/test_psf.py : 14%

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
1import unittest
2import numpy as np
3import copy
5import lsst.utils.tests
6import lsst.afw.detection as afwDetection
7import lsst.afw.geom as afwGeom
8import lsst.afw.image as afwImage
9import lsst.afw.math as afwMath
10import lsst.afw.table as afwTable
11import lsst.daf.base as dafBase
12import lsst.geom as geom
13import lsst.meas.algorithms as measAlg
14from lsst.meas.base import SingleFrameMeasurementTask
15import lsst.meas.extensions.piff.piffPsfDeterminer # noqa
18def psfVal(ix, iy, x, y, sigma1, sigma2, b):
19 """Return the value at (ix, iy) of a double Gaussian
20 (N(0, sigma1^2) + b*N(0, sigma2^2))/(1 + b)
21 centered at (x, y)
22 """
23 dx, dy = x - ix, y - iy
24 theta = np.radians(30)
25 ab = 1.0/0.75 # axis ratio
26 c, s = np.cos(theta), np.sin(theta)
27 u, v = c*dx - s*dy, s*dx + c*dy
29 return (np.exp(-0.5*(u**2 + (v*ab)**2)/sigma1**2)
30 + b*np.exp(-0.5*(u**2 + (v*ab)**2)/sigma2**2))/(1 + b)
33class SpatialModelPsfTestCase(lsst.utils.tests.TestCase):
34 """A test case for SpatialModelPsf"""
36 def measure(self, footprintSet, exposure):
37 """Measure a set of Footprints, returning a SourceCatalog"""
38 catalog = afwTable.SourceCatalog(self.schema)
40 footprintSet.makeSources(catalog)
42 self.measureSources.run(catalog, exposure)
43 return catalog
45 def setUp(self):
46 config = SingleFrameMeasurementTask.ConfigClass()
47 config.slots.apFlux = 'base_CircularApertureFlux_12_0'
48 self.schema = afwTable.SourceTable.makeMinimalSchema()
50 self.measureSources = SingleFrameMeasurementTask(
51 self.schema, config=config
52 )
53 self.usePsfFlag = self.schema.addField("use_psf", type="Flag")
55 width, height = 110, 301
57 self.mi = afwImage.MaskedImageF(geom.ExtentI(width, height))
58 self.mi.set(0)
59 sd = 3 # standard deviation of image
60 self.mi.getVariance().set(sd*sd)
61 self.mi.getMask().addMaskPlane("DETECTED")
63 self.ksize = 31 # size of desired kernel
65 sigma1 = 1.75
66 sigma2 = 2*sigma1
68 self.exposure = afwImage.makeExposure(self.mi)
69 self.exposure.setPsf(measAlg.DoubleGaussianPsf(self.ksize, self.ksize,
70 1.5*sigma1, 1, 0.1))
71 cdMatrix = np.array([1.0, 0.0, 0.0, 1.0])
72 cdMatrix.shape = (2, 2)
73 wcs = afwGeom.makeSkyWcs(crpix=geom.PointD(0, 0),
74 crval=geom.SpherePoint(0.0, 0.0, geom.degrees),
75 cdMatrix=cdMatrix)
76 self.exposure.setWcs(wcs)
78 #
79 # Make a kernel with the exactly correct basis functions.
80 # Useful for debugging
81 #
82 basisKernelList = []
83 for sigma in (sigma1, sigma2):
84 basisKernel = afwMath.AnalyticKernel(
85 self.ksize, self.ksize, afwMath.GaussianFunction2D(sigma, sigma)
86 )
87 basisImage = afwImage.ImageD(basisKernel.getDimensions())
88 basisKernel.computeImage(basisImage, True)
89 basisImage /= np.sum(basisImage.getArray())
91 if sigma == sigma1:
92 basisImage0 = basisImage
93 else:
94 basisImage -= basisImage0
96 basisKernelList.append(afwMath.FixedKernel(basisImage))
98 order = 1 # 1 => up to linear
99 spFunc = afwMath.PolynomialFunction2D(order)
101 exactKernel = afwMath.LinearCombinationKernel(basisKernelList, spFunc)
102 exactKernel.setSpatialParameters(
103 [[1.0, 0, 0],
104 [0.0, 0.5*1e-2, 0.2e-2]]
105 )
107 rand = afwMath.Random() # make these tests repeatable by setting seed
109 im = self.mi.getImage()
110 afwMath.randomGaussianImage(im, rand) # N(0, 1)
111 im *= sd # N(0, sd^2)
113 xarr, yarr = [], []
115 for x, y in [(20, 20), (60, 20),
116 (30, 35),
117 (50, 50),
118 (20, 90), (70, 160), (25, 265), (75, 275), (85, 30),
119 (50, 120), (70, 80),
120 (60, 210), (20, 210),
121 ]:
122 xarr.append(x)
123 yarr.append(y)
125 for x, y in zip(xarr, yarr):
126 dx = rand.uniform() - 0.5 # random (centered) offsets
127 dy = rand.uniform() - 0.5
129 k = exactKernel.getSpatialFunction(1)(x, y)
130 b = (k*sigma1**2/((1 - k)*sigma2**2))
132 flux = 80000*(1 + 0.1*(rand.uniform() - 0.5))
133 I0 = flux*(1 + b)/(2*np.pi*(sigma1**2 + b*sigma2**2))
134 for iy in range(y - self.ksize//2, y + self.ksize//2 + 1):
135 if iy < 0 or iy >= self.mi.getHeight():
136 continue
138 for ix in range(x - self.ksize//2, x + self.ksize//2 + 1):
139 if ix < 0 or ix >= self.mi.getWidth():
140 continue
142 II = I0*psfVal(ix, iy, x + dx, y + dy, sigma1, sigma2, b)
143 Isample = rand.poisson(II)
144 self.mi.image[ix, iy, afwImage.LOCAL] += Isample
145 self.mi.variance[ix, iy, afwImage.LOCAL] += II
147 bbox = geom.BoxI(geom.PointI(0, 0), geom.ExtentI(width, height))
148 self.cellSet = afwMath.SpatialCellSet(bbox, 100)
150 self.footprintSet = afwDetection.FootprintSet(
151 self.mi, afwDetection.Threshold(100), "DETECTED"
152 )
154 self.catalog = self.measure(self.footprintSet, self.exposure)
156 for source in self.catalog:
157 cand = measAlg.makePsfCandidate(source, self.exposure)
158 self.cellSet.insertCandidate(cand)
160 def setupDeterminer(self, exposure):
161 """Setup the starSelector and psfDeterminer"""
162 starSelectorClass = measAlg.sourceSelectorRegistry["objectSize"]
163 starSelectorConfig = starSelectorClass.ConfigClass()
164 starSelectorConfig.sourceFluxField = "base_GaussianFlux_instFlux"
165 starSelectorConfig.badFlags = [
166 "base_PixelFlags_flag_edge",
167 "base_PixelFlags_flag_interpolatedCenter",
168 "base_PixelFlags_flag_saturatedCenter",
169 "base_PixelFlags_flag_crCenter",
170 ]
171 # Set to match when the tolerance of the test was set
172 starSelectorConfig.widthStdAllowed = 0.5
174 self.starSelector = starSelectorClass(config=starSelectorConfig)
176 self.makePsfCandidates = measAlg.MakePsfCandidatesTask()
178 psfDeterminerClass = measAlg.psfDeterminerRegistry["piff"]
179 psfDeterminerConfig = psfDeterminerClass.ConfigClass()
180 width, height = exposure.getMaskedImage().getDimensions()
181 psfDeterminerConfig.spatialOrder = 1
183 self.psfDeterminer = psfDeterminerClass(psfDeterminerConfig)
185 def subtractStars(self, exposure, catalog, chi_lim=-1):
186 """Subtract the exposure's PSF from all the sources in catalog"""
187 mi, psf = exposure.getMaskedImage(), exposure.getPsf()
189 subtracted = mi.Factory(mi, True)
190 for s in catalog:
191 xc, yc = s.getX(), s.getY()
192 bbox = subtracted.getBBox(afwImage.PARENT)
193 if bbox.contains(geom.PointI(int(xc), int(yc))):
194 measAlg.subtractPsf(psf, subtracted, xc, yc)
195 chi = subtracted.Factory(subtracted, True)
196 var = subtracted.getVariance()
197 np.sqrt(var.getArray(), var.getArray()) # inplace sqrt
198 chi /= var
200 chi_min = np.min(chi.getImage().getArray())
201 chi_max = np.max(chi.getImage().getArray())
202 print(chi_min, chi_max)
204 if chi_lim > 0:
205 self.assertGreater(chi_min, -chi_lim)
206 self.assertLess(chi_max, chi_lim)
208 def testPiffDeterminer(self):
209 """Test the (Piff) psfDeterminer on subImages"""
211 self.setupDeterminer(self.exposure)
212 metadata = dafBase.PropertyList()
214 stars = self.starSelector.run(self.catalog, exposure=self.exposure)
215 psfCandidateList = self.makePsfCandidates.run(
216 stars.sourceCat,
217 exposure=self.exposure
218 ).psfCandidates
219 psf, cellSet = self.psfDeterminer.determinePsf(
220 self.exposure,
221 psfCandidateList,
222 metadata,
223 flagKey=self.usePsfFlag
224 )
225 self.exposure.setPsf(psf)
227 self.assertEqual(len(psfCandidateList), metadata['numAvailStars'])
228 self.assertEqual(sum(self.catalog['use_psf']), metadata['numGoodStars'])
230 # Test how well we can subtract the PSF model
231 self.subtractStars(self.exposure, self.catalog, chi_lim=5.6)
233 # Test bboxes
234 for point in [
235 psf.getAveragePosition(),
236 geom.Point2D(),
237 geom.Point2D(1, 1)
238 ]:
239 self.assertEqual(
240 psf.computeBBox(point),
241 psf.computeKernelImage(point).getBBox()
242 )
243 self.assertEqual(
244 psf.computeKernelBBox(point),
245 psf.computeKernelImage(point).getBBox()
246 )
247 self.assertEqual(
248 psf.computeImageBBox(point),
249 psf.computeImage(point).getBBox()
250 )
252 # Some roundtrips
253 with lsst.utils.tests.getTempFilePath(".fits") as tmpFile:
254 self.exposure.writeFits(tmpFile)
255 fitsIm = afwImage.ExposureF(tmpFile)
256 copyIm = copy.deepcopy(self.exposure)
258 for newIm in [fitsIm, copyIm]:
259 # Piff doesn't enable __eq__ for its results, so we just check
260 # that some PSF images come out the same.
261 for point in [
262 geom.Point2D(0, 0),
263 geom.Point2D(10, 100),
264 geom.Point2D(-200, 30),
265 geom.Point2D(float("nan")) # "nullPoint"
266 ]:
267 self.assertImagesAlmostEqual(
268 psf.computeImage(point),
269 newIm.getPsf().computeImage(point)
270 )
271 # Also check using default position
272 self.assertImagesAlmostEqual(
273 psf.computeImage(),
274 newIm.getPsf().computeImage()
275 )
278class TestMemory(lsst.utils.tests.MemoryTestCase):
279 pass
282def setup_module(module):
283 lsst.utils.tests.init()
286if __name__ == "__main__": 286 ↛ 287line 286 didn't jump to line 287, because the condition on line 286 was never true
287 lsst.utils.tests.init()
288 unittest.main()