Coverage for tests/test_edges.py: 9%
229 statements
« prev ^ index » next coverage.py v7.4.4, created at 2024-03-28 11:05 +0000
« prev ^ index » next coverage.py v7.4.4, created at 2024-03-28 11:05 +0000
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
25import numpy as np
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
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__)
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)
51def imExt(img):
52 bbox = img.getBBox()
53 return [bbox.getMinX(), bbox.getMaxX(),
54 bbox.getMinY(), bbox.getMaxY()]
57def doubleGaussianPsf(W, H, fwhm1, fwhm2, a2):
58 return measAlg.DoubleGaussianPsf(W, H, fwhm1, fwhm2, a2)
61def gaussianPsf(W, H, fwhm):
62 return measAlg.DoubleGaussianPsf(W, H, fwhm)
65class RampEdgeTestCase(lsst.utils.tests.TestCase):
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.
71 We run the detection code to get realistic peaks and
72 footprints.
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 """
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()
86 var = afwimg.getVariance().getArray()
87 var[:, :] = 1.
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)
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)
105 bim = bim.Factory(bim, bbb)
106 bim2 = bim.getArray()
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)
113 img[bbb.getMinY():bbb.getMaxY()+1,
114 bbb.getMinX():bbb.getMaxX()+1] += flux * bim2
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')
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)
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')
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)
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
163 ima = dict(interpolation='nearest', origin='lower', # cmap='gray',
164 cmap='jet',
165 vmin=0, vmax=400)
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'
179 parent_img = afwImage.ImageF(fpbb)
180 fp.spans.copyImage(afwimg.getImage(), parent_img)
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()]
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)
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)
206 mono = afwImage.ImageF(fpbb)
207 dpk.templateFootprint.spans.copyImage(dpk.templateImage, mono)
208 monos.append(mono)
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)
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])
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.))
243 if not doPlot:
244 continue
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)
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))
267 plt.clf()
269 pa = dict(color='m', marker='.', linestyle='None', zorder=30)
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')
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')
286 sumimg = None
287 for i, dpk in enumerate(deb.deblendedParents[0].peaks):
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')
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')
304 # monotonic template
305 mimg = afwImage.ImageF(fpbb)
306 afwDet.copyWithinFootprintImage(dpk.templateFootprint,
307 dpk.templateImage, mimg)
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')
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)
324 if dpk.strayFlux is not None:
325 simg = afwImage.ImageF(fpbb)
326 dpk.strayFlux.insert(simg)
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)
335 himg2 = afwImage.ImageF(fpbb)
336 portion = dpk.getFluxPortion()
337 portion.insert(himg2)
339 if sumimg is None:
340 sumimg = himg2.getArray().copy()
341 else:
342 sumimg += himg2.getArray()
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)
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')
358 plt.suptitle(tt)
359 fn = plotpat % (2*j + 1)
360 plt.savefig(fn)
361 print('Wrote', fn)
364class TestMemory(lsst.utils.tests.MemoryTestCase):
365 pass
368def setup_module(module):
369 lsst.utils.tests.init()
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()