Coverage for tests/test_kernelCandidateAndSolution.py: 13%

331 statements  

« prev     ^ index     » next       coverage.py v7.2.5, created at 2023-05-02 11:28 -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.ImagePsfMatchTask.ConfigClass() 

67 self.config.kernel.name = "DF" 

68 self.subconfig = self.config.kernel.active 

69 

70 self.ps = pexConfig.makePropertySet(self.subconfig) 

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

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

73 self.ps["useRegularization"] = False 

74 

75 if defDataDir: 

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

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

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

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

80 

81 scienceExposure = afwImage.ExposureF(defSciencePath) 

82 templateExposure = afwImage.ExposureF(defTemplatePath) 

83 # set XY0 = 0 

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

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

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

87 warper = afwMath.Warper.fromConfig(self.subconfig.warpingConfig) 

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

89 destBBox=scienceExposure.getBBox()) 

90 

91 # Change xy0 

92 # Nice star at position 276, 717 

93 # And should be at index 40, 40 

94 # No masked pixels in this one 

95 self.x02 = 276 

96 self.y02 = 717 

97 size = 40 

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

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

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

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

102 

103 def addNoise(self, mi): 

104 img = mi.getImage() 

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

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

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

108 afwMath.randomGaussianImage(rdmImage, rdm) 

109 img += rdmImage 

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

111 

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

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

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

115 # 

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

117 # 

118 bgSolution = solution.getBackground() 

119 self.assertAlmostEqual(bgSolution, bg, 4) 

120 

121 # again when kSum = 1.0 this agrees. otherwise 

122 # 

123 # 2.7000000605594079 != 2.7000000000000002 within 7 places 

124 # 

125 kSumSolution = solution.getKsum() 

126 self.assertAlmostEqual(kSumSolution, kSum, 5) 

127 

128 kImage = solution.makeKernelImage() 

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

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

131 

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

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

134 else: 

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

136 

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

138 def testConstructor(self): 

139 # Original and uninitialized 

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

141 self.templateExposure2.getMaskedImage(), 

142 self.scienceImage2.getMaskedImage(), 

143 self.ps) 

144 

145 # Kernel not initialized 

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

147 

148 # But this should be set on construction 

149 try: 

150 kc.getCandidateRating() 

151 except Exception as e: 

152 print(e) 

153 self.fail() 

154 

155 # And these should be filled 

156 try: 

157 kc.getTemplateMaskedImage() 

158 kc.getScienceMaskedImage() 

159 except Exception as e: 

160 print(e) 

161 self.fail() 

162 

163 # And of the right type 

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

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

166 

167 # None of these should work 

168 for kType in (ipDiffim.KernelCandidateF.ORIG, 

169 ipDiffim.KernelCandidateF.PCA, 

170 ipDiffim.KernelCandidateF.RECENT): 

171 for kMethod in (kc.getKernelSolution, 

172 kc.getKernel, 

173 kc.getBackground, 

174 kc.getKsum, 

175 kc.getKernelImage, 

176 kc.getDifferenceImage): 

177 try: 

178 kMethod(kType) 

179 except Exception: 

180 pass 

181 else: 

182 self.fail() 

183 try: 

184 kc.getImage() 

185 except Exception: 

186 pass 

187 else: 

188 self.fail() 

189 

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

191 def testSourceStats(self): 

192 source = self.ss.addNew() 

193 source.setId(1) 

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

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

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

197 

198 kc = ipDiffim.KernelCandidateF(source, 

199 self.templateExposure2.getMaskedImage(), 

200 self.scienceImage2.getMaskedImage(), 

201 self.ps) 

202 kList = ipDiffim.makeKernelBasisList(self.subconfig) 

203 

204 kc.build(kList) 

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

206 

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

208 def testSourceConstructor(self): 

209 source = self.ss.addNew() 

210 source.setId(1) 

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

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

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

214 

215 kc = ipDiffim.KernelCandidateF(source, 

216 self.templateExposure2.getMaskedImage(), 

217 self.scienceImage2.getMaskedImage(), 

218 self.ps) 

219 

220 # Kernel not initialized 

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

222 

223 # Check that the source is set 

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

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

226 

227 # But this should be set on construction 

228 try: 

229 kc.getCandidateRating() 

230 except Exception as e: 

231 print(e) 

232 self.fail() 

233 

234 # And these should be filled 

235 try: 

236 kc.getTemplateMaskedImage() 

237 kc.getScienceMaskedImage() 

238 except Exception as e: 

239 print(e) 

240 self.fail() 

241 

242 # And of the right type 

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

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

245 

246 # None of these should work 

247 for kType in (ipDiffim.KernelCandidateF.ORIG, 

248 ipDiffim.KernelCandidateF.PCA, 

249 ipDiffim.KernelCandidateF.RECENT): 

250 for kMethod in (kc.getKernelSolution, 

251 kc.getKernel, 

252 kc.getBackground, 

253 kc.getKsum, 

254 kc.getKernelImage, 

255 kc.getDifferenceImage): 

256 try: 

257 kMethod(kType) 

258 except Exception: 

259 pass 

260 else: 

261 self.fail() 

262 try: 

263 kc.getImage() 

264 except Exception: 

265 pass 

266 else: 

267 self.fail() 

268 

269 kList = ipDiffim.makeKernelBasisList(self.subconfig) 

270 

271 kc.build(kList) 

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

273 

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

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

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

277 sIm *= scaling 

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

279 self.templateExposure2.getMaskedImage(), 

280 sIm, 

281 self.ps) 

282 

283 kList = ipDiffim.makeKernelBasisList(self.subconfig) 

284 kc.build(kList) 

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

286 kSum=scaling) 

