Coverage for tests/test_sourceSelector.py : 16%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1#
2# LSST Data Management System
3#
4# Copyright 2008-2017 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#
24import unittest
25import numpy as np
26import astropy.units as u
28import lsst.afw.table
29import lsst.meas.algorithms
30import lsst.meas.base.tests
31import lsst.pipe.base
32import lsst.utils.tests
34from lsst.meas.algorithms import ColorLimit
37class SourceSelectorTester:
38 """Mixin for testing
40 This provides a base class for doing tests common to the
41 ScienceSourceSelectorTask and ReferenceSourceSelectorTask.
42 """
43 Task = None
45 def setUp(self):
46 schema = lsst.afw.table.SourceTable.makeMinimalSchema()
47 schema.addField("flux", float, "Flux value")
48 schema.addField("flux_flag", "Flag", "Bad flux?")
49 schema.addField("other_flux", float, "Flux value 2")
50 schema.addField("other_flux_flag", "Flag", "Bad flux 2?")
51 schema.addField("other_fluxErr", float, "Flux error 2")
52 schema.addField("goodFlag", "Flag", "Flagged if good")
53 schema.addField("badFlag", "Flag", "Flagged if bad")
54 schema.addField("starGalaxy", float, "0=star, 1=galaxy")
55 schema.addField("nChild", np.int32, "Number of children")
56 self.catalog = lsst.afw.table.SourceCatalog(schema)
57 self.catalog.reserve(10)
58 self.config = self.Task.ConfigClass()
60 def tearDown(self):
61 del self.catalog
63 def check(self, expected):
64 task = self.Task(config=self.config)
65 results = task.run(self.catalog)
66 self.assertListEqual(results.selected.tolist(), expected)
67 self.assertListEqual([src.getId() for src in results.sourceCat],
68 [src.getId() for src, ok in zip(self.catalog, expected) if ok])
70 def testFlags(self):
71 bad1 = self.catalog.addNew()
72 bad1.set("goodFlag", False)
73 bad1.set("badFlag", False)
74 bad2 = self.catalog.addNew()
75 bad2.set("goodFlag", True)
76 bad2.set("badFlag", True)
77 bad3 = self.catalog.addNew()
78 bad3.set("goodFlag", False)
79 bad3.set("badFlag", True)
80 good = self.catalog.addNew()
81 good.set("goodFlag", True)
82 good.set("badFlag", False)
83 self.catalog["flux"] = 1.0
84 self.catalog["other_flux"] = 1.0
85 self.config.flags.good = ["goodFlag"]
86 self.config.flags.bad = ["badFlag"]
87 self.check([False, False, False, True])
89 def testSignalToNoise(self):
90 low = self.catalog.addNew()
91 low.set("other_flux", 1.0)
92 low.set("other_fluxErr", 1.0)
93 good = self.catalog.addNew()
94 good.set("other_flux", 1.0)
95 good.set("other_fluxErr", 0.1)
96 high = self.catalog.addNew()
97 high.set("other_flux", 1.0)
98 high.set("other_fluxErr", 0.001)
99 self.config.doSignalToNoise = True
100 self.config.signalToNoise.fluxField = "other_flux"
101 self.config.signalToNoise.errField = "other_fluxErr"
102 self.config.signalToNoise.minimum = 5.0
103 self.config.signalToNoise.maximum = 100.0
104 self.check([False, True, False])
107class ScienceSourceSelectorTaskTest(SourceSelectorTester, lsst.utils.tests.TestCase):
108 Task = lsst.meas.algorithms.ScienceSourceSelectorTask
110 def setUp(self):
111 SourceSelectorTester.setUp(self)
112 self.config.fluxLimit.fluxField = "flux"
113 self.config.flags.bad = []
114 self.config.doFluxLimit = True
115 self.config.doFlags = True
116 self.config.doUnresolved = False
117 self.config.doIsolated = False
119 def testFluxLimit(self):
120 tooBright = self.catalog.addNew()
121 tooBright.set("flux", 1.0e10)
122 tooBright.set("flux_flag", False)
123 good = self.catalog.addNew()
124 good.set("flux", 1000.0)
125 good.set("flux_flag", False)
126 bad = self.catalog.addNew()
127 bad.set("flux", good.get("flux"))
128 bad.set("flux_flag", True)
129 tooFaint = self.catalog.addNew()
130 tooFaint.set("flux", 1.0)
131 tooFaint.set("flux_flag", False)
132 self.config.fluxLimit.minimum = 10.0
133 self.config.fluxLimit.maximum = 1.0e6
134 self.config.fluxLimit.fluxField = "flux"
135 self.check([False, True, False, False])
137 # Works with no maximum set?
138 self.config.fluxLimit.maximum = None
139 self.check([True, True, False, False])
141 # Works with no minimum set?
142 self.config.fluxLimit.minimum = None
143 self.check([True, True, False, True])
145 def testUnresolved(self):
146 num = 5
147 for _ in range(num):
148 self.catalog.addNew()
149 self.catalog["flux"] = 1.0
150 starGalaxy = np.linspace(0.0, 1.0, num, False)
151 self.catalog["starGalaxy"] = starGalaxy
152 self.config.doUnresolved = True
153 self.config.unresolved.name = "starGalaxy"
154 minimum, maximum = 0.3, 0.7
155 self.config.unresolved.minimum = minimum
156 self.config.unresolved.maximum = maximum
157 self.check(((starGalaxy > minimum) & (starGalaxy < maximum)).tolist())
159 # Works with no minimum set?
160 self.config.unresolved.minimum = None
161 self.check((starGalaxy < maximum).tolist())
163 # Works with no maximum set?
164 self.config.unresolved.minimum = minimum
165 self.config.unresolved.maximum = None
166 self.check((starGalaxy > minimum).tolist())
168 def testIsolated(self):
169 num = 5
170 for _ in range(num):
171 self.catalog.addNew()
172 self.catalog["flux"] = 1.0
173 parent = np.array([0, 0, 10, 0, 0], dtype=int)
174 nChild = np.array([2, 0, 0, 0, 0], dtype=int)
175 self.catalog["parent"] = parent
176 self.catalog["nChild"] = nChild
177 self.config.doIsolated = True
178 self.config.isolated.parentName = "parent"
179 self.config.isolated.nChildName = "nChild"
180 self.check(((parent == 0) & (nChild == 0)).tolist())
183class ReferenceSourceSelectorTaskTest(SourceSelectorTester, lsst.utils.tests.TestCase):
184 Task = lsst.meas.algorithms.ReferenceSourceSelectorTask
186 def setUp(self):
187 SourceSelectorTester.setUp(self)
188 self.config.magLimit.fluxField = "flux"
189 self.config.doMagLimit = True
190 self.config.doFlags = True
191 self.config.doUnresolved = False
193 def testMagnitudeLimit(self):
194 tooBright = self.catalog.addNew()
195 tooBright.set("flux", 1.0e10)
196 tooBright.set("flux_flag", False)
197 good = self.catalog.addNew()
198 good.set("flux", 1000.0)
199 good.set("flux_flag", False)
200 bad = self.catalog.addNew()
201 bad.set("flux", good.get("flux"))
202 bad.set("flux_flag", True)
203 tooFaint = self.catalog.addNew()
204 tooFaint.set("flux", 1.0)
205 tooFaint.set("flux_flag", False)
206 # Note: magnitudes are backwards, so the minimum flux is the maximum magnitude
207 self.config.magLimit.minimum = (1.0e6*u.nJy).to_value(u.ABmag)
208 self.config.magLimit.maximum = (10.0*u.nJy).to_value(u.ABmag)
209 self.config.magLimit.fluxField = "flux"
210 self.check([False, True, False, False])
212 # Works with no minimum set?
213 self.config.magLimit.minimum = None
214 self.check([True, True, False, False])
216 # Works with no maximum set?
217 self.config.magLimit.maximum = None
218 self.check([True, True, False, True])
220 def testMagErrorLimit(self):
221 # Using an arbitrary field as if it was a magnitude error to save adding a new field
222 field = "other_fluxErr"
223 tooFaint = self.catalog.addNew()
224 tooFaint.set(field, 0.5)
225 tooBright = self.catalog.addNew()
226 tooBright.set(field, 0.00001)
227 good = self.catalog.addNew()
228 good.set(field, 0.2)
230 self.config.doMagError = True
231 self.config.magError.minimum = 0.01
232 self.config.magError.maximum = 0.3
233 self.config.magError.magErrField = field
234 self.check([False, False, True])
236 def testColorLimits(self):
237 num = 10
238 for _ in range(num):
239 self.catalog.addNew()
240 color = np.linspace(-0.5, 0.5, num, True)
241 flux = 1000.0*u.nJy
242 # Definition: color = mag(flux) - mag(otherFlux)
243 otherFlux = (flux.to(u.ABmag) - color*u.mag).to_value(u.nJy)
244 self.catalog["flux"] = flux.value
245 self.catalog["other_flux"] = otherFlux
246 minimum, maximum = -0.1, 0.2
247 self.config.colorLimits = {"test": ColorLimit(primary="flux", secondary="other_flux",
248 minimum=minimum, maximum=maximum)}
249 self.check(((color > minimum) & (color < maximum)).tolist())
251 # Works with no minimum set?
252 self.config.colorLimits["test"].minimum = None
253 self.check((color < maximum).tolist())
255 # Works with no maximum set?
256 self.config.colorLimits["test"].maximum = None
257 self.config.colorLimits["test"].minimum = minimum
258 self.check((color > minimum).tolist())
260 # Multiple limits
261 self.config.colorLimits = {"test": ColorLimit(primary="flux", secondary="other_flux",
262 minimum=minimum),
263 "other": ColorLimit(primary="flux", secondary="other_flux",
264 maximum=maximum)}
265 assert maximum > minimum # To be non-mutually-exclusive
266 self.check(((color > minimum) & (color < maximum)).tolist())
268 # Multiple mutually-exclusive limits
269 self.config.colorLimits["test"] = ColorLimit(primary="flux", secondary="other_flux", maximum=-0.1)
270 self.config.colorLimits["other"] = ColorLimit(primary="flux", secondary="other_flux", minimum=0.1)
271 self.check([False]*num)
273 def testUnresolved(self):
274 num = 5
275 for _ in range(num):
276 self.catalog.addNew()
277 self.catalog["flux"] = 1.0
278 starGalaxy = np.linspace(0.0, 1.0, num, False)
279 self.catalog["starGalaxy"] = starGalaxy
280 self.config.doUnresolved = True
281 self.config.unresolved.name = "starGalaxy"
282 minimum, maximum = 0.3, 0.7
283 self.config.unresolved.minimum = minimum
284 self.config.unresolved.maximum = maximum
285 self.check(((starGalaxy > minimum) & (starGalaxy < maximum)).tolist())
287 # Works with no minimum set?
288 self.config.unresolved.minimum = None
289 self.check((starGalaxy < maximum).tolist())
291 # Works with no maximum set?
292 self.config.unresolved.minimum = minimum
293 self.config.unresolved.maximum = None
294 self.check((starGalaxy > minimum).tolist())
297class TrivialSourceSelector(lsst.meas.algorithms.BaseSourceSelectorTask):
298 """Return true for every source. Purely for testing."""
299 def selectSources(self, sourceCat, matches=None, exposure=None):
300 return lsst.pipe.base.Struct(selected=np.ones(len(sourceCat), dtype=bool))
303class TestBaseSourceSelector(lsst.utils.tests.TestCase):
304 """Test the API of the Abstract Base Class with a trivial example."""
305 def setUp(self):
306 schema = lsst.meas.base.tests.TestDataset.makeMinimalSchema()
307 self.selectedKeyName = "is_selected"
308 schema.addField(self.selectedKeyName, type="Flag")
309 self.catalog = lsst.afw.table.SourceCatalog(schema)
310 for i in range(4):
311 self.catalog.addNew()
313 self.sourceSelector = TrivialSourceSelector()
315 def testRun(self):
316 """Test that run() returns a catalog and boolean selected array."""
317 result = self.sourceSelector.run(self.catalog)
318 for i, x in enumerate(self.catalog['id']):
319 self.assertIn(x, result.sourceCat['id'])
320 self.assertTrue(result.selected[i])
322 def testRunSourceSelectedField(self):
323 """Test that the selected flag is set in the original catalog."""
324 self.sourceSelector.run(self.catalog, sourceSelectedField=self.selectedKeyName)
325 np.testing.assert_array_equal(self.catalog[self.selectedKeyName], True)
327 def testRunNonContiguousRaises(self):
328 """Cannot do source selection on non-contiguous catalogs."""
329 del self.catalog[1] # take one out of the middle to make it non-contiguous.
330 self.assertFalse(self.catalog.isContiguous(), "Catalog is contiguous: the test won't work.")
332 with self.assertRaises(RuntimeError):
333 self.sourceSelector.run(self.catalog)
336class TestMemory(lsst.utils.tests.MemoryTestCase):
337 pass
340def setup_module(module):
341 lsst.utils.tests.init()
344if __name__ == "__main__": 344 ↛ 345line 344 didn't jump to line 345, because the condition on line 344 was never true
345 import sys
346 setup_module(sys.modules[__name__])
347 unittest.main()