Skip to content

Commit

Permalink
Add PPDB-based association metrics.
Browse files Browse the repository at this point in the history
  • Loading branch information
kfindeisen committed Feb 28, 2019
1 parent b059bd3 commit 5a6fb62
Show file tree
Hide file tree
Showing 3 changed files with 183 additions and 4 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
.. lsst-task-topic:: lsst.ap.association.metrics.TotalUnassociatedDiaObjectsMetricTask

#####################################
TotalUnassociatedDiaObjectsMetricTask
#####################################

``TotalUnassociatedDiaObjectsMetricTask`` computes the number of DIAObjects that have only a single source (as the ``ap_association.totalUnassociatedDiaObjects`` metric).
It requires a prompt products database as input, and is meaningful only at dataset-level granularity.

.. _lsst.ap.association.metrics.TotalUnassociatedDiaObjectsMetricTask-summary:

Processing summary
==================

``TotalUnassociatedDiaObjectsMetricTask`` queries the database (through `~lsst.dax.ppdb.Ppdb`) for the number of DIAObjects with exactly one source.

.. _lsst.ap.association.metrics.TotalUnassociatedDiaObjectsMetricTask-api:

Python API summary
==================

.. lsst-task-api-summary:: lsst.ap.association.metrics.TotalUnassociatedDiaObjectsMetricTask

.. _lsst.ap.association.metrics.TotalUnassociatedDiaObjectsMetricTask-butler:

Butler datasets
===============

Input datasets
--------------

