Coverage for tests/test_edges.py: 9%

229 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2023-03-08 01:54 -0800

1# 

2# LSST Data Management System 

3# 

4# Copyright 2008-2016 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# 

23import unittest 

24 

25import numpy as np 

26 

27import lsst.utils.tests 

28import lsst.afw.detection as afwDet 

29import lsst.geom as geom 

30import lsst.afw.image as afwImage 

31import lsst.meas.algorithms as measAlg 

32from lsst.log import Log 

33from lsst.meas.deblender.baseline import deblend 

34 

35doPlot = False 

36if doPlot: 36 ↛ 37line 36 didn't jump to line 37, because the condition on line 36 was never true

37 import matplotlib 

38 matplotlib.use('Agg') 

39 import pylab as plt 

40 import os.path 

41 plotpat = os.path.join(os.path.dirname(__file__), 'edge%i.png') 

42 print('Writing plots to', plotpat) 

43else: 

44 print('"doPlot" not set -- not making plots. To enable plots, edit', __file__) 

45 

46# Lower the level to Log.DEBUG to see debug messages 

47Log.getLogger('lsst.meas.deblender.symmetrizeFootprint').setLevel(Log.INFO) 

48Log.getLogger('lsst.meas.deblender.symmetricFootprint').setLevel(Log.INFO) 

49 

50 

51def imExt(img): 

52 bbox = img.getBBox() 

53 return [bbox.getMinX(), bbox.getMaxX(), 

54 bbox.getMinY(), bbox.getMaxY()] 

55 

56 

57def doubleGaussianPsf(W, H, fwhm1, fwhm2, a2): 

58 return measAlg.DoubleGaussianPsf(W, H, fwhm1, fwhm2, a2) 

59 

60 

61def gaussianPsf(W, H, fwhm): 

62 return measAlg.DoubleGaussianPsf(W, H, fwhm) 

63 

64 

65class RampEdgeTestCase(lsst.utils.tests.TestCase): 

66 

67 def test1(self): 

68 """In this test, we create a test image containing two blobs, one 

69 of which is truncated by the edge of the image. 

70 

71 We run the detection code to get realistic peaks and 

72 footprints. 

73 

74 We then test out the different edge treatments and assert that 

75 they do what they claim. We also make plots, tests/edge*.png 

76 """ 

77 

78 # Create fake image... 

79 H, W = 100, 100 

80 fpbb = geom.Box2I(geom.Point2I(0, 0), 

81 geom.Point2I(W-1, H-1)) 

82 afwimg = afwImage.MaskedImageF(fpbb) 

83 imgbb = afwimg.getBBox() 

84 img = afwimg.getImage().getArray() 

85 

86 var = afwimg.getVariance().getArray() 

87 var[:, :] = 1. 

88 

89 blob_fwhm = 15. 

90 blob_psf = doubleGaussianPsf(201, 201, blob_fwhm, 3.*blob_fwhm, 0.03) 

91 fakepsf_fwhm = 5. 

92 S = int(np.ceil(fakepsf_fwhm * 2.)) * 2 + 1 

93 print('S', S) 

94 fakepsf = gaussianPsf(S, S, fakepsf_fwhm) 

95 

96 # Create and save blob images, and add to image to deblend. 

97 blobimgs = [] 

98 XY = [(50., 50.), (90., 50.)] 

99 flux = 1e6 

100 for x, y in XY: 

101 bim = blob_psf.computeImage(geom.Point2D(x, y)) 

102 bbb = bim.getBBox() 

103 bbb.clip(imgbb) 

104 

105 bim = bim.Factory(bim, bbb) 

106 bim2 = bim.getArray() 

107 

108 blobimg = np.zeros_like(img) 

109 blobimg[bbb.getMinY():bbb.getMaxY()+1, 

110 bbb.getMinX():bbb.getMaxX()+1] += flux * bim2 

111 blobimgs.append(blobimg) 

112 

113 img[bbb.getMinY():bbb.getMaxY()+1, 

114 bbb.getMinX():bbb.getMaxX()+1] += flux * bim2 

115 

116 # Run the detection code to get a ~ realistic footprint 

117 thresh = afwDet.createThreshold(10., 'value', True) 

118 fpSet = afwDet.FootprintSet(afwimg, thresh, 'DETECTED', 1) 

119 fps = fpSet.getFootprints() 

120 print('found', len(fps), 'footprints') 

121 

122 # set EDGE bit on edge pixels. 

123 margin = 5 

124 lo = imgbb.getMin() 

125 lo.shift(geom.Extent2I(margin, margin)) 

126 hi = imgbb.getMax() 

127 hi.shift(geom.Extent2I(-margin, -margin)) 

128 goodbbox = geom.Box2I(lo, hi) 

129 print('Good bbox for setting EDGE pixels:', goodbbox) 

130 print('image bbox:', imgbb) 

131 edgebit = afwimg.getMask().getPlaneBitMask("EDGE") 

132 print('edgebit:', edgebit) 

