Coverage for tests/test_sourceTable.py: 10%

550 statements  

« prev     ^ index     » next       coverage.py v7.14.1, created at 2026-05-29 01:21 -0700

1# This file is part of afw. 

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/>. 

21 

22import os 

23import unittest 

24import tempfile 

25import pickle 

26import math 

27 

28import numpy as np 

29 

30import lsst.utils.tests 

31import lsst.pex.exceptions 

32import lsst.geom 

33import lsst.afw.table 

34import lsst.afw.geom 

35import lsst.afw.image 

36import lsst.afw.detection 

37 

38testPath = os.path.abspath(os.path.dirname(__file__)) 

39 

40 

41def makeArray(size, dtype): 

42 return np.array(np.random.randn(*size), dtype=dtype) 

43 

44 

45def makeCov(size, dtype): 

46 m = np.array(np.random.randn(size, size), dtype=dtype) 

47 return np.dot(m, m.transpose()) 

48 

49 

50def makeWcs(): 

51 crval = lsst.geom.SpherePoint(1.606631*lsst.geom.degrees, 

52 5.090329*lsst.geom.degrees) 

53 crpix = lsst.geom.Point2D(2036.0, 2000.0) 

54 cdMatrix = np.array([5.399452e-5, -1.30770e-5, 1.30770e-5, 5.399452e-5]) 

55 cdMatrix.shape = (2, 2) 

56 return lsst.afw.geom.makeSkyWcs(crpix=crpix, crval=crval, cdMatrix=cdMatrix) 

57 

58 

59class SourceTableTestCase(lsst.utils.tests.TestCase): 

60 

61 def fillRecord(self, record): 

62 record.set(self.instFluxKey, np.random.randn()) 

63 record.set(self.instFluxErrKey, np.random.randn()) 

64 record.set(self.centroidKey.getX(), np.random.randn()) 

65 record.set(self.centroidKey.getY(), np.random.randn()) 

66 record.set(self.xErrKey, np.random.randn()) 

67 record.set(self.yErrKey, np.random.randn()) 

68 record.set(self.shapeKey.getIxx(), np.random.randn()) 

69 record.set(self.shapeKey.getIyy(), np.random.randn()) 

70 record.set(self.shapeKey.getIxy(), np.random.randn()) 

71 record.set(self.xxErrKey, np.random.randn()) 

72 record.set(self.yyErrKey, np.random.randn()) 

73 record.set(self.xyErrKey, np.random.randn()) 

74 record.set(self.psfShapeKey.getIxx(), np.random.randn()) 

75 record.set(self.psfShapeKey.getIyy(), np.random.randn()) 

76 record.set(self.psfShapeKey.getIxy(), np.random.randn()) 

77 record.set(self.fluxFlagKey, np.random.randn() > 0) 

78 record.set(self.centroidFlagKey, np.random.randn() > 0) 

79 record.set(self.shapeFlagKey, np.random.randn() > 0) 

80 

81 def setUp(self): 

82 np.random.seed(1) 

83 self.schema = lsst.afw.table.SourceTable.makeMinimalSchema() 

84 self.instFluxKey = self.schema.addField("a_instFlux", type="D") 

85 self.instFluxErrKey = self.schema.addField("a_instFluxErr", type="D") 

86 self.fluxFlagKey = self.schema.addField("a_flag", type="Flag") 

87 

88 # the meas field is added using a functor key, but the error is added 

89 # as scalars, as we lack a ResultKey functor as exists in meas_base 

90 self.centroidKey = lsst.afw.table.Point2DKey.addFields( 

91 self.schema, "b", "", "pixel") 

92 self.xErrKey = self.schema.addField("b_xErr", type="F") 

93 self.yErrKey = self.schema.addField("b_yErr", type="F") 

94 self.centroidFlagKey = self.schema.addField("b_flag", type="Flag") 

95 

96 self.shapeKey = lsst.afw.table.QuadrupoleKey.addFields( 

97 self.schema, "c", "", lsst.afw.table.CoordinateType.PIXEL) 

98 self.xxErrKey = self.schema.addField("c_xxErr", type="F") 

99 self.xyErrKey = self.schema.addField("c_xyErr", type="F") 

100 self.yyErrKey = self.schema.addField("c_yyErr", type="F") 

101 self.shapeFlagKey = self.schema.addField("c_flag", type="Flag") 

102 

103 self.psfShapeKey = lsst.afw.table.QuadrupoleKey.addFields( 

104 self.schema, "d", "", lsst.afw.table.CoordinateType.PIXEL) 

105 self.psfShapeFlagKey = self.schema.addField("d_flag", type="Flag") 

106 

107 self.coordErrKey = lsst.afw.table.CoordKey.addErrorFields(self.schema) 

108 

109 self.table = lsst.afw.table.SourceTable.make(self.schema) 

110 self.catalog = lsst.afw.table.SourceCatalog(self.table) 

111 self.record = self.catalog.addNew() 

112 self.fillRecord(self.record) 

113 self.record.setId(50) 

114 self.fillRecord(self.catalog.addNew()) 

115 self.fillRecord(self.catalog.addNew()) 

116 

117 def tearDown(self): 

118 del self.schema 

119 del self.record 

120 del self.table 

121 del self.catalog 

122 

123 def checkCanonical(self): 

124 self.assertEqual(self.record.get(self.instFluxKey), 

125 self.record.getPsfInstFlux()) 

126 self.assertEqual(self.record.get(self.fluxFlagKey), 

127 self.record.getPsfFluxFlag()) 

128 self.assertEqual(self.table.getSchema().getAliasMap().get("slot_Centroid"), "b") 

129 self.assertEqual(self.centroidKey.get(self.record), 

130 self.record.getCentroid()) 

