Coverage for python/lsst/pipe/tasks/metrics.py: 46%

44 statements  

« prev     ^ index     » next       coverage.py v7.3.1, created at 2023-09-23 10:48 +0000

1# This file is part of pipe_tasks. 

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__all__ = [ 

23 "NumberDeblendedSourcesMetricTask", "NumberDeblendedSourcesMetricConfig", 

24 "NumberDeblendChildSourcesMetricTask", "NumberDeblendChildSourcesMetricConfig", 

25] 

26 

27 

28import numpy as np 

29import astropy.units as u 

30 

31from lsst.pipe.base import NoWorkFound, Struct, connectionTypes 

32from lsst.verify import Measurement 

33from lsst.verify.tasks import MetricTask, MetricConfig, MetricConnections, MetricComputationError 

34 

35 

36class NumberDeblendedSourcesMetricConnections( 

37 MetricConnections, 

38 defaultTemplates={"package": "pipe_tasks", 

39 "metric": "numDeblendedSciSources"}, 

40 dimensions={"instrument", "visit", "detector"}, 

41): 

42 sources = connectionTypes.Input( 

43 doc="The catalog of science sources.", 

44 name="src", 

45 storageClass="SourceCatalog", 

46 dimensions={"instrument", "visit", "detector"}, 

47 ) 

48 

49 

50class NumberDeblendedSourcesMetricConfig( 

51 MetricConfig, 

52 pipelineConnections=NumberDeblendedSourcesMetricConnections): 

53 pass 

54 

55 

56class NumberDeblendedSourcesMetricTask(MetricTask): 

57 """Task that computes the number of science sources that have 

58 been deblended. 

59 

60 This task only counts sources that existed prior to any deblending; 

61 i.e., if deblending was run more than once or with multiple iterations, 

62 only the "top-level" deblended sources are counted, and not any 

63 intermediate ones. If sky source information is present, sky sources 

64 are excluded. 

65 

66 Notes 

67 ----- 

68 The task excludes any non-sky sources in the catalog, but it does 

69 not require that the catalog include a ``sky_sources`` column. 

70 """ 

71 _DefaultName = "numDeblendedSciSources" 

72 ConfigClass = NumberDeblendedSourcesMetricConfig 

73 

74 def run(self, sources): 

75 """Count the number of deblended science sources. 

76 

77 Parameters 

78 ---------- 

79 sources : `lsst.afw.table.SourceCatalog` 

80 A science source catalog, which may be empty. 

81 

82 Returns 

83 ------- 

84 result : `lsst.pipe.base.Struct` 

85 A `~lsst.pipe.base.Struct` containing the following component: 

86 

87 ``measurement`` 

88 the total number of deblended science sources 

89 (`lsst.verify.Measurement`). If no deblending information is 

90 available in ``sources``, this is `None`. 

91 

92 Raises 

93 ------ 

94 MetricComputationError 

95 Raised if ``sources`` is missing mandatory keys for 

96 source catalogs. 

97 """ 

98 if "deblend_nChild" not in sources.schema: 

99 raise NoWorkFound("Nothing to do: no deblending performed.") 

100 else: 

101 try: 

102 deblended = ((sources["parent"] == 0) # top-level source 

103 & (sources["deblend_nChild"] > 0) # deblended 

104 ) 

105 deblended = _filterSkySources(sources, deblended) 

106 except LookupError as e: 

107 # Probably "parent"; all other columns already checked 

108 raise MetricComputationError("Invalid input catalog") from e 

109 else: 

110 nDeblended = np.count_nonzero(deblended) 

111 return Struct(measurement=Measurement(self.config.metricName, 

112 nDeblended * u.dimensionless_unscaled)) 

113 

114 

115class NumberDeblendChildSourcesMetricConnections( 

116 MetricConnections, 

117 defaultTemplates={"package": "pipe_tasks", 

118 "metric": "numDeblendChildSciSources"}, 

119 dimensions={"instrument", "visit", "detector"}, 

120): 

