Coverage for tests/test_psfCandidate.py: 21%
114 statements
« prev ^ index » next coverage.py v6.5.0, created at 2022-11-19 10:17 +0000
« prev ^ index » next coverage.py v6.5.0, created at 2022-11-19 10:17 +0000
1# This file is part of meas_algorithms.
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
24import lsst.geom
25import lsst.afw.detection as afwDet
26import lsst.afw.image as afwImage
27import lsst.afw.table as afwTable
28import lsst.meas.algorithms as measAlg
29import lsst.utils.tests
31try:
32 display
33except NameError:
34 display = False
35else:
36 import lsst.afw.display as afwDisplay
37 afwDisplay.setDefaultMaskTransparency(75)
40def makeEmptyCatalog(psfCandidateField=None):
41 """Return an empty catalog with a useful schema for psfCandidate testing.
43 Parameters
44 ----------
45 psfCandidateField : `str` or None
46 The name of a flag field to add to the schema.
48 Returns
49 -------
50 catalog : `lsst.afw.table.SourceCatalog`
51 The created (empty) catalog.
52 """
53 schema = afwTable.SourceTable.makeMinimalSchema()
54 lsst.afw.table.Point2DKey.addFields(schema, "centroid", "centroid", "pixels")
55 if psfCandidateField is not None:
56 schema.addField(psfCandidateField, type="Flag", doc="Is a psfCandidate?")
57 catalog = afwTable.SourceCatalog(schema)
58 catalog.defineCentroid('centroid')
60 return catalog
63def createFakeSource(x, y, catalog, exposure, threshold=0.1):
64 """Create a fake source at the given x/y centroid location.
66 Parameters
67 ----------
68 x,y : `int`
69 The x and y centroid coordinates to place the image at.
70 catalog : `lsst.afw.table.SourceCatalog`
71 The catalog to add the new source to.
72 exposure : `lsst.afw.image.Exposure`
73 The exposure to add the source to.
74 threshold : `float`, optional
75 The footprint threshold for identifying the source.
77 Returns
78 -------
79 source : `lsst.afw.table.SourceRecord`
80 The created source record that was added to ``catalog``.
81 """
82 source = catalog.addNew()
83 source['centroid_x'] = x
84 source['centroid_y'] = y
86 exposure.image[x, y, afwImage.LOCAL] = 1.0
87 fpSet = afwDet.FootprintSet(exposure.getMaskedImage(), afwDet.Threshold(threshold), "DETECTED")
88 if display:
89 disp = afwDisplay.Display(frame=1)
90 disp.mtv(exposure, title="createFakeSource: image")
91 for fp in fpSet.getFootprints():
92 for peak in fp.getPeaks():
93 disp.dot("x", peak.getIx(), peak.getIy())
95 # There might be multiple footprints; only the one around x,y should go in the source
96 found = False
97 for fp in fpSet.getFootprints():
98 if fp.contains(lsst.geom.Point2I(x, y)):
99 found = True
100 break
101 # We cannot continue if the the created source wasn't found.
102 assert found, "Unable to find central peak in footprint: faulty test"
104 source.setFootprint(fp)
105 return source
108class CandidateMaskingTestCase(lsst.utils.tests.TestCase):
109 """Testing masking around PSF candidates.
110 """
111 def setUp(self):
112 self.catalog = makeEmptyCatalog()
114 self.x, self.y = 123, 45
115 self.exposure = afwImage.ExposureF(256, 256)
116 self.exposure.variance.set(0.01)
118 def tearDown(self):
119 del self.exposure
120 del self.catalog
122 def createCandidate(self, threshold=0.1):
123 """Create a PSF candidate from self.exposure.
125 Parameters
126 ----------
127 threshold : `float`, optional
128 Threshold for creating footprints on image.
129 """
130 source = createFakeSource(self.x, self.y, self.catalog, self.exposure, threshold)
132 return measAlg.makePsfCandidate(source, self.exposure)
134 def checkCandidateMasking(self, badPixels, extraPixels=[], size=25, threshold=0.1, pixelThreshold=0.0):
135 """Check that candidates are masked properly.
137 We add various pixels to the image and investigate the masking.
139 Parameters
140 ----------
141 badPixels : `list` of `tuple` of `float`
142 The (x,y,flux) triplet of pixels that should be masked.
143 extraPixels : `tuple` of `int`, optional
144 The (x,y,flux) triplet of additional pixels to add to image.
145 size : `int`, optional
146 Size of candidate.
147 threshold : `float`, optional
148 Threshold for creating footprints on image.
149 pixelThreshold : `float`, optional
150 Threshold for masking pixels on candidate.
151 """
152 image = self.exposure.getMaskedImage().getImage()
153 for x, y, f in badPixels + extraPixels:
154 image[x, y, afwImage.LOCAL] = f
155 cand = self.createCandidate(threshold=threshold)
156 oldPixelThreshold = cand.getPixelThreshold()
157 try:
158 cand.setPixelThreshold(pixelThreshold)
159 candImage = cand.getMaskedImage(size, size)
160 mask = candImage.getMask()
161 if display:
162 afwDisplay.Display(frame=2).mtv(candImage, title=self._testMethodName + ": candImage")
163 afwDisplay.Display(frame=3).mtv(mask, title=self._testMethodName + ": mask")
165 detected = mask.getPlaneBitMask("DETECTED")
166 intrp = mask.getPlaneBitMask("INTRP")
167 for x, y, f in badPixels:
168 x -= self.x - size//2
169 y -= self.y - size//2
170 self.assertTrue(mask[x, y, afwImage.LOCAL] & intrp)
171 self.assertFalse(mask[x, y, afwImage.LOCAL] & detected)
172 finally:
173 # Ensure this static variable is reset
174 cand.setPixelThreshold(oldPixelThreshold)
176 def testBlends(self):
177 """Test that blended objects are masked.
179 We create another object next to the one of interest,
180 joined by a bridge so that they're part of the same
181 footprint. The extra object should be masked.
182 """
183 self.checkCandidateMasking([(self.x + 2, self.y, 1.0)], [(self.x + 1, self.y, 0.5)])
185 def testNeighborMasking(self):
186 """Test that neighbours are masked.
188 We create another object separated from the one of
189 interest, which should be masked.
190 """
191 self.checkCandidateMasking([(self.x + 5, self.y, 1.0)])
193 def testFaintNeighborMasking(self):
194 """Test that faint neighbours are masked.
196 We create another faint (i.e., undetected) object separated
197 from the one of interest, which should be masked.
198 """
199 self.checkCandidateMasking([(self.x + 5, self.y, 0.5)], threshold=0.9, pixelThreshold=1.0)
202class MakePsfCandidatesTaskTest(lsst.utils.tests.TestCase):
203 """Test MakePsfCandidatesTask on a handful of fake sources.
205 Notes
206 -----
207 Does not test sources with NaN/Inf in their footprint. Also does not test
208 any properties of the resulting PsfCandidates: those are assumed to be tested
209 in ``CandidateMaskingTestCase`` above.
210 """
211 def setUp(self):
212 self.psfCandidateField = "psfCandidate"
213 self.catalog = makeEmptyCatalog(self.psfCandidateField)
215 # id=0 is bad because it's on the edge, so fails with a WARN: LengthError.
216 self.badIds = [1, ]
217 self.goodIds = [2, 3]
218 # x and y coordinate: keep these in sync with the above good/bad list.
219 self.xCoords = [0, 100, 200]
220 self.yCoords = [0, 100, 20]
221 self.exposure = afwImage.ExposureF(256, 256)
222 self.exposure.variance.set(0.01)
223 for x, y in zip(self.xCoords, self.yCoords):
224 createFakeSource(x, y, self.catalog, self.exposure, 0.1)
226 self.makePsfCandidates = measAlg.MakePsfCandidatesTask()
228 def testMakePsfCandidates(self):
229 result = self.makePsfCandidates.run(self.catalog, self.exposure)
230 self.assertEqual(len(result.psfCandidates), len(self.goodIds))
232 for goodId in self.goodIds:
233 self.assertIn(goodId, result.goodStarCat['id'])
235 for badId in self.badIds:
236 self.assertNotIn(badId, result.goodStarCat['id'])
238 def testMakePsfCandidatesStarSelectedField(self):
239 """Test MakePsfCandidatesTask setting a selected field.
240 """
241 result = self.makePsfCandidates.run(self.catalog,
242 self.exposure,
243 psfCandidateField=self.psfCandidateField)
244 self.assertEqual(len(result.psfCandidates), len(self.goodIds))
246 for goodId in self.goodIds:
247 self.assertTrue(self.catalog.find(goodId).get(self.psfCandidateField))
249 for badId in self.badIds:
250 self.assertFalse(self.catalog.find(badId).get(self.psfCandidateField))
253class TestMemory(lsst.utils.tests.MemoryTestCase):
254 pass
257def setup_module(module):
258 lsst.utils.tests.init()
261if __name__ == "__main__": 261 ↛ 262line 261 didn't jump to line 262, because the condition on line 261 was never true
262 lsst.utils.tests.init()
263 unittest.main()