Coverage for tests/test_psfCandidate.py: 24%

114 statements  

« prev     ^ index     » next       coverage.py v6.4.4, created at 2022-08-19 12:35 -0700

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/>. 

21 

22import unittest 

23 

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 

30 

31try: 

32 display 

33except NameError: 

34 display = False 

35else: 

36 import lsst.afw.display as afwDisplay 

37 afwDisplay.setDefaultMaskTransparency(75) 

38 

39 

40def makeEmptyCatalog(psfCandidateField=None): 

41 """Return an empty catalog with a useful schema for psfCandidate testing. 

42 

43 Parameters 

44 ---------- 

45 psfCandidateField : `str` or None 

46 The name of a flag field to add to the schema. 

47 

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') 

59 

60 return catalog 

61 

62 

63def createFakeSource(x, y, catalog, exposure, threshold=0.1): 

64 """Create a fake source at the given x/y centroid location. 

65 

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. 

76 

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 

85 

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()) 

94 

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" 

103 

104 source.setFootprint(fp) 

105 return source 

106 

107 

108class CandidateMaskingTestCase(lsst.utils.tests.TestCase): 

109 """Testing masking around PSF candidates. 

110 """ 

111 def setUp(self): 

112 self.catalog = makeEmptyCatalog() 

113 

114 self.x, self.y = 123, 45 

115 self.exposure = afwImage.ExposureF(256, 256) 

116 self.exposure.variance.set(0.01) 

117 

118 def tearDown(self): 

119 del self.exposure 

120 del self.catalog 

121 

122 def createCandidate(self, threshold=0.1): 

123 """Create a PSF candidate from self.exposure. 

124 

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) 

131 

132 return measAlg.makePsfCandidate(source, self.exposure) 

133 

134 def checkCandidateMasking(self, badPixels, extraPixels=[], size=25, threshold=0.1, pixelThreshold=0.0): 

135 """Check that candidates are masked properly. 

136 

137 We add various pixels to the image and investigate the masking. 

138 

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") 

164 

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) 

175 

176 def testBlends(self): 

177 """Test that blended objects are masked. 

178 

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)]) 

184 

185 def testNeighborMasking(self): 

186 """Test that neighbours are masked. 

187 

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)]) 

192 

193 def testFaintNeighborMasking(self): 

194 """Test that faint neighbours are masked. 

195 

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) 

200 

201 

202class MakePsfCandidatesTaskTest(lsst.utils.tests.TestCase): 

203 """Test MakePsfCandidatesTask on a handful of fake sources. 

204 

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) 

214 

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) 

225 

226 self.makePsfCandidates = measAlg.MakePsfCandidatesTask() 

227 

228 def testMakePsfCandidates(self): 

229 result = self.makePsfCandidates.run(self.catalog, self.exposure) 

230 self.assertEqual(len(result.psfCandidates), len(self.goodIds)) 

231 

232 for goodId in self.goodIds: 

233 self.assertIn(goodId, result.goodStarCat['id']) 

234 

235 for badId in self.badIds: 

236 self.assertNotIn(badId, result.goodStarCat['id']) 

237 

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)) 

245 

246 for goodId in self.goodIds: 

247 self.assertTrue(self.catalog.find(goodId).get(self.psfCandidateField)) 

248 

249 for badId in self.badIds: 

250 self.assertFalse(self.catalog.find(badId).get(self.psfCandidateField)) 

251 

252 

253class TestMemory(lsst.utils.tests.MemoryTestCase): 

254 pass 

255 

256 

257def setup_module(module): 

258 lsst.utils.tests.init() 

259 

260 

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()