131 self.assertFloatsAlmostEqual( 

132 math.fabs(self.record.get(self.xErrKey)), 

133 math.sqrt(self.record.getCentroidErr()[0, 0]), rtol=1e-6) 

134 self.assertFloatsAlmostEqual( 

135 math.fabs(self.record.get(self.yErrKey)), 

136 math.sqrt(self.record.getCentroidErr()[1, 1]), rtol=1e-6) 

137 self.assertEqual(self.table.getSchema().getAliasMap().get("slot_Shape"), "c") 

138 self.assertEqual(self.shapeKey.get(self.record), 

139 self.record.getShape()) 

140 self.assertFloatsAlmostEqual( 

141 math.fabs(self.record.get(self.xxErrKey)), 

142 math.sqrt(self.record.getShapeErr()[0, 0]), rtol=1e-6) 

143 self.assertFloatsAlmostEqual( 

144 math.fabs(self.record.get(self.yyErrKey)), 

145 math.sqrt(self.record.getShapeErr()[1, 1]), rtol=1e-6) 

146 self.assertFloatsAlmostEqual( 

147 math.fabs(self.record.get(self.xyErrKey)), 

148 math.sqrt(self.record.getShapeErr()[2, 2]), rtol=1e-6) 

149 self.assertEqual(self.table.getSchema().getAliasMap().get("slot_PsfShape"), "d") 

150 self.assertEqual(self.psfShapeKey.get(self.record), 

151 self.record.getPsfShape()) 

152 

153 def testPersisted(self): 

154 self.table.definePsfFlux("a") 

155 self.table.defineCentroid("b") 

156 self.table.defineShape("c") 

157 self.table.definePsfShape("d") 

158 with lsst.utils.tests.getTempFilePath(".fits") as filename: 

159 self.catalog.writeFits(filename) 

160 catalog = lsst.afw.table.SourceCatalog.readFits(filename) 

161 table = catalog.getTable() 

162 record = catalog[0] 

163 # I'm using the keys from the non-persisted table. They should work at least in the 

164 # current implementation 

165 self.assertEqual(record.get(self.instFluxKey), record.getPsfInstFlux()) 

166 self.assertEqual(record.get(self.fluxFlagKey), record.getPsfFluxFlag()) 

167 self.assertEqual(table.getSchema().getAliasMap().get("slot_Centroid"), "b") 

168 centroid = self.centroidKey.get(self.record) 

169 self.assertEqual(centroid, record.getCentroid()) 

170 self.assertFloatsAlmostEqual( 

171 math.fabs(self.record.get(self.xErrKey)), 

172 math.sqrt(self.record.getCentroidErr()[0, 0]), rtol=1e-6) 

173 self.assertFloatsAlmostEqual( 

174 math.fabs(self.record.get(self.yErrKey)), 

175 math.sqrt(self.record.getCentroidErr()[1, 1]), rtol=1e-6) 

176 shape = self.shapeKey.get(self.record) 

177 self.assertEqual(table.getSchema().getAliasMap().get("slot_Shape"), "c") 

178 self.assertEqual(shape, record.getShape()) 

179 self.assertFloatsAlmostEqual( 

180 math.fabs(self.record.get(self.xxErrKey)), 

181 math.sqrt(self.record.getShapeErr()[0, 0]), rtol=1e-6) 

182 self.assertFloatsAlmostEqual( 

183 math.fabs(self.record.get(self.yyErrKey)), 

184 math.sqrt(self.record.getShapeErr()[1, 1]), rtol=1e-6) 

185 self.assertFloatsAlmostEqual( 

186 math.fabs(self.record.get(self.xyErrKey)), 

187 math.sqrt(self.record.getShapeErr()[2, 2]), rtol=1e-6) 

188 psfShape = self.psfShapeKey.get(self.record) 

189 self.assertEqual(table.getSchema().getAliasMap().get("slot_PsfShape"), "d") 

190 self.assertEqual(psfShape, record.getPsfShape()) 

191 

192 def testCanonical2(self): 

193 self.table.definePsfFlux("a") 

194 self.table.defineCentroid("b") 

195 self.table.defineShape("c") 

196 self.table.definePsfShape("d") 

197 self.checkCanonical() 

198 

199 def testPickle(self): 

200 p = pickle.dumps(self.catalog) 

201 new = pickle.loads(p) 

202 

203 self.assertEqual(self.catalog.schema.getNames(), new.schema.getNames()) 

204 self.assertEqual(len(self.catalog), len(new)) 

205 for r1, r2 in zip(self.catalog, new): 

206 # Columns that are easy to test 

207 for field in ("a_instFlux", "a_instFluxErr", "id"): 

208 k1 = self.catalog.schema.find(field).getKey() 

209 k2 = new.schema.find(field).getKey() 

210 self.assertEqual(r1[k1], r2[k2]) 

211 

212 def testCoordUpdate(self): 

213 self.table.defineCentroid("b") 

214 wcs = makeWcs() 

215 self.record.updateCoord(wcs) 

216 coord1 = self.record.getCoord() 

217 coord2 = wcs.pixelToSky(self.record.get(self.centroidKey)) 

218 self.assertEqual(coord1, coord2) 

219 

220 def testCoordErrors(self): 

221 self.table.defineCentroid("b") 

222 wcs = makeWcs() 

223 self.record.updateCoord(wcs) 

224 

225 scale = (1.0 * lsst.geom.arcseconds).asDegrees() 

226 center = self.record.getCentroid() 

227 skyCenter = wcs.pixelToSky(center) 

228 localGnomonicWcs = lsst.afw.geom.makeSkyWcs( 

229 center, skyCenter, np.diag((scale, scale))) 

230 measurementToLocalGnomonic = wcs.getTransform().then( 

231 localGnomonicWcs.getTransform().inverted() 

232 ) 