:lsst-config-field:`~lsst.verify.PpdbMetricTask.PpdbMetricConfig.dbInfo`
The Butler dataset from which the database connection can be initialized.
The type must match the input required by the :lsst-config-field:`~lsst.verify.ppdbMetricTask.PpdbMetricConfig.dbLoader` subtask (default: the top-level science task's config).
If the input is a config, its name **must** be explicitly configured when running ``TotalUnassociatedDiaObjectsMetricTask`` or a :lsst-task:`~lsst.verify.gen2tasks.MetricsControllerTask` that contains it.

.. _lsst.ap.association.metrics.TotalUnassociatedDiaObjectsMetricTask-subtasks:

Retargetable subtasks
=====================

.. lsst-task-config-subtasks:: lsst.ap.association.metrics.TotalUnassociatedDiaObjectsMetricTask

.. _lsst.ap.association.metrics.TotalUnassociatedDiaObjectsMetricTask-configs:

Configuration fields
====================

.. lsst-task-config-fields:: lsst.ap.association.metrics.TotalUnassociatedDiaObjectsMetricTask
54 changes: 53 additions & 1 deletion python/lsst/ap/association/metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,15 @@
__all__ = ["NumberNewDiaObjectsMetricTask",
"NumberUnassociatedDiaObjectsMetricTask",
"FractionUpdatedDiaObjectsMetricTask",
"TotalUnassociatedDiaObjectsMetricTask"
]


import astropy.units as u

from lsst.verify import Measurement, MetadataMetricTask, MetricComputationError
from lsst.dax.ppdb import countUnassociatedObjects

from lsst.verify import Measurement, MetadataMetricTask, PpdbMetricTask, MetricComputationError
from lsst.verify.gen2tasks import register


Expand Down Expand Up @@ -199,3 +202,52 @@ def getInputMetadataKeys(cls, config):
@classmethod
def getOutputMetricName(cls, config):
return "ap_association.fracUpdatedDiaObjects"


@register("totalUnassociatedDiaObjects")
class TotalUnassociatedDiaObjectsMetricTask(PpdbMetricTask):
"""Task that computes the number of DIAObjects with only one
associated DIASource.
"""
_DefaultName = "totalUnassociatedDiaObjects"

def makeMeasurement(self, dbHandle, outputDataId):
"""Compute the number of unassociated DIAObjects.
Parameters
----------
dbHandle : `lsst.dax.ppdb.Ppdb`
A database instance.
outputDataId : any data ID type
The subset of the database to which this measurement applies.
Must be empty, as the number of unassociated sources is
ill-defined for subsets of the dataset.
Returns
-------
measurement : `lsst.verify.Measurement`
The total number of unassociated objects.
Raises
------
MetricComputationError
Raised on any failure to query the database.
ValueError
Raised if outputDataId is not empty
"""
# All data ID types define keys()
if outputDataId.keys():
raise ValueError("%s must not be associated with specific data IDs."
% self.getOutputMetricName(self.config))

try:
nUnassociatedDiaObjects = countUnassociatedObjects(dbHandle)
except Exception as e:
raise MetricComputationError("Could not get unassociated objects from database") from e

meas = Measurement(self.getOutputMetricName(self.config), nUnassociatedDiaObjects * u.count)
return meas

@classmethod
def getOutputMetricName(cls, config):
return "ap_association.totalUnassociatedDiaObjects"
84 changes: 81 additions & 3 deletions tests/test_metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,25 @@
# along with this program. If not, see <https://www.gnu.org/licenses/>.

import unittest
import unittest.mock

import astropy.units as u
from astropy.tests.helper import assert_quantity_allclose

import lsst.utils.tests
from lsst.pex.config import Config
from lsst.daf.base import PropertySet
from lsst.verify import Name, MetricComputationError
from lsst.dax.ppdb import Ppdb
from lsst.pipe.base import Task, Struct
from lsst.verify import Name, Measurement, MetricComputationError
from lsst.verify.gen2tasks.testUtils import MetricTaskTestCase
from lsst.verify.testUtils import MetadataMetricTestCase
from lsst.verify.testUtils import MetadataMetricTestCase, PpdbMetricTestCase

from lsst.ap.association.metrics import \
NumberNewDiaObjectsMetricTask, \
NumberUnassociatedDiaObjectsMetricTask, \
FractionUpdatedDiaObjectsMetricTask
FractionUpdatedDiaObjectsMetricTask, \
TotalUnassociatedDiaObjectsMetricTask


def _makeAssociationMetadata():
Expand Down Expand Up @@ -245,9 +250,82 @@ def testCoarseGrainedMetric(self):
assert_quantity_allclose(measMany.quantity, measDirect.quantity)


class TestTotalUnassociatedObjects(PpdbMetricTestCase):

@staticmethod
def _makePpdb():
ppdb = unittest.mock.Mock(Ppdb)
return ppdb

@classmethod
def makeTask(cls):
class SimpleDbLoader(Task):
ConfigClass = Config

def run(self, dummy):
if dummy is not None:
return Struct(ppdb=cls._makePpdb())
else:
return Struct(ppdb=None)

config = TotalUnassociatedDiaObjectsMetricTask.ConfigClass()
config.dbLoader.retarget(SimpleDbLoader)
return TotalUnassociatedDiaObjectsMetricTask(config=config)

def setUp(self):
super().setUp()
# Do the patch here to avoid passing extra arguments to superclass tests
patcher = unittest.mock.patch("lsst.ap.association.metrics.countUnassociatedObjects", return_value=42)
self.mockCounter = patcher.start()
self.addCleanup(patcher.stop)

def testValid(self):
result = self.task.adaptArgsAndRun({"dbInfo": "DB source"}, {"dbInfo": {}}, {"measurement": {}})
meas = result.measurement

self.assertEqual(meas.metric_name, Name(metric="ap_association.totalUnassociatedDiaObjects"))
nObjects = self.mockCounter(self._makePpdb())
self.assertEqual(meas.quantity, nObjects * u.count)

def testMissingData(self):
result = self.task.adaptArgsAndRun({"dbInfo": None}, {"dbInfo": {}}, {"measurement": {}})
meas = result.measurement
self.assertIsNone(meas)

def testFineGrainedMetric(self):
with self.assertRaises(ValueError):
self.task.adaptArgsAndRun({"dbInfo": "DB source"}, {"dbInfo": {}}, {"measurement": {"visit": 42}})

def testGetInputDatasetTypes(self):
config = self.taskClass.ConfigClass()
config.dbInfo.name = "absolutely anything"
types = self.taskClass.getInputDatasetTypes(config)
# dict.keys() is a collections.abc.Set, which has a narrower interface than __builtins__.set... WTF???
self.assertSetEqual(set(types.keys()), {"dbInfo"})
self.assertEqual(types["dbInfo"], "absolutely anything")

# Override because of non-standard adaptArgsAndRun
def testCallAddStandardMetadata(self):
dummy = Measurement('foo.bar', 0.0)
with unittest.mock.patch.multiple(
self.taskClass, autospec=True,
makeMeasurement=unittest.mock.DEFAULT,
addStandardMetadata=unittest.mock.DEFAULT) as mockDict:
mockDict['makeMeasurement'].return_value = Struct(measurement=dummy)

dataId = {}
result = self.task.adaptArgsAndRun(
{"dbInfo": "DB Source"},
{"dbInfo": dataId},
{'measurement': dataId})
mockDict['addStandardMetadata'].assert_called_once_with(
self.task, result.measurement, dataId)


# Hack around unittest's hacky test setup system
del MetricTaskTestCase
del MetadataMetricTestCase
del PpdbMetricTestCase


class MemoryTester(lsst.utils.tests.MemoryTestCase):
Expand Down

0 comments on commit 5a6fb62

Please sign in to comment.