Coverage for tests/test_detection.py: 15%

128 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2023-02-11 03:07 -0800

1# 

2# LSST Data Management System 

3# 

4# Copyright 2008-2016 AURA/LSST. 

5# 

6# This product includes software developed by the 

7# LSST Project (http://www.lsst.org/). 

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 LSST License Statement and 

20# the GNU General Public License along with this program. If not, 

21# see <https://www.lsstcorp.org/LegalNotices/>. 

22# 

23import unittest 

24import numpy as np 

25 

26import lsst.geom 

27import lsst.afw.table as afwTable 

28import lsst.afw.image as afwImage 

29import lsst.afw.math as afwMath 

30from lsst.meas.algorithms import SourceDetectionTask 

31from lsst.meas.algorithms.testUtils import plantSources 

32import lsst.utils.tests 

33 

34# To plot in ds9, `setup display_ds9` first, and open a ds9 window. 

35# import lsstDebug 

36# def DebugInfo(name): 

37# debug = lsstDebug.getInfo(name) 

38# if name == "lsst.meas.algorithms.detection": 

39# debug.display = 2 

40# return debug 

41# lsstDebug.Info = DebugInfo 

42 

43 

44class SourceDetectionTaskTestCase(lsst.utils.tests.TestCase): 

45 

46 def _create_exposure(self): 

47 """Return a simulated exposure (and relevant parameters) with Gaussian 

48 stars. 

49 """ 

50 bbox = lsst.geom.Box2I(lsst.geom.Point2I(256, 100), lsst.geom.Extent2I(128, 127)) 

51 minCounts = 5000 

52 maxCounts = 50000 

53 starSigma = 1.5 

54 numX = 5 

55 numY = 5 

56 coordList = self.makeCoordList( 

57 bbox=bbox, 

58 numX=numX, 

59 numY=numY, 

60 minCounts=minCounts, 

61 maxCounts=maxCounts, 

62 sigma=starSigma, 

63 ) 

64 kwid = 11 

65 sky = 2000 

66 addPoissonNoise = True 

67 exposure = plantSources(bbox=bbox, kwid=kwid, sky=sky, coordList=coordList, 

68 addPoissonNoise=addPoissonNoise) 

69 schema = afwTable.SourceTable.makeMinimalSchema() 

70 

71 return exposure, schema, numX, numY, starSigma 

72 

73 def _check_detectFootprints(self, exposure, numX, numY, starSigma, task, config, doSmooth=False): 

74 """Run detectFootprints and check that the output is reasonable, 

75 for either value of doSmooth. 

76 """ 

77 taskSigma = 2.2 

78 res = task.detectFootprints(exposure, doSmooth=doSmooth, sigma=taskSigma) 

79 self.assertEqual(res.numPos, numX * numY) 

80 self.assertEqual(res.numNeg, 0) 

81 self.assertEqual(task.metadata.getScalar("sigma"), taskSigma) 

82 self.assertEqual(task.metadata.getScalar("doSmooth"), doSmooth) 

83 self.assertEqual(task.metadata.getScalar("nGrow"), int(taskSigma * config.nSigmaToGrow + 0.5)) 

84 

85 res = task.detectFootprints(exposure, doSmooth=doSmooth, sigma=None) 

86 taskSigma = task.metadata.getScalar("sigma") 

87 self.assertLess(abs(taskSigma - starSigma), 0.1) 

88 self.assertEqual(res.numPos, numX * numY) 

89 self.assertEqual(res.numNeg, 0) 

90 return res 

91 

92 def test_stdev(self): 

93 """Test that sources are detected on a simulated image with 

94 thresholdType='stdev'. 

95 """ 

96 exposure, schema, numX, numY, starSigma = self._create_exposure() 

97 

98 config = SourceDetectionTask.ConfigClass() 

99 # don't modify the image after detection. 

100 config.reEstimateBackground = False 

101 config.thresholdType = "stdev" 

102 task = SourceDetectionTask(config=config, schema=schema) 

103 

104 self._check_detectFootprints(exposure, numX, numY, starSigma, task, config, doSmooth=True) 

105 self._check_detectFootprints(exposure, numX, numY, starSigma, task, config, doSmooth=False) 

106 

107 def test_significance_stdev(self): 

108 """Check the non-smoothed, non-background updated peak significance 

109 values with thresholdType="stddev". 

110 """ 

111 exposure, schema, numX, numY, starSigma = self._create_exposure() 

112 

113 config = SourceDetectionTask.ConfigClass() 

114 # don't modify the image after detection. 

115 config.reEstimateBackground = False 

116 config.doTempLocalBackground = False 

117 config.thresholdType = "stdev" 

118 task = SourceDetectionTask(config=config, schema=schema) 

119 

120 result = self._check_detectFootprints(exposure, numX, numY, starSigma, task, config, doSmooth=False) 

121 

122 bad = exposure.mask.getPlaneBitMask(config.statsMask) 