233 localMatrix = measurementToLocalGnomonic.getJacobian(center) 

234 radMatrix = np.radians(localMatrix / 3600) 

235 

236 centroidErr = self.record.getCentroidErr() 

237 coordErr = radMatrix.dot(centroidErr.dot(radMatrix.T)) 

238 catCoordErr = self.record.get(self.coordErrKey) 

239 np.testing.assert_almost_equal(coordErr, catCoordErr, decimal=16) 

240 

241 def testSorting(self): 

242 self.assertFalse(self.catalog.isSorted()) 

243 self.catalog.sort() 

244 self.assertTrue(self.catalog.isSorted()) 

245 r = self.catalog.find(2) 

246 self.assertEqual(r["id"], 2) 

247 r = self.catalog.find(500) 

248 self.assertIsNone(r) 

249 

250 def testConversion(self): 

251 catalog1 = self.catalog.cast(lsst.afw.table.SourceCatalog) 

252 catalog2 = self.catalog.cast(lsst.afw.table.SimpleCatalog) 

253 catalog3 = self.catalog.cast(lsst.afw.table.SourceCatalog, deep=True) 

254 catalog4 = self.catalog.cast(lsst.afw.table.SimpleCatalog, deep=True) 

255 self.assertEqual(self.catalog.table, catalog1.table) 

256 self.assertEqual(self.catalog.table, catalog2.table) 

257 self.assertNotEqual(self.catalog.table, catalog3.table) 

258 self.assertNotEqual(self.catalog.table, catalog3.table) 

259 for r, r1, r2, r3, r4 in zip(self.catalog, catalog1, catalog2, catalog3, catalog4): 

260 self.assertEqual(r, r1) 

261 self.assertEqual(r, r2) 

262 self.assertNotEqual(r, r3) 

263 self.assertNotEqual(r, r4) 

264 self.assertEqual(r.getId(), r3.getId()) 

265 self.assertEqual(r.getId(), r4.getId()) 

266 

267 def testColumnView(self): 

268 cols1 = self.catalog.getColumnView() 

269 cols2 = self.catalog.columns 

270 self.assertIs(cols1, cols2) 

271 self.assertIsInstance(cols1, lsst.afw.table.SourceColumnView) 

272 self.table.definePsfFlux("a") 

273 self.table.defineCentroid("b") 

274 self.table.defineShape("c") 

275 self.table.definePsfShape("d") 

276 self.assertFloatsEqual(cols2["a_instFlux"], cols2.getPsfInstFlux()) 

277 self.assertFloatsEqual(cols2["a_instFluxErr"], cols2.getPsfInstFluxErr()) 

278 self.assertFloatsEqual(cols2["b_x"], cols2.getX()) 

279 self.assertFloatsEqual(cols2["b_y"], cols2.getY()) 

280 self.assertFloatsEqual(cols2["c_xx"], cols2.getIxx()) 

281 self.assertFloatsEqual(cols2["c_yy"], cols2.getIyy()) 

282 self.assertFloatsEqual(cols2["c_xy"], cols2.getIxy()) 

283 self.assertFloatsEqual(cols2["d_xx"], cols2.getPsfIxx()) 

284 self.assertFloatsEqual(cols2["d_yy"], cols2.getPsfIyy()) 

285 self.assertFloatsEqual(cols2["d_xy"], cols2.getPsfIxy()) 

286 

287 # Trying to access slots which have been removed should raise. 

288 self.catalog.table.schema.getAliasMap().erase("slot_Centroid") 

289 self.catalog.table.schema.getAliasMap().erase("slot_Shape") 

290 self.catalog.table.schema.getAliasMap().erase("slot_PsfShape") 

291 for quantity in ["X", "Y", "Ixx", "Iyy", "Ixy", "PsfIxx", "PsfIyy", "PsfIxy"]: 

292 with self.assertRaises(lsst.pex.exceptions.LogicError): 

293 getattr(self.catalog, f"get{quantity}")() 

294 

295 def testForwarding(self): 

296 """Verify that Catalog forwards unknown methods to its table and/or columns.""" 

297 self.table.definePsfFlux("a") 

298 self.table.defineCentroid("b") 

299 self.table.defineShape("c") 

300 self.assertFloatsEqual(self.catalog.columns["a_instFlux"], 

301 self.catalog["a_instFlux"]) 

302 self.assertFloatsEqual(self.catalog.columns[self.instFluxKey], 

303 self.catalog.get(self.instFluxKey)) 

304 self.assertFloatsEqual(self.catalog.columns.get(self.instFluxKey), 

305 self.catalog.getPsfInstFlux()) 

306 self.assertEqual(self.instFluxKey, self.catalog.getPsfFluxSlot().getMeasKey()) 

307 with self.assertRaises(AttributeError): 

308 self.catalog.foo() 

309 

310 def testBitsColumn(self): 

311 

312 allBits = self.catalog.getBits() 

313 someBits = self.catalog.getBits(["a_flag", "c_flag"]) 

314 self.assertEqual(allBits.getMask("a_flag"), 0x1) 

315 self.assertEqual(allBits.getMask("b_flag"), 0x2) 

316 self.assertEqual(allBits.getMask("c_flag"), 0x4) 

317 self.assertEqual(someBits.getMask(self.fluxFlagKey), 0x1) 

318 self.assertEqual(someBits.getMask(self.shapeFlagKey), 0x2) 

319 np.testing.assert_array_equal((allBits.array & 0x1 != 0), self.catalog["a_flag"]) 

320 np.testing.assert_array_equal((allBits.array & 0x2 != 0), self.catalog["b_flag"]) 

321 np.testing.assert_array_equal((allBits.array & 0x4 != 0), self.catalog["c_flag"]) 

