Coverage for python/lsst/verify/tasks/metricTask.py: 48%
49 statements
« prev ^ index » next coverage.py v6.4.2, created at 2022-07-16 10:09 +0000
« prev ^ index » next coverage.py v6.4.2, created at 2022-07-16 10:09 +0000
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/>.
23__all__ = ["MetricComputationError", "MetricTask", "MetricConfig",
24 "MetricConnections"]
27import abc
29import lsst.pipe.base as pipeBase
30from lsst.pipe.base import connectionTypes
32from lsst.verify import Name
35class MetricComputationError(RuntimeError):
36 """This class represents unresolvable errors in computing a metric.
38 `lsst.verify.tasks.MetricTask` raises ``MetricComputationError``
39 instead of other data- or processing-related exceptions to let code that
40 calls a mix of data processing and metric tasks distinguish between
41 the two. Therefore, most ``MetricComputationError`` instances should be
42 chained to another exception representing the underlying problem.
43 """
44 pass
47class MetricConnections(pipeBase.PipelineTaskConnections,
48 defaultTemplates={"package": None, "metric": None},
49 dimensions={"instrument", "visit", "detector"},
50 ):
51 """An abstract connections class defining a metric output.
53 This class assumes detector-level metrics, which is the most common case.
54 Subclasses can redeclare ``measurement`` and ``dimensions`` to override
55 this assumption.
57 Notes
58 -----
59 ``MetricConnections`` defines the following dataset templates:
60 ``package``
61 Name of the metric's namespace. By
62 :ref:`verify_metrics <verify-metrics-package>` convention, this is
63 the name of the package the metric is most closely
64 associated with.
65 ``metric``
66 Name of the metric, excluding any namespace.
67 """
68 measurement = connectionTypes.Output(
69 name="metricvalue_{package}_{metric}",
70 doc="The metric value computed by this task.",
71 storageClass="MetricValue",
72 dimensions={"instrument", "visit", "detector"},
73 )
76class MetricConfig(pipeBase.PipelineTaskConfig,
77 pipelineConnections=MetricConnections):
79 def validate(self):
80 super().validate()
82 if "." in self.connections.package:
83 raise ValueError(f"package name {self.connections.package} must "
84 "not contain periods")
85 if "." in self.connections.metric:
86 raise ValueError(f"metric name {self.connections.metric} must "
87 "not contain periods; use connections.package "
88 "instead")
90 @property
91 def metricName(self):
92 """The metric calculated by a `MetricTask` with this config
93 (`lsst.verify.Name`, read-only).
94 """
95 return Name(package=self.connections.package,
96 metric=self.connections.metric)
99class MetricTask(pipeBase.PipelineTask, metaclass=abc.ABCMeta):
100 """A base class for tasks that compute one metric from input datasets.
102 Parameters
103 ----------
104 *args
105 **kwargs
106 Constructor parameters are the same as for
107 `lsst.pipe.base.PipelineTask`.
109 Notes
110 -----
111 In general, both the ``MetricTask``'s metric and its input data are
112 configurable. Metrics may be associated with a data ID at any level of
113 granularity, including repository-wide.
115 Like `lsst.pipe.base.PipelineTask`, this class should be customized by
116 overriding `run` and by providing a `lsst.pipe.base.connectionTypes.Input`
117 for each parameter of `run`. For requirements that are specific to
118 ``MetricTask``, see `run`.
119 """
121 ConfigClass = MetricConfig
123 def __init__(self, **kwargs):
124 super().__init__(**kwargs)
126 @abc.abstractmethod
127 def run(self, **kwargs):
128 """Run the MetricTask on in-memory data.
130 Parameters
131 ----------
132 **kwargs
133 Keyword arguments matching the inputs given in the class config;
134 see `lsst.pipe.base.PipelineTask.run` for more details.
136 Returns
137 -------
138 struct : `lsst.pipe.base.Struct`
139 A `~lsst.pipe.base.Struct` containing at least the
140 following component:
142 - ``measurement``: the value of the metric
143 (`lsst.verify.Measurement` or `None`). This method is not
144 responsible for adding mandatory metadata (e.g., the data ID);
145 this is handled by the caller.
147 Raises
148 ------
149 lsst.verify.tasks.MetricComputationError
150 Raised if an algorithmic or system error prevents calculation
151 of the metric. Examples include corrupted input data or
152 unavoidable exceptions raised by analysis code. The
153 `~lsst.verify.tasks.MetricComputationError` should be chained to a
154 more specific exception describing the root cause.
156 Not having enough data for a metric to be applicable is not an
157 error, and should not trigger this exception.
159 Notes
160 -----
161 All input data must be treated as optional. This maximizes the
162 ``MetricTask``'s usefulness for incomplete pipeline runs or runs with
163 optional processing steps. If a metric cannot be calculated because
164 the necessary inputs are missing, the ``MetricTask`` must return `None`
165 in place of the measurement.
166 """
168 def runQuantum(self, butlerQC, inputRefs, outputRefs):
169 """Do Butler I/O to provide in-memory objects for run.
171 This specialization of runQuantum performs error-handling specific to
172 MetricTasks. Most or all of this functionality may be moved to
173 activators in the future.
174 """
175 # Synchronize changes to this method with ApdbMetricTask
176 try:
177 inputs = butlerQC.get(inputRefs)
178 outputs = self.run(**inputs)
179 if outputs.measurement is not None:
180 butlerQC.put(outputs, outputRefs)
181 else:
182 self.log.debug("Skipping measurement of %r on %s "
183 "as not applicable.", self, inputRefs)
184 except MetricComputationError:
185 self.log.error(
186 "Measurement of %r failed on %s->%s",
187 self, inputRefs, outputRefs, exc_info=True)
189 def adaptArgsAndRun(self, inputData, inputDataIds, outputDataId):
190 """A wrapper around `run` used by
191 `~lsst.verify.gen2tasks.MetricsControllerTask`.
193 Task developers should not need to call or override this method.
195 Parameters
196 ----------
197 inputData : `dict` from `str` to any
198 Dictionary whose keys are the names of input parameters and values
199 are Python-domain data objects (or lists of objects) retrieved
200 from data butler. Input objects may be `None` to represent
201 missing data.
202 inputDataIds : `dict` from `str` to `list` of dataId
203 Dictionary whose keys are the names of input parameters and values
204 are data IDs (or lists of data IDs) that the task consumes for
205 corresponding dataset type. Data IDs are guaranteed to match data
206 objects in ``inputData``.
207 outputDataId : `dict` from `str` to dataId
208 Dictionary containing a single key, ``"measurement"``, which maps
209 to a single data ID for the measurement. The data ID must have the
210 same granularity as the metric.
212 Returns
213 -------
214 struct : `lsst.pipe.base.Struct`
215 A `~lsst.pipe.base.Struct` containing at least the
216 following component:
218 - ``measurement``: the value of the metric, computed from
219 ``inputData`` (`lsst.verify.Measurement` or `None`). The
220 measurement is guaranteed to contain not only the value of the
221 metric, but also any mandatory supplementary information.
223 Raises
224 ------
225 lsst.verify.tasks.MetricComputationError
226 Raised if an algorithmic or system error prevents calculation
227 of the metric. Examples include corrupted input data or
228 unavoidable exceptions raised by analysis code. The
229 `~lsst.verify.tasks.MetricComputationError` should be chained to a
230 more specific exception describing the root cause.
232 Not having enough data for a metric to be applicable is not an
233 error, and should not trigger this exception.
235 Notes
236 -----
237 This implementation calls `run` on the contents of ``inputData``,
238 followed by calling `addStandardMetadata` on the result before
239 returning it.
241 Examples
242 --------
243 Consider a metric that characterizes PSF variations across the entire
244 field of view, given processed images. Then, if `run` has the
245 signature ``run(images)``:
247 .. code-block:: py
249 inputData = {'images': [image1, image2, ...]}
250 inputDataIds = {'images': [{'visit': 42, 'ccd': 1},
251 {'visit': 42, 'ccd': 2},
252 ...]}
253 outputDataId = {'measurement': {'visit': 42}}
254 result = task.adaptArgsAndRun(
255 inputData, inputDataIds, outputDataId)
256 """
257 result = self.run(**inputData)
258 if result.measurement is not None:
259 self.addStandardMetadata(result.measurement,
260 outputDataId["measurement"])
261 return result
263 @classmethod
264 def getInputDatasetTypes(cls, config):
265 """Return input dataset types for this task.
267 Parameters
268 ----------
269 config : ``cls.ConfigClass``
270 Configuration for this task.
272 Returns
273 -------
274 datasets : `dict` from `str` to `str`
275 Dictionary where the key is the name of the input dataset (must
276 match a parameter to `run`) and the value is the name of its
277 Butler dataset type.
279 Notes
280 -----
281 The default implementation extracts a
282 `~lsst.pipe.base.PipelineTaskConnections` object from ``config``.
283 """
284 # Get connections from config for backward-compatibility
285 connections = config.connections.ConnectionsClass(config=config)
286 return {name: getattr(connections, name).name
287 for name in connections.inputs}
289 @classmethod
290 def areInputDatasetsScalar(cls, config):
291 """Return input dataset multiplicity.
293 Parameters
294 ----------
295 config : ``cls.ConfigClass``
296 Configuration for this task.
298 Returns
299 -------
300 datasets : `Dict` [`str`, `bool`]
301 Dictionary where the key is the name of the input dataset (must
302 match a parameter to `run`) and the value is `True` if `run` takes
303 only one object and `False` if it takes a list.
305 Notes
306 -----
307 The default implementation extracts a
308 `~lsst.pipe.base.PipelineTaskConnections` object from ``config``.
309 """
310 connections = config.connections.ConnectionsClass(config=config)
311 return {name: not getattr(connections, name).multiple
312 for name in connections.inputs}
314 def addStandardMetadata(self, measurement, outputDataId):
315 """Add data ID-specific metadata required for all metrics.
317 This method currently does not add any metadata, but may do so
318 in the future.
320 Parameters
321 ----------
322 measurement : `lsst.verify.Measurement`
323 The `~lsst.verify.Measurement` that the metadata are added to.
324 outputDataId : ``dataId``
325 The data ID to which the measurement applies, at the appropriate
326 level of granularity.
328 Notes
329 -----
330 This method should not be overridden by subclasses.
332 This method is not responsible for shared metadata like the execution
333 environment (which should be added by this ``MetricTask``'s caller),
334 nor for metadata specific to a particular metric (which should be
335 added when the metric is calculated).
337 .. warning::
338 This method's signature will change whenever additional data needs
339 to be provided. This is a deliberate restriction to ensure that all
340 subclasses pass in the new data as well.
341 """
342 pass