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 

23__all__ = ["MetricComputationError", "MetricTask", "MetricConfig", 

24 "MetricConnections"] 

25 

26 

27import abc 

28import traceback 

29 

30import lsst.pipe.base as pipeBase 

31from lsst.pipe.base import connectionTypes 

32 

33from lsst.verify import Name 

34 

35 

36class MetricComputationError(RuntimeError): 

37 """This class represents unresolvable errors in computing a metric. 

38 

39 `lsst.verify.tasks.MetricTask` raises ``MetricComputationError`` 

40 instead of other data- or processing-related exceptions to let code that 

41 calls a mix of data processing and metric tasks distinguish between 

42 the two. Therefore, most ``MetricComputationError`` instances should be 

43 chained to another exception representing the underlying problem. 

44 """ 

45 pass 

46 

47 

48class MetricConnections(pipeBase.PipelineTaskConnections, 

49 defaultTemplates={"package": None, "metric": None}, 

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

51 ): 

52 """An abstract connections class defining a metric output. 

53 

54 This class assumes detector-level metrics, which is the most common case. 

55 Subclasses can redeclare ``measurement`` and ``dimensions`` to override 

56 this assumption. 

57 

58 Notes 

59 ----- 

60 ``MetricConnections`` defines the following dataset templates: 

61 ``package`` 

62 Name of the metric's namespace. By 

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

64 the name of the package the metric is most closely 

65 associated with. 

66 ``metric`` 

67 Name of the metric, excluding any namespace. 

68 """ 

69 measurement = connectionTypes.Output( 

70 name="metricvalue_{package}_{metric}", 

71 doc="The metric value computed by this task.", 

72 storageClass="MetricValue", 

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

74 ) 

75 

76 

77class MetricConfig(pipeBase.PipelineTaskConfig, 

78 pipelineConnections=MetricConnections): 

79 

80 def validate(self): 

81 super().validate() 

82 

83 if "." in self.connections.package: 

84 raise ValueError(f"package name {self.connections.package} must " 

85 "not contain periods") 

86 if "." in self.connections.metric: 

87 raise ValueError(f"metric name {self.connections.metric} must " 

88 "not contain periods; use connections.package " 

89 "instead") 

90 

91 @property 

92 def metricName(self): 

93 """The metric calculated by a `MetricTask` with this config 

94 (`lsst.verify.Name`, read-only). 

95 """ 

96 return Name(package=self.connections.package, 

97 metric=self.connections.metric) 

98 

99 

100class MetricTask(pipeBase.PipelineTask, metaclass=abc.ABCMeta): 

101 """A base class for tasks that compute one metric from input datasets. 

102 

103 Parameters 

104 ---------- 

105 *args 

106 **kwargs 

107 Constructor parameters are the same as for 

108 `lsst.pipe.base.PipelineTask`. 

109 

110 Notes 

111 ----- 

112 In general, both the ``MetricTask``'s metric and its input data are 

113 configurable. Metrics may be associated with a data ID at any level of 

114 granularity, including repository-wide. 

115 

116 Like `lsst.pipe.base.PipelineTask`, this class should be customized by 

117 overriding `run` and by providing a `lsst.pipe.base.connectionTypes.Input` 

118 for each parameter of `run`. For requirements that are specific to 

119 ``MetricTask``, see `run`. 

120 """ 

121 

122 ConfigClass = MetricConfig 

123 

124 def __init__(self, **kwargs): 

125 super().__init__(**kwargs) 

126 

127 @abc.abstractmethod 

128 def run(self, **kwargs): 

