Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

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 

22"""Support utilities for Measuring sources""" 

23 

24import math 

25import numpy 

26 

27import lsst.log 

28import lsst.pex.exceptions as pexExcept 

29import lsst.daf.base as dafBase 

30import lsst.geom 

31import lsst.afw.geom as afwGeom 

32import lsst.afw.detection as afwDet 

33import lsst.afw.image as afwImage 

34import lsst.afw.math as afwMath 

35import lsst.afw.table as afwTable 

36import lsst.afw.display as afwDisplay 

37import lsst.afw.display.utils as displayUtils 

38import lsst.meas.base as measBase 

39from . import subtractPsf, fitKernelParamsToImage 

40 

41keptPlots = False # Have we arranged to keep spatial plots open? 

42 

43afwDisplay.setDefaultMaskTransparency(75) 

44 

45 

46def splitId(oid, asDict=True): 

47 

48 objId = int((oid & 0xffff) - 1) # Should be the value set by apps code 

49 

50 if asDict: 

51 return dict(objId=objId) 

52 else: 

53 return [objId] 

54 

55 

56def showSourceSet(sSet, xy0=(0, 0), display=None, ctype=afwDisplay.GREEN, symb="+", size=2): 

57 """Draw the (XAstrom, YAstrom) positions of a set of Sources. Image has the given XY0""" 

58 

59 if not display: 

60 display = afwDisplay.Display() 

61 with display.Buffering(): 

62 for s in sSet: 

63 xc, yc = s.getXAstrom() - xy0[0], s.getYAstrom() - xy0[1] 

64 

65 if symb == "id": 

66 display.dot(str(splitId(s.getId(), True)["objId"]), xc, yc, ctype=ctype, size=size) 

67 else: 

68 display.dot(symb, xc, yc, ctype=ctype, size=size) 

69 

70# 

71# PSF display utilities 

72# 

73 

74 

75def showPsfSpatialCells(exposure, psfCellSet, nMaxPerCell=-1, showChi2=False, showMoments=False, 

76 symb=None, ctype=None, ctypeUnused=None, ctypeBad=None, size=2, display=None): 

77 """Show the SpatialCells. 

78 

79 If symb is something that afwDisplay.Display.dot() understands (e.g. "o"), 

80 the top nMaxPerCell candidates will be indicated with that symbol, using 

81 ctype and size. 

82 """ 

83 

84 if not display: 

85 display = afwDisplay.Display() 

86 with display.Buffering(): 

87 origin = [-exposure.getMaskedImage().getX0(), -exposure.getMaskedImage().getY0()] 

88 for cell in psfCellSet.getCellList(): 

89 displayUtils.drawBBox(cell.getBBox(), origin=origin, display=display) 

90 

91 if nMaxPerCell < 0: 

92 nMaxPerCell = 0 

93 

94 i = 0 

95 goodies = ctypeBad is None 

96 for cand in cell.begin(goodies): 

97 if nMaxPerCell > 0: 

98 i += 1 

99 

100 xc, yc = cand.getXCenter() + origin[0], cand.getYCenter() + origin[1] 

101 

102 if i > nMaxPerCell: 

103 if not ctypeUnused: 

104 continue 

105 

106 color = ctypeBad if cand.isBad() else ctype 

107 

108 if symb: 

109 if i > nMaxPerCell: 

110 ct = ctypeUnused 

111 else: 

112 ct = ctype 

113 

114 display.dot(symb, xc, yc, ctype=ct, size=size) 

115 

116 source = cand.getSource() 

117 

118 if showChi2: 

119 rchi2 = cand.getChi2() 

120 if rchi2 > 1e100: 

121 rchi2 = numpy.nan 

122 display.dot("%d %.1f" % (splitId(source.getId(), True)["objId"], rchi2), 

123 xc - size, yc - size - 4, ctype=color, size=2) 

124 

125 if showMoments: 

126 display.dot("%.2f %.2f %.2f" % (source.getIxx(), source.getIxy(), source.getIyy()), 

127 xc-size, yc + size + 4, ctype=color, size=size) 

128 return display 

129 

130 

131def showPsfCandidates(exposure, psfCellSet, psf=None, display=None, normalize=True, showBadCandidates=True, 

132 fitBasisComponents=False, variance=None, chi=None): 