322 np.testing.assert_array_equal((someBits.array & 0x1 != 0), self.catalog["a_flag"]) 

323 np.testing.assert_array_equal((someBits.array & 0x2 != 0), self.catalog["c_flag"]) 

324 

325 def testCast(self): 

326 baseCat = self.catalog.cast(lsst.afw.table.BaseCatalog) 

327 baseCat.cast(lsst.afw.table.SourceCatalog) 

328 

329 def testFootprints(self): 

330 '''Test round-tripping Footprints (inc. HeavyFootprints) to FITS 

331 ''' 

332 src1 = self.catalog.addNew() 

333 src2 = self.catalog.addNew() 

334 src3 = self.catalog.addNew() 

335 self.fillRecord(src1) 

336 self.fillRecord(src2) 

337 self.fillRecord(src3) 

338 src2.setParent(src1.getId()) 

339 

340 W, H = 100, 100 

341 mim = lsst.afw.image.MaskedImageF(W, H) 

342 im = mim.getImage() 

343 msk = mim.getMask() 

344 var = mim.getVariance() 

345 x, y = np.meshgrid(np.arange(W, dtype=int), np.arange(H, dtype=int)) 

346 im.array[:] = y*1E6 + x*1E3 

347 msk.array[:] = (y << 8) | x 

348 var.array[:] = y*1E2 + x 

349 spanSet = lsst.afw.geom.SpanSet.fromShape(20).shiftedBy(50, 50) 

350 circ = lsst.afw.detection.Footprint(spanSet) 

351 heavy = lsst.afw.detection.makeHeavyFootprint(circ, mim) 

352 src2.setFootprint(heavy) 

353 

354 for i, src in enumerate(self.catalog): 

355 if src != src2: 

356 spanSet = lsst.afw.geom.SpanSet.fromShape(1 + i*2).shiftedBy(50, 50) 

357 src.setFootprint(lsst.afw.detection.Footprint(spanSet)) 

358 

359 # insert this HeavyFootprint into an otherwise blank image (for comparing the results) 

360 mim2 = lsst.afw.image.MaskedImageF(W, H) 

361 heavy.insert(mim2) 

362 

363 with lsst.utils.tests.getTempFilePath(".fits") as fn: 

364 self.catalog.writeFits(fn) 

365 

366 cat2 = lsst.afw.table.SourceCatalog.readFits(fn) 

367 r2 = cat2[-2] 

368 h2 = r2.getFootprint() 

369 self.assertTrue(h2.isHeavy()) 

370 mim3 = lsst.afw.image.MaskedImageF(W, H) 

371 h2.insert(mim3) 

372 

373 self.assertFalse(cat2[-1].getFootprint().isHeavy()) 

374 self.assertFalse(cat2[-3].getFootprint().isHeavy()) 

375 self.assertFalse(cat2[0].getFootprint().isHeavy()) 

376 self.assertFalse(cat2[1].getFootprint().isHeavy()) 

377 self.assertFalse(cat2[2].getFootprint().isHeavy()) 

378 

379 if False: 

380 # Write out before-n-after FITS images 

381 for MI in [mim, mim2, mim3]: 

382 f, fn2 = tempfile.mkstemp(prefix='testHeavyFootprint-', suffix='.fits') 

383 os.close(f) 

384 MI.writeFits(fn2) 

385 print('wrote', fn2) 

386 

387 self.assertFloatsEqual(mim2.getImage().getArray(), mim3.getImage().getArray()) 

388 self.assertFloatsEqual(mim2.getMask().getArray(), mim3.getMask().getArray()) 

389 self.assertFloatsEqual(mim2.getVariance().getArray(), mim3.getVariance().getArray()) 

390 

391 im3 = mim3.getImage() 

392 ma3 = mim3.getMask() 

393 va3 = mim3.getVariance() 

394 for y in range(H): 

395 for x in range(W): 

396 if circ.contains(lsst.geom.Point2I(x, y)): 

397 self.assertEqual(im[x, y, lsst.afw.image.PARENT], im3[x, y, lsst.afw.image.PARENT]) 

398 self.assertEqual(msk[x, y, lsst.afw.image.PARENT], ma3[x, y, lsst.afw.image.PARENT]) 

399 self.assertEqual(var[x, y, lsst.afw.image.PARENT], va3[x, y, lsst.afw.image.PARENT]) 

400 else: 

401 self.assertEqual(im3[x, y, lsst.afw.image.PARENT], 0.) 

402 self.assertEqual(ma3[x, y, lsst.afw.image.PARENT], 0.) 

403 self.assertEqual(va3[x, y, lsst.afw.image.PARENT], 0.) 

404 

405 cat3 = lsst.afw.table.SourceCatalog.readFits( 

406 fn, flags=lsst.afw.table.SOURCE_IO_NO_HEAVY_FOOTPRINTS) 

407 for src in cat3: 

408 self.assertFalse(src.getFootprint().isHeavy()) 

409 cat4 = lsst.afw.table.SourceCatalog.readFits( 

410 fn, flags=lsst.afw.table.SOURCE_IO_NO_FOOTPRINTS) 

411 for src in cat4: 

412 self.assertIsNone(src.getFootprint()) 

413 

414 self.catalog.writeFits( 

415 fn, flags=lsst.afw.table.SOURCE_IO_NO_HEAVY_FOOTPRINTS) 

416 cat5 = lsst.afw.table.SourceCatalog.readFits(fn) 

417 for src in cat5: 

418 self.assertFalse(src.getFootprint().isHeavy()) 

419 

420 self.catalog.writeFits( 

421 fn, flags=lsst.afw.table.SOURCE_IO_NO_FOOTPRINTS) 