287 

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

289 sIm += bg 

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

291 self.templateExposure2.getMaskedImage(), 

292 sIm, 

293 self.ps) 

294 

295 kList = ipDiffim.makeKernelBasisList(self.subconfig) 

296 kc.build(kList) 

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

298 bg=bg) 

299 

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

301 def testDeltaFunction(self): 

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

303 # No regularization 

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

305 self.templateExposure2.getMaskedImage(), 

306 self.templateExposure2.getMaskedImage(), 

307 self.ps) 

308 

309 kList = ipDiffim.makeKernelBasisList(self.subconfig) 

310 

311 kc.build(kList) 

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

313 

314 # These should work 

315 for kType in (ipDiffim.KernelCandidateF.ORIG, 

316 ipDiffim.KernelCandidateF.RECENT): 

317 for kMethod in (kc.getKernelSolution, 

318 kc.getKernel, 

319 kc.getBackground, 

320 kc.getKsum, 

321 kc.getKernelImage, 

322 kc.getDifferenceImage): 

323 try: 

324 kMethod(kType) 

325 except Exception as e: 

326 print(kMethod, e) 

327 self.fail() 

328 else: 

329 pass 

330 try: 

331 kc.getImage() 

332 except Exception as e: 

333 print(kMethod, e) 

334 self.fail() 

335 else: 

336 pass 

337 

338 # None of these should work 

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

340 for kMethod in (kc.getKernelSolution, 

341 kc.getKernel, 

342 kc.getBackground, 

343 kc.getKsum, 

344 kc.getKernelImage, 

345 kc.getImage, 

346 kc.getDifferenceImage): 

347 try: 

348 kMethod(kType) 

349 except Exception: 

350 pass 

351 else: 

352 print(kMethod) 

353 self.fail() 

354 

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

356 

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

358 def testGaussianWithNoise(self): 

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

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

361 

362 gsize = self.ps["kernelSize"] 

363 gaussFunction = afwMath.GaussianFunction2D(2, 3) 

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

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

366 kSumIn = gaussKernel.computeImage(kImageIn, False) 

367 

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

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

370 

371 convolutionControl = afwMath.ConvolutionControl() 

372 convolutionControl.setDoNormalize(False) 

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

374 

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

376 

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

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

379 

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

381 kList = ipDiffim.makeKernelBasisList(self.subconfig) 

382 kc.build(kList) 

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

384 kImageOut = kc.getImage() 

385 

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

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

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

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

390 

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

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

393 

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

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

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

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

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

399 # 0.99941584433815966 != 1.0 within 3 places 

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

401 1.0, 2) 

402 

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

404 self.addNoise(smi2) 

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

406 kList = ipDiffim.makeKernelBasisList(self.subconfig) 

407 kc.build(kList) 

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

409 kImageOut = kc.getImage() 

410 

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

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

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

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

415 

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

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

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

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

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

421 

422 def testGaussian(self, imsize=50): 

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

424 # recover using delta-function basis 

425 

426 gsize = self.ps["kernelSize"] 

427 tsize = imsize + gsize 

428 

429 gaussFunction = afwMath.GaussianFunction2D(2, 3) 

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

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

432 gaussKernel.computeImage(kImageIn, False) 

433 

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

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

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

437 cpix = tsize // 2 

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

439 

440 # science image 

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

442 convolutionControl = afwMath.ConvolutionControl() 

443 convolutionControl.setDoNormalize(False) 

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

445 

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

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

448 

449 # grab only the non-masked subregion 

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

451 

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

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

454 

455 # make sure its a valid subregion! 

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

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

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

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

460 

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

462 kList = ipDiffim.makeKernelBasisList(self.subconfig) 

463 kc.build(kList) 

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

465 kImageOut = kc.getImage() 

466 

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

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

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

470 

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

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

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

474 1.0, 5) 

475 

476 def testZeroVariance(self, imsize=50): 

477 gsize = self.ps["kernelSize"] 

478 tsize = imsize + gsize 

479 

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

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

482 cpix = tsize // 2 

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

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

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

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

487 

488 kList = ipDiffim.makeKernelBasisList(self.subconfig) 

489 self.ps["constantVarianceWeighting"] = False 

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

491 try: 

492 kc.build(kList) 

493 except Exception: 

494 pass 

495 else: 

496 self.fail() 

497 

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

499 def testConstantWeighting(self): 

500 self.ps["fitForBackground"] = False 

501 self.testGaussian() 

502 self.testGaussianWithNoise() 

503 

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

505 def testNoBackgroundFit(self): 

506 self.ps["constantVarianceWeighting"] = True 

507 self.testGaussian() 

508 

509 def testInsert(self): 

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

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

512 kc.setStatus(afwMath.SpatialCellCandidate.GOOD) 

513 

514 sizeCellX = self.ps["sizeCellX"] 

515 sizeCellY = self.ps["sizeCellY"] 

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

517 sizeCellX, sizeCellY) 

518 kernelCellSet.insertCandidate(kc) 

519 nSeen = 0 

520 for cell in kernelCellSet.getCellList(): 

521 for cand in cell.begin(True): 

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

523 nSeen += 1 

524 self.assertEqual(nSeen, 1) 

525 

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

527 def testDisp(self): 

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

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

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

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

532 

533 def tearDown(self): 

534 del self.ps 

535 del self.table 

536 del self.ss 

537 if defDataDir: 

538 del self.scienceImage2 

539 del self.templateExposure2 

540 

541##### 

542 

543 

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

545 pass 

546 

547 

548def setup_module(module): 

549 lsst.utils.tests.init() 

550 

551 

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

553 lsst.utils.tests.init() 

554 unittest.main()