Coverage for tests/test_PluginLogs.py: 29%

139 statements  

« prev     ^ index     » next       coverage.py v6.4.2, created at 2022-07-15 11:12 +0000

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/>. 

21 

22import unittest 

23import os 

24import numpy 

25import logging 

26 

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 

37 

38ROOT = os.path.abspath(os.path.dirname(__file__)) 

39 

40 

41class LoggingPluginConfig(SingleFramePluginConfig): 

42 """Configuration for sample plugin. 

43 """ 

44 pass 

45 

46 

47@register("test_LoggingPlugin") 

48class LoggingPlugin(SingleFramePlugin): 

49 """Sample Python plugin which has an associated log name. 

50 

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 

59 

60 @classmethod 

61 def getExecutionOrder(cls): 

62 return cls.FLUX_ORDER 

63 

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") 

72 

73 def measure(self, measRecord, exposure): 

74 """Perform measurement. 

75 

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) 

88 

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) 

93 

94 def fail(self, measRecord, error=None): 

95 """Handle measurement failures. 

96 

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) 

108 

109 

110class RegisteredPluginsTestCase(AlgorithmTestCase, lsst.utils.tests.TestCase): 

111 """Test all registered Plugins to see if their logName is set as expected. 

112 

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()) 

139 

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() 

150 

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) 

159 

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()) 

172 

173 

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) 

193 

194 def tearDown(self): 

195 del self.config 

196 del self.dataset 

197 

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) 

211 

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] 

216 

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) 

221 

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 

230 

231 

232class TestMemory(lsst.utils.tests.MemoryTestCase): 

233 pass 

234 

235 

236def setup_module(module): 

237 lsst.utils.tests.init() 

238 

239 

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()