Coverage for tests/test_psfIO.py: 16%

229 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2022-11-11 11:11 +0000

1# This file is part of meas_algorithms. 

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 math 

24import unittest 

25import tempfile 

26 

27import numpy as np 

28 

29import lsst.utils.tests 

30import lsst.geom 

31import lsst.afw.image as afwImage 

32import lsst.afw.detection as afwDetection 

33import lsst.afw.math as afwMath 

34import lsst.afw.table as afwTable 

35from lsst.log import Log 

36import lsst.meas.base as measBase 

37import lsst.meas.algorithms as algorithms 

38 

39try: 

40 type(display) 

41except NameError: 

42 display = False 

43else: 

44 import lsst.afw.display as afwDisplay 

45 afwDisplay.setDefaultMaskTransparency(75) 

46 

47# Change the level to Log.DEBUG or Log.TRACE to see debug messages 

48Log.getLogger("lsst.measurement").setLevel(Log.INFO) 

49 

50 

51def roundTripPsf(key, psf): 

52 with tempfile.NamedTemporaryFile() as f: 

53 psf.writeFits(f.name) 

54 psf2 = type(psf).readFits(f.name) 

55 

56 return psf2 

57 

58 

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

60 """A test case for SpatialModelPsf""" 

61 

62 def setUp(self): 

63 width, height = 100, 300 

64 self.mi = afwImage.MaskedImageF(lsst.geom.ExtentI(width, height)) 

65 self.mi.set(0) 

66 self.mi.getVariance().set(10) 

67 self.mi.getMask().addMaskPlane("DETECTED") 

68 

69 self.FWHM = 5 

70 self.ksize = 25 # size of desired kernel 

71 

72 self.exposure = afwImage.makeExposure(self.mi) 

73 

74 psf = roundTripPsf(2, algorithms.DoubleGaussianPsf(self.ksize, self.ksize, 

75 self.FWHM/(2*math.sqrt(2*math.log(2))), 1, 0.1)) 

76 self.exposure.setPsf(psf) 

77 

78 for x, y in [(20, 20), 

79 # (30, 35), (50, 50), 

80 (60, 20), (60, 210), (20, 210)]: 

81 

82 flux = 10000 - 0*x - 10*y 

83 

84 sigma = 3 + 0.01*(y - self.mi.getHeight()/2) 

85 psf = roundTripPsf(3, algorithms.DoubleGaussianPsf(self.ksize, self.ksize, sigma, 1, 0.1)) 

86 im = psf.computeImage(psf.getAveragePosition()).convertF() 

87 im *= flux 

