Coverage for tests/test_negative.py: 18%

107 statements  

« prev     ^ index     » next       coverage.py v7.2.5, created at 2023-05-11 10:49 +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/>. 

21 

22import unittest 

23 

24import lsst.geom 

25import lsst.afw.math as afwMath 

26import lsst.afw.table as afwTable 

27import lsst.daf.base as dafBase 

28from lsst.meas.algorithms import SourceDetectionTask 

29from lsst.meas.base import SingleFrameMeasurementTask as SourceMeasurementTask 

30from lsst.meas.algorithms.testUtils import plantSources 

31import lsst.utils.tests 

32 

33try: 

34 display 

35except NameError: 

36 display = False 

37else: 

38 import lsst.afw.display as afwDisplay 

39 afwDisplay.setDefaultMaskTransparency(75) 

40 

41 

42class NegativeMeasurementTestCase(lsst.utils.tests.TestCase): 

43 """Testing detection and measurement on negative objects. 

44 """ 

45 

46 def _create_exposure(self): 

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

48 minCounts = 2000 

49 maxCounts = 20000 

50 starSigma = 1.5 

51 numX = 4 

52 numY = 4 

53 coordList = self.makeCoordList( 

54 bbox=bbox, 

55 numX=numX, 

56 numY=numY, 

57 minCounts=minCounts, 

58 maxCounts=maxCounts, 

59 sigma=starSigma, 

60 ) 

61 kwid = 11 

62 sky = 2000 

63 addPoissonNoise = True 

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

65 addPoissonNoise=addPoissonNoise) 

66 return exposure, numX, numY 

67 

68 def test_detection_stdev(self): 

69 """Test detection and measurement on an exposure with negative sources 

70 for thresholdType="stdev". 

71 """ 

72 exposure, numX, numY = self._create_exposure() 

73 

74 if display: 

75 disp = afwDisplay.Display(frame=1) 

76 disp.mtv(exposure, title=self._testMethodName + ": image with -ve sources") 

77 

78 schema = afwTable.SourceTable.makeMinimalSchema() 

79 config = SourceDetectionTask.ConfigClass() 

80 config.reEstimateBackground = False 

81 config.thresholdPolarity = 'both' 

82 detection = SourceDetectionTask(config=config, schema=schema) 

83 algMetadata = dafBase.PropertyList() 

84 measurement = SourceMeasurementTask(schema=schema, algMetadata=algMetadata) 

85 

86 table = afwTable.SourceTable.make(schema) 

87 detections = detection.run(table, exposure) 

88 sources = detections.sources 

89 

90 self.assertEqual(len(sources), numX*numY) 

91 self.assertEqual(detections.numPos, numX*numY/2) 

92 self.assertEqual(detections.numNeg, numX*numY/2) 

93 

94 measurement.run(sources, exposure) 

95 

96 nGoodCent = 0 

97 nGoodShape = 0 

98 for s in sources: 

99 cent = s.getCentroid() 

100 shape = s.getShape() 

101 

102 if cent[0] == cent[0] and cent[1] == cent[1]: 

103 nGoodCent += 1 

104 

105 if (shape.getIxx() == shape.getIxx() 

106 and shape.getIyy() == shape.getIyy() 

107 and shape.getIxy() == shape.getIxy()): 

108 nGoodShape += 1 

109 

110 if display: 

111 xy = cent[0], cent[1] 

112 disp.dot('+', *xy) 

113 disp.dot(shape, *xy, ctype=afwDisplay.RED) 

114 

115 self.assertEqual(nGoodCent, numX*numY) 

116 self.assertEqual(nGoodShape, numX*numY) 

117 

118 def test_significance(self): 

119 """Test that negative peaks have the right significance for 

120 thresholdType='stdev' for the non-convolved, non-local-background case. 

121 """ 

122 exposure, numX, numY = self._create_exposure() 

123 

124 schema = afwTable.SourceTable.makeMinimalSchema() 

125 config = SourceDetectionTask.ConfigClass() 

126 config.thresholdPolarity = 'both' 

127 # don't modify the image after detection. 

128 config.reEstimateBackground = False 

129 config.doTempLocalBackground = False 

130 detection = SourceDetectionTask(config=config, schema=schema) 

131 result = detection.detectFootprints(exposure, doSmooth=False) 

132 

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

134 sctrl = afwMath.StatisticsControl() 

135 sctrl.setAndMask(bad) 

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

137 stddev = stats.getValue(afwMath.STDEVCLIP) 

138 # Don't bother checking positive footprints: those are tested more 

139 # thoroughly in test_detection.py. 

140 for footprint in result.negative.getFootprints(): 

141 for peak in footprint.peaks: 

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

143 value = exposure.image[point] 

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

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

146 -value/stddev, # S/N for negative peak 

147 rtol=1e-7, 

148 msg=str(point)) 

149 

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

151 """Make a coordList for makeExposure. 

152 """ 

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

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

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

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

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

158 

159 coordList = [] 

160 counts = minCounts 

161 for i in range(numX): 

162 x = minX + (dX*i) 

163 for j in range(numY): 

164 y = minY + (dY*j) 

165 if j%2 == 0: 

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

167 else: 

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

169 counts += dCounts 

170 return coordList 

171 

172 

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

174 pass 

175 

176 

177def setup_module(module): 

178 lsst.utils.tests.init() 

179 

180 

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

182 lsst.utils.tests.init() 

183 unittest.main()