22 """Support utilities for Measuring sources"""
39 from .
import subtractPsf, fitKernelParamsToImage
43 afwDisplay.setDefaultMaskTransparency(75)
48 objId = int((oid & 0xffff) - 1)
51 return dict(objId=objId)
56 def showSourceSet(sSet, xy0=(0, 0), display=
None, ctype=afwDisplay.GREEN, symb=
"+", size=2):
57 """Draw the (XAstrom, YAstrom) positions of a set of Sources. Image has the given XY0"""
60 display = afwDisplay.Display()
61 with display.Buffering():
63 xc, yc = s.getXAstrom() - xy0[0], s.getYAstrom() - xy0[1]
66 display.dot(str(
splitId(s.getId(),
True)[
"objId"]), xc, yc, ctype=ctype, size=size)
68 display.dot(symb, xc, yc, ctype=ctype, size=size)
76 symb=None, ctype=None, ctypeUnused=None, ctypeBad=None, size=2, display=None):
77 """Show the SpatialCells.
79 If symb is something that afwDisplay.Display.dot() understands (e.g. "o"),
80 the top nMaxPerCell candidates will be indicated with that symbol, using
85 display = afwDisplay.Display()
86 with display.Buffering():
87 origin = [-exposure.getMaskedImage().getX0(), -exposure.getMaskedImage().getY0()]
88 for cell
in psfCellSet.getCellList():
89 displayUtils.drawBBox(cell.getBBox(), origin=origin, display=display)
95 goodies = ctypeBad
is None
96 for cand
in cell.begin(goodies):
100 xc, yc = cand.getXCenter() + origin[0], cand.getYCenter() + origin[1]
106 color = ctypeBad
if cand.isBad()
else ctype
114 display.dot(symb, xc, yc, ctype=ct, size=size)
116 source = cand.getSource()
119 rchi2 = cand.getChi2()
122 display.dot(
"%d %.1f" % (
splitId(source.getId(),
True)[
"objId"], rchi2),
123 xc - size, yc - size - 4, ctype=color, size=2)
126 display.dot(
"%.2f %.2f %.2f" % (source.getIxx(), source.getIxy(), source.getIyy()),
127 xc-size, yc + size + 4, ctype=color, size=size)
131 def showPsfCandidates(exposure, psfCellSet, psf=None, display=None, normalize=True, showBadCandidates=True,
132 fitBasisComponents=False, variance=None, chi=None):
133 """Display the PSF candidates.
135 If psf is provided include PSF model and residuals; if normalize is true normalize the PSFs
138 If chi is True, generate a plot of residuals/sqrt(variance), i.e. chi
140 If fitBasisComponents is true, also find the best linear combination of the PSF's components
144 display = afwDisplay.Display()
147 if variance
is not None:
152 mos = displayUtils.Mosaic()
154 candidateCenters = []
155 candidateCentersBad = []
158 for cell
in psfCellSet.getCellList():
159 for cand
in cell.begin(
False):
160 rchi2 = cand.getChi2()
164 if not showBadCandidates
and cand.isBad():
168 im_resid = displayUtils.Mosaic(gutter=0, background=-5, mode=
"x")
171 im = cand.getMaskedImage()
172 xc, yc = cand.getXCenter(), cand.getYCenter()
174 margin = 0
if True else 5
175 w, h = im.getDimensions()
179 bim = im.Factory(w + 2*margin, h + 2*margin)
181 stdev = numpy.sqrt(afwMath.makeStatistics(im.getVariance(), afwMath.MEAN).getValue())
182 afwMath.randomGaussianImage(bim.getImage(), afwMath.Random())
183 bim.getVariance().set(stdev**2)
190 im = im.Factory(im,
True)
191 im.setXY0(cand.getMaskedImage().getXY0())
196 im_resid.append(im.Factory(im,
True))
200 psfIm = mi.getImage()
201 config = measBase.SingleFrameMeasurementTask.ConfigClass()
202 config.slots.centroid =
"base_SdssCentroid"
204 schema = afwTable.SourceTable.makeMinimalSchema()
205 measureSources = measBase.SingleFrameMeasurementTask(schema, config=config)
206 catalog = afwTable.SourceCatalog(schema)
209 miBig = mi.Factory(im.getWidth() + 2*extra, im.getHeight() + 2*extra)
210 miBig[extra:-extra, extra:-extra, afwImage.LOCAL] = mi
211 miBig.setXY0(mi.getX0() - extra, mi.getY0() - extra)
215 exp = afwImage.makeExposure(mi)
218 footprintSet = afwDet.FootprintSet(mi,
219 afwDet.Threshold(0.5*numpy.max(psfIm.getArray())),
221 footprintSet.makeSources(catalog)
223 if len(catalog) == 0:
224 raise RuntimeError(
"Failed to detect any objects")
226 measureSources.run(catalog, exp)
227 if len(catalog) == 1:
231 for i, s
in enumerate(catalog):
232 d = numpy.hypot(xc - s.getX(), yc - s.getY())
233 if i == 0
or d < dmin:
235 xc, yc = source.getCentroid()
245 resid = resid.getImage()
246 var = im.getVariance()
247 var = var.Factory(var,
True)
248 numpy.sqrt(var.getArray(), var.getArray())
251 im_resid.append(resid)
254 if fitBasisComponents:
255 im = cand.getMaskedImage()
257 im = im.Factory(im,
True)
258 im.setXY0(cand.getMaskedImage().getXY0())
261 noSpatialKernel = psf.getKernel()
263 noSpatialKernel =
None
269 kernels = afwMath.KernelList(fit[1])
270 outputKernel = afwMath.LinearCombinationKernel(kernels, params)
272 outImage = afwImage.ImageD(outputKernel.getDimensions())
273 outputKernel.computeImage(outImage,
False)
275 im -= outImage.convertF()
279 bim = im.Factory(w + 2*margin, h + 2*margin)
280 afwMath.randomGaussianImage(bim.getImage(), afwMath.Random())
283 bim.assign(resid, bbox)
287 resid = resid.getImage()
290 im_resid.append(resid)
292 im = im_resid.makeMosaic()
294 im = cand.getMaskedImage()
297 im /= afwMath.makeStatistics(im, afwMath.MAX).getValue()
299 objId =
splitId(cand.getSource().getId(),
True)[
"objId"]
301 lab =
"%d chi^2 %.1f" % (objId, rchi2)
302 ctype = afwDisplay.RED
if cand.isBad()
else afwDisplay.GREEN
304 lab =
"%d flux %8.3g" % (objId, cand.getSource().getPsfInstFlux())
305 ctype = afwDisplay.GREEN
307 mos.append(im, lab, ctype)
309 if False and numpy.isnan(rchi2):
310 display.mtv(cand.getMaskedImage().getImage(), title=
"showPsfCandidates: candidate")
311 print(
"amp", cand.getAmplitude())
313 im = cand.getMaskedImage()
314 center = (candidateIndex, xc - im.getX0(), yc - im.getY0())
317 candidateCentersBad.append(center)
319 candidateCenters.append(center)
322 title =
"chi(Psf fit)"
324 title =
"Stars & residuals"
325 mosaicImage = mos.makeMosaic(display=display, title=title)
327 with display.Buffering():
328 for centers, color
in ((candidateCenters, afwDisplay.GREEN), (candidateCentersBad, afwDisplay.RED)):
330 bbox = mos.getBBox(cen[0])
331 display.dot(
"+", cen[1] + bbox.getMinX(), cen[2] + bbox.getMinY(), ctype=color)
336 def makeSubplots(fig, nx=2, ny=2, Nx=1, Ny=1, plottingArea=(0.1, 0.1, 0.85, 0.80),
337 pxgutter=0.05, pygutter=0.05, xgutter=0.04, ygutter=0.04,
338 headroom=0.0, panelBorderWeight=0, panelColor=
'black'):
339 """Return a generator of a set of subplots, a set of Nx*Ny panels of nx*ny plots. Each panel is fully
340 filled by row (starting in the bottom left) before the next panel is started. If panelBorderWidth is
341 greater than zero a border is drawn around each panel, adjusted to enclose the axis labels.
344 subplots = makeSubplots(fig, 2, 2, Nx=1, Ny=1, panelColor='k')
345 ax = subplots.next(); ax.text(0.3, 0.5, '[0, 0] (0,0)')
346 ax = subplots.next(); ax.text(0.3, 0.5, '[0, 0] (1,0)')
347 ax = subplots.next(); ax.text(0.3, 0.5, '[0, 0] (0,1)')
348 ax = subplots.next(); ax.text(0.3, 0.5, '[0, 0] (1,1)')
353 fig : `matplotlib.pyplot.figure`
354 The matplotlib figure to draw
356 The number of plots in each row of each panel
358 The number of plots in each column of each panel
360 The number of panels in each row of the figure
362 The number of panels in each column of the figure
363 plottingArea : `tuple`
364 (x0, y0, x1, y1) for the part of the figure containing all the panels
366 Spacing between columns of panels in units of (x1 - x0)
368 Spacing between rows of panels in units of (y1 - y0)
370 Spacing between columns of plots within a panel in units of (x1 - x0)
372 Spacing between rows of plots within a panel in units of (y1 - y0)
374 Extra spacing above each plot for e.g. a title
375 panelBorderWeight : `int`
376 Width of border drawn around panels
378 Colour of border around panels
383 import matplotlib.pyplot
as plt
384 except ImportError
as e:
385 log.warning(
"Unable to import matplotlib: %s", e)
391 except AttributeError:
392 fig.__show = fig.show
399 fig.show = types.MethodType(myShow, fig)
408 Callback to draw the panel borders when the plots are drawn to the canvas
410 if panelBorderWeight <= 0:
413 for p
in axes.keys():
416 bboxes.append(ax.bbox.union([label.get_window_extent()
for label
in
417 ax.get_xticklabels() + ax.get_yticklabels()]))
424 bbox = ax.bbox.union(bboxes)
426 xy0, xy1 = ax.transData.inverted().transform(bbox)
429 w, h = x1 - x0, y1 - y0
438 rec = ax.add_patch(plt.Rectangle((x0, y0), w, h, fill=
False,
439 lw=panelBorderWeight, edgecolor=panelColor))
440 rec.set_clip_on(
False)
444 fig.canvas.mpl_connect(
'draw_event', on_draw)
448 x0, y0 = plottingArea[0:2]
449 W, H = plottingArea[2:4]
450 w = (W - (Nx - 1)*pxgutter - (nx*Nx - 1)*xgutter)/float(nx*Nx)
451 h = (H - (Ny - 1)*pygutter - (ny*Ny - 1)*ygutter)/float(ny*Ny)
455 for panel
in range(Nx*Ny):
459 for window
in range(nx*ny):
460 x = nx*px + window%nx
461 y = ny*py + window//nx
462 ax = fig.add_axes((x0 + xgutter + pxgutter + x*w + (px - 1)*pxgutter + (x - 1)*xgutter,
463 y0 + ygutter + pygutter + y*h + (py - 1)*pygutter + (y - 1)*ygutter,
464 w, h), frame_on=
True, facecolor=
'w')
465 axes[panel].append(ax)
470 matchKernelAmplitudes=False, keepPlots=True):
471 """Plot the PSF spatial model."""
475 import matplotlib.pyplot
as plt
476 import matplotlib
as mpl
477 except ImportError
as e:
478 log.warning(
"Unable to import matplotlib: %s", e)
481 noSpatialKernel = psf.getKernel()
488 for cell
in psfCellSet.getCellList():
489 for cand
in cell.begin(
False):
490 if not showBadCandidates
and cand.isBad():
494 im = cand.getMaskedImage()
502 for p, k
in zip(params, kernels):
503 amp += p * k.getSum()
505 targetFits = badFits
if cand.isBad()
else candFits
506 targetPos = badPos
if cand.isBad()
else candPos
507 targetAmps = badAmps
if cand.isBad()
else candAmps
509 targetFits.append([x / amp
for x
in params])
510 targetPos.append(candCenter)
511 targetAmps.append(amp)
513 xGood = numpy.array([pos.getX()
for pos
in candPos]) - exposure.getX0()
514 yGood = numpy.array([pos.getY()
for pos
in candPos]) - exposure.getY0()
515 zGood = numpy.array(candFits)
517 xBad = numpy.array([pos.getX()
for pos
in badPos]) - exposure.getX0()
518 yBad = numpy.array([pos.getY()
for pos
in badPos]) - exposure.getY0()
519 zBad = numpy.array(badFits)
522 xRange = numpy.linspace(0, exposure.getWidth(), num=numSample)
523 yRange = numpy.linspace(0, exposure.getHeight(), num=numSample)
525 kernel = psf.getKernel()
526 nKernelComponents = kernel.getNKernelParameters()
530 nPanelX = int(math.sqrt(nKernelComponents))
531 nPanelY = nKernelComponents//nPanelX
532 while nPanelY*nPanelX < nKernelComponents:
538 fig.canvas._tkcanvas._root().lift()
544 mpl.rcParams[
"figure.titlesize"] =
"x-small"
545 subplots =
makeSubplots(fig, 2, 2, Nx=nPanelX, Ny=nPanelY, xgutter=0.06, ygutter=0.06, pygutter=0.04)
547 for k
in range(nKernelComponents):
548 func = kernel.getSpatialFunction(k)
549 dfGood = zGood[:, k] - numpy.array([func(pos.getX(), pos.getY())
for pos
in candPos])
553 dfBad = zBad[:, k] - numpy.array([func(pos.getX(), pos.getY())
for pos
in badPos])
554 yMin = min([yMin, dfBad.min()])
555 yMax = max([yMax, dfBad.max()])
556 yMin -= 0.05 * (yMax - yMin)
557 yMax += 0.05 * (yMax - yMin)
562 fRange = numpy.ndarray((len(xRange), len(yRange)))
563 for j, yVal
in enumerate(yRange):
564 for i, xVal
in enumerate(xRange):
565 fRange[j][i] = func(xVal, yVal)
569 ax.set_autoscale_on(
False)
570 ax.set_xbound(lower=0, upper=exposure.getHeight())
571 ax.set_ybound(lower=yMin, upper=yMax)
572 ax.plot(yGood, dfGood,
'b+')
574 ax.plot(yBad, dfBad,
'r+')
576 ax.set_title(
'Residuals(y)')
580 if matchKernelAmplitudes
and k == 0:
587 norm = mpl.colors.Normalize(vmin=vmin, vmax=vmax)
588 im = ax.imshow(fRange, aspect=
'auto', origin=
"lower", norm=norm,
589 extent=[0, exposure.getWidth()-1, 0, exposure.getHeight()-1])
590 ax.set_title(
'Spatial poly')
591 plt.colorbar(im, orientation=
'horizontal', ticks=[vmin, vmax])
594 ax.set_autoscale_on(
False)
595 ax.set_xbound(lower=0, upper=exposure.getWidth())
596 ax.set_ybound(lower=yMin, upper=yMax)
597 ax.plot(xGood, dfGood,
'b+')
599 ax.plot(xBad, dfBad,
'r+')
601 ax.set_title(
'K%d Residuals(x)' % k)
605 photoCalib = exposure.getPhotoCalib()
607 if photoCalib.getCalibrationMean() <= 0:
608 photoCalib = afwImage.PhotoCalib(1.0)
610 ampMag = [photoCalib.instFluxToMagnitude(candAmp)
for candAmp
in candAmps]
611 ax.plot(ampMag, zGood[:, k],
'b+')
613 badAmpMag = [photoCalib.instFluxToMagnitude(badAmp)
for badAmp
in badAmps]
614 ax.plot(badAmpMag, zBad[:, k],
'r+')
616 ax.set_title(
'Flux variation')
621 if keepPlots
and not keptPlots:
624 print(
"%s: Please close plots when done." % __name__)
629 print(
"Plots closed, exiting...")
631 atexit.register(show)
635 def showPsf(psf, eigenValues=None, XY=None, normalize=True, display=None):
636 """Display a PSF's eigen images
638 If normalize is True, set the largest absolute value of each eigenimage to 1.0 (n.b. sum == 0.0 for i > 0)
644 coeffs = psf.getLocalKernel(
lsst.geom.PointD(XY[0], XY[1])).getKernelParameters()
648 mos = displayUtils.Mosaic(gutter=2, background=-0.1)
649 for i, k
in enumerate(psf.getKernel().getKernelList()):
650 im = afwImage.ImageD(k.getDimensions())
651 k.computeImage(im,
False)
653 im /= numpy.max(numpy.abs(im.getArray()))
656 mos.append(im,
"%g" % (coeffs[i]/coeffs[0]))
661 display = afwDisplay.Display()
662 mos.makeMosaic(display=display, title=
"Kernel Basis Functions")
667 def showPsfMosaic(exposure, psf=None, nx=7, ny=None, showCenter=True, showEllipticity=False,
668 showFwhm=False, stampSize=0, display=None, title=None):
669 """Show a mosaic of Psf images. exposure may be an Exposure (optionally with PSF),
670 or a tuple (width, height)
672 If stampSize is > 0, the psf images will be trimmed to stampSize*stampSize
677 showEllipticity =
True
678 scale = 2*math.log(2)
680 mos = displayUtils.Mosaic()
683 width, height = exposure.getWidth(), exposure.getHeight()
684 x0, y0 = exposure.getXY0()
686 psf = exposure.getPsf()
687 except AttributeError:
689 width, height = exposure[0], exposure[1]
692 raise RuntimeError(
"Unable to extract width/height from object of type %s" % type(exposure))
695 ny = int(nx*float(height)/width + 0.5)
699 centroidName =
"SdssCentroid"
700 shapeName =
"base_SdssShape"
702 schema = afwTable.SourceTable.makeMinimalSchema()
703 schema.getAliasMap().set(
"slot_Centroid", centroidName)
704 schema.getAliasMap().set(
"slot_Centroid_flag", centroidName+
"_flag")
706 control = measBase.SdssCentroidControl()
707 centroider = measBase.SdssCentroidAlgorithm(control, centroidName, schema)
709 sdssShape = measBase.SdssShapeControl()
710 shaper = measBase.SdssShapeAlgorithm(sdssShape, shapeName, schema)
711 table = afwTable.SourceTable.make(schema)
713 table.defineCentroid(centroidName)
714 table.defineShape(shapeName)
719 if stampSize <= w
and stampSize <= h:
727 x = int(ix*(width-1)/(nx-1)) + x0
728 y = int(iy*(height-1)/(ny-1)) + y0
734 im = im.Factory(im, bbox)
735 lab =
"PSF(%d,%d)" % (x, y)
if False else ""
738 exp = afwImage.makeExposure(afwImage.makeMaskedImage(im))
740 w, h = im.getWidth(), im.getHeight()
741 centerX = im.getX0() + w//2
742 centerY = im.getY0() + h//2
743 src = table.makeRecord()
744 spans = afwGeom.SpanSet(exp.getBBox())
745 foot = afwDet.Footprint(spans)
746 foot.addPeak(centerX, centerY, 1)
747 src.setFootprint(foot)
750 centroider.measure(src, exp)
751 centers.append((src.getX() - im.getX0(), src.getY() - im.getY0()))
753 shaper.measure(src, exp)
754 shapes.append((src.getIxx(), src.getIxy(), src.getIyy()))
759 display = afwDisplay.Display()
760 mos.makeMosaic(display=display, title=title
if title
else "Model Psf", mode=nx)
762 if centers
and display:
763 with display.Buffering():
764 for i, (cen, shape)
in enumerate(zip(centers, shapes)):
765 bbox = mos.getBBox(i)
766 xc, yc = cen[0] + bbox.getMinX(), cen[1] + bbox.getMinY()
768 display.dot(
"+", xc, yc, ctype=afwDisplay.BLUE)
771 ixx, ixy, iyy = shape
775 display.dot(
"@:%g,%g,%g" % (ixx, ixy, iyy), xc, yc, ctype=afwDisplay.RED)
781 mimIn = exposure.getMaskedImage()
782 mimIn = mimIn.Factory(mimIn,
True)
784 psf = exposure.getPsf()
785 psfWidth, psfHeight = psf.getLocalKernel().getDimensions()
789 w, h = int(mimIn.getWidth()/scale), int(mimIn.getHeight()/scale)
791 im = mimIn.Factory(w + psfWidth, h + psfHeight)
795 x, y = s.getX(), s.getY()
797 sx, sy = int(x/scale + 0.5), int(y/scale + 0.5)
801 sim = smim.getImage()
805 flux = s.getApInstFlux()
806 elif magType ==
"model":
807 flux = s.getModelInstFlux()
808 elif magType ==
"psf":
809 flux = s.getPsfInstFlux()
811 raise RuntimeError(
"Unknown flux type %s" % magType)
814 except Exception
as e:
818 expIm = mimIn.getImage().Factory(mimIn.getImage(),
820 int(y) - psfHeight//2),
823 except pexExcept.Exception:
826 cenPos.append([x - expIm.getX0() + sx, y - expIm.getY0() + sy])
831 display = afwDisplay.Display()
832 display.mtv(im, title=
"showPsfResiduals: image")
833 with display.Buffering():
835 display.dot(
"+", x, y)
841 """Write the contents of a SpatialCellSet to a many-MEF fits file"""
844 for cell
in psfCellSet.getCellList():
845 for cand
in cell.begin(
False):
846 dx = afwImage.positionToIndex(cand.getXCenter(),
True)[1]
847 dy = afwImage.positionToIndex(cand.getYCenter(),
True)[1]
848 im = afwMath.offsetImage(cand.getMaskedImage(), -dx, -dy,
"lanczos5")
850 md = dafBase.PropertySet()
851 md.set(
"CELL", cell.getLabel())
852 md.set(
"ID", cand.getId())
853 md.set(
"XCENTER", cand.getXCenter())
854 md.set(
"YCENTER", cand.getYCenter())
855 md.set(
"BAD", cand.isBad())
856 md.set(
"AMPL", cand.getAmplitude())
857 md.set(
"FLUX", cand.getSource().getPsfInstFlux())
858 md.set(
"CHI2", cand.getSource().getChi2())
860 im.writeFits(fileName, md, mode)
864 display.mtv(im, title=
"saveSpatialCellSet: image")
static Log getLogger(std::string const &loggername)
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 showPsfSpatialCells(exposure, psfCellSet, nMaxPerCell=-1, showChi2=False, showMoments=False, symb=None, ctype=None, ctypeUnused=None, ctypeBad=None, size=2, display=None)
def showPsf(psf, eigenValues=None, XY=None, normalize=True, display=None)
def plotPsfSpatialModel(exposure, psf, psfCellSet, showBadCandidates=True, numSample=128, matchKernelAmplitudes=False, keepPlots=True)
def splitId(oid, asDict=True)
def showPsfResiduals(exposure, sourceSet, magType="psf", scale=10, display=None)
def saveSpatialCellSet(psfCellSet, fileName="foo.fits", display=None)
def showSourceSet(sSet, xy0=(0, 0), display=None, ctype=afwDisplay.GREEN, symb="+", size=2)
def showPsfCandidates(exposure, psfCellSet, psf=None, display=None, normalize=True, showBadCandidates=True, fitBasisComponents=False, variance=None, chi=None)
def showPsfMosaic(exposure, psf=None, nx=7, ny=None, showCenter=True, showEllipticity=False, showFwhm=False, stampSize=0, display=None, title=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.
double subtractPsf(afw::detection::Psf const &psf, ImageT *data, double x, double y, double psfFlux=std::numeric_limits< double >::quiet_NaN())