Coverage for tests/test_measureApCorr.py: 19%
150 statements
« prev ^ index » next coverage.py v6.4.2, created at 2022-07-15 03:05 -0700
« prev ^ index » next coverage.py v6.4.2, created at 2022-07-15 03:05 -0700
1#
2# LSST Data Management System
3#
4# Copyright 2008-2016 AURA/LSST.
5#
6# This product includes software developed by the
7# LSST Project (http://www.lsst.org/).
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 LSST License Statement and
20# the GNU General Public License along with this program. If not,
21# see <https://www.lsstcorp.org/LegalNotices/>.
22#
23import unittest
24import numpy as np
26import lsst.geom
27import lsst.afw.image as afwImage
28import lsst.afw.table as afwTable
29from lsst.afw.math import ChebyshevBoundedField
30import lsst.pex.config
31import lsst.meas.algorithms.measureApCorr as measureApCorr
32from lsst.meas.base.apCorrRegistry import addApCorrName
33import lsst.meas.base.tests
34import lsst.utils.tests
37def apCorrDefaultMap(value=None, bbox=None):
38 default_coefficients = np.ones((1, 1), dtype=float)
39 default_coefficients /= value
40 default_apCorrMap = ChebyshevBoundedField(bbox, default_coefficients)
41 default_fill = afwImage.ImageF(bbox)
42 default_apCorrMap.fillImage(default_fill)
43 return(default_fill)
46class MeasureApCorrTestCase(lsst.meas.base.tests.AlgorithmTestCase, lsst.utils.tests.TestCase):
48 def makeCatalog(self, apCorrScale=1.0, numSources=5):
49 sourceCat = afwTable.SourceCatalog(self.schema)
50 inputFilterFlagKey = self.schema.find(self.meas_apCorr_task.config.sourceSelector.active.field).key
52 centroidKey = afwTable.Point2DKey(self.schema["slot_Centroid"])
53 x = np.random.rand(numSources)*self.exposure.getWidth() + self.exposure.getX0()
54 y = np.random.rand(numSources)*self.exposure.getHeight() + self.exposure.getY0()
55 for _i in range(numSources):
56 source_test_instFlux = 5.1
57 source_test_centroid = lsst.geom.Point2D(x[_i], y[_i])
58 source = sourceCat.addNew()
59 source.set(centroidKey, source_test_centroid)
60 source.set(inputFilterFlagKey, True)
62 for name in self.names:
63 fluxName = name + "_instFlux"
64 flagName = name + "_flag"
65 fluxErrName = name + "_instFluxErr"
66 apFluxName = name + self.apNameStr + "_instFlux"
67 apFlagName = name + self.apNameStr + "_flag"
68 apFluxErrName = name + self.apNameStr + "_instFluxErr"
69 fluxKey = self.schema.find(fluxName).key
70 flagKey = self.schema.find(flagName).key
71 fluxErrKey = self.schema.find(fluxErrName).key
72 apFluxKey = self.schema.find(apFluxName).key
73 apFlagKey = self.schema.find(apFlagName).key
74 apFluxErrKey = self.schema.find(apFluxErrName).key
75 for source in sourceCat:
76 source.set(fluxKey, source_test_instFlux)
77 source.set(apFluxKey, source_test_instFlux * apCorrScale)
78 source.set(fluxErrKey, 0.)
79 source.set(apFluxErrKey, 0.)
80 source.set(flagKey, False)
81 source.set(apFlagKey, False)
82 return(sourceCat)
84 def setUp(self):
85 schema = afwTable.SourceTable.makeMinimalSchema()
86 apNameStr = "Ap"
87 calib_flag_name = "cal_source_use"
88 # Add fields in anti-sorted order to try to impose a need for sorting
89 # in the addition of the apCorr fields (may happen by fluke, but this
90 # is the best we can do to test this here.
91 names = ["test2", "test1"]
92 for name in names:
93 apName = name + apNameStr
94 addApCorrName(apName)
95 schema.addField(name + "_instFlux", type=float)
96 schema.addField(name + "_instFluxErr", type=float)
97 schema.addField(name + "_flag", type="Flag")
98 schema.addField(apName + "_instFlux", type=float)
99 schema.addField(apName + "_instFluxErr", type=float)
100 schema.addField(apName + "_flag", type="Flag")
101 schema.addField(names[0] + "_Centroid_x", type=float)
102 schema.addField(names[0] + "_Centroid_y", type=float)
103 schema.getAliasMap().set("slot_Centroid", names[0] + "_Centroid")
104 schema.addField(calib_flag_name, type="Flag")
105 config = measureApCorr.MeasureApCorrTask.ConfigClass()
106 config.refFluxName = names[0]
107 config.sourceSelector.active.field = calib_flag_name
108 self.meas_apCorr_task = measureApCorr.MeasureApCorrTask(schema=schema, config=config)
109 self.names = names
110 self.apNameStr = apNameStr
111 self.schema = schema
112 self.exposure = lsst.afw.image.ExposureF(10, 10)
114 def tearDown(self):
115 del self.schema
116 del self.meas_apCorr_task
117 del self.exposure
119 def testAddFields(self):
120 """Instantiating the task should add one field to the schema."""
121 for name in self.names:
122 self.assertIn("apcorr_" + name + self.apNameStr + "_used", self.schema.getNames())
123 sortedNames = sorted(self.names)
124 key0 = self.schema.find("apcorr_" + sortedNames[0] + self.apNameStr + "_used").key
125 key1 = self.schema.find("apcorr_" + sortedNames[1] + self.apNameStr + "_used").key
126 # Check that the apCorr fields were added in a sorted order (not
127 # foolproof as this could have happened by fluke, but it's the best
128 # we can do to test this here (having added the two fields in an anti-
129 # sorted order).
130 self.assertLess(key0.getOffset() + key0.getBit(), key1.getOffset() + key1.getBit())
132 def testReturnApCorrMap(self):
133 """The measureApCorr task should return a structure with a single key 'apCorrMap'."""
134 struct = self.meas_apCorr_task.run(catalog=self.makeCatalog(), exposure=self.exposure)
135 self.assertEqual(list(struct.getDict().keys()), ['apCorrMap'])
137 def testApCorrMapKeys(self):
138 """An apCorrMap structure should have two keys per name supplied to addApCorrName()."""
139 key_names = []
140 for name in self.names:
141 apFluxName = name + self.apNameStr + "_instFlux"
142 apFluxErrName = name + self.apNameStr + "_instFluxErr"
143 struct = self.meas_apCorr_task.run(catalog=self.makeCatalog(), exposure=self.exposure)
144 key_names.append(apFluxName)
145 key_names.append(apFluxErrName)
146 self.assertEqual(set(struct.apCorrMap.keys()), set(key_names))
148 def testTooFewSources(self):
149 """ If there are too few sources, check that an exception is raised."""
150 catalog = afwTable.SourceCatalog(self.schema)
151 with self.assertRaises(RuntimeError):
152 self.meas_apCorr_task.run(catalog=catalog, exposure=self.exposure)
153 # With the measurement algorithm declared as something that might fail, should not get an exception
154 for name in self.names:
155 self.meas_apCorr_task.config.allowFailure.append(name + self.apNameStr)
156 self.meas_apCorr_task.run(catalog=catalog, exposure=self.exposure)
158 def testSourceNotUsed(self):
159 """ Check that a source outside the bounding box is flagged as not used (False)."""
160 fluxName = self.names[0] + "_instFlux"
161 apCorrFlagKey = self.schema.find("apcorr_" + self.names[0] + "_used").key
162 sourceCat = self.makeCatalog()
163 source = sourceCat.addNew()
164 source_test_instFlux = 5.1
165 source_test_centroid = lsst.geom.Point2D(15, 7.1)
166 fluxKey = self.schema.find(fluxName).key
167 centroidKey = afwTable.Point2DKey(self.schema["slot_Centroid"])
168 source.set(fluxKey, source_test_instFlux)
169 source.set(centroidKey, source_test_centroid)
170 self.meas_apCorr_task.run(catalog=sourceCat, exposure=self.exposure)
171 self.assertFalse(sourceCat[apCorrFlagKey][-1])
173 def testSourceUsed(self):
174 """Check that valid sources inside the bounding box that are used have their flags set to True."""
175 inputFilterFlagKey = self.schema.find(self.meas_apCorr_task.config.sourceSelector.active.field).key
176 sourceCat = self.makeCatalog()
177 self.meas_apCorr_task.run(catalog=sourceCat, exposure=self.exposure)
178 self.assertTrue(sourceCat[inputFilterFlagKey].all())
180 def testApertureMeasOnes(self):
181 """ Check that sources with aperture fluxes exactly the same as their catalog fluxes
182 returns an aperture correction map of 1s"""
183 apFluxName = self.names[0] + self.apNameStr + "_instFlux"
184 sourceCat = self.makeCatalog()
185 struct = self.meas_apCorr_task.run(catalog=sourceCat, exposure=self.exposure)
186 default_fill = apCorrDefaultMap(value=1.0, bbox=self.exposure.getBBox())
187 test_fill = afwImage.ImageF(self.exposure.getBBox())
188 struct.apCorrMap[apFluxName].fillImage(test_fill)
189 np.testing.assert_allclose(test_fill.getArray(), default_fill.getArray())
191 def testApertureMeasTens(self):
192 """Check that aperture correction scales source fluxes in the correct direction."""
193 apCorr_factor = 10.
194 sourceCat = self.makeCatalog(apCorrScale=apCorr_factor)
195 apFluxName = self.names[0] + self.apNameStr + "_instFlux"
196 struct = self.meas_apCorr_task.run(catalog=sourceCat, exposure=self.exposure)
197 default_fill = apCorrDefaultMap(value=apCorr_factor, bbox=self.exposure.getBBox())
198 test_fill = afwImage.ImageF(self.exposure.getBBox())
199 struct.apCorrMap[apFluxName].fillImage(test_fill)
200 np.testing.assert_allclose(test_fill.getArray(), default_fill.getArray())
203class TestMemory(lsst.utils.tests.MemoryTestCase):
204 pass
207def setup_module(module):
208 lsst.utils.tests.init()
211if __name__ == "__main__": 211 ↛ 212line 211 didn't jump to line 212, because the condition on line 211 was never true
212 lsst.utils.tests.init()
213 unittest.main()