Coverage for python/lsst/verify/tasks/apdbMetricTask.py: 30%
77 statements
« prev ^ index » next coverage.py v7.4.4, created at 2024-04-10 10:54 +0000
« prev ^ index » next coverage.py v7.4.4, created at 2024-04-10 10:54 +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/>.
22__all__ = ["ApdbMetricTask", "ApdbMetricConfig", "ConfigApdbLoader",
23 "DirectApdbLoader", "ApdbMetricConnections"]
25import abc
27from lsst.pex.config import Config, ConfigurableField, ConfigurableInstance, \
28 ConfigDictField, ConfigChoiceField, FieldValidationError
29from lsst.pipe.base import NoWorkFound, Task, Struct, connectionTypes
30from lsst.dax.apdb import Apdb, ApdbConfig
32from lsst.verify.tasks import MetricTask, MetricConfig, MetricConnections
35class ConfigApdbLoader(Task):
36 """A Task that takes a science task config and returns the corresponding
37 Apdb object.
39 Parameters
40 ----------
41 *args
42 **kwargs
43 Constructor parameters are the same as for `lsst.pipe.base.Task`.
44 """
45 _DefaultName = "configApdb"
46 ConfigClass = Config
48 def __init__(self, **kwargs):
49 super().__init__(**kwargs)
51 def _getApdb(self, config):
52 """Extract an Apdb object from an arbitrary task config.
54 Parameters
55 ----------
56 config : `lsst.pex.config.Config`
57 A config that may contain a `lsst.dax.apdb.ApdbConfig`.
58 Behavior is undefined if there is more than one such member.
60 Returns
61 -------
62 apdb : `lsst.dax.apdb.Apdb`-like or `None`
63 A `lsst.dax.apdb.Apdb` object or a drop-in replacement, or `None`
64 if no `lsst.dax.apdb.ApdbConfig` is present in ``config``.
65 """
66 if isinstance(config, ApdbConfig):
67 return Apdb.from_config(config)
69 for field in config.values():
70 if isinstance(field, ConfigurableInstance):
71 result = self._getApdbFromConfigurableField(field)
72 if result:
73 return result
74 elif isinstance(field, ConfigChoiceField.instanceDictClass):
75 try:
76 # can't test with hasattr because of non-standard getattr
77 field.names
78 except FieldValidationError:
79 result = self._getApdb(field.active)
80 else:
81 result = self._getApdbFromConfigIterable(field.active)
82 if result:
83 return result
84 elif isinstance(field, ConfigDictField.DictClass):
85 result = self._getApdbFromConfigIterable(field.values())
86 if result:
87 return result
88 elif isinstance(field, Config):
89 # Can't test for `ConfigField` more directly than this
90 result = self._getApdb(field)
91 if result:
92 return result
93 return None
95 def _getApdbFromConfigurableField(self, configurable):
96 """Extract an Apdb object from a ConfigurableField.
98 Parameters
99 ----------
100 configurable : `lsst.pex.config.ConfigurableInstance`
101 A configurable that may contain a `lsst.dax.apdb.ApdbConfig`.
103 Returns
104 -------
105 apdb : `lsst.dax.apdb.Apdb`-like or `None`
106 A `lsst.dax.apdb.Apdb` object or a drop-in replacement, if a
107 suitable config exists.
108 """
109 if issubclass(configurable.ConfigClass, ApdbConfig):
110 return configurable.apply()
111 else:
112 return self._getApdb(configurable.value)
114 def _getApdbFromConfigIterable(self, configDict):
115 """Extract an Apdb object from an iterable of configs.
117 Parameters
118 ----------
119 configDict: iterable of `lsst.pex.config.Config`
120 A config iterable that may contain a `lsst.dax.apdb.ApdbConfig`.
122 Returns
123 -------
124 apdb : `lsst.dax.apdb.Apdb`-like or `None`
125 A `lsst.dax.apdb.Apdb` object or a drop-in replacement, if a
126 suitable config exists.
127 """
128 for config in configDict:
129 result = self._getApdb(config)
130 if result:
131 return result
133 def run(self, config):
134 """Create a database consistent with a science task config.
136 Parameters
137 ----------
138 config : `lsst.pex.config.Config`
139 A config that should contain a `lsst.dax.apdb.ApdbConfig`.
140 Behavior is undefined if there is more than one such member.
142 Returns
143 -------
144 result : `lsst.pipe.base.Struct`
145 Result struct with components:
147 ``apdb``
148 A database configured the same way as in ``config``, if one
149 exists (`lsst.dax.apdb.Apdb` or `None`).
150 """
151 return Struct(apdb=self._getApdb(config))
154class DirectApdbLoader(Task):
155 """A Task that takes a Apdb config and returns the corresponding
156 Apdb object.
158 Parameters
159 ----------
160 *args
161 **kwargs
162 Constructor parameters are the same as for `lsst.pipe.base.Task`.
163 """
165 _DefaultName = "directApdb"
166 ConfigClass = Config
168 def __init__(self, **kwargs):
169 super().__init__(**kwargs)
171 def run(self, config):
172 """Create a database from a config.
174 Parameters
175 ----------
176 config : `lsst.dax.apdb.ApdbConfig`
177 A config for the database connection.
179 Returns
180 -------
181 result : `lsst.pipe.base.Struct`
182 Result struct with components:
184 ``apdb``
185 A database configured the same way as in ``config``.
186 """
187 return Struct(apdb=(Apdb.from_config(config) if config else None))
190class ApdbMetricConnections(
191 MetricConnections,
192 dimensions={"instrument"},
193):
194 """An abstract connections class defining a database input.
196 Notes
197 -----
198 ``ApdbMetricConnections`` defines the following dataset templates:
199 ``package``
200 Name of the metric's namespace. By
201 :ref:`verify_metrics <verify-metrics-package>` convention, this is
202 the name of the package the metric is most closely
203 associated with.
204 ``metric``
205 Name of the metric, excluding any namespace.
206 """
207 dbInfo = connectionTypes.Input(
208 name="apdb_marker",
209 doc="The dataset from which an APDB instance can be constructed by "
210 "`dbLoader`. By default this is assumed to be a marker produced "
211 "by AP processing.",
212 storageClass="Config",
213 multiple=True,
214 minimum=1,
215 dimensions={"instrument", "visit", "detector"},
216 )
217 # Replaces MetricConnections.measurement, which is detector-level
218 measurement = connectionTypes.Output(
219 name="metricvalue_{package}_{metric}",
220 doc="The metric value computed by this task.",
221 storageClass="MetricValue",
222 dimensions={"instrument"},
223 )
226class ApdbMetricConfig(MetricConfig,
227 pipelineConnections=ApdbMetricConnections):
228 """A base class for APDB metric task configs.
229 """
230 dbLoader = ConfigurableField(
231 target=DirectApdbLoader,
232 doc="Task for loading a database from `dbInfo`. Its run method must "
233 "take one object of the dataset type indicated by `dbInfo` and return "
234 "a Struct with an 'apdb' member."
235 )
238class ApdbMetricTask(MetricTask):
239 """A base class for tasks that compute metrics from an alert production
240 database.
242 Parameters
243 ----------
244 **kwargs
245 Constructor parameters are the same as for
246 `lsst.pipe.base.PipelineTask`.
248 Notes
249 -----
250 This class should be customized by overriding `makeMeasurement`. You
251 should not need to override `run`.
252 """
253 # Design note: makeMeasurement is an overrideable method rather than a
254 # subtask to keep the configs for `MetricsControllerTask` as simple as
255 # possible. This was judged more important than ensuring that no
256 # implementation details of MetricTask can leak into
257 # application-specific code.
259 ConfigClass = ApdbMetricConfig
261 def __init__(self, **kwargs):
262 super().__init__(**kwargs)
264 self.makeSubtask("dbLoader")
266 @abc.abstractmethod
267 def makeMeasurement(self, dbHandle, outputDataId):
268 """Compute the metric from database data.
270 Parameters
271 ----------
272 dbHandle : `lsst.dax.apdb.Apdb`
273 A database instance.
274 outputDataId : any data ID type
275 The subset of the database to which this measurement applies.
276 May be empty to represent the entire dataset.
278 Returns
279 -------
280 measurement : `lsst.verify.Measurement` or `None`
281 The measurement corresponding to the input data.
283 Raises
284 ------
285 lsst.verify.tasks.MetricComputationError
286 Raised if an algorithmic or system error prevents calculation of
287 the metric. See `run` for expected behavior.
288 lsst.pipe.base.NoWorkFound
289 Raised if the metric is ill-defined or otherwise inapplicable to
290 the database state. Typically this means that the pipeline step or
291 option being measured was not run.
292 """
294 def run(self, dbInfo, outputDataId={}):
295 """Compute a measurement from a database.
297 Parameters
298 ----------
299 dbInfo : `list`
300 The datasets (of the type indicated by the config) from
301 which to load the database. If more than one dataset is provided
302 (as may be the case if DB writes are fine-grained), all are
303 assumed identical.
304 outputDataId: any data ID type, optional
305 The output data ID for the metric value. Defaults to the empty ID,
306 representing a value that covers the entire dataset.
308 Returns
309 -------
310 result : `lsst.pipe.base.Struct`
311 Result struct with component:
313 ``measurement``
314 the value of the metric (`lsst.verify.Measurement` or `None`)
316 Raises
317 ------
318 lsst.verify.tasks.MetricComputationError
319 Raised if an algorithmic or system error prevents calculation of
320 the metric.
321 lsst.pipe.base.NoWorkFound
322 Raised if the metric is ill-defined or otherwise inapplicable to
323 the database state. Typically this means that the pipeline step or
324 option being measured was not run.
326 Notes
327 -----
328 This implementation calls
329 `~lsst.verify.tasks.ApdbMetricConfig.dbLoader` to acquire a database
330 handle, then passes it and the value of
331 ``outputDataId`` to `makeMeasurement`. The result of `makeMeasurement`
332 is returned to the caller.
333 """
334 db = self.dbLoader.run(dbInfo[0]).apdb
336 if db is not None:
337 return Struct(measurement=self.makeMeasurement(db, outputDataId))
338 else:
339 raise NoWorkFound("No APDB to measure!")
341 def runQuantum(self, butlerQC, inputRefs, outputRefs):
342 """Do Butler I/O to provide in-memory objects for run.
344 This specialization of runQuantum passes the output data ID to `run`.
345 """
346 inputs = butlerQC.get(inputRefs)
347 outputs = self.run(**inputs,
348 outputDataId=outputRefs.measurement.dataId)
349 if outputs.measurement is not None:
350 butlerQC.put(outputs, outputRefs)
351 else:
352 self.log.debug("Skipping measurement of %r on %s "
353 "as not applicable.", self, inputRefs)