lsst.meas.algorithms  16.0-17-g6a7bfb3b+9
utils.py
Go to the documentation of this file.
1 #
2 # LSST Data Management System
3 #
4 # Copyright 2008-2017 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 #
23 """Support utilities for Measuring sources"""
24 
25 import math
26 import numpy
27 
28 import lsst.log
29 import lsst.pex.exceptions as pexExcept
30 import lsst.daf.base as dafBase
31 import lsst.geom
32 import lsst.afw.detection as afwDet
33 import lsst.afw.image as afwImage
34 import lsst.afw.math as afwMath
35 import lsst.afw.table as afwTable
36 import lsst.afw.display.ds9 as ds9
37 import lsst.afw.display.utils as displayUtils
38 import lsst.meas.base as measBase
39 from . import subtractPsf, fitKernelParamsToImage
40 from lsst.afw.image.utils import CalibNoThrow
41 
42 keptPlots = False # Have we arranged to keep spatial plots open?
43 
44 
45 def splitId(oid, asDict=True):
46 
47  objId = int((oid & 0xffff) - 1) # Should be the value set by apps code
48 
49  if asDict:
50  return dict(objId=objId)
51  else:
52  return [objId]
53 
54 
55 def showSourceSet(sSet, xy0=(0, 0), frame=0, ctype=ds9.GREEN, symb="+", size=2):
56  """Draw the (XAstrom, YAstrom) positions of a set of Sources. Image has the given XY0"""
57 
58  with ds9.Buffering():
59  for s in sSet:
60  xc, yc = s.getXAstrom() - xy0[0], s.getYAstrom() - xy0[1]
61 
62  if symb == "id":
63  ds9.dot(str(splitId(s.getId(), True)["objId"]), xc, yc, frame=frame, ctype=ctype, size=size)
64  else:
65  ds9.dot(symb, xc, yc, frame=frame, ctype=ctype, size=size)
66 
67 #
68 # PSF display utilities
69 #
70 
71 
72 def showPsfSpatialCells(exposure, psfCellSet, nMaxPerCell=-1, showChi2=False, showMoments=False,
73  symb=None, ctype=None, ctypeUnused=None, ctypeBad=None, size=2, frame=None):
74  """Show the SpatialCells. If symb is something that ds9.dot understands (e.g. "o"), the
75  top nMaxPerCell candidates will be indicated with that symbol, using ctype and size"""
76 
77  with ds9.Buffering():
78  origin = [-exposure.getMaskedImage().getX0(), -exposure.getMaskedImage().getY0()]
79  for cell in psfCellSet.getCellList():
80  displayUtils.drawBBox(cell.getBBox(), origin=origin, frame=frame)
81 
82  if nMaxPerCell < 0:
83  nMaxPerCell = 0
84 
85  i = 0
86  goodies = ctypeBad is None
87  for cand in cell.begin(goodies):
88  if nMaxPerCell > 0:
89  i += 1
90 
91  xc, yc = cand.getXCenter() + origin[0], cand.getYCenter() + origin[1]
92 
93  if i > nMaxPerCell:
94  if not ctypeUnused:
95  continue
96 
97  color = ctypeBad if cand.isBad() else ctype
98 
99  if symb:
100  if i > nMaxPerCell:
101  ct = ctypeUnused
102  else:
103  ct = ctype
104 
105  ds9.dot(symb, xc, yc, frame=frame, ctype=ct, size=size)
106 
107  source = cand.getSource()
108 
109  if showChi2:
110  rchi2 = cand.getChi2()
111  if rchi2 > 1e100:
112  rchi2 = numpy.nan
113  ds9.dot("%d %.1f" % (splitId(source.getId(), True)["objId"], rchi2),
114  xc - size, yc - size - 4, frame=frame, ctype=color, size=2)
115 
116  if showMoments:
117  ds9.dot("%.2f %.2f %.2f" % (source.getIxx(), source.getIxy(), source.getIyy()),
118  xc-size, yc + size + 4, frame=frame, ctype=color, size=size)
119 
120 
121 def showPsfCandidates(exposure, psfCellSet, psf=None, frame=None, normalize=True, showBadCandidates=True,
122  fitBasisComponents=False, variance=None, chi=None):
123  """Display the PSF candidates.
124 
125  If psf is provided include PSF model and residuals; if normalize is true normalize the PSFs
126  (and residuals)
127 
128  If chi is True, generate a plot of residuals/sqrt(variance), i.e. chi
129 
130  If fitBasisComponents is true, also find the best linear combination of the PSF's components
131  (if they exist)
132  """
133  if chi is None:
134  if variance is not None: # old name for chi
135  chi = variance
136  #
137  # Show us the ccandidates
138  #
139  mos = displayUtils.Mosaic()
140  #
141  candidateCenters = []
142  candidateCentersBad = []
143  candidateIndex = 0
144 
145  for cell in psfCellSet.getCellList():
146  for cand in cell.begin(False): # include bad candidates
147  rchi2 = cand.getChi2()
148  if rchi2 > 1e100:
149  rchi2 = numpy.nan
150 
151  if not showBadCandidates and cand.isBad():
152  continue
153 
154  if psf:
155  im_resid = displayUtils.Mosaic(gutter=0, background=-5, mode="x")
156 
157  try:
158  im = cand.getMaskedImage() # copy of this object's image
159  xc, yc = cand.getXCenter(), cand.getYCenter()
160 
161  margin = 0 if True else 5
162  w, h = im.getDimensions()
163  bbox = lsst.geom.BoxI(lsst.geom.PointI(margin, margin), im.getDimensions())
164 
165  if margin > 0:
166  bim = im.Factory(w + 2*margin, h + 2*margin)
167 
168  stdev = numpy.sqrt(afwMath.makeStatistics(im.getVariance(), afwMath.MEAN).getValue())
169  afwMath.randomGaussianImage(bim.getImage(), afwMath.Random())
170  bim.getVariance().set(stdev**2)
171 
172  bim.assign(im, bbox)
173  im = bim
174  xc += margin
175  yc += margin
176 
177  im = im.Factory(im, True)
178  im.setXY0(cand.getMaskedImage().getXY0())
179  except Exception:
180  continue
181 
182  if not variance:
183  im_resid.append(im.Factory(im, True))
184 
185  if True: # tweak up centroids
186  mi = im
187  psfIm = mi.getImage()
188  config = measBase.SingleFrameMeasurementTask.ConfigClass()
189  config.slots.centroid = "base_SdssCentroid"
190 
191  schema = afwTable.SourceTable.makeMinimalSchema()
192  measureSources = measBase.SingleFrameMeasurementTask(schema, config=config)
193  catalog = afwTable.SourceCatalog(schema)
194 
195  extra = 10 # enough margin to run the sdss centroider
196  miBig = mi.Factory(im.getWidth() + 2*extra, im.getHeight() + 2*extra)
197  miBig[extra:-extra, extra:-extra] = mi
198  miBig.setXY0(mi.getX0() - extra, mi.getY0() - extra)
199  mi = miBig
200  del miBig
201 
202  exp = afwImage.makeExposure(mi)
203  exp.setPsf(psf)
204 
205  footprintSet = afwDet.FootprintSet(mi,
206  afwDet.Threshold(0.5*numpy.max(psfIm.getArray())),
207  "DETECTED")
208  footprintSet.makeSources(catalog)
209 
210  if len(catalog) == 0:
211  raise RuntimeError("Failed to detect any objects")
212 
213  measureSources.run(catalog, exp)
214  if len(catalog) == 1:
215  source = catalog[0]
216  else: # more than one source; find the once closest to (xc, yc)
217  dmin = None # an invalid value to catch logic errors
218  for i, s in enumerate(catalog):
219  d = numpy.hypot(xc - s.getX(), yc - s.getY())
220  if i == 0 or d < dmin:
221  source, dmin = s, d
222  xc, yc = source.getCentroid()
223 
224  # residuals using spatial model
225  try:
226  subtractPsf(psf, im, xc, yc)
227  except Exception:
228  continue
229 
230  resid = im
231  if variance:
232  resid = resid.getImage()
233  var = im.getVariance()
234  var = var.Factory(var, True)
235  numpy.sqrt(var.getArray(), var.getArray()) # inplace sqrt
236  resid /= var
237 
238  im_resid.append(resid)
239 
240  # Fit the PSF components directly to the data (i.e. ignoring the spatial model)
241  if fitBasisComponents:
242  im = cand.getMaskedImage()
243 
244  im = im.Factory(im, True)
245  im.setXY0(cand.getMaskedImage().getXY0())
246 
247  try:
248  noSpatialKernel = psf.getKernel()
249  except Exception:
250  noSpatialKernel = None
251 
252  if noSpatialKernel:
253  candCenter = lsst.geom.PointD(cand.getXCenter(), cand.getYCenter())
254  fit = fitKernelParamsToImage(noSpatialKernel, im, candCenter)
255  params = fit[0]
256  kernels = afwMath.KernelList(fit[1])
257  outputKernel = afwMath.LinearCombinationKernel(kernels, params)
258 
259  outImage = afwImage.ImageD(outputKernel.getDimensions())
260  outputKernel.computeImage(outImage, False)
261 
262  im -= outImage.convertF()
263  resid = im
264 
265  if margin > 0:
266  bim = im.Factory(w + 2*margin, h + 2*margin)
267  afwMath.randomGaussianImage(bim.getImage(), afwMath.Random())
268  bim *= stdev
269 
270  bim.assign(resid, bbox)
271  resid = bim
272 
273  if variance:
274  resid = resid.getImage()
275  resid /= var
276 
277  im_resid.append(resid)
278 
279  im = im_resid.makeMosaic()
280  else:
281  im = cand.getMaskedImage()
282 
283  if normalize:
284  im /= afwMath.makeStatistics(im, afwMath.MAX).getValue()
285 
286  objId = splitId(cand.getSource().getId(), True)["objId"]
287  if psf:
288  lab = "%d chi^2 %.1f" % (objId, rchi2)
289  ctype = ds9.RED if cand.isBad() else ds9.GREEN
290  else:
291  lab = "%d flux %8.3g" % (objId, cand.getSource().getPsfInstFlux())
292  ctype = ds9.GREEN
293 
294  mos.append(im, lab, ctype)
295 
296  if False and numpy.isnan(rchi2):
297  ds9.mtv(cand.getMaskedImage().getImage(), title="candidate", frame=1)
298  print("amp", cand.getAmplitude())
299 
300  im = cand.getMaskedImage()
301  center = (candidateIndex, xc - im.getX0(), yc - im.getY0())
302  candidateIndex += 1
303  if cand.isBad():
304  candidateCentersBad.append(center)
305  else:
306  candidateCenters.append(center)
307 
308  if variance:
309  title = "chi(Psf fit)"
310  else:
311  title = "Stars & residuals"
312  mosaicImage = mos.makeMosaic(frame=frame, title=title)
313 
314  with ds9.Buffering():
315  for centers, color in ((candidateCenters, ds9.GREEN), (candidateCentersBad, ds9.RED)):
316  for cen in centers:
317  bbox = mos.getBBox(cen[0])
318  ds9.dot("+", cen[1] + bbox.getMinX(), cen[2] + bbox.getMinY(), frame=frame, ctype=color)
319 
320  return mosaicImage
321 
322 
323 def makeSubplots(fig, nx=2, ny=2, Nx=1, Ny=1, plottingArea=(0.1, 0.1, 0.85, 0.80),
324  pxgutter=0.05, pygutter=0.05, xgutter=0.04, ygutter=0.04,
325  headroom=0.0, panelBorderWeight=0, panelColor='black'):
326  """Return a generator of a set of subplots, a set of Nx*Ny panels of nx*ny plots. Each panel is fully
327  filled by row (starting in the bottom left) before the next panel is started. If panelBorderWidth is
328  greater than zero a border is drawn around each panel, adjusted to enclose the axis labels.
329 
330  E.g.
331  subplots = makeSubplots(fig, 2, 2, Nx=1, Ny=1, panelColor='k')
332  ax = subplots.next(); ax.text(0.3, 0.5, '[0, 0] (0,0)')
333  ax = subplots.next(); ax.text(0.3, 0.5, '[0, 0] (1,0)')
334  ax = subplots.next(); ax.text(0.3, 0.5, '[0, 0] (0,1)')
335  ax = subplots.next(); ax.text(0.3, 0.5, '[0, 0] (1,1)')
336  fig.show()
337 
338  @param fig The matplotlib figure to draw
339  @param nx The number of plots in each row of each panel
340  @param ny The number of plots in each column of each panel
341  @param Nx The number of panels in each row of the figure
342  @param Ny The number of panels in each column of the figure
343  @param plottingArea (x0, y0, x1, y1) for the part of the figure containing all the panels
344  @param pxgutter Spacing between columns of panels in units of (x1 - x0)
345  @param pygutter Spacing between rows of panels in units of (y1 - y0)
346  @param xgutter Spacing between columns of plots within a panel in units of (x1 - x0)
347  @param ygutter Spacing between rows of plots within a panel in units of (y1 - y0)
348  @param headroom Extra spacing above each plot for e.g. a title
349  @param panelBorderWeight Width of border drawn around panels
350  @param panelColor Colour of border around panels
351  """
352 
353  log = lsst.log.Log.getLogger("utils.makeSubplots")
354  try:
355  import matplotlib.pyplot as plt
356  except ImportError as e:
357  log.warn("Unable to import matplotlib: %s", e)
358  return
359 
360  # Make show() call canvas.draw() too so that we know how large the axis labels are. Sigh
361  try:
362  fig.__show
363  except AttributeError:
364  fig.__show = fig.show
365 
366  def myShow(fig):
367  fig.__show()
368  fig.canvas.draw()
369 
370  import types
371  fig.show = types.MethodType(myShow, fig, fig.__class__)
372  #
373  # We can't get the axis sizes until after draw()'s been called, so use a callback Sigh^2
374  #
375  axes = {} # all axes in all the panels we're drawing: axes[panel][0] etc.
376  #
377 
378  def on_draw(event):
379  """
380  Callback to draw the panel borders when the plots are drawn to the canvas
381  """
382  if panelBorderWeight <= 0:
383  return False
384 
385  for p in axes.keys():
386  bboxes = []
387  for ax in axes[p]:
388  bboxes.append(ax.bbox.union([label.get_window_extent() for label in
389  ax.get_xticklabels() + ax.get_yticklabels()]))
390 
391  ax = axes[p][0]
392 
393  # this is the bbox that bounds all the bboxes, again in relative
394  # figure coords
395 
396  bbox = ax.bbox.union(bboxes)
397 
398  xy0, xy1 = ax.transData.inverted().transform(bbox)
399  x0, y0 = xy0
400  x1, y1 = xy1
401  w, h = x1 - x0, y1 - y0
402  # allow a little space around BBox
403  x0 -= 0.02*w
404  w += 0.04*w
405  y0 -= 0.02*h
406  h += 0.04*h
407  h += h*headroom
408  # draw BBox
409  ax.patches = [] # remove old ones
410  rec = ax.add_patch(plt.Rectangle((x0, y0), w, h, fill=False,
411  lw=panelBorderWeight, edgecolor=panelColor))
412  rec.set_clip_on(False)
413 
414  return False
415 
416  fig.canvas.mpl_connect('draw_event', on_draw)
417  #
418  # Choose the plotting areas for each subplot
419  #
420  x0, y0 = plottingArea[0:2]
421  W, H = plottingArea[2:4]
422  w = (W - (Nx - 1)*pxgutter - (nx*Nx - 1)*xgutter)/float(nx*Nx)
423  h = (H - (Ny - 1)*pygutter - (ny*Ny - 1)*ygutter)/float(ny*Ny)
424  #
425  # OK! Time to create the subplots
426  #
427  for panel in range(Nx*Ny):
428  axes[panel] = []
429  px = panel%Nx
430  py = panel//Nx
431  for window in range(nx*ny):
432  x = nx*px + window%nx
433  y = ny*py + window//nx
434  ax = fig.add_axes((x0 + xgutter + pxgutter + x*w + (px - 1)*pxgutter + (x - 1)*xgutter,
435  y0 + ygutter + pygutter + y*h + (py - 1)*pygutter + (y - 1)*ygutter,
436  w, h), frame_on=True, axis_bgcolor='w')
437  axes[panel].append(ax)
438  yield ax
439 
440 
441 def plotPsfSpatialModel(exposure, psf, psfCellSet, showBadCandidates=True, numSample=128,
442  matchKernelAmplitudes=False, keepPlots=True):
443  """Plot the PSF spatial model."""
444 
445  log = lsst.log.Log.getLogger("utils.plotPsfSpatialModel")
446  try:
447  import matplotlib.pyplot as plt
448  import matplotlib.colors
449  except ImportError as e:
450  log.warn("Unable to import matplotlib: %s", e)
451  return
452 
453  noSpatialKernel = psf.getKernel()
454  candPos = list()
455  candFits = list()
456  badPos = list()
457  badFits = list()
458  candAmps = list()
459  badAmps = list()
460  for cell in psfCellSet.getCellList():
461  for cand in cell.begin(False):
462  if not showBadCandidates and cand.isBad():
463  continue
464  candCenter = lsst.geom.PointD(cand.getXCenter(), cand.getYCenter())
465  try:
466  im = cand.getMaskedImage()
467  except Exception:
468  continue
469 
470  fit = fitKernelParamsToImage(noSpatialKernel, im, candCenter)
471  params = fit[0]
472  kernels = fit[1]
473  amp = 0.0
474  for p, k in zip(params, kernels):
475  amp += p * k.getSum()
476 
477  targetFits = badFits if cand.isBad() else candFits
478  targetPos = badPos if cand.isBad() else candPos
479  targetAmps = badAmps if cand.isBad() else candAmps
480 
481  targetFits.append([x / amp for x in params])
482  targetPos.append(candCenter)
483  targetAmps.append(amp)
484 
485  xGood = numpy.array([pos.getX() for pos in candPos]) - exposure.getX0()
486  yGood = numpy.array([pos.getY() for pos in candPos]) - exposure.getY0()
487  zGood = numpy.array(candFits)
488 
489  xBad = numpy.array([pos.getX() for pos in badPos]) - exposure.getX0()
490  yBad = numpy.array([pos.getY() for pos in badPos]) - exposure.getY0()
491  zBad = numpy.array(badFits)
492  numBad = len(badPos)
493 
494  xRange = numpy.linspace(0, exposure.getWidth(), num=numSample)
495  yRange = numpy.linspace(0, exposure.getHeight(), num=numSample)
496 
497  kernel = psf.getKernel()
498  nKernelComponents = kernel.getNKernelParameters()
499  #
500  # Figure out how many panels we'll need
501  #
502  nPanelX = int(math.sqrt(nKernelComponents))
503  nPanelY = nKernelComponents//nPanelX
504  while nPanelY*nPanelX < nKernelComponents:
505  nPanelX += 1
506 
507  fig = plt.figure(1)
508  fig.clf()
509  try:
510  fig.canvas._tkcanvas._root().lift() # == Tk's raise, but raise is a python reserved word
511  except Exception: # protect against API changes
512  pass
513  #
514  # Generator for axes arranged in panels
515  #
516  subplots = makeSubplots(fig, 2, 2, Nx=nPanelX, Ny=nPanelY, xgutter=0.06, ygutter=0.06, pygutter=0.04)
517 
518  for k in range(nKernelComponents):
519  func = kernel.getSpatialFunction(k)
520  dfGood = zGood[:, k] - numpy.array([func(pos.getX(), pos.getY()) for pos in candPos])
521  yMin = dfGood.min()
522  yMax = dfGood.max()
523  if numBad > 0:
524  dfBad = zBad[:, k] - numpy.array([func(pos.getX(), pos.getY()) for pos in badPos])
525  yMin = min([yMin, dfBad.min()])
526  yMax = max([yMax, dfBad.max()])
527  yMin -= 0.05 * (yMax - yMin)
528  yMax += 0.05 * (yMax - yMin)
529 
530  yMin = -0.01
531  yMax = 0.01
532 
533  fRange = numpy.ndarray((len(xRange), len(yRange)))
534  for j, yVal in enumerate(yRange):
535  for i, xVal in enumerate(xRange):
536  fRange[j][i] = func(xVal, yVal)
537 
538  ax = next(subplots)
539 
540  ax.set_autoscale_on(False)
541  ax.set_xbound(lower=0, upper=exposure.getHeight())
542  ax.set_ybound(lower=yMin, upper=yMax)
543  ax.plot(yGood, dfGood, 'b+')
544  if numBad > 0:
545  ax.plot(yBad, dfBad, 'r+')
546  ax.axhline(0.0)
547  ax.set_title('Residuals(y)')
548 
549  ax = next(subplots)
550 
551  if matchKernelAmplitudes and k == 0:
552  vmin = 0.0
553  vmax = 1.1
554  else:
555  vmin = fRange.min()
556  vmax = fRange.max()
557 
558  norm = matplotlib.colors.Normalize(vmin=vmin, vmax=vmax)
559  im = ax.imshow(fRange, aspect='auto', origin="lower", norm=norm,
560  extent=[0, exposure.getWidth()-1, 0, exposure.getHeight()-1])
561  ax.set_title('Spatial poly')
562  plt.colorbar(im, orientation='horizontal', ticks=[vmin, vmax])
563 
564  ax = next(subplots)
565  ax.set_autoscale_on(False)
566  ax.set_xbound(lower=0, upper=exposure.getWidth())
567  ax.set_ybound(lower=yMin, upper=yMax)
568  ax.plot(xGood, dfGood, 'b+')
569  if numBad > 0:
570  ax.plot(xBad, dfBad, 'r+')
571  ax.axhline(0.0)
572  ax.set_title('K%d Residuals(x)' % k)
573 
574  ax = next(subplots)
575 
576  if False:
577  ax.scatter(xGood, yGood, c=dfGood, marker='o')
578  ax.scatter(xBad, yBad, c=dfBad, marker='x')
579  ax.set_xbound(lower=0, upper=exposure.getWidth())
580  ax.set_ybound(lower=0, upper=exposure.getHeight())
581  ax.set_title('Spatial residuals')
582  plt.colorbar(im, orientation='horizontal')
583  else:
584  calib = exposure.getCalib()
585  if calib.getFluxMag0()[0] <= 0:
586  calib = type(calib)()
587  calib.setFluxMag0(1.0)
588 
589  with CalibNoThrow():
590  ax.plot(calib.getMagnitude(candAmps), zGood[:, k], 'b+')
591  if numBad > 0:
592  ax.plot(calib.getMagnitude(badAmps), zBad[:, k], 'r+')
593 
594  ax.set_title('Flux variation')
595 
596  fig.show()
597 
598  global keptPlots
599  if keepPlots and not keptPlots:
600  # Keep plots open when done
601  def show():
602  print("%s: Please close plots when done." % __name__)
603  try:
604  plt.show()
605  except Exception:
606  pass
607  print("Plots closed, exiting...")
608  import atexit
609  atexit.register(show)
610  keptPlots = True
611 
612 
613 def showPsf(psf, eigenValues=None, XY=None, normalize=True, frame=None):
614  """Display a PSF's eigen images
615 
616  If normalize is True, set the largest absolute value of each eigenimage to 1.0 (n.b. sum == 0.0 for i > 0)
617  """
618 
619  if eigenValues:
620  coeffs = eigenValues
621  elif XY is not None:
622  coeffs = psf.getLocalKernel(lsst.geom.PointD(XY[0], XY[1])).getKernelParameters()
623  else:
624  coeffs = None
625 
626  mos = displayUtils.Mosaic(gutter=2, background=-0.1)
627  for i, k in enumerate(psf.getKernel().getKernelList()):
628  im = afwImage.ImageD(k.getDimensions())
629  k.computeImage(im, False)
630  if normalize:
631  im /= numpy.max(numpy.abs(im.getArray()))
632 
633  if coeffs:
634  mos.append(im, "%g" % (coeffs[i]/coeffs[0]))
635  else:
636  mos.append(im)
637 
638  mos.makeMosaic(frame=frame, title="Kernel Basis Functions")
639 
640  return mos
641 
642 
643 def showPsfMosaic(exposure, psf=None, nx=7, ny=None,
644  showCenter=True, showEllipticity=False, showFwhm=False,
645  stampSize=0, frame=None, title=None):
646  """Show a mosaic of Psf images. exposure may be an Exposure (optionally with PSF),
647  or a tuple (width, height)
648 
649  If stampSize is > 0, the psf images will be trimmed to stampSize*stampSize
650  """
651 
652  scale = 1.0
653  if showFwhm:
654  showEllipticity = True
655  scale = 2*math.log(2) # convert sigma^2 to HWHM^2 for a Gaussian
656 
657  mos = displayUtils.Mosaic()
658 
659  try: # maybe it's a real Exposure
660  width, height = exposure.getWidth(), exposure.getHeight()
661  x0, y0 = exposure.getXY0()
662  if not psf:
663  psf = exposure.getPsf()
664  except AttributeError:
665  try: # OK, maybe a list [width, height]
666  width, height = exposure[0], exposure[1]
667  x0, y0 = 0, 0
668  except TypeError: # I guess not
669  raise RuntimeError("Unable to extract width/height from object of type %s" % type(exposure))
670 
671  if not ny:
672  ny = int(nx*float(height)/width + 0.5)
673  if not ny:
674  ny = 1
675 
676  centroidName = "SdssCentroid"
677  shapeName = "base_SdssShape"
678 
679  schema = afwTable.SourceTable.makeMinimalSchema()
680  schema.getAliasMap().set("slot_Centroid", centroidName)
681  schema.getAliasMap().set("slot_Centroid_flag", centroidName+"_flag")
682 
683  control = measBase.SdssCentroidControl()
684  centroider = measBase.SdssCentroidAlgorithm(control, centroidName, schema)
685 
686  sdssShape = measBase.SdssShapeControl()
687  shaper = measBase.SdssShapeAlgorithm(sdssShape, shapeName, schema)
688  table = afwTable.SourceTable.make(schema)
689 
690  table.defineCentroid(centroidName)
691  table.defineShape(shapeName)
692 
693  bbox = None
694  if stampSize > 0:
695  w, h = psf.computeImage(lsst.geom.PointD(0, 0)).getDimensions()
696  if stampSize <= w and stampSize <= h:
697  bbox = lsst.geom.BoxI(lsst.geom.PointI((w - stampSize)//2, (h - stampSize)//2),
698  lsst.geom.ExtentI(stampSize, stampSize))
699 
700  centers = []
701  shapes = []
702  for iy in range(ny):
703  for ix in range(nx):
704  x = int(ix*(width-1)/(nx-1)) + x0
705  y = int(iy*(height-1)/(ny-1)) + y0
706 
707  im = psf.computeImage(lsst.geom.PointD(x, y)).convertF()
708  imPeak = psf.computePeak(lsst.geom.PointD(x, y))
709  im /= imPeak
710  if bbox:
711  im = im.Factory(im, bbox)
712  lab = "PSF(%d,%d)" % (x, y) if False else ""
713  mos.append(im, lab)
714 
715  exp = afwImage.makeExposure(afwImage.makeMaskedImage(im))
716  w, h = im.getWidth(), im.getHeight()
717  centerX = im.getX0() + w//2
718  centerY = im.getY0() + h//2
719  src = table.makeRecord()
720  foot = afwDet.Footprint(exp.getBBox())
721  foot.addPeak(centerX, centerY, 1)
722  src.setFootprint(foot)
723 
724  centroider.measure(src, exp)
725  centers.append((src.getX() - im.getX0(), src.getY() - im.getY0()))
726 
727  shaper.measure(src, exp)
728  shapes.append((src.getIxx(), src.getIxy(), src.getIyy()))
729 
730  mos.makeMosaic(frame=frame, title=title if title else "Model Psf", mode=nx)
731 
732  if centers and frame is not None:
733  with ds9.Buffering():
734  for i, (cen, shape) in enumerate(zip(centers, shapes)):
735  bbox = mos.getBBox(i)
736  xc, yc = cen[0] + bbox.getMinX(), cen[1] + bbox.getMinY()
737  if showCenter:
738  ds9.dot("+", xc, yc, ctype=ds9.BLUE, frame=frame)
739 
740  if showEllipticity:
741  ixx, ixy, iyy = shape
742  ixx *= scale
743  ixy *= scale
744  iyy *= scale
745  ds9.dot("@:%g,%g,%g" % (ixx, ixy, iyy), xc, yc, frame=frame, ctype=ds9.RED)
746 
747  return mos
748 
749 
750 def showPsfResiduals(exposure, sourceSet, magType="psf", scale=10, frame=None):
751  mimIn = exposure.getMaskedImage()
752  mimIn = mimIn.Factory(mimIn, True) # make a copy to subtract from
753 
754  psf = exposure.getPsf()
755  psfWidth, psfHeight = psf.getLocalKernel().getDimensions()
756  #
757  # Make the image that we'll paste our residuals into. N.b. they can overlap the edges
758  #
759  w, h = int(mimIn.getWidth()/scale), int(mimIn.getHeight()/scale)
760 
761  im = mimIn.Factory(w + psfWidth, h + psfHeight)
762 
763  cenPos = []
764  for s in sourceSet:
765  x, y = s.getX(), s.getY()
766 
767  sx, sy = int(x/scale + 0.5), int(y/scale + 0.5)
768 
769  smim = im.Factory(im, lsst.geom.BoxI(lsst.geom.PointI(sx, sy),
770  lsst.geom.ExtentI(psfWidth, psfHeight)))
771  sim = smim.getImage()
772 
773  try:
774  if magType == "ap":
775  flux = s.getApInstFlux()
776  elif magType == "model":
777  flux = s.getModelInstFlux()
778  elif magType == "psf":
779  flux = s.getPsfInstFlux()
780  else:
781  raise RuntimeError("Unknown flux type %s" % magType)
782 
783  subtractPsf(psf, mimIn, x, y, flux)
784  except Exception as e:
785  print(e)
786 
787  try:
788  expIm = mimIn.getImage().Factory(mimIn.getImage(),
789  lsst.geom.BoxI(lsst.geom.PointI(int(x) - psfWidth//2,
790  int(y) - psfHeight//2),
791  lsst.geom.ExtentI(psfWidth, psfHeight)),
792  )
793  except pexExcept.Exception:
794  continue
795 
796  cenPos.append([x - expIm.getX0() + sx, y - expIm.getY0() + sy])
797 
798  sim += expIm
799 
800  if frame is not None:
801  ds9.mtv(im, frame=frame)
802  with ds9.Buffering():
803  for x, y in cenPos:
804  ds9.dot("+", x, y, frame=frame)
805 
806  return im
807 
808 
809 def saveSpatialCellSet(psfCellSet, fileName="foo.fits", frame=None):
810  """Write the contents of a SpatialCellSet to a many-MEF fits file"""
811 
812  mode = "w"
813  for cell in psfCellSet.getCellList():
814  for cand in cell.begin(False): # include bad candidates
815  dx = afwImage.positionToIndex(cand.getXCenter(), True)[1]
816  dy = afwImage.positionToIndex(cand.getYCenter(), True)[1]
817  im = afwMath.offsetImage(cand.getMaskedImage(), -dx, -dy, "lanczos5")
818 
819  md = dafBase.PropertySet()
820  md.set("CELL", cell.getLabel())
821  md.set("ID", cand.getId())
822  md.set("XCENTER", cand.getXCenter())
823  md.set("YCENTER", cand.getYCenter())
824  md.set("BAD", cand.isBad())
825  md.set("AMPL", cand.getAmplitude())
826  md.set("FLUX", cand.getSource().getPsfInstFlux())
827  md.set("CHI2", cand.getSource().getChi2())
828 
829  im.writeFits(fileName, md, mode)
830  mode = "a"
831 
832  if frame is not None:
833  ds9.mtv(im, frame=frame)
def showPsfSpatialCells(exposure, psfCellSet, nMaxPerCell=-1, showChi2=False, showMoments=False, symb=None, ctype=None, ctypeUnused=None, ctypeBad=None, size=2, frame=None)
Definition: utils.py:73
double subtractPsf(afw::detection::Psf const &psf, ImageT *data, double x, double y, double psfFlux=std::numeric_limits< double >::quiet_NaN())
def plotPsfSpatialModel(exposure, psf, psfCellSet, showBadCandidates=True, numSample=128, matchKernelAmplitudes=False, keepPlots=True)
Definition: utils.py:442
def showPsfResiduals(exposure, sourceSet, magType="psf", scale=10, frame=None)
Definition: utils.py:750
def makeSubplots(fig, nx=2, ny=2, Nx=1, Ny=1, plottingArea=(0.1, 0.1, 0.85, 0.80), pxgutter=0.05, pygutter=0.05, xgutter=0.04, ygutter=0.04, headroom=0.0, panelBorderWeight=0, panelColor='black')
Definition: utils.py:325
def showSourceSet(sSet, xy0=(0, 0), frame=0, ctype=ds9.GREEN, symb="+", size=2)
Definition: utils.py:55
static Log getLogger(std::string const &loggername)
def showPsf(psf, eigenValues=None, XY=None, normalize=True, frame=None)
Definition: utils.py:613
def saveSpatialCellSet(psfCellSet, fileName="foo.fits", frame=None)
Definition: utils.py:809
def splitId(oid, asDict=True)
Definition: utils.py:45
def showPsfMosaic(exposure, psf=None, nx=7, ny=None, showCenter=True, showEllipticity=False, showFwhm=False, stampSize=0, frame=None, title=None)
Definition: utils.py:645
std::pair< std::vector< double >, afw::math::KernelList > fitKernelParamsToImage(afw::math::LinearCombinationKernel const &kernel, Image const &image, geom::Point2D const &pos)
Fit a LinearCombinationKernel to an Image, allowing the coefficients of the components to vary...
def showPsfCandidates(exposure, psfCellSet, psf=None, frame=None, normalize=True, showBadCandidates=True, fitBasisComponents=False, variance=None, chi=None)
Definition: utils.py:122