Coverage for python/lsst/verify/tasks/metadataMetricTask.py : 36%

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/>.
22__all__ = ["MetadataMetricTask", "MetadataMetricConfig",
23 "SingleMetadataMetricConnections"]
25import abc
27from lsst.pipe.base import Struct, connectionTypes
28from lsst.verify.tasks import MetricTask, MetricConfig, MetricConnections, \
29 MetricComputationError
32class SingleMetadataMetricConnections(
33 MetricConnections,
34 dimensions={"instrument", "exposure", "detector"},
35 defaultTemplates={"labelName": "", "package": None, "metric": None}):
36 """An abstract connections class defining a metadata input.
38 Notes
39 -----
40 ``SingleMetadataMetricConnections`` defines the following dataset
41 templates:
43 ``package``
44 Name of the metric's namespace. By
45 :ref:`verify_metrics <verify-metrics-package>` convention, this is
46 the name of the package the metric is most closely
47 associated with.
48 ``metric``
49 Name of the metric, excluding any namespace.
50 ``labelName``
51 Pipeline label of the `~lsst.pipe.base.PipelineTask` or name of
52 the `~lsst.pipe.base.CmdLineTask` whose metadata are being read.
53 """
54 metadata = connectionTypes.Input(
55 name="{labelName}_metadata",
56 doc="The target top-level task's metadata. The name must be set to "
57 "the metadata's butler type, such as 'processCcd_metadata'.",
58 storageClass="PropertySet",
59 dimensions={"Instrument", "Exposure", "Detector"},
60 multiple=False,
61 )
64class MetadataMetricConfig(
65 MetricConfig,
66 pipelineConnections=SingleMetadataMetricConnections):
67 """A base class for metadata metric task configs.
69 Notes
70 -----
71 `MetadataMetricTask` classes that have CCD-level granularity can use
72 this class as-is. Support for metrics of a different granularity
73 may be added later.
74 """
75 pass
78class _AbstractMetadataMetricTask(MetricTask):
79 """A base class for tasks that compute metrics from metadata values.
81 This class contains code that is agnostic to whether the input is one
82 metadata object or many.
84 Parameters
85 ----------
86 *args
87 **kwargs
88 Constructor parameters are the same as for
89 `lsst.pipe.base.PipelineTask`.
91 Notes
92 -----
93 This class should be customized by overriding `getInputMetadataKeys`
94 and `makeMeasurement`. You should not need to override `run`.
96 This class makes no assumptions about how to handle missing data;
97 `makeMeasurement` may be called with `None` values, and is responsible
98 for deciding how to deal with them.
99 """
100 # Design note: getInputMetadataKeys and makeMeasurement are overrideable
101 # methods rather than subtask(s) to keep the configs for
102 # `MetricsControllerTask` as simple as possible. This was judged more
103 # important than ensuring that no implementation details of MetricTask
104 # can leak into application-specific code.
106 @classmethod
107 @abc.abstractmethod
108 def getInputMetadataKeys(cls, config):
109 """Return the metadata keys read by this task.
111 Parameters
112 ----------
113 config : ``cls.ConfigClass``
114 Configuration for this task.
116 Returns
117 -------
118 keys : `dict` [`str`, `str`]
119 The keys are the (arbitrary) names of values needed by
120 `makeMeasurement`, the values are the metadata keys to be looked
121 up. Metadata keys are assumed to include task prefixes in the
122 format of `lsst.pipe.base.Task.getFullMetadata()`. This method may
123 return a substring of the desired (full) key, but multiple matches
124 for any key will cause an error.
125 """
127 @staticmethod
128 def _searchKeys(metadata, keyFragment):
129 """Search the metadata for all keys matching a substring.
131 Parameters
132 ----------
133 metadata : `lsst.daf.base.PropertySet`
134 A metadata object with task-qualified keys as returned by
135 `lsst.pipe.base.Task.getFullMetadata()`.
136 keyFragment : `str`
137 A substring for a full metadata key.
139 Returns
140 -------
141 keys : `set` of `str`
142 All keys in ``metadata`` that have ``keyFragment`` as a substring.
143 """
144 keys = metadata.paramNames(topLevelOnly=False)
145 return {key for key in keys if keyFragment in key}
147 @staticmethod
148 def _extractMetadata(metadata, metadataKeys):
149 """Read multiple keys from a metadata object.
151 Parameters
152 ----------
153 metadata : `lsst.daf.base.PropertySet`
154 A metadata object, assumed not `None`.
155 metadataKeys : `dict` [`str`, `str`]
156 Keys are arbitrary labels, values are metadata keys (or their
157 substrings) in the format of
158 `lsst.pipe.base.Task.getFullMetadata()`.
160 Returns
161 -------
162 metadataValues : `dict` [`str`, any]
163 Keys are the same as for ``metadataKeys``, values are the value of
164 each metadata key, or `None` if no matching key was found.
166 Raises
167 ------
168 lsst.verify.tasks.MetricComputationError
169 Raised if any metadata key string has more than one match
170 in ``metadata``.
171 """
172 data = {}
173 for dataName, keyFragment in metadataKeys.items():
174 matchingKeys = MetadataMetricTask._searchKeys(
175 metadata, keyFragment)
176 if len(matchingKeys) == 1:
177 key, = matchingKeys
178 data[dataName] = metadata.getScalar(key)
179 elif not matchingKeys:
180 data[dataName] = None
181 else:
182 error = "String %s matches multiple metadata keys: %s" \
183 % (keyFragment, matchingKeys)
184 raise MetricComputationError(error)
185 return data
188class MetadataMetricTask(_AbstractMetadataMetricTask):
189 """A base class for tasks that compute metrics from single metadata objects.
191 Parameters
192 ----------
193 *args
194 **kwargs
195 Constructor parameters are the same as for
196 `lsst.pipe.base.PipelineTask`.
198 Notes
199 -----
200 This class should be customized by overriding `getInputMetadataKeys`
201 and `makeMeasurement`. You should not need to override `run`.
203 This class makes no assumptions about how to handle missing data;
204 `makeMeasurement` may be called with `None` values, and is responsible
205 for deciding how to deal with them.
206 """
207 # Design note: getInputMetadataKeys and makeMeasurement are overrideable
208 # methods rather than subtask(s) to keep the configs for
209 # `MetricsControllerTask` as simple as possible. This was judged more
210 # important than ensuring that no implementation details of MetricTask
211 # can leak into application-specific code.
213 ConfigClass = MetadataMetricConfig
215 @abc.abstractmethod
216 def makeMeasurement(self, values):
217 """Compute the metric given the values of the metadata.
219 Parameters
220 ----------
221 values : `dict` [`str`, any]
222 A `dict` representation of the metadata passed to `run`. It has the
223 same keys as returned by `getInputMetadataKeys`, and maps them to
224 the values extracted from the metadata. Any value may be `None` to
225 represent missing data.
227 Returns
228 -------
229 measurement : `lsst.verify.Measurement` or `None`
230 The measurement corresponding to the input data.
232 Raises
233 ------
234 lsst.verify.tasks.MetricComputationError
235 Raised if an algorithmic or system error prevents calculation of
236 the metric. See `run` for expected behavior.
237 """
239 def run(self, metadata):
240 """Compute a measurement from science task metadata.
242 Parameters
243 ----------
244 metadata : `lsst.daf.base.PropertySet` or `None`
245 A metadata object for the unit of science processing to use for
246 this metric, or a collection of such objects if this task combines
247 many units of processing into a single metric.
249 Returns
250 -------
251 result : `lsst.pipe.base.Struct`
252 A `~lsst.pipe.base.Struct` containing the following component:
254 - ``measurement``: the value of the metric
255 (`lsst.verify.Measurement` or `None`)
257 Raises
258 ------
259 lsst.verify.tasks.MetricComputationError
260 Raised if the strings returned by `getInputMetadataKeys` match
261 more than one key in any metadata object.
263 Notes
264 -----
265 This implementation calls `getInputMetadataKeys`, then searches for
266 matching keys in each metadata. It then passes the values of these
267 keys (or `None` if no match) to `makeMeasurement`, and returns its
268 result to the caller.
269 """
270 metadataKeys = self.getInputMetadataKeys(self.config)
272 if metadata is not None:
273 data = self._extractMetadata(metadata, metadataKeys)
274 else:
275 data = {dataName: None for dataName in metadataKeys}
277 return Struct(measurement=self.makeMeasurement(data))