Coverage for tests/test_CentroidChecker.py: 28%
141 statements
« prev ^ index » next coverage.py v7.4.4, created at 2024-03-22 02:35 -0700
« prev ^ index » next coverage.py v7.4.4, created at 2024-03-22 02:35 -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
24import lsst.utils.tests
25import lsst.geom
26import lsst.meas.base
27import lsst.meas.base.tests
28from lsst.meas.base.tests import (AlgorithmTestCase)
29import lsst.pex.config
30from lsst.meas.base.pluginRegistry import register
31from lsst.meas.base.sfm import SingleFramePluginConfig, SingleFramePlugin
32from lsst.meas.base import FlagDefinitionList, FlagHandler
35class CentroiderConfig(SingleFramePluginConfig):
37 moveX = lsst.pex.config.Field(dtype=int, default=0,
38 doc="amount to re-position in X")
39 moveY = lsst.pex.config.Field(dtype=int, default=0,
40 doc="amount to re-position in Y")
41 dist = lsst.pex.config.Field(dtype=int, default=0,
42 doc="distance to allow centroid to be off")
43 setErrors = lsst.pex.config.Field(dtype=bool, default=False,
44 doc="set errors on measurement to errX, errY")
45 errX = lsst.pex.config.Field(dtype=float, default=0,
46 doc="uncertainty on X measurement")
47 errY = lsst.pex.config.Field(dtype=float, default=0,
48 doc="uncertainty on X measurement")
51@register("test_Centroider")
52class Centroider(SingleFramePlugin):
53 """Sample Python measurement plugin.
55 The flag handler for this plugin is created during construction, and is
56 called using the method `fail`. All plugins are required to implement
57 this method, which is used to set the flags in the output source record if
58 an error occurs.
59 """
60 ConfigClass = CentroiderConfig
61 # Class variables ErrEnum and FLAGDEFS are added by the decorator
63 @classmethod
64 def getExecutionOrder(cls):
65 return cls.CENTROID_ORDER
67 def __init__(self, config, name, schema, metadata):
68 SingleFramePlugin.__init__(self, config, name, schema, metadata)
70 flagDefs = FlagDefinitionList()
71 flagDefs.add("flag", "General Failure error")
72 flagDefs.add("test_flag", "second flag")
73 self.flagHandler = FlagHandler.addFields(schema, name, flagDefs)
75 if self.config.setErrors:
76 uncertainty = lsst.meas.base.UncertaintyEnum.SIGMA_ONLY
77 else:
78 uncertainty = lsst.meas.base.UncertaintyEnum.NO_UNCERTAINTY
80 self.centroidKey = lsst.meas.base.CentroidResultKey.addFields(schema, name, name, uncertainty)
82 if self.config.dist is None:
83 self.centroidChecker = lsst.meas.base.CentroidChecker(schema, name)
84 else:
85 self.centroidChecker = lsst.meas.base.CentroidChecker(schema, name, True, self.config.dist)
87 def measure(self, measRecord, exposure):
88 """This measure routine moves the centroid by design to create an error.
89 """
90 measRecord.set(self.centroidKey.getX(), measRecord.getX() + self.config.moveX)
91 measRecord.set(self.centroidKey.getY(), measRecord.getY() + self.config.moveY)
92 if self.centroidKey.getCentroidErr().isValid():
93 err = measRecord.get(self.centroidKey.getCentroidErr())
94 err[0][0] = self.config.errX
95 err[1][1] = self.config.errY
96 measRecord.set(self.centroidKey.getCentroidErr(), err)
97 self.centroidChecker(measRecord)
99 def fail(self, measRecord, error=None):
100 """Respond to measurement failures.
102 This routine responds to the standard failure call in baseMeasurement
103 If the exception is a MeasurementError, the error will be passed to
104 the fail method by the MeasurementFramework.
105 """
106 if error is None:
107 self.flagHandler.handleFailure(measRecord)
108 else:
109 self.flagHandler.handleFailure(measRecord, error.cpp)
112class CentroidCheckerTestCase(AlgorithmTestCase, lsst.utils.tests.TestCase):
114 # Setup a configuration and datasource to be used by the plugin tests
115 def setUp(self):
116 self.algName = "test_Centroider"
117 bbox = lsst.geom.Box2I(lsst.geom.Point2I(0, 0), lsst.geom.Point2I(100, 100))
118 self.dataset = lsst.meas.base.tests.TestDataset(bbox)
119 self.dataset.addSource(instFlux=1E5, centroid=lsst.geom.Point2D(25, 26))
121 def tearDown(self):
122 del self.dataset
124 def testNoError(self):
125 """Test that the ``resetToPeak`` flag is not set when no error seen.
126 """
127 schema = self.dataset.makeMinimalSchema()
128 config = self.makeSingleFrameMeasurementConfig(plugin=self.algName)
129 task = lsst.meas.base.SingleFrameMeasurementTask(schema=schema, config=config)
130 exposure, cat = self.dataset.realize(noise=100.0, schema=schema, randomSeed=0)
131 task.run(cat, exposure)
132 source = cat[0]
133 self.assertFalse(source.get("test_Centroider_flag"))
134 self.assertFalse(source.get("test_Centroider_flag_resetToPeak"))
136 def testCheckErrors(self):
137 """Test that centroids with invalid (NaN) errors are flagged.
138 """
139 def runMeasurement(errX, errY):
140 schema = self.dataset.makeMinimalSchema()
141 config = self.makeSingleFrameMeasurementConfig(plugin=self.algName)
142 config.plugins[self.algName].setErrors = True
143 config.plugins[self.algName].errX = errX
144 config.plugins[self.algName].errY = errY
145 task = lsst.meas.base.SingleFrameMeasurementTask(schema=schema, config=config)
146 exposure, cat = self.dataset.realize(noise=100.0, schema=schema, randomSeed=0)
147 task.run(cat, exposure)
148 return cat[0]
150 # Errors are real numbers: flags should not be set.
151 source = runMeasurement(1.0, 1.0)
152 self.assertFalse(source.get("test_Centroider_flag"))
153 self.assertFalse(source.get("test_Centroider_flag_badError"))
155 # Error on X is NaN: flags should be set.
156 source = runMeasurement(float('nan'), 1.0)
157 self.assertTrue(source.get("test_Centroider_flag"))
158 self.assertTrue(source.get("test_Centroider_flag_badError"))
160 # Error on Y is NaN: flags should be set.
161 source = runMeasurement(1.0, float('nan'))
162 self.assertTrue(source.get("test_Centroider_flag"))
163 self.assertTrue(source.get("test_Centroider_flag_badError"))
165 # Error on both X and Y is NaN: flags should be set.
166 source = runMeasurement(float('nan'), float('nan'))
167 self.assertTrue(source.get("test_Centroider_flag"))
168 self.assertTrue(source.get("test_Centroider_flag_badError"))
170 def testCentroidDistance(self):
171 """Test that a slight centroid movement triggers the distance error.
172 """
173 schema = self.dataset.makeMinimalSchema()
174 config = self.makeSingleFrameMeasurementConfig(plugin=self.algName)
175 config.plugins[self.algName].moveX = -2
176 config.plugins[self.algName].dist = 1
177 task = lsst.meas.base.SingleFrameMeasurementTask(schema=schema, config=config)
178 exposure, cat = self.dataset.realize(noise=100.0, schema=schema, randomSeed=1)
179 source = cat[0]
180 task.run(cat, exposure)
181 self.assertTrue(source.get("test_Centroider_flag"))
182 self.assertTrue(source.get("test_Centroider_flag_resetToPeak"))
183 self.assertEqual(source.getFootprint().getPeaks()[0].getFx(), source.get("test_Centroider_x"))
185 def testCentroidOutsideFootprint(self):
186 """A large centroid movement should trigger a move back to first peak.
187 """
188 schema = self.dataset.makeMinimalSchema()
189 config = self.makeSingleFrameMeasurementConfig(plugin=self.algName)
190 config.plugins[self.algName].moveX = -30
191 task = lsst.meas.base.SingleFrameMeasurementTask(schema=schema, config=config)
192 exposure, cat = self.dataset.realize(noise=100.0, schema=schema, randomSeed=2)
193 source = cat[0]
194 task.run(cat, exposure)
195 self.assertTrue(source.get("test_Centroider_flag"))
196 self.assertTrue(source.get("test_Centroider_flag_resetToPeak"))
197 self.assertEqual(source.getFootprint().getPeaks()[0].getFx(), source.get("test_Centroider_x"))
199 def testNaiveCentroid(self):
200 """Test the `NaiveCentroid` works with the ``maxDistance`` check.
201 """
202 schema = self.dataset.makeMinimalSchema()
203 self.algName = "base_NaiveCentroid"
204 config = self.makeSingleFrameMeasurementConfig(plugin=self.algName)
205 config.plugins[self.algName].maxDistToPeak = .0001
206 task = lsst.meas.base.SingleFrameMeasurementTask(schema=schema, config=config)
207 exposure, cat = self.dataset.realize(noise=100.0, schema=schema, randomSeed=3)
208 source = cat[0]
209 task.run(cat, exposure)
210 self.assertTrue(source.get("base_NaiveCentroid_flag"))
211 self.assertTrue(source.get("base_NaiveCentroid_flag_resetToPeak"))
213 def testSdssCentroid(self):
214 """Test the `SdssCentroid` works with the ``maxDistance`` check.
215 """
216 schema = self.dataset.makeMinimalSchema()
217 self.algName = "base_SdssCentroid"
218 config = self.makeSingleFrameMeasurementConfig(plugin=self.algName)
219 config.plugins[self.algName].maxDistToPeak = .0001
220 task = lsst.meas.base.SingleFrameMeasurementTask(schema=schema, config=config)
221 exposure, cat = self.dataset.realize(noise=100.0, schema=schema, randomSeed=4)
222 source = cat[0]
223 task.run(cat, exposure)
224 self.assertTrue(source.get("base_SdssCentroid_flag"))
225 self.assertTrue(source.get("base_SdssCentroid_flag_resetToPeak"))
228class TestMemory(lsst.utils.tests.MemoryTestCase):
229 pass
232def setup_module(module):
233 lsst.utils.tests.init()
236if __name__ == "__main__": 236 ↛ 237line 236 didn't jump to line 237, because the condition on line 236 was never true
237 lsst.utils.tests.init()
238 unittest.main()