129 """Run the MetricTask on in-memory data. 

130 

131 Parameters 

132 ---------- 

133 **kwargs 

134 Keyword arguments matching the inputs given in the class config; 

135 see `lsst.pipe.base.PipelineTask.run` for more details. 

136 

137 Returns 

138 ------- 

139 struct : `lsst.pipe.base.Struct` 

140 A `~lsst.pipe.base.Struct` containing at least the 

141 following component: 

142 

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

144 (`lsst.verify.Measurement` or `None`). This method is not 

145 responsible for adding mandatory metadata (e.g., the data ID); 

146 this is handled by the caller. 

147 

148 Raises 

149 ------ 

150 lsst.verify.tasks.MetricComputationError 

151 Raised if an algorithmic or system error prevents calculation 

152 of the metric. Examples include corrupted input data or 

153 unavoidable exceptions raised by analysis code. The 

154 `~lsst.verify.tasks.MetricComputationError` should be chained to a 

155 more specific exception describing the root cause. 

156 

157 Not having enough data for a metric to be applicable is not an 

158 error, and should not trigger this exception. 

159 

160 Notes 

161 ----- 

162 All input data must be treated as optional. This maximizes the 

163 ``MetricTask``'s usefulness for incomplete pipeline runs or runs with 

164 optional processing steps. If a metric cannot be calculated because 

165 the necessary inputs are missing, the ``MetricTask`` must return `None` 

166 in place of the measurement. 

167 """ 

168 

169 def runQuantum(self, butlerQC, inputRefs, outputRefs): 

170 """Do Butler I/O to provide in-memory objects for run. 

171 

172 This specialization of runQuantum performs error-handling specific to 

173 MetricTasks. Most or all of this functionality may be moved to 

174 activators in the future. 

175 """ 

176 # Synchronize changes to this method with ApdbMetricTask 

177 try: 

178 inputs = butlerQC.get(inputRefs) 

179 outputs = self.run(**inputs) 

180 if outputs.measurement is not None: 

181 butlerQC.put(outputs, outputRefs) 

182 else: 

183 self.log.debugf("Skipping measurement of {!r} on {} " 

184 "as not applicable.", self, inputRefs) 

185 except MetricComputationError: 

186 # Apparently lsst.log doesn't have built-in exception support? 

187 self.log.errorf( 

188 "Measurement of {!r} failed on {}->{}\n{}", 

189 self, inputRefs, outputRefs, traceback.format_exc()) 

190 

191 def adaptArgsAndRun(self, inputData, inputDataIds, outputDataId): 

192 """A wrapper around `run` used by 

193 `~lsst.verify.gen2tasks.MetricsControllerTask`. 

194 

195 Task developers should not need to call or override this method. 

196 

197 Parameters 

198 ---------- 

199 inputData : `dict` from `str` to any 

200 Dictionary whose keys are the names of input parameters and values 

201 are Python-domain data objects (or lists of objects) retrieved 

202 from data butler. Input objects may be `None` to represent 

203 missing data. 

204 inputDataIds : `dict` from `str` to `list` of dataId 

205 Dictionary whose keys are the names of input parameters and values 

206 are data IDs (or lists of data IDs) that the task consumes for 

207 corresponding dataset type. Data IDs are guaranteed to match data 

208 objects in ``inputData``. 

209 outputDataId : `dict` from `str` to dataId 

210 Dictionary containing a single key, ``"measurement"``, which maps 

211 to a single data ID for the measurement. The data ID must have the 

212 same granularity as the metric. 

213 

214 Returns 

215 ------- 

216 struct : `lsst.pipe.base.Struct` 

217 A `~lsst.pipe.base.Struct` containing at least the 

218 following component: 

219 

220 - ``measurement``: the value of the metric, computed from 

221 ``inputData`` (`lsst.verify.Measurement` or `None`). The 

222 measurement is guaranteed to contain not only the value of the 

223 metric, but also any mandatory supplementary information. 

224 

225 Raises 

226 ------ 

227 lsst.verify.tasks.MetricComputationError 

228 Raised if an algorithmic or system error prevents calculation 

229 of the metric. Examples include corrupted input data or 

230 unavoidable exceptions raised by analysis code. The 

231 `~lsst.verify.tasks.MetricComputationError` should be chained to a 

232 more specific exception describing the root cause. 

233 

234 Not having enough data for a metric to be applicable is not an 

235 error, and should not trigger this exception. 

236 

237 Notes 

238 ----- 

239 This implementation calls `run` on the contents of ``inputData``, 

240 followed by calling `addStandardMetadata` on the result before 

241 returning it. 

242 

243 Examples 

244 -------- 

245 Consider a metric that characterizes PSF variations across the entire 

246 field of view, given processed images. Then, if `run` has the 

247 signature ``run(images)``: 

248 

249 .. code-block:: py 

250 

251 inputData = {'images': [image1, image2, ...]} 

252 inputDataIds = {'images': [{'visit': 42, 'ccd': 1}, 

253 {'visit': 42, 'ccd': 2}, 

254 ...]} 

255 outputDataId = {'measurement': {'visit': 42}} 

256 result = task.adaptArgsAndRun( 

257 inputData, inputDataIds, outputDataId) 

258 """ 

