Coverage for tests/test_kernelCandidateAndSolution.py: 13%

329 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-06-06 03:35 -0700

1# This file is part of ip_diffim. 

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 

24 

25import lsst.utils.tests 

26import lsst.utils 

27import lsst.afw.image as afwImage 

28import lsst.afw.math as afwMath 

29import lsst.geom as geom 

30import lsst.ip.diffim as ipDiffim 

31import lsst.pex.config as pexConfig 

32import lsst.utils.logging as logUtils 

33import lsst.afw.table as afwTable 

34 

35logUtils.trace_set_at("lsst.ip.diffim", 4) 

36 

37# known input images 

38try: 

39 defDataDir = lsst.utils.getPackageDir('afwdata') 

40except Exception: 

41 defDataDir = None 

42 

43try: 

44 display 

45 defDataDir 

46except NameError: 

47 display = False 

48else: 

49 import lsst.afw.display as afwDisplay 

50 afwDisplay.setDefaultMaskTransparency(75) 

51 

52 

53class DiffimTestCases(lsst.utils.tests.TestCase): 

54 

55 def setUp(self): 

56 schema = afwTable.SourceTable.makeMinimalSchema() 

57 afwTable.Point2DKey.addFields(schema, "Centroid", "input centroid", "pixel") 

58 schema.addField("PsfFlux_instFlux", type=float) 

59 schema.addField("PsfFlux_instFluxErr", type=float) 

60 schema.addField("PsfFlux_flag", type="Flag") 

61 self.table = afwTable.SourceTable.make(schema) 

62 self.table.definePsfFlux("PsfFlux") 

63 self.table.defineCentroid("Centroid") 

64 self.ss = afwTable.SourceCatalog(self.table) 

65 

66 self.config = ipDiffim.PsfMatchConfigDF() 

67 

68 self.ps = pexConfig.makePropertySet(self.config) 

69 self.ps['fitForBackground'] = True # we are testing known background recovery here 

70 self.ps['checkConditionNumber'] = False # just in case 

71 self.ps["useRegularization"] = False 

72 

73 if defDataDir: 

74 defSciencePath = os.path.join(defDataDir, "DC3a-Sim", "sci", "v26-e0", 

75 "v26-e0-c011-a10.sci.fits") 

76 defTemplatePath = os.path.join(defDataDir, "DC3a-Sim", "sci", "v5-e0", 

77 "v5-e0-c011-a10.sci.fits") 

78 

79 scienceExposure = afwImage.ExposureF(defSciencePath) 

80 templateExposure = afwImage.ExposureF(defTemplatePath) 

81 # set XY0 = 0 

82 scienceExposure.setXY0(geom.Point2I(0, 0)) 

83 templateExposure.setXY0(geom.Point2I(0, 0)) 

84 # do the warping first so we don't have any masked pixels in the postage stamps 

85 warper = afwMath.Warper.fromConfig(self.config.warpingConfig) 

86 templateExposure = warper.warpExposure(scienceExposure.getWcs(), templateExposure, 

87 destBBox=scienceExposure.getBBox()) 

88 

89 # Change xy0 

90 # Nice star at position 276, 717 

91 # And should be at index 40, 40 

92 # No masked pixels in this one 

93 self.x02 = 276 

94 self.y02 = 717 

95 size = 40 

96 bbox2 = geom.Box2I(geom.Point2I(self.x02 - size, self.y02 - size), 

97 geom.Point2I(self.x02 + size, self.y02 + size)) 

98 self.scienceImage2 = afwImage.ExposureF(scienceExposure, bbox2, origin=afwImage.LOCAL) 

99 self.templateExposure2 = afwImage.ExposureF(templateExposure, bbox2, origin=afwImage.LOCAL) 

100 

101 def addNoise(self, mi): 

102 img = mi.image 

103 seed = int(afwMath.makeStatistics(mi.getVariance(), afwMath.MEDIAN).getValue()) 

104 rdm = afwMath.Random(afwMath.Random.MT19937, seed) 

105 rdmImage = img.Factory(img.getDimensions()) 

