Coverage for tests/test_undeblended.py: 20%

113 statements  

« prev     ^ index     » next       coverage.py v6.4.4, created at 2022-09-15 10:18 +0000

1# This file is part of meas_base. 

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 

22"""Tests for measuring sources on undeblended images. 

23""" 

24 

25import sys 

26import unittest 

27import warnings 

28 

29import numpy as np 

30 

31import lsst.geom 

32import lsst.afw.image as afwImage 

33import lsst.afw.table as afwTable 

34import lsst.afw.geom as afwGeom 

35import lsst.afw.detection as afwDetection 

36import lsst.afw.math as afwMath 

37import lsst.meas.base as measBase 

38import lsst.utils.tests 

39 

40 

41class UndeblendedTestCase(lsst.utils.tests.TestCase): 

42 def testUndeblendedMeasurement(self): 

43 """Check undeblended measurement and aperture correction. 

44 """ 

45 width, height = 100, 100 # Dimensions of image 

46 x0, y0 = 1234, 5678 # Offset of image 

47 radius = 3.0 # Aperture radius 

48 

49 # Position of first source; integer values, for convenience 

50 xCenter, yCenter = width//2, height//2 

51 xOffset, yOffset = 1, 1 # Offset from first source to second source 

52 instFlux1, instFlux2 = 1000, 1 # Flux of sources 

53 apCorrValue = 3.21 # Aperture correction value to apply 

54 

55 image = afwImage.MaskedImageF(lsst.geom.ExtentI(width, height)) 

56 image.setXY0(x0, y0) 

57 image.getVariance().set(1.0) 

58 

59 schema = afwTable.SourceTable.makeMinimalSchema() 

60 schema.addField("centroid_x", type=np.float64) 

61 schema.addField("centroid_y", type=np.float64) 

62 schema.addField("centroid_flag", type='Flag') 

63 schema.getAliasMap().set("slot_Centroid", "centroid") 

64 

65 with warnings.catch_warnings(): 

66 warnings.filterwarnings("ignore", message="ignoreSlotPluginChecks", category=FutureWarning) 

67 sfmConfig = measBase.SingleFrameMeasurementConfig(ignoreSlotPluginChecks=True) 

68 algName = "base_CircularApertureFlux" 

69 

70 for subConfig in (sfmConfig.plugins, sfmConfig.undeblended): 

71 subConfig.names = [algName] 

72 subConfig[algName].radii = [radius] 

73 # Disable sinc photometry because we're undersampled 

74 subConfig[algName].maxSincRadius = 0 

75 slots = sfmConfig.slots 

76 slots.centroid = "centroid" 

77 slots.shape = None 

78 slots.psfShape = None 

79 slots.apFlux = None 

80 slots.modelFlux = None 

81 slots.psfFlux = None 

82 slots.gaussianFlux = None 

83 slots.calibFlux = None 

84 

85 fieldName = lsst.meas.base.CircularApertureFluxAlgorithm.makeFieldPrefix(algName, radius) 

86 measBase.addApCorrName(fieldName) 

87 

88 apCorrConfig = measBase.ApplyApCorrConfig() 

89 apCorrConfig.proxies = {"undeblended_" + fieldName: fieldName} 

90 

91 sfm = measBase.SingleFrameMeasurementTask(config=sfmConfig, schema=schema) 

92 apCorr = measBase.ApplyApCorrTask(config=apCorrConfig, schema=schema) 

93 

94 cat = afwTable.SourceCatalog(schema) 

95 parent = cat.addNew() 

96 parent.set("centroid_x", x0 + xCenter) 

97 parent.set("centroid_y", y0 + yCenter) 

98 spanSetParent = afwGeom.SpanSet.fromShape(int(radius)) 

99 spanSetParent = spanSetParent.shiftedBy(x0 + xCenter, y0 + yCenter) 

100 parent.setFootprint(afwDetection.Footprint(spanSetParent)) 

101 

102 # First child is bright, dominating the blend 

103 child1 = cat.addNew() 

104 child1.set("centroid_x", parent.get("centroid_x")) 

105 child1.set("centroid_y", parent.get("centroid_y")) 