121 sources = connectionTypes.Input( 

122 doc="The catalog of science sources.", 

123 name="src", 

124 storageClass="SourceCatalog", 

125 dimensions={"instrument", "visit", "detector"}, 

126 ) 

127 

128 

129class NumberDeblendChildSourcesMetricConfig( 

130 MetricConfig, 

131 pipelineConnections=NumberDeblendChildSourcesMetricConnections): 

132 pass 

133 

134 

135class NumberDeblendChildSourcesMetricTask(MetricTask): 

136 """Task that computes the number of science sources created 

137 through deblending. 

138 

139 This task only counts final deblending products; i.e., if deblending was 

140 run more than once or with multiple iterations, only the final set of 

141 deblended sources are counted, and not any intermediate ones. 

142 If sky source information is present, sky sources are excluded. 

143 

144 Notes 

145 ----- 

146 The task excludes any non-sky sources in the catalog, but it does 

147 not require that the catalog include a ``sky_sources`` column. 

148 """ 

149 _DefaultName = "numDeblendChildSciSources" 

150 ConfigClass = NumberDeblendChildSourcesMetricConfig 

151 

152 def run(self, sources): 

153 """Count the number of science sources created by deblending. 

154 

155 Parameters 

156 ---------- 

157 sources : `lsst.afw.table.SourceCatalog` 

158 A science source catalog, which may be empty. 

159 

160 Returns 

161 ------- 

162 result : `lsst.pipe.base.Struct` 

163 A `~lsst.pipe.base.Struct` containing the following component: 

164 

165 ``measurement`` 

166 the total number of science sources from deblending 

167 (`lsst.verify.Measurement`). If no deblending information is 

168 available in ``sources``, this is `None`. 

169 

170 Raises 

171 ------ 

172 MetricComputationError 

173 Raised if ``sources`` is missing mandatory keys for 

174 source catalogs. 

175 """ 

176 # Use deblend_parentNChild rather than detect_fromBlend because the 

177 # latter need not be defined in post-deblending catalogs. 

178 if "deblend_parentNChild" not in sources.schema or "deblend_nChild" not in sources.schema: 

179 raise NoWorkFound("Nothing to do: no deblending performed.") 

180 else: 

181 try: 

182 children = ((sources["deblend_parentNChild"] > 1) # deblend child 

183 & (sources["deblend_nChild"] == 0) # not deblended 

184 ) 

185 children = _filterSkySources(sources, children) 

186 except LookupError as e: 

187 # Probably "parent"; all other columns already checked 

188 raise MetricComputationError("Invalid input catalog") from e 

189 else: 

190 nChildren = np.count_nonzero(children) 

191 return Struct(measurement=Measurement(self.config.metricName, 

192 nChildren * u.dimensionless_unscaled)) 

193 

194 

195def _filterSkySources(catalog, selection): 

196 """Filter out any sky sources from a vector of selected sources. 

197 

198 If no sky source information is available, all sources are assumed to 

199 be non-sky. 

200 

201 Parameters 

202 ---------- 

203 catalog : `lsst.afw.table.SourceCatalog` 

204 The catalog to filter. 

205 selection : `numpy.ndarray` [`bool`], (N,) 

206 A vector of existing source selections, of the same length as 

207 ``catalog``, where selected sources are marked `True`. 

208 

209 Returns 

210 ------- 

211 filtered : `numpy.ndarray` [`bool`], (N,) 

212 A version of ``selection`` with any sky sources filtered out 

213 (set to `False`). May be the same vector as ``selection`` if 

214 no changes were made. 

215 """ 

216 if "sky_source" in catalog.schema: 

217 # E712 is not applicable, because afw.table.SourceRecord.ColumnView 

218 # is not a bool. 

219 return selection & (catalog["sky_source"] == False) # noqa: E712 

220 else: 

221 return selection