Coverage for tests/test_SincPhotSums.py: 24%
141 statements
« prev ^ index » next coverage.py v6.4.1, created at 2022-06-09 03:07 -0700
« prev ^ index » next coverage.py v6.4.1, created at 2022-06-09 03:07 -0700
1# This file is part of meas_base.
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 math
23import unittest
25import numpy as np
27import lsst.geom
28import lsst.afw.image as afwImage
29import lsst.afw.detection as afwDetection
30import lsst.afw.math as afwMath
31import lsst.afw.geom as afwGeom
32import lsst.afw.geom.ellipses as afwEll
33import lsst.meas.base as measBase
34import lsst.utils.tests
36try:
37 display
38except NameError:
39 display = False
40else:
41 import lsst.afw.display as afwDisplay
42 afwDisplay.setDefaultMaskTransparency(75)
45def plantSources(bbox, kwid, sky, coordList, addPoissonNoise=True):
46 """Make an exposure with stars (modelled as Gaussians).
48 Parameters
49 ----------
50 bbox : `lsst.afw.geom.Box2I`
51 Parent bounding box of output exposure.
52 kwid : `float`
53 Kernel width (and height; kernel is square).
54 sky : `float`
55 Amount of sky background (counts)
56 coordList : iterable of `list`
57 Where:
58 - ``coordList[0]`` is the X coord of the star relative to the origin
59 - ``coordList[1]`` is the Y coord of the star relative to the origin
60 - ``coordList[2]`` is the integrated counts in the star
61 - ``coordlist[3]`` is the Gaussian sigma in pixels.
62 addPoissonNoise : `bool`, optional
63 Add Poisson noise to the output exposure?
65 Returns
66 -------
67 exposure : `lsst.afw.image.ExposureF`
68 Resulting exposure with simulated stars.
69 """
70 # make an image with sources
71 img = afwImage.ImageD(bbox)
72 meanSigma = 0.0
73 for coord in coordList:
74 x, y, counts, sigma = coord
75 meanSigma += sigma
77 # make a single gaussian psf
78 psf = afwDetection.GaussianPsf(kwid, kwid, sigma)
80 # make an image of it and scale to the desired number of counts
81 thisPsfImg = psf.computeImage(lsst.geom.PointD(int(x), int(y)))
82 thisPsfImg *= counts
84 # bbox a window in our image and add the fake star image
85 imgSeg = img.Factory(img, thisPsfImg.getBBox())
86 imgSeg += thisPsfImg
87 meanSigma /= len(coordList)
89 img += sky
91 # add Poisson noise
92 if (addPoissonNoise):
93 np.random.seed(seed=1) # make results reproducible
94 imgArr = img.getArray()
95 imgArr[:] = np.random.poisson(imgArr)
97 # bundle into a maskedimage and an exposure
98 mask = afwImage.Mask(bbox)
99 var = img.convertFloat()
100 img -= sky
101 mimg = afwImage.MaskedImageF(img.convertFloat(), mask, var)
102 exposure = afwImage.makeExposure(mimg)
104 # insert an approximate psf
105 psf = afwDetection.GaussianPsf(kwid, kwid, meanSigma)
106 exposure.setPsf(psf)
108 return exposure
111class SincPhotSums(lsst.utils.tests.TestCase):
113 def setUp(self):
114 self.nx = 64
115 self.ny = 64
116 self.kwid = 15
117 self.sky = 100.0
118 self.val = 10000.0
119 self.sigma = 4.0
120 coordList = [[self.nx/2, self.ny/2, self.val, self.sigma]]
122 # exposure with gaussian
123 bbox = lsst.geom.Box2I(lsst.geom.Point2I(0, 0), lsst.geom.Extent2I(self.nx, self.ny))
124 self.expGaussPsf = plantSources(bbox, self.kwid, self.sky, coordList, addPoissonNoise=False)
126 # just plain sky (ie. a constant)
127 self.mimg = afwImage.MaskedImageF(lsst.geom.ExtentI(self.nx, self.ny))
128 self.mimg.set(self.sky, 0x0, self.sky)
129 self.expSky = afwImage.makeExposure(self.mimg)
131 if display:
132 frame = 0
133 disp = afwDisplay.Display(frame=frame)
134 disp.mtv(self.expGaussPsf, title=self._testMethodName + ": expGaussPsf")
136 def tearDown(self):
137 del self.mimg
138 del self.expGaussPsf
139 del self.expSky
141 def testEllipticalGaussian(self):
142 """Test measuring elliptical aperture mags for an elliptical Gaussian.
143 """
144 width, height = 200, 200
145 xcen, ycen = 0.5*width, 0.5*height
146 #
147 # Make the object
148 #
149 gal = afwImage.ImageF(lsst.geom.ExtentI(width, height))
150 a, b, theta = float(10), float(5), 20
151 instFlux = 1e4
152 I0 = instFlux/(2*math.pi*a*b)
154 c, s = math.cos(math.radians(theta)), math.sin(math.radians(theta))
155 for y in range(height):
156 for x in range(width):
157 dx, dy = x - xcen, y - ycen
158 u = c*dx + s*dy
159 v = -s*dx + c*dy
160 val = I0*math.exp(-0.5*((u/a)**2 + (v/b)**2))
161 if val < 0:
162 val = 0
163 gal[x, y, afwImage.LOCAL] = val
165 objImg = afwImage.makeExposure(afwImage.makeMaskedImage(gal))
166 del gal
168 if display:
169 frame = 1
170 disp = afwDisplay.Display(frame=frame)
171 disp.mtv(objImg, title=self._testMethodName + ": Elliptical")
173 self.assertAlmostEqual(1.0, afwMath.makeStatistics(objImg.getMaskedImage().getImage(),
174 afwMath.SUM).getValue()/instFlux)
175 #
176 # Now measure some annuli
177 #
178 for r1, r2 in [(0., 0.45*a),
179 (0.45*a, 1.0*a),
180 (1.0*a, 2.0*a),
181 (2.0*a, 3.0*a),
182 (3.0*a, 5.0*a),
183 (3.0*a, 10.0*a),
184 ]:
185 if display: # draw the inner and outer boundaries of the aperture
186 Mxx = 1
187 Myy = (b/a)**2
189 mxx, mxy, myy = c**2*Mxx + s**2*Myy, c*s*(Mxx - Myy), s**2*Mxx + c**2*Myy
190 for r in (r1, r2):
191 disp.dot("@:%g,%g,%g" % (r**2*mxx, r**2*mxy, r**2*myy), xcen, ycen)
193 center = lsst.geom.Point2D(xcen, ycen)
195 # this tests tests a sync algorithm with an inner and outer radius
196 # since that is no longer available from the ApertureFluxAlgorithm,
197 # we will calculate the two and subtract.
199 axes = afwGeom.ellipses.Axes(r2, r2*(1-b/a), math.radians(theta))
200 ellipse = afwGeom.Ellipse(axes, center)
201 result2 = measBase.ApertureFluxAlgorithm.computeSincFlux(objImg.getMaskedImage(), ellipse)
203 axes = afwGeom.ellipses.Axes(r1, r1*(1-b/a), math.radians(theta))
204 ellipse = afwGeom.Ellipse(axes, center)
205 result1 = measBase.ApertureFluxAlgorithm.computeSincFlux(objImg.getMaskedImage(), ellipse)
207 self.assertAlmostEqual(math.exp(-0.5*(r1/a)**2) - math.exp(-0.5*(r2/a)**2),
208 (result2.instFlux-result1.instFlux)/instFlux, 4)
211class SincCoeffTestCase(lsst.utils.tests.TestCase):
213 def setUp(self):
214 self.ellipse = afwEll.Axes(10.0, 5.0, 0.12345)
215 self.radius1 = 0.1234
216 self.radius2 = 4.3210
217 self.inner = self.radius1/self.radius2
219 def tearDown(self):
220 del self.ellipse
222 def assertCached(self, coeff1, coeff2):
223 np.testing.assert_array_equal(coeff1.getArray(), coeff2.getArray())
224 # This compares the memory address and read-only attributes of the images.
225 self.assertEqual(coeff1.array.ctypes.data,
226 coeff2.array.ctypes.data)
228 def assertNotCached(self, coeff1, coeff2):
229 np.testing.assert_array_equal(coeff1.getArray(), coeff2.getArray())
230 # This compares the memory address and read-only attributes of the images.
231 self.assertNotEqual(coeff1.array.ctypes.data,
232 coeff2.array.ctypes.data)
234 def getCoeffCircle(self, radius2):
235 circle = afwEll.Axes(radius2, radius2, 0.0)
236 inner = self.radius1/radius2
237 coeff1 = measBase.SincCoeffsF.get(circle, inner)
238 coeff2 = measBase.SincCoeffsF.get(circle, inner)
239 return coeff1, coeff2
241 def testNoCachingElliptical(self):
242 coeff1 = measBase.SincCoeffsF.get(self.ellipse, self.inner)
243 coeff2 = measBase.SincCoeffsF.get(self.ellipse, self.inner)
244 self.assertNotCached(coeff1, coeff2)
246 def testNoCachingCircular(self):
247 coeff1, coeff2 = self.getCoeffCircle(2*self.radius2) # not self.radius2 because that may be cached
248 self.assertNotCached(coeff1, coeff2)
250 def testWithCaching(self):
251 measBase.SincCoeffsF.cache(self.radius1, self.radius2)
252 coeff1, coeff2 = self.getCoeffCircle(self.radius2)
253 self.assertCached(coeff1, coeff2)
256class TestMemory(lsst.utils.tests.MemoryTestCase):
257 pass
260def setup_module(module):
261 lsst.utils.tests.init()
264if __name__ == "__main__": 264 ↛ 265line 264 didn't jump to line 265, because the condition on line 264 was never true
265 lsst.utils.tests.init()
266 unittest.main()