Coverage for tests/test_metrics.py: 32%
255 statements
« prev ^ index » next coverage.py v6.4.1, created at 2022-06-26 01:44 -0700
« prev ^ index » next coverage.py v6.4.1, created at 2022-06-26 01:44 -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# (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/>.
22import unittest
23import unittest.mock
25import astropy.units as u
27import lsst.utils.tests
28from lsst.pex.config import Config
29from lsst.daf.base import PropertySet
30from lsst.dax.apdb import Apdb
31from lsst.pipe.base import Task, Struct
32import lsst.pipe.base.testUtils
33from lsst.verify import Name
34from lsst.verify.gen2tasks.testUtils import MetricTaskTestCase
35from lsst.verify.tasks import MetricComputationError
36from lsst.verify.tasks.testUtils import MetadataMetricTestCase, ApdbMetricTestCase
38from lsst.ap.association.metrics import \
39 NumberNewDiaObjectsMetricTask, \
40 NumberUnassociatedDiaObjectsMetricTask, \
41 FractionUpdatedDiaObjectsMetricTask, \
42 NumberSolarSystemObjectsMetricTask, \
43 NumberAssociatedSolarSystemObjectsMetricTask, \
44 TotalUnassociatedDiaObjectsMetricTask
47def _makeAssociationMetadata(numUpdated=27, numNew=4, numUnassociated=15, numSso=5, numAssocSso=1):
48 metadata = PropertySet()
49 metadata.add("association.numUpdatedDiaObjects", numUpdated)
50 metadata.add("association.numNewDiaObjects", numNew)
51 metadata.add("association.numTotalSolarSystemObjects", numSso)
52 metadata.add("association.numAssociatedSsObjects", numAssocSso)
53 metadata.add("association.numUnassociatedDiaObjects", numUnassociated)
54 return metadata
57class TestNewDiaObjects(MetadataMetricTestCase):
59 @classmethod
60 def makeTask(cls):
61 return NumberNewDiaObjectsMetricTask()
63 def testValid(self):
64 metadata = _makeAssociationMetadata()
65 result = self.task.run(metadata)
66 lsst.pipe.base.testUtils.assertValidOutput(self.task, result)
67 meas = result.measurement
69 self.assertEqual(meas.metric_name, Name(metric="ap_association.numNewDiaObjects"))
70 self.assertEqual(meas.quantity, metadata.getAsDouble("association.numNewDiaObjects") * u.count)
72 def testNoNew(self):
73 metadata = _makeAssociationMetadata(numNew=0)
74 result = self.task.run(metadata)
75 lsst.pipe.base.testUtils.assertValidOutput(self.task, result)
76 meas = result.measurement
78 self.assertEqual(meas.metric_name, Name(metric="ap_association.numNewDiaObjects"))
79 self.assertEqual(meas.quantity, 0.0 * u.count)
81 def testMissingData(self):
82 result = self.task.run(None)
83 lsst.pipe.base.testUtils.assertValidOutput(self.task, result)
84 meas = result.measurement
85 self.assertIsNone(meas)
87 def testAssociationFailed(self):
88 result = self.task.run(PropertySet())
89 lsst.pipe.base.testUtils.assertValidOutput(self.task, result)
90 meas = result.measurement
91 self.assertIsNone(meas)
93 def testBadlyTypedKeys(self):
94 metadata = _makeAssociationMetadata()
95 metadata.set("association.numNewDiaObjects", "Ultimate Answer")
97 with self.assertRaises(MetricComputationError):
98 self.task.run(metadata)
101class TestUnassociatedDiaObjects(MetadataMetricTestCase):
103 @classmethod
104 def makeTask(cls):
105 return NumberUnassociatedDiaObjectsMetricTask()
107 def testValid(self):
108 metadata = _makeAssociationMetadata()
109 result = self.task.run(metadata)
110 lsst.pipe.base.testUtils.assertValidOutput(self.task, result)
111 meas = result.measurement
113 self.assertEqual(meas.metric_name, Name(metric="ap_association.numUnassociatedDiaObjects"))
114 self.assertEqual(meas.quantity,
115 metadata.getAsDouble("association.numUnassociatedDiaObjects") * u.count)
117 def testAllUpdated(self):
118 metadata = _makeAssociationMetadata(numUnassociated=0)
119 result = self.task.run(metadata)
120 lsst.pipe.base.testUtils.assertValidOutput(self.task, result)
121 meas = result.measurement
123 self.assertEqual(meas.metric_name, Name(metric="ap_association.numUnassociatedDiaObjects"))
124 self.assertEqual(meas.quantity, 0.0 * u.count)
126 def testMissingData(self):
127 result = self.task.run(None)
128 lsst.pipe.base.testUtils.assertValidOutput(self.task, result)
129 meas = result.measurement
130 self.assertIsNone(meas)
132 def testAssociationFailed(self):
133 result = self.task.run(PropertySet())
134 lsst.pipe.base.testUtils.assertValidOutput(self.task, result)
135 meas = result.measurement
136 self.assertIsNone(meas)
138 def testBadlyTypedKeys(self):
139 metadata = _makeAssociationMetadata()
140 metadata.set("association.numUnassociatedDiaObjects", "Ultimate Answer")
142 with self.assertRaises(MetricComputationError):
143 self.task.run(metadata)
146class TestFracUpdatedDiaObjects(MetadataMetricTestCase):
148 @classmethod
149 def makeTask(cls):
150 return FractionUpdatedDiaObjectsMetricTask()
152 def testValid(self):
153 metadata = _makeAssociationMetadata()
154 result = self.task.run(metadata)
155 lsst.pipe.base.testUtils.assertValidOutput(self.task, result)
156 meas = result.measurement
158 self.assertEqual(meas.metric_name, Name(metric="ap_association.fracUpdatedDiaObjects"))
159 nUpdated = metadata.getAsDouble("association.numUpdatedDiaObjects")
160 nTotal = metadata.getAsDouble("association.numUnassociatedDiaObjects") + nUpdated
161 self.assertEqual(meas.quantity, nUpdated / nTotal * u.dimensionless_unscaled)
163 def testNoUpdated(self):
164 metadata = _makeAssociationMetadata(numUpdated=0)
165 result = self.task.run(metadata)
166 lsst.pipe.base.testUtils.assertValidOutput(self.task, result)
167 meas = result.measurement
169 self.assertEqual(meas.metric_name, Name(metric="ap_association.fracUpdatedDiaObjects"))
170 self.assertEqual(meas.quantity, 0.0 * u.dimensionless_unscaled)
172 def testAllUpdated(self):
173 metadata = _makeAssociationMetadata(numUnassociated=0)
174 result = self.task.run(metadata)
175 lsst.pipe.base.testUtils.assertValidOutput(self.task, result)
176 meas = result.measurement
178 self.assertEqual(meas.metric_name, Name(metric="ap_association.fracUpdatedDiaObjects"))
179 self.assertEqual(meas.quantity, 1.0 * u.dimensionless_unscaled)
181 def testNoObjects(self):
182 metadata = _makeAssociationMetadata(numUpdated=0, numUnassociated=0)
183 result = self.task.run(metadata)
184 lsst.pipe.base.testUtils.assertValidOutput(self.task, result)
185 meas = result.measurement
187 self.assertIsNone(meas)
189 def testMissingData(self):
190 result = self.task.run(None)
191 lsst.pipe.base.testUtils.assertValidOutput(self.task, result)
192 meas = result.measurement
193 self.assertIsNone(meas)
195 def testAssociationFailed(self):
196 result = self.task.run(PropertySet())
197 lsst.pipe.base.testUtils.assertValidOutput(self.task, result)
198 meas = result.measurement
199 self.assertIsNone(meas)
201 def testBadlyTypedKeys(self):
202 metadata = _makeAssociationMetadata()
203 metadata.set("association.numUnassociatedDiaObjects", "Ultimate Answer")
205 with self.assertRaises(MetricComputationError):
206 self.task.run(metadata)
209class TestNumberSolarSystemObjects(MetadataMetricTestCase):
211 @classmethod
212 def makeTask(cls):
213 return NumberSolarSystemObjectsMetricTask()
215 def testValid(self):
216 metadata = _makeAssociationMetadata()
217 result = self.task.run(metadata)
218 lsst.pipe.base.testUtils.assertValidOutput(self.task, result)
219 meas = result.measurement
221 self.assertEqual(meas.metric_name, Name(metric="ap_association.numTotalSolarSystemObjects"))
222 self.assertEqual(meas.quantity,
223 metadata.getAsDouble("association.numTotalSolarSystemObjects") * u.count)
225 def testAllUpdated(self):
226 metadata = _makeAssociationMetadata(numSso=0)
227 result = self.task.run(metadata)
228 lsst.pipe.base.testUtils.assertValidOutput(self.task, result)
229 meas = result.measurement
231 self.assertEqual(meas.metric_name, Name(metric="ap_association.numTotalSolarSystemObjects"))
232 self.assertEqual(meas.quantity, 0.0 * u.count)
234 def testMissingData(self):
235 result = self.task.run(None)
236 lsst.pipe.base.testUtils.assertValidOutput(self.task, result)
237 meas = result.measurement
238 self.assertIsNone(meas)
240 def testAssociationFailed(self):
241 result = self.task.run(PropertySet())
242 lsst.pipe.base.testUtils.assertValidOutput(self.task, result)
243 meas = result.measurement
244 self.assertIsNone(meas)
246 def testBadlyTypedKeys(self):
247 metadata = _makeAssociationMetadata()
248 metadata.set("association.numTotalSolarSystemObjects", "Ultimate Answer")
250 with self.assertRaises(MetricComputationError):
251 self.task.run(metadata)
254class TestNumberAssociatedSolarSystemObjects(MetadataMetricTestCase):
256 @classmethod
257 def makeTask(cls):
258 return NumberAssociatedSolarSystemObjectsMetricTask()
260 def testValid(self):
261 metadata = _makeAssociationMetadata()
262 result = self.task.run(metadata)
263 lsst.pipe.base.testUtils.assertValidOutput(self.task, result)
264 meas = result.measurement
266 self.assertEqual(meas.metric_name, Name(metric="ap_association.numAssociatedSsObjects"))
267 self.assertEqual(meas.quantity,
268 metadata.getAsDouble("association.numAssociatedSsObjects") * u.count)
270 def testAllUpdated(self):
271 metadata = _makeAssociationMetadata(numAssocSso=0)
272 result = self.task.run(metadata)
273 lsst.pipe.base.testUtils.assertValidOutput(self.task, result)
274 meas = result.measurement
276 self.assertEqual(meas.metric_name, Name(metric="ap_association.numAssociatedSsObjects"))
277 self.assertEqual(meas.quantity, 0.0 * u.count)
279 def testMissingData(self):
280 result = self.task.run(None)
281 lsst.pipe.base.testUtils.assertValidOutput(self.task, result)
282 meas = result.measurement
283 self.assertIsNone(meas)
285 def testAssociationFailed(self):
286 result = self.task.run(PropertySet())
287 lsst.pipe.base.testUtils.assertValidOutput(self.task, result)
288 meas = result.measurement
289 self.assertIsNone(meas)
291 def testBadlyTypedKeys(self):
292 metadata = _makeAssociationMetadata()
293 metadata.set("association.numAssociatedSsObjects", "Ultimate Answer")
295 with self.assertRaises(MetricComputationError):
296 self.task.run(metadata)
299class TestTotalUnassociatedObjects(ApdbMetricTestCase):
301 @staticmethod
302 def _makeApdb(dummy_dbInfo):
303 """Create a dummy apdb.
305 We don't have access to the apdb in the task directly so mocking
306 return values is difficult. We thus make use of the dummy dbInfo
307 that is passed to the init task to pass values to the apdb object
308 instantiated.
309 """
310 apdb = unittest.mock.Mock(Apdb)
311 test_value = dummy_dbInfo["test_value"]
312 apdb.countUnassociatedObjects = unittest.mock.MagicMock(
313 return_value=test_value)
314 return apdb
316 @classmethod
317 def makeTask(cls):
318 class SimpleDbLoader(Task):
319 ConfigClass = Config
321 def run(self, dummy):
322 if dummy is not None:
323 return Struct(apdb=cls._makeApdb(dummy))
324 else:
325 return Struct(apdb=None)
327 config = TotalUnassociatedDiaObjectsMetricTask.ConfigClass()
328 config.dbLoader.retarget(SimpleDbLoader)
329 return TotalUnassociatedDiaObjectsMetricTask(config=config)
331 @classmethod
332 def makeDbInfo(cls):
333 return {"test_value": "whatever"}
335 def setUp(self):
336 super().setUp()
338 def testValid(self):
339 result = self.task.run([{"test_value": 42}])
340 lsst.pipe.base.testUtils.assertValidOutput(self.task, result)
341 meas = result.measurement
343 self.assertEqual(meas.metric_name, Name(metric="ap_association.totalUnassociatedDiaObjects"))
344 self.assertEqual(meas.quantity, 42 * u.count)
346 def testAllAssociated(self):
347 result = self.task.run([{"test_value": 0}])
348 lsst.pipe.base.testUtils.assertValidOutput(self.task, result)
349 meas = result.measurement
351 self.assertEqual(meas.metric_name, Name(metric="ap_association.totalUnassociatedDiaObjects"))
352 self.assertEqual(meas.quantity, 0.0 * u.count)
354 def testMissingData(self):
355 result = self.task.run(None)
356 lsst.pipe.base.testUtils.assertValidOutput(self.task, result)
357 meas = result.measurement
358 self.assertIsNone(meas)
360 def testFineGrainedMetric(self):
361 with self.assertRaises(ValueError):
362 self.task.run([self.makeDbInfo()], outputDataId={"visit": 42})
365# Hack around unittest's hacky test setup system
366del MetricTaskTestCase
367del MetadataMetricTestCase
368del ApdbMetricTestCase
371class MemoryTester(lsst.utils.tests.MemoryTestCase):
372 pass
375def setup_module(module):
376 lsst.utils.tests.init()
379if __name__ == "__main__": 379 ↛ 380line 379 didn't jump to line 380, because the condition on line 379 was never true
380 lsst.utils.tests.init()
381 unittest.main()