Coverage for tests/test_psf.py: 14%
Shortcuts 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
Shortcuts 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'])
229 self.assertEqual(
230 psf.getAveragePosition(),
231 geom.Point2D(
232 np.mean([s.x for s in psf._piffResult.stars]),
233 np.mean([s.y for s in psf._piffResult.stars])
234 )
235 )
237 # Test how well we can subtract the PSF model
238 self.subtractStars(self.exposure, self.catalog, chi_lim=5.6)
240 # Test bboxes
241 for point in [
242 psf.getAveragePosition(),
243 geom.Point2D(),
244 geom.Point2D(1, 1)
245 ]:
246 self.assertEqual(
247 psf.computeBBox(point),
248 psf.computeKernelImage(point).getBBox()
249 )
250 self.assertEqual(
251 psf.computeKernelBBox(point),
252 psf.computeKernelImage(point).getBBox()
253 )
254 self.assertEqual(
255 psf.computeImageBBox(point),
256 psf.computeImage(point).getBBox()
257 )
259 # Some roundtrips
260 with lsst.utils.tests.getTempFilePath(".fits") as tmpFile:
261 self.exposure.writeFits(tmpFile)
262 fitsIm = afwImage.ExposureF(tmpFile)
263 copyIm = copy.deepcopy(self.exposure)
265 for newIm in [fitsIm, copyIm]:
266 # Piff doesn't enable __eq__ for its results, so we just check
267 # that some PSF images come out the same.
268 for point in [
269 geom.Point2D(0, 0),
270 geom.Point2D(10, 100),
271 geom.Point2D(-200, 30),
272 geom.Point2D(float("nan")) # "nullPoint"
273 ]:
274 self.assertImagesAlmostEqual(
275 psf.computeImage(point),
276 newIm.getPsf().computeImage(point)
277 )
278 # Also check average position
279 newPsf = newIm.getPsf()
280 self.assertImagesAlmostEqual(
281 psf.computeImage(psf.getAveragePosition()),
282 newPsf.computeImage(newPsf.getAveragePosition())
283 )
286class TestMemory(lsst.utils.tests.MemoryTestCase):
287 pass
290def setup_module(module):
291 lsst.utils.tests.init()
294if __name__ == "__main__": 294 ↛ 295line 294 didn't jump to line 295, because the condition on line 294 was never true
295 lsst.utils.tests.init()
296 unittest.main()