123 sctrl = afwMath.StatisticsControl() 

124 sctrl.setAndMask(bad) 

125 stats = afwMath.makeStatistics(exposure.maskedImage, afwMath.STDEVCLIP, sctrl) 

126 stddev = stats.getValue(afwMath.STDEVCLIP) 

127 for footprint in result.positive.getFootprints(): 

128 for peak in footprint.peaks: 

129 point = lsst.geom.Point2I(peak.getIx(), peak.getIy()) 

130 value = exposure.image[point] 

131 with self.subTest(str(point)): 

132 self.assertFloatsAlmostEqual(peak["significance"], 

133 value/stddev, 

134 rtol=1e-7, 

135 msg=str(point)) 

136 

137 def test_pixel_stdev(self): 

138 """Test that sources are detected on a simulated image with 

139 thresholdType='pixel_stdev', and that they have the right significance. 

140 """ 

141 exposure, schema, numX, numY, starSigma = self._create_exposure() 

142 

143 config = SourceDetectionTask.ConfigClass() 

144 config.thresholdType = "pixel_stdev" 

145 config.reEstimateBackground = False 

146 # TempLocalBackground changes the peak value of the faintest peak, 

147 # so disable it for this test so that we can calculate an expected 

148 # answer without having to try to deal with backgrounds. 

149 config.doTempLocalBackground = False 

150 task = SourceDetectionTask(config=config, schema=schema) 

151 # Don't smooth, so that we can directly calculate the s/n from the exposure. 

152 result = task.detectFootprints(exposure, doSmooth=False) 

153 self.assertEqual(result.numPos, numX * numY) 

154 self.assertEqual(result.numNeg, 0) 

155 # Significance values for `pixel_stdev` should match image/sqrt(variance). 

156 for footprint in result.positive.getFootprints(): 

157 for peak in footprint.peaks: 

158 point = lsst.geom.Point2I(peak.getIx(), peak.getIy()) 

159 value = exposure.image[point] 

160 stddev = np.sqrt(exposure.variance[point]) 

161 with self.subTest(str(point)): 

162 self.assertFloatsAlmostEqual(peak["significance"], 

163 value/stddev, 

164 rtol=1e-7, 

165 msg=str(point)) 

166 

167 def makeCoordList(self, bbox, numX, numY, minCounts, maxCounts, sigma): 

168 """Make a coordList for plantSources.""" 

169 """ 

170 Coords are uniformly spaced in a rectangular grid, with linearly increasing counts 

171 """ 

172 dX = bbox.getWidth() / float(numX) 

173 dY = bbox.getHeight() / float(numY) 

174 minX = bbox.getMinX() + (dX / 2.0) 

175 minY = bbox.getMinY() + (dY / 2.0) 

176 dCounts = (maxCounts - minCounts) / (numX * numY - 1) 

177 

178 coordList = [] 

179 counts = minCounts 

180 for i in range(numX): 

181 x = minX + (dX * i) 

182 for j in range(numY): 

183 y = minY + (dY * j) 

184 coordList.append([x, y, counts, sigma]) 

185 counts += dCounts 

186 return coordList 

187 

188 def testTempBackgrounds(self): 

189 """Test that the temporary backgrounds we remove are properly restored""" 

190 bbox = lsst.geom.Box2I(lsst.geom.Point2I(12345, 67890), lsst.geom.Extent2I(128, 127)) 

191 original = afwImage.ExposureF(bbox) 

192 rng = np.random.RandomState(123) 

193 original.image.array[:] = rng.normal(size=original.image.array.shape) 

194 original.mask.set(0) 

195 original.variance.set(1.0) 

196 

197 def checkExposure(original, doTempLocalBackground, doTempWideBackground): 

198 """Check that the original exposure is unmodified.""" 

199 config = SourceDetectionTask.ConfigClass() 

200 config.reEstimateBackground = False 

201 config.thresholdType = "pixel_stdev" 

202 config.doTempLocalBackground = doTempLocalBackground 

203 config.doTempWideBackground = doTempWideBackground 

204 schema = afwTable.SourceTable.makeMinimalSchema() 

205 task = SourceDetectionTask(config=config, schema=schema) 

206 

207 exposure = original.clone() 

208 task.detectFootprints(exposure, sigma=3.21) 

209 

210 self.assertFloatsEqual(exposure.image.array, original.image.array) 

211 # Mask is permitted to vary: DETECTED bit gets set 

212 self.assertFloatsEqual(exposure.variance.array, original.variance.array) 

213 

214 checkExposure(original, False, False) 

215 checkExposure(original, True, False) 

216 checkExposure(original, False, True) 

217 checkExposure(original, True, True) 

218 

219 

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

221 pass 

222 

223 

224def setup_module(module): 

225 lsst.utils.tests.init() 

226 

227 

228if __name__ == "__main__": 228 ↛ 229line 228 didn't jump to line 229, because the condition on line 228 was never true

229 lsst.utils.tests.init() 

230 unittest.main()