Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1# This file is part of verify. 

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__ = ["MetadataMetricTask", "MetadataMetricConfig", 

23 "SingleMetadataMetricConnections"] 

24 

25import abc 

26 

27from lsst.pipe.base import Struct, connectionTypes 

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

29 MetricComputationError 

30 

31 

32class SingleMetadataMetricConnections( 

33 MetricConnections, 

34 dimensions={"instrument", "exposure", "detector"}, 

35 defaultTemplates={"labelName": "", "package": None, "metric": None}): 

36 """An abstract connections class defining a metadata input. 

37 

38 Notes 

39 ----- 

40 ``SingleMetadataMetricConnections`` defines the following dataset 

41 templates: 

42 

43 ``package`` 

44 Name of the metric's namespace. By 

45 :ref:`verify_metrics <verify-metrics-package>` convention, this is 

46 the name of the package the metric is most closely 

47 associated with. 

48 ``metric`` 

49 Name of the metric, excluding any namespace. 

50 ``labelName`` 

51 Pipeline label of the `~lsst.pipe.base.PipelineTask` or name of 

52 the `~lsst.pipe.base.CmdLineTask` whose metadata are being read. 

53 """ 

54 metadata = connectionTypes.Input( 

55 name="{labelName}_metadata", 

56 doc="The target top-level task's metadata. The name must be set to " 

57 "the metadata's butler type, such as 'processCcd_metadata'.", 

58 storageClass="PropertySet", 

59 dimensions={"Instrument", "Exposure", "Detector"}, 

60 multiple=False, 

61 ) 

62 

63 

64class MetadataMetricConfig( 

65 MetricConfig, 

66 pipelineConnections=SingleMetadataMetricConnections): 

67 """A base class for metadata metric task configs. 

68 

69 Notes 

70 ----- 

71 `MetadataMetricTask` classes that have CCD-level granularity can use 

72 this class as-is. Support for metrics of a different granularity 

73 may be added later. 

74 """ 

75 pass 

76 

77 

78class _AbstractMetadataMetricTask(MetricTask): 

79 """A base class for tasks that compute metrics from metadata values. 

80 

81 This class contains code that is agnostic to whether the input is one 

82 metadata object or many. 

83 

84 Parameters 

85 ---------- 

86 *args 

87 **kwargs 

88 Constructor parameters are the same as for 

89 `lsst.pipe.base.PipelineTask`. 

90 

91 Notes 

92 ----- 

93 This class should be customized by overriding `getInputMetadataKeys` 

94 and `makeMeasurement`. You should not need to override `run`. 

95 

96 This class makes no assumptions about how to handle missing data; 

97 `makeMeasurement` may be called with `None` values, and is responsible 

98 for deciding how to deal with them. 

99 """ 

100 # Design note: getInputMetadataKeys and makeMeasurement are overrideable 

101 # methods rather than subtask(s) to keep the configs for 

102 # `MetricsControllerTask` as simple as possible. This was judged more 

103 # important than ensuring that no implementation details of MetricTask 

104 # can leak into application-specific code. 

105 

106 @classmethod 

107 @abc.abstractmethod 

108 def getInputMetadataKeys(cls, config): 

109 """Return the metadata keys read by this task. 

110 

111 Parameters 

112 ---------- 

113 config : ``cls.ConfigClass`` 

114 Configuration for this task. 

115 

116 Returns 

117 ------- 

118 keys : `dict` [`str`, `str`] 

119 The keys are the (arbitrary) names of values needed by 

120 `makeMeasurement`, the values are the metadata keys to be looked 

121 up. Metadata keys are assumed to include task prefixes in the 

122 format of `lsst.pipe.base.Task.getFullMetadata()`. This method may 

123 return a substring of the desired (full) key, but multiple matches 

124 for any key will cause an error. 

125 """ 

126 

127 @staticmethod 

128 def _searchKeys(metadata, keyFragment): 

129 """Search the metadata for all keys matching a substring. 

130 

131 Parameters 

132 ---------- 

133 metadata : `lsst.daf.base.PropertySet` 

134 A metadata object with task-qualified keys as returned by 

135 `lsst.pipe.base.Task.getFullMetadata()`. 

136 keyFragment : `str` 

137 A substring for a full metadata key. 

138 

139 Returns 

140 ------- 

141 keys : `set` of `str` 

142 All keys in ``metadata`` that have ``keyFragment`` as a substring. 

143 """ 

144 keys = metadata.paramNames(topLevelOnly=False) 

145 return {key for key in keys if keyFragment in key} 

146 

147 @staticmethod 

