23 """Support utilities for Measuring sources"""
47 objId = int((oid & 0xffff) - 1)
50 return dict(objId=objId)
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"""
59 xc, yc = s.getXAstrom() - xy0[0], s.getYAstrom() - xy0[1]
62 ds9.dot(str(
splitId(s.getId(),
True)[
"objId"]), xc, yc, frame=frame, ctype=ctype, size=size)
64 ds9.dot(symb, xc, yc, frame=frame, ctype=ctype, size=size)
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"""
75 origin = [-exposure.getMaskedImage().getX0(), -exposure.getMaskedImage().getY0()]
76 for cell
in psfCellSet.getCellList():
77 displayUtils.drawBBox(cell.getBBox(), origin=origin, frame=frame)
83 goodies = ctypeBad
is None
84 for cand
in cell.begin(goodies):
88 cand = algorithmsLib.cast_PsfCandidateF(cand)
90 xc, yc = cand.getXCenter() + origin[0], cand.getYCenter() + origin[1]
96 color = ctypeBad
if cand.isBad()
else ctype
104 ds9.dot(symb, xc, yc, frame=frame, ctype=ct, size=size)
106 source = cand.getSource()
109 rchi2 = cand.getChi2()
112 ds9.dot(
"%d %.1f" % (
splitId(source.getId(),
True)[
"objId"], rchi2),
113 xc - size, yc - size - 4, frame=frame, ctype=color, size=2)
116 ds9.dot(
"%.2f %.2f %.2f" % (source.getIxx(), source.getIxy(), source.getIyy()),
117 xc-size, yc + size + 4, frame=frame, ctype=color, size=size)
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
126 if variance
is not None:
131 mos = displayUtils.Mosaic()
133 candidateCenters = []
134 candidateCentersBad = []
137 for cell
in psfCellSet.getCellList():
138 for cand
in cell.begin(
False):
139 cand = algorithmsLib.cast_PsfCandidateF(cand)
141 rchi2 = cand.getChi2()
145 if not showBadCandidates
and cand.isBad():
149 im_resid = displayUtils.Mosaic(gutter=0, background=-5, mode=
"x")
152 im = cand.getMaskedImage()
153 xc, yc = cand.getXCenter(), cand.getYCenter()
155 margin = 0
if True else 5
156 w, h = im.getDimensions()
160 bim = im.Factory(w + 2*margin, h + 2*margin)
165 var = bim.getVariance(); var.set(stdev**2); del var
167 sbim = im.Factory(bim, bbox)
171 xc += margin; yc += margin
173 im = im.Factory(im,
True)
174 im.setXY0(cand.getMaskedImage().getXY0())
179 im_resid.append(im.Factory(im,
True))
183 chi2 = algorithmsLib.subtractPsf(psf, im, xc, yc)
189 resid = resid.getImage()
190 var = im.getVariance()
191 var = var.Factory(var,
True)
192 numpy.sqrt(var.getArray(), var.getArray())
195 im_resid.append(resid)
198 im = cand.getMaskedImage()
200 im = im.Factory(im,
True)
201 im.setXY0(cand.getMaskedImage().getXY0())
204 noSpatialKernel = afwMath.cast_LinearCombinationKernel(psf.getKernel())
206 noSpatialKernel =
None
210 fit = algorithmsLib.fitKernelParamsToImage(noSpatialKernel, im, candCenter)
215 outImage = afwImage.ImageD(outputKernel.getDimensions())
216 outputKernel.computeImage(outImage,
False)
218 im -= outImage.convertF()
222 bim = im.Factory(w + 2*margin, h + 2*margin)
226 sbim = im.Factory(bim, bbox)
232 resid = resid.getImage()
235 im_resid.append(resid)
237 im = im_resid.makeMosaic()
239 im = cand.getMaskedImage()
244 objId =
splitId(cand.getSource().getId(),
True)[
"objId"]
246 lab =
"%d chi^2 %.1f" % (objId, rchi2)
247 ctype = ds9.RED
if cand.isBad()
else ds9.GREEN
249 lab =
"%d flux %8.3g" % (objId, cand.getSource().getPsfFlux())
252 mos.append(im, lab, ctype)
254 if False and numpy.isnan(rchi2):
255 ds9.mtv(cand.getMaskedImage().getImage(), title=
"candidate", frame=1)
256 print "amp", cand.getAmplitude()
258 im = cand.getMaskedImage()
259 center = (candidateIndex, xc - im.getX0(), yc - im.getY0())
262 candidateCentersBad.append(center)
264 candidateCenters.append(center)
267 title =
"chi(Psf fit)"
269 title =
"Stars & residuals"
270 mosaicImage = mos.makeMosaic(frame=frame, title=title)
272 with ds9.Buffering():
273 for centers, color
in ((candidateCenters, ds9.GREEN), (candidateCentersBad, ds9.RED)):
275 bbox = mos.getBBox(cen[0])
276 ds9.dot(
"+", cen[1] + bbox.getMinX(), cen[2] + bbox.getMinY(), frame=frame, ctype=color)
281 import matplotlib.pyplot
as plt
282 import matplotlib.colors
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.
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)')
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
319 except AttributeError:
320 fig.__show = fig.show
326 fig.show = types.MethodType(myShow, fig, fig.__class__)
334 Callback to draw the panel borders when the plots are drawn to the canvas
336 if panelBorderWeight <= 0:
339 for p
in axes.keys():
342 bboxes.append(ax.bbox.union([label.get_window_extent()
for label
in
343 ax.get_xticklabels() + ax.get_yticklabels()]))
350 bbox = ax.bbox.union(bboxes)
352 xy0, xy1 = ax.transData.inverted().transform(bbox)
353 x0, y0 = xy0; x1, y1 = xy1
354 w, h = x1 - x0, y1 - y0
356 x0 -= 0.02*w; w += 0.04*w
357 y0 -= 0.02*h; h += 0.04*h
361 rec = ax.add_patch(plt.Rectangle((x0, y0), w, h, fill=
False,
362 lw=panelBorderWeight, edgecolor=panelColor))
363 rec.set_clip_on(
False)
367 fig.canvas.mpl_connect(
'draw_event', on_draw)
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)
378 for panel
in range(Nx*Ny):
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)
392 matchKernelAmplitudes=
False, keepPlots=
True):
393 """Plot the PSF spatial model."""
396 print >> sys.stderr,
"Unable to import matplotlib"
399 noSpatialKernel = afwMath.cast_LinearCombinationKernel(psf.getKernel())
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():
413 im = cand.getMaskedImage()
417 fit = algorithmsLib.fitKernelParamsToImage(noSpatialKernel, im, candCenter)
421 for p, k
in zip(params, kernels):
422 amp += p * afwMath.cast_FixedKernel(k).getSum()
424 targetFits = badFits
if cand.isBad()
else candFits
425 targetPos = badPos
if cand.isBad()
else candPos
426 targetAmps = badAmps
if cand.isBad()
else candAmps
428 targetFits.append([x / amp
for x
in params])
429 targetPos.append(candCenter)
430 targetAmps.append(amp)
432 numCandidates = len(candFits)
433 numBasisFuncs = noSpatialKernel.getNBasisKernels()
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)
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)
446 xRange = numpy.linspace(0, exposure.getWidth(), num=numSample)
447 yRange = numpy.linspace(0, exposure.getHeight(), num=numSample)
449 kernel = psf.getKernel()
450 nKernelComponents = kernel.getNKernelParameters()
454 nPanelX = int(math.sqrt(nKernelComponents))
455 nPanelY = nKernelComponents//nPanelX
456 while nPanelY*nPanelX < nKernelComponents:
462 fig.canvas._tkcanvas._root().lift()
468 subplots =
makeSubplots(fig, 2, 2, Nx=nPanelX, Ny=nPanelY, xgutter=0.06, ygutter=0.06, pygutter=0.04)
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])
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)
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)
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+')
499 ax.plot(yBad, dfBad,
'r+')
501 ax.set_title(
'Residuals(y)')
507 if matchKernelAmplitudes
and k == 0:
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])
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+')
528 ax.plot(xBad, dfBad,
'r+')
530 ax.set_title(
'K%d Residuals(x)' % k)
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')
544 calib = exposure.getCalib()
545 if calib.getFluxMag0()[0] <= 0:
546 calib = type(calib)()
547 calib.setFluxMag0(1.0)
550 ax.plot(calib.getMagnitude(candAmps), zGood[:,k],
'b+')
552 ax.plot(calib.getMagnitude(badAmps), zBad[:,k],
'r+')
554 ax.set_title(
'Flux variation')
559 if keepPlots
and not keptPlots:
562 print "%s: Please close plots when done." % __name__
567 print "Plots closed, exiting..."
569 atexit.register(show)
572 def showPsf(psf, eigenValues=None, XY=None, normalize=True, frame=None):
573 """Display a PSF's eigen images
575 If normalize is True, set the largest absolute value of each eigenimage to 1.0 (n.b. sum == 0.0 for i > 0)
581 coeffs = psf.getLocalKernel(
afwGeom.PointD(XY[0], XY[1])).getKernelParameters()
585 mos = displayUtils.Mosaic()
587 for k
in afwMath.cast_LinearCombinationKernel(psf.getKernel()).getKernelList():
588 im = afwImage.ImageD(k.getDimensions())
589 k.computeImage(im,
False)
591 im /= numpy.max(numpy.abs(im.getArray()))
594 mos.append(im,
"%g" % (coeffs[i]/coeffs[0]))
599 mos.makeMosaic(frame=frame, title=
"Eigen Images")
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)
608 If stampSize is > 0, the psf images will be trimmed to stampSize*stampSize
613 showEllipticity =
True
614 scale = 2*math.log(2)
616 mos = displayUtils.Mosaic()
619 width, height = exposure.getWidth(), exposure.getHeight()
620 x0, y0 = exposure.getXY0()
622 psf = exposure.getPsf()
623 except AttributeError:
625 width, height = exposure[0], exposure[1]
628 raise RuntimeError, (
"Unable to extract width/height from object of type %s" % type(exposure))
631 ny = int(nx*float(height)/width + 0.5)
635 schema = afwTable.SourceTable.makeMinimalSchema()
637 control = algorithmsLib.GaussianCentroidControl()
638 centroider = algorithmsLib.MeasureSourcesBuilder().addAlgorithm(control).build(schema)
640 sdssShape = algorithmsLib.SdssShapeControl()
641 shaper = algorithmsLib.MeasureSourcesBuilder().addAlgorithm(sdssShape).build(schema)
643 table = afwTable.SourceTable.make(schema)
645 table.defineCentroid(control.name)
646 table.defineShape(sdssShape.name)
651 if stampSize <= w
and stampSize <= h:
659 x = int(ix*(width-1)/(nx-1)) + x0
660 y = int(iy*(height-1)/(ny-1)) + y0
666 im = im.Factory(im, bbox)
667 lab =
"PSF(%d,%d)" % (x, y)
if False else ""
671 w, h = im.getWidth(), im.getHeight()
673 src = table.makeRecord()
675 src.setFootprint(foot)
677 centroider.apply(src, exp, cen)
678 centers.append((src.getX() - im.getX0(), src.getY() - im.getY0()))
680 shaper.apply(src, exp, cen)
681 shapes.append((src.getIxx(), src.getIxy(), src.getIyy()))
683 mos.makeMosaic(frame=frame, title=title
if title
else "Model Psf", mode=nx)
685 if centers
and frame
is not None:
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()
692 ds9.dot(
"+", xc, yc, ctype=ds9.BLUE, frame=frame)
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)
701 def showPsfResiduals(exposure, sourceSet, magType="psf", scale=10, frame=None, showAmps=False):
702 mimIn = exposure.getMaskedImage()
703 mimIn = mimIn.Factory(mimIn,
True)
705 psf = exposure.getPsf()
706 psfWidth, psfHeight = psf.getLocalKernel().getDimensions()
710 w, h = int(mimIn.getWidth()/scale), int(mimIn.getHeight()/scale)
712 im = mimIn.Factory(w + psfWidth, h + psfHeight)
716 x, y = s.getX(), s.getY()
718 sx, sy = int(x/scale + 0.5), int(y/scale + 0.5)
721 sim = smim.getImage()
726 elif magType ==
"model":
727 flux = s.getModelFlux()
728 elif magType ==
"psf":
729 flux = s.getPsfFlux()
731 raise RuntimeError(
"Unknown flux type %s" % magType)
733 algorithmsLib.subtractPsf(psf, mimIn, x, y, flux)
738 expIm = mimIn.getImage().Factory(mimIn.getImage(),
740 int(y) - psfHeight//2),
746 cenPos.append([x - expIm.getX0() + sx, y - expIm.getY0() + sy])
750 if frame
is not None:
751 ds9.mtv(im, frame=frame)
752 with ds9.Buffering():
754 ds9.dot(
"+", x, y, frame=frame)
760 xc = numpy.array([0, 1, 1, 0, 0])
761 yc = numpy.array([0, 0, 1, 1, 0])
764 for k
in range(len(xc)):
765 corners.append([psfWidth//2 + w/nx*(i + xc[k]), psfHeight//2 + h/ny*(j + yc[k])])
767 ds9.line(corners, frame=frame)
774 """Write the contents of a SpatialCellSet to a many-MEF fits file"""
777 for cell
in psfCellSet.getCellList():
778 for cand
in cell.begin(
False):
779 cand = algorithmsLib.cast_PsfCandidateF(cand)
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())
795 im.writeFits(fileName, md, mode)
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())
std::pair< int, double > positionToIndex(double const pos, bool)
Convert image position to index (nearest integer and fractional parts)
An integer coordinate rectangle.
A kernel that is a linear combination of fixed basis kernels.
void randomGaussianImage(ImageT *image, Random &rand)
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.
Class for storing generic metadata.
Statistics makeStatistics(afwImage::Mask< afwImage::MaskPixel > const &msk, int const flags, StatisticsControl const &sctrl)
Specialization to handle Masks.
Exposure< ImagePixelT, MaskPixelT, VariancePixelT >::Ptr makeExposure(MaskedImage< ImagePixelT, MaskPixelT, VariancePixelT > &mimage, boost::shared_ptr< Wcs const > wcs=boost::shared_ptr< Wcs const >())
std::vector< boost::shared_ptr< Kernel > > KernelList