133 """Display the PSF candidates. 

134 

135 If psf is provided include PSF model and residuals; if normalize is true normalize the PSFs 

136 (and residuals) 

137 

138 If chi is True, generate a plot of residuals/sqrt(variance), i.e. chi 

139 

140 If fitBasisComponents is true, also find the best linear combination of the PSF's components 

141 (if they exist) 

142 """ 

143 if not display: 

144 display = afwDisplay.Display() 

145 

146 if chi is None: 

147 if variance is not None: # old name for chi 

148 chi = variance 

149 # 

150 # Show us the ccandidates 

151 # 

152 mos = displayUtils.Mosaic() 

153 # 

154 candidateCenters = [] 

155 candidateCentersBad = [] 

156 candidateIndex = 0 

157 

158 for cell in psfCellSet.getCellList(): 

159 for cand in cell.begin(False): # include bad candidates 

160 rchi2 = cand.getChi2() 

161 if rchi2 > 1e100: 

162 rchi2 = numpy.nan 

163 

164 if not showBadCandidates and cand.isBad(): 

165 continue 

166 

167 if psf: 

168 im_resid = displayUtils.Mosaic(gutter=0, background=-5, mode="x") 

169 

170 try: 

171 im = cand.getMaskedImage() # copy of this object's image 

172 xc, yc = cand.getXCenter(), cand.getYCenter() 

173 

174 margin = 0 if True else 5 

175 w, h = im.getDimensions() 

176 bbox = lsst.geom.BoxI(lsst.geom.PointI(margin, margin), im.getDimensions()) 

177 

178 if margin > 0: 

179 bim = im.Factory(w + 2*margin, h + 2*margin) 

180 

181 stdev = numpy.sqrt(afwMath.makeStatistics(im.getVariance(), afwMath.MEAN).getValue()) 

182 afwMath.randomGaussianImage(bim.getImage(), afwMath.Random()) 

183 bim.getVariance().set(stdev**2) 

184 

185 bim.assign(im, bbox) 

186 im = bim 

187 xc += margin 

188 yc += margin 

189 

190 im = im.Factory(im, True) 

191 im.setXY0(cand.getMaskedImage().getXY0()) 

192 except Exception: 

193 continue 

194 

195 if not variance: 

196 im_resid.append(im.Factory(im, True)) 

197 

198 if True: # tweak up centroids 

199 mi = im 

200 psfIm = mi.getImage() 

201 config = measBase.SingleFrameMeasurementTask.ConfigClass() 

202 config.slots.centroid = "base_SdssCentroid" 

203 

204 schema = afwTable.SourceTable.makeMinimalSchema() 

205 measureSources = measBase.SingleFrameMeasurementTask(schema, config=config) 

206 catalog = afwTable.SourceCatalog(schema) 

207 

208 extra = 10 # enough margin to run the sdss centroider 

209 miBig = mi.Factory(im.getWidth() + 2*extra, im.getHeight() + 2*extra) 

210 miBig[extra:-extra, extra:-extra, afwImage.LOCAL] = mi 

211 miBig.setXY0(mi.getX0() - extra, mi.getY0() - extra) 

212 mi = miBig 

213 del miBig 

214 

215 exp = afwImage.makeExposure(mi) 

216 exp.setPsf(psf) 

217 

218 footprintSet = afwDet.FootprintSet(mi, 

219 afwDet.Threshold(0.5*numpy.max(psfIm.getArray())), 

220 "DETECTED") 

221 footprintSet.makeSources(catalog) 

222 

223 if len(catalog) == 0: 

224 raise RuntimeError("Failed to detect any objects") 

225 

226 measureSources.run(catalog, exp) 

227 if len(catalog) == 1: 

228 source = catalog[0] 

229 else: # more than one source; find the once closest to (xc, yc) 

230 dmin = None # an invalid value to catch logic errors 

231 for i, s in enumerate(catalog): 

232 d = numpy.hypot(xc - s.getX(), yc - s.getY()) 

233 if i == 0 or d < dmin: 

234 source, dmin = s, d 

235 xc, yc = source.getCentroid() 

236 

237 # residuals using spatial model 

238 try: 

239 subtractPsf(psf, im, xc, yc) 

240 except Exception: 

241 continue 

242 

243 resid = im 

244 if variance: 

245 resid = resid.getImage() 

246 var = im.getVariance() 

247 var = var.Factory(var, True) 

