Coverage for python/lsst/verify/tasks/metricTask.py : 42%

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/>.
23__all__ = ["MetricComputationError", "MetricTask", "MetricConfig",
24 "MetricConnections"]
27import abc
28import traceback
30import lsst.pipe.base as pipeBase
31from lsst.pipe.base import connectionTypes
33from lsst.verify import Name
36class MetricComputationError(RuntimeError):
37 """This class represents unresolvable errors in computing a metric.
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
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.
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.
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 )
77class MetricConfig(pipeBase.PipelineTaskConfig,
78 pipelineConnections=MetricConnections):
80 def validate(self):
81 super().validate()
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")
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)
100class MetricTask(pipeBase.PipelineTask, metaclass=abc.ABCMeta):
101 """A base class for tasks that compute one metric from input datasets.
103 Parameters
104 ----------
105 *args
106 **kwargs
107 Constructor parameters are the same as for
108 `lsst.pipe.base.PipelineTask`.
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.
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 """
122 ConfigClass = MetricConfig
124 def __init__(self, **kwargs):
125 super().__init__(**kwargs)
127 @abc.abstractmethod
128 def run(self, **kwargs):
129 """Run the MetricTask on in-memory data.
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.
137 Returns
138 -------
139 struct : `lsst.pipe.base.Struct`
140 A `~lsst.pipe.base.Struct` containing at least the
141 following component:
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.
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.
157 Not having enough data for a metric to be applicable is not an
158 error, and should not trigger this exception.
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 """
169 def runQuantum(self, butlerQC, inputRefs, outputRefs):
170 """Do Butler I/O to provide in-memory objects for run.
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())
191 def adaptArgsAndRun(self, inputData, inputDataIds, outputDataId):
192 """A wrapper around `run` used by
193 `~lsst.verify.gen2tasks.MetricsControllerTask`.
195 Task developers should not need to call or override this method.
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.
214 Returns
215 -------
216 struct : `lsst.pipe.base.Struct`
217 A `~lsst.pipe.base.Struct` containing at least the
218 following component:
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.
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.
234 Not having enough data for a metric to be applicable is not an
235 error, and should not trigger this exception.
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.
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)``:
249 .. code-block:: py
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
265 @classmethod
266 def getInputDatasetTypes(cls, config):
267 """Return input dataset types for this task.
269 Parameters
270 ----------
271 config : ``cls.ConfigClass``
272 Configuration for this task.
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.
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}
291 @classmethod
292 def areInputDatasetsScalar(cls, config):
293 """Return input dataset multiplicity.
295 Parameters
296 ----------
297 config : ``cls.ConfigClass``
298 Configuration for this task.
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.
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}
316 def addStandardMetadata(self, measurement, outputDataId):
317 """Add data ID-specific metadata required for all metrics.
319 This method currently does not add any metadata, but may do so
320 in the future.
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.
330 Notes
331 -----
332 This method should not be overridden by subclasses.
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).
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