422 cat6 = lsst.afw.table.SourceCatalog.readFits(fn) 

423 for src in cat6: 

424 self.assertIsNone(src.getFootprint()) 

425 

426 # Insert a source with no Footprint to ensure that a None footprint 

427 # is persisted correctly 

428 self.catalog.addNew() 

429 

430 with lsst.utils.tests.getTempFilePath(".fits") as fn: 

431 self.catalog.writeFits(fn) 

432 

433 cat2 = lsst.afw.table.SourceCatalog.readFits(fn) 

434 self.assertIsNone(cat2[-1].getFootprint()) 

435 

436 def testFootprintsToNumpy(self): 

437 schema = lsst.afw.table.SourceTable.makeMinimalSchema() 

438 table = lsst.afw.table.SourceTable.make(schema) 

439 catalog = lsst.afw.table.SourceCatalog(table) 

440 src1 = catalog.addNew() 

441 src2 = catalog.addNew() 

442 src3 = catalog.addNew() 

443 

444 spans1 = lsst.afw.geom.SpanSet.fromShape(5, lsst.afw.geom.Stencil.BOX).shiftedBy(10, 20) 

445 spans2 = lsst.afw.geom.SpanSet.fromShape(5, lsst.afw.geom.Stencil.BOX).shiftedBy(30, 40) 

446 spans3 = lsst.afw.geom.SpanSet.fromShape(3, lsst.afw.geom.Stencil.BOX).shiftedBy(40, 50) 

447 

448 src1.setFootprint(lsst.afw.detection.Footprint(spans1)) 

449 src2.setFootprint(lsst.afw.detection.Footprint(spans2)) 

450 src3.setFootprint(lsst.afw.detection.Footprint(spans3)) 

451 

452 truth = np.zeros((100, 100), dtype=int) 

453 truth[15:26, 5:16] = src1.getId() 

454 truth[35:46, 25:36] = src2.getId() 

455 truth[47:54, 37:44] = src3.getId() 

456 

457 footprintIds = lsst.afw.detection.footprintsToNumpy(catalog, shape=(100, 100), asBool=False) 

458 booleanArray = lsst.afw.detection.footprintsToNumpy(catalog, shape=(100, 100), asBool=True) 

459 

460 np.testing.assert_array_equal(footprintIds, truth) 

461 np.testing.assert_array_equal(booleanArray, truth > 0) 

462 

463 def testIdFactory(self): 

464 expId = int(1257198) 

465 reserved = 32 

466 factory = lsst.afw.table.IdFactory.makeSource(expId, reserved) 

467 id1 = factory() 

468 id2 = factory() 

469 self.assertEqual(id2 - id1, 1) 

470 factory.notify(0xFFFFFFFF) 

471 with self.assertRaises(lsst.pex.exceptions.LengthError): 

472 factory() 

473 with self.assertRaises(lsst.pex.exceptions.InvalidParameterError): 

474 factory.notify(0x1FFFFFFFF) 

475 with self.assertRaises(lsst.pex.exceptions.InvalidParameterError): 

476 lsst.afw.table.IdFactory.makeSource(0x1FFFFFFFF, reserved) 

477 

478 def testFamilies(self): 

479 self.catalog.sort() 

480 parents = self.catalog.getChildren(0) 

481 self.assertEqual(list(parents), list(self.catalog)) 

482 parentKey = lsst.afw.table.SourceTable.getParentKey() 

483 for parent in parents: 

484 self.assertEqual(parent.get(parentKey), 0) 

485 for i in range(10): 

486 child = self.catalog.addNew() 

487 self.fillRecord(child) 

488 child.set(parentKey, parent.getId()) 

489 childrenIter = self.catalog.getChildren([parent.getId() for parent in parents], 

490 [record.getId() for record in self.catalog]) 

491 for parent, (children, ids) in zip(parents, childrenIter): 

492 self.assertEqual(len(children), 10) 

493 self.assertEqual(len(children), len(ids)) 

494 for child, id in zip(children, ids): 

495 self.assertEqual(child.getParent(), parent.getId()) 

496 self.assertEqual(child.getId(), id) 

497 

498 # Check detection of unsorted catalog 

499 self.catalog.sort(self.instFluxKey) 

500 with self.assertRaises(AssertionError): 

501 self.catalog.getChildren(0) 

502 self.catalog.sort(parentKey) 

503 self.catalog.getChildren(0) # Just care this succeeds 

504 

505 def testFitsReadVersion0Compatibility(self): 

506 cat = lsst.afw.table.SourceCatalog.readFits(os.path.join(testPath, "data/empty-v0.fits")) 

507 self.assertTrue(cat.getPsfFluxSlot().isValid()) 

508 self.assertTrue(cat.getApFluxSlot().isValid()) 

509 self.assertTrue(cat.getGaussianFluxSlot().isValid()) 

510 self.assertTrue(cat.getModelFluxSlot().isValid()) 

511 self.assertTrue(cat.getCentroidSlot().isValid()) 

512 self.assertTrue(cat.getShapeSlot().isValid()) 

513 self.assertEqual(cat.getPsfFluxSlot().getMeasKey(), 

514 cat.schema.find("flux_psf").key) 

515 self.assertEqual(cat.getApFluxSlot().getMeasKey(), 

516 cat.schema.find("flux_sinc").key) 

517 self.assertEqual(cat.getGaussianFluxSlot().getMeasKey(), 

518 cat.schema.find("flux_naive").key) 

519 self.assertEqual(cat.getModelFluxSlot().getMeasKey(), 

520 cat.schema.find("cmodel_flux").key) 

521 self.assertEqual(cat.getCentroidSlot().getMeasKey().getX(), 

522 cat.schema.find("centroid_sdss_x").key) 