248 numpy.sqrt(var.getArray(), var.getArray()) # inplace sqrt 

249 resid /= var 

250 

251 im_resid.append(resid) 

252 

253 # Fit the PSF components directly to the data (i.e. ignoring the spatial model) 

254 if fitBasisComponents: 

255 im = cand.getMaskedImage() 

256 

257 im = im.Factory(im, True) 

258 im.setXY0(cand.getMaskedImage().getXY0()) 

259 

260 try: 

261 noSpatialKernel = psf.getKernel() 

262 except Exception: 

263 noSpatialKernel = None 

264 

265 if noSpatialKernel: 

266 candCenter = lsst.geom.PointD(cand.getXCenter(), cand.getYCenter()) 

267 fit = fitKernelParamsToImage(noSpatialKernel, im, candCenter) 

268 params = fit[0] 

269 kernels = afwMath.KernelList(fit[1]) 

270 outputKernel = afwMath.LinearCombinationKernel(kernels, params) 

271 

272 outImage = afwImage.ImageD(outputKernel.getDimensions()) 

273 outputKernel.computeImage(outImage, False) 

274 

275 im -= outImage.convertF() 

276 resid = im 

277 

278 if margin > 0: 

279 bim = im.Factory(w + 2*margin, h + 2*margin) 

280 afwMath.randomGaussianImage(bim.getImage(), afwMath.Random()) 

281 bim *= stdev 

282 

283 bim.assign(resid, bbox) 

284 resid = bim 

285 

286 if variance: 

287 resid = resid.getImage() 

288 resid /= var 

289 

290 im_resid.append(resid) 

291 

292 im = im_resid.makeMosaic() 

293 else: 

294 im = cand.getMaskedImage() 

295 

296 if normalize: 

297 im /= afwMath.makeStatistics(im, afwMath.MAX).getValue() 

298 

299 objId = splitId(cand.getSource().getId(), True)["objId"] 

300 if psf: 

301 lab = "%d chi^2 %.1f" % (objId, rchi2) 

302 ctype = afwDisplay.RED if cand.isBad() else afwDisplay.GREEN 

303 else: 

304 lab = "%d flux %8.3g" % (objId, cand.getSource().getPsfInstFlux()) 

305 ctype = afwDisplay.GREEN 

306 

307 mos.append(im, lab, ctype) 

308 

309 if False and numpy.isnan(rchi2): 

310 display.mtv(cand.getMaskedImage().getImage(), title="showPsfCandidates: candidate") 

311 print("amp", cand.getAmplitude()) 

312 

313 im = cand.getMaskedImage() 

314 center = (candidateIndex, xc - im.getX0(), yc - im.getY0()) 

315 candidateIndex += 1 

316 if cand.isBad(): 

317 candidateCentersBad.append(center) 

318 else: 

319 candidateCenters.append(center) 

320 

321 if variance: 

322 title = "chi(Psf fit)" 

323 else: 

324 title = "Stars & residuals" 

325 mosaicImage = mos.makeMosaic(display=display, title=title) 

326 

327 with display.Buffering(): 

328 for centers, color in ((candidateCenters, afwDisplay.GREEN), (candidateCentersBad, afwDisplay.RED)): 

329 for cen in centers: 

330 bbox = mos.getBBox(cen[0]) 

331 display.dot("+", cen[1] + bbox.getMinX(), cen[2] + bbox.getMinY(), ctype=color) 

332 

333 return mosaicImage 

334 

335 

336def makeSubplots(fig, nx=2, ny=2, Nx=1, Ny=1, plottingArea=(0.1, 0.1, 0.85, 0.80), 

337 pxgutter=0.05, pygutter=0.05, xgutter=0.04, ygutter=0.04, 

338 headroom=0.0, panelBorderWeight=0, panelColor='black'): 

