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