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 # Sort to ensure default order is consistent between runs 

97 default=sorted(SingleMetadataMetricConnections.dimensions), 

98 dtype=str, 

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

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

101 "per visit.", 

102 ) 

103 

104 

105class _AbstractMetadataMetricTask(MetricTask): 

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

107 

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

109 metadata object or many. 

110 

111 Parameters 

112 ---------- 

113 *args 

114 **kwargs 

115 Constructor parameters are the same as for 

116 `lsst.pipe.base.PipelineTask`. 

117 

118 Notes 

119 ----- 

120 This class should be customized by overriding `getInputMetadataKeys` 

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

122 

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

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

125 for deciding how to deal with them. 

126 """ 

127 # Design note: getInputMetadataKeys and makeMeasurement are overrideable 

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

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

130 # important than ensuring that no implementation details of MetricTask 

131 # can leak into application-specific code. 

132 

133 @classmethod 

134 @abc.abstractmethod 

135 def getInputMetadataKeys(cls, config): 

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

137 

138 Parameters 

139 ---------- 

140 config : ``cls.ConfigClass`` 

141 Configuration for this task. 

142 

143 Returns 

144 ------- 

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

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

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

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

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

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

151 for any key will cause an error. 

152 """ 

153 

154 @staticmethod 

155 def _searchKeys(metadata, keyFragment): 

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

157 

158 Parameters 

159 ---------- 

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

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

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

163 keyFragment : `str` 

164 A substring for a full metadata key. 

165 

166 Returns 

167 ------- 

168 keys : `set` of `str` 

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

170 """ 

171 keys = metadata.paramNames(topLevelOnly=False) 

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

173 

174 @staticmethod 

175 def _extractMetadata(metadata, metadataKeys): 

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

177 

178 Parameters 

179 ---------- 

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

181 A metadata object, assumed not `None`. 

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

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

184 substrings) in the format of 

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

186 

187 Returns 

188 ------- 

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

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

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

192 

193 Raises 

194 ------ 

195 lsst.verify.tasks.MetricComputationError 

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

197 in ``metadata``. 

198 """ 

199 data = {} 

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

201 matchingKeys = MetadataMetricTask._searchKeys( 

202 metadata, keyFragment) 

203 if len(matchingKeys) == 1: 

204 key, = matchingKeys 

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

206 elif not matchingKeys: 

207 data[dataName] = None 

208 else: 

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

210 % (keyFragment, matchingKeys) 

211 raise MetricComputationError(error) 

212 return data 

213 

214 

215class MetadataMetricTask(_AbstractMetadataMetricTask): 

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

217 

218 Parameters 

219 ---------- 

220 *args 

221 **kwargs 

222 Constructor parameters are the same as for 

223 `lsst.pipe.base.PipelineTask`. 

224 

225 Notes 

226 ----- 

227 This class should be customized by overriding `getInputMetadataKeys` 

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

229 

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

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

232 for deciding how to deal with them. 

233 """ 

234 # Design note: getInputMetadataKeys and makeMeasurement are overrideable 

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

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

237 # important than ensuring that no implementation details of MetricTask 

238 # can leak into application-specific code. 

239 

240 ConfigClass = MetadataMetricConfig 

241 

242 @abc.abstractmethod 

243 def makeMeasurement(self, values): 

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

245 

246 Parameters 

247 ---------- 

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

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

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

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

252 represent missing data. 

253 

254 Returns 

255 ------- 

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

257 The measurement corresponding to the input data. 

258 

259 Raises 

260 ------ 

261 lsst.verify.tasks.MetricComputationError 

262 Raised if an algorithmic or system error prevents calculation of 

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

264 """ 

265 

266 def run(self, metadata): 

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

268 

269 Parameters 

270 ---------- 

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

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

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

274 many units of processing into a single metric. 

275 

276 Returns 

277 ------- 

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

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

280 

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

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

283 

284 Raises 

285 ------ 

286 lsst.verify.tasks.MetricComputationError 

287 Raised if the strings returned by `getInputMetadataKeys` match 

288 more than one key in any metadata object. 

289 

290 Notes 

291 ----- 

292 This implementation calls `getInputMetadataKeys`, then searches for 

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

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

295 result to the caller. 

296 """ 

297 metadataKeys = self.getInputMetadataKeys(self.config) 

298 

299 if metadata is not None: 

300 data = self._extractMetadata(metadata, metadataKeys) 

301 else: 

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

303 

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