339 """Return a generator of a set of subplots, a set of Nx*Ny panels of nx*ny plots. Each panel is fully 

340 filled by row (starting in the bottom left) before the next panel is started. If panelBorderWidth is 

341 greater than zero a border is drawn around each panel, adjusted to enclose the axis labels. 

342 

343 E.g. 

344 subplots = makeSubplots(fig, 2, 2, Nx=1, Ny=1, panelColor='k') 

345 ax = subplots.next(); ax.text(0.3, 0.5, '[0, 0] (0,0)') 

346 ax = subplots.next(); ax.text(0.3, 0.5, '[0, 0] (1,0)') 

347 ax = subplots.next(); ax.text(0.3, 0.5, '[0, 0] (0,1)') 

348 ax = subplots.next(); ax.text(0.3, 0.5, '[0, 0] (1,1)') 

349 fig.show() 

350 

351 Parameters 

352 ---------- 

353 fig : `matplotlib.pyplot.figure` 

354 The matplotlib figure to draw 

355 nx : `int` 

356 The number of plots in each row of each panel 

357 ny : `int` 

358 The number of plots in each column of each panel 

359 Nx : `int` 

360 The number of panels in each row of the figure 

361 Ny : `int` 

362 The number of panels in each column of the figure 

363 plottingArea : `tuple` 

364 (x0, y0, x1, y1) for the part of the figure containing all the panels 

365 pxgutter : `float` 

366 Spacing between columns of panels in units of (x1 - x0) 

367 pygutter : `float` 

368 Spacing between rows of panels in units of (y1 - y0) 

369 xgutter : `float` 

370 Spacing between columns of plots within a panel in units of (x1 - x0) 

371 ygutter : `float` 

372 Spacing between rows of plots within a panel in units of (y1 - y0) 

373 headroom : `float` 

374 Extra spacing above each plot for e.g. a title 

375 panelBorderWeight : `int` 

376 Width of border drawn around panels 

377 panelColor : `str` 

378 Colour of border around panels 

379 """ 

380 

381 log = lsst.log.Log.getLogger("utils.makeSubplots") 

382 try: 

383 import matplotlib.pyplot as plt 

384 except ImportError as e: 

385 log.warning("Unable to import matplotlib: %s", e) 

386 return 

387 

388 # Make show() call canvas.draw() too so that we know how large the axis labels are. Sigh 

389 try: 

390 fig.__show 

391 except AttributeError: 

392 fig.__show = fig.show 

393 

394 def myShow(fig): 

395 fig.__show() 

396 fig.canvas.draw() 

397 

398 import types 

399 fig.show = types.MethodType(myShow, fig) 

400 # 

401 # We can't get the axis sizes until after draw()'s been called, so use a callback Sigh^2 

402 # 

403 axes = {} # all axes in all the panels we're drawing: axes[panel][0] etc. 

404 # 

405 

406 def on_draw(event): 

407 """ 

408 Callback to draw the panel borders when the plots are drawn to the canvas 

409 """ 

410 if panelBorderWeight <= 0: 

411 return False 

412 

413 for p in axes.keys(): 

414 bboxes = [] 

415 for ax in axes[p]: 

416 bboxes.append(ax.bbox.union([label.get_window_extent() for label in 

417 ax.get_xticklabels() + ax.get_yticklabels()])) 

418 

419 ax = axes[p][0] 

420 

421 # this is the bbox that bounds all the bboxes, again in relative 

422 # figure coords 

423 

424 bbox = ax.bbox.union(bboxes) 

425 

426 xy0, xy1 = ax.transData.inverted().transform(bbox) 

427 x0, y0 = xy0 

428 x1, y1 = xy1 

429 w, h = x1 - x0, y1 - y0 

430 # allow a little space around BBox 

431 x0 -= 0.02*w 

432 w += 0.04*w 

433 y0 -= 0.02*h 

434 h += 0.04*h 

435 h += h*headroom 

436 # draw BBox 

437 ax.patches = [] # remove old ones 

438 rec = ax.add_patch(plt.Rectangle((x0, y0), w, h, fill=False, 

439 lw=panelBorderWeight, edgecolor=panelColor)) 

440 rec.set_clip_on(False) 

441 

442 return False 

443 

444 fig.canvas.mpl_connect('draw_event', on_draw) 

445 # 

446 # Choose the plotting areas for each subplot 

447 # 

448 x0, y0 = plottingArea[0:2] 

449 W, H = plottingArea[2:4] 

450 w = (W - (Nx - 1)*pxgutter - (nx*Nx - 1)*xgutter)/float(nx*Nx) 

451 h = (H - (Ny - 1)*pygutter - (ny*Ny - 1)*ygutter)/float(ny*Ny) 

452 # 

453 # OK! Time to create the subplots 

454 # 

455 for panel in range(Nx*Ny): 

456 axes[panel] = [] 

457 px = panel%Nx 

458 py = panel//Nx 