523 self.assertEqual(cat.getCentroidSlot().getMeasKey().getY(), 

524 cat.schema.find("centroid_sdss_y").key) 

525 self.assertEqual(cat.getShapeSlot().getMeasKey().getIxx(), 

526 cat.schema.find("shape_hsm_moments_xx").key) 

527 self.assertEqual(cat.getShapeSlot().getMeasKey().getIyy(), 

528 cat.schema.find("shape_hsm_moments_yy").key) 

529 self.assertEqual(cat.getShapeSlot().getMeasKey().getIxy(), 

530 cat.schema.find("shape_hsm_moments_xy").key) 

531 self.assertEqual(cat.getPsfFluxSlot().getErrKey(), 

532 cat.schema.find("flux_psf_err").key) 

533 self.assertEqual(cat.getApFluxSlot().getErrKey(), 

534 cat.schema.find("flux_sinc_err").key) 

535 self.assertEqual(cat.getGaussianFluxSlot().getErrKey(), 

536 cat.schema.find("flux_naive_err").key) 

537 self.assertEqual(cat.getModelFluxSlot().getErrKey(), 

538 cat.schema.find("cmodel_flux_err").key) 

539 self.assertEqual( 

540 cat.getCentroidSlot().getErrKey(), 

541 lsst.afw.table.CovarianceMatrix2fKey(cat.schema["centroid_sdss_err"], ["x", "y"])) 

542 self.assertEqual( 

543 cat.getShapeSlot().getErrKey(), 

544 lsst.afw.table.CovarianceMatrix3fKey(cat.schema["shape_hsm_moments_err"], ["xx", "yy", "xy"])) 

545 self.assertEqual(cat.getPsfFluxSlot().getFlagKey(), 

546 cat.schema.find("flux_psf_flags").key) 

547 self.assertEqual(cat.getApFluxSlot().getFlagKey(), 

548 cat.schema.find("flux_sinc_flags").key) 

549 self.assertEqual(cat.getGaussianFluxSlot().getFlagKey(), 

550 cat.schema.find("flux_naive_flags").key) 

551 self.assertEqual(cat.getModelFluxSlot().getFlagKey(), 

552 cat.schema.find("cmodel_flux_flags").key) 

553 self.assertEqual(cat.getCentroidSlot().getFlagKey(), 

554 cat.schema.find("centroid_sdss_flags").key) 

555 self.assertEqual(cat.getShapeSlot().getFlagKey(), 

556 cat.schema.find("shape_hsm_moments_flags").key) 

557 

558 def testFitsReadVersion1Compatibility(self): 

559 """Test reading of catalogs with version 1 schema 

560 

561 Version 1 catalogs need to have added aliases from Sigma->Err and 

562 from `_flux`->`_instFlux`. 

563 """ 

564 cat = lsst.afw.table.SourceCatalog.readFits( 

565 os.path.join(testPath, "data", "sourceTable-v1.fits")) 

566 self.assertEqual( 

567 cat.getCentroidSlot().getErrKey(), 

568 lsst.afw.table.CovarianceMatrix2fKey( 

569 cat.schema["slot_Centroid"], 

570 ["x", "y"])) 

571 self.assertEqual( 

572 cat.getShapeSlot().getErrKey(), 

573 lsst.afw.table.CovarianceMatrix3fKey(cat.schema["slot_Shape"], ["xx", "yy", "xy"])) 

574 # check the flux->instFlux conversion 

575 self.assertEqual(cat.schema["a_flux"].asKey(), cat.schema["a_instFlux"].asKey()) 

576 self.assertEqual(cat.schema["a_fluxSigma"].asKey(), cat.schema["a_instFluxErr"].asKey()) 

577 

578 def testFitsReadVersion2CompatibilityRealSourceCatalog(self): 

579 """DM-15891: some fields were getting aliases they shouldn't have.""" 

580 cat = lsst.afw.table.SourceCatalog.readFits( 

581 os.path.join(testPath, "data", "sourceCatalog-hsc-v2.fits")) 

582 self.assertNotIn('base_SdssShape_flux_xxinstFlux', cat.schema) 

583 self.assertIn('base_SdssShape_instFlux_xx_Cov', cat.schema) 

584 self.assertNotIn('base_Blendedness_abs_flux_cinstFlux', cat.schema) 

585 

586 def testFitsReadVersion2CompatibilityRealCoaddMeasCatalog(self): 

587 """DM-16068: some fields were not getting aliases they should have 

588 

589 In particular, the alias setting relies on flux fields having their 

590 units set properly. Prior to a resolution of DM-16068, the units 

591 were not getting set for several CModel flux fields and one deblender 

592 field (deblend_psfFlux), and thus were not getting the 

593 `_flux`->`_instFlux` aliases set. 

594 

595 NOTE: this test will (is meant to) fail on the read until DM-16068 is 

596 resolved. The error is: 

597 

598 lsst::pex::exceptions::NotFoundError: 'Field or subfield with 

599 name 'modelfit_CModel_instFlux' not found with type 'D'.' 

600 """ 

601 cat = lsst.afw.table.SourceCatalog.readFits( 

602 os.path.join(testPath, "data", "deepCoadd_meas_HSC_v2.fits")) 

603 self.assertIn('modelfit_CModel_instFlux', cat.schema) 

604 self.assertIn('modelfit_CModel_instFluxErr', cat.schema) 

605 self.assertIn('modelfit_CModel_instFlux_inner', cat.schema) 

606 self.assertIn('modelfit_CModel_dev_instFlux_inner', cat.schema) 

607 self.assertIn('modelfit_CModel_exp_instFlux_inner', cat.schema) 

608 self.assertIn('modelfit_CModel_initial_instFlux_inner', cat.schema) 

609 

610 def testFitsVersion2Compatibility(self): 