106 child1.setParent(parent.getId()) 

107 image[xCenter, yCenter, afwImage.LOCAL] = (instFlux1, 0, 0) 

108 spanSetChild1 = afwGeom.SpanSet.fromShape(1) 

109 spanSetChild1 = spanSetChild1.shiftedBy(x0 + xCenter, y0 + yCenter) 

110 foot1 = afwDetection.Footprint(spanSetChild1) 

111 child1.setFootprint(afwDetection.HeavyFootprintF(foot1, image)) 

112 

113 # Second child is fainter, but we want to be able to measure it! 

114 child2 = cat.addNew() 

115 child2.set("centroid_x", parent.get("centroid_x") + xOffset) 

116 child2.set("centroid_y", parent.get("centroid_y") + yOffset) 

117 child2.setParent(parent.getId()) 

118 image[xCenter + xOffset, yCenter + yOffset, afwImage.LOCAL] = (instFlux2, 0, 0) 

119 spanSetChild2 = afwGeom.SpanSet.fromShape(1) 

120 tmpPoint = (x0 + xCenter + xOffset, y0 + yCenter + yOffset) 

121 spanSetChild2 = spanSetChild2.shiftedBy(*tmpPoint) 

122 foot2 = afwDetection.Footprint(spanSetChild2) 

123 child2.setFootprint(afwDetection.HeavyFootprintF(foot2, image)) 

124 

125 spans = foot1.spans.union(foot2.spans) 

126 bbox = lsst.geom.Box2I() 

127 bbox.include(foot1.getBBox()) 

128 bbox.include(foot2.getBBox()) 

129 parent.setFootprint(afwDetection.Footprint(spans, bbox)) 

130 

131 exposure = afwImage.makeExposure(image) 

132 

133 sfm.run(cat, exposure) 

134 

135 def checkSource(source, baseName, expectedFlux): 

136 """Check that we get the expected results. 

137 """ 

138 self.assertEqual(source.get(baseName + "_instFlux"), expectedFlux) 

139 self.assertGreater(source.get(baseName + "_instFluxErr"), 0) 

140 self.assertFalse(source.get(baseName + "_flag")) 

141 

142 # Deblended 

143 checkSource(child1, fieldName, instFlux1) 

144 checkSource(child2, fieldName, instFlux2) 

145 

146 # Undeblended 

147 checkSource(child1, "undeblended_" + fieldName, instFlux1 + instFlux2) 

148 checkSource(child2, "undeblended_" + fieldName, instFlux1 + instFlux2) 

149 

150 # Apply aperture correction 

151 apCorrMap = afwImage.ApCorrMap() 

152 apCorrMap[fieldName + "_instFlux"] = afwMath.ChebyshevBoundedField( 

153 image.getBBox(), 

154 apCorrValue*np.ones((1, 1), dtype=np.float64) 

155 ) 

156 apCorrMap[fieldName + "_instFluxErr"] = afwMath.ChebyshevBoundedField( 

157 image.getBBox(), 

158 apCorrValue*np.zeros((1, 1), dtype=np.float64) 

159 ) 

160 

161 apCorr.run(cat, apCorrMap) 

162 

163 # Deblended 

164 checkSource(child1, fieldName, instFlux1*apCorrValue) 

165 checkSource(child2, fieldName, instFlux2*apCorrValue) 

166 

167 # Undeblended 

168 checkSource(child1, "undeblended_" + fieldName, (instFlux1 + instFlux2)*apCorrValue) 

169 checkSource(child2, "undeblended_" + fieldName, (instFlux1 + instFlux2)*apCorrValue) 

170 

171 self.assertIn(fieldName + "_apCorr", schema) 

172 self.assertIn(fieldName + "_apCorrErr", schema) 

173 self.assertIn("undeblended_" + fieldName + "_apCorr", schema) 

174 self.assertIn("undeblended_" + fieldName + "_apCorrErr", schema) 

175 

176 

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

178 pass 

179 

180 

181def setup_module(module): 

182 lsst.utils.tests.init() 

183 

184 

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

186 setup_module(sys.modules[__name__]) 

187 unittest.main()