148 def _extractMetadata(metadata, metadataKeys): 

149 """Read multiple keys from a metadata object. 

150 

151 Parameters 

152 ---------- 

153 metadata : `lsst.daf.base.PropertySet` 

154 A metadata object, assumed not `None`. 

155 metadataKeys : `dict` [`str`, `str`] 

156 Keys are arbitrary labels, values are metadata keys (or their 

157 substrings) in the format of 

158 `lsst.pipe.base.Task.getFullMetadata()`. 

159 

160 Returns 

161 ------- 

162 metadataValues : `dict` [`str`, any] 

163 Keys are the same as for ``metadataKeys``, values are the value of 

164 each metadata key, or `None` if no matching key was found. 

165 

166 Raises 

167 ------ 

168 lsst.verify.tasks.MetricComputationError 

169 Raised if any metadata key string has more than one match 

170 in ``metadata``. 

171 """ 

172 data = {} 

173 for dataName, keyFragment in metadataKeys.items(): 

174 matchingKeys = MetadataMetricTask._searchKeys( 

175 metadata, keyFragment) 

176 if len(matchingKeys) == 1: 

177 key, = matchingKeys 

178 data[dataName] = metadata.getScalar(key) 

179 elif not matchingKeys: 

180 data[dataName] = None 

181 else: 

182 error = "String %s matches multiple metadata keys: %s" \ 

183 % (keyFragment, matchingKeys) 

184 raise MetricComputationError(error) 

185 return data 

186 

187 

188class MetadataMetricTask(_AbstractMetadataMetricTask): 

189 """A base class for tasks that compute metrics from single metadata objects. 

190 

191 Parameters 

192 ---------- 

193 *args 

194 **kwargs 

195 Constructor parameters are the same as for 

196 `lsst.pipe.base.PipelineTask`. 

197 

198 Notes 

199 ----- 

200 This class should be customized by overriding `getInputMetadataKeys` 

201 and `makeMeasurement`. You should not need to override `run`. 

202 

203 This class makes no assumptions about how to handle missing data; 

204 `makeMeasurement` may be called with `None` values, and is responsible 

205 for deciding how to deal with them. 

206 """ 

207 # Design note: getInputMetadataKeys and makeMeasurement are overrideable 

208 # methods rather than subtask(s) to keep the configs for 

209 # `MetricsControllerTask` as simple as possible. This was judged more 

210 # important than ensuring that no implementation details of MetricTask 

211 # can leak into application-specific code. 

212 

213 ConfigClass = MetadataMetricConfig 

214 

215 @abc.abstractmethod 

216 def makeMeasurement(self, values): 

217 """Compute the metric given the values of the metadata. 

218 

219 Parameters 

220 ---------- 

221 values : `dict` [`str`, any] 

222 A `dict` representation of the metadata passed to `run`. It has the 

223 same keys as returned by `getInputMetadataKeys`, and maps them to 

224 the values extracted from the metadata. Any value may be `None` to 

225 represent missing data. 

226 

227 Returns 

228 ------- 

229 measurement : `lsst.verify.Measurement` or `None` 

230 The measurement corresponding to the input data. 

231 

232 Raises 

233 ------ 

234 lsst.verify.tasks.MetricComputationError 

235 Raised if an algorithmic or system error prevents calculation of 

236 the metric. See `run` for expected behavior. 

237 """ 

238 

239 def run(self, metadata): 

240 """Compute a measurement from science task metadata. 

241 

242 Parameters 

243 ---------- 

244 metadata : `lsst.daf.base.PropertySet` or `None` 

245 A metadata object for the unit of science processing to use for 

246 this metric, or a collection of such objects if this task combines 

247 many units of processing into a single metric. 

248 

249 Returns 

250 ------- 

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

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

253 

254 - ``measurement``: the value of the metric 

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

256 

257 Raises 

258 ------ 

259 lsst.verify.tasks.MetricComputationError 

260 Raised if the strings returned by `getInputMetadataKeys` match 

261 more than one key in any metadata object. 

262 

263 Notes 

264 ----- 

265 This implementation calls `getInputMetadataKeys`, then searches for 

266 matching keys in each metadata. It then passes the values of these 

267 keys (or `None` if no match) to `makeMeasurement`, and returns its 

268 result to the caller. 

269 """ 

270 metadataKeys = self.getInputMetadataKeys(self.config) 

271 

272 if metadata is not None: 

273 data = self._extractMetadata(metadata, metadataKeys) 

274 else: 

275 data = {dataName: None for dataName in metadataKeys} 

276 

277 return Struct(measurement=self.makeMeasurement(data))