611 """Test reading of catalogs with version 2 schema 

612 

613 Version 2 catalogs need to have added aliases from `_flux`->`_instFlux`. 

614 """ 

615 cat = lsst.afw.table.SourceCatalog.readFits(os.path.join(testPath, "data", "sourceTable-v2.fits")) 

616 # check the flux->instFlux conversion 

617 self.assertEqual(cat.schema["a_flux"].asKey(), cat.schema["a_instFlux"].asKey()) 

618 self.assertEqual(cat.schema["a_fluxErr"].asKey(), cat.schema["a_instFluxErr"].asKey()) 

619 

620 def testDM1083(self): 

621 schema = lsst.afw.table.SourceTable.makeMinimalSchema() 

622 st = lsst.afw.table.SourceTable.make(schema) 

623 cat = lsst.afw.table.SourceCatalog(st) 

624 tmp = lsst.afw.table.SourceCatalog(cat.getTable()) 

625 record = tmp.addNew() 

626 cat.extend(tmp) 

627 self.assertEqual(cat[0].getId(), record.getId()) 

628 # check that the same record is in both catalogs (not a copy) 

629 record.setId(15) 

630 self.assertEqual(cat[0].getId(), record.getId()) 

631 

632 def testSlotUndefine(self): 

633 """Test that we can correctly define and undefine a slot after a SourceTable has been created""" 

634 schema = lsst.afw.table.SourceTable.makeMinimalSchema() 

635 key = schema.addField("a_instFlux", type=np.float64, doc="flux field") 

636 table = lsst.afw.table.SourceTable.make(schema) 

637 table.definePsfFlux("a") 

638 self.assertEqual(table.getPsfFluxSlot().getMeasKey(), key) 

639 table.schema.getAliasMap().erase("slot_PsfFlux") 

640 self.assertFalse(table.getPsfFluxSlot().isValid()) 

641 

642 def testOldFootprintPersistence(self): 

643 """Test that we can still read SourceCatalogs with (Heavy)Footprints saved by an older 

644 version of the pipeline with a different format. 

645 """ 

646 filename = os.path.join(testPath, "data", "old-footprint-persistence.fits") 

647 catalog1 = lsst.afw.table.SourceCatalog.readFits(filename) 

648 self.assertEqual(len(catalog1), 2) 

649 with self.assertRaises(LookupError): 

650 catalog1.schema.find("footprint") 

651 fp1 = catalog1[0].getFootprint() 

652 fp2 = catalog1[1].getFootprint() 

653 self.assertEqual(fp1.getArea(), 495) 

654 self.assertEqual(fp2.getArea(), 767) 

655 self.assertFalse(fp1.isHeavy()) 

656 self.assertTrue(fp2.isHeavy()) 

657 self.assertEqual(len(fp1.getSpans()), 29) 

658 self.assertEqual(len(fp2.getSpans()), 44) 

659 self.assertEqual(len(fp1.getPeaks()), 1) 

660 self.assertEqual(len(fp2.getPeaks()), 1) 

661 self.assertEqual(fp1.getBBox(), 

662 lsst.geom.Box2I(lsst.geom.Point2I(129, 2), lsst.geom.Extent2I(25, 29))) 

663 self.assertEqual(fp2.getBBox(), 

664 lsst.geom.Box2I(lsst.geom.Point2I(1184, 2), lsst.geom.Extent2I(78, 38))) 

665 hfp = lsst.afw.detection.HeavyFootprintF(fp2) 

666 self.assertEqual(len(hfp.getImageArray()), fp2.getArea()) 

667 self.assertEqual(len(hfp.getMaskArray()), fp2.getArea()) 

668 self.assertEqual(len(hfp.getVarianceArray()), fp2.getArea()) 

669 catalog2 = lsst.afw.table.SourceCatalog.readFits( 

670 filename, flags=lsst.afw.table.SOURCE_IO_NO_HEAVY_FOOTPRINTS) 

671 self.assertEqual(list(fp1.getSpans()), 

672 list(catalog2[0].getFootprint().getSpans())) 

673 self.assertEqual(list(fp2.getSpans()), 

674 list(catalog2[1].getFootprint().getSpans())) 

675 self.assertFalse(catalog2[1].getFootprint().isHeavy()) 

676 catalog3 = lsst.afw.table.SourceCatalog.readFits( 

677 filename, flags=lsst.afw.table.SOURCE_IO_NO_FOOTPRINTS) 

678 self.assertEqual(catalog3[0].getFootprint(), None) 

679 self.assertEqual(catalog3[1].getFootprint(), None) 

680 

681 def _testFluxSlot(self, slotName): 

682 """Demonstrate that we can create & use the named Flux slot.""" 

683 schema = lsst.afw.table.SourceTable.makeMinimalSchema() 

684 baseName = "afw_Test" 

685 instFluxKey = schema.addField(f"{baseName}_instFlux", type=np.float64, doc="flux") 

686 errKey = schema.addField(f"{baseName}_instFluxErr", type=np.float64, doc="flux uncertainty") 

687 flagKey = schema.addField(f"{baseName}_flag", type="Flag", doc="flux flag") 

688 catalog = lsst.afw.table.SourceCatalog(schema) 

689 table = catalog.table 

690 

691 # Initially, the slot is undefined. 

692 self.assertFalse(getattr(table, f"get{slotName}Slot")().isValid()) 

693 

694 # After definition, it maps to the keys defined above. 

695 getattr(table, f"define{slotName}")(baseName) 

696 self.assertTrue(getattr(table, f"get{slotName}Slot")().isValid()) 

697 self.assertEqual(getattr(table, f"get{slotName}Slot")().getMeasKey(), instFluxKey) 

698 self.assertEqual(getattr(table, f"get{slotName}Slot")().getErrKey(), errKey) 

