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