Coverage for tests/test_psf_trampoline.py : 29%

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# This file is part of afw.
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 unittest
23from copy import deepcopy
25import numpy as np
27import lsst.utils.tests
28from lsst.afw.detection import Psf, GaussianPsf
29from lsst.afw.image import Image
30from lsst.geom import Box2I, Extent2I, Point2I, Point2D
31from lsst.afw.geom.ellipses import Quadrupole
32import testPsfTrampolineLib as cppLib
35# Subclass Psf in python. Main tests here are that python virtual methods get
36# resolved by trampoline class. The test suite below calls python compute*
37# methods which are implemented in c++ to call the _doCompute* methods defined
38# in the PyGaussianPsf class.
39class PyGaussianPsf(Psf):
40 def __init__(self, width, height, sigma):
41 Psf.__init__(self, isFixed=True)
42 self.dimensions = Extent2I(width, height)
43 self.sigma = sigma
45 # "public" virtual overrides
46 def __deepcopy__(self, memo=None):
47 return PyGaussianPsf(self.dimensions.x, self.dimensions.y, self.sigma)
49 def resized(self, width, height):
50 return PyGaussianPsf(width, height, self.sigma)
52 # "private" virtual overrides are underscored
53 def _doComputeKernelImage(self, position=None, color=None):
54 bbox = self.computeBBox()
55 img = Image(bbox, dtype=np.float64)
56 x, y = np.ogrid[bbox.minY:bbox.maxY+1, bbox.minX:bbox.maxX+1]
57 rsqr = x**2 + y**2
58 img.array[:] = np.exp(-0.5*rsqr/self.sigma**2)
59 img.array /= np.sum(img.array)
60 return img
62 def _doComputeBBox(self, position=None, color=None):
63 return Box2I(Point2I(-self.dimensions/2), self.dimensions)
65 def _doComputeShape(self, position=None, color=None):
66 return Quadrupole(self.sigma**2, self.sigma**2, 0.0)
68 def _doComputeApertureFlux(self, radius, position=None, color=None):
69 return 1 - np.exp(-0.5*(radius/self.sigma)**2)
72class PsfTrampolineTestSuite(lsst.utils.tests.TestCase):
73 def setUp(self):
74 self.pgps = []
75 self.gps = []
76 for width, height, sigma in [
77 (5, 5, 1.1),
78 (5, 3, 1.2),
79 (7, 7, 1.3)
80 ]:
81 self.pgps.append(PyGaussianPsf(width, height, sigma))
82 self.gps.append(GaussianPsf(width, height, sigma))
84 def testImages(self):
85 for pgp, gp in zip(self.pgps, self.gps):
86 self.assertImagesAlmostEqual(
87 pgp.computeImage(),
88 gp.computeImage()
89 )
90 self.assertImagesAlmostEqual(
91 pgp.computeKernelImage(),
92 gp.computeKernelImage()
93 )
95 def testApertureFlux(self):
96 for pgp, gp in zip(self.pgps, self.gps):
97 for r in [0.1, 0.2, 0.3]:
98 self.assertAlmostEqual(
99 pgp.computeApertureFlux(r),
100 gp.computeApertureFlux(r)
101 )
103 def testPeak(self):
104 for pgp, gp in zip(self.pgps, self.gps):
105 self.assertAlmostEqual(
106 pgp.computePeak(),
107 gp.computePeak()
108 )
110 def testBBox(self):
111 for pgp, gp in zip(self.pgps, self.gps):
112 self.assertEqual(
113 pgp.computeBBox(),
114 gp.computeBBox()
115 )
117 def testShape(self):
118 for pgp, gp in zip(self.pgps, self.gps):
119 self.assertAlmostEqual(
120 pgp.computeShape(),
121 gp.computeShape()
122 )
124 def testResized(self):
125 for pgp, gp in zip(self.pgps, self.gps):
126 width, height = pgp.dimensions
127 rpgp = pgp.resized(width+2, height+4)
128 # cppLib.resizedPsf calls Psf::resized, which redirects to
129 # PyGaussianPsf.resized above
130 rpgp2 = cppLib.resizedPsf(pgp, width+2, height+4)
131 rgp = gp.resized(width+2, height+4)
132 self.assertImagesAlmostEqual(
133 rpgp.computeImage(),
134 rgp.computeImage()
135 )
136 self.assertImagesAlmostEqual(
137 rpgp2.computeImage(),
138 rgp.computeImage()
139 )
141 def testClone(self):
142 """Test different ways of invoking PyGaussianPsf.__deepcopy__
143 """
144 for pgp in self.pgps:
145 # directly
146 p1 = deepcopy(pgp)
147 # cppLib::clonedPsf -> Psf::clone
148 p2 = cppLib.clonedPsf(pgp)
149 # cppLib::clonedStorablePsf -> Psf::cloneStorable
150 p3 = cppLib.clonedStorablePsf(pgp)
151 # Psf::clone()
152 p4 = pgp.clone()
154 for p in [p1, p2, p3, p4]:
155 self.assertIsNot(pgp, p)
156 self.assertImagesEqual(
157 pgp.computeImage(),
158 p.computeImage()
159 )
162# Psf with position-dependent image, but nonetheless may use isFixed=True.
163# When isFixed=True, first image returned is cached for all subsequent image
164# queries
165class TestPsf(Psf):
166 def __init__(self, isFixed):
167 Psf.__init__(self, isFixed=isFixed)
169 def _doComputeKernelImage(self, position=None, color=None):
170 bbox = Box2I(Point2I(-3, -3), Extent2I(7, 7))
171 img = Image(bbox, dtype=np.float64)
172 x, y = np.ogrid[bbox.minY:bbox.maxY+1, bbox.minX:bbox.maxX+1]
173 rsqr = x**2 + y**2
174 if position.x >= 0.0:
175 img.array[:] = np.exp(-0.5*rsqr)
176 else:
177 img.array[:] = np.exp(-0.5*rsqr/4)
178 img.array /= np.sum(img.array)
179 return img
182class FixedPsfTestSuite(lsst.utils.tests.TestCase):
183 def setUp(self):
184 self.fixedPsf = TestPsf(isFixed=True)
185 self.floatPsf = TestPsf(isFixed=False)
187 def testFloat(self):
188 pos1 = Point2D(1.0, 1.0)
189 pos2 = Point2D(-1.0, -1.0)
190 img1 = self.floatPsf.computeKernelImage(pos1)
191 img2 = self.floatPsf.computeKernelImage(pos2)
192 self.assertFloatsNotEqual(img1.array, img2.array)
194 def testFixed(self):
195 pos1 = Point2D(1.0, 1.0)
196 pos2 = Point2D(-1.0, -1.0)
197 img1 = self.fixedPsf.computeKernelImage(pos1)
198 # Although _doComputeKernelImage would return a different image here due
199 # do the difference between pos1 and pos2, for the fixed Psf, the
200 # caching mechanism intercepts instead and _doComputeKernelImage is
201 # never called with position=pos2. So img1 == img2.
202 img2 = self.fixedPsf.computeKernelImage(pos2)
203 self.assertFloatsEqual(img1.array, img2.array)
206class MemoryTester(lsst.utils.tests.MemoryTestCase):
207 pass
210def setup_module(module):
211 lsst.utils.tests.init()
214if __name__ == "__main__": 214 ↛ 215line 214 didn't jump to line 215, because the condition on line 214 was never true
215 lsst.utils.tests.init()
216 unittest.main()