Coverage for python/lsst/ip/diffim/metrics.py: 56%

48 statements  

« prev     ^ index     » next       coverage.py v6.4.1, created at 2022-06-29 12:09 +0000

1# This file is part of ip_diffim. 

2# 

3# Developed for the LSST Data Management System. 

4# This product includes software developed by the LSST Project 

5# (http://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 <http://www.gnu.org/licenses/>. 

21# 

22 

23__all__ = [ 

24 "NumberSciSourcesMetricTask", "NumberSciSourcesMetricConfig", 

25 "FractionDiaSourcesToSciSourcesMetricTask", "FractionDiaSourcesToSciSourcesMetricConfig", 

26] 

27 

28 

29import numpy as np 

30import astropy.units as u 

31 

32from lsst.pipe.base import Struct, connectionTypes 

33from lsst.verify import Measurement 

34from lsst.verify.gen2tasks import register 

35from lsst.verify.tasks import MetricTask, MetricConfig, MetricConnections, \ 

36 MetricComputationError 

37 

38 

39class NumberSciSourcesMetricConnections( 

40 MetricConnections, 

41 defaultTemplates={"package": "ip_diffim", 

42 "metric": "numSciSources"}, 

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

44): 

45 sources = connectionTypes.Input( 

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

47 name="src", 

48 storageClass="SourceCatalog", 

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

50 ) 

51 

52 

53class NumberSciSourcesMetricConfig( 

54 MetricConfig, 

55 pipelineConnections=NumberSciSourcesMetricConnections): 

56 pass 

57 

58 

59@register("numSciSources") 

60class NumberSciSourcesMetricTask(MetricTask): 

61 """Task that computes the number of cataloged non-primary science sources. 

62 

63 Notes 

64 ----- 

65 The task excludes any non-primary sources in the catalog, but it does 

66 not require that the catalog include a ``detect_isPrimary`` or 

67 ``sky_sources`` column. 

68 """ 

69 _DefaultName = "numSciSources" 

70 ConfigClass = NumberSciSourcesMetricConfig 

71 

72 def run(self, sources): 

73 """Count the number of non-primary science sources. 

74 

75 Parameters 

76 ---------- 

77 sources : `lsst.afw.table.SourceCatalog` or `None` 

78 A science source catalog, which may be empty or `None`. 

79 

80 Returns 

81 ------- 

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

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

84 

85 ``measurement`` 

86 the total number of non-primary science sources 

87 (`lsst.verify.Measurement` or `None`) 

88 """ 

89 if sources is not None: 

90 nSciSources = _countRealSources(sources) 

91 meas = Measurement(self.config.metricName, nSciSources * u.count) 

92 else: 

93 self.log.info("Nothing to do: no catalogs found.") 

94 meas = None 

95 return Struct(measurement=meas) 

96 

97 

98class FractionDiaSourcesToSciSourcesMetricConnections( 

99 MetricTask.ConfigClass.ConnectionsClass, 

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

101 defaultTemplates={"coaddName": "deep", 

102 "fakesType": "", 

103 "package": "ip_diffim", 

104 "metric": "fracDiaSourcesToSciSources"}): 

105 sciSources = connectionTypes.Input( 

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

107 name="src", 

108 storageClass="SourceCatalog", 

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

110 ) 

111 diaSources = connectionTypes.Input( 

112 doc="The catalog of DIASources.", 

113 name="{fakesType}{coaddName}Diff_diaSrc", 

114 storageClass="SourceCatalog", 

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

116 ) 

117 

118 

119class FractionDiaSourcesToSciSourcesMetricConfig( 

120 MetricTask.ConfigClass, 

121 pipelineConnections=FractionDiaSourcesToSciSourcesMetricConnections): 

122 pass 

123 

124 

125@register("fracDiaSourcesToSciSources") 

126class FractionDiaSourcesToSciSourcesMetricTask(MetricTask): 

127 """Task that computes the ratio of difference image sources to science 

128 sources in an image, visit, etc. 

129 

130 Notes 

131 ----- 

132 The task excludes any non-primary sources in the catalog, but it does 

133 not require that the catalog include a ``detect_isPrimary`` or 

134 ``sky_sources`` column. 

135 """ 

136 _DefaultName = "fracDiaSourcesToSciSources" 

137 ConfigClass = FractionDiaSourcesToSciSourcesMetricConfig 

138 

139 def run(self, sciSources, diaSources): 

140 """Compute the ratio of DIASources to non-primary science sources. 

141 

142 Parameters 

143 ---------- 

144 sciSources : `lsst.afw.table.SourceCatalog` or `None` 

145 A science source catalog, which may be empty or `None`. 

146 diaSources : `lsst.afw.table.SourceCatalog` or `None` 

147 A DIASource catalog for the same unit of processing 

148 as ``sciSources``. 

149 

150 Returns 

151 ------- 

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

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

154 

155 ``measurement`` 

156 the ratio (`lsst.verify.Measurement` or `None`) 

157 """ 

158 if diaSources is not None and sciSources is not None: 

159 nSciSources = _countRealSources(sciSources) 

160 nDiaSources = _countRealSources(diaSources) 

161 metricName = self.config.metricName 

162 if nSciSources <= 0: 

163 raise MetricComputationError( 

164 "No science sources found; ratio of DIASources to science sources ill-defined.") 

165 else: 

166 meas = Measurement(metricName, nDiaSources / nSciSources * u.dimensionless_unscaled) 

167 else: 

168 self.log.info("Nothing to do: no catalogs found.") 

169 meas = None 

170 return Struct(measurement=meas) 

171 

172 

173def _countRealSources(catalog): 

174 """Return the number of valid sources in a catalog. 

175 

176 At present, this definition includes only primary sources. If a catalog 

177 does not have a ``detect_isPrimary`` flag, this function counts non-sky 

178 sources. If it does not have a ``sky_source`` flag, either, all sources 

179 are counted. 

180 

181 Parameters 

182 ---------- 

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

184 The catalog of sources to count. 

185 

186 Returns 

187 ------- 

188 count : `int` 

189 The number of sources that satisfy the criteria. 

190 """ 

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

192 # is not a bool. 

193 if "detect_isPrimary" in catalog.schema: 

194 return np.count_nonzero(catalog["detect_isPrimary"] == True) # noqa: E712 

195 elif "sky_source" in catalog.schema: 

196 return np.count_nonzero(catalog["sky_source"] == False) # noqa: E712 

197 else: 

198 return len(catalog)