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