88 x0y0 = lsst.geom.PointI(x - self.ksize//2, y - self.ksize//2) 

89 smi = self.mi.getImage().Factory(self.mi.getImage(), 

90 lsst.geom.BoxI(x0y0, lsst.geom.ExtentI(self.ksize)), 

91 afwImage.LOCAL) 

92 

93 if False: # Test subtraction with non-centered psfs 

94 im = afwMath.offsetImage(im, 0.5, 0.5) 

95 

96 smi += im 

97 del psf 

98 del im 

99 del smi 

100 

101 roundTripPsf(4, algorithms.DoubleGaussianPsf(self.ksize, self.ksize, 

102 self.FWHM/(2*math.sqrt(2*math.log(2))), 1, 0.1)) 

103 

104 self.cellSet = afwMath.SpatialCellSet(lsst.geom.BoxI(lsst.geom.PointI(0, 0), 

105 lsst.geom.ExtentI(width, height)), 100) 

106 ds = afwDetection.FootprintSet(self.mi, afwDetection.Threshold(10), "DETECTED") 

107 # 

108 # Prepare to measure 

109 # 

110 schema = afwTable.SourceTable.makeMinimalSchema() 

111 sfm_config = measBase.SingleFrameMeasurementConfig() 

112 sfm_config.plugins = ["base_SdssCentroid", "base_CircularApertureFlux", "base_PsfFlux", 

113 "base_SdssShape", "base_GaussianFlux", "base_PixelFlags"] 

114 sfm_config.slots.centroid = "base_SdssCentroid" 

115 sfm_config.slots.shape = "base_SdssShape" 

116 sfm_config.slots.psfFlux = "base_PsfFlux" 

117 sfm_config.slots.gaussianFlux = None 

118 sfm_config.slots.apFlux = "base_CircularApertureFlux_3_0" 

119 sfm_config.slots.modelFlux = "base_GaussianFlux" 

120 sfm_config.slots.calibFlux = None 

121 sfm_config.plugins["base_SdssShape"].maxShift = 10.0 

122 sfm_config.plugins["base_CircularApertureFlux"].radii = [3.0] 

123 task = measBase.SingleFrameMeasurementTask(schema, config=sfm_config) 

124 measCat = afwTable.SourceCatalog(schema) 

125 # detect the sources and run with the measurement task 

126 ds.makeSources(measCat) 

127 task.run(measCat, self.exposure) 

128 for source in measCat: 

129 self.cellSet.insertCandidate(algorithms.makePsfCandidate(source, self.exposure)) 

130 

131 def tearDown(self): 

132 del self.exposure 

133 del self.cellSet 

134 del self.mi 

135 del self.ksize 

136 del self.FWHM 

137 

138 def testGetPcaKernel(self): 

139 """Convert our cellSet to a LinearCombinationKernel""" 

140 

141 nEigenComponents = 2 

142 spatialOrder = 1 

143 kernelSize = 21 

144 nStarPerCell = 2 

145 nStarPerCellSpatialFit = 2 

146 tolerance = 1e-5 

147 

148 if display: 

149 disp = afwDisplay.Display(frame=0) 

150 disp.mtv(self.mi, title=self._testMethodName + ": image") 

151 # 

152 # Show the candidates we're using 

153 # 

154 for cell in self.cellSet.getCellList(): 

155 i = 0 

156 for cand in cell: 

157 i += 1 

158 source = cand.getSource() 

159 xc, yc = source.getX() - self.mi.getX0(), source.getY() - self.mi.getY0() 

160 if i <= nStarPerCell: 

161 disp.dot("o", xc, yc, ctype=afwDisplay.GREEN) 

162 else: 

163 disp.dot("o", xc, yc, ctype=afwDisplay.YELLOW) 

164 

165 pair = algorithms.createKernelFromPsfCandidates(self.cellSet, self.exposure.getDimensions(), 

166 self.exposure.getXY0(), nEigenComponents, 

167 spatialOrder, kernelSize, nStarPerCell) 

168 

169 kernel, eigenValues = pair[0], pair[1] 

170 del pair 

171 

172 print("lambda", " ".join(["%g" % val for val in eigenValues])) 

173 

174 pair = algorithms.fitSpatialKernelFromPsfCandidates(kernel, self.cellSet, nStarPerCellSpatialFit, 

175 tolerance) 

176 status, chi2 = pair[0], pair[1] 

177 del pair 

178 print("Spatial fit: %s chi^2 = %.2g" % (status, chi2)) 

179 

180 psf = roundTripPsf(5, algorithms.PcaPsf(kernel)) # Hurrah! 

181 

182 self.assertIsNotNone(psf.getKernel()) 

183 

184 self.checkTablePersistence(psf) 

185 

186 if display: 

187 # print psf.getKernel().toString() 

188 

189 eImages = [] 

190 for k in psf.getKernel().getKernelList(): 

191 im = afwImage.ImageD(k.getDimensions()) 

192 k.computeImage(im, False) 

193 eImages.append(im) 

194 

195 mos = afwDisplay.utils.Mosaic() 

196 disp = afwDisplay.Display(frame=3) 

197 disp.mtv(mos.makeMosaic(eImages), title=self._testMethodName + ": mosaic") 

198 disp.dot("Eigen Images", 0, 0) 

199 # 

200 # Make a mosaic of PSF candidates 

201 # 

202 stamps = [] 

203 stampInfo = [] 

204 

205 for cell in self.cellSet.getCellList(): 

206 for cand in cell: 

207 s = cand.getSource() 

208 im = cand.getMaskedImage() 

209 

210 stamps.append(im) 

211 stampInfo.append("[%d 0x%x]" % (s.getId(), s["base_PixelFlags_flag"])) 

212 

213 mos = afwDisplay.utils.Mosaic() 

214 disp = afwDisplay.Display(frame=1) 

215 disp.mtv(mos.makeMosaic(stamps), title=self._testMethodName + ": PSF candidates") 

216 for i in range(len(stampInfo)): 

217 disp.dot(stampInfo[i], mos.getBBox(i).getMinX(), mos.getBBox(i).getMinY(), 

218 ctype=afwDisplay.RED) 

219 

220 psfImages = [] 

221 labels = [] 

222 if False: 

223 nx, ny = 3, 4 

224 for iy in range(ny): 

225 for ix in range(nx): 

226 x = int((ix + 0.5)*self.mi.getWidth()/nx) 

227 y = int((iy + 0.5)*self.mi.getHeight()/ny) 

228 

229 im = psf.getImage(x, y) 

230 psfImages.append(im.Factory(im, True)) 

231 labels.append("PSF(%d,%d)" % (int(x), int(y))) 

232 

233 if True: 

234 print((x, y, "PSF parameters:", psf.getKernel().getKernelParameters())) 

235 else: 

236 nx, ny = 2, 2 

237 for x, y in [(20, 20), (60, 20), 

238 (60, 210), (20, 210)]: 

239 

240 im = psf.computeImage(lsst.geom.PointD(x, y)) 

241 psfImages.append(im.Factory(im, True)) 

242 labels.append("PSF(%d,%d)" % (int(x), int(y))) 

243 

244 if True: 

245 print(x, y, "PSF parameters:", psf.getKernel().getKernelParameters()) 

246 mos = afwDisplay.utils.Mosaic() 

247 disp = afwDisplay.Display(frame=2) 

248 mos.makeMosaic(psfImages, display=disp, mode=nx) 

249 mos.drawLabels(labels, display=disp) 

250 

251 if display: 

252 disp = afwDisplay.Display(frame=0) 

253 disp.mtv(self.mi, title=self._testMethodName + ": image") 

254 

255 psfImages = [] 

256 labels = [] 

257 if False: 

258 nx, ny = 3, 4 

259 for iy in range(ny): 

260 for ix in range(nx): 

261 x = int((ix + 0.5)*self.mi.getWidth()/nx) 

262 y = int((iy + 0.5)*self.mi.getHeight()/ny) 

263 

264 algorithms.subtractPsf(psf, self.mi, x, y) 

265 else: 

266 nx, ny = 2, 2 

267 for x, y in [(20, 20), (60, 20), 

268 (60, 210), (20, 210)]: 

269 

270 if False: # Test subtraction with non-centered psfs 

271 x += 0.5 

272 y -= 0.5 

273 

274 # algorithms.subtractPsf(psf, self.mi, x, y) 

275 

276 afwDisplay.Display(frame=1).mtv(self.mi, title=self._testMethodName + ": image") 

277 

278 def testCandidateList(self): 

279 if False and display: 

280 disp = afwDisplay.Display(frame=0) 

281 disp.mtv(self.mi, title=self._testMethodName + ": image") 

282 

283 for cell in self.cellSet.getCellList(): 

284 x0, y0, x1, y1 = ( 

285 cell.getBBox().getX0(), cell.getBBox().getY0(), 

286 cell.getBBox().getX1(), cell.getBBox().getY1()) 

287 print((x0, y0, " ", x1, y1)) 

288 x0 -= 0.5 

289 y0 -= 0.5 

290 x1 += 0.5 

291 y1 += 0.5 

292 

293 disp.line([(x0, y0), (x1, y0), (x1, y1), (x0, y1), (x0, y0)], ctype=afwDisplay.RED) 

294 

295 self.assertFalse(self.cellSet.getCellList()[0].empty()) 

296 self.assertTrue(self.cellSet.getCellList()[1].empty()) 

297 self.assertFalse(self.cellSet.getCellList()[2].empty()) 

298 

299 stamps = [] 

300 for cell in self.cellSet.getCellList(): 

301 for cand in cell: 

302 cand = cell[0] 

303 width, height = 15, 17 

304 cand.setWidth(width) 

305 cand.setHeight(height) 

306 

307 im = cand.getMaskedImage() 

308 stamps.append(im) 

309 

310 self.assertEqual(im.getWidth(), width) 

311 self.assertEqual(im.getHeight(), height) 

312 

313 if display: 

314 mos = afwDisplay.utils.Mosaic() 

315 afwDisplay.Display(frame=1).mtv(mos.makeMosaic(stamps), title=self._testMethodName + ": image") 

316 

317 def checkTablePersistence(self, psf1): 

318 """Called by testGetPcaKernel to test table-based persistence; it's a pain to 

319 build a PcaPsf, so we don't want to repeat it all for each test case. 

320 

321 We just verify here that we get a LinearCombinationKernel; all the details of 

322 testing that we get the *right* one are tested more thoroughly in afw. 

323 """ 

324 print("Testing PcaPsf!") 

325 filename = "PcaPsf.fits" 

326 psf1.writeFits(filename) 

327 psf2 = algorithms.PcaPsf.readFits(filename) 

328 self.assertIsNotNone(psf2) 

329 self.assertIsNotNone(psf2.getKernel()) 

330 os.remove(filename) 

331 

332 

333class SingleGaussianPsfTestCase(unittest.TestCase): 

334 

335 def testTablePersistence(self): 

336 filename = "SingleGaussianPsf.fits" 

337 psf1 = algorithms.SingleGaussianPsf(5, 7, 4.2) 

338 psf1.writeFits(filename) 

339 psf2 = algorithms.SingleGaussianPsf.readFits(filename) 

340 self.assertEqual(psf1.getSigma(), psf2.getSigma()) 

341 os.remove(filename) 

342 

343 

344class DoubleGaussianPsfTestCase(unittest.TestCase): 

345 """A test case for DoubleGaussianPsf""" 

346 

347 def comparePsfs(self, psf1, psf2): 

348 self.assertTrue(isinstance(psf1, algorithms.DoubleGaussianPsf)) 

349 self.assertTrue(isinstance(psf2, algorithms.DoubleGaussianPsf)) 

350 self.assertEqual(psf1.getKernel().getWidth(), psf2.getKernel().getWidth()) 

351 self.assertEqual(psf1.getKernel().getHeight(), psf2.getKernel().getHeight()) 

352 self.assertEqual(psf1.getSigma1(), psf2.getSigma1()) 

353 self.assertEqual(psf1.getSigma2(), psf2.getSigma2()) 

354 self.assertEqual(psf1.getB(), psf2.getB()) 

355 

356 def setUp(self): 

357 self.ksize = 25 # size of desired kernel 

358 FWHM = 5 

359 self.sigma1 = FWHM/(2*np.sqrt(2*np.log(2))) 

360 self.sigma2 = 2*self.sigma1 

361 self.b = 0.1 

362 

363 def tearDown(self): 

364 del self.ksize 

365 del self.sigma1 

366 del self.sigma2 

367 del self.b 

368 

369 def testBoostPersistence(self): 

370 psf1 = algorithms.DoubleGaussianPsf(self.ksize, self.ksize, self.sigma1, self.sigma2, self.b) 

371 psf2 = roundTripPsf(1, psf1) 

372 psf3 = roundTripPsf(1, psf1) 

373 self.comparePsfs(psf1, psf2) 

374 self.comparePsfs(psf1, psf3) 

375 

376 def testFitsPersistence(self): 

377 psf1 = algorithms.DoubleGaussianPsf(self.ksize, self.ksize, self.sigma1, self.sigma2, self.b) 

378 filename = "tests/data/psf1-1.fits" 

379 psf1.writeFits(filename) 

380 psf2 = algorithms.DoubleGaussianPsf.readFits(filename) 

381 self.comparePsfs(psf1, psf2) 

382 

383 

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

385 pass 

386 

387 

388def setup_module(module): 

389 lsst.utils.tests.init() 

390 

391 

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

393 lsst.utils.tests.init() 

394 unittest.main()