133 measAlg.SourceDetectionTask.setEdgeBits(afwimg, goodbbox, edgebit) 

134 

135 if False: 

136 plt.clf() 

137 plt.imshow(afwimg.getMask().getArray(), 

138 interpolation='nearest', origin='lower') 

139 plt.colorbar() 

140 plt.title('Mask') 

141 plt.savefig('mask.png') 

142 

143 M = afwimg.getMask().getArray() 

144 for bit in range(32): 

145 mbit = (1 << bit) 

146 if not np.any(M & mbit): 

147 continue 

148 plt.clf() 

149 plt.imshow(M & mbit, 

150 interpolation='nearest', origin='lower') 

151 plt.colorbar() 

152 plt.title('Mask bit %i (0x%x)' % (bit, mbit)) 

153 plt.savefig('mask-%02i.png' % bit) 

154 

155 for fp in fps: 

156 print('peaks:', len(fp.getPeaks())) 

157 for pk in fp.getPeaks(): 

158 print(' ', pk.getIx(), pk.getIy()) 

159 assert(len(fps) == 1) 

160 fp = fps[0] 

161 assert(len(fp.getPeaks()) == 2) 

162 

163 ima = dict(interpolation='nearest', origin='lower', # cmap='gray', 

164 cmap='jet', 

165 vmin=0, vmax=400) 

166 

167 for j, (tt, kwa) in enumerate([ 

168 ('No edge treatment', dict()), 

169 ('Ramp by PSF', dict(rampFluxAtEdge=True)), 

170 ('No clip at edge', dict(patchEdges=True)), 

171 ]): 

172 # print 'Deblending...' 

173 # Change verbose to False to quiet down the meas_deblender.baseline logger 

174 deb = deblend(fp, afwimg, fakepsf, fakepsf_fwhm, verbose=True, 

175 **kwa) 

176 # print 'Result:', deb 

177 # print len(deb.peaks), 'deblended peaks' 

178 

179 parent_img = afwImage.ImageF(fpbb) 

180 fp.spans.copyImage(afwimg.getImage(), parent_img) 

181 

182 X = [x for x, y in XY] 

183 Y = [y for x, y in XY] 

184 PX = [pk.getIx() for pk in fp.getPeaks()] 

185 PY = [pk.getIy() for pk in fp.getPeaks()] 

186 

187 # Grab 1-d slices to make assertion about. 

188 symms = [] 

189 monos = [] 

190 symm1ds = [] 

191 mono1ds = [] 

192 yslice = H//2 

193 parent1d = img[yslice, :] 

194 for i, dpk in enumerate(deb.deblendedParents[0].peaks): 

195 symm = dpk.origTemplate 

196 symms.append(symm) 

197 

198 bbox = symm.getBBox() 

199 x0, y0 = bbox.getMinX(), bbox.getMinY() 

200 im = symm.getArray() 

201 h, w = im.shape 

202 oned = np.zeros(W) 

203 oned[x0: x0+w] = im[yslice-y0, :] 

204 symm1ds.append(oned) 

205 

206 mono = afwImage.ImageF(fpbb) 

207 dpk.templateFootprint.spans.copyImage(dpk.templateImage, mono) 

208 monos.append(mono) 

209 

210 im = mono.getArray() 

211 bbox = mono.getBBox() 

212 x0, y0 = bbox.getMinX(), bbox.getMinY() 

213 h, w = im.shape 

214 oned = np.zeros(W) 

215 oned[x0: x0+w] = im[yslice-y0, :] 

216 mono1ds.append(oned) 

217 

218 for i, (symm, mono) in enumerate(zip(symm1ds, mono1ds)): 

219 # for the first two cases, the basic symmetric 

220 # template for the second source drops to zero at < 

221 # ~75 where the symmetric part is outside the 

222 # footprint. 

223 if i == 1 and j in [0, 1]: 

224 self.assertFloatsEqual(symm[:74], 0.0) 

225 if i == 1 and j == 2: 

226 # For the third case, the 'symm' template gets 

227 # "patched" with the parent's value 

228 self.assertFloatsEqual(symm[:74], parent1d[:74]) 

229 

230 if i == 1 and j == 0: 

231 # No edge handling: mono template == 0 

232 self.assertFloatsEqual(mono[:74], 0.0) 

233 if i == 1 and j == 1: 

234 # ramp by psf: zero up to ~65, ramps up 

235 self.assertFloatsEqual(mono[:64], 0.0) 

236 self.assertTrue(np.any(mono[65:74] > 0)) 

237 self.assertTrue(np.all(np.diff(mono)[60:80] >= 0.)) 

238 if i == 1 and j == 2: 

239 # no edge clipping: profile is monotonic and positive. 

240 self.assertTrue(np.all(np.diff(mono)[:85] >= 0.)) 

241 self.assertTrue(np.all(mono[:85] > 0.)) 

242 

243 if not doPlot: 

244 continue 

245 

246 plt.clf() 

