23 """Support utilities for Measuring sources""" 39 from .
import subtractPsf, fitKernelParamsToImage
47 objId = int((oid & 0xffff) - 1)
50 return dict(objId=objId)
55 def showSourceSet(sSet, xy0=(0, 0), frame=0, ctype=ds9.GREEN, symb=
"+", size=2):
56 """Draw the (XAstrom, YAstrom) positions of a set of Sources. Image has the given XY0""" 60 xc, yc = s.getXAstrom() - xy0[0], s.getYAstrom() - xy0[1]
63 ds9.dot(str(
splitId(s.getId(),
True)[
"objId"]), xc, yc, frame=frame, ctype=ctype, size=size)
65 ds9.dot(symb, xc, yc, frame=frame, ctype=ctype, size=size)
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""" 78 origin = [-exposure.getMaskedImage().getX0(), -exposure.getMaskedImage().getY0()]
79 for cell
in psfCellSet.getCellList():
80 displayUtils.drawBBox(cell.getBBox(), origin=origin, frame=frame)
86 goodies = ctypeBad
is None 87 for cand
in cell.begin(goodies):
91 xc, yc = cand.getXCenter() + origin[0], cand.getYCenter() + origin[1]
97 color = ctypeBad
if cand.isBad()
else ctype
105 ds9.dot(symb, xc, yc, frame=frame, ctype=ct, size=size)
107 source = cand.getSource()
110 rchi2 = cand.getChi2()
113 ds9.dot(
"%d %.1f" % (
splitId(source.getId(),
True)[
"objId"], rchi2),
114 xc - size, yc - size - 4, frame=frame, ctype=color, size=2)
117 ds9.dot(
"%.2f %.2f %.2f" % (source.getIxx(), source.getIxy(), source.getIyy()),
118 xc-size, yc + size + 4, frame=frame, ctype=color, size=size)
121 def showPsfCandidates(exposure, psfCellSet, psf=None, frame=None, normalize=True, showBadCandidates=True,
122 fitBasisComponents=False, variance=None, chi=None):
123 """Display the PSF candidates. 125 If psf is provided include PSF model and residuals; if normalize is true normalize the PSFs 128 If chi is True, generate a plot of residuals/sqrt(variance), i.e. chi 130 If fitBasisComponents is true, also find the best linear combination of the PSF's components 134 if variance
is not None:
139 mos = displayUtils.Mosaic()
141 candidateCenters = []
142 candidateCentersBad = []
145 for cell
in psfCellSet.getCellList():
146 for cand
in cell.begin(
False):
147 rchi2 = cand.getChi2()
151 if not showBadCandidates
and cand.isBad():
155 im_resid = displayUtils.Mosaic(gutter=0, background=-5, mode=
"x")
158 im = cand.getMaskedImage()
159 xc, yc = cand.getXCenter(), cand.getYCenter()
161 margin = 0
if True else 5
162 w, h = im.getDimensions()
166 bim = im.Factory(w + 2*margin, h + 2*margin)
170 bim.getVariance().set(stdev**2)
177 im = im.Factory(im,
True)
178 im.setXY0(cand.getMaskedImage().getXY0())
183 im_resid.append(im.Factory(im,
True))
187 psfIm = mi.getImage()
188 config = measBase.SingleFrameMeasurementTask.ConfigClass()
189 config.slots.centroid =
"base_SdssCentroid" 191 schema = afwTable.SourceTable.makeMinimalSchema()
192 measureSources = measBase.SingleFrameMeasurementTask(schema, config=config)
193 catalog = afwTable.SourceCatalog(schema)
196 miBig = mi.Factory(im.getWidth() + 2*extra, im.getHeight() + 2*extra)
197 miBig[extra:-extra, extra:-extra] = mi
198 miBig.setXY0(mi.getX0() - extra, mi.getY0() - extra)
205 footprintSet = afwDet.FootprintSet(mi,
206 afwDet.Threshold(0.5*numpy.max(psfIm.getArray())),
208 footprintSet.makeSources(catalog)
210 if len(catalog) == 0:
211 raise RuntimeError(
"Failed to detect any objects")
213 measureSources.run(catalog, exp)
214 if len(catalog) == 1:
218 for i, s
in enumerate(catalog):
219 d = numpy.hypot(xc - s.getX(), yc - s.getY())
220 if i == 0
or d < dmin:
222 xc, yc = source.getCentroid()
232 resid = resid.getImage()
233 var = im.getVariance()
234 var = var.Factory(var,
True)
235 numpy.sqrt(var.getArray(), var.getArray())
238 im_resid.append(resid)
241 if fitBasisComponents:
242 im = cand.getMaskedImage()
244 im = im.Factory(im,
True)
245 im.setXY0(cand.getMaskedImage().getXY0())
248 noSpatialKernel = psf.getKernel()
250 noSpatialKernel =
None 259 outImage = afwImage.ImageD(outputKernel.getDimensions())
260 outputKernel.computeImage(outImage,
False)
262 im -= outImage.convertF()
266 bim = im.Factory(w + 2*margin, h + 2*margin)
270 bim.assign(resid, bbox)
274 resid = resid.getImage()
277 im_resid.append(resid)
279 im = im_resid.makeMosaic()
281 im = cand.getMaskedImage()
286 objId =
splitId(cand.getSource().getId(),
True)[
"objId"]
288 lab =
"%d chi^2 %.1f" % (objId, rchi2)
289 ctype = ds9.RED
if cand.isBad()
else ds9.GREEN
291 lab =
"%d flux %8.3g" % (objId, cand.getSource().getPsfFlux())
294 mos.append(im, lab, ctype)
296 if False and numpy.isnan(rchi2):
297 ds9.mtv(cand.getMaskedImage().getImage(), title=
"candidate", frame=1)
298 print(
"amp", cand.getAmplitude())
300 im = cand.getMaskedImage()
301 center = (candidateIndex, xc - im.getX0(), yc - im.getY0())
304 candidateCentersBad.append(center)
306 candidateCenters.append(center)
309 title =
"chi(Psf fit)" 311 title =
"Stars & residuals" 312 mosaicImage = mos.makeMosaic(frame=frame, title=title)
314 with ds9.Buffering():
315 for centers, color
in ((candidateCenters, ds9.GREEN), (candidateCentersBad, ds9.RED)):
317 bbox = mos.getBBox(cen[0])
318 ds9.dot(
"+", cen[1] + bbox.getMinX(), cen[2] + bbox.getMinY(), frame=frame, ctype=color)
323 def makeSubplots(fig, nx=2, ny=2, Nx=1, Ny=1, plottingArea=(0.1, 0.1, 0.85, 0.80),
324 pxgutter=0.05, pygutter=0.05, xgutter=0.04, ygutter=0.04,
325 headroom=0.0, panelBorderWeight=0, panelColor=
'black'):
326 """Return a generator of a set of subplots, a set of Nx*Ny panels of nx*ny plots. Each panel is fully 327 filled by row (starting in the bottom left) before the next panel is started. If panelBorderWidth is 328 greater than zero a border is drawn around each panel, adjusted to enclose the axis labels. 331 subplots = makeSubplots(fig, 2, 2, Nx=1, Ny=1, panelColor='k') 332 ax = subplots.next(); ax.text(0.3, 0.5, '[0, 0] (0,0)') 333 ax = subplots.next(); ax.text(0.3, 0.5, '[0, 0] (1,0)') 334 ax = subplots.next(); ax.text(0.3, 0.5, '[0, 0] (0,1)') 335 ax = subplots.next(); ax.text(0.3, 0.5, '[0, 0] (1,1)') 338 @param fig The matplotlib figure to draw 339 @param nx The number of plots in each row of each panel 340 @param ny The number of plots in each column of each panel 341 @param Nx The number of panels in each row of the figure 342 @param Ny The number of panels in each column of the figure 343 @param plottingArea (x0, y0, x1, y1) for the part of the figure containing all the panels 344 @param pxgutter Spacing between columns of panels in units of (x1 - x0) 345 @param pygutter Spacing between rows of panels in units of (y1 - y0) 346 @param xgutter Spacing between columns of plots within a panel in units of (x1 - x0) 347 @param ygutter Spacing between rows of plots within a panel in units of (y1 - y0) 348 @param headroom Extra spacing above each plot for e.g. a title 349 @param panelBorderWeight Width of border drawn around panels 350 @param panelColor Colour of border around panels 355 import matplotlib.pyplot
as plt
356 except ImportError
as e:
357 log.warn(
"Unable to import matplotlib: %s", e)
363 except AttributeError:
364 fig.__show = fig.show
371 fig.show = types.MethodType(myShow, fig, fig.__class__)
380 Callback to draw the panel borders when the plots are drawn to the canvas 382 if panelBorderWeight <= 0:
385 for p
in axes.keys():
388 bboxes.append(ax.bbox.union([label.get_window_extent()
for label
in 389 ax.get_xticklabels() + ax.get_yticklabels()]))
396 bbox = ax.bbox.union(bboxes)
398 xy0, xy1 = ax.transData.inverted().transform(bbox)
401 w, h = x1 - x0, y1 - y0
410 rec = ax.add_patch(plt.Rectangle((x0, y0), w, h, fill=
False,
411 lw=panelBorderWeight, edgecolor=panelColor))
412 rec.set_clip_on(
False)
416 fig.canvas.mpl_connect(
'draw_event', on_draw)
420 x0, y0 = plottingArea[0:2]
421 W, H = plottingArea[2:4]
422 w = (W - (Nx - 1)*pxgutter - (nx*Nx - 1)*xgutter)/float(nx*Nx)
423 h = (H - (Ny - 1)*pygutter - (ny*Ny - 1)*ygutter)/float(ny*Ny)
427 for panel
in range(Nx*Ny):
431 for window
in range(nx*ny):
432 x = nx*px + window%nx
433 y = ny*py + window//nx
434 ax = fig.add_axes((x0 + xgutter + pxgutter + x*w + (px - 1)*pxgutter + (x - 1)*xgutter,
435 y0 + ygutter + pygutter + y*h + (py - 1)*pygutter + (y - 1)*ygutter,
436 w, h), frame_on=
True, axis_bgcolor=
'w')
437 axes[panel].append(ax)
442 matchKernelAmplitudes=False, keepPlots=True):
443 """Plot the PSF spatial model.""" 447 import matplotlib.pyplot
as plt
448 import matplotlib.colors
449 except ImportError
as e:
450 log.warn(
"Unable to import matplotlib: %s", e)
453 noSpatialKernel = psf.getKernel()
460 for cell
in psfCellSet.getCellList():
461 for cand
in cell.begin(
False):
462 if not showBadCandidates
and cand.isBad():
466 im = cand.getMaskedImage()
474 for p, k
in zip(params, kernels):
475 amp += p * k.getSum()
477 targetFits = badFits
if cand.isBad()
else candFits
478 targetPos = badPos
if cand.isBad()
else candPos
479 targetAmps = badAmps
if cand.isBad()
else candAmps
481 targetFits.append([x / amp
for x
in params])
482 targetPos.append(candCenter)
483 targetAmps.append(amp)
485 xGood = numpy.array([pos.getX()
for pos
in candPos]) - exposure.getX0()
486 yGood = numpy.array([pos.getY()
for pos
in candPos]) - exposure.getY0()
487 zGood = numpy.array(candFits)
489 xBad = numpy.array([pos.getX()
for pos
in badPos]) - exposure.getX0()
490 yBad = numpy.array([pos.getY()
for pos
in badPos]) - exposure.getY0()
491 zBad = numpy.array(badFits)
494 xRange = numpy.linspace(0, exposure.getWidth(), num=numSample)
495 yRange = numpy.linspace(0, exposure.getHeight(), num=numSample)
497 kernel = psf.getKernel()
498 nKernelComponents = kernel.getNKernelParameters()
502 nPanelX = int(math.sqrt(nKernelComponents))
503 nPanelY = nKernelComponents//nPanelX
504 while nPanelY*nPanelX < nKernelComponents:
510 fig.canvas._tkcanvas._root().lift()
516 subplots =
makeSubplots(fig, 2, 2, Nx=nPanelX, Ny=nPanelY, xgutter=0.06, ygutter=0.06, pygutter=0.04)
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])
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)
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)
540 ax.set_autoscale_on(
False)
541 ax.set_xbound(lower=0, upper=exposure.getHeight())
542 ax.set_ybound(lower=yMin, upper=yMax)
543 ax.plot(yGood, dfGood,
'b+')
545 ax.plot(yBad, dfBad,
'r+')
547 ax.set_title(
'Residuals(y)')
551 if matchKernelAmplitudes
and k == 0:
558 norm = matplotlib.colors.Normalize(vmin=vmin, vmax=vmax)
559 im = ax.imshow(fRange, aspect=
'auto', origin=
"lower", norm=norm,
560 extent=[0, exposure.getWidth()-1, 0, exposure.getHeight()-1])
561 ax.set_title(
'Spatial poly')
562 plt.colorbar(im, orientation=
'horizontal', ticks=[vmin, vmax])
565 ax.set_autoscale_on(
False)
566 ax.set_xbound(lower=0, upper=exposure.getWidth())
567 ax.set_ybound(lower=yMin, upper=yMax)
568 ax.plot(xGood, dfGood,
'b+')
570 ax.plot(xBad, dfBad,
'r+')
572 ax.set_title(
'K%d Residuals(x)' % k)
577 ax.scatter(xGood, yGood, c=dfGood, marker=
'o')
578 ax.scatter(xBad, yBad, c=dfBad, marker=
'x')
579 ax.set_xbound(lower=0, upper=exposure.getWidth())
580 ax.set_ybound(lower=0, upper=exposure.getHeight())
581 ax.set_title(
'Spatial residuals')
582 plt.colorbar(im, orientation=
'horizontal')
584 calib = exposure.getCalib()
585 if calib.getFluxMag0()[0] <= 0:
586 calib = type(calib)()
587 calib.setFluxMag0(1.0)
590 ax.plot(calib.getMagnitude(candAmps), zGood[:, k],
'b+')
592 ax.plot(calib.getMagnitude(badAmps), zBad[:, k],
'r+')
594 ax.set_title(
'Flux variation')
599 if keepPlots
and not keptPlots:
602 print(
"%s: Please close plots when done." % __name__)
607 print(
"Plots closed, exiting...")
609 atexit.register(show)
613 def showPsf(psf, eigenValues=None, XY=None, normalize=True, frame=None):
614 """Display a PSF's eigen images 616 If normalize is True, set the largest absolute value of each eigenimage to 1.0 (n.b. sum == 0.0 for i > 0) 622 coeffs = psf.getLocalKernel(
afwGeom.PointD(XY[0], XY[1])).getKernelParameters()
626 mos = displayUtils.Mosaic(gutter=2, background=-0.1)
627 for i, k
in enumerate(psf.getKernel().getKernelList()):
628 im = afwImage.ImageD(k.getDimensions())
629 k.computeImage(im,
False)
631 im /= numpy.max(numpy.abs(im.getArray()))
634 mos.append(im,
"%g" % (coeffs[i]/coeffs[0]))
638 mos.makeMosaic(frame=frame, title=
"Kernel Basis Functions")
644 showCenter=True, showEllipticity=False, showFwhm=False,
645 stampSize=0, frame=None, title=None):
646 """Show a mosaic of Psf images. exposure may be an Exposure (optionally with PSF), 647 or a tuple (width, height) 649 If stampSize is > 0, the psf images will be trimmed to stampSize*stampSize 654 showEllipticity =
True 655 scale = 2*math.log(2)
657 mos = displayUtils.Mosaic()
660 width, height = exposure.getWidth(), exposure.getHeight()
661 x0, y0 = exposure.getXY0()
663 psf = exposure.getPsf()
664 except AttributeError:
666 width, height = exposure[0], exposure[1]
669 raise RuntimeError(
"Unable to extract width/height from object of type %s" % type(exposure))
672 ny = int(nx*float(height)/width + 0.5)
676 centroidName =
"SdssCentroid" 677 shapeName =
"base_SdssShape" 679 schema = afwTable.SourceTable.makeMinimalSchema()
680 schema.getAliasMap().set(
"slot_Centroid", centroidName)
681 schema.getAliasMap().set(
"slot_Centroid_flag", centroidName+
"_flag")
683 control = measBase.SdssCentroidControl()
684 centroider = measBase.SdssCentroidAlgorithm(control, centroidName, schema)
686 sdssShape = measBase.SdssShapeControl()
687 shaper = measBase.SdssShapeAlgorithm(sdssShape, shapeName, schema)
688 table = afwTable.SourceTable.make(schema)
690 table.defineCentroid(centroidName)
691 table.defineShape(shapeName)
696 if stampSize <= w
and stampSize <= h:
704 x = int(ix*(width-1)/(nx-1)) + x0
705 y = int(iy*(height-1)/(ny-1)) + y0
711 im = im.Factory(im, bbox)
712 lab =
"PSF(%d,%d)" % (x, y)
if False else "" 716 w, h = im.getWidth(), im.getHeight()
717 centerX = im.getX0() + w//2
718 centerY = im.getY0() + h//2
719 src = table.makeRecord()
720 foot = afwDet.Footprint(exp.getBBox())
721 foot.addPeak(centerX, centerY, 1)
722 src.setFootprint(foot)
724 centroider.measure(src, exp)
725 centers.append((src.getX() - im.getX0(), src.getY() - im.getY0()))
727 shaper.measure(src, exp)
728 shapes.append((src.getIxx(), src.getIxy(), src.getIyy()))
730 mos.makeMosaic(frame=frame, title=title
if title
else "Model Psf", mode=nx)
732 if centers
and frame
is not None:
733 with ds9.Buffering():
734 for i, (cen, shape)
in enumerate(zip(centers, shapes)):
735 bbox = mos.getBBox(i)
736 xc, yc = cen[0] + bbox.getMinX(), cen[1] + bbox.getMinY()
738 ds9.dot(
"+", xc, yc, ctype=ds9.BLUE, frame=frame)
741 ixx, ixy, iyy = shape
745 ds9.dot(
"@:%g,%g,%g" % (ixx, ixy, iyy), xc, yc, frame=frame, ctype=ds9.RED)
751 mimIn = exposure.getMaskedImage()
752 mimIn = mimIn.Factory(mimIn,
True)
754 psf = exposure.getPsf()
755 psfWidth, psfHeight = psf.getLocalKernel().getDimensions()
759 w, h = int(mimIn.getWidth()/scale), int(mimIn.getHeight()/scale)
761 im = mimIn.Factory(w + psfWidth, h + psfHeight)
765 x, y = s.getX(), s.getY()
767 sx, sy = int(x/scale + 0.5), int(y/scale + 0.5)
770 sim = smim.getImage()
775 elif magType ==
"model":
776 flux = s.getModelFlux()
777 elif magType ==
"psf":
778 flux = s.getPsfFlux()
780 raise RuntimeError(
"Unknown flux type %s" % magType)
783 except Exception
as e:
787 expIm = mimIn.getImage().Factory(mimIn.getImage(),
789 int(y) - psfHeight//2),
792 except pexExcept.Exception:
795 cenPos.append([x - expIm.getX0() + sx, y - expIm.getY0() + sy])
799 if frame
is not None:
800 ds9.mtv(im, frame=frame)
801 with ds9.Buffering():
803 ds9.dot(
"+", x, y, frame=frame)
809 """Write the contents of a SpatialCellSet to a many-MEF fits file""" 812 for cell
in psfCellSet.getCellList():
813 for cand
in cell.begin(
False):
818 md = dafBase.PropertySet()
819 md.set(
"CELL", cell.getLabel())
820 md.set(
"ID", cand.getId())
821 md.set(
"XCENTER", cand.getXCenter())
822 md.set(
"YCENTER", cand.getYCenter())
823 md.set(
"BAD", cand.isBad())
824 md.set(
"AMPL", cand.getAmplitude())
825 md.set(
"FLUX", cand.getSource().getPsfFlux())
826 md.set(
"CHI2", cand.getSource().getChi2())
828 im.writeFits(fileName, md, mode)
831 if frame
is not None:
832 ds9.mtv(im, frame=frame)
def showPsfSpatialCells(exposure, psfCellSet, nMaxPerCell=-1, showChi2=False, showMoments=False, symb=None, ctype=None, ctypeUnused=None, ctypeBad=None, size=2, frame=None)
def plotPsfSpatialModel(exposure, psf, psfCellSet, showBadCandidates=True, numSample=128, matchKernelAmplitudes=False, keepPlots=True)
def showPsfResiduals(exposure, sourceSet, magType="psf", scale=10, frame=None)
std::pair< int, double > positionToIndex(double const pos, bool)
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')
double subtractPsf(lsst::afw::detection::Psf const &psf, ImageT *data, double x, double y, double psfFlux=std::numeric_limits< double >::quiet_NaN())
std::pair< std::vector< double >, lsst::afw::math::KernelList > fitKernelParamsToImage(lsst::afw::math::LinearCombinationKernel const &kernel, Image const &image, lsst::afw::geom::Point2D const &pos)
Fit a LinearCombinationKernel to an Image, allowing the coefficients of the components to vary...
MaskedImage< ImagePixelT, MaskPixelT, VariancePixelT > * makeMaskedImage(typename std::shared_ptr< Image< ImagePixelT >> image, typename std::shared_ptr< Mask< MaskPixelT >> mask=Mask< MaskPixelT >(), typename std::shared_ptr< Image< VariancePixelT >> variance=Image< VariancePixelT >())
Statistics makeStatistics(lsst::afw::math::MaskedVector< EntryT > const &mv, std::vector< WeightPixel > const &vweights, int const flags, StatisticsControl const &sctrl=StatisticsControl())
std::shared_ptr< Exposure< ImagePixelT, MaskPixelT, VariancePixelT > > makeExposure(MaskedImage< ImagePixelT, MaskPixelT, VariancePixelT > &mimage, std::shared_ptr< geom::SkyWcs const > wcs=std::shared_ptr< geom::SkyWcs const >())
def showSourceSet(sSet, xy0=(0, 0), frame=0, ctype=ds9.GREEN, symb="+", size=2)
static Log getLogger(std::string const &loggername)
void randomGaussianImage(ImageT *image, Random &rand)
def showPsf(psf, eigenValues=None, XY=None, normalize=True, frame=None)
def saveSpatialCellSet(psfCellSet, fileName="foo.fits", frame=None)
def splitId(oid, asDict=True)
std::shared_ptr< ImageT > offsetImage(ImageT const &image, float dx, float dy, std::string const &algorithmName="lanczos5", unsigned int buffer=0)
def showPsfMosaic(exposure, psf=None, nx=7, ny=None, showCenter=True, showEllipticity=False, showFwhm=False, stampSize=0, frame=None, title=None)
def showPsfCandidates(exposure, psfCellSet, psf=None, frame=None, normalize=True, showBadCandidates=True, fitBasisComponents=False, variance=None, chi=None)