259 result = self.run(**inputData) 

260 if result.measurement is not None: 

261 self.addStandardMetadata(result.measurement, 

262 outputDataId["measurement"]) 

263 return result 

264 

265 @classmethod 

266 def getInputDatasetTypes(cls, config): 

267 """Return input dataset types for this task. 

268 

269 Parameters 

270 ---------- 

271 config : ``cls.ConfigClass`` 

272 Configuration for this task. 

273 

274 Returns 

275 ------- 

276 datasets : `dict` from `str` to `str` 

277 Dictionary where the key is the name of the input dataset (must 

278 match a parameter to `run`) and the value is the name of its 

279 Butler dataset type. 

280 

281 Notes 

282 ----- 

283 The default implementation extracts a 

284 `~lsst.pipe.base.PipelineTaskConnections` object from ``config``. 

285 """ 

286 # Get connections from config for backward-compatibility 

287 connections = config.connections.ConnectionsClass(config=config) 

288 return {name: getattr(connections, name).name 

289 for name in connections.inputs} 

290 

291 @classmethod 

292 def areInputDatasetsScalar(cls, config): 

293 """Return input dataset multiplicity. 

294 

295 Parameters 

296 ---------- 

297 config : ``cls.ConfigClass`` 

298 Configuration for this task. 

299 

300 Returns 

301 ------- 

302 datasets : `Dict` [`str`, `bool`] 

303 Dictionary where the key is the name of the input dataset (must 

304 match a parameter to `run`) and the value is `True` if `run` takes 

305 only one object and `False` if it takes a list. 

306 

307 Notes 

308 ----- 

309 The default implementation extracts a 

310 `~lsst.pipe.base.PipelineTaskConnections` object from ``config``. 

311 """ 

312 connections = config.connections.ConnectionsClass(config=config) 

313 return {name: not getattr(connections, name).multiple 

314 for name in connections.inputs} 

315 

316 def addStandardMetadata(self, measurement, outputDataId): 

317 """Add data ID-specific metadata required for all metrics. 

318 

319 This method currently does not add any metadata, but may do so 

320 in the future. 

321 

322 Parameters 

323 ---------- 

324 measurement : `lsst.verify.Measurement` 

325 The `~lsst.verify.Measurement` that the metadata are added to. 

326 outputDataId : ``dataId`` 

327 The data ID to which the measurement applies, at the appropriate 

328 level of granularity. 

329 

330 Notes 

331 ----- 

332 This method should not be overridden by subclasses. 

333 

334 This method is not responsible for shared metadata like the execution 

335 environment (which should be added by this ``MetricTask``'s caller), 

336 nor for metadata specific to a particular metric (which should be 

337 added when the metric is calculated). 

338 

339 .. warning:: 

340 This method's signature will change whenever additional data needs 

341 to be provided. This is a deliberate restriction to ensure that all 

342 subclasses pass in the new data as well. 

343 """ 

344 pass