247 p1 = plt.plot(parent1d, 'b-', lw=3, alpha=0.5) 

248 for i, (symm, mono) in enumerate(zip(symm1ds, mono1ds)): 

249 p2 = plt.plot(symm, 'r-', lw=2, alpha=0.7) 

250 p3 = plt.plot(mono, 'g-') 

251 plt.legend((p1[0], p2[0], p3[0]), ('Parent', 'Symm template', 'Mono template'), 

252 loc='upper left') 

253 plt.title('1-d slice: %s' % tt) 

254 fn = plotpat % (2*j+0) 

255 plt.savefig(fn) 

256 print('Wrote', fn) 

257 

258 def myimshow(*args, **kwargs): 

259 x0, x1, y0, y1 = imExt(afwimg) 

260 plt.fill([x0, x0, x1, x1, x0], [y0, y1, y1, y0, y0], color=(1, 1, 0.8), 

261 zorder=20) 

262 plt.imshow(*args, zorder=25, **kwargs) 

263 plt.xticks([]) 

264 plt.yticks([]) 

265 plt.axis(imExt(afwimg)) 

266 

267 plt.clf() 

268 

269 pa = dict(color='m', marker='.', linestyle='None', zorder=30) 

270 

271 R, C = 3, 6 

272 plt.subplot(R, C, (2*C) + 1) 

273 myimshow(img, **ima) 

274 ax = plt.axis() 

275 plt.plot(X, Y, **pa) 

276 plt.axis(ax) 

277 plt.title('Image') 

278 

279 plt.subplot(R, C, (2*C) + 2) 

280 myimshow(parent_img.getArray(), **ima) 

281 ax = plt.axis() 

282 plt.plot(PX, PY, **pa) 

283 plt.axis(ax) 

284 plt.title('Footprint') 

285 

286 sumimg = None 

287 for i, dpk in enumerate(deb.deblendedParents[0].peaks): 

288 

289 plt.subplot(R, C, i*C + 1) 

290 myimshow(blobimgs[i], **ima) 

291 ax = plt.axis() 

292 plt.plot(PX[i], PY[i], **pa) 

293 plt.axis(ax) 

294 plt.title('true') 

295 

296 plt.subplot(R, C, i*C + 2) 

297 t = dpk.origTemplate 

298 myimshow(t.getArray(), extent=imExt(t), **ima) 

299 ax = plt.axis() 

300 plt.plot(PX[i], PY[i], **pa) 

301 plt.axis(ax) 

302 plt.title('symm') 

303 

304 # monotonic template 

305 mimg = afwImage.ImageF(fpbb) 

306 afwDet.copyWithinFootprintImage(dpk.templateFootprint, 

307 dpk.templateImage, mimg) 

308 

309 plt.subplot(R, C, i*C + 3) 

310 myimshow(mimg.getArray(), extent=imExt(mimg), **ima) 

311 ax = plt.axis() 

312 plt.plot(PX[i], PY[i], **pa) 

313 plt.axis(ax) 

314 plt.title('monotonic') 

315 

316 plt.subplot(R, C, i*C + 4) 

317 port = dpk.fluxPortion.getImage() 

318 myimshow(port.getArray(), extent=imExt(port), **ima) 

319 plt.title('portion') 

320 ax = plt.axis() 

321 plt.plot(PX[i], PY[i], **pa) 

322 plt.axis(ax) 

323 

324 if dpk.strayFlux is not None: 

325 simg = afwImage.ImageF(fpbb) 

326 dpk.strayFlux.insert(simg) 

327 

328 plt.subplot(R, C, i*C + 5) 

329 myimshow(simg.getArray(), **ima) 

330 plt.title('stray') 

331 ax = plt.axis() 

332 plt.plot(PX, PY, **pa) 

333 plt.axis(ax) 

334 

335 himg2 = afwImage.ImageF(fpbb) 

336 portion = dpk.getFluxPortion() 

337 portion.insert(himg2) 

338 

339 if sumimg is None: 

340 sumimg = himg2.getArray().copy() 

341 else: 

342 sumimg += himg2.getArray() 

343 

344 plt.subplot(R, C, i*C + 6) 

345 myimshow(himg2.getArray(), **ima) 

346 plt.title('portion+stray') 

347 ax = plt.axis() 

348 plt.plot(PX, PY, **pa) 

349 plt.axis(ax) 

350 

351 plt.subplot(R, C, (2*C) + C) 

352 myimshow(sumimg, **ima) 

353 ax = plt.axis() 

354 plt.plot(X, Y, **pa) 

355 plt.axis(ax) 

356 plt.title('Sum of deblends') 

357 

358 plt.suptitle(tt) 

359 fn = plotpat % (2*j + 1) 

360 plt.savefig(fn) 

361 print('Wrote', fn) 

362 

363 

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

365 pass 

366 

367 

368def setup_module(module): 

369 lsst.utils.tests.init() 

370 

371 

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

373 lsst.utils.tests.init() 

374 unittest.main()