22"""Support utilities for Measuring sources"""
25__all__ = [
"DipoleTestImage",
"evaluateMeanPsfFwhm",
"getPsfFwhm"]
36import lsst.meas.algorithms
as measAlg
38from lsst.meas.algorithms.testUtils
import plantSources
40from lsst.utils.logging
import getLogger
41from .dipoleFitTask
import DipoleFitAlgorithm
42from .
import diffimLib
43from .
import diffimTools
45afwDisplay.setDefaultMaskTransparency(75)
48_LOG = getLogger(__name__)
51def showSourceSet(sSet, xy0=(0, 0), frame=0, ctype=afwDisplay.GREEN, symb=
"+", size=2):
52 """Draw the (XAstrom, YAstrom) positions of a set of Sources.
54 Image has the given XY0.
56 disp = afwDisplay.afwDisplay(frame=frame)
57 with disp.Buffering():
59 xc, yc = s.getXAstrom() - xy0[0], s.getYAstrom() - xy0[1]
62 disp.dot(str(s.getId()), xc, yc, ctype=ctype, size=size)
64 disp.dot(symb, xc, yc, ctype=ctype, size=size)
72 ctype=None, ctypeUnused=None, ctypeBad=None, size=3,
73 frame=None, title="Spatial Cells"):
74 """Show the SpatialCells.
76 If symb is something that display.dot understands (e.g. "o"), the top
77 nMaxPerCell candidates will be indicated with that symbol, using ctype
80 disp = afwDisplay.Display(frame=frame)
81 disp.mtv(maskedIm, title=title)
82 with disp.Buffering():
83 origin = [-maskedIm.getX0(), -maskedIm.getY0()]
84 for cell
in kernelCellSet.getCellList():
85 afwDisplay.utils.drawBBox(cell.getBBox(), origin=origin, display=disp)
87 goodies = ctypeBad
is None
88 for cand
in cell.begin(goodies):
89 xc, yc = cand.getXCenter() + origin[0], cand.getYCenter() + origin[1]
90 if cand.getStatus() == afwMath.SpatialCellCandidate.BAD:
92 elif cand.getStatus() == afwMath.SpatialCellCandidate.GOOD:
94 elif cand.getStatus() == afwMath.SpatialCellCandidate.UNKNOWN:
100 disp.dot(symb, xc, yc, ctype=color, size=size)
103 rchi2 = cand.getChi2()
106 disp.dot(
"%d %.1f" % (cand.getId(), rchi2),
107 xc - size, yc - size - 4, ctype=color, size=size)
111 """Display Dia Sources.
117 disp = afwDisplay.Display(frame=frame)
118 for plane
in (
"BAD",
"CR",
"EDGE",
"INTERPOlATED",
"INTRP",
"SAT",
"SATURATED"):
119 disp.setMaskPlaneColor(plane, color=
"ignore")
121 mos = afwDisplay.utils.Mosaic()
122 for i
in range(
len(sources)):
124 badFlag = isFlagged[i]
125 dipoleFlag = isDipole[i]
126 bbox = source.getFootprint().getBBox()
127 stamp = exposure.Factory(exposure, bbox,
True)
128 im = afwDisplay.utils.Mosaic(gutter=1, background=0, mode=
"x")
129 im.append(stamp.getMaskedImage())
130 lab =
"%.1f,%.1f:" % (source.getX(), source.getY())
132 ctype = afwDisplay.RED
135 ctype = afwDisplay.YELLOW
137 if not badFlag
and not dipoleFlag:
138 ctype = afwDisplay.GREEN
140 mos.append(im.makeMosaic(), lab, ctype)
141 title =
"Dia Sources"
142 mosaicImage = mos.makeMosaic(display=disp, title=title)
147 resids=False, kernels=False):
148 """Display the Kernel candidates.
150 If kernel is provided include spatial model and residuals;
151 If chi is True, generate a plot of residuals/sqrt(variance), i.e. chi.
157 mos = afwDisplay.utils.Mosaic(gutter=5, background=0)
159 mos = afwDisplay.utils.Mosaic(gutter=5, background=-1)
161 candidateCenters = []
162 candidateCentersBad = []
164 for cell
in kernelCellSet.getCellList():
165 for cand
in cell.begin(
False):
168 resid = cand.getDifferenceImage(diffimLib.KernelCandidateF.ORIG)
172 rchi2 = cand.getChi2()
176 if not showBadCandidates
and cand.isBad():
179 im_resid = afwDisplay.utils.Mosaic(gutter=1, background=-0.5, mode=
"x")
182 im = cand.getScienceMaskedImage()
183 im = im.Factory(im,
True)
184 im.setXY0(cand.getScienceMaskedImage().getXY0())
187 if (
not resids
and not kernels):
188 im_resid.append(im.Factory(im,
True))
190 im = cand.getTemplateMaskedImage()
191 im = im.Factory(im,
True)
192 im.setXY0(cand.getTemplateMaskedImage().getXY0())
195 if (
not resids
and not kernels):
196 im_resid.append(im.Factory(im,
True))
201 var = var.Factory(var,
True)
202 np.sqrt(var.array, var.array)
205 bbox = kernel.shrinkBBox(resid.getBBox())
206 resid = resid.Factory(resid, bbox, deep=
True)
208 kim = cand.getKernelImage(diffimLib.KernelCandidateF.ORIG).convertF()
209 resid = kim.Factory(kim,
True)
210 im_resid.append(resid)
213 ski = afwImage.ImageD(kernel.getDimensions())
214 kernel.computeImage(ski,
False,
int(cand.getXCenter()),
int(cand.getYCenter()))
218 sbg = background(
int(cand.getXCenter()),
int(cand.getYCenter()))
219 sresid = cand.getDifferenceImage(sk, sbg)
224 bbox = kernel.shrinkBBox(resid.getBBox())
225 resid = resid.Factory(resid, bbox, deep=
True)
228 resid = kim.Factory(kim,
True)
229 im_resid.append(resid)
231 im = im_resid.makeMosaic()
233 lab =
"%d chi^2 %.1f" % (cand.getId(), rchi2)
234 ctype = afwDisplay.RED
if cand.isBad()
else afwDisplay.GREEN
236 mos.append(im, lab, ctype)
238 if False and np.isnan(rchi2):
239 disp = afwDisplay.Display(frame=1)
240 disp.mtv(cand.getScienceMaskedImage.image, title=
"candidate")
241 print(
"rating", cand.getCandidateRating())
243 im = cand.getScienceMaskedImage()
244 center = (candidateIndex, cand.getXCenter() - im.getX0(), cand.getYCenter() - im.getY0())
247 candidateCentersBad.append(center)
249 candidateCenters.append(center)
256 title =
"Candidates & residuals"
258 disp = afwDisplay.Display(frame=frame)
259 mosaicImage = mos.makeMosaic(display=disp, title=title)
265 """Display a Kernel's basis images.
267 mos = afwDisplay.utils.Mosaic()
269 for k
in kernel.getKernelList():
270 im = afwImage.ImageD(k.getDimensions())
271 k.computeImage(im,
False)
274 disp = afwDisplay.Display(frame=frame)
275 mos.makeMosaic(display=disp, title=
"Kernel Basis Images")
283 numSample=128, keepPlots=True, maxCoeff=10):
284 """Plot the Kernel spatial model.
287 import matplotlib.pyplot
as plt
288 import matplotlib.colors
289 except ImportError
as e:
290 print(
"Unable to import numpy and matplotlib: %s" % e)
293 x0 = kernelCellSet.getBBox().getBeginX()
294 y0 = kernelCellSet.getBBox().getBeginY()
302 for cell
in kernelCellSet.getCellList():
303 for cand
in cell.begin(
False):
304 if not showBadCandidates
and cand.isBad():
306 candCenter =
geom.PointD(cand.getXCenter(), cand.getYCenter())
308 im = cand.getTemplateMaskedImage()
312 targetFits = badFits
if cand.isBad()
else candFits
313 targetPos = badPos
if cand.isBad()
else candPos
314 targetAmps = badAmps
if cand.isBad()
else candAmps
317 kp0 = np.array(cand.getKernel(diffimLib.KernelCandidateF.ORIG).getKernelParameters())
318 amp = cand.getCandidateRating()
320 targetFits = badFits
if cand.isBad()
else candFits
321 targetPos = badPos
if cand.isBad()
else candPos
322 targetAmps = badAmps
if cand.isBad()
else candAmps
324 targetFits.append(kp0)
325 targetPos.append(candCenter)
326 targetAmps.append(amp)
328 xGood = np.array([pos.getX()
for pos
in candPos]) - x0
329 yGood = np.array([pos.getY()
for pos
in candPos]) - y0
330 zGood = np.array(candFits)
332 xBad = np.array([pos.getX()
for pos
in badPos]) - x0
333 yBad = np.array([pos.getY()
for pos
in badPos]) - y0
334 zBad = np.array(badFits)
337 xRange = np.linspace(0, kernelCellSet.getBBox().getWidth(), num=numSample)
338 yRange = np.linspace(0, kernelCellSet.getBBox().getHeight(), num=numSample)
341 maxCoeff = min(maxCoeff, kernel.getNKernelParameters())
343 maxCoeff = kernel.getNKernelParameters()
345 for k
in range(maxCoeff):
346 func = kernel.getSpatialFunction(k)
347 dfGood = zGood[:, k] - np.array([
func(pos.getX(), pos.getY())
for pos
in candPos])
351 dfBad = zBad[:, k] - np.array([
func(pos.getX(), pos.getY())
for pos
in badPos])
353 yMin = min([yMin, dfBad.min()])
354 yMax = max([yMax, dfBad.max()])
355 yMin -= 0.05*(yMax - yMin)
356 yMax += 0.05*(yMax - yMin)
358 fRange = np.ndarray((
len(xRange),
len(yRange)))
361 fRange[j][i] =
func(xVal, yVal)
367 fig.canvas._tkcanvas._root().
lift()
371 fig.suptitle(
'Kernel component %d' % k)
374 ax = fig.add_axes((0.1, 0.05, 0.35, 0.35))
377 norm = matplotlib.colors.Normalize(vmin=vmin, vmax=vmax)
378 im = ax.imshow(fRange, aspect=
'auto', norm=norm,
379 extent=[0, kernelCellSet.getBBox().getWidth() - 1,
380 0, kernelCellSet.getBBox().getHeight() - 1])
381 ax.set_title(
'Spatial polynomial')
382 plt.colorbar(im, orientation=
'horizontal', ticks=[vmin, vmax])
385 ax = fig.add_axes((0.1, 0.55, 0.35, 0.35))
386 ax.plot(-2.5*np.log10(candAmps), zGood[:, k],
'b+')
388 ax.plot(-2.5*np.log10(badAmps), zBad[:, k],
'r+')
389 ax.set_title(
"Basis Coefficients")
390 ax.set_xlabel(
"Instr mag")
391 ax.set_ylabel(
"Coeff")
394 ax = fig.add_axes((0.55, 0.05, 0.35, 0.35))
395 ax.set_autoscale_on(
False)
396 ax.set_xbound(lower=0, upper=kernelCellSet.getBBox().getHeight())
397 ax.set_ybound(lower=yMin, upper=yMax)
398 ax.plot(yGood, dfGood,
'b+')
400 ax.plot(yBad, dfBad,
'r+')
402 ax.set_title(
'dCoeff (indiv-spatial) vs. y')
405 ax = fig.add_axes((0.55, 0.55, 0.35, 0.35))
406 ax.set_autoscale_on(
False)
407 ax.set_xbound(lower=0, upper=kernelCellSet.getBBox().getWidth())
408 ax.set_ybound(lower=yMin, upper=yMax)
409 ax.plot(xGood, dfGood,
'b+')
411 ax.plot(xBad, dfBad,
'r+')
413 ax.set_title(
'dCoeff (indiv-spatial) vs. x')
418 if keepPlots
and not keptPlots:
421 print(
"%s: Please close plots when done." % __name__)
426 print(
"Plots closed, exiting...")
428 atexit.register(show)
433 """Plot the individual kernel candidate and the spatial kernel solution coefficients.
438 spatialKernel : `lsst.afw.math.LinearCombinationKernel`
439 The spatial spatialKernel solution model which is a spatially varying linear combination
440 of the spatialKernel basis functions.
441 Typically returned by `lsst.ip.diffim.SpatialKernelSolution.getSolutionPair()`.
443 kernelCellSet : `lsst.afw.math.SpatialCellSet`
444 The spatial cells that was used for solution for the spatialKernel. They contain the
445 local solutions of the AL kernel for the selected sources.
447 showBadCandidates : `bool`, optional
448 If True, plot the coefficient values for kernel candidates where the solution was marked
449 bad by the numerical algorithm. Defaults to False.
451 keepPlots: `bool`, optional
452 If True, sets ``plt.show()`` to be called before the task terminates, so that the plots
453 can be explored interactively. Defaults to True.
457 This function produces 3 figures per image subtraction operation.
458 * A grid plot of the local solutions. Each grid cell corresponds to a proportional area in
459 the image. In each cell, local kernel solution coefficients are plotted of kernel candidates (color)
460 that fall into this area as a function of the kernel basis function number.
461 * A grid plot of the spatial solution. Each grid cell corresponds to a proportional area in
462 the image. In each cell, the spatial solution coefficients are evaluated for the center of the cell.
463 * Histogram of the local solution coefficients. Red line marks the spatial solution value at
466 This function is called if ``lsst.ip.diffim.psfMatch.plotKernelCoefficients==True`` in lsstDebug. This
467 function was implemented as part of DM-17825.
470 import matplotlib.pyplot
as plt
471 except ImportError
as e:
472 print(
"Unable to import matplotlib: %s" % e)
476 imgBBox = kernelCellSet.getBBox()
477 x0 = imgBBox.getBeginX()
478 y0 = imgBBox.getBeginY()
479 wImage = imgBBox.getWidth()
480 hImage = imgBBox.getHeight()
481 imgCenterX = imgBBox.getCenterX()
482 imgCenterY = imgBBox.getCenterY()
494 fig.suptitle(
"Kernel candidate parameters on an image grid")
495 arrAx = fig.subplots(nrows=nY, ncols=nX, sharex=
True, sharey=
True, gridspec_kw=
dict(
499 arrAx = arrAx[::-1, :]
502 for cell
in kernelCellSet.getCellList():
505 iX =
int((cellBBox.getCenterX() - x0)//wCell)
506 iY =
int((cellBBox.getCenterY() - y0)//hCell)
508 for cand
in cell.begin(
False):
510 kernel = cand.getKernel(cand.ORIG)
514 if not showBadCandidates
and cand.isBad():
517 nKernelParams = kernel.getNKernelParameters()
518 kernelParams = np.array(kernel.getKernelParameters())
519 allParams.append(kernelParams)
525 arrAx[iY, iX].plot(np.arange(nKernelParams), kernelParams,
'.-',
526 color=color, drawstyle=
'steps-mid', linewidth=0.1)
527 for ax
in arrAx.ravel():
528 ax.grid(
True, axis=
'y')
533 spatialFuncs = spatialKernel.getSpatialFunctionList()
534 nKernelParams = spatialKernel.getNKernelParameters()
537 fig.suptitle(
"Hist. of parameters marked with spatial solution at img center")
538 arrAx = fig.subplots(nrows=
int(nKernelParams//nX)+1, ncols=nX)
539 arrAx = arrAx[::-1, :]
540 allParams = np.array(allParams)
541 for k
in range(nKernelParams):
542 ax = arrAx.ravel()[k]
543 ax.hist(allParams[:, k], bins=20, edgecolor=
'black')
544 ax.set_xlabel(
'P{}'.format(k))
545 valueParam = spatialFuncs[k](imgCenterX, imgCenterY)
546 ax.axvline(x=valueParam, color=
'red')
547 ax.text(0.1, 0.9,
'{:.1f}'.format(valueParam),
548 transform=ax.transAxes, backgroundcolor=
'lightsteelblue')
561 fig.suptitle(
"Spatial solution of kernel parameters on an image grid")
562 arrAx = fig.subplots(nrows=nY, ncols=nX, sharex=
True, sharey=
True, gridspec_kw=
dict(
564 arrAx = arrAx[::-1, :]
565 kernelParams = np.zeros(nKernelParams, dtype=float)
572 kernelParams = [
f(x, y)
for f
in spatialFuncs]
573 arrAx[iY, iX].plot(np.arange(nKernelParams), kernelParams,
'.-', drawstyle=
'steps-mid')
574 arrAx[iY, iX].grid(
True, axis=
'y')
577 if keepPlots
and not keptPlots:
580 print(
"%s: Please close plots when done." % __name__)
585 print(
"Plots closed, exiting...")
587 atexit.register(show)
592 showCenter=True, showEllipticity=True):
593 """Show a mosaic of Kernel images.
595 mos = afwDisplay.utils.Mosaic()
597 x0 = bbox.getBeginX()
598 y0 = bbox.getBeginY()
599 width = bbox.getWidth()
600 height = bbox.getHeight()
603 ny =
int(nx*
float(height)/width + 0.5)
607 schema = afwTable.SourceTable.makeMinimalSchema()
608 centroidName =
"base_SdssCentroid"
609 shapeName =
"base_SdssShape"
610 control = measBase.SdssCentroidControl()
611 schema.getAliasMap().set(
"slot_Centroid", centroidName)
612 schema.getAliasMap().set(
"slot_Centroid_flag", centroidName +
"_flag")
613 centroider = measBase.SdssCentroidAlgorithm(control, centroidName, schema)
614 sdssShape = measBase.SdssShapeControl()
615 shaper = measBase.SdssShapeAlgorithm(sdssShape, shapeName, schema)
616 table = afwTable.SourceTable.make(schema)
617 table.defineCentroid(centroidName)
618 table.defineShape(shapeName)
624 x =
int(ix*(width - 1)/(nx - 1)) + x0
625 y =
int(iy*(height - 1)/(ny - 1)) + y0
627 im = afwImage.ImageD(kernel.getDimensions())
628 ksum = kernel.computeImage(im,
False, x, y)
629 lab =
"Kernel(%d,%d)=%.2f" % (x, y, ksum)
if False else ""
635 w, h = im.getWidth(), im.getHeight()
636 centerX = im.getX0() + w//2
637 centerY = im.getY0() + h//2
638 src = table.makeRecord()
641 foot.addPeak(centerX, centerY, 1)
642 src.setFootprint(foot)
645 centroider.measure(src, exp)
646 centers.append((src.getX(), src.getY()))
648 shaper.measure(src, exp)
649 shapes.append((src.getIxx(), src.getIxy(), src.getIyy()))
653 disp = afwDisplay.Display(frame=frame)
654 mos.makeMosaic(display=disp, title=title
if title
else "Model Kernel", mode=nx)
656 if centers
and frame
is not None:
657 disp = afwDisplay.Display(frame=frame)
659 with disp.Buffering():
660 for cen, shape
in zip(centers, shapes):
661 bbox = mos.getBBox(i)
663 xc, yc = cen[0] + bbox.getMinX(), cen[1] + bbox.getMinY()
665 disp.dot(
"+", xc, yc, ctype=afwDisplay.BLUE)
668 ixx, ixy, iyy = shape
669 disp.dot(
"@:%g,%g,%g" % (ixx, ixy, iyy), xc, yc, ctype=afwDisplay.RED)
675 kernel, background, testSources, config,
676 origVariance=False, nptsFull=1e6, keepPlots=True, titleFs=14):
677 """Plot diffim residuals for LOCAL and SPATIAL models.
683 for cell
in kernelCellSet.getCellList():
684 for cand
in cell.begin(
True):
686 if not (cand.getStatus() == afwMath.SpatialCellCandidate.GOOD):
689 diffim = cand.getDifferenceImage(diffimLib.KernelCandidateF.ORIG)
690 orig = cand.getScienceMaskedImage()
692 ski = afwImage.ImageD(kernel.getDimensions())
693 kernel.computeImage(ski,
False,
int(cand.getXCenter()),
int(cand.getYCenter()))
695 sbg = background(
int(cand.getXCenter()),
int(cand.getYCenter()))
696 sdiffim = cand.getDifferenceImage(sk, sbg)
699 bbox = kernel.shrinkBBox(diffim.getBBox())
700 tdiffim = diffim.Factory(diffim, bbox)
701 torig = orig.Factory(orig, bbox)
702 tsdiffim = sdiffim.Factory(sdiffim, bbox)
705 candidateResids.append(np.ravel(tdiffim.image.array
706 / np.sqrt(torig.variance.array)))
707 spatialResids.append(np.ravel(tsdiffim.image.array
708 / np.sqrt(torig.variance.array)))
710 candidateResids.append(np.ravel(tdiffim.image.array
711 / np.sqrt(tdiffim.variance.array)))
712 spatialResids.append(np.ravel(tsdiffim.image.array
713 / np.sqrt(tsdiffim.variance.array)))
715 fullIm = diffExposure.image.array
716 fullMask = diffExposure.mask.array
718 fullVar = exposure.variance.array
720 fullVar = diffExposure.variance.array
723 bitmaskBad |= afwImage.Mask.getPlaneBitMask(
'NO_DATA')
724 bitmaskBad |= afwImage.Mask.getPlaneBitMask(
'SAT')
725 idx = np.where((fullMask & bitmaskBad) == 0)
726 stride =
int(
len(idx[0])//nptsFull)
727 sidx = idx[0][::stride], idx[1][::stride]
728 allResids = fullIm[sidx]/np.sqrt(fullVar[sidx])
730 testFootprints = diffimTools.sourceToFootprintList(testSources, warpedTemplateExposure,
732 _LOG.getChild(
"plotPixelResiduals"))
733 for fp
in testFootprints:
734 subexp = diffExposure.Factory(diffExposure, fp[
"footprint"].getBBox())
737 subvar = afwImage.ExposureF(exposure, fp[
"footprint"].getBBox()).variance
739 subvar = subexp.variance
740 nonfitResids.append(np.ravel(subim.array/np.sqrt(subvar.array)))
742 candidateResids = np.ravel(np.array(candidateResids))
743 spatialResids = np.ravel(np.array(spatialResids))
744 nonfitResids = np.ravel(np.array(nonfitResids))
748 from matplotlib.font_manager
import FontProperties
749 except ImportError
as e:
750 print(
"Unable to import pylab: %s" % e)
756 fig.canvas._tkcanvas._root().
lift()
760 fig.suptitle(
"Diffim residuals: Normalized by sqrt(input variance)", fontsize=titleFs)
762 fig.suptitle(
"Diffim residuals: Normalized by sqrt(diffim variance)", fontsize=titleFs)
764 sp1 = pylab.subplot(221)
765 sp2 = pylab.subplot(222, sharex=sp1, sharey=sp1)
766 sp3 = pylab.subplot(223, sharex=sp1, sharey=sp1)
767 sp4 = pylab.subplot(224, sharex=sp1, sharey=sp1)
768 xs = np.arange(-5, 5.05, 0.1)
769 ys = 1./np.sqrt(2*np.pi)*np.exp(-0.5*xs**2)
771 sp1.hist(candidateResids, bins=xs, normed=
True, alpha=0.5, label=
"N(%.2f, %.2f)"
772 % (np.mean(candidateResids), np.var(candidateResids)))
773 sp1.plot(xs, ys,
"r-", lw=2, label=
"N(0,1)")
774 sp1.set_title(
"Candidates: basis fit", fontsize=titleFs - 2)
775 sp1.legend(loc=1, fancybox=
True, shadow=
True, prop=
FontProperties(size=titleFs - 6))
777 sp2.hist(spatialResids, bins=xs, normed=
True, alpha=0.5, label=
"N(%.2f, %.2f)"
778 % (np.mean(spatialResids), np.var(spatialResids)))
779 sp2.plot(xs, ys,
"r-", lw=2, label=
"N(0,1)")
780 sp2.set_title(
"Candidates: spatial fit", fontsize=titleFs - 2)
781 sp2.legend(loc=1, fancybox=
True, shadow=
True, prop=
FontProperties(size=titleFs - 6))
783 sp3.hist(nonfitResids, bins=xs, normed=
True, alpha=0.5, label=
"N(%.2f, %.2f)"
784 % (np.mean(nonfitResids), np.var(nonfitResids)))
785 sp3.plot(xs, ys,
"r-", lw=2, label=
"N(0,1)")
786 sp3.set_title(
"Control sample: spatial fit", fontsize=titleFs - 2)
787 sp3.legend(loc=1, fancybox=
True, shadow=
True, prop=
FontProperties(size=titleFs - 6))
789 sp4.hist(allResids, bins=xs, normed=
True, alpha=0.5, label=
"N(%.2f, %.2f)"
790 % (np.mean(allResids), np.var(allResids)))
791 sp4.plot(xs, ys,
"r-", lw=2, label=
"N(0,1)")
792 sp4.set_title(
"Full image (subsampled)", fontsize=titleFs - 2)
793 sp4.legend(loc=1, fancybox=
True, shadow=
True, prop=
FontProperties(size=titleFs - 6))
795 pylab.setp(sp1.get_xticklabels() + sp1.get_yticklabels(), fontsize=titleFs - 4)
796 pylab.setp(sp2.get_xticklabels() + sp2.get_yticklabels(), fontsize=titleFs - 4)
797 pylab.setp(sp3.get_xticklabels() + sp3.get_yticklabels(), fontsize=titleFs - 4)
798 pylab.setp(sp4.get_xticklabels() + sp4.get_yticklabels(), fontsize=titleFs - 4)
805 if keepPlots
and not keptPlots:
808 print(
"%s: Please close plots when done." % __name__)
813 print(
"Plots closed, exiting...")
815 atexit.register(show)
820 """Calculate first moment of a (kernel) image.
824 xarr = np.asarray([[el
for el
in range(x)]
for el2
in range(y)])
825 yarr = np.asarray([[el2
for el
in range(x)]
for el2
in range(y)])
828 centx = narr.sum()/sarrSum
830 centy = narr.sum()/sarrSum
835 """Calculate second moment of a (kernel) image.
840 xarr = np.asarray([[el
for el
in range(x)]
for el2
in range(y)])
841 yarr = np.asarray([[el2
for el
in range(x)]
for el2
in range(y)])
842 narr = sarr*np.power((xarr - centx), 2.)
844 xstd = np.sqrt(narr.sum()/sarrSum)
845 narr = sarr*np.power((yarr - centy), 2.)
846 ystd = np.sqrt(narr.sum()/sarrSum)
851 """Print differences in sky coordinates.
853 The difference is that between the source Position and its Centroid mapped
857 sCentroid = s.getCentroid()
858 sPosition = s.getCoord().getPosition(geom.degrees)
859 dra = 3600*(sPosition.getX() - wcs.pixelToSky(sCentroid).getPosition(geom.degrees).getX())/0.2
860 ddec = 3600*(sPosition.getY() - wcs.pixelToSky(sCentroid).getPosition(geom.degrees).getY())/0.2
861 if np.isfinite(dra)
and np.isfinite(ddec):
866 """Create regions file for display from input source list.
868 fh = open(outfilename,
"w")
869 fh.write(
"global color=red font=\"helvetica 10 normal\" "
870 "select=1 highlite=1 edit=1 move=1 delete=1 include=1 fixed=0 source\nfk5\n")
873 (ra, dec) = wcs.pixelToSky(s.getCentroid()).getPosition(geom.degrees)
875 (ra, dec) = s.getCoord().getPosition(geom.degrees)
876 if np.isfinite(ra)
and np.isfinite(dec):
877 fh.write(
"circle(%f,%f,2\")\n"%(ra, dec))
883 """Draw the (RA, Dec) positions of a set of Sources. Image has the XY0.
885 disp = afwDisplay.Display(frame=frame)
886 with disp.Buffering():
888 (xc, yc) = wcs.skyToPixel(s.getCoord().getRa(), s.getCoord().getDec())
891 disp.dot(symb, xc, yc, ctype=ctype, size=size)
895 """Plot whisker diagram of astromeric offsets between results.matches.
897 refCoordKey = results.matches[0].first.getTable().getCoordKey()
898 inCentroidKey = results.matches[0].second.getTable().getCentroidSlot().getMeasKey()
899 positions = [m.first.get(refCoordKey)
for m
in results.matches]
901 newWcs.pixelToSky(m.second.get(inCentroidKey)))
for
902 m
in results.matches]
903 import matplotlib.pyplot
as plt
905 sp = fig.add_subplot(1, 1, 0)
906 xpos = [x[0].asDegrees()
for x
in positions]
907 ypos = [x[1].asDegrees()
for x
in positions]
908 xpos.append(0.02*(max(xpos) - min(xpos)) + min(xpos))
909 ypos.append(0.98*(max(ypos) - min(ypos)) + min(ypos))
910 xidxs = np.isfinite(xpos)
911 yidxs = np.isfinite(ypos)
912 X = np.asarray(xpos)[xidxs]
913 Y = np.asarray(ypos)[yidxs]
914 distance = [x[1].asArcseconds()
for x
in residuals]
916 distance = np.asarray(distance)[xidxs]
919 bearing = [x[0].asRadians()
for x
in residuals]
921 bearing = np.asarray(bearing)[xidxs]
922 U = (distance*np.cos(bearing))
923 V = (distance*np.sin(bearing))
924 sp.quiver(X, Y, U, V)
925 sp.set_title(
"WCS Residual")
930 """Utility class for dipole measurement testing.
932 Generate an image with simulated dipoles and noise; store the original
933 "pre-subtraction" images and catalogs as well.
934 Used to generate test data for DMTN-007 (http://dmtn-007.lsst.io).
937 def __init__(self, w=101, h=101, xcenPos=[27.], ycenPos=[25.], xcenNeg=[23.], ycenNeg=[25.],
938 psfSigma=2., flux=[30000.], fluxNeg=None, noise=10., gradientParams=None):
955 """Generate an exposure and catalog with the given dipole source(s).
964 dipole = posImage.clone()
965 di = dipole.getMaskedImage()
966 di -= negImage.getMaskedImage()
969 = dipole, posImage, posCatalog, negImage, negCatalog
971 def _makeStarImage(self, xc=[15.3], yc=[18.6], flux=[2500], schema=None, randomSeed=None):
972 """Generate an exposure and catalog with the given stellar source(s).
976 dataset = TestDataset(bbox, psfSigma=self.
psfSigma, threshold=1.)
978 for i
in range(
len(xc)):
979 dataset.addSource(instFlux=flux[i], centroid=
geom.Point2D(xc[i], yc[i]))
982 schema = TestDataset.makeMinimalSchema()
983 exposure, catalog = dataset.realize(noise=self.
noise, schema=schema, randomSeed=randomSeed)
986 y, x = np.mgrid[:self.
w, :self.
h]
988 gradient = gp[0] + gp[1]*x + gp[2]*y
990 gradient += gp[3]*x*y + gp[4]*x*x + gp[5]*y*y
991 imgArr = exposure.image.array
994 return exposure, catalog
998 fitResult = alg.fitDipole(source, **kwds)
1002 """Utility function for detecting dipoles.
1004 Detect pos/neg sources in the diffim, then merge them. A
1005 bigger "grow" parameter leads to a larger footprint which
1006 helps with dipole measurement for faint dipoles.
1011 Whether to merge the positive and negagive detections into a single
1013 diffim : `lsst.afw.image.exposure.exposure.ExposureF`
1014 Difference image on which to perform detection.
1015 detectSigma : `float`
1016 Threshold for object detection.
1018 Number of pixels to grow the footprints before merging.
1020 Minimum bin size for the background (re)estimation (only applies if
1021 the default leads to min(nBinX, nBinY) < fit order so the default
1022 config parameter needs to be decreased, but not to a value smaller
1023 than ``minBinSize``, in which case the fitting algorithm will take
1024 over and decrease the fit order appropriately.)
1028 sources : `lsst.afw.table.SourceCatalog`
1029 If doMerge=True, the merged source catalog is returned OR
1030 detectTask : `lsst.meas.algorithms.SourceDetectionTask`
1031 schema : `lsst.afw.table.Schema`
1032 If doMerge=False, the source detection task and its schema are
1039 schema = afwTable.SourceTable.makeMinimalSchema()
1042 detectConfig = measAlg.SourceDetectionConfig()
1043 detectConfig.returnOriginalFootprints =
False
1046 detectConfig.thresholdPolarity =
"both"
1047 detectConfig.thresholdValue = detectSigma
1049 detectConfig.reEstimateBackground =
True
1050 detectConfig.thresholdType =
"pixel_stdev"
1051 detectConfig.excludeMaskPlanes = [
"EDGE"]
1053 while ((min(diffim.getWidth(), diffim.getHeight()))//detectConfig.background.binSize
1054 < detectConfig.background.approxOrderX
and detectConfig.background.binSize > minBinSize):
1055 detectConfig.background.binSize = max(minBinSize, detectConfig.background.binSize//2)
1058 detectTask = measAlg.SourceDetectionTask(schema, config=detectConfig)
1060 table = afwTable.SourceTable.make(schema)
1061 catalog = detectTask.run(table, diffim)
1065 fpSet = catalog.positive
1066 fpSet.merge(catalog.negative, grow, grow,
False)
1067 sources = afwTable.SourceCatalog(table)
1068 fpSet.makeSources(sources)
1073 return detectTask, schema
1077 vec = image.take(peaks[1 - axis], axis=axis)
1078 low = np.interp(threshold, vec[:peaks[axis] + 1], np.arange(peaks[axis] + 1))
1079 high = np.interp(threshold, vec[:peaks[axis] - 1:-1], np.arange(
len(vec) - 1, peaks[axis] - 1, -1))
1084 """Directly calculate the horizontal and vertical widths
1085 of a PSF at half its maximum value.
1089 psf : `~lsst.afw.detection.Psf`
1090 Point spread function (PSF) to evaluate.
1091 average : `bool`, optional
1092 Set to return the average width over Y and X axes.
1093 position : `~lsst.geom.Point2D`, optional
1094 The position at which to evaluate the PSF. If `None`, then the
1095 average position is used.
1099 psfSize : `float` | `tuple` [`float`]
1100 The FWHM of the PSF computed at its average position.
1101 Returns the widths along the Y and X axes,
1102 or the average of the two if `average` is set.
1108 if position
is None:
1109 position = psf.getAveragePosition()
1110 image = psf.computeKernelImage(position).array
1111 peak = psf.computePeak(position)
1112 peakLocs = np.unravel_index(np.argmax(image), image.shape)
1114 return np.nanmean(width)
if average
else width
1118 fwhmExposureBuffer: float, fwhmExposureGrid: int) -> float:
1119 """Get the mean PSF FWHM by evaluating it on a grid within an exposure.
1123 exposure : `~lsst.afw.image.Exposure`
1124 The exposure for which the mean FWHM of the PSF is to be computed.
1125 The exposure must contain a `psf` attribute.
1126 fwhmExposureBuffer : `float`
1127 Fractional buffer margin to be left out of all sides of the image
1128 during the construction of the grid to compute mean PSF FWHM in an
1130 fwhmExposureGrid : `int`
1131 Grid size to compute the mean FWHM in an exposure.
1136 The mean PSF FWHM on the exposure.
1141 Raised if the PSF cannot be computed at any of the grid points.
1151 bbox = exposure.getBBox()
1152 xmax, ymax = bbox.getMax()
1153 xmin, ymin = bbox.getMin()
1155 xbuffer = fwhmExposureBuffer*(xmax-xmin)
1156 ybuffer = fwhmExposureBuffer*(ymax-ymin)
1159 for (x, y)
in itertools.product(np.linspace(xmin+xbuffer, xmax-xbuffer, fwhmExposureGrid),
1160 np.linspace(ymin+ybuffer, ymax-ybuffer, fwhmExposureGrid)
1164 fwhm = getPsfFwhm(psf, average=
True, position=pos)
1165 except InvalidParameterError:
1166 _LOG.debug(
"Unable to compute PSF FWHM at position (%f, %f).", x, y)
1172 raise ValueError(
"Unable to compute PSF FWHM at any position on the exposure.")
1174 return np.nanmean(width)
1178 psfExposureBuffer: float, psfExposureGrid: int) -> afwImage.ImageD:
1179 """Get the average PSF by evaluating it on a grid within an exposure.
1183 exposure : `~lsst.afw.image.Exposure`
1184 The exposure for which the average PSF is to be computed.
1185 The exposure must contain a `psf` attribute.
1186 psfExposureBuffer : `float`
1187 Fractional buffer margin to be left out of all sides of the image
1188 during the construction of the grid to compute average PSF in an
1190 psfExposureGrid : `int`
1191 Grid size to compute the average PSF in an exposure.
1195 psfImage : `~lsst.afw.image.Image`
1196 The average PSF across the exposure.
1201 Raised if the PSF cannot be computed at any of the grid points.
1205 `evaluateMeanPsfFwhm`
1210 bbox = exposure.getBBox()
1211 xmax, ymax = bbox.getMax()
1212 xmin, ymin = bbox.getMin()
1214 xbuffer = psfExposureBuffer*(xmax-xmin)
1215 ybuffer = psfExposureBuffer*(ymax-ymin)
1219 for (x, y)
in itertools.product(np.linspace(xmin+xbuffer, xmax-xbuffer, psfExposureGrid),
1220 np.linspace(ymin+ybuffer, ymax-ybuffer, psfExposureGrid)
1224 singleImage = psf.computeKernelImage(pos)
1225 except InvalidParameterError:
1226 _LOG.debug(
"Unable to compute PSF image at position (%f, %f).", x, y)
1229 if psfArray
is None:
1230 psfArray = singleImage.array
1232 psfArray += singleImage.array
1235 if psfArray
is None:
1236 raise ValueError(
"Unable to compute PSF image at any position on the exposure.")
1238 psfImage = afwImage.ImageD(psfArray/nImg)
1243 """Minimal source detection wrapper suitable for unit tests.
1247 exposure : `lsst.afw.image.Exposure`
1248 Exposure on which to run detection/measurement
1249 The exposure is modified in place to set the 'DETECTED' mask plane.
1254 Source catalog containing candidates
1257 schema = afwTable.SourceTable.makeMinimalSchema()
1258 selectDetection = measAlg.SourceDetectionTask(schema=schema)
1259 selectMeasurement = measBase.SingleFrameMeasurementTask(schema=schema)
1260 table = afwTable.SourceTable.make(schema)
1262 detRet = selectDetection.run(
1268 selectSources = detRet.sources
1269 selectMeasurement.run(measCat=selectSources, exposure=exposure)
1271 return selectSources
1275 """Make a fake, affine Wcs.
1279 cdMatrix = np.array([[5.19513851e-05, -2.81124812e-07],
1280 [-3.25186974e-07, -5.19112119e-05]])
1285 noiseSeed=6, fluxLevel=500., fluxRange=2.,
1286 kernelSize=32, templateBorderSize=0,
1293 doApplyCalibration=False,
1297 clearEdgeMask=False,
1299 """Make a reproduceable PSF-convolved exposure for testing.
1303 seed : `int`, optional
1304 Seed value to initialize the random number generator for sources.
1305 nSrc : `int`, optional
1306 Number of sources to simulate.
1307 psfSize : `float`, optional
1308 Width of the PSF of the simulated sources, in pixels.
1309 noiseLevel : `float`, optional
1310 Standard deviation of the noise to add to each pixel.
1311 noiseSeed : `int`, optional
1312 Seed value to initialize the random number generator for noise.
1313 fluxLevel : `float`, optional
1314 Reference flux of the simulated sources.
1315 fluxRange : `float`, optional
1316 Range in flux amplitude of the simulated sources.
1317 kernelSize : `int`, optional
1318 Size in pixels of the kernel for simulating sources.
1319 templateBorderSize : `int`, optional
1320 Size in pixels of the image border used to pad the image.
1321 background : `lsst.afw.math.Chebyshev1Function2D`, optional
1322 Optional background to add to the output image.
1323 xSize, ySize : `int`, optional
1324 Size in pixels of the simulated image.
1325 x0, y0 : `int`, optional
1326 Origin of the image.
1327 calibration : `float`, optional
1328 Conversion factor between instFlux and nJy.
1329 doApplyCalibration : `bool`, optional
1330 Apply the photometric calibration and return the image in nJy?
1331 xLoc, yLoc : `list` of `float`, optional
1332 User-specified coordinates of the simulated sources.
1333 If specified, must have length equal to ``nSrc``
1334 flux : `list` of `float`, optional
1335 User-specified fluxes of the simulated sources.
1336 If specified, must have length equal to ``nSrc``
1337 clearEdgeMask : `bool`, optional
1338 Clear the "EDGE" mask plane after source detection.
1342 modelExposure : `lsst.afw.image.Exposure`
1343 The model image, with the mask and variance planes.
1344 sourceCat : `lsst.afw.table.SourceCatalog`
1345 Catalog of sources detected on the model image.
1350 If `xloc`, `yloc`, or `flux` are supplied with inconsistant lengths.
1354 bufferSize = kernelSize/2 + templateBorderSize + 1
1357 if templateBorderSize > 0:
1358 bbox.grow(templateBorderSize)
1360 rng = np.random.RandomState(seed)
1361 rngNoise = np.random.RandomState(noiseSeed)
1362 x0, y0 = bbox.getBegin()
1363 xSize, ySize = bbox.getDimensions()
1365 xLoc = rng.rand(nSrc)*(xSize - 2*bufferSize) + bufferSize + x0
1367 if len(xLoc) != nSrc:
1368 raise ValueError(
"xLoc must have length equal to nSrc. %f supplied vs %f",
len(xLoc), nSrc)
1370 yLoc = rng.rand(nSrc)*(ySize - 2*bufferSize) + bufferSize + y0
1372 if len(yLoc) != nSrc:
1373 raise ValueError(
"yLoc must have length equal to nSrc. %f supplied vs %f",
len(yLoc), nSrc)
1376 flux = (rng.rand(nSrc)*(fluxRange - 1.) + 1.)*fluxLevel
1378 if len(flux) != nSrc:
1379 raise ValueError(
"flux must have length equal to nSrc. %f supplied vs %f",
len(flux), nSrc)
1380 sigmas = [psfSize
for src
in range(nSrc)]
1381 coordList = list(
zip(xLoc, yLoc, flux, sigmas))
1384 modelExposure = plantSources(bbox, kernelSize, skyLevel, coordList, addPoissonNoise=
False)
1386 noise = rngNoise.randn(ySize, xSize)*noiseLevel
1387 noise -= np.mean(noise)
1388 modelExposure.variance.array = np.sqrt(np.abs(modelExposure.image.array)) + noiseLevel**2
1389 modelExposure.image.array += noise
1394 modelExposure.mask &= ~modelExposure.mask.getPlaneBitMask(
"EDGE")
1396 if background
is not None:
1397 modelExposure.image += background
1398 modelExposure.maskedImage /= calibration
1399 modelExposure.info.setId(seed)
1400 if doApplyCalibration:
1401 modelExposure.maskedImage = modelExposure.photoCalib.calibrateImage(modelExposure.maskedImage)
1403 return modelExposure, sourceCat
1407 """Create a statistics control for configuring calculations on images.
1411 badMaskPlanes : `list` of `str`, optional
1412 List of mask planes to exclude from calculations.
1416 statsControl : ` lsst.afw.math.StatisticsControl`
1417 Statistics control object for configuring calculations on images.
1419 if badMaskPlanes
is None:
1420 badMaskPlanes = (
"INTRP",
"EDGE",
"DETECTED",
"SAT",
"CR",
1421 "BAD",
"NO_DATA",
"DETECTED_NEGATIVE")
1423 statsControl.setNumSigmaClip(3.)
1424 statsControl.setNumIter(3)
1425 statsControl.setAndMask(afwImage.Mask.getPlaneBitMask(badMaskPlanes))
1430 """Calculate a robust mean of the variance plane of an exposure.
1434 image : `lsst.afw.image.Image`
1435 Image or variance plane of an exposure to evaluate.
1436 mask : `lsst.afw.image.Mask`
1437 Mask plane to use for excluding pixels.
1438 statsCtrl : `lsst.afw.math.StatisticsControl`
1439 Statistics control object for configuring the calculation.
1440 statistic : `lsst.afw.math.Property`, optional
1441 The type of statistic to compute. Typical values are
1442 ``afwMath.MEANCLIP`` or ``afwMath.STDEVCLIP``.
1447 The result of the statistic calculated from the unflagged pixels.
1450 return statObj.getValue(statistic)
1454 """Compute the noise equivalent area for an image psf
1458 psf : `lsst.afw.detection.Psf`
1464 psfImg = psf.computeImage(psf.getAveragePosition())
1465 nea = 1./np.sum(psfImg.array**2)
Asseses the quality of a candidate given a spatial kernel and background model.
detectDipoleSources(self, doMerge=True, diffim=None, detectSigma=5.5, grow=3, minBinSize=32)
_makeStarImage(self, xc=[15.3], yc=[18.6], flux=[2500], schema=None, randomSeed=None)
__init__(self, w=101, h=101, xcenPos=[27.], ycenPos=[25.], xcenNeg=[23.], ycenNeg=[25.], psfSigma=2., flux=[30000.], fluxNeg=None, noise=10., gradientParams=None)
fitDipoleSource(self, source, **kwds)
std::shared_ptr< SkyWcs > makeSkyWcs(daf::base::PropertySet &metadata, bool strip=false)
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 >())
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 >())
Statistics makeStatistics(lsst::afw::image::Image< Pixel > const &img, lsst::afw::image::Mask< image::MaskPixel > const &msk, int const flags, StatisticsControl const &sctrl=StatisticsControl())
afwImage.ImageD computeAveragePsf(afwImage.Exposure exposure, float psfExposureBuffer, int psfExposureGrid)
plotKernelSpatialModel(kernel, kernelCellSet, showBadCandidates=True, numSample=128, keepPlots=True, maxCoeff=10)
calcWidth(arr, centx, centy)
_sliceWidth(image, threshold, peaks, axis)
makeStats(badMaskPlanes=None)
showKernelMosaic(bbox, kernel, nx=7, ny=None, frame=None, title=None, showCenter=True, showEllipticity=True)
showSourceSet(sSet, xy0=(0, 0), frame=0, ctype=afwDisplay.GREEN, symb="+", size=2)
showKernelSpatialCells(maskedIm, kernelCellSet, showChi2=False, symb="o", ctype=None, ctypeUnused=None, ctypeBad=None, size=3, frame=None, title="Spatial Cells")
plotWhisker(results, newWcs)
makeRegions(sources, outfilename, wcs=None)
showKernelBasis(kernel, frame=None)
detectTestSources(exposure)
showDiaSources(sources, exposure, isFlagged, isDipole, frame=None)
makeTestImage(seed=5, nSrc=20, psfSize=2., noiseLevel=5., noiseSeed=6, fluxLevel=500., fluxRange=2., kernelSize=32, templateBorderSize=0, background=None, xSize=256, ySize=256, x0=12345, y0=67890, calibration=1., doApplyCalibration=False, xLoc=None, yLoc=None, flux=None, clearEdgeMask=False)
computePSFNoiseEquivalentArea(psf)
plotPixelResiduals(exposure, warpedTemplateExposure, diffExposure, kernelCellSet, kernel, background, testSources, config, origVariance=False, nptsFull=1e6, keepPlots=True, titleFs=14)
showKernelCandidates(kernelCellSet, kernel, background, frame=None, showBadCandidates=True, resids=False, kernels=False)
showSourceSetSky(sSet, wcs, xy0, frame=0, ctype=afwDisplay.GREEN, symb="+", size=2)
plotKernelCoefficients(spatialKernel, kernelCellSet, showBadCandidates=False, keepPlots=True)
getPsfFwhm(psf, average=True, position=None)
float evaluateMeanPsfFwhm(afwImage.Exposure exposure, float fwhmExposureBuffer, int fwhmExposureGrid)
printSkyDiffs(sources, wcs)
computeRobustStatistics(image, mask, statsCtrl, statistic=afwMath.MEANCLIP)