Coverage for tests/test_PluginLogs.py: 25%
139 statements
« prev ^ index » next coverage.py v7.2.5, created at 2023-05-02 18:48 -0700
« prev ^ index » next coverage.py v7.2.5, created at 2023-05-02 18:48 -0700
1# This file is part of meas_base.
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 os
24import numpy
25import logging
27import lsst.geom
28import lsst.afw.table
29import lsst.daf.base
30import lsst.meas.base
31import lsst.utils.tests
32from lsst.meas.base.tests import (AlgorithmTestCase, )
33from lsst.meas.base.sfm import SingleFramePluginConfig, SingleFramePlugin
34from lsst.meas.base.forcedMeasurement import ForcedPlugin
35from lsst.meas.base.pluginRegistry import register
36from lsst.meas.base import FlagDefinitionList, FlagHandler, MeasurementError
38ROOT = os.path.abspath(os.path.dirname(__file__))
41class LoggingPluginConfig(SingleFramePluginConfig):
42 """Configuration for sample plugin.
43 """
44 pass
47@register("test_LoggingPlugin")
48class LoggingPlugin(SingleFramePlugin):
49 """Sample Python plugin which has an associated log name.
51 Notes
52 -----
53 The log name is provided to the plugin by the measurement task which is
54 running it. This requires that the `hasLogName` attribute must be a member
55 of the plugin class, and it must be `True`.
56 """
57 hasLogName = True
58 ConfigClass = LoggingPluginConfig
60 @classmethod
61 def getExecutionOrder(cls):
62 return cls.FLUX_ORDER
64 # The initializer for the class must accept an optional logName parameter.
65 def __init__(self, config, name, schema, metadata, logName=None):
66 SingleFramePlugin.__init__(self, config, name, schema, metadata, logName=logName)
67 flagDefs = FlagDefinitionList()
68 self.FAILURE = flagDefs.addFailureFlag()
69 self.CONTAINS_NAN = flagDefs.add("flag_containsNan", "Measurement area contains a nan")
70 self.flagHandler = FlagHandler.addFields(schema, name, flagDefs)
71 self.instFluxKey = schema.addField(name + "_instFlux", "F", doc="flux")
73 def measure(self, measRecord, exposure):
74 """Perform measurement.
76 Notes
77 -----
78 The `measure` method is called by the measurement framework when `run`
79 is called. If a `MeasurementError` is raised during this method, the
80 `fail` method will be called to set the error flags.
81 """
82 logging.getLogger(self.getLogName()).info("%s plugin measuring.", self.name)
83 # Sum the pixels inside the bounding box
84 centerPoint = lsst.geom.Point2I(int(measRecord.getX()), int(measRecord.getY()))
85 bbox = lsst.geom.Box2I(centerPoint, lsst.geom.Extent2I(1, 1))
86 instFlux = lsst.afw.image.ImageF(exposure.getMaskedImage().getImage(), bbox).getArray().sum()
87 measRecord.set(self.instFluxKey, instFlux)
89 # If there was a NaN inside the bounding box, the instFlux will still
90 # be NaN
91 if numpy.isnan(instFlux):
92 raise MeasurementError(self.CONTAINS_NAN.doc, self.CONTAINS_NAN.number)
94 def fail(self, measRecord, error=None):
95 """Handle measurement failures.
97 Notes
98 -----
99 If measurement raises a `MeasurementError`, the error will be passed
100 to the fail method by the measurement framework. If the error is not
101 `None`, ``error.cpp`` should correspond to a specific error and the
102 appropriate error flag will be set.
103 """
104 if error is None:
105 self.flagHandler.handleFailure(measRecord)
106 else:
107 self.flagHandler.handleFailure(measRecord, error.cpp)
110class RegisteredPluginsTestCase(AlgorithmTestCase, lsst.utils.tests.TestCase):
111 """Test all registered Plugins to see if their logName is set as expected.
113 Those which have the ``hasLogName=True`` attribute will have a ``logName``
114 parameter passed to their ``__init__``, and should set the internal
115 ``_logName`` attribute. If they are wrapped C++ algorithms, the
116 `getLogName` should also return same ``logName`` as the plugin.
117 """
118 def testSingleFramePlugins(self):
119 center = lsst.geom.Point2D(50, 50)
120 bbox = lsst.geom.Box2I(lsst.geom.Point2I(0, 0),
121 lsst.geom.Extent2I(100, 100))
122 dataset = lsst.meas.base.tests.TestDataset(bbox)
123 dataset.addSource(1000000.0, center)
124 registry = SingleFramePlugin.registry
125 dependencies = registry.keys()
126 task = self.makeSingleFrameMeasurementTask("base_SdssCentroid", dependencies=dependencies)
127 exposure, catalog = dataset.realize(noise=100.0, schema=task.schema, randomSeed=0)
128 task.log.setLevel(task.log.ERROR)
129 task.run(catalog, exposure)
130 for pluginName in dependencies:
131 plugin = task.plugins[pluginName]
132 if hasattr(plugin, "hasLogName") and plugin.hasLogName:
133 self.assertEqual(plugin.getLogName(), task.log.getChild(pluginName).name)
134 # if the plugin is cpp, check the cpp Algorithm as well
135 if hasattr(plugin, "cpp"):
136 self.assertEqual(plugin.cpp.getLogName(), plugin.getLogName())
137 else:
138 self.assertIsNone(plugin.getLogName())
140 def testForcedPlugins(self):
141 # Test all the ForcedPlugins registered to see if their logName is set
142 # as expected.
143 center = lsst.geom.Point2D(50, 50)
144 bbox = lsst.geom.Box2I(lsst.geom.Point2I(0, 0),
145 lsst.geom.Extent2I(100, 100))
146 dataset = lsst.meas.base.tests.TestDataset(bbox)
147 dataset.addSource(1000000.0, center)
148 registry = ForcedPlugin.registry
149 dependencies = registry.keys()
151 task = self.makeForcedMeasurementTask("base_SdssCentroid", dependencies=dependencies)
152 measWcs = dataset.makePerturbedWcs(dataset.exposure.getWcs(), randomSeed=1)
153 measDataset = dataset.transform(measWcs)
154 exposure, truthCatalog = measDataset.realize(10.0, measDataset.makeMinimalSchema(), randomSeed=1)
155 refCat = dataset.catalog
156 refWcs = dataset.exposure.getWcs()
157 measCat = task.generateMeasCat(exposure, refCat, refWcs)
158 task.attachTransformedFootprints(measCat, refCat, exposure, refWcs)
160 task.log.setLevel(task.log.ERROR)
161 task.run(measCat, exposure, refCat, refWcs)
162 for pluginName in dependencies:
163 plugin = task.plugins[pluginName]
164 if hasattr(plugin, "hasLogName") and plugin.hasLogName:
165 child_log = task.log.getChild(pluginName)
166 self.assertEqual(plugin.getLogName(), child_log.name)
167 # if the plugin is cpp, check the cpp Algorithm as well
168 if hasattr(plugin, "cpp"):
169 self.assertEqual(plugin.cpp.getLogName(), child_log.name)
170 else:
171 self.assertIsNone(plugin.getLogName())
174class LoggingPythonTestCase(AlgorithmTestCase, lsst.utils.tests.TestCase):
175 """Test one C++ and one Python plugin which have hasLogName=True.
176 """
177 def setUp(self):
178 bbox = lsst.geom.Box2I(lsst.geom.Point2I(0, 0), lsst.geom.Point2I(100, 100))
179 self.dataset = lsst.meas.base.tests.TestDataset(bbox)
180 self.dataset.addSource(instFlux=1E5, centroid=lsst.geom.Point2D(25, 25))
181 config = lsst.meas.base.SingleFrameMeasurementConfig()
182 config.slots.centroid = None
183 config.slots.apFlux = None
184 config.slots.calibFlux = None
185 config.slots.gaussianFlux = None
186 config.slots.modelFlux = None
187 config.slots.psfFlux = None
188 config.slots.shape = None
189 config.slots.psfShape = None
190 self.config = config
191 # Ensure that the C++ logs are forwarded to Python.
192 lsst.log.configure_pylog_MDC("INFO", MDC_class=None)
194 def tearDown(self):
195 del self.config
196 del self.dataset
198 def testLoggingPythonPlugin(self):
199 algName = "test_LoggingPlugin"
200 schema = self.dataset.makeMinimalSchema()
201 self.config.plugins = [algName]
202 task = lsst.meas.base.SingleFrameMeasurementTask(schema=schema, config=self.config)
203 # test that the plugin's logName has been propagated to the plugin
204 self.assertEqual(task.plugins[algName].getLogName(), task.log.getChild(algName).name)
205 log = task.log.getChild(algName)
206 with self.assertLogs(log.name) as cm:
207 exposure, cat = self.dataset.realize(noise=0.0, schema=schema, randomSeed=2)
208 task.run(cat, exposure)
209 lines = "\n".join(cm.output)
210 self.assertIn("measuring", lines)
212 def testLoggingCppPlugin(self):
213 # PsfFlux is known to log an ``ERROR`` if a Psf is not attached
214 algName = "base_PsfFlux"
215 self.config.plugins = [algName]
217 schema = self.dataset.makeMinimalSchema()
218 task = lsst.meas.base.SingleFrameMeasurementTask(schema=schema, config=self.config)
219 log = task.log.getChild(algName)
220 log.setLevel(log.ERROR)
222 with self.assertLogs(log.name, level="ERROR"):
223 exposure, cat = self.dataset.realize(noise=0.0, schema=schema, randomSeed=3)
224 exposure.setPsf(None)
225 # This call throws an error, so be prepared for it
226 try:
227 task.run(cat, exposure)
228 except Exception:
229 pass
232class TestMemory(lsst.utils.tests.MemoryTestCase):
233 pass
236def setup_module(module):
237 lsst.utils.tests.init()
240if __name__ == "__main__": 240 ↛ 241line 240 didn't jump to line 241, because the condition on line 240 was never true
241 lsst.utils.tests.init()
242 unittest.main()