LSSTApplications  10.0+286,10.0+36,10.0+46,10.0-2-g4f67435,10.1+152,10.1+37,11.0,11.0+1,11.0-1-g47edd16,11.0-1-g60db491,11.0-1-g7418c06,11.0-2-g04d2804,11.0-2-g68503cd,11.0-2-g818369d,11.0-2-gb8b8ce7
LSSTDataManagementBasePackage
utils.py
Go to the documentation of this file.
1 #
2 # LSST Data Management System
3 # Copyright 2008-2015 AURA/LSST.
4 #
5 # This product includes software developed by the
6 # LSST Project (http://www.lsst.org/).
7 #
8 # This program is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation, either version 3 of the License, or
11 # (at your option) any later version.
12 #
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
17 #
18 # You should have received a copy of the LSST License Statement and
19 # the GNU General Public License along with this program. If not,
20 # see <https://www.lsstcorp.org/LegalNotices/>.
21 #
22 
23 """Support utilities for Measuring sources"""
24 
25 import sys
26 import math
27 import numpy
28 
29 import lsst.pex.exceptions as pexExcept
30 import lsst.daf.base as dafBase
31 import lsst.afw.detection as afwDet
32 import lsst.afw.geom as afwGeom
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 import algorithmsLib
40 from lsst.afw.image.utils import CalibNoThrow
41 
42 keptPlots = False # Have we arranged to keep spatial plots open?
43 
44 #
45 # This should be provided by the mapper. The details are camera-specific and
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 def showSourceSet(sSet, xy0=(0, 0), frame=0, ctype=ds9.GREEN, symb="+", size=2):
57  """Draw the (XAstrom, YAstrom) positions of a set of Sources. Image has the given XY0"""
58 
59  with ds9.Buffering():
60  for s in sSet:
61  xc, yc = s.getXAstrom() - xy0[0], s.getYAstrom() - xy0[1]
62 
63  if symb == "id":
64  ds9.dot(str(splitId(s.getId(), True)["objId"]), xc, yc, frame=frame, ctype=ctype, size=size)
65  else:
66  ds9.dot(symb, xc, yc, frame=frame, ctype=ctype, size=size)
67 
68 #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
69 #
70 # PSF display utilities
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  cand = algorithmsLib.cast_PsfCandidateF(cand)
92 
93  xc, yc = cand.getXCenter() + origin[0], cand.getYCenter() + origin[1]
94 
95  if i > nMaxPerCell:
96  if not ctypeUnused:
97  continue
98 
99  color = ctypeBad if cand.isBad() else ctype
100 
101  if symb:
102  if i > nMaxPerCell:
103  ct = ctypeUnused
104  else:
105  ct = ctype
106 
107  ds9.dot(symb, xc, yc, frame=frame, ctype=ct, size=size)
108 
109  source = cand.getSource()
110 
111  if showChi2:
112  rchi2 = cand.getChi2()
113  if rchi2 > 1e100:
114  rchi2 = numpy.nan
115  ds9.dot("%d %.1f" % (splitId(source.getId(), True)["objId"], rchi2),
116  xc - size, yc - size - 4, frame=frame, ctype=color, size=2)
117 
118  if showMoments:
119  ds9.dot("%.2f %.2f %.2f" % (source.getIxx(), source.getIxy(), source.getIyy()),
120  xc-size, yc + size + 4, frame=frame, ctype=color, size=size)
121 
122 def showPsfCandidates(exposure, psfCellSet, psf=None, frame=None, normalize=True, showBadCandidates=True,
123  fitBasisComponents=False, variance=None, chi=None):
124  """Display the PSF candidates.
125 
126  If psf is provided include PSF model and residuals; if normalize is true normalize the PSFs
127  (and residuals)
128 
129  If chi is True, generate a plot of residuals/sqrt(variance), i.e. chi
130 
131  If fitBasisComponents is true, also find the best linear combination of the PSF's components
132  (if they exist)
133  """
134  if chi is None:
135  if variance is not None: # old name for chi
136  chi = variance
137  #
138  # Show us the ccandidates
139  #
140  mos = displayUtils.Mosaic()
141  #
142  candidateCenters = []
143  candidateCentersBad = []
144  candidateIndex = 0
145 
146  for cell in psfCellSet.getCellList():
147  for cand in cell.begin(False): # include bad candidates
148  cand = algorithmsLib.cast_PsfCandidateF(cand)
149 
150  rchi2 = cand.getChi2()
151  if rchi2 > 1e100:
152  rchi2 = numpy.nan
153 
154  if not showBadCandidates and cand.isBad():
155  continue
156 
157  if psf:
158  im_resid = displayUtils.Mosaic(gutter=0, background=-5, mode="x")
159 
160  try:
161  im = cand.getMaskedImage() # copy of this object's image
162  xc, yc = cand.getXCenter(), cand.getYCenter()
163 
164  margin = 0 if True else 5
165  w, h = im.getDimensions()
166  bbox = afwGeom.BoxI(afwGeom.PointI(margin, margin), im.getDimensions())
167 
168  if margin > 0:
169  bim = im.Factory(w + 2*margin, h + 2*margin)
170 
171  stdev = numpy.sqrt(afwMath.makeStatistics(im.getVariance(), afwMath.MEAN).getValue())
173  bim *= stdev
174  var = bim.getVariance(); var.set(stdev**2); del var
175 
176  sbim = im.Factory(bim, bbox)
177  sbim <<= im
178  del sbim
179  im = bim
180  xc += margin; 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; del miBig
205 
206  exp = afwImage.makeExposure(mi)
207  exp.setPsf(psf)
208 
209  footprintSet = afwDet.FootprintSet(mi,
210  afwDet.Threshold(0.5*numpy.max(psfIm.getArray())),
211  "DETECTED")
212  footprintSet.makeSources(catalog)
213 
214  if len(catalog) == 0:
215  raise RuntimeError("Failed to detect any objects")
216 
217  measureSources.run(catalog, exp)
218  if len(catalog) == 1:
219  source = catalog[0]
220  else: # more than one source; find the once closest to (xc, yc)
221  for i, s in enumerate(catalog):
222  d = numpy.hypot(xc - s.getX(), yc - s.getY())
223  if i == 0 or d < dmin:
224  source, dmin = s, d
225  xc, yc = source.getCentroid()
226 
227  # residuals using spatial model
228  try:
229  chi2 = algorithmsLib.subtractPsf(psf, im, xc, yc)
230  except:
231  chi2 = numpy.nan
232  continue
233 
234  resid = im
235  if variance:
236  resid = resid.getImage()
237  var = im.getVariance()
238  var = var.Factory(var, True)
239  numpy.sqrt(var.getArray(), var.getArray()) # inplace sqrt
240  resid /= var
241 
242  im_resid.append(resid)
243 
244  # Fit the PSF components directly to the data (i.e. ignoring the spatial model)
245  if fitBasisComponents:
246  im = cand.getMaskedImage()
247 
248  im = im.Factory(im, True)
249  im.setXY0(cand.getMaskedImage().getXY0())
250 
251  try:
252  noSpatialKernel = afwMath.cast_LinearCombinationKernel(psf.getKernel())
253  except:
254  noSpatialKernel = None
255 
256  if noSpatialKernel:
257  candCenter = afwGeom.PointD(cand.getXCenter(), cand.getYCenter())
258  fit = algorithmsLib.fitKernelParamsToImage(noSpatialKernel, im, candCenter)
259  params = fit[0]
260  kernels = afwMath.KernelList(fit[1])
261  outputKernel = afwMath.LinearCombinationKernel(kernels, params)
262 
263  outImage = afwImage.ImageD(outputKernel.getDimensions())
264  outputKernel.computeImage(outImage, False)
265 
266  im -= outImage.convertF()
267  resid = im
268 
269  if margin > 0:
270  bim = im.Factory(w + 2*margin, h + 2*margin)
272  bim *= stdev
273 
274  sbim = im.Factory(bim, bbox)
275  sbim <<= resid
276  del sbim
277  resid = bim
278 
279  if variance:
280  resid = resid.getImage()
281  resid /= var
282 
283  im_resid.append(resid)
284 
285  im = im_resid.makeMosaic()
286  else:
287  im = cand.getMaskedImage()
288 
289  if normalize:
290  im /= afwMath.makeStatistics(im, afwMath.MAX).getValue()
291 
292  objId = splitId(cand.getSource().getId(), True)["objId"]
293  if psf:
294  lab = "%d chi^2 %.1f" % (objId, rchi2)
295  ctype = ds9.RED if cand.isBad() else ds9.GREEN
296  else:
297  lab = "%d flux %8.3g" % (objId, cand.getSource().getPsfFlux())
298  ctype = ds9.GREEN
299 
300  mos.append(im, lab, ctype)
301 
302  if False and numpy.isnan(rchi2):
303  ds9.mtv(cand.getMaskedImage().getImage(), title="candidate", frame=1)
304  print "amp", cand.getAmplitude()
305 
306  im = cand.getMaskedImage()
307  center = (candidateIndex, xc - im.getX0(), yc - im.getY0())
308  candidateIndex += 1
309  if cand.isBad():
310  candidateCentersBad.append(center)
311  else:
312  candidateCenters.append(center)
313 
314  if variance:
315  title = "chi(Psf fit)"
316  else:
317  title = "Stars & residuals"
318  mosaicImage = mos.makeMosaic(frame=frame, title=title)
319 
320  with ds9.Buffering():
321  for centers, color in ((candidateCenters, ds9.GREEN), (candidateCentersBad, ds9.RED)):
322  for cen in centers:
323  bbox = mos.getBBox(cen[0])
324  ds9.dot("+", cen[1] + bbox.getMinX(), cen[2] + bbox.getMinY(), frame=frame, ctype=color)
325 
326  return mosaicImage
327 
328 try:
329  import matplotlib.pyplot as plt
330  import matplotlib.colors
331 except ImportError:
332  plt = None
333 
334 def makeSubplots(fig, nx=2, ny=2, Nx=1, Ny=1, plottingArea=(0.1, 0.1, 0.85, 0.80),
335  pxgutter=0.05, pygutter=0.05, xgutter=0.04, ygutter=0.04,
336  headroom=0.0, panelBorderWeight=0, panelColor='black'):
337  """Return a generator of a set of subplots, a set of Nx*Ny panels of nx*ny plots. Each panel is fully
338  filled by row (starting in the bottom left) before the next panel is started. If panelBorderWidth is
339  greater than zero a border is drawn around each panel, adjusted to enclose the axis labels.
340 
341  E.g.
342  subplots = makeSubplots(fig, 2, 2, Nx=1, Ny=1, panelColor='k')
343  ax = subplots.next(); ax.text(0.3, 0.5, '[0, 0] (0,0)')
344  ax = subplots.next(); ax.text(0.3, 0.5, '[0, 0] (1,0)')
345  ax = subplots.next(); ax.text(0.3, 0.5, '[0, 0] (0,1)')
346  ax = subplots.next(); ax.text(0.3, 0.5, '[0, 0] (1,1)')
347  fig.show()
348 
349  @param fig The matplotlib figure to draw
350  @param nx The number of plots in each row of each panel
351  @param ny The number of plots in each column of each panel
352  @param Nx The number of panels in each row of the figure
353  @param Ny The number of panels in each column of the figure
354  @param plottingArea (x0, y0, x1, y1) for the part of the figure containing all the panels
355  @param pxgutter Spacing between columns of panels in units of (x1 - x0)
356  @param pygutter Spacing between rows of panels in units of (y1 - y0)
357  @param xgutter Spacing between columns of plots within a panel in units of (x1 - x0)
358  @param ygutter Spacing between rows of plots within a panel in units of (y1 - y0)
359  @param headroom Extra spacing above each plot for e.g. a title
360  @param panelBorderWeight Width of border drawn around panels
361  @param panelColor Colour of border around panels
362  """
363  #
364  # Make show() call canvas.draw() too so that we know how large the axis labels are. Sigh
365  try:
366  fig.__show
367  except AttributeError:
368  fig.__show = fig.show
369  def myShow(fig):
370  fig.__show()
371  fig.canvas.draw()
372 
373  import types
374  fig.show = types.MethodType(myShow, fig, fig.__class__)
375  #
376  # We can't get the axis sizes until after draw()'s been called, so use a callback Sigh^2
377  #
378  axes = {} # all axes in all the panels we're drawing: axes[panel][0] etc.
379  #
380  def on_draw(event):
381  """
382  Callback to draw the panel borders when the plots are drawn to the canvas
383  """
384  if panelBorderWeight <= 0:
385  return False
386 
387  for p in axes.keys():
388  bboxes = []
389  for ax in axes[p]:
390  bboxes.append(ax.bbox.union([label.get_window_extent() for label in
391  ax.get_xticklabels() + ax.get_yticklabels()]))
392 
393  ax = axes[p][0]
394 
395  # this is the bbox that bounds all the bboxes, again in relative
396  # figure coords
397 
398  bbox = ax.bbox.union(bboxes)
399 
400  xy0, xy1 = ax.transData.inverted().transform(bbox)
401  x0, y0 = xy0; x1, y1 = xy1
402  w, h = x1 - x0, y1 - y0
403  # allow a little space around BBox
404  x0 -= 0.02*w; w += 0.04*w
405  y0 -= 0.02*h; h += 0.04*h
406  h += h*headroom
407  # draw BBox
408  ax.patches = [] # remove old ones
409  rec = ax.add_patch(plt.Rectangle((x0, y0), w, h, fill=False,
410  lw=panelBorderWeight, edgecolor=panelColor))
411  rec.set_clip_on(False)
412 
413  return False
414 
415  fig.canvas.mpl_connect('draw_event', on_draw)
416  #
417  # Choose the plotting areas for each subplot
418  #
419  x0, y0 = plottingArea[0:2]
420  W, H = plottingArea[2:4]
421  w = (W - (Nx - 1)*pxgutter - (nx*Nx - 1)*xgutter)/float(nx*Nx)
422  h = (H - (Ny - 1)*pygutter - (ny*Ny - 1)*ygutter)/float(ny*Ny)
423  #
424  # OK! Time to create the subplots
425  #
426  for panel in range(Nx*Ny):
427  axes[panel] = []
428  px = panel%Nx
429  py = panel//Nx
430  for window in range(nx*ny):
431  x = nx*px + window%nx
432  y = ny*py + window//nx
433  ax = fig.add_axes((x0 + xgutter + pxgutter + x*w + (px - 1)*pxgutter + (x - 1)*xgutter,
434  y0 + ygutter + pygutter + y*h + (py - 1)*pygutter + (y - 1)*ygutter,
435  w, h), frame_on=True, axis_bgcolor='w')
436  axes[panel].append(ax)
437  yield ax
438 
439 def plotPsfSpatialModel(exposure, psf, psfCellSet, showBadCandidates=True, numSample=128,
440  matchKernelAmplitudes=False, keepPlots=True):
441  """Plot the PSF spatial model."""
442 
443  if not plt:
444  print >> sys.stderr, "Unable to import matplotlib"
445  return
446 
447  noSpatialKernel = afwMath.cast_LinearCombinationKernel(psf.getKernel())
448  candPos = list()
449  candFits = list()
450  badPos = list()
451  badFits = list()
452  candAmps = list()
453  badAmps = list()
454  for cell in psfCellSet.getCellList():
455  for cand in cell.begin(False):
456  cand = algorithmsLib.cast_PsfCandidateF(cand)
457  if not showBadCandidates and cand.isBad():
458  continue
459  candCenter = afwGeom.PointD(cand.getXCenter(), cand.getYCenter())
460  try:
461  im = cand.getMaskedImage()
462  except Exception:
463  continue
464 
465  fit = algorithmsLib.fitKernelParamsToImage(noSpatialKernel, im, candCenter)
466  params = fit[0]
467  kernels = fit[1]
468  amp = 0.0
469  for p, k in zip(params, kernels):
470  amp += p * afwMath.cast_FixedKernel(k).getSum()
471 
472  targetFits = badFits if cand.isBad() else candFits
473  targetPos = badPos if cand.isBad() else candPos
474  targetAmps = badAmps if cand.isBad() else candAmps
475 
476  targetFits.append([x / amp for x in params])
477  targetPos.append(candCenter)
478  targetAmps.append(amp)
479 
480  numCandidates = len(candFits)
481  numBasisFuncs = noSpatialKernel.getNBasisKernels()
482 
483  xGood = numpy.array([pos.getX() for pos in candPos]) - exposure.getX0()
484  yGood = numpy.array([pos.getY() for pos in candPos]) - exposure.getY0()
485  zGood = numpy.array(candFits)
486  ampGood = numpy.array(candAmps)
487 
488  xBad = numpy.array([pos.getX() for pos in badPos]) - exposure.getX0()
489  yBad = numpy.array([pos.getY() for pos in badPos]) - exposure.getY0()
490  zBad = numpy.array(badFits)
491  ampBad = numpy.array(badAmps)
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: # 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  #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
539 
540  ax = subplots.next()
541 
542  ax.set_autoscale_on(False)
543  ax.set_xbound(lower=0, upper=exposure.getHeight())
544  ax.set_ybound(lower=yMin, upper=yMax)
545  ax.plot(yGood, dfGood, 'b+')
546  if numBad > 0:
547  ax.plot(yBad, dfBad, 'r+')
548  ax.axhline(0.0)
549  ax.set_title('Residuals(y)')
550 
551  #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
552 
553  ax = subplots.next()
554 
555  if matchKernelAmplitudes and k == 0:
556  vmin = 0.0
557  vmax = 1.1
558  else:
559  vmin = fRange.min()
560  vmax = fRange.max()
561 
562  norm = matplotlib.colors.Normalize(vmin=vmin, vmax=vmax)
563  im = ax.imshow(fRange, aspect='auto', origin="lower", norm=norm,
564  extent=[0, exposure.getWidth()-1, 0, exposure.getHeight()-1])
565  ax.set_title('Spatial poly')
566  plt.colorbar(im, orientation='horizontal', ticks=[vmin, vmax])
567 
568  #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
569 
570  ax = subplots.next()
571  ax.set_autoscale_on(False)
572  ax.set_xbound(lower=0, upper=exposure.getWidth())
573  ax.set_ybound(lower=yMin, upper=yMax)
574  ax.plot(xGood, dfGood, 'b+')
575  if numBad > 0:
576  ax.plot(xBad, dfBad, 'r+')
577  ax.axhline(0.0)
578  ax.set_title('K%d Residuals(x)' % k)
579 
580  #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
581 
582  ax = subplots.next()
583 
584  if False:
585  ax.scatter(xGood, yGood, c=dfGood, marker='o')
586  ax.scatter(xBad, yBad, c=dfBad, marker='x')
587  ax.set_xbound(lower=0, upper=exposure.getWidth())
588  ax.set_ybound(lower=0, upper=exposure.getHeight())
589  ax.set_title('Spatial residuals')
590  plt.colorbar(im, orientation='horizontal')
591  else:
592  calib = exposure.getCalib()
593  if calib.getFluxMag0()[0] <= 0:
594  calib = type(calib)()
595  calib.setFluxMag0(1.0)
596 
597  with CalibNoThrow():
598  ax.plot(calib.getMagnitude(candAmps), zGood[:,k], 'b+')
599  if numBad > 0:
600  ax.plot(calib.getMagnitude(badAmps), zBad[:,k], 'r+')
601 
602  ax.set_title('Flux variation')
603 
604  fig.show()
605 
606  global keptPlots
607  if keepPlots and not keptPlots:
608  # Keep plots open when done
609  def show():
610  print "%s: Please close plots when done." % __name__
611  try:
612  plt.show()
613  except:
614  pass
615  print "Plots closed, exiting..."
616  import atexit
617  atexit.register(show)
618  keptPlots = True
619 
620 def showPsf(psf, eigenValues=None, XY=None, normalize=True, frame=None):
621  """Display a PSF's eigen images
622 
623  If normalize is True, set the largest absolute value of each eigenimage to 1.0 (n.b. sum == 0.0 for i > 0)
624  """
625 
626  if eigenValues:
627  coeffs = eigenValues
628  elif XY is not None:
629  coeffs = psf.getLocalKernel(afwGeom.PointD(XY[0], XY[1])).getKernelParameters()
630  else:
631  coeffs = None
632 
633  mos = displayUtils.Mosaic(gutter=2, background=-0.1)
634  for i, k in enumerate(afwMath.cast_LinearCombinationKernel(psf.getKernel()).getKernelList()):
635  im = afwImage.ImageD(k.getDimensions())
636  k.computeImage(im, False)
637  if normalize:
638  im /= numpy.max(numpy.abs(im.getArray()))
639 
640  if coeffs:
641  mos.append(im, "%g" % (coeffs[i]/coeffs[0]))
642  else:
643  mos.append(im)
644 
645  mos.makeMosaic(frame=frame, title="Kernel Basis Functions")
646 
647  return mos
648 
649 def showPsfMosaic(exposure, psf=None, nx=7, ny=None,
650  showCenter=True, showEllipticity=False, showFwhm=False,
651  stampSize=0, frame=None, title=None):
652  """Show a mosaic of Psf images. exposure may be an Exposure (optionally with PSF),
653  or a tuple (width, height)
654 
655  If stampSize is > 0, the psf images will be trimmed to stampSize*stampSize
656  """
657 
658  scale = 1.0
659  if showFwhm:
660  showEllipticity = True
661  scale = 2*math.log(2) # convert sigma^2 to HWHM^2 for a Gaussian
662 
663  mos = displayUtils.Mosaic()
664 
665  try: # maybe it's a real Exposure
666  width, height = exposure.getWidth(), exposure.getHeight()
667  x0, y0 = exposure.getXY0()
668  if not psf:
669  psf = exposure.getPsf()
670  except AttributeError:
671  try: # OK, maybe a list [width, height]
672  width, height = exposure[0], exposure[1]
673  x0, y0 = 0, 0
674  except TypeError: # I guess not
675  raise RuntimeError, ("Unable to extract width/height from object of type %s" % type(exposure))
676 
677  if not ny:
678  ny = int(nx*float(height)/width + 0.5)
679  if not ny:
680  ny = 1
681 
682  centroidName = "base_GaussianCentroid"
683  shapeName = "base_SdssShape"
684 
685  schema = afwTable.SourceTable.makeMinimalSchema()
686  schema.getAliasMap().set("slot_Centroid", centroidName)
687  schema.getAliasMap().set("slot_Centroid_flag", centroidName+"_flag")
688 
689  control = measBase.GaussianCentroidControl()
690  centroider = measBase.GaussianCentroidAlgorithm(control,centroidName,schema)
691 
692  sdssShape = measBase.SdssShapeControl()
693  shaper = measBase.SdssShapeAlgorithm(sdssShape,shapeName,schema)
694  table = afwTable.SourceTable.make(schema)
695 
696  table.defineCentroid(centroidName)
697  table.defineShape(shapeName)
698 
699  bbox = None
700  if stampSize > 0:
701  w, h = psf.computeImage(afwGeom.PointD(0, 0)).getDimensions()
702  if stampSize <= w and stampSize <= h:
703  bbox = afwGeom.BoxI(afwGeom.PointI((w - stampSize)//2, (h - stampSize)//2),
704  afwGeom.ExtentI(stampSize, stampSize))
705 
706  centers = []
707  shapes = []
708  for iy in range(ny):
709  for ix in range(nx):
710  x = int(ix*(width-1)/(nx-1)) + x0
711  y = int(iy*(height-1)/(ny-1)) + y0
712 
713  im = psf.computeImage(afwGeom.PointD(x, y)).convertF()
714  imPeak = psf.computePeak(afwGeom.PointD(x, y))
715  im /= imPeak
716  if bbox:
717  im = im.Factory(im, bbox)
718  lab = "PSF(%d,%d)" % (x, y) if False else ""
719  mos.append(im, lab)
720 
722  w, h = im.getWidth(), im.getHeight()
723  centerX = im.getX0() + w//2
724  centerY = im.getY0() + h//2
725  src = table.makeRecord()
726  src.set(centroidName+"_x", centerX)
727  src.set(centroidName+"_y", centerY)
728  foot = afwDet.Footprint(exp.getBBox())
729  src.setFootprint(foot)
730 
731  centroider.measure(src, exp)
732  centers.append((src.getX() - im.getX0(), src.getY() - im.getY0()))
733 
734  shaper.measure(src, exp)
735  shapes.append((src.getIxx(), src.getIxy(), src.getIyy()))
736 
737  mos.makeMosaic(frame=frame, title=title if title else "Model Psf", mode=nx)
738 
739  if centers and frame is not None:
740  i = 0
741  with ds9.Buffering():
742  for cen, shape in zip(centers, shapes):
743  bbox = mos.getBBox(i); i += 1
744  xc, yc = cen[0] + bbox.getMinX(), cen[1] + bbox.getMinY()
745  if showCenter:
746  ds9.dot("+", xc, yc, ctype=ds9.BLUE, frame=frame)
747 
748  if showEllipticity:
749  ixx, ixy, iyy = shape
750  ixx *= scale; ixy *= scale; iyy *= scale
751  ds9.dot("@:%g,%g,%g" % (ixx, ixy, iyy), xc, yc, frame=frame, ctype=ds9.RED)
752 
753  return mos
754 
755 def showPsfResiduals(exposure, sourceSet, magType="psf", scale=10, frame=None, showAmps=False):
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  algorithmsLib.subtractPsf(psf, mimIn, x, y, flux)
788  except Exception, 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  if showAmps:
811  nx, ny = namp
812  for i in range(nx):
813  for j in range(ny):
814  xc = numpy.array([0, 1, 1, 0, 0])
815  yc = numpy.array([0, 0, 1, 1, 0])
816 
817  corners = []
818  for k in range(len(xc)):
819  corners.append([psfWidth//2 + w/nx*(i + xc[k]), psfHeight//2 + h/ny*(j + yc[k])])
820 
821  ds9.line(corners, frame=frame)
822 
823  return im
824 
825 #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
826 
827 def saveSpatialCellSet(psfCellSet, fileName="foo.fits", frame=None):
828  """Write the contents of a SpatialCellSet to a many-MEF fits file"""
829 
830  mode = "w"
831  for cell in psfCellSet.getCellList():
832  for cand in cell.begin(False): # include bad candidates
833  cand = algorithmsLib.cast_PsfCandidateF(cand)
834 
835  dx = afwImage.positionToIndex(cand.getXCenter(), True)[1]
836  dy = afwImage.positionToIndex(cand.getYCenter(), True)[1]
837  im = afwMath.offsetImage(cand.getMaskedImage(), -dx, -dy, "lanczos5")
838 
839  md = dafBase.PropertySet()
840  md.set("CELL", cell.getLabel())
841  md.set("ID", cand.getId())
842  md.set("XCENTER", cand.getXCenter())
843  md.set("YCENTER", cand.getYCenter())
844  md.set("BAD", cand.isBad())
845  md.set("AMPL", cand.getAmplitude())
846  md.set("FLUX", cand.getSource().getPsfFlux())
847  md.set("CHI2", cand.getSource().getChi2())
848 
849  im.writeFits(fileName, md, mode)
850  mode = "a"
851 
852  if frame is not None:
853  ds9.mtv(im, frame=frame)
MaskedImage< ImagePixelT, MaskPixelT, VariancePixelT > * makeMaskedImage(typename Image< ImagePixelT >::Ptr image, typename Mask< MaskPixelT >::Ptr mask=typename Mask< MaskPixelT >::Ptr(), typename Image< VariancePixelT >::Ptr variance=typename Image< VariancePixelT >::Ptr())
Definition: MaskedImage.h:1067
std::pair< int, double > positionToIndex(double const pos, bool)
Convert image position to index (nearest integer and fractional parts)
Definition: ImageUtils.h:100
A Threshold is used to pass a threshold value to detection algorithms.
Definition: Threshold.h:44
An integer coordinate rectangle.
Definition: Box.h:53
Custom catalog class for record/table subclasses that are guaranteed to have an ID, and should generally be sorted by that ID.
Definition: fwd.h:55
A kernel that is a linear combination of fixed basis kernels.
Definition: Kernel.h:814
void randomGaussianImage(ImageT *image, Random &rand)
Definition: RandomImage.cc:155
A set of pixels in an Image.
Definition: Footprint.h:62
ImageT::Ptr offsetImage(ImageT const &image, float dx, float dy, std::string const &algorithmName="lanczos5", unsigned int buffer=0)
Return an image offset by (dx, dy) using the specified algorithm.
Definition: offsetImage.cc:55
A set of Footprints, associated with a MaskedImage.
Definition: FootprintSet.h:53
Class for storing generic metadata.
Definition: PropertySet.h:82
Statistics makeStatistics(afwImage::Mask< afwImage::MaskPixel > const &msk, int const flags, StatisticsControl const &sctrl)
Specialization to handle Masks.
Definition: Statistics.cc:1082
Exposure< ImagePixelT, MaskPixelT, VariancePixelT >::Ptr makeExposure(MaskedImage< ImagePixelT, MaskPixelT, VariancePixelT > &mimage, boost::shared_ptr< Wcs const > wcs=boost::shared_ptr< Wcs const >())
Definition: Exposure.h:308
std::vector< boost::shared_ptr< Kernel > > KernelList
Definition: Kernel.h:542