699 self.assertEqual(getattr(table, f"get{slotName}Slot")().getFlagKey(), flagKey) 

700 

701 # We should be able to retrieve arbitrary values set in records. 

702 record = catalog.addNew() 

703 instFlux, err, flag = 10.0, 1.0, False 

704 record.set(instFluxKey, instFlux) 

705 record.set(errKey, err) 

706 record.set(flagKey, flag) 

707 instFluxName = slotName.replace("Flux", "InstFlux") 

708 self.assertEqual(getattr(record, f"get{instFluxName}")(), instFlux) 

709 self.assertEqual(getattr(record, f"get{instFluxName}Err")(), err) 

710 self.assertEqual(getattr(record, f"get{slotName}Flag")(), flag) 

711 

712 # Should also be able to retrieve them as columns from the catalog 

713 self.assertEqual(getattr(catalog, f"get{instFluxName}")()[0], instFlux) 

714 self.assertEqual(getattr(catalog, f"get{instFluxName}Err")()[0], err) 

715 

716 # And we should be able to delete the slot, breaking the mapping. 

717 table.schema.getAliasMap().erase(f"slot_{slotName}") 

718 self.assertFalse(getattr(table, f"get{slotName}Slot")().isValid()) 

719 self.assertNotEqual(getattr(table, f"get{slotName}Slot")().getMeasKey(), instFluxKey) 

720 self.assertNotEqual(getattr(table, f"get{slotName}Slot")().getErrKey(), errKey) 

721 self.assertNotEqual(getattr(table, f"get{slotName}Slot")().getFlagKey(), flagKey) 

722 

723 # When the slot has been deleted, attempting to access it should 

724 # throw a LogicError. 

725 with self.assertRaises(lsst.pex.exceptions.LogicError): 

726 getattr(catalog, f"get{instFluxName}")() 

727 with self.assertRaises(lsst.pex.exceptions.LogicError): 

728 getattr(catalog, f"get{instFluxName}Err")() 

729 

730 def testFluxSlots(self): 

731 """Check that all the expected flux slots are present & correct.""" 

732 for slotName in ["ApFlux", "CalibFlux", "GaussianFlux", "ModelFlux", 

733 "PsfFlux"]: 

734 self._testFluxSlot(slotName) 

735 

736 # But, of course, we should not accept a slot which hasn't be defined. 

737 with self.assertRaises(AttributeError): 

738 self._testFluxSlot("NotExtantFlux") 

739 

740 def testStr(self): 

741 """Check that the str() produced on a catalog contains expected things.""" 

742 string = str(self.catalog) 

743 for field in ('id', 'coord_ra', 'coord_dec'): 

744 self.assertIn(field, string) 

745 

746 def testRepr(self): 

747 """Check that the repr() produced on a catalog contains expected things.""" 

748 string = repr(self.catalog) 

749 self.assertIn(str(type(self.catalog)), string) 

750 for field in ('id', 'coord_ra', 'coord_dec'): 

751 self.assertIn(field, string) 

752 

753 def testStrNonContiguous(self): 

754 """Check that str() doesn't fail on non-contiguous tables.""" 

755 del self.catalog[1] 

756 string = str(self.catalog) 

757 self.assertIn('Non-contiguous afw.Catalog of 2 rows.', string) 

758 for field in ('id', 'coord_ra', 'coord_dec'): 

759 self.assertIn(field, string) 

760 

761 def testRecordStr(self): 

762 """Test that str(record) contains expected things.""" 

763 string = str(self.catalog[0]) 

764 for field in ('id: 50', 'coord_ra: nan', 'coord_dec: nan'): 

765 self.assertIn(field, string) 

766 

767 def testRecordRepr(self): 

768 """Test that repr(record) contains expected things.""" 

769 string = repr(self.catalog[0]) 

770 self.assertIn(str(type(self.catalog[0])), string) 

771 for field in ('id: 50', 'coord_ra: nan', 'coord_dec: nan'): 

772 self.assertIn(field, string) 

773 

774 def testGetNonContiguous(self): 

775 """Check that we can index on non-contiguous tables""" 

776 # Make a non-contiguous catalog 

777 nonContiguous = type(self.catalog)(self.catalog.table) 

778 for rr in reversed(self.catalog): 

779 nonContiguous.append(rr) 

780 num = len(self.catalog) 

781 # Check assumptions 

782 self.assertFalse(nonContiguous.isContiguous()) # We managed to produce a non-contiguous catalog 

783 self.assertEqual(len(set(self.catalog["id"])), num) # ID values are unique 

784 # Indexing with boolean array 

785 select = np.zeros(num, dtype=bool) 

786 select[1] = True 

787 self.assertEqual(nonContiguous[np.flip(select, 0)]["id"], self.catalog[select]["id"]) 

788 # Extracting a number column 

789 column = "a_instFlux" 

790 array = nonContiguous[column] 

791 self.assertFloatsEqual(np.flip(array, 0), self.catalog[column]) 

792 with self.assertRaises(ValueError): 

793 array[1] = 1.2345 # Should be immutable 

794 # Extracting a flag column 

795 column = "a_flag" 

796 array = nonContiguous[column] 

797 np.testing.assert_equal(np.flip(array, 0), self.catalog[column]) 

798 with self.assertRaises(ValueError): 

799 array[1] = True # Should be immutable 

800 

801 

802class MemoryTester(lsst.utils.tests.MemoryTestCase): 

803 pass 

804 

805 

806def setup_module(module): 

807 lsst.utils.tests.init() 

808 

809 

810if __name__ == "__main__": 810 ↛ 811line 810 didn't jump to line 811 because the condition on line 810 was never true

811 lsst.utils.tests.init() 

812 unittest.main()