Coverage for python/lsst/ap/association/metrics.py: 47%
119 statements
« prev ^ index » next coverage.py v6.4.4, created at 2022-09-15 03:38 -0700
« prev ^ index » next coverage.py v6.4.4, created at 2022-09-15 03:38 -0700
1# This file is part of ap_association.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (http://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 <http://www.gnu.org/licenses/>.
21#
23__all__ = ["NumberNewDiaObjectsMetricTask",
24 "NumberUnassociatedDiaObjectsMetricTask",
25 "FractionUpdatedDiaObjectsMetricTask",
26 "NumberSolarSystemObjectsMetricTask",
27 "NumberAssociatedSolarSystemObjectsMetricTask",
28 "TotalUnassociatedDiaObjectsMetricTask",
29 ]
32import astropy.units as u
34from lsst.verify import Measurement
35from lsst.verify.tasks import MetadataMetricTask, MetadataMetricConfig, \
36 ApdbMetricTask, ApdbMetricConfig, MetricComputationError
39class NumberNewDiaObjectsMetricConfig(MetadataMetricConfig):
40 def setDefaults(self):
41 self.connections.package = "ap_association"
42 self.connections.metric = "numNewDiaObjects"
45class NumberNewDiaObjectsMetricTask(MetadataMetricTask):
46 """Task that computes the number of DIASources that create new DIAObjects
47 in an image, visit, etc..
48 """
49 _DefaultName = "numNewDiaObjects"
50 ConfigClass = NumberNewDiaObjectsMetricConfig
52 def makeMeasurement(self, values):
53 """Compute the number of new DIAObjects.
55 Parameters
56 ----------
57 values : `dict` [`str`, `int` or `None`]
58 A `dict` representation of the metadata. Each `dict` has the
59 following key:
61 ``"newObjects"``
62 The number of new objects created for this image (`int`
63 or `None`). May be `None` if the image was not successfully
64 associated.
66 Returns
67 -------
68 measurement : `lsst.verify.Measurement` or `None`
69 The total number of new objects.
70 """
71 if values["newObjects"] is not None:
72 try:
73 nNew = int(values["newObjects"])
74 except (ValueError, TypeError) as e:
75 raise MetricComputationError("Corrupted value of numNewDiaObjects") from e
76 else:
77 return Measurement(self.config.metricName, nNew * u.count)
78 else:
79 self.log.info("Nothing to do: no association results found.")
80 return None
82 @classmethod
83 def getInputMetadataKeys(cls, config):
84 return {"newObjects": ".numNewDiaObjects"}
87class NumberUnassociatedDiaObjectsMetricConfig(MetadataMetricConfig):
88 def setDefaults(self):
89 self.connections.package = "ap_association"
90 self.connections.metric = "numUnassociatedDiaObjects"
93class NumberUnassociatedDiaObjectsMetricTask(MetadataMetricTask):
94 """Task that computes the number of previously-known DIAObjects that do
95 not have detected DIASources in an image, visit, etc..
96 """
97 _DefaultName = "numUnassociatedDiaObjects"
98 ConfigClass = NumberUnassociatedDiaObjectsMetricConfig
100 def makeMeasurement(self, values):
101 """Compute the number of non-updated DIAObjects.
103 Parameters
104 ----------
105 values : `dict` [`str`, `int` or `None`]
106 A `dict` representation of the metadata. Each `dict` has the
107 following key:
109 ``"unassociatedObjects"``
110 The number of DIAObjects not associated with a DiaSource in
111 this image (`int` or `None`). May be `None` if the image was
112 not successfully associated.
114 Returns
115 -------
116 measurement : `lsst.verify.Measurement` or `None`
117 The total number of unassociated objects.
118 """
119 if values["unassociatedObjects"] is not None:
120 try:
121 nNew = int(values["unassociatedObjects"])
122 except (ValueError, TypeError) as e:
123 raise MetricComputationError("Corrupted value of numUnassociatedDiaObjects") from e
124 else:
125 return Measurement(self.config.metricName, nNew * u.count)
126 else:
127 self.log.info("Nothing to do: no association results found.")
128 return None
130 @classmethod
131 def getInputMetadataKeys(cls, config):
132 return {"unassociatedObjects": ".numUnassociatedDiaObjects"}
135class FractionUpdatedDiaObjectsMetricConfig(MetadataMetricConfig):
136 def setDefaults(self):
137 self.connections.package = "ap_association"
138 self.connections.metric = "fracUpdatedDiaObjects"
141class FractionUpdatedDiaObjectsMetricTask(MetadataMetricTask):
142 """Task that computes the fraction of previously created DIAObjects that
143 have a new association in this image, visit, etc..
144 """
145 _DefaultName = "fracUpdatedDiaObjects"
146 ConfigClass = FractionUpdatedDiaObjectsMetricConfig
148 def makeMeasurement(self, values):
149 """Compute the number of non-updated DIAObjects.
151 AssociationTask reports each pre-existing DIAObject as either updated
152 (associated with a new DIASource) or unassociated.
154 Parameters
155 ----------
156 values : `dict` [`str`, `int` or `None`]
157 A `dict` representation of the metadata. Each `dict` has the
158 following keys:
160 ``"updatedObjects"``
161 The number of DIAObjects updated for this image (`int` or
162 `None`). May be `None` if the image was not
163 successfully associated.
164 ``"unassociatedObjects"``
165 The number of DIAObjects not associated with a DiaSource in
166 this image (`int` or `None`). May be `None` if the image was
167 not successfully associated.
169 Returns
170 -------
171 measurement : `lsst.verify.Measurement` or `None`
172 The total number of unassociated objects.
173 """
174 if values["updatedObjects"] is not None \
175 and values["unassociatedObjects"] is not None:
176 try:
177 nUpdated = int(values["updatedObjects"])
178 nUnassociated = int(values["unassociatedObjects"])
179 except (ValueError, TypeError) as e:
180 raise MetricComputationError("Corrupted value of numUpdatedDiaObjects "
181 "or numUnassociatedDiaObjects") from e
182 else:
183 if nUpdated <= 0 and nUnassociated <= 0:
184 return None # No pre-existing DIAObjects; no fraction to compute
185 else:
186 fraction = nUpdated / (nUpdated + nUnassociated)
187 return Measurement(self.config.metricName, fraction * u.dimensionless_unscaled)
188 else:
189 self.log.info("Nothing to do: no association results found.")
190 return None
192 @classmethod
193 def getInputMetadataKeys(cls, config):
194 return {"updatedObjects": ".numUpdatedDiaObjects",
195 "unassociatedObjects": ".numUnassociatedDiaObjects"}
198class NumberSolarSystemObjectsMetricConfig(MetadataMetricConfig):
199 def setDefaults(self):
200 self.connections.package = "ap_association"
201 self.connections.metric = "numTotalSolarSystemObjects"
204class NumberSolarSystemObjectsMetricTask(MetadataMetricTask):
205 """Task that computes the number of SolarSystemObjects that are
206 observable within this detectorVisit.
207 """
208 _DefaultName = "numTotalSolarSystemObjects"
209 ConfigClass = NumberSolarSystemObjectsMetricConfig
211 def makeMeasurement(self, values):
212 """Compute the total number of SolarSystemObjects within a
213 detectorVisit.
215 Parameters
216 ----------
217 values : `dict` [`str`, `int` or `None`]
218 A `dict` representation of the metadata. Each `dict` has the
219 following key:
221 ``"numTotalSolarSystemObjects"``
222 The number of SolarSystemObjects within the observable detector
223 area (`int` or `None`). May be `None` if solar system
224 association was not attempted or the image was not
225 successfully associated.
227 Returns
228 -------
229 measurement : `lsst.verify.Measurement` or `None`
230 The total number of Solar System objects.
231 """
232 if values["numTotalSolarSystemObjects"] is not None:
233 try:
234 nNew = int(values["numTotalSolarSystemObjects"])
235 except (ValueError, TypeError) as e:
236 raise MetricComputationError("Corrupted value of numTotalSolarSystemObjects") from e
237 else:
238 return Measurement(self.config.metricName, nNew * u.count)
239 else:
240 self.log.info("Nothing to do: no solar system results found.")
241 return None
243 @classmethod
244 def getInputMetadataKeys(cls, config):
245 return {"numTotalSolarSystemObjects": ".numTotalSolarSystemObjects"}
248class NumberAssociatedSolarSystemObjectsMetricConfig(MetadataMetricConfig):
249 def setDefaults(self):
250 self.connections.package = "ap_association"
251 self.connections.metric = "numAssociatedSsObjects"
254class NumberAssociatedSolarSystemObjectsMetricTask(MetadataMetricTask):
255 """Number of SolarSystemObjects that were associated with new DiaSources
256 for this detectorVisit.
257 """
258 _DefaultName = "numAssociatedSsObjects"
259 ConfigClass = NumberAssociatedSolarSystemObjectsMetricConfig
261 def makeMeasurement(self, values):
262 """Compute the number of associated SolarSystemObjects.
264 Parameters
265 ----------
266 values : `dict` [`str`, `int` or `None`]
267 A `dict` representation of the metadata. Each `dict` has the
268 following key:
270 ``"numAssociatedSsObjects"``
271 The number of successfully associated SolarSystem Objects
272 (`int` or `None`). May be `None` if solar system association
273 was not attempted or the image was not successfully associated.
275 Returns
276 -------
277 measurement : `lsst.verify.Measurement` or `None`
278 The total number of associated SolarSystemObjects.
279 """
280 if values["numAssociatedSsObjects"] is not None:
281 try:
282 nNew = int(values["numAssociatedSsObjects"])
283 except (ValueError, TypeError) as e:
284 raise MetricComputationError("Corrupted value of numAssociatedSsObjects") from e
285 else:
286 return Measurement(self.config.metricName, nNew * u.count)
287 else:
288 self.log.info("Nothing to do: no solar system results found.")
289 return None
291 @classmethod
292 def getInputMetadataKeys(cls, config):
293 return {"numAssociatedSsObjects": ".numAssociatedSsObjects"}
296class TotalUnassociatedDiaObjectsMetricConfig(ApdbMetricConfig):
297 def setDefaults(self):
298 self.connections.package = "ap_association"
299 self.connections.metric = "totalUnassociatedDiaObjects"
302class TotalUnassociatedDiaObjectsMetricTask(ApdbMetricTask):
303 """Task that computes the number of DIAObjects with only one
304 associated DIASource.
305 """
306 _DefaultName = "totalUnassociatedDiaObjects"
307 ConfigClass = TotalUnassociatedDiaObjectsMetricConfig
309 def makeMeasurement(self, dbHandle, outputDataId):
310 """Compute the number of unassociated DIAObjects.
312 Parameters
313 ----------
314 dbHandle : `lsst.dax.apdb.Apdb`
315 A database instance.
316 outputDataId : any data ID type
317 The subset of the database to which this measurement applies.
318 Must be empty, as the number of unassociated sources is
319 ill-defined for subsets of the dataset.
321 Returns
322 -------
323 measurement : `lsst.verify.Measurement`
324 The total number of unassociated objects.
326 Raises
327 ------
328 MetricComputationError
329 Raised on any failure to query the database.
330 ValueError
331 Raised if outputDataId is not empty
332 """
333 # All data ID types define keys()
334 if outputDataId.keys() - {'instrument'}:
335 raise ValueError("%s must not be associated with specific data IDs (gave %s)."
336 % (self.config.metricName, outputDataId))
338 try:
339 nUnassociatedDiaObjects = dbHandle.countUnassociatedObjects()
340 except Exception as e:
341 raise MetricComputationError("Could not get unassociated objects from database") from e
343 meas = Measurement(self.config.metricName, nUnassociatedDiaObjects * u.count)
344 return meas