Coverage for python / lsst / verify / tasks / apdbMetricTask.py: 57%
26 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-14 23:51 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-14 23:51 +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", "ApdbMetricConnections"]
24import abc
26from lsst.pex.config import Field
27from lsst.pipe.base import NoWorkFound, Struct, connectionTypes
28from lsst.dax.apdb import Apdb
30from lsst.verify.tasks import MetricTask, MetricConfig, MetricConnections
33class ApdbMetricConnections(
34 MetricConnections,
35 dimensions={"instrument"},
36):
37 """An abstract connections class defining a database input.
39 Notes
40 -----
41 ``ApdbMetricConnections`` defines the following dataset templates:
42 ``package``
43 Name of the metric's namespace. By
44 :ref:`verify_metrics <verify-metrics-package>` convention, this is
45 the name of the package the metric is most closely
46 associated with.
47 ``metric``
48 Name of the metric, excluding any namespace.
49 """
50 dbInfo = connectionTypes.Input(
51 name="apdb_marker",
52 doc="The dataset(s) indicating that AP processing has finished for a "
53 "given data ID.",
54 storageClass="Config",
55 multiple=True,
56 minimum=1,
57 dimensions={"instrument", "visit", "detector"},
58 )
59 # Replaces MetricConnections.measurement, which is detector-level
60 measurement = connectionTypes.Output(
61 name="metricvalue_{package}_{metric}",
62 doc="The metric value computed by this task.",
63 storageClass="MetricValue",
64 dimensions={"instrument"},
65 )
68class ApdbMetricConfig(MetricConfig,
69 pipelineConnections=ApdbMetricConnections):
70 """A base class for APDB metric task configs.
71 """
72 apdb_config_url = Field(
73 dtype=str,
74 default=None,
75 optional=False,
76 doc="A config file specifying the APDB and its connection parameters, "
77 "typically written by the apdb-cli command-line utility.",
78 )
81class ApdbMetricTask(MetricTask):
82 """A base class for tasks that compute metrics from an alert production
83 database.
85 Parameters
86 ----------
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 `makeMeasurement`. You
94 should not need to override `run`.
95 """
96 ConfigClass = ApdbMetricConfig
98 @abc.abstractmethod
99 def makeMeasurement(self, dbHandle, outputDataId):
100 """Compute the metric from database data.
102 Parameters
103 ----------
104 dbHandle : `lsst.dax.apdb.Apdb`
105 A database instance.
106 outputDataId : any data ID type
107 The subset of the database to which this measurement applies.
108 May be empty to represent the entire dataset.
110 Returns
111 -------
112 measurement : `lsst.verify.Measurement` or `None`
113 The measurement corresponding to the input data.
115 Raises
116 ------
117 lsst.verify.tasks.MetricComputationError
118 Raised if an algorithmic or system error prevents calculation of
119 the metric. See `run` for expected behavior.
120 lsst.pipe.base.NoWorkFound
121 Raised if the metric is ill-defined or otherwise inapplicable to
122 the database state. Typically this means that the pipeline step or
123 option being measured was not run.
124 """
126 def run(self, dbInfo, outputDataId={}):
127 """Compute a measurement from a database.
129 Parameters
130 ----------
131 dbInfo : `list`
132 The datasets (of the type indicated by the config) from
133 which to load the database. If more than one dataset is provided
134 (as may be the case if DB writes are fine-grained), all are
135 assumed identical.
136 outputDataId: any data ID type, optional
137 The output data ID for the metric value. Defaults to the empty ID,
138 representing a value that covers the entire dataset.
140 Returns
141 -------
142 result : `lsst.pipe.base.Struct`
143 Result struct with component:
145 ``measurement``
146 the value of the metric (`lsst.verify.Measurement` or `None`)
148 Raises
149 ------
150 lsst.verify.tasks.MetricComputationError
151 Raised if an algorithmic or system error prevents calculation of
152 the metric.
153 lsst.pipe.base.NoWorkFound
154 Raised if the metric is ill-defined or otherwise inapplicable to
155 the database state. Typically this means that the pipeline step or
156 option being measured was not run.
158 Notes
159 -----
160 This implementation calls
161 `~lsst.verify.tasks.ApdbMetricConfig.dbLoader` to acquire a database
162 handle, then passes it and the value of
163 ``outputDataId`` to `makeMeasurement`. The result of `makeMeasurement`
164 is returned to the caller.
165 """
166 db = Apdb.from_uri(self.config.apdb_config_url)
168 if db is not None:
169 return Struct(measurement=self.makeMeasurement(db, outputDataId))
170 else:
171 raise NoWorkFound("No APDB to measure!")
173 def runQuantum(self, butlerQC, inputRefs, outputRefs):
174 """Do Butler I/O to provide in-memory objects for run.
176 This specialization of runQuantum passes the output data ID to `run`.
177 """
178 inputs = butlerQC.get(inputRefs)
179 outputs = self.run(**inputs,
180 outputDataId=outputRefs.measurement.dataId)
181 if outputs.measurement is not None:
182 butlerQC.put(outputs, outputRefs)
183 else:
184 self.log.debug("Skipping measurement of %r on %s "
185 "as not applicable.", self, inputRefs)