459 for window in range(nx*ny): 

460 x = nx*px + window%nx 

461 y = ny*py + window//nx 

462 ax = fig.add_axes((x0 + xgutter + pxgutter + x*w + (px - 1)*pxgutter + (x - 1)*xgutter, 

463 y0 + ygutter + pygutter + y*h + (py - 1)*pygutter + (y - 1)*ygutter, 

464 w, h), frame_on=True, facecolor='w') 

465 axes[panel].append(ax) 

466 yield ax 

467 

468 

469def plotPsfSpatialModel(exposure, psf, psfCellSet, showBadCandidates=True, numSample=128, 

470 matchKernelAmplitudes=False, keepPlots=True): 

471 """Plot the PSF spatial model.""" 

472 

473 log = lsst.log.Log.getLogger("utils.plotPsfSpatialModel") 

474 try: 

475 import matplotlib.pyplot as plt 

476 import matplotlib as mpl 

477 except ImportError as e: 

478 log.warning("Unable to import matplotlib: %s", e) 

479 return 

480 

481 noSpatialKernel = psf.getKernel() 

482 candPos = list() 

483 candFits = list() 

484 badPos = list() 

485 badFits = list() 

486 candAmps = list() 

487 badAmps = list() 

488 for cell in psfCellSet.getCellList(): 

489 for cand in cell.begin(False): 

490 if not showBadCandidates and cand.isBad(): 

491 continue 

492 candCenter = lsst.geom.PointD(cand.getXCenter(), cand.getYCenter()) 

493 try: 

494 im = cand.getMaskedImage() 

495 except Exception: 

496 continue 

497 

498 fit = fitKernelParamsToImage(noSpatialKernel, im, candCenter) 

499 params = fit[0] 

500 kernels = fit[1] 

501 amp = 0.0 

502 for p, k in zip(params, kernels): 

503 amp += p * k.getSum() 

504 

505 targetFits = badFits if cand.isBad() else candFits 

506 targetPos = badPos if cand.isBad() else candPos 

507 targetAmps = badAmps if cand.isBad() else candAmps 

508 

509 targetFits.append([x / amp for x in params]) 

510 targetPos.append(candCenter) 

511 targetAmps.append(amp) 

512 

513 xGood = numpy.array([pos.getX() for pos in candPos]) - exposure.getX0() 

514 yGood = numpy.array([pos.getY() for pos in candPos]) - exposure.getY0() 

515 zGood = numpy.array(candFits) 

516 

517 xBad = numpy.array([pos.getX() for pos in badPos]) - exposure.getX0() 

518 yBad = numpy.array([pos.getY() for pos in badPos]) - exposure.getY0() 

519 zBad = numpy.array(badFits) 

520 numBad = len(badPos) 

521 

522 xRange = numpy.linspace(0, exposure.getWidth(), num=numSample) 

523 yRange = numpy.linspace(0, exposure.getHeight(), num=numSample) 

524 

525 kernel = psf.getKernel() 

526 nKernelComponents = kernel.getNKernelParameters() 

527 # 

528 # Figure out how many panels we'll need 

529 # 

530 nPanelX = int(math.sqrt(nKernelComponents)) 

531 nPanelY = nKernelComponents//nPanelX 

532 while nPanelY*nPanelX < nKernelComponents: 

533 nPanelX += 1 

534 

535 fig = plt.figure(1) 

536 fig.clf() 

537 try: 

538 fig.canvas._tkcanvas._root().lift() # == Tk's raise, but raise is a python reserved word 

539 except Exception: # protect against API changes 

540 pass 

541 # 

542 # Generator for axes arranged in panels 

543 # 

544 mpl.rcParams["figure.titlesize"] = "x-small" 

545 subplots = makeSubplots(fig, 2, 2, Nx=nPanelX, Ny=nPanelY, xgutter=0.06, ygutter=0.06, pygutter=0.04) 

546 

547 for k in range(nKernelComponents): 

548 func = kernel.getSpatialFunction(k) 

549 dfGood = zGood[:, k] - numpy.array([func(pos.getX(), pos.getY()) for pos in candPos]) 

550 yMin = dfGood.min() 

551 yMax = dfGood.max() 

552 if numBad > 0: 

553 dfBad = zBad[:, k] - numpy.array([func(pos.getX(), pos.getY()) for pos in badPos]) 

