Hide keyboard shortcuts

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# 

23 

24import unittest 

25import numpy as np 

26import astropy.units as u 

27 

28import lsst.afw.table 

29import lsst.meas.algorithms 

30import lsst.meas.base.tests 

31import lsst.pipe.base 

32import lsst.utils.tests 

33 

34from lsst.meas.algorithms import ColorLimit 

35 

36 

37class SourceSelectorTester: 

38 """Mixin for testing 

39 

40 This provides a base class for doing tests common to the 

41 ScienceSourceSelectorTask and ReferenceSourceSelectorTask. 

42 """ 

43 Task = None 

44 

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

59 

60 def tearDown(self): 

61 del self.catalog 

62 

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

69 

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

88 

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

105 

106 

107class ScienceSourceSelectorTaskTest(SourceSelectorTester, lsst.utils.tests.TestCase): 

108 Task = lsst.meas.algorithms.ScienceSourceSelectorTask 

109 

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 

118 

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

136 

137 # Works with no maximum set? 

138 self.config.fluxLimit.maximum = None 

139 self.check([True, True, False, False]) 

140 

141 # Works with no minimum set? 

142 self.config.fluxLimit.minimum = None 

143 self.check([True, True, False, True]) 

144 

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

158 

159 # Works with no minimum set? 

160 self.config.unresolved.minimum = None 

161 self.check((starGalaxy < maximum).tolist()) 

162 

163 # Works with no maximum set? 

164 self.config.unresolved.minimum = minimum 

165 self.config.unresolved.maximum = None 

166 self.check((starGalaxy > minimum).tolist()) 

167 

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

181 

182 

183class ReferenceSourceSelectorTaskTest(SourceSelectorTester, lsst.utils.tests.TestCase): 

184 Task = lsst.meas.algorithms.ReferenceSourceSelectorTask 

185 

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 

192 

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

211 

212 # Works with no minimum set? 

213 self.config.magLimit.minimum = None 

214 self.check([True, True, False, False]) 

215 

216 # Works with no maximum set? 

217 self.config.magLimit.maximum = None 

218 self.check([True, True, False, True]) 

219 

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) 

229 

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

235 

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

250 

251 # Works with no minimum set? 

252 self.config.colorLimits["test"].minimum = None 

253 self.check((color < maximum).tolist()) 

254 

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

259 

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

267 

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) 

272 

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

286 

287 # Works with no minimum set? 

288 self.config.unresolved.minimum = None 

289 self.check((starGalaxy < maximum).tolist()) 

290 

291 # Works with no maximum set? 

292 self.config.unresolved.minimum = minimum 

293 self.config.unresolved.maximum = None 

294 self.check((starGalaxy > minimum).tolist()) 

295 

296 

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

301 

302 

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

312 

313 self.sourceSelector = TrivialSourceSelector() 

314 

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

321 

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) 

326 

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

331 

332 with self.assertRaises(RuntimeError): 

333 self.sourceSelector.run(self.catalog) 

334 

335 

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

337 pass 

338 

339 

340def setup_module(module): 

341 lsst.utils.tests.init() 

342 

343 

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