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 

27import lsst.pex.config 

28 

29from lsst.pipe.base import Struct, connectionTypes 

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

31 MetricComputationError 

32 

33 

34class SingleMetadataMetricConnections( 

35 MetricConnections, 

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

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

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

39 

40 Notes 

41 ----- 

42 ``SingleMetadataMetricConnections`` defines the following dataset 

43 templates: 

44 

45 ``package`` 

46 Name of the metric's namespace. By 

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

48 the name of the package the metric is most closely 

49 associated with. 

50 ``metric`` 

51 Name of the metric, excluding any namespace. 

52 ``labelName`` 

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

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

55 """ 

56 metadata = connectionTypes.Input( 

57 name="{labelName}_metadata", 

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

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

60 storageClass="PropertySet", 

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

62 multiple=False, 

63 ) 

64 

65 def __init__(self, *, config=None): 

66 """Customize the connections for a specific MetricTask instance. 

67 

68 Parameters 

69 ---------- 

70 config : `MetadataMetricConfig` 

71 A config for `MetadataMetricTask` or one of its subclasses. 

72 """ 

73 super().__init__(config=config) 

74 if config and config.metadataDimensions != self.metadata.dimensions: 

75 # Hack, but only way to get a connection without fixed dimensions 

76 newMetadata = connectionTypes.Input( 

77 name=self.metadata.name, 

78 doc=self.metadata.doc, 

79 storageClass=self.metadata.storageClass, 

80 dimensions=config.metadataDimensions, 

81 multiple=self.metadata.multiple, 

82 ) 

83 self.metadata = newMetadata 

84 # Registry must match actual connections 

85 self.allConnections['metadata'] = self.metadata 

86 # Task requires that quantum dimensions match input dimensions 

87 self.dimensions = config.metadataDimensions 

88 

89 

90class MetadataMetricConfig( 

91 MetricConfig, 

92 pipelineConnections=SingleMetadataMetricConnections): 

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

94 """ 

95 metadataDimensions = lsst.pex.config.ListField( 

96 default=SingleMetadataMetricConnections.dimensions, 

97 dtype=str, 

98 doc="Override for the dimensions of the 'metadata' input, when " 

99 "instrumenting Tasks that don't produce one metadata object " 

100 "per visit.", 

101 ) 

102 

103 

104class _AbstractMetadataMetricTask(MetricTask): 

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

106 

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

108 metadata object or many. 

109 

110 Parameters 

111 ---------- 

112 *args 

113 **kwargs 

114 Constructor parameters are the same as for 

115 `lsst.pipe.base.PipelineTask`. 

116 

117 Notes 

118 ----- 

119 This class should be customized by overriding `getInputMetadataKeys` 

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

121 

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

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

124 for deciding how to deal with them. 

125 """ 

126 # Design note: getInputMetadataKeys and makeMeasurement are overrideable 

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

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

129 # important than ensuring that no implementation details of MetricTask 

130 # can leak into application-specific code. 

131 

132 @classmethod 

133 @abc.abstractmethod 

134 def getInputMetadataKeys(cls, config): 

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

136 

137 Parameters 

138 ---------- 

139 config : ``cls.ConfigClass`` 

140 Configuration for this task. 

141 

142 Returns 

143 ------- 

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

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

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

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

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

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

150 for any key will cause an error. 

151 """ 

152 

153 @staticmethod 

154 def _searchKeys(metadata, keyFragment): 

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

156 

157 Parameters 

158 ---------- 

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

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

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

162 keyFragment : `str` 

163 A substring for a full metadata key. 

164 

165 Returns 

166 ------- 

167 keys : `set` of `str` 

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

169 """ 

170 keys = metadata.paramNames(topLevelOnly=False) 

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

172 

173 @staticmethod 

174 def _extractMetadata(metadata, metadataKeys): 

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

176 

177 Parameters 

178 ---------- 

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

180 A metadata object, assumed not `None`. 

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

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

183 substrings) in the format of 

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

185 

186 Returns 

187 ------- 

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

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

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

191 

192 Raises 

193 ------ 

194 lsst.verify.tasks.MetricComputationError 

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

196 in ``metadata``. 

197 """ 

198 data = {} 

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

200 matchingKeys = MetadataMetricTask._searchKeys( 

201 metadata, keyFragment) 

202 if len(matchingKeys) == 1: 

203 key, = matchingKeys 

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

205 elif not matchingKeys: 

206 data[dataName] = None 

207 else: 

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

209 % (keyFragment, matchingKeys) 

210 raise MetricComputationError(error) 

211 return data 

212 

213 

214class MetadataMetricTask(_AbstractMetadataMetricTask): 

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

216 

217 Parameters 

218 ---------- 

219 *args 

220 **kwargs 

221 Constructor parameters are the same as for 

222 `lsst.pipe.base.PipelineTask`. 

223 

224 Notes 

225 ----- 

226 This class should be customized by overriding `getInputMetadataKeys` 

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

228 

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

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

231 for deciding how to deal with them. 

232 """ 

233 # Design note: getInputMetadataKeys and makeMeasurement are overrideable 

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

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

236 # important than ensuring that no implementation details of MetricTask 

237 # can leak into application-specific code. 

238 

239 ConfigClass = MetadataMetricConfig 

240 

241 @abc.abstractmethod 

242 def makeMeasurement(self, values): 

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

244 

245 Parameters 

246 ---------- 

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

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

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

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

251 represent missing data. 

252 

253 Returns 

254 ------- 

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

256 The measurement corresponding to the input data. 

257 

258 Raises 

259 ------ 

260 lsst.verify.tasks.MetricComputationError 

261 Raised if an algorithmic or system error prevents calculation of 

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

263 """ 

264 

265 def run(self, metadata): 

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

267 

268 Parameters 

269 ---------- 

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

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

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

273 many units of processing into a single metric. 

274 

275 Returns 

276 ------- 

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

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

279 

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

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

282 

283 Raises 

284 ------ 

285 lsst.verify.tasks.MetricComputationError 

286 Raised if the strings returned by `getInputMetadataKeys` match 

287 more than one key in any metadata object. 

288 

289 Notes 

290 ----- 

291 This implementation calls `getInputMetadataKeys`, then searches for 

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

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

294 result to the caller. 

295 """ 

296 metadataKeys = self.getInputMetadataKeys(self.config) 

297 

298 if metadata is not None: 

299 data = self._extractMetadata(metadata, metadataKeys) 

300 else: 

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

302 

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