106 afwMath.randomGaussianImage(rdmImage, rdm) 

107 img += rdmImage 

108 return afwMath.makeStatistics(rdmImage, afwMath.MEAN).getValue(afwMath.MEAN) 

109 

110 def verifyDeltaFunctionSolution(self, solution, kSum=1.0, bg=0.0): 

111 # when kSum = 1.0, this agrees to the default precision. when 

112 # kSum != 1.0 I need to go to only 4 digits. 

113 # 

114 # -5.4640810225678728e-06 != 0.0 within 7 places 

115 # 

116 bgSolution = solution.getBackground() 

117 self.assertAlmostEqual(bgSolution, bg, 4) 

118 

119 # again when kSum = 1.0 this agrees. otherwise 

120 # 

121 # 2.7000000605594079 != 2.7000000000000002 within 7 places 

122 # 

123 kSumSolution = solution.getKsum() 

124 self.assertAlmostEqual(kSumSolution, kSum, 5) 

125 

126 kImage = solution.makeKernelImage() 

127 for j in range(kImage.getHeight()): 

128 for i in range(kImage.getWidth()): 

129 

130 if (i == kImage.getWidth() // 2) and (j == kImage.getHeight() // 2): 

131 self.assertAlmostEqual(kImage[i, j, afwImage.LOCAL], kSum, 5) 

132 else: 

133 self.assertAlmostEqual(kImage[i, j, afwImage.LOCAL], 0., 5) 

134 

135 @unittest.skipIf(not defDataDir, "Warning: afwdata is not set up") 

136 def testConstructor(self): 

137 # Original and uninitialized 

138 kc = ipDiffim.KernelCandidateF(self.x02, self.y02, 

139 self.templateExposure2.getMaskedImage(), 

140 self.scienceImage2.getMaskedImage(), 

141 self.ps) 

142 

143 # Kernel not initialized 

144 self.assertEqual(kc.isInitialized(), False) 

145 

146 # But this should be set on construction 

147 try: 

148 kc.getCandidateRating() 

149 except Exception as e: 

150 print(e) 

151 self.fail() 

152 

153 # And these should be filled 

154 try: 

155 kc.getTemplateMaskedImage() 

156 kc.getScienceMaskedImage() 

157 except Exception as e: 

158 print(e) 

159 self.fail() 

160 

161 # And of the right type 

162 self.assertEqual(type(kc.getTemplateMaskedImage()), afwImage.MaskedImageF) 

163 self.assertEqual(type(kc.getScienceMaskedImage()), afwImage.MaskedImageF) 

164 

165 # None of these should work 

166 for kType in (ipDiffim.KernelCandidateF.ORIG, 

167 ipDiffim.KernelCandidateF.PCA, 

168 ipDiffim.KernelCandidateF.RECENT): 

169 for kMethod in (kc.getKernelSolution, 

170 kc.getKernel, 

171 kc.getBackground, 

172 kc.getKsum, 

173 kc.getKernelImage, 

174 kc.getDifferenceImage): 

175 try: 

176 kMethod(kType) 

177 except Exception: 

178 pass 

179 else: 

180 self.fail() 

181 try: 

182 kc.getImage() 

183 except Exception: 

184 pass 

185 else: 

186 self.fail() 

187 

188 @unittest.skipIf(not defDataDir, "Warning: afwdata is not set up") 

189 def testSourceStats(self): 

190 source = self.ss.addNew() 

191 source.setId(1) 

192 source.set(self.table.getCentroidSlot().getMeasKey().getX(), 276) 

193 source.set(self.table.getCentroidSlot().getMeasKey().getY(), 717) 

194 source.set("slot_PsfFlux_instFlux", 1.) 

195 

196 kc = ipDiffim.KernelCandidateF(source, 

197 self.templateExposure2.getMaskedImage(), 

198 self.scienceImage2.getMaskedImage(), 

199 self.ps) 

200 kList = ipDiffim.makeKernelBasisList(self.config) 

201 

202 kc.build(kList) 

203 self.assertEqual(kc.isInitialized(), True) 

204 

205 @unittest.skipIf(not defDataDir, "Warning: afwdata is not set up") 

206 def testSourceConstructor(self): 

207 source = self.ss.addNew() 

208 source.setId(1) 

209 source.set(self.table.getCentroidSlot().getMeasKey().getX(), 276) 

210 source.set(self.table.getCentroidSlot().getMeasKey().getY(), 717) 

211 source.set("slot_PsfFlux_instFlux", 1.) 

212 

213 kc = ipDiffim.KernelCandidateF(source, 

214 self.templateExposure2.getMaskedImage(), 

215 self.scienceImage2.getMaskedImage(), 

216 self.ps) 

217 

218 # Kernel not initialized 

219 self.assertEqual(kc.isInitialized(), False) 

220 

221 # Check that the source is set 

222 self.assertEqual(kc.getSource(), source) 

223 self.assertEqual(kc.getCandidateRating(), source.getPsfInstFlux()) 

224 

225 # But this should be set on construction 

226 try: 

227 kc.getCandidateRating() 

228 except Exception as e: 

229 print(e) 

230 self.fail() 

231 

232 # And these should be filled 

233 try: 

234 kc.getTemplateMaskedImage() 

235 kc.getScienceMaskedImage() 

236 except Exception as e: 

237 print(e) 

238 self.fail() 

239 

240 # And of the right type 

241 self.assertEqual(type(kc.getTemplateMaskedImage()), afwImage.MaskedImageF) 

242 self.assertEqual(type(kc.getScienceMaskedImage()), afwImage.MaskedImageF) 

243 

244 # None of these should work 

245 for kType in (ipDiffim.KernelCandidateF.ORIG, 

246 ipDiffim.KernelCandidateF.PCA, 

247 ipDiffim.KernelCandidateF.RECENT): 

248 for kMethod in (kc.getKernelSolution, 

249 kc.getKernel, 

250 kc.getBackground, 

251 kc.getKsum, 

252 kc.getKernelImage, 

253 kc.getDifferenceImage): 

254 try: 

255 kMethod(kType) 

256 except Exception: 

257 pass 

258 else: 

259 self.fail() 

260 try: 

261 kc.getImage() 

262 except Exception: 

263 pass 

264 else: 

265 self.fail() 

266 

267 kList = ipDiffim.makeKernelBasisList(self.config) 

268 

269 kc.build(kList) 

270 self.assertEqual(kc.isInitialized(), True) 

271 

272 @unittest.skipIf(not defDataDir, "Warning: afwdata is not set up") 

273 def testDeltaFunctionScaled(self, scaling=2.7, bg=11.3): 

274 sIm = afwImage.MaskedImageF(self.templateExposure2.getMaskedImage(), deep=True) 

275 sIm *= scaling 

276 kc = ipDiffim.KernelCandidateF(self.x02, self.y02, 

277 self.templateExposure2.getMaskedImage(), 

278 sIm, 

279 self.ps) 

280 

281 kList = ipDiffim.makeKernelBasisList(self.config) 

282 kc.build(kList) 

283 self.verifyDeltaFunctionSolution(kc.getKernelSolution(ipDiffim.KernelCandidateF.RECENT), 

284 kSum=scaling) 

285 

286 sIm = afwImage.MaskedImageF(self.templateExposure2.getMaskedImage(), deep=True) 

287 sIm += bg 

288 kc = ipDiffim.KernelCandidateF(self.x02, self.y02, 

289 self.templateExposure2.getMaskedImage(), 

290 sIm, 

291 self.ps) 

292 

293 kList = ipDiffim.makeKernelBasisList(self.config) 

294 kc.build(kList) 

295 self.verifyDeltaFunctionSolution(kc.getKernelSolution(ipDiffim.KernelCandidateF.RECENT), 

296 bg=bg) 

297 

298 @unittest.skipIf(not defDataDir, "Warning: afwdata is not set up") 

299 def testDeltaFunction(self): 

300 # Match an image to itself, with delta-function basis set 

301 # No regularization 

302 kc = ipDiffim.KernelCandidateF(self.x02, self.y02, 

303 self.templateExposure2.getMaskedImage(), 

304 self.templateExposure2.getMaskedImage(), 

305 self.ps) 

306 

307 kList = ipDiffim.makeKernelBasisList(self.config) 

308 

309 kc.build(kList) 

310 self.assertEqual(kc.isInitialized(), True) 

311 

312 # These should work 

313 for kType in (ipDiffim.KernelCandidateF.ORIG, 

314 ipDiffim.KernelCandidateF.RECENT): 

315 for kMethod in (kc.getKernelSolution, 

316 kc.getKernel, 

317 kc.getBackground, 

318 kc.getKsum, 

319 kc.getKernelImage, 

320 kc.getDifferenceImage): 

321 try: 

322 kMethod(kType) 

323 except Exception as e: 

324 print(kMethod, e) 

325 self.fail() 

326 else: 

327 pass 

328 try: 

329 kc.getImage() 

330 except Exception as e: 

331 print(kMethod, e) 

332 self.fail() 

333 else: 

334 pass 

335 

336 # None of these should work 

337 for kType in (ipDiffim.KernelCandidateF.PCA,): 

338 for kMethod in (kc.getKernelSolution, 

339 kc.getKernel, 

340 kc.getBackground, 

341 kc.getKsum, 

342 kc.getKernelImage, 

343 kc.getImage, 

344 kc.getDifferenceImage): 

345 try: 

346 kMethod(kType) 

347 except Exception: 

348 pass 

349 else: 

350 print(kMethod) 

351 self.fail() 

352 

353 self.verifyDeltaFunctionSolution(kc.getKernelSolution(ipDiffim.KernelCandidateF.RECENT)) 

354 

355 @unittest.skipIf(not defDataDir, "Warning: afwdata is not set up") 

356 def testGaussianWithNoise(self): 

357 # Convolve a real image with a gaussian and try and recover 

358 # it. Add noise and perform the same test. 

359 

360 gsize = self.ps["kernelSize"] 

361 gaussFunction = afwMath.GaussianFunction2D(2, 3) 

362 gaussKernel = afwMath.AnalyticKernel(gsize, gsize, gaussFunction) 

363 kImageIn = afwImage.ImageD(geom.Extent2I(gsize, gsize)) 

364 kSumIn = gaussKernel.computeImage(kImageIn, False) 

365 

366 imX, imY = self.templateExposure2.getMaskedImage().getDimensions() 

367 smi = afwImage.MaskedImageF(geom.Extent2I(imX, imY)) 

368 

369 convolutionControl = afwMath.ConvolutionControl() 

370 convolutionControl.setDoNormalize(False) 

371 afwMath.convolve(smi, self.templateExposure2.getMaskedImage(), gaussKernel, convolutionControl) 

372 

373 bbox = gaussKernel.shrinkBBox(smi.getBBox(afwImage.LOCAL)) 

374 

375 tmi2 = afwImage.MaskedImageF(self.templateExposure2.getMaskedImage(), bbox, origin=afwImage.LOCAL) 

376 smi2 = afwImage.MaskedImageF(smi, bbox, origin=afwImage.LOCAL) 

377 

378 kc = ipDiffim.KernelCandidateF(self.x02, self.y02, tmi2, smi2, self.ps) 

379 kList = ipDiffim.makeKernelBasisList(self.config) 

380 kc.build(kList) 

381 self.assertEqual(kc.isInitialized(), True) 

382 kImageOut = kc.getImage() 

383 

384 soln = kc.getKernelSolution(ipDiffim.KernelCandidateF.RECENT) 

385 self.assertAlmostEqual(soln.getKsum(), kSumIn) 

386 # 8.7499380640430563e-06 != 0.0 within 7 places 

387 self.assertAlmostEqual(soln.getBackground(), 0.0, 4) 

388 

389 for j in range(kImageOut.getHeight()): 

390 for i in range(kImageOut.getWidth()): 

391 

392 # in the outskirts of the kernel, the ratio can get screwed because of low S/N 

393 # e.g. 7.45817359824e-09 vs. 1.18062529402e-08 

394 # in the guts of the kernel it should look closer 

395 if kImageIn[i, j, afwImage.LOCAL] > 1e-4: 

396 # sigh, too bad this sort of thing fails.. 

397 # 0.99941584433815966 != 1.0 within 3 places 

398 self.assertAlmostEqual(kImageOut[i, j, afwImage.LOCAL]/kImageIn[i, j, afwImage.LOCAL], 

399 1.0, 2) 

400 

401 # now repeat with noise added; decrease precision of comparison 

402 self.addNoise(smi2) 

403 kc = ipDiffim.KernelCandidateF(self.x02, self.y02, tmi2, smi2, self.ps) 

404 kList = ipDiffim.makeKernelBasisList(self.config) 

405 kc.build(kList) 

406 self.assertEqual(kc.isInitialized(), True) 

407 kImageOut = kc.getImage() 

408 

409 soln = kc.getKernelSolution(ipDiffim.KernelCandidateF.RECENT) 

410 self.assertAlmostEqual(soln.getKsum(), kSumIn, 3) 

411 if not self.ps.get("fitForBackground"): 

412 self.assertEqual(soln.getBackground(), 0.0) 

413 

414 for j in range(kImageOut.getHeight()): 

415 for i in range(kImageOut.getWidth()): 

416 if kImageIn[i, j, afwImage.LOCAL] > 1e-2: 

417 self.assertAlmostEqual(kImageOut[i, j, afwImage.LOCAL], 

418 kImageIn[i, j, afwImage.LOCAL], 2) 

419 

420 def testGaussian(self, imsize=50): 

421 # Convolve a delta function with a known gaussian; try to 

422 # recover using delta-function basis 

423 

424 gsize = self.ps["kernelSize"] 

425 tsize = imsize + gsize 

426 

427 gaussFunction = afwMath.GaussianFunction2D(2, 3) 

428 gaussKernel = afwMath.AnalyticKernel(gsize, gsize, gaussFunction) 

429 kImageIn = afwImage.ImageD(geom.Extent2I(gsize, gsize)) 

430 gaussKernel.computeImage(kImageIn, False) 

431 

432 # template image with a single hot pixel in the exact center 

433 tmi = afwImage.MaskedImageF(geom.Extent2I(tsize, tsize)) 

434 tmi.set(0, 0x0, 1e-4) 

435 cpix = tsize // 2 

436 tmi[cpix, cpix, afwImage.LOCAL] = (1, 0x0, 1) 

437 

438 # science image 

439 smi = afwImage.MaskedImageF(tmi.getDimensions()) 

440 convolutionControl = afwMath.ConvolutionControl() 

441 convolutionControl.setDoNormalize(False) 

442 afwMath.convolve(smi, tmi, gaussKernel, convolutionControl) 

443 

444 # get the actual kernel sum (since the image is not infinite) 

445 gscaling = afwMath.makeStatistics(smi, afwMath.SUM).getValue(afwMath.SUM) 

446 

447 # grab only the non-masked subregion 

448 bbox = gaussKernel.shrinkBBox(smi.getBBox(afwImage.LOCAL)) 

449 

450 tmi2 = afwImage.MaskedImageF(tmi, bbox, origin=afwImage.LOCAL) 

451 smi2 = afwImage.MaskedImageF(smi, bbox, origin=afwImage.LOCAL) 

452 

453 # make sure its a valid subregion! 

454 for j in range(tmi2.getHeight()): 

455 for i in range(tmi2.getWidth()): 

456 self.assertEqual(tmi2.mask[i, j, afwImage.LOCAL], 0) 

457 self.assertEqual(smi2.mask[i, j, afwImage.LOCAL], 0) 

458 

459 kc = ipDiffim.KernelCandidateF(0.0, 0.0, tmi2, smi2, self.ps) 

460 kList = ipDiffim.makeKernelBasisList(self.config) 

461 kc.build(kList) 

462 self.assertEqual(kc.isInitialized(), True) 

463 kImageOut = kc.getImage() 

464 

465 soln = kc.getKernelSolution(ipDiffim.KernelCandidateF.RECENT) 

466 self.assertAlmostEqual(soln.getKsum(), gscaling) 

467 self.assertAlmostEqual(soln.getBackground(), 0.0) 

468 

469 for j in range(kImageOut.getHeight()): 

470 for i in range(kImageOut.getWidth()): 

471 self.assertAlmostEqual(kImageOut[i, j, afwImage.LOCAL]/kImageIn[i, j, afwImage.LOCAL], 

472 1.0, 5) 

473 

474 def testZeroVariance(self, imsize=50): 

475 gsize = self.ps["kernelSize"] 

476 tsize = imsize + gsize 

477 

478 tmi = afwImage.MaskedImageF(geom.Extent2I(tsize, tsize)) 

479 tmi.set(0, 0x0, 1.0) 

480 cpix = tsize // 2 

481 tmi[cpix, cpix, afwImage.LOCAL] = (1, 0x0, 0.0) 

482 smi = afwImage.MaskedImageF(geom.Extent2I(tsize, tsize)) 

483 smi.set(0, 0x0, 1.0) 

484 smi[cpix, cpix, afwImage.LOCAL] = (1, 0x0, 0.0) 

485 

486 kList = ipDiffim.makeKernelBasisList(self.config) 

487 self.ps["constantVarianceWeighting"] = False 

488 kc = ipDiffim.KernelCandidateF(0.0, 0.0, tmi, smi, self.ps) 

489 try: 

490 kc.build(kList) 

491 except Exception: 

492 pass 

493 else: 

494 self.fail() 

495 

496 @unittest.skipIf(not defDataDir, "Warning: afwdata is not set up") 

497 def testConstantWeighting(self): 

498 self.ps["fitForBackground"] = False 

499 self.testGaussian() 

500 self.testGaussianWithNoise() 

501 

502 @unittest.skipIf(not defDataDir, "Warning: afwdata is not set up") 

503 def testNoBackgroundFit(self): 

504 self.ps["constantVarianceWeighting"] = True 

505 self.testGaussian() 

506 

507 def testInsert(self): 

508 mi = afwImage.MaskedImageF(geom.Extent2I(10, 10)) 

509 kc = ipDiffim.makeKernelCandidate(0., 0., mi, mi, self.ps) 

510 kc.setStatus(afwMath.SpatialCellCandidate.GOOD) 

511 

512 sizeCellX = self.ps["sizeCellX"] 

513 sizeCellY = self.ps["sizeCellY"] 

514 kernelCellSet = afwMath.SpatialCellSet(geom.Box2I(geom.Point2I(0, 0), geom.Extent2I(1, 1)), 

515 sizeCellX, sizeCellY) 

516 kernelCellSet.insertCandidate(kc) 

517 nSeen = 0 

518 for cell in kernelCellSet.getCellList(): 

519 for cand in cell.begin(True): 

520 self.assertEqual(cand.getStatus(), afwMath.SpatialCellCandidate.GOOD) 

521 nSeen += 1 

522 self.assertEqual(nSeen, 1) 

523 

524 @unittest.skipIf(not display, "display is None: skipping testDisp") 

525 def testDisp(self): 

526 afwDisplay.Display(frame=1).mtv(self.scienceImage2, 

527 title=self._testMethodName + ": scienceImage2") 

528 afwDisplay.Display(frame=2).mtv(self.templateExposure2, 

529 title=self._testMethodName + ": templateExposure2") 

530 

531 def tearDown(self): 

532 del self.ps 

533 del self.table 

534 del self.ss 

535 if defDataDir: 

536 del self.scienceImage2 

537 del self.templateExposure2 

538 

539##### 

540 

541 

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

543 pass 

544 

545 

546def setup_module(module): 

547 lsst.utils.tests.init() 

548 

549 

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

551 lsst.utils.tests.init() 

552 unittest.main()