23 """Support utilities for Measuring sources""" 24 from __future__
import print_function
25 from builtins
import next
26 from builtins
import zip
27 from builtins
import str
28 from builtins
import range
44 from .
import subtractPsf, fitKernelParamsToImage
52 objId = int((oid & 0xffff) - 1)
55 return dict(objId=objId)
60 def showSourceSet(sSet, xy0=(0, 0), frame=0, ctype=ds9.GREEN, symb=
"+", size=2):
61 """Draw the (XAstrom, YAstrom) positions of a set of Sources. Image has the given XY0""" 65 xc, yc = s.getXAstrom() - xy0[0], s.getYAstrom() - xy0[1]
68 ds9.dot(str(
splitId(s.getId(),
True)[
"objId"]), xc, yc, frame=frame, ctype=ctype, size=size)
70 ds9.dot(symb, xc, yc, frame=frame, ctype=ctype, size=size)
77 def showPsfSpatialCells(exposure, psfCellSet, nMaxPerCell=-1, showChi2=False, showMoments=False,
78 symb=None, ctype=None, ctypeUnused=None, ctypeBad=None, size=2, frame=None):
79 """Show the SpatialCells. If symb is something that ds9.dot understands (e.g. "o"), the 80 top nMaxPerCell candidates will be indicated with that symbol, using ctype and size""" 83 origin = [-exposure.getMaskedImage().getX0(), -exposure.getMaskedImage().getY0()]
84 for cell
in psfCellSet.getCellList():
85 displayUtils.drawBBox(cell.getBBox(), origin=origin, frame=frame)
91 goodies = ctypeBad
is None 92 for cand
in cell.begin(goodies):
96 xc, yc = cand.getXCenter() + origin[0], cand.getYCenter() + origin[1]
102 color = ctypeBad
if cand.isBad()
else ctype
110 ds9.dot(symb, xc, yc, frame=frame, ctype=ct, size=size)
112 source = cand.getSource()
115 rchi2 = cand.getChi2()
118 ds9.dot(
"%d %.1f" % (
splitId(source.getId(),
True)[
"objId"], rchi2),
119 xc - size, yc - size - 4, frame=frame, ctype=color, size=2)
122 ds9.dot(
"%.2f %.2f %.2f" % (source.getIxx(), source.getIxy(), source.getIyy()),
123 xc-size, yc + size + 4, frame=frame, ctype=color, size=size)
126 def showPsfCandidates(exposure, psfCellSet, psf=None, frame=None, normalize=True, showBadCandidates=True,
127 fitBasisComponents=False, variance=None, chi=None):
128 """Display the PSF candidates. 130 If psf is provided include PSF model and residuals; if normalize is true normalize the PSFs 133 If chi is True, generate a plot of residuals/sqrt(variance), i.e. chi 135 If fitBasisComponents is true, also find the best linear combination of the PSF's components 139 if variance
is not None:
144 mos = displayUtils.Mosaic()
146 candidateCenters = []
147 candidateCentersBad = []
150 for cell
in psfCellSet.getCellList():
151 for cand
in cell.begin(
False):
152 rchi2 = cand.getChi2()
156 if not showBadCandidates
and cand.isBad():
160 im_resid = displayUtils.Mosaic(gutter=0, background=-5, mode=
"x")
163 im = cand.getMaskedImage()
164 xc, yc = cand.getXCenter(), cand.getYCenter()
166 margin = 0
if True else 5
167 w, h = im.getDimensions()
171 bim = im.Factory(w + 2*margin, h + 2*margin)
175 bim.getVariance().set(stdev**2)
182 im = im.Factory(im,
True)
183 im.setXY0(cand.getMaskedImage().getXY0())
188 im_resid.append(im.Factory(im,
True))
192 psfIm = mi.getImage()
193 config = measBase.SingleFrameMeasurementTask.ConfigClass()
194 config.slots.centroid =
"base_SdssCentroid" 196 schema = afwTable.SourceTable.makeMinimalSchema()
197 measureSources = measBase.SingleFrameMeasurementTask(schema, config=config)
198 catalog = afwTable.SourceCatalog(schema)
201 miBig = mi.Factory(im.getWidth() + 2*extra, im.getHeight() + 2*extra)
202 miBig[extra:-extra, extra:-extra] = mi
203 miBig.setXY0(mi.getX0() - extra, mi.getY0() - extra)
210 footprintSet = afwDet.FootprintSet(mi,
211 afwDet.Threshold(0.5*numpy.max(psfIm.getArray())),
213 footprintSet.makeSources(catalog)
215 if len(catalog) == 0:
216 raise RuntimeError(
"Failed to detect any objects")
218 measureSources.run(catalog, exp)
219 if len(catalog) == 1:
223 for i, s
in enumerate(catalog):
224 d = numpy.hypot(xc - s.getX(), yc - s.getY())
225 if i == 0
or d < dmin:
227 xc, yc = source.getCentroid()
237 resid = resid.getImage()
238 var = im.getVariance()
239 var = var.Factory(var,
True)
240 numpy.sqrt(var.getArray(), var.getArray())
243 im_resid.append(resid)
246 if fitBasisComponents:
247 im = cand.getMaskedImage()
249 im = im.Factory(im,
True)
250 im.setXY0(cand.getMaskedImage().getXY0())
253 noSpatialKernel = psf.getKernel()
255 noSpatialKernel =
None 264 outImage = afwImage.ImageD(outputKernel.getDimensions())
265 outputKernel.computeImage(outImage,
False)
267 im -= outImage.convertF()
271 bim = im.Factory(w + 2*margin, h + 2*margin)
275 bim.assign(resid, bbox)
279 resid = resid.getImage()
282 im_resid.append(resid)
284 im = im_resid.makeMosaic()
286 im = cand.getMaskedImage()
291 objId =
splitId(cand.getSource().getId(),
True)[
"objId"]
293 lab =
"%d chi^2 %.1f" % (objId, rchi2)
294 ctype = ds9.RED
if cand.isBad()
else ds9.GREEN
296 lab =
"%d flux %8.3g" % (objId, cand.getSource().getPsfFlux())
299 mos.append(im, lab, ctype)
301 if False and numpy.isnan(rchi2):
302 ds9.mtv(cand.getMaskedImage().getImage(), title=
"candidate", frame=1)
303 print(
"amp", cand.getAmplitude())
305 im = cand.getMaskedImage()
306 center = (candidateIndex, xc - im.getX0(), yc - im.getY0())
309 candidateCentersBad.append(center)
311 candidateCenters.append(center)
314 title =
"chi(Psf fit)" 316 title =
"Stars & residuals" 317 mosaicImage = mos.makeMosaic(frame=frame, title=title)
319 with ds9.Buffering():
320 for centers, color
in ((candidateCenters, ds9.GREEN), (candidateCentersBad, ds9.RED)):
322 bbox = mos.getBBox(cen[0])
323 ds9.dot(
"+", cen[1] + bbox.getMinX(), cen[2] + bbox.getMinY(), frame=frame, ctype=color)
328 def makeSubplots(fig, nx=2, ny=2, Nx=1, Ny=1, plottingArea=(0.1, 0.1, 0.85, 0.80),
329 pxgutter=0.05, pygutter=0.05, xgutter=0.04, ygutter=0.04,
330 headroom=0.0, panelBorderWeight=0, panelColor=
'black'):
331 """Return a generator of a set of subplots, a set of Nx*Ny panels of nx*ny plots. Each panel is fully 332 filled by row (starting in the bottom left) before the next panel is started. If panelBorderWidth is 333 greater than zero a border is drawn around each panel, adjusted to enclose the axis labels. 336 subplots = makeSubplots(fig, 2, 2, Nx=1, Ny=1, panelColor='k') 337 ax = subplots.next(); ax.text(0.3, 0.5, '[0, 0] (0,0)') 338 ax = subplots.next(); ax.text(0.3, 0.5, '[0, 0] (1,0)') 339 ax = subplots.next(); ax.text(0.3, 0.5, '[0, 0] (0,1)') 340 ax = subplots.next(); ax.text(0.3, 0.5, '[0, 0] (1,1)') 343 @param fig The matplotlib figure to draw 344 @param nx The number of plots in each row of each panel 345 @param ny The number of plots in each column of each panel 346 @param Nx The number of panels in each row of the figure 347 @param Ny The number of panels in each column of the figure 348 @param plottingArea (x0, y0, x1, y1) for the part of the figure containing all the panels 349 @param pxgutter Spacing between columns of panels in units of (x1 - x0) 350 @param pygutter Spacing between rows of panels in units of (y1 - y0) 351 @param xgutter Spacing between columns of plots within a panel in units of (x1 - x0) 352 @param ygutter Spacing between rows of plots within a panel in units of (y1 - y0) 353 @param headroom Extra spacing above each plot for e.g. a title 354 @param panelBorderWeight Width of border drawn around panels 355 @param panelColor Colour of border around panels 360 import matplotlib.pyplot
as plt
361 except ImportError
as e:
362 log.warn(
"Unable to import matplotlib: %s", e)
368 except AttributeError:
369 fig.__show = fig.show
376 fig.show = types.MethodType(myShow, fig, fig.__class__)
385 Callback to draw the panel borders when the plots are drawn to the canvas 387 if panelBorderWeight <= 0:
390 for p
in axes.keys():
393 bboxes.append(ax.bbox.union([label.get_window_extent()
for label
in 394 ax.get_xticklabels() + ax.get_yticklabels()]))
401 bbox = ax.bbox.union(bboxes)
403 xy0, xy1 = ax.transData.inverted().transform(bbox)
406 w, h = x1 - x0, y1 - y0
415 rec = ax.add_patch(plt.Rectangle((x0, y0), w, h, fill=
False,
416 lw=panelBorderWeight, edgecolor=panelColor))
417 rec.set_clip_on(
False)
421 fig.canvas.mpl_connect(
'draw_event', on_draw)
425 x0, y0 = plottingArea[0:2]
426 W, H = plottingArea[2:4]
427 w = (W - (Nx - 1)*pxgutter - (nx*Nx - 1)*xgutter)/float(nx*Nx)
428 h = (H - (Ny - 1)*pygutter - (ny*Ny - 1)*ygutter)/float(ny*Ny)
432 for panel
in range(Nx*Ny):
436 for window
in range(nx*ny):
437 x = nx*px + window%nx
438 y = ny*py + window//nx
439 ax = fig.add_axes((x0 + xgutter + pxgutter + x*w + (px - 1)*pxgutter + (x - 1)*xgutter,
440 y0 + ygutter + pygutter + y*h + (py - 1)*pygutter + (y - 1)*ygutter,
441 w, h), frame_on=
True, axis_bgcolor=
'w')
442 axes[panel].append(ax)
447 matchKernelAmplitudes=False, keepPlots=True):
448 """Plot the PSF spatial model.""" 452 import matplotlib.pyplot
as plt
453 import matplotlib.colors
454 except ImportError
as e:
455 log.warn(
"Unable to import matplotlib: %s", e)
458 noSpatialKernel = psf.getKernel()
465 for cell
in psfCellSet.getCellList():
466 for cand
in cell.begin(
False):
467 if not showBadCandidates
and cand.isBad():
471 im = cand.getMaskedImage()
479 for p, k
in zip(params, kernels):
480 amp += p * k.getSum()
482 targetFits = badFits
if cand.isBad()
else candFits
483 targetPos = badPos
if cand.isBad()
else candPos
484 targetAmps = badAmps
if cand.isBad()
else candAmps
486 targetFits.append([x / amp
for x
in params])
487 targetPos.append(candCenter)
488 targetAmps.append(amp)
490 xGood = numpy.array([pos.getX()
for pos
in candPos]) - exposure.getX0()
491 yGood = numpy.array([pos.getY()
for pos
in candPos]) - exposure.getY0()
492 zGood = numpy.array(candFits)
494 xBad = numpy.array([pos.getX()
for pos
in badPos]) - exposure.getX0()
495 yBad = numpy.array([pos.getY()
for pos
in badPos]) - exposure.getY0()
496 zBad = numpy.array(badFits)
499 xRange = numpy.linspace(0, exposure.getWidth(), num=numSample)
500 yRange = numpy.linspace(0, exposure.getHeight(), num=numSample)
502 kernel = psf.getKernel()
503 nKernelComponents = kernel.getNKernelParameters()
507 nPanelX = int(math.sqrt(nKernelComponents))
508 nPanelY = nKernelComponents//nPanelX
509 while nPanelY*nPanelX < nKernelComponents:
515 fig.canvas._tkcanvas._root().lift()
521 subplots =
makeSubplots(fig, 2, 2, Nx=nPanelX, Ny=nPanelY, xgutter=0.06, ygutter=0.06, pygutter=0.04)
523 for k
in range(nKernelComponents):
524 func = kernel.getSpatialFunction(k)
525 dfGood = zGood[:, k] - numpy.array([func(pos.getX(), pos.getY())
for pos
in candPos])
529 dfBad = zBad[:, k] - numpy.array([func(pos.getX(), pos.getY())
for pos
in badPos])
530 yMin = min([yMin, dfBad.min()])
531 yMax = max([yMax, dfBad.max()])
532 yMin -= 0.05 * (yMax - yMin)
533 yMax += 0.05 * (yMax - yMin)
538 fRange = numpy.ndarray((len(xRange), len(yRange)))
539 for j, yVal
in enumerate(yRange):
540 for i, xVal
in enumerate(xRange):
541 fRange[j][i] = func(xVal, yVal)
545 ax.set_autoscale_on(
False)
546 ax.set_xbound(lower=0, upper=exposure.getHeight())
547 ax.set_ybound(lower=yMin, upper=yMax)
548 ax.plot(yGood, dfGood,
'b+')
550 ax.plot(yBad, dfBad,
'r+')
552 ax.set_title(
'Residuals(y)')
556 if matchKernelAmplitudes
and k == 0:
563 norm = matplotlib.colors.Normalize(vmin=vmin, vmax=vmax)
564 im = ax.imshow(fRange, aspect=
'auto', origin=
"lower", norm=norm,
565 extent=[0, exposure.getWidth()-1, 0, exposure.getHeight()-1])
566 ax.set_title(
'Spatial poly')
567 plt.colorbar(im, orientation=
'horizontal', ticks=[vmin, vmax])
570 ax.set_autoscale_on(
False)
571 ax.set_xbound(lower=0, upper=exposure.getWidth())
572 ax.set_ybound(lower=yMin, upper=yMax)
573 ax.plot(xGood, dfGood,
'b+')
575 ax.plot(xBad, dfBad,
'r+')
577 ax.set_title(
'K%d Residuals(x)' % k)
582 ax.scatter(xGood, yGood, c=dfGood, marker=
'o')
583 ax.scatter(xBad, yBad, c=dfBad, marker=
'x')
584 ax.set_xbound(lower=0, upper=exposure.getWidth())
585 ax.set_ybound(lower=0, upper=exposure.getHeight())
586 ax.set_title(
'Spatial residuals')
587 plt.colorbar(im, orientation=
'horizontal')
589 calib = exposure.getCalib()
590 if calib.getFluxMag0()[0] <= 0:
591 calib = type(calib)()
592 calib.setFluxMag0(1.0)
595 ax.plot(calib.getMagnitude(candAmps), zGood[:, k],
'b+')
597 ax.plot(calib.getMagnitude(badAmps), zBad[:, k],
'r+')
599 ax.set_title(
'Flux variation')
604 if keepPlots
and not keptPlots:
607 print(
"%s: Please close plots when done." % __name__)
612 print(
"Plots closed, exiting...")
614 atexit.register(show)
618 def showPsf(psf, eigenValues=None, XY=None, normalize=True, frame=None):
619 """Display a PSF's eigen images 621 If normalize is True, set the largest absolute value of each eigenimage to 1.0 (n.b. sum == 0.0 for i > 0) 627 coeffs = psf.getLocalKernel(
afwGeom.PointD(XY[0], XY[1])).getKernelParameters()
631 mos = displayUtils.Mosaic(gutter=2, background=-0.1)
632 for i, k
in enumerate(psf.getKernel().getKernelList()):
633 im = afwImage.ImageD(k.getDimensions())
634 k.computeImage(im,
False)
636 im /= numpy.max(numpy.abs(im.getArray()))
639 mos.append(im,
"%g" % (coeffs[i]/coeffs[0]))
643 mos.makeMosaic(frame=frame, title=
"Kernel Basis Functions")
649 showCenter=True, showEllipticity=False, showFwhm=False,
650 stampSize=0, frame=None, title=None):
651 """Show a mosaic of Psf images. exposure may be an Exposure (optionally with PSF), 652 or a tuple (width, height) 654 If stampSize is > 0, the psf images will be trimmed to stampSize*stampSize 659 showEllipticity =
True 660 scale = 2*math.log(2)
662 mos = displayUtils.Mosaic()
665 width, height = exposure.getWidth(), exposure.getHeight()
666 x0, y0 = exposure.getXY0()
668 psf = exposure.getPsf()
669 except AttributeError:
671 width, height = exposure[0], exposure[1]
674 raise RuntimeError(
"Unable to extract width/height from object of type %s" % type(exposure))
677 ny = int(nx*float(height)/width + 0.5)
681 centroidName =
"base_GaussianCentroid" 682 shapeName =
"base_SdssShape" 684 schema = afwTable.SourceTable.makeMinimalSchema()
685 schema.getAliasMap().set(
"slot_Centroid", centroidName)
686 schema.getAliasMap().set(
"slot_Centroid_flag", centroidName+
"_flag")
688 control = measBase.GaussianCentroidControl()
689 centroider = measBase.GaussianCentroidAlgorithm(control, centroidName, schema)
691 sdssShape = measBase.SdssShapeControl()
692 shaper = measBase.SdssShapeAlgorithm(sdssShape, shapeName, schema)
693 table = afwTable.SourceTable.make(schema)
695 table.defineCentroid(centroidName)
696 table.defineShape(shapeName)
701 if stampSize <= w
and stampSize <= h:
709 x = int(ix*(width-1)/(nx-1)) + x0
710 y = int(iy*(height-1)/(ny-1)) + y0
716 im = im.Factory(im, bbox)
717 lab =
"PSF(%d,%d)" % (x, y)
if False else "" 721 w, h = im.getWidth(), im.getHeight()
722 centerX = im.getX0() + w//2
723 centerY = im.getY0() + h//2
724 src = table.makeRecord()
725 foot = afwDet.Footprint(exp.getBBox())
726 foot.addPeak(centerX, centerY, 1)
727 src.setFootprint(foot)
729 centroider.measure(src, exp)
730 centers.append((src.getX() - im.getX0(), src.getY() - im.getY0()))
732 shaper.measure(src, exp)
733 shapes.append((src.getIxx(), src.getIxy(), src.getIyy()))
735 mos.makeMosaic(frame=frame, title=title
if title
else "Model Psf", mode=nx)
737 if centers
and frame
is not None:
738 with ds9.Buffering():
739 for i, (cen, shape)
in enumerate(zip(centers, shapes)):
740 bbox = mos.getBBox(i)
741 xc, yc = cen[0] + bbox.getMinX(), cen[1] + bbox.getMinY()
743 ds9.dot(
"+", xc, yc, ctype=ds9.BLUE, frame=frame)
746 ixx, ixy, iyy = shape
750 ds9.dot(
"@:%g,%g,%g" % (ixx, ixy, iyy), xc, yc, frame=frame, ctype=ds9.RED)
756 mimIn = exposure.getMaskedImage()
757 mimIn = mimIn.Factory(mimIn,
True)
759 psf = exposure.getPsf()
760 psfWidth, psfHeight = psf.getLocalKernel().getDimensions()
764 w, h = int(mimIn.getWidth()/scale), int(mimIn.getHeight()/scale)
766 im = mimIn.Factory(w + psfWidth, h + psfHeight)
770 x, y = s.getX(), s.getY()
772 sx, sy = int(x/scale + 0.5), int(y/scale + 0.5)
775 sim = smim.getImage()
780 elif magType ==
"model":
781 flux = s.getModelFlux()
782 elif magType ==
"psf":
783 flux = s.getPsfFlux()
785 raise RuntimeError(
"Unknown flux type %s" % magType)
788 except Exception
as e:
792 expIm = mimIn.getImage().Factory(mimIn.getImage(),
794 int(y) - psfHeight//2),
797 except pexExcept.Exception:
800 cenPos.append([x - expIm.getX0() + sx, y - expIm.getY0() + sy])
804 if frame
is not None:
805 ds9.mtv(im, frame=frame)
806 with ds9.Buffering():
808 ds9.dot(
"+", x, y, frame=frame)
814 """Write the contents of a SpatialCellSet to a many-MEF fits file""" 817 for cell
in psfCellSet.getCellList():
818 for cand
in cell.begin(
False):
823 md = dafBase.PropertySet()
824 md.set(
"CELL", cell.getLabel())
825 md.set(
"ID", cand.getId())
826 md.set(
"XCENTER", cand.getXCenter())
827 md.set(
"YCENTER", cand.getYCenter())
828 md.set(
"BAD", cand.isBad())
829 md.set(
"AMPL", cand.getAmplitude())
830 md.set(
"FLUX", cand.getSource().getPsfFlux())
831 md.set(
"CHI2", cand.getSource().getChi2())
833 im.writeFits(fileName, md, mode)
836 if frame
is not None:
837 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< Wcs const > wcs=std::shared_ptr< Wcs 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)