Coverage for tests/test_SdssCentroid.py: 26%
115 statements
« prev ^ index » next coverage.py v6.4.2, created at 2022-07-21 03:08 -0700
« prev ^ index » next coverage.py v6.4.2, created at 2022-07-21 03:08 -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 numpy as np
26import lsst.geom
27from lsst.meas.base.tests import (AlgorithmTestCase, CentroidTransformTestCase,
28 SingleFramePluginTransformSetupHelper)
29import lsst.afw.geom
30import lsst.utils.tests
32# N.B. Some tests here depend on the noise realization in the test data
33# or from the numpy random number generator.
34# For the current test data and seed value, they pass, but they may not
35# if the test data is regenerated or the seed value changes. I've marked
36# these with an "rng dependent" comment. In most cases, they test that
37# the measured instFlux lies within 2 sigma of the correct value, which we
38# should expect to fail sometimes.
41class SdssCentroidTestCase(AlgorithmTestCase, lsst.utils.tests.TestCase):
43 def setUp(self):
44 self.center = lsst.geom.Point2D(50.1, 49.8)
45 self.bbox = lsst.geom.Box2I(lsst.geom.Point2I(-20, -30),
46 lsst.geom.Extent2I(140, 160))
47 self.dataset = lsst.meas.base.tests.TestDataset(self.bbox)
48 self.dataset.addSource(100000.0, self.center)
50 def tearDown(self):
52 del self.center
53 del self.bbox
54 del self.dataset
56 def makeAlgorithm(self, ctrl=None):
57 """Construct an algorithm and return both it and its schema.
58 """
59 if ctrl is None:
60 ctrl = lsst.meas.base.SdssCentroidControl()
61 schema = lsst.meas.base.tests.TestDataset.makeMinimalSchema()
62 algorithm = lsst.meas.base.SdssCentroidAlgorithm(ctrl, "base_SdssCentroid", schema)
63 return algorithm, schema
65 def testSingleFramePlugin(self):
66 """Test calling the algorithm through the plugin interface.
67 """
68 task = self.makeSingleFrameMeasurementTask("base_SdssCentroid")
69 exposure, catalog = self.dataset.realize(10.0, task.schema, randomSeed=0)
70 task.run(catalog, exposure)
71 record = catalog[0]
72 self.assertFalse(record.get("base_SdssCentroid_flag"))
73 self.assertFalse(record.get("base_SdssCentroid_flag_edge"))
74 self.assertFloatsAlmostEqual(record.get("base_SdssCentroid_x"), record.get("truth_x"), rtol=0.005)
75 self.assertFloatsAlmostEqual(record.get("base_SdssCentroid_y"), record.get("truth_y"), rtol=0.005)
77 def testMonteCarlo(self):
78 """Test an ideal simulation, with no noise.
80 Demonstrate that:
82 - We get exactly the right answer, and
83 - The reported uncertainty agrees with a Monte Carlo test of the noise.
84 """
85 algorithm, schema = self.makeAlgorithm()
86 exposure, catalog = self.dataset.realize(0.0, schema, randomSeed=1)
87 record = catalog[0]
88 x = record.get("truth_x")
89 y = record.get("truth_y")
90 instFlux = record.get("truth_instFlux")
91 algorithm.measure(record, exposure)
92 self.assertFloatsAlmostEqual(record.get("base_SdssCentroid_x"), x, rtol=1E-4)
93 self.assertFloatsAlmostEqual(record.get("base_SdssCentroid_y"), y, rtol=1E-4)
94 for noise in (0.001, 0.01):
95 xList = []
96 yList = []
97 xErrList = []
98 yErrList = []
99 nSamples = 1000
100 for repeat in range(nSamples):
101 # By using ``repeat`` to seed the RNG, we get results which
102 # fall within the tolerances defined below. If we allow this
103 # test to be truly random, passing becomes RNG-dependent.
104 exposure, catalog = self.dataset.realize(noise*instFlux, schema, randomSeed=repeat)
105 record = catalog[0]
106 algorithm.measure(record, exposure)
107 xList.append(record.get("base_SdssCentroid_x"))
108 yList.append(record.get("base_SdssCentroid_y"))
109 xErrList.append(record.get("base_SdssCentroid_xErr"))
110 yErrList.append(record.get("base_SdssCentroid_yErr"))
111 xMean = np.mean(xList)
112 yMean = np.mean(yList)
113 xErrMean = np.mean(xErrList)
114 yErrMean = np.mean(yErrList)
115 xStandardDeviation = np.std(xList)
116 yStandardDeviation = np.std(yList)
117 self.assertFloatsAlmostEqual(xErrMean, xStandardDeviation, rtol=0.2) # rng dependent
118 self.assertFloatsAlmostEqual(yErrMean, yStandardDeviation, rtol=0.2) # rng dependent
119 self.assertLess(abs(xMean - x), 3.0*xErrMean / nSamples**0.5) # rng dependent
120 self.assertLess(abs(yMean - y), 3.0*yErrMean / nSamples**0.5) # rng dependent
122 def testEdge(self):
123 task = self.makeSingleFrameMeasurementTask("base_SdssCentroid")
124 exposure, catalog = self.dataset.realize(10.0, task.schema, randomSeed=2)
125 psfImage = exposure.getPsf().computeImage(self.center)
126 # construct a box that won't fit the full PSF model
127 bbox = psfImage.getBBox()
128 bbox.grow(-5)
129 subImage = lsst.afw.image.ExposureF(exposure, bbox)
130 # we also need to install a smaller footprint, or NoiseReplacer
131 # complains before we even get to measuring the centroid
132 record = catalog[0]
133 spanSet = lsst.afw.geom.SpanSet(bbox)
134 newFootprint = lsst.afw.detection.Footprint(spanSet)
135 peak = record.getFootprint().getPeaks()[0]
136 newFootprint.addPeak(peak.getFx(), peak.getFy(), peak.getPeakValue())
137 record.setFootprint(newFootprint)
138 # just measure the one object we've prepared for
139 task.measure(catalog, subImage)
140 self.assertTrue(record.get("base_SdssCentroid_flag"))
141 self.assertTrue(record.get("base_SdssCentroid_flag_edge"))
143 def testNo2ndDerivative(self):
144 task = self.makeSingleFrameMeasurementTask("base_SdssCentroid")
145 exposure, catalog = self.dataset.realize(10.0, task.schema, randomSeed=3)
146 # cutout a subimage around object in the test image
147 bbox = lsst.geom.Box2I(lsst.geom.Point2I(self.center), lsst.geom.Extent2I(1, 1))
148 bbox.grow(20)
149 subImage = lsst.afw.image.ExposureF(exposure, bbox)
150 # A completely flat image will trigger the no 2nd derivative error
151 subImage.getMaskedImage().getImage().getArray()[:] = 0
152 task.measure(catalog, subImage)
153 self.assertTrue(catalog[0].get("base_SdssCentroid_flag"))
154 self.assertTrue(catalog[0].get("base_SdssCentroid_flag_noSecondDerivative"))
156 def testNotAtMaximum(self):
157 task = self.makeSingleFrameMeasurementTask("base_SdssCentroid")
158 exposure, catalog = self.dataset.realize(10.0, task.schema, randomSeed=4)
159 # cutout a subimage around the object in the test image
160 bbox = lsst.geom.Box2I(lsst.geom.Point2I(self.center), lsst.geom.Extent2I(1, 1))
161 bbox.grow(20)
162 subImage = lsst.afw.image.ExposureF(exposure, bbox)
163 # zero out the central region, which will destroy the maximum
164 subImage.getMaskedImage().getImage().getArray()[18:22, 18:22] = 0
165 task.measure(catalog, subImage)
166 self.assertTrue(catalog[0].get("base_SdssCentroid_flag"))
167 self.assertTrue(catalog[0].get("base_SdssCentroid_flag_notAtMaximum"))
170class SdssCentroidTransformTestCase(CentroidTransformTestCase,
171 SingleFramePluginTransformSetupHelper,
172 lsst.utils.tests.TestCase):
173 controlClass = lsst.meas.base.SdssCentroidControl
174 algorithmClass = lsst.meas.base.SdssCentroidAlgorithm
175 transformClass = lsst.meas.base.SdssCentroidTransform
176 flagNames = ('flag', 'flag_edge', 'flag_badData')
177 singleFramePlugins = ('base_SdssCentroid',)
178 forcedPlugins = ('base_SdssCentroid',)
181class TestMemory(lsst.utils.tests.MemoryTestCase):
182 pass
185def setup_module(module):
186 lsst.utils.tests.init()
189if __name__ == "__main__": 189 ↛ 190line 189 didn't jump to line 190, because the condition on line 189 was never true
190 lsst.utils.tests.init()
191 unittest.main()