Coverage for tests/test_metrics.py: 31%
242 statements
« prev ^ index » next coverage.py v7.3.2, created at 2023-11-01 11:16 +0000
« prev ^ index » next coverage.py v7.3.2, created at 2023-11-01 11:16 +0000
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.tasks import MetricComputationError
35from lsst.verify.tasks.testUtils import MetricTaskTestCase, MetadataMetricTestCase, ApdbMetricTestCase
37from lsst.ap.association.metrics import \
38 NumberNewDiaObjectsMetricTask, \
39 NumberUnassociatedDiaObjectsMetricTask, \
40 FractionUpdatedDiaObjectsMetricTask, \
41 NumberSolarSystemObjectsMetricTask, \
42 NumberAssociatedSolarSystemObjectsMetricTask, \
43 TotalUnassociatedDiaObjectsMetricTask
46def _makeAssociationMetadata(numUpdated=27, numNew=4, numUnassociated=15, numSso=5, numAssocSso=1):
47 metadata = PropertySet()
48 metadata.add("association.numUpdatedDiaObjects", numUpdated)
49 metadata.add("association.numNewDiaObjects", numNew)
50 metadata.add("association.numTotalSolarSystemObjects", numSso)
51 metadata.add("association.numAssociatedSsObjects", numAssocSso)
52 metadata.add("association.numUnassociatedDiaObjects", numUnassociated)
53 return metadata
56class TestNewDiaObjects(MetadataMetricTestCase):
58 @classmethod
59 def makeTask(cls):
60 return NumberNewDiaObjectsMetricTask()
62 def testValid(self):
63 metadata = _makeAssociationMetadata()
64 result = self.task.run(metadata)
65 lsst.pipe.base.testUtils.assertValidOutput(self.task, result)
66 meas = result.measurement
68 self.assertEqual(meas.metric_name, Name(metric="ap_association.numNewDiaObjects"))
69 self.assertEqual(meas.quantity, metadata.getAsDouble("association.numNewDiaObjects") * u.count)
71 def testNoNew(self):
72 metadata = _makeAssociationMetadata(numNew=0)
73 result = self.task.run(metadata)
74 lsst.pipe.base.testUtils.assertValidOutput(self.task, result)
75 meas = result.measurement
77 self.assertEqual(meas.metric_name, Name(metric="ap_association.numNewDiaObjects"))
78 self.assertEqual(meas.quantity, 0.0 * u.count)
80 def testAssociationFailed(self):
81 try:
82 result = self.task.run(PropertySet())
83 except lsst.pipe.base.NoWorkFound:
84 # Correct behavior
85 pass
86 else:
87 # Alternative correct behavior
88 lsst.pipe.base.testUtils.assertValidOutput(self.task, result)
89 meas = result.measurement
90 self.assertIsNone(meas)
92 def testBadlyTypedKeys(self):
93 metadata = _makeAssociationMetadata()
94 metadata.set("association.numNewDiaObjects", "Ultimate Answer")
96 with self.assertRaises(MetricComputationError):
97 self.task.run(metadata)
100class TestUnassociatedDiaObjects(MetadataMetricTestCase):
102 @classmethod
103 def makeTask(cls):
104 return NumberUnassociatedDiaObjectsMetricTask()
106 def testValid(self):
107 metadata = _makeAssociationMetadata()
108 result = self.task.run(metadata)
109 lsst.pipe.base.testUtils.assertValidOutput(self.task, result)
110 meas = result.measurement
112 self.assertEqual(meas.metric_name, Name(metric="ap_association.numUnassociatedDiaObjects"))
113 self.assertEqual(meas.quantity,
114 metadata.getAsDouble("association.numUnassociatedDiaObjects") * u.count)
116 def testAllUpdated(self):
117 metadata = _makeAssociationMetadata(numUnassociated=0)
118 result = self.task.run(metadata)
119 lsst.pipe.base.testUtils.assertValidOutput(self.task, result)
120 meas = result.measurement
122 self.assertEqual(meas.metric_name, Name(metric="ap_association.numUnassociatedDiaObjects"))
123 self.assertEqual(meas.quantity, 0.0 * u.count)
125 def testAssociationFailed(self):
126 try:
127 result = self.task.run(PropertySet())
128 except lsst.pipe.base.NoWorkFound:
129 # Correct behavior
130 pass
131 else:
132 # Alternative correct behavior
133 lsst.pipe.base.testUtils.assertValidOutput(self.task, result)
134 meas = result.measurement
135 self.assertIsNone(meas)
137 def testBadlyTypedKeys(self):
138 metadata = _makeAssociationMetadata()
139 metadata.set("association.numUnassociatedDiaObjects", "Ultimate Answer")
141 with self.assertRaises(MetricComputationError):
142 self.task.run(metadata)
145class TestFracUpdatedDiaObjects(MetadataMetricTestCase):
147 @classmethod
148 def makeTask(cls):
149 return FractionUpdatedDiaObjectsMetricTask()
151 def testValid(self):
152 metadata = _makeAssociationMetadata()
153 result = self.task.run(metadata)
154 lsst.pipe.base.testUtils.assertValidOutput(self.task, result)
155 meas = result.measurement
157 self.assertEqual(meas.metric_name, Name(metric="ap_association.fracUpdatedDiaObjects"))
158 nUpdated = metadata.getAsDouble("association.numUpdatedDiaObjects")
159 nTotal = metadata.getAsDouble("association.numUnassociatedDiaObjects") + nUpdated
160 self.assertEqual(meas.quantity, nUpdated / nTotal * u.dimensionless_unscaled)
162 def testNoUpdated(self):
163 metadata = _makeAssociationMetadata(numUpdated=0)
164 result = self.task.run(metadata)
165 lsst.pipe.base.testUtils.assertValidOutput(self.task, result)
166 meas = result.measurement
168 self.assertEqual(meas.metric_name, Name(metric="ap_association.fracUpdatedDiaObjects"))
169 self.assertEqual(meas.quantity, 0.0 * u.dimensionless_unscaled)
171 def testAllUpdated(self):
172 metadata = _makeAssociationMetadata(numUnassociated=0)
173 result = self.task.run(metadata)
174 lsst.pipe.base.testUtils.assertValidOutput(self.task, result)
175 meas = result.measurement
177 self.assertEqual(meas.metric_name, Name(metric="ap_association.fracUpdatedDiaObjects"))
178 self.assertEqual(meas.quantity, 1.0 * u.dimensionless_unscaled)
180 def testNoObjects(self):
181 metadata = _makeAssociationMetadata(numUpdated=0, numUnassociated=0)
182 try:
183 result = self.task.run(metadata)
184 except lsst.pipe.base.NoWorkFound:
185 # Correct behavior
186 pass
187 else:
188 # Alternative correct behavior
189 lsst.pipe.base.testUtils.assertValidOutput(self.task, result)
190 meas = result.measurement
191 self.assertIsNone(meas)
193 def testAssociationFailed(self):
194 try:
195 result = self.task.run(PropertySet())
196 except lsst.pipe.base.NoWorkFound:
197 # Correct behavior
198 pass
199 else:
200 # Alternative correct behavior
201 lsst.pipe.base.testUtils.assertValidOutput(self.task, result)
202 meas = result.measurement
203 self.assertIsNone(meas)
205 def testBadlyTypedKeys(self):
206 metadata = _makeAssociationMetadata()
207 metadata.set("association.numUnassociatedDiaObjects", "Ultimate Answer")
209 with self.assertRaises(MetricComputationError):
210 self.task.run(metadata)
213class TestNumberSolarSystemObjects(MetadataMetricTestCase):
215 @classmethod
216 def makeTask(cls):
217 return NumberSolarSystemObjectsMetricTask()
219 def testValid(self):
220 metadata = _makeAssociationMetadata()
221 result = self.task.run(metadata)
222 lsst.pipe.base.testUtils.assertValidOutput(self.task, result)
223 meas = result.measurement
225 self.assertEqual(meas.metric_name, Name(metric="ap_association.numTotalSolarSystemObjects"))
226 self.assertEqual(meas.quantity,
227 metadata.getAsDouble("association.numTotalSolarSystemObjects") * u.count)
229 def testAllUpdated(self):
230 metadata = _makeAssociationMetadata(numSso=0)
231 result = self.task.run(metadata)
232 lsst.pipe.base.testUtils.assertValidOutput(self.task, result)
233 meas = result.measurement
235 self.assertEqual(meas.metric_name, Name(metric="ap_association.numTotalSolarSystemObjects"))
236 self.assertEqual(meas.quantity, 0.0 * u.count)
238 def testAssociationFailed(self):
239 try:
240 result = self.task.run(PropertySet())
241 except lsst.pipe.base.NoWorkFound:
242 # Correct behavior
243 pass
244 else:
245 # Alternative correct behavior
246 lsst.pipe.base.testUtils.assertValidOutput(self.task, result)
247 meas = result.measurement
248 self.assertIsNone(meas)
250 def testBadlyTypedKeys(self):
251 metadata = _makeAssociationMetadata()
252 metadata.set("association.numTotalSolarSystemObjects", "Ultimate Answer")
254 with self.assertRaises(MetricComputationError):
255 self.task.run(metadata)
258class TestNumberAssociatedSolarSystemObjects(MetadataMetricTestCase):
260 @classmethod
261 def makeTask(cls):
262 return NumberAssociatedSolarSystemObjectsMetricTask()
264 def testValid(self):
265 metadata = _makeAssociationMetadata()
266 result = self.task.run(metadata)
267 lsst.pipe.base.testUtils.assertValidOutput(self.task, result)
268 meas = result.measurement
270 self.assertEqual(meas.metric_name, Name(metric="ap_association.numAssociatedSsObjects"))
271 self.assertEqual(meas.quantity,
272 metadata.getAsDouble("association.numAssociatedSsObjects") * u.count)
274 def testAllUpdated(self):
275 metadata = _makeAssociationMetadata(numAssocSso=0)
276 result = self.task.run(metadata)
277 lsst.pipe.base.testUtils.assertValidOutput(self.task, result)
278 meas = result.measurement
280 self.assertEqual(meas.metric_name, Name(metric="ap_association.numAssociatedSsObjects"))
281 self.assertEqual(meas.quantity, 0.0 * u.count)
283 def testAssociationFailed(self):
284 try:
285 result = self.task.run(PropertySet())
286 except lsst.pipe.base.NoWorkFound:
287 # Correct behavior
288 pass
289 else:
290 # Alternative correct behavior
291 lsst.pipe.base.testUtils.assertValidOutput(self.task, result)
292 meas = result.measurement
293 self.assertIsNone(meas)
295 def testBadlyTypedKeys(self):
296 metadata = _makeAssociationMetadata()
297 metadata.set("association.numAssociatedSsObjects", "Ultimate Answer")
299 with self.assertRaises(MetricComputationError):
300 self.task.run(metadata)
303class TestTotalUnassociatedObjects(ApdbMetricTestCase):
305 @staticmethod
306 def _makeApdb(dummy_dbInfo):
307 """Create a dummy apdb.
309 We don't have access to the apdb in the task directly so mocking
310 return values is difficult. We thus make use of the dummy dbInfo
311 that is passed to the init task to pass values to the apdb object
312 instantiated.
313 """
314 apdb = unittest.mock.Mock(Apdb)
315 test_value = dummy_dbInfo["test_value"]
316 apdb.countUnassociatedObjects = unittest.mock.MagicMock(
317 return_value=test_value)
318 return apdb
320 @classmethod
321 def makeTask(cls):
322 class SimpleDbLoader(Task):
323 ConfigClass = Config
325 def run(self, dummy):
326 if dummy is not None:
327 return Struct(apdb=cls._makeApdb(dummy))
328 else:
329 return Struct(apdb=None)
331 config = TotalUnassociatedDiaObjectsMetricTask.ConfigClass()
332 config.dbLoader.retarget(SimpleDbLoader)
333 return TotalUnassociatedDiaObjectsMetricTask(config=config)
335 @classmethod
336 def makeDbInfo(cls):
337 return {"test_value": "whatever"}
339 def setUp(self):
340 super().setUp()
342 def testValid(self):
343 result = self.task.run([{"test_value": 42}])
344 lsst.pipe.base.testUtils.assertValidOutput(self.task, result)
345 meas = result.measurement
347 self.assertEqual(meas.metric_name, Name(metric="ap_association.totalUnassociatedDiaObjects"))
348 self.assertEqual(meas.quantity, 42 * u.count)
350 def testAllAssociated(self):
351 result = self.task.run([{"test_value": 0}])
352 lsst.pipe.base.testUtils.assertValidOutput(self.task, result)
353 meas = result.measurement
355 self.assertEqual(meas.metric_name, Name(metric="ap_association.totalUnassociatedDiaObjects"))
356 self.assertEqual(meas.quantity, 0.0 * u.count)
358 def testFineGrainedMetric(self):
359 with self.assertRaises(ValueError):
360 self.task.run([self.makeDbInfo()], outputDataId={"visit": 42})
363# Hack around unittest's hacky test setup system
364del MetricTaskTestCase
365del MetadataMetricTestCase
366del ApdbMetricTestCase
369class MemoryTester(lsst.utils.tests.MemoryTestCase):
370 pass
373def setup_module(module):
374 lsst.utils.tests.init()
377if __name__ == "__main__": 377 ↛ 378line 377 didn't jump to line 378, because the condition on line 377 was never true
378 lsst.utils.tests.init()
379 unittest.main()