22 """Support utilities for Measuring sources""" 39 from .
import subtractPsf, fitKernelParamsToImage
44 afwDisplay.setDefaultMaskTransparency(75)
49 objId = int((oid & 0xffff) - 1)
52 return dict(objId=objId)
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""" 61 display = afwDisplay.Display()
62 with display.Buffering():
64 xc, yc = s.getXAstrom() - xy0[0], s.getYAstrom() - xy0[1]
67 display.dot(str(
splitId(s.getId(),
True)[
"objId"]), xc, yc, ctype=ctype, size=size)
69 display.dot(symb, xc, yc, ctype=ctype, size=size)
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. 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 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)
96 goodies = ctypeBad
is None 97 for cand
in cell.begin(goodies):
101 xc, yc = cand.getXCenter() + origin[0], cand.getYCenter() + origin[1]
107 color = ctypeBad
if cand.isBad()
else ctype
115 display.dot(symb, xc, yc, ctype=ct, size=size)
117 source = cand.getSource()
120 rchi2 = cand.getChi2()
123 display.dot(
"%d %.1f" % (
splitId(source.getId(),
True)[
"objId"], rchi2),
124 xc - size, yc - size - 4, ctype=color, size=2)
127 display.dot(
"%.2f %.2f %.2f" % (source.getIxx(), source.getIxy(), source.getIyy()),
128 xc-size, yc + size + 4, ctype=color, size=size)
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. 136 If psf is provided include PSF model and residuals; if normalize is true normalize the PSFs 139 If chi is True, generate a plot of residuals/sqrt(variance), i.e. chi 141 If fitBasisComponents is true, also find the best linear combination of the PSF's components 145 display = afwDisplay.Display()
148 if variance
is not None:
153 mos = displayUtils.Mosaic()
155 candidateCenters = []
156 candidateCentersBad = []
159 for cell
in psfCellSet.getCellList():
160 for cand
in cell.begin(
False):
161 rchi2 = cand.getChi2()
165 if not showBadCandidates
and cand.isBad():
169 im_resid = displayUtils.Mosaic(gutter=0, background=-5, mode=
"x")
172 im = cand.getMaskedImage()
173 xc, yc = cand.getXCenter(), cand.getYCenter()
175 margin = 0
if True else 5
176 w, h = im.getDimensions()
180 bim = im.Factory(w + 2*margin, h + 2*margin)
182 stdev = numpy.sqrt(afwMath.makeStatistics(im.getVariance(), afwMath.MEAN).getValue())
183 afwMath.randomGaussianImage(bim.getImage(), afwMath.Random())
184 bim.getVariance().set(stdev**2)
191 im = im.Factory(im,
True)
192 im.setXY0(cand.getMaskedImage().getXY0())
197 im_resid.append(im.Factory(im,
True))
201 psfIm = mi.getImage()
202 config = measBase.SingleFrameMeasurementTask.ConfigClass()
203 config.slots.centroid =
"base_SdssCentroid" 205 schema = afwTable.SourceTable.makeMinimalSchema()
206 measureSources = measBase.SingleFrameMeasurementTask(schema, config=config)
207 catalog = afwTable.SourceCatalog(schema)
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)
216 exp = afwImage.makeExposure(mi)
219 footprintSet = afwDet.FootprintSet(mi,
220 afwDet.Threshold(0.5*numpy.max(psfIm.getArray())),
222 footprintSet.makeSources(catalog)
224 if len(catalog) == 0:
225 raise RuntimeError(
"Failed to detect any objects")
227 measureSources.run(catalog, exp)
228 if len(catalog) == 1:
232 for i, s
in enumerate(catalog):
233 d = numpy.hypot(xc - s.getX(), yc - s.getY())
234 if i == 0
or d < dmin:
236 xc, yc = source.getCentroid()
246 resid = resid.getImage()
247 var = im.getVariance()
248 var = var.Factory(var,
True)
249 numpy.sqrt(var.getArray(), var.getArray())
252 im_resid.append(resid)
255 if fitBasisComponents:
256 im = cand.getMaskedImage()
258 im = im.Factory(im,
True)
259 im.setXY0(cand.getMaskedImage().getXY0())
262 noSpatialKernel = psf.getKernel()
264 noSpatialKernel =
None 270 kernels = afwMath.KernelList(fit[1])
271 outputKernel = afwMath.LinearCombinationKernel(kernels, params)
273 outImage = afwImage.ImageD(outputKernel.getDimensions())
274 outputKernel.computeImage(outImage,
False)
276 im -= outImage.convertF()
280 bim = im.Factory(w + 2*margin, h + 2*margin)
281 afwMath.randomGaussianImage(bim.getImage(), afwMath.Random())
284 bim.assign(resid, bbox)
288 resid = resid.getImage()
291 im_resid.append(resid)
293 im = im_resid.makeMosaic()
295 im = cand.getMaskedImage()
298 im /= afwMath.makeStatistics(im, afwMath.MAX).getValue()
300 objId =
splitId(cand.getSource().getId(),
True)[
"objId"]
302 lab =
"%d chi^2 %.1f" % (objId, rchi2)
303 ctype = afwDisplay.RED
if cand.isBad()
else afwDisplay.GREEN
305 lab =
"%d flux %8.3g" % (objId, cand.getSource().getPsfInstFlux())
306 ctype = afwDisplay.GREEN
308 mos.append(im, lab, ctype)
310 if False and numpy.isnan(rchi2):
311 display.mtv(cand.getMaskedImage().getImage(), title=
"showPsfCandidates: candidate")
312 print(
"amp", cand.getAmplitude())
314 im = cand.getMaskedImage()
315 center = (candidateIndex, xc - im.getX0(), yc - im.getY0())
318 candidateCentersBad.append(center)
320 candidateCenters.append(center)
323 title =
"chi(Psf fit)" 325 title =
"Stars & residuals" 326 mosaicImage = mos.makeMosaic(display=display, title=title)
328 with display.Buffering():
329 for centers, color
in ((candidateCenters, afwDisplay.GREEN), (candidateCentersBad, afwDisplay.RED)):
331 bbox = mos.getBBox(cen[0])
332 display.dot(
"+", cen[1] + bbox.getMinX(), cen[2] + bbox.getMinY(), ctype=color)
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. 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)') 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 369 import matplotlib.pyplot
as plt
370 except ImportError
as e:
371 log.warn(
"Unable to import matplotlib: %s", e)
377 except AttributeError:
378 fig.__show = fig.show
385 fig.show = types.MethodType(myShow, fig)
394 Callback to draw the panel borders when the plots are drawn to the canvas 396 if panelBorderWeight <= 0:
399 for p
in axes.keys():
402 bboxes.append(ax.bbox.union([label.get_window_extent()
for label
in 403 ax.get_xticklabels() + ax.get_yticklabels()]))
410 bbox = ax.bbox.union(bboxes)
412 xy0, xy1 = ax.transData.inverted().transform(bbox)
415 w, h = x1 - x0, y1 - y0
424 rec = ax.add_patch(plt.Rectangle((x0, y0), w, h, fill=
False,
425 lw=panelBorderWeight, edgecolor=panelColor))
426 rec.set_clip_on(
False)
430 fig.canvas.mpl_connect(
'draw_event', on_draw)
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)
441 for panel
in range(Nx*Ny):
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)
456 matchKernelAmplitudes=False, keepPlots=True):
457 """Plot the PSF spatial model.""" 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)
467 noSpatialKernel = psf.getKernel()
474 for cell
in psfCellSet.getCellList():
475 for cand
in cell.begin(
False):
476 if not showBadCandidates
and cand.isBad():
480 im = cand.getMaskedImage()
488 for p, k
in zip(params, kernels):
489 amp += p * k.getSum()
491 targetFits = badFits
if cand.isBad()
else candFits
492 targetPos = badPos
if cand.isBad()
else candPos
493 targetAmps = badAmps
if cand.isBad()
else candAmps
495 targetFits.append([x / amp
for x
in params])
496 targetPos.append(candCenter)
497 targetAmps.append(amp)
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)
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)
508 xRange = numpy.linspace(0, exposure.getWidth(), num=numSample)
509 yRange = numpy.linspace(0, exposure.getHeight(), num=numSample)
511 kernel = psf.getKernel()
512 nKernelComponents = kernel.getNKernelParameters()
516 nPanelX = int(math.sqrt(nKernelComponents))
517 nPanelY = nKernelComponents//nPanelX
518 while nPanelY*nPanelX < nKernelComponents:
524 fig.canvas._tkcanvas._root().lift()
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)
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])
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)
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)
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+')
560 ax.plot(yBad, dfBad,
'r+')
562 ax.set_title(
'Residuals(y)')
566 if matchKernelAmplitudes
and k == 0:
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])
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+')
585 ax.plot(xBad, dfBad,
'r+')
587 ax.set_title(
'K%d Residuals(x)' % k)
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')
599 calib = exposure.getCalib()
600 if calib.getFluxMag0()[0] <= 0:
601 calib = type(calib)()
602 calib.setFluxMag0(1.0)
605 ampMag = [calib.getMagnitude(candAmp)
for candAmp
in candAmps]
606 ax.plot(ampMag, zGood[:, k],
'b+')
608 badAmpMag = [calib.getMagnitude(badAmp)
for badAmp
in badAmps]
609 ax.plot(badAmpMag, zBad[:, k],
'r+')
611 ax.set_title(
'Flux variation')
616 if keepPlots
and not keptPlots:
619 print(
"%s: Please close plots when done." % __name__)
624 print(
"Plots closed, exiting...")
626 atexit.register(show)
630 def showPsf(psf, eigenValues=None, XY=None, normalize=True, display=None):
631 """Display a PSF's eigen images 633 If normalize is True, set the largest absolute value of each eigenimage to 1.0 (n.b. sum == 0.0 for i > 0) 639 coeffs = psf.getLocalKernel(
lsst.geom.PointD(XY[0], XY[1])).getKernelParameters()
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)
648 im /= numpy.max(numpy.abs(im.getArray()))
651 mos.append(im,
"%g" % (coeffs[i]/coeffs[0]))
656 display = afwDisplay.Display()
657 mos.makeMosaic(display=display, title=
"Kernel Basis Functions")
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) 667 If stampSize is > 0, the psf images will be trimmed to stampSize*stampSize 672 showEllipticity =
True 673 scale = 2*math.log(2)
675 mos = displayUtils.Mosaic()
678 width, height = exposure.getWidth(), exposure.getHeight()
679 x0, y0 = exposure.getXY0()
681 psf = exposure.getPsf()
682 except AttributeError:
684 width, height = exposure[0], exposure[1]
687 raise RuntimeError(
"Unable to extract width/height from object of type %s" % type(exposure))
690 ny = int(nx*float(height)/width + 0.5)
694 centroidName =
"SdssCentroid" 695 shapeName =
"base_SdssShape" 697 schema = afwTable.SourceTable.makeMinimalSchema()
698 schema.getAliasMap().set(
"slot_Centroid", centroidName)
699 schema.getAliasMap().set(
"slot_Centroid_flag", centroidName+
"_flag")
701 control = measBase.SdssCentroidControl()
702 centroider = measBase.SdssCentroidAlgorithm(control, centroidName, schema)
704 sdssShape = measBase.SdssShapeControl()
705 shaper = measBase.SdssShapeAlgorithm(sdssShape, shapeName, schema)
706 table = afwTable.SourceTable.make(schema)
708 table.defineCentroid(centroidName)
709 table.defineShape(shapeName)
714 if stampSize <= w
and stampSize <= h:
722 x = int(ix*(width-1)/(nx-1)) + x0
723 y = int(iy*(height-1)/(ny-1)) + y0
729 im = im.Factory(im, bbox)
730 lab =
"PSF(%d,%d)" % (x, y)
if False else "" 733 exp = afwImage.makeExposure(afwImage.makeMaskedImage(im))
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)
745 centroider.measure(src, exp)
746 centers.append((src.getX() - im.getX0(), src.getY() - im.getY0()))
748 shaper.measure(src, exp)
749 shapes.append((src.getIxx(), src.getIxy(), src.getIyy()))
754 display = afwDisplay.Display()
755 mos.makeMosaic(display=display, title=title
if title
else "Model Psf", mode=nx)
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()
763 display.dot(
"+", xc, yc, ctype=afwDisplay.BLUE)
766 ixx, ixy, iyy = shape
770 display.dot(
"@:%g,%g,%g" % (ixx, ixy, iyy), xc, yc, ctype=afwDisplay.RED)
776 mimIn = exposure.getMaskedImage()
777 mimIn = mimIn.Factory(mimIn,
True)
779 psf = exposure.getPsf()
780 psfWidth, psfHeight = psf.getLocalKernel().getDimensions()
784 w, h = int(mimIn.getWidth()/scale), int(mimIn.getHeight()/scale)
786 im = mimIn.Factory(w + psfWidth, h + psfHeight)
790 x, y = s.getX(), s.getY()
792 sx, sy = int(x/scale + 0.5), int(y/scale + 0.5)
796 sim = smim.getImage()
800 flux = s.getApInstFlux()
801 elif magType ==
"model":
802 flux = s.getModelInstFlux()
803 elif magType ==
"psf":
804 flux = s.getPsfInstFlux()
806 raise RuntimeError(
"Unknown flux type %s" % magType)
809 except Exception
as e:
813 expIm = mimIn.getImage().Factory(mimIn.getImage(),
815 int(y) - psfHeight//2),
818 except pexExcept.Exception:
821 cenPos.append([x - expIm.getX0() + sx, y - expIm.getY0() + sy])
826 display = afwDisplay.Display()
827 display.mtv(im, title=
"showPsfResiduals: image")
828 with display.Buffering():
830 display.dot(
"+", x, y)
836 """Write the contents of a SpatialCellSet to a many-MEF fits file""" 839 for cell
in psfCellSet.getCellList():
840 for cand
in cell.begin(
False):
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")
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())
855 im.writeFits(fileName, md, mode)
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)
def showPsfCandidates(exposure, psfCellSet, psf=None, display=None, normalize=True, showBadCandidates=True, fitBasisComponents=False, variance=None, chi=None)
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')
def showPsfMosaic(exposure, psf=None, nx=7, ny=None, showCenter=True, showEllipticity=False, showFwhm=False, stampSize=0, display=None, title=None)
def saveSpatialCellSet(psfCellSet, fileName="foo.fits", display=None)
def showSourceSet(sSet, xy0=(0, 0), display=None, ctype=afwDisplay.GREEN, symb="+", size=2)
def splitId(oid, asDict=True)
def showPsfResiduals(exposure, sourceSet, magType="psf", scale=10, display=None)
static Log getLogger(std::string const &loggername)
def showPsf(psf, eigenValues=None, XY=None, normalize=True, display=None)
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)