554 yMin = min([yMin, dfBad.min()]) 

555 yMax = max([yMax, dfBad.max()]) 

556 yMin -= 0.05 * (yMax - yMin) 

557 yMax += 0.05 * (yMax - yMin) 

558 

559 yMin = -0.01 

560 yMax = 0.01 

561 

562 fRange = numpy.ndarray((len(xRange), len(yRange))) 

563 for j, yVal in enumerate(yRange): 

564 for i, xVal in enumerate(xRange): 

565 fRange[j][i] = func(xVal, yVal) 

566 

567 ax = next(subplots) 

568 

569 ax.set_autoscale_on(False) 

570 ax.set_xbound(lower=0, upper=exposure.getHeight()) 

571 ax.set_ybound(lower=yMin, upper=yMax) 

572 ax.plot(yGood, dfGood, 'b+') 

573 if numBad > 0: 

574 ax.plot(yBad, dfBad, 'r+') 

575 ax.axhline(0.0) 

576 ax.set_title('Residuals(y)') 

577 

578 ax = next(subplots) 

579 

580 if matchKernelAmplitudes and k == 0: 

581 vmin = 0.0 

582 vmax = 1.1 

583 else: 

584 vmin = fRange.min() 

585 vmax = fRange.max() 

586 

587 norm = mpl.colors.Normalize(vmin=vmin, vmax=vmax) 

588 im = ax.imshow(fRange, aspect='auto', origin="lower", norm=norm, 

589 extent=[0, exposure.getWidth()-1, 0, exposure.getHeight()-1]) 

590 ax.set_title('Spatial poly') 

591 plt.colorbar(im, orientation='horizontal', ticks=[vmin, vmax]) 

592 

593 ax = next(subplots) 

594 ax.set_autoscale_on(False) 

595 ax.set_xbound(lower=0, upper=exposure.getWidth()) 

596 ax.set_ybound(lower=yMin, upper=yMax) 

597 ax.plot(xGood, dfGood, 'b+') 

598 if numBad > 0: 

599 ax.plot(xBad, dfBad, 'r+') 

600 ax.axhline(0.0) 

601 ax.set_title('K%d Residuals(x)' % k) 

602 

603 ax = next(subplots) 

604 

605 photoCalib = exposure.getPhotoCalib() 

606 # If there is no calibration factor, use 1.0. 

607 if photoCalib.getCalibrationMean() <= 0: 

608 photoCalib = afwImage.PhotoCalib(1.0) 

609 

610 ampMag = [photoCalib.instFluxToMagnitude(candAmp) for candAmp in candAmps] 

611 ax.plot(ampMag, zGood[:, k], 'b+') 

612 if numBad > 0: 

613 badAmpMag = [photoCalib.instFluxToMagnitude(badAmp) for badAmp in badAmps] 

614 ax.plot(badAmpMag, zBad[:, k], 'r+') 

615 

616 ax.set_title('Flux variation') 

617 

618 fig.show() 

619 

620 global keptPlots 

621 if keepPlots and not keptPlots: 

622 # Keep plots open when done 

623 def show(): 

624 print("%s: Please close plots when done." % __name__) 

625 try: 

626 plt.show() 

627 except Exception: 

628 pass 

629 print("Plots closed, exiting...") 

630 import atexit 

631 atexit.register(show) 

632 keptPlots = True 

633 

634 

635def showPsf(psf, eigenValues=None, XY=None, normalize=True, display=None): 

636 """Display a PSF's eigen images 

637 

638 If normalize is True, set the largest absolute value of each eigenimage to 1.0 (n.b. sum == 0.0 for i > 0) 

639 """ 

640 

641 if eigenValues: 

642 coeffs = eigenValues 

643 elif XY is not None: 

644 coeffs = psf.getLocalKernel(lsst.geom.PointD(XY[0], XY[1])).getKernelParameters() 

645 else: 

646 coeffs = None 

647 

648 mos = displayUtils.Mosaic(gutter=2, background=-0.1) 

649 for i, k in enumerate(psf.getKernel().getKernelList()): 

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

651 k.computeImage(im, False) 

652 if normalize: 

653 im /= numpy.max(numpy.abs(im.getArray())) 

654 

655 if coeffs: 

656 mos.append(im, "%g" % (coeffs[i]/coeffs[0])) 

657 else: 

658 mos.append(im) 

659 

660 if not display: 

661 display = afwDisplay.Display() 

662 mos.makeMosaic(display=display, title="Kernel Basis Functions") 

663 

664 return mos 

665 

666 

667def showPsfMosaic(exposure, psf=None, nx=7, ny=None, showCenter=True, showEllipticity=False, 

668 showFwhm=False, stampSize=0, display=None, title=None): 

669 """Show a mosaic of Psf images. exposure may be an Exposure (optionally with PSF), 

670 or a tuple (width, height) 

671 

672 If stampSize is > 0, the psf images will be trimmed to stampSize*stampSize 

673 """ 

674 

675 scale = 1.0 

676 if showFwhm: 

677 showEllipticity = True 

678 scale = 2*math.log(2) # convert sigma^2 to HWHM^2 for a Gaussian 

679 

680 mos = displayUtils.Mosaic() 

681 

682 try: # maybe it's a real Exposure 

683 width, height = exposure.getWidth(), exposure.getHeight() 

684 x0, y0 = exposure.getXY0() 

685 if not psf: 

686 psf = exposure.getPsf() 

687 except AttributeError: 

688 try: # OK, maybe a list [width, height] 

689 width, height = exposure[0], exposure[1] 

690 x0, y0 = 0, 0 

691 except TypeError: # I guess not 

692 raise RuntimeError("Unable to extract width/height from object of type %s" % type(exposure)) 

693 

694 if not ny: 

695 ny = int(nx*float(height)/width + 0.5) 

696 if not ny: 

697 ny = 1 

698 

699 centroidName = "SdssCentroid" 

700 shapeName = "base_SdssShape" 

701 

702 schema = afwTable.SourceTable.makeMinimalSchema() 

703 schema.getAliasMap().set("slot_Centroid", centroidName) 

704 schema.getAliasMap().set("slot_Centroid_flag", centroidName+"_flag") 

705 

706 control = measBase.SdssCentroidControl() 

707 centroider = measBase.SdssCentroidAlgorithm(control, centroidName, schema) 

708 

709 sdssShape = measBase.SdssShapeControl() 

710 shaper = measBase.SdssShapeAlgorithm(sdssShape, shapeName, schema) 

711 table = afwTable.SourceTable.make(schema) 

712 

713 table.defineCentroid(centroidName) 

714 table.defineShape(shapeName) 

715 

716 bbox = None 

717 if stampSize > 0: 

718 w, h = psf.computeImage(lsst.geom.PointD(0, 0)).getDimensions() 

719 if stampSize <= w and stampSize <= h: 

720 bbox = lsst.geom.BoxI(lsst.geom.PointI((w - stampSize)//2, (h - stampSize)//2), 

721 lsst.geom.ExtentI(stampSize, stampSize)) 

722 

723 centers = [] 

724 shapes = [] 

725 for iy in range(ny): 

726 for ix in range(nx): 

727 x = int(ix*(width-1)/(nx-1)) + x0 

728 y = int(iy*(height-1)/(ny-1)) + y0 

729 

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

731 imPeak = psf.computePeak(lsst.geom.PointD(x, y)) 

732 im /= imPeak 

733 if bbox: 

734 im = im.Factory(im, bbox) 

735 lab = "PSF(%d,%d)" % (x, y) if False else "" 

736 mos.append(im, lab) 

737 

738 exp = afwImage.makeExposure(afwImage.makeMaskedImage(im)) 

739 exp.setPsf(psf) 

740 w, h = im.getWidth(), im.getHeight() 

741 centerX = im.getX0() + w//2 

742 centerY = im.getY0() + h//2 

743 src = table.makeRecord() 

744 spans = afwGeom.SpanSet(exp.getBBox()) 

745 foot = afwDet.Footprint(spans) 

746 foot.addPeak(centerX, centerY, 1) 

747 src.setFootprint(foot) 

748 

749 try: 

750 centroider.measure(src, exp) 

751 centers.append((src.getX() - im.getX0(), src.getY() - im.getY0())) 

752 

753 shaper.measure(src, exp) 

754 shapes.append((src.getIxx(), src.getIxy(), src.getIyy())) 

755 except Exception: 

756 pass 

757 

758 if not display: 

759 display = afwDisplay.Display() 

760 mos.makeMosaic(display=display, title=title if title else "Model Psf", mode=nx) 

761 

762 if centers and display: 

763 with display.Buffering(): 

764 for i, (cen, shape) in enumerate(zip(centers, shapes)): 

765 bbox = mos.getBBox(i) 

766 xc, yc = cen[0] + bbox.getMinX(), cen[1] + bbox.getMinY() 

767 if showCenter: 

768 display.dot("+", xc, yc, ctype=afwDisplay.BLUE) 

769 

770 if showEllipticity: 

771 ixx, ixy, iyy = shape 

772 ixx *= scale 

773 ixy *= scale 

774 iyy *= scale 

775 display.dot("@:%g,%g,%g" % (ixx, ixy, iyy), xc, yc, ctype=afwDisplay.RED) 

776 

777 return mos 

778 

779 

780def showPsfResiduals(exposure, sourceSet, magType="psf", scale=10, display=None): 

781 mimIn = exposure.getMaskedImage() 

782 mimIn = mimIn.Factory(mimIn, True) # make a copy to subtract from 

783 

784 psf = exposure.getPsf() 

785 psfWidth, psfHeight = psf.getLocalKernel().getDimensions() 

786 # 

787 # Make the image that we'll paste our residuals into. N.b. they can overlap the edges 

788 # 

789 w, h = int(mimIn.getWidth()/scale), int(mimIn.getHeight()/scale) 

790 

791 im = mimIn.Factory(w + psfWidth, h + psfHeight) 

792 

793 cenPos = [] 

794 for s in sourceSet: 

795 x, y = s.getX(), s.getY() 

796 

797 sx, sy = int(x/scale + 0.5), int(y/scale + 0.5) 

798 

799 smim = im.Factory(im, lsst.geom.BoxI(lsst.geom.PointI(sx, sy), 

800 lsst.geom.ExtentI(psfWidth, psfHeight))) 

801 sim = smim.getImage() 

802 

803 try: 

804 if magType == "ap": 

805 flux = s.getApInstFlux() 

806 elif magType == "model": 

807 flux = s.getModelInstFlux() 

808 elif magType == "psf": 

809 flux = s.getPsfInstFlux() 

810 else: 

811 raise RuntimeError("Unknown flux type %s" % magType) 

812 

813 subtractPsf(psf, mimIn, x, y, flux) 

814 except Exception as e: 

815 print(e) 

816 

817 try: 

818 expIm = mimIn.getImage().Factory(mimIn.getImage(), 

819 lsst.geom.BoxI(lsst.geom.PointI(int(x) - psfWidth//2, 

820 int(y) - psfHeight//2), 

821 lsst.geom.ExtentI(psfWidth, psfHeight)), 

822 ) 

823 except pexExcept.Exception: 

824 continue 

825 

826 cenPos.append([x - expIm.getX0() + sx, y - expIm.getY0() + sy]) 

827 

828 sim += expIm 

829 

830 if display: 

831 display = afwDisplay.Display() 

832 display.mtv(im, title="showPsfResiduals: image") 

833 with display.Buffering(): 

834 for x, y in cenPos: 

835 display.dot("+", x, y) 

836 

837 return im 

838 

839 

840def saveSpatialCellSet(psfCellSet, fileName="foo.fits", display=None): 

841 """Write the contents of a SpatialCellSet to a many-MEF fits file""" 

842 

843 mode = "w" 

844 for cell in psfCellSet.getCellList(): 

845 for cand in cell.begin(False): # include bad candidates 

846 dx = afwImage.positionToIndex(cand.getXCenter(), True)[1] 

847 dy = afwImage.positionToIndex(cand.getYCenter(), True)[1] 

848 im = afwMath.offsetImage(cand.getMaskedImage(), -dx, -dy, "lanczos5") 

849 

850 md = dafBase.PropertySet() 

851 md.set("CELL", cell.getLabel()) 

852 md.set("ID", cand.getId()) 

853 md.set("XCENTER", cand.getXCenter()) 

854 md.set("YCENTER", cand.getYCenter()) 

855 md.set("BAD", cand.isBad()) 

856 md.set("AMPL", cand.getAmplitude()) 

857 md.set("FLUX", cand.getSource().getPsfInstFlux()) 

858 md.set("CHI2", cand.getSource().getChi2()) 

859 

860 im.writeFits(fileName, md, mode) 

861 mode = "a" 

862 

863 if display: 

864 display.mtv(im, title="saveSpatialCellSet: image")