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
44afwDisplay.setDefaultMaskTransparency(75)
47_LOG = getLogger(__name__)
50def showSourceSet(sSet, xy0=(0, 0), frame=0, ctype=afwDisplay.GREEN, symb=
"+", size=2):
51 """Draw the (XAstrom, YAstrom) positions of a set of Sources.
53 Image has the given XY0.
55 disp = afwDisplay.afwDisplay(frame=frame)
56 with disp.Buffering():
58 xc, yc = s.getXAstrom() - xy0[0], s.getYAstrom() - xy0[1]
61 disp.dot(str(s.getId()), xc, yc, ctype=ctype, size=size)
63 disp.dot(symb, xc, yc, ctype=ctype, size=size)
71 ctype=None, ctypeUnused=None, ctypeBad=None, size=3,
72 frame=None, title="Spatial Cells"):
73 """Show the SpatialCells.
75 If symb is something that display.dot understands (e.g. "o"), the top
76 nMaxPerCell candidates will be indicated with that symbol, using ctype
79 disp = afwDisplay.Display(frame=frame)
80 disp.mtv(maskedIm, title=title)
81 with disp.Buffering():
82 origin = [-maskedIm.getX0(), -maskedIm.getY0()]
83 for cell
in kernelCellSet.getCellList():
84 afwDisplay.utils.drawBBox(cell.getBBox(), origin=origin, display=disp)
86 goodies = ctypeBad
is None
87 for cand
in cell.begin(goodies):
88 xc, yc = cand.getXCenter() + origin[0], cand.getYCenter() + origin[1]
89 if cand.getStatus() == afwMath.SpatialCellCandidate.BAD:
91 elif cand.getStatus() == afwMath.SpatialCellCandidate.GOOD:
93 elif cand.getStatus() == afwMath.SpatialCellCandidate.UNKNOWN:
99 disp.dot(symb, xc, yc, ctype=color, size=size)
102 rchi2 = cand.getChi2()
105 disp.dot(
"%d %.1f" % (cand.getId(), rchi2),
106 xc - size, yc - size - 4, ctype=color, size=size)
110 """Display Dia Sources.
116 disp = afwDisplay.Display(frame=frame)
117 for plane
in (
"BAD",
"CR",
"EDGE",
"INTERPOlATED",
"INTRP",
"SAT",
"SATURATED"):
118 disp.setMaskPlaneColor(plane, color=
"ignore")
120 mos = afwDisplay.utils.Mosaic()
121 for i
in range(
len(sources)):
123 badFlag = isFlagged[i]
124 dipoleFlag = isDipole[i]
125 bbox = source.getFootprint().getBBox()
126 stamp = exposure.Factory(exposure, bbox,
True)
127 im = afwDisplay.utils.Mosaic(gutter=1, background=0, mode=
"x")
128 im.append(stamp.getMaskedImage())
129 lab =
"%.1f,%.1f:" % (source.getX(), source.getY())
131 ctype = afwDisplay.RED
134 ctype = afwDisplay.YELLOW
136 if not badFlag
and not dipoleFlag:
137 ctype = afwDisplay.GREEN
139 mos.append(im.makeMosaic(), lab, ctype)
140 title =
"Dia Sources"
141 mosaicImage = mos.makeMosaic(display=disp, title=title)
146 resids=False, kernels=False):
147 """Display the Kernel candidates.
149 If kernel is provided include spatial model and residuals;
150 If chi is True, generate a plot of residuals/sqrt(variance), i.e. chi.
156 mos = afwDisplay.utils.Mosaic(gutter=5, background=0)
158 mos = afwDisplay.utils.Mosaic(gutter=5, background=-1)
160 candidateCenters = []
161 candidateCentersBad = []
163 for cell
in kernelCellSet.getCellList():
164 for cand
in cell.begin(
False):
167 resid = cand.getDifferenceImage(diffimLib.KernelCandidateF.ORIG)
171 rchi2 = cand.getChi2()
175 if not showBadCandidates
and cand.isBad():
178 im_resid = afwDisplay.utils.Mosaic(gutter=1, background=-0.5, mode=
"x")
181 im = cand.getScienceMaskedImage()
182 im = im.Factory(im,
True)
183 im.setXY0(cand.getScienceMaskedImage().getXY0())
186 if (
not resids
and not kernels):
187 im_resid.append(im.Factory(im,
True))
189 im = cand.getTemplateMaskedImage()
190 im = im.Factory(im,
True)
191 im.setXY0(cand.getTemplateMaskedImage().getXY0())
194 if (
not resids
and not kernels):
195 im_resid.append(im.Factory(im,
True))
200 var = var.Factory(var,
True)
201 np.sqrt(var.array, var.array)
204 bbox = kernel.shrinkBBox(resid.getBBox())
205 resid = resid.Factory(resid, bbox, deep=
True)
207 kim = cand.getKernelImage(diffimLib.KernelCandidateF.ORIG).convertF()
208 resid = kim.Factory(kim,
True)
209 im_resid.append(resid)
212 ski = afwImage.ImageD(kernel.getDimensions())
213 kernel.computeImage(ski,
False,
int(cand.getXCenter()),
int(cand.getYCenter()))
217 sbg = background(
int(cand.getXCenter()),
int(cand.getYCenter()))
218 sresid = cand.getDifferenceImage(sk, sbg)
223 bbox = kernel.shrinkBBox(resid.getBBox())
224 resid = resid.Factory(resid, bbox, deep=
True)
227 resid = kim.Factory(kim,
True)
228 im_resid.append(resid)
230 im = im_resid.makeMosaic()
232 lab =
"%d chi^2 %.1f" % (cand.getId(), rchi2)
233 ctype = afwDisplay.RED
if cand.isBad()
else afwDisplay.GREEN
235 mos.append(im, lab, ctype)
237 if False and np.isnan(rchi2):
238 disp = afwDisplay.Display(frame=1)
239 disp.mtv(cand.getScienceMaskedImage.image, title=
"candidate")
240 print(
"rating", cand.getCandidateRating())
242 im = cand.getScienceMaskedImage()
243 center = (candidateIndex, cand.getXCenter() - im.getX0(), cand.getYCenter() - im.getY0())
246 candidateCentersBad.append(center)
248 candidateCenters.append(center)
255 title =
"Candidates & residuals"
257 disp = afwDisplay.Display(frame=frame)
258 mosaicImage = mos.makeMosaic(display=disp, title=title)
264 """Display a Kernel's basis images.
266 mos = afwDisplay.utils.Mosaic()
268 for k
in kernel.getKernelList():
269 im = afwImage.ImageD(k.getDimensions())
270 k.computeImage(im,
False)
273 disp = afwDisplay.Display(frame=frame)
274 mos.makeMosaic(display=disp, title=
"Kernel Basis Images")
282 numSample=128, keepPlots=True, maxCoeff=10):
283 """Plot the Kernel spatial model.
286 import matplotlib.pyplot
as plt
287 import matplotlib.colors
288 except ImportError
as e:
289 print(
"Unable to import numpy and matplotlib: %s" % e)
292 x0 = kernelCellSet.getBBox().getBeginX()
293 y0 = kernelCellSet.getBBox().getBeginY()
301 for cell
in kernelCellSet.getCellList():
302 for cand
in cell.begin(
False):
303 if not showBadCandidates
and cand.isBad():
305 candCenter =
geom.PointD(cand.getXCenter(), cand.getYCenter())
307 im = cand.getTemplateMaskedImage()
311 targetFits = badFits
if cand.isBad()
else candFits
312 targetPos = badPos
if cand.isBad()
else candPos
313 targetAmps = badAmps
if cand.isBad()
else candAmps
316 kp0 = np.array(cand.getKernel(diffimLib.KernelCandidateF.ORIG).getKernelParameters())
317 amp = cand.getCandidateRating()
319 targetFits = badFits
if cand.isBad()
else candFits
320 targetPos = badPos
if cand.isBad()
else candPos
321 targetAmps = badAmps
if cand.isBad()
else candAmps
323 targetFits.append(kp0)
324 targetPos.append(candCenter)
325 targetAmps.append(amp)
327 xGood = np.array([pos.getX()
for pos
in candPos]) - x0
328 yGood = np.array([pos.getY()
for pos
in candPos]) - y0
329 zGood = np.array(candFits)
331 xBad = np.array([pos.getX()
for pos
in badPos]) - x0
332 yBad = np.array([pos.getY()
for pos
in badPos]) - y0
333 zBad = np.array(badFits)
336 xRange = np.linspace(0, kernelCellSet.getBBox().getWidth(), num=numSample)
337 yRange = np.linspace(0, kernelCellSet.getBBox().getHeight(), num=numSample)
340 maxCoeff = min(maxCoeff, kernel.getNKernelParameters())
342 maxCoeff = kernel.getNKernelParameters()
344 for k
in range(maxCoeff):
345 func = kernel.getSpatialFunction(k)
346 dfGood = zGood[:, k] - np.array([
func(pos.getX(), pos.getY())
for pos
in candPos])
350 dfBad = zBad[:, k] - np.array([
func(pos.getX(), pos.getY())
for pos
in badPos])
352 yMin = min([yMin, dfBad.min()])
353 yMax = max([yMax, dfBad.max()])
354 yMin -= 0.05*(yMax - yMin)
355 yMax += 0.05*(yMax - yMin)
357 fRange = np.ndarray((
len(xRange),
len(yRange)))
360 fRange[j][i] =
func(xVal, yVal)
366 fig.canvas._tkcanvas._root().
lift()
370 fig.suptitle(
'Kernel component %d' % k)
373 ax = fig.add_axes((0.1, 0.05, 0.35, 0.35))
376 norm = matplotlib.colors.Normalize(vmin=vmin, vmax=vmax)
377 im = ax.imshow(fRange, aspect=
'auto', norm=norm,
378 extent=[0, kernelCellSet.getBBox().getWidth() - 1,
379 0, kernelCellSet.getBBox().getHeight() - 1])
380 ax.set_title(
'Spatial polynomial')
381 plt.colorbar(im, orientation=
'horizontal', ticks=[vmin, vmax])
384 ax = fig.add_axes((0.1, 0.55, 0.35, 0.35))
385 ax.plot(-2.5*np.log10(candAmps), zGood[:, k],
'b+')
387 ax.plot(-2.5*np.log10(badAmps), zBad[:, k],
'r+')
388 ax.set_title(
"Basis Coefficients")
389 ax.set_xlabel(
"Instr mag")
390 ax.set_ylabel(
"Coeff")
393 ax = fig.add_axes((0.55, 0.05, 0.35, 0.35))
394 ax.set_autoscale_on(
False)
395 ax.set_xbound(lower=0, upper=kernelCellSet.getBBox().getHeight())
396 ax.set_ybound(lower=yMin, upper=yMax)
397 ax.plot(yGood, dfGood,
'b+')
399 ax.plot(yBad, dfBad,
'r+')
401 ax.set_title(
'dCoeff (indiv-spatial) vs. y')
404 ax = fig.add_axes((0.55, 0.55, 0.35, 0.35))
405 ax.set_autoscale_on(
False)
406 ax.set_xbound(lower=0, upper=kernelCellSet.getBBox().getWidth())
407 ax.set_ybound(lower=yMin, upper=yMax)
408 ax.plot(xGood, dfGood,
'b+')
410 ax.plot(xBad, dfBad,
'r+')
412 ax.set_title(
'dCoeff (indiv-spatial) vs. x')
417 if keepPlots
and not keptPlots:
420 print(
"%s: Please close plots when done." % __name__)
425 print(
"Plots closed, exiting...")
427 atexit.register(show)
432 """Plot the individual kernel candidate and the spatial kernel solution coefficients.
437 spatialKernel : `lsst.afw.math.LinearCombinationKernel`
438 The spatial spatialKernel solution model which is a spatially varying linear combination
439 of the spatialKernel basis functions.
440 Typically returned by `lsst.ip.diffim.SpatialKernelSolution.getSolutionPair()`.
442 kernelCellSet : `lsst.afw.math.SpatialCellSet`
443 The spatial cells that was used for solution for the spatialKernel. They contain the
444 local solutions of the AL kernel for the selected sources.
446 showBadCandidates : `bool`, optional
447 If True, plot the coefficient values for kernel candidates where the solution was marked
448 bad by the numerical algorithm. Defaults to False.
450 keepPlots: `bool`, optional
451 If True, sets ``plt.show()`` to be called before the task terminates, so that the plots
452 can be explored interactively. Defaults to True.
456 This function produces 3 figures per image subtraction operation.
457 * A grid plot of the local solutions. Each grid cell corresponds to a proportional area in
458 the image. In each cell, local kernel solution coefficients are plotted of kernel candidates (color)
459 that fall into this area as a function of the kernel basis function number.
460 * A grid plot of the spatial solution. Each grid cell corresponds to a proportional area in
461 the image. In each cell, the spatial solution coefficients are evaluated for the center of the cell.
462 * Histogram of the local solution coefficients. Red line marks the spatial solution value at
465 This function is called if ``lsst.ip.diffim.psfMatch.plotKernelCoefficients==True`` in lsstDebug. This
466 function was implemented as part of DM-17825.
469 import matplotlib.pyplot
as plt
470 except ImportError
as e:
471 print(
"Unable to import matplotlib: %s" % e)
475 imgBBox = kernelCellSet.getBBox()
476 x0 = imgBBox.getBeginX()
477 y0 = imgBBox.getBeginY()
478 wImage = imgBBox.getWidth()
479 hImage = imgBBox.getHeight()
480 imgCenterX = imgBBox.getCenterX()
481 imgCenterY = imgBBox.getCenterY()
493 fig.suptitle(
"Kernel candidate parameters on an image grid")
494 arrAx = fig.subplots(nrows=nY, ncols=nX, sharex=
True, sharey=
True, gridspec_kw=
dict(
498 arrAx = arrAx[::-1, :]
501 for cell
in kernelCellSet.getCellList():
504 iX =
int((cellBBox.getCenterX() - x0)//wCell)
505 iY =
int((cellBBox.getCenterY() - y0)//hCell)
507 for cand
in cell.begin(
False):
509 kernel = cand.getKernel(cand.ORIG)
513 if not showBadCandidates
and cand.isBad():
516 nKernelParams = kernel.getNKernelParameters()
517 kernelParams = np.array(kernel.getKernelParameters())
518 allParams.append(kernelParams)
524 arrAx[iY, iX].plot(np.arange(nKernelParams), kernelParams,
'.-',
525 color=color, drawstyle=
'steps-mid', linewidth=0.1)
526 for ax
in arrAx.ravel():
527 ax.grid(
True, axis=
'y')
532 spatialFuncs = spatialKernel.getSpatialFunctionList()
533 nKernelParams = spatialKernel.getNKernelParameters()
536 fig.suptitle(
"Hist. of parameters marked with spatial solution at img center")
537 arrAx = fig.subplots(nrows=
int(nKernelParams//nX)+1, ncols=nX)
538 arrAx = arrAx[::-1, :]
539 allParams = np.array(allParams)
540 for k
in range(nKernelParams):
541 ax = arrAx.ravel()[k]
542 ax.hist(allParams[:, k], bins=20, edgecolor=
'black')
543 ax.set_xlabel(
'P{}'.format(k))
544 valueParam = spatialFuncs[k](imgCenterX, imgCenterY)
545 ax.axvline(x=valueParam, color=
'red')
546 ax.text(0.1, 0.9,
'{:.1f}'.format(valueParam),
547 transform=ax.transAxes, backgroundcolor=
'lightsteelblue')
560 fig.suptitle(
"Spatial solution of kernel parameters on an image grid")
561 arrAx = fig.subplots(nrows=nY, ncols=nX, sharex=
True, sharey=
True, gridspec_kw=
dict(
563 arrAx = arrAx[::-1, :]
564 kernelParams = np.zeros(nKernelParams, dtype=float)
571 kernelParams = [
f(x, y)
for f
in spatialFuncs]
572 arrAx[iY, iX].plot(np.arange(nKernelParams), kernelParams,
'.-', drawstyle=
'steps-mid')
573 arrAx[iY, iX].grid(
True, axis=
'y')
576 if keepPlots
and not keptPlots:
579 print(
"%s: Please close plots when done." % __name__)
584 print(
"Plots closed, exiting...")
586 atexit.register(show)
591 showCenter=True, showEllipticity=True):
592 """Show a mosaic of Kernel images.
594 mos = afwDisplay.utils.Mosaic()
596 x0 = bbox.getBeginX()
597 y0 = bbox.getBeginY()
598 width = bbox.getWidth()
599 height = bbox.getHeight()
602 ny =
int(nx*
float(height)/width + 0.5)
606 schema = afwTable.SourceTable.makeMinimalSchema()
607 centroidName =
"base_SdssCentroid"
608 shapeName =
"base_SdssShape"
609 control = measBase.SdssCentroidControl()
610 schema.getAliasMap().set(
"slot_Centroid", centroidName)
611 schema.getAliasMap().set(
"slot_Centroid_flag", centroidName +
"_flag")
612 centroider = measBase.SdssCentroidAlgorithm(control, centroidName, schema)
613 sdssShape = measBase.SdssShapeControl()
614 shaper = measBase.SdssShapeAlgorithm(sdssShape, shapeName, schema)
615 table = afwTable.SourceTable.make(schema)
616 table.defineCentroid(centroidName)
617 table.defineShape(shapeName)
623 x =
int(ix*(width - 1)/(nx - 1)) + x0
624 y =
int(iy*(height - 1)/(ny - 1)) + y0
626 im = afwImage.ImageD(kernel.getDimensions())
627 ksum = kernel.computeImage(im,
False, x, y)
628 lab =
"Kernel(%d,%d)=%.2f" % (x, y, ksum)
if False else ""
634 w, h = im.getWidth(), im.getHeight()
635 centerX = im.getX0() + w//2
636 centerY = im.getY0() + h//2
637 src = table.makeRecord()
640 foot.addPeak(centerX, centerY, 1)
641 src.setFootprint(foot)
644 centroider.measure(src, exp)
645 centers.append((src.getX(), src.getY()))
647 shaper.measure(src, exp)
648 shapes.append((src.getIxx(), src.getIxy(), src.getIyy()))
652 disp = afwDisplay.Display(frame=frame)
653 mos.makeMosaic(display=disp, title=title
if title
else "Model Kernel", mode=nx)
655 if centers
and frame
is not None:
656 disp = afwDisplay.Display(frame=frame)
658 with disp.Buffering():
659 for cen, shape
in zip(centers, shapes):
660 bbox = mos.getBBox(i)
662 xc, yc = cen[0] + bbox.getMinX(), cen[1] + bbox.getMinY()
664 disp.dot(
"+", xc, yc, ctype=afwDisplay.BLUE)
667 ixx, ixy, iyy = shape
668 disp.dot(
"@:%g,%g,%g" % (ixx, ixy, iyy), xc, yc, ctype=afwDisplay.RED)
674 """Plot whisker diagram of astromeric offsets between results.matches.
676 refCoordKey = results.matches[0].first.getTable().getCoordKey()
677 inCentroidKey = results.matches[0].second.getTable().getCentroidSlot().getMeasKey()
678 positions = [m.first.get(refCoordKey)
for m
in results.matches]
680 newWcs.pixelToSky(m.second.get(inCentroidKey)))
for
681 m
in results.matches]
682 import matplotlib.pyplot
as plt
684 sp = fig.add_subplot(1, 1, 0)
685 xpos = [x[0].asDegrees()
for x
in positions]
686 ypos = [x[1].asDegrees()
for x
in positions]
687 xpos.append(0.02*(max(xpos) - min(xpos)) + min(xpos))
688 ypos.append(0.98*(max(ypos) - min(ypos)) + min(ypos))
689 xidxs = np.isfinite(xpos)
690 yidxs = np.isfinite(ypos)
691 X = np.asarray(xpos)[xidxs]
692 Y = np.asarray(ypos)[yidxs]
693 distance = [x[1].asArcseconds()
for x
in residuals]
695 distance = np.asarray(distance)[xidxs]
698 bearing = [x[0].asRadians()
for x
in residuals]
700 bearing = np.asarray(bearing)[xidxs]
701 U = (distance*np.cos(bearing))
702 V = (distance*np.sin(bearing))
703 sp.quiver(X, Y, U, V)
704 sp.set_title(
"WCS Residual")
709 """Utility class for dipole measurement testing.
711 Generate an image with simulated dipoles and noise; store the original
712 "pre-subtraction" images and catalogs as well.
713 Used to generate test data for DMTN-007 (http://dmtn-007.lsst.io).
716 def __init__(self, w=101, h=101, xcenPos=[27.], ycenPos=[25.], xcenNeg=[23.], ycenNeg=[25.],
717 psfSigma=2., flux=[30000.], fluxNeg=None, noise=10., gradientParams=None):
734 """Generate an exposure and catalog with the given dipole source(s).
743 dipole = posImage.clone()
744 di = dipole.getMaskedImage()
745 di -= negImage.getMaskedImage()
748 = dipole, posImage, posCatalog, negImage, negCatalog
750 def _makeStarImage(self, xc=[15.3], yc=[18.6], flux=[2500], schema=None, randomSeed=None):
751 """Generate an exposure and catalog with the given stellar source(s).
755 dataset = TestDataset(bbox, psfSigma=self.
psfSigma, threshold=1.)
757 for i
in range(
len(xc)):
758 dataset.addSource(instFlux=flux[i], centroid=
geom.Point2D(xc[i], yc[i]))
761 schema = TestDataset.makeMinimalSchema()
762 exposure, catalog = dataset.realize(noise=self.
noise, schema=schema, randomSeed=randomSeed)
765 y, x = np.mgrid[:self.
w, :self.
h]
767 gradient = gp[0] + gp[1]*x + gp[2]*y
769 gradient += gp[3]*x*y + gp[4]*x*x + gp[5]*y*y
770 imgArr = exposure.image.array
773 return exposure, catalog
777 fitResult = alg.fitDipole(source, **kwds)
781 """Utility function for detecting dipoles.
783 Detect pos/neg sources in the diffim, then merge them. A
784 bigger "grow" parameter leads to a larger footprint which
785 helps with dipole measurement for faint dipoles.
790 Whether to merge the positive and negagive detections into a single
792 diffim : `lsst.afw.image.exposure.exposure.ExposureF`
793 Difference image on which to perform detection.
794 detectSigma : `float`
795 Threshold for object detection.
797 Number of pixels to grow the footprints before merging.
799 Minimum bin size for the background (re)estimation (only applies if
800 the default leads to min(nBinX, nBinY) < fit order so the default
801 config parameter needs to be decreased, but not to a value smaller
802 than ``minBinSize``, in which case the fitting algorithm will take
803 over and decrease the fit order appropriately.)
807 sources : `lsst.afw.table.SourceCatalog`
808 If doMerge=True, the merged source catalog is returned OR
809 detectTask : `lsst.meas.algorithms.SourceDetectionTask`
810 schema : `lsst.afw.table.Schema`
811 If doMerge=False, the source detection task and its schema are
818 schema = afwTable.SourceTable.makeMinimalSchema()
821 detectConfig = measAlg.SourceDetectionConfig()
822 detectConfig.returnOriginalFootprints =
False
825 detectConfig.thresholdPolarity =
"both"
826 detectConfig.thresholdValue = detectSigma
828 detectConfig.reEstimateBackground =
True
829 detectConfig.thresholdType =
"pixel_stdev"
830 detectConfig.excludeMaskPlanes = [
"EDGE"]
832 while ((min(diffim.getWidth(), diffim.getHeight()))//detectConfig.background.binSize
833 < detectConfig.background.approxOrderX
and detectConfig.background.binSize > minBinSize):
834 detectConfig.background.binSize = max(minBinSize, detectConfig.background.binSize//2)
837 detectTask = measAlg.SourceDetectionTask(schema, config=detectConfig)
839 table = afwTable.SourceTable.make(schema)
840 catalog = detectTask.run(table, diffim)
844 fpSet = catalog.positive
845 fpSet.merge(catalog.negative, grow, grow,
False)
846 sources = afwTable.SourceCatalog(table)
847 fpSet.makeSources(sources)
852 return detectTask, schema
856 vec = image.take(peaks[1 - axis], axis=axis)
857 low = np.interp(threshold, vec[:peaks[axis] + 1], np.arange(peaks[axis] + 1))
858 high = np.interp(threshold, vec[:peaks[axis] - 1:-1], np.arange(
len(vec) - 1, peaks[axis] - 1, -1))
863 """Directly calculate the horizontal and vertical widths
864 of a PSF at half its maximum value.
868 psf : `~lsst.afw.detection.Psf`
869 Point spread function (PSF) to evaluate.
870 average : `bool`, optional
871 Set to return the average width over Y and X axes.
872 position : `~lsst.geom.Point2D`, optional
873 The position at which to evaluate the PSF. If `None`, then the
874 average position is used.
878 psfSize : `float` | `tuple` [`float`]
879 The FWHM of the PSF computed at its average position.
880 Returns the widths along the Y and X axes,
881 or the average of the two if `average` is set.
888 position = psf.getAveragePosition()
889 image = psf.computeKernelImage(position).array
890 peak = psf.computePeak(position)
891 peakLocs = np.unravel_index(np.argmax(image), image.shape)
893 return np.nanmean(width)
if average
else width
897 fwhmExposureBuffer: float, fwhmExposureGrid: int) -> float:
898 """Get the mean PSF FWHM by evaluating it on a grid within an exposure.
902 exposure : `~lsst.afw.image.Exposure`
903 The exposure for which the mean FWHM of the PSF is to be computed.
904 The exposure must contain a `psf` attribute.
905 fwhmExposureBuffer : `float`
906 Fractional buffer margin to be left out of all sides of the image
907 during the construction of the grid to compute mean PSF FWHM in an
909 fwhmExposureGrid : `int`
910 Grid size to compute the mean FWHM in an exposure.
915 The mean PSF FWHM on the exposure.
920 Raised if the PSF cannot be computed at any of the grid points.
930 bbox = exposure.getBBox()
931 xmax, ymax = bbox.getMax()
932 xmin, ymin = bbox.getMin()
934 xbuffer = fwhmExposureBuffer*(xmax-xmin)
935 ybuffer = fwhmExposureBuffer*(ymax-ymin)
938 for (x, y)
in itertools.product(np.linspace(xmin+xbuffer, xmax-xbuffer, fwhmExposureGrid),
939 np.linspace(ymin+ybuffer, ymax-ybuffer, fwhmExposureGrid)
943 fwhm = getPsfFwhm(psf, average=
True, position=pos)
944 except InvalidParameterError:
945 _LOG.debug(
"Unable to compute PSF FWHM at position (%f, %f).", x, y)
951 raise ValueError(
"Unable to compute PSF FWHM at any position on the exposure.")
953 return np.nanmean(width)
957 psfExposureBuffer: float, psfExposureGrid: int) -> afwImage.ImageD:
958 """Get the average PSF by evaluating it on a grid within an exposure.
962 exposure : `~lsst.afw.image.Exposure`
963 The exposure for which the average PSF is to be computed.
964 The exposure must contain a `psf` attribute.
965 psfExposureBuffer : `float`
966 Fractional buffer margin to be left out of all sides of the image
967 during the construction of the grid to compute average PSF in an
969 psfExposureGrid : `int`
970 Grid size to compute the average PSF in an exposure.
974 psfImage : `~lsst.afw.image.Image`
975 The average PSF across the exposure.
980 Raised if the PSF cannot be computed at any of the grid points.
984 `evaluateMeanPsfFwhm`
989 bbox = exposure.getBBox()
990 xmax, ymax = bbox.getMax()
991 xmin, ymin = bbox.getMin()
993 xbuffer = psfExposureBuffer*(xmax-xmin)
994 ybuffer = psfExposureBuffer*(ymax-ymin)
998 for (x, y)
in itertools.product(np.linspace(xmin+xbuffer, xmax-xbuffer, psfExposureGrid),
999 np.linspace(ymin+ybuffer, ymax-ybuffer, psfExposureGrid)
1003 singleImage = psf.computeKernelImage(pos)
1004 except InvalidParameterError:
1005 _LOG.debug(
"Unable to compute PSF image at position (%f, %f).", x, y)
1008 if psfArray
is None:
1009 psfArray = singleImage.array
1011 psfArray += singleImage.array
1014 if psfArray
is None:
1015 raise ValueError(
"Unable to compute PSF image at any position on the exposure.")
1017 psfImage = afwImage.ImageD(psfArray/nImg)
1022 """Minimal source detection wrapper suitable for unit tests.
1026 exposure : `lsst.afw.image.Exposure`
1027 Exposure on which to run detection/measurement
1028 The exposure is modified in place to set the 'DETECTED' mask plane.
1029 addMaskPlanes : `list` of `str`, optional
1030 Additional mask planes to add to the maskedImage of the exposure.
1035 Source catalog containing candidates
1037 if addMaskPlanes
is None:
1040 addMaskPlanes = [
"STREAK",
"INJECTED",
"INJECTED_TEMPLATE"]
1042 schema = afwTable.SourceTable.makeMinimalSchema()
1043 selectDetection = measAlg.SourceDetectionTask(schema=schema)
1044 selectMeasurement = measBase.SingleFrameMeasurementTask(schema=schema)
1045 table = afwTable.SourceTable.make(schema)
1047 detRet = selectDetection.run(
1053 for mp
in addMaskPlanes:
1054 exposure.mask.addMaskPlane(mp)
1056 selectSources = detRet.sources
1057 selectMeasurement.run(measCat=selectSources, exposure=exposure)
1059 return selectSources
1063 """Make a fake, affine Wcs.
1067 cdMatrix = np.array([[5.19513851e-05, -2.81124812e-07],
1068 [-3.25186974e-07, -5.19112119e-05]])
1073 noiseSeed=6, fluxLevel=500., fluxRange=2.,
1074 kernelSize=32, templateBorderSize=0,
1081 doApplyCalibration=False,
1085 clearEdgeMask=False,
1088 """Make a reproduceable PSF-convolved exposure for testing.
1092 seed : `int`, optional
1093 Seed value to initialize the random number generator for sources.
1094 nSrc : `int`, optional
1095 Number of sources to simulate.
1096 psfSize : `float`, optional
1097 Width of the PSF of the simulated sources, in pixels.
1098 noiseLevel : `float`, optional
1099 Standard deviation of the noise to add to each pixel.
1100 noiseSeed : `int`, optional
1101 Seed value to initialize the random number generator for noise.
1102 fluxLevel : `float`, optional
1103 Reference flux of the simulated sources.
1104 fluxRange : `float`, optional
1105 Range in flux amplitude of the simulated sources.
1106 kernelSize : `int`, optional
1107 Size in pixels of the kernel for simulating sources.
1108 templateBorderSize : `int`, optional
1109 Size in pixels of the image border used to pad the image.
1110 background : `lsst.afw.math.Chebyshev1Function2D`, optional
1111 Optional background to add to the output image.
1112 xSize, ySize : `int`, optional
1113 Size in pixels of the simulated image.
1114 x0, y0 : `int`, optional
1115 Origin of the image.
1116 calibration : `float`, optional
1117 Conversion factor between instFlux and nJy.
1118 doApplyCalibration : `bool`, optional
1119 Apply the photometric calibration and return the image in nJy?
1120 xLoc, yLoc : `list` of `float`, optional
1121 User-specified coordinates of the simulated sources.
1122 If specified, must have length equal to ``nSrc``
1123 flux : `list` of `float`, optional
1124 User-specified fluxes of the simulated sources.
1125 If specified, must have length equal to ``nSrc``
1126 clearEdgeMask : `bool`, optional
1127 Clear the "EDGE" mask plane after source detection.
1128 addMaskPlanes : `list` of `str`, optional
1129 Mask plane names to add to the image.
1133 modelExposure : `lsst.afw.image.Exposure`
1134 The model image, with the mask and variance planes.
1135 sourceCat : `lsst.afw.table.SourceCatalog`
1136 Catalog of sources detected on the model image.
1141 If `xloc`, `yloc`, or `flux` are supplied with inconsistant lengths.
1145 bufferSize = kernelSize/2 + templateBorderSize + 1
1148 if templateBorderSize > 0:
1149 bbox.grow(templateBorderSize)
1151 rng = np.random.RandomState(seed)
1152 rngNoise = np.random.RandomState(noiseSeed)
1153 x0, y0 = bbox.getBegin()
1154 xSize, ySize = bbox.getDimensions()
1156 xLoc = rng.rand(nSrc)*(xSize - 2*bufferSize) + bufferSize + x0
1158 if len(xLoc) != nSrc:
1159 raise ValueError(
"xLoc must have length equal to nSrc. %f supplied vs %f",
len(xLoc), nSrc)
1161 yLoc = rng.rand(nSrc)*(ySize - 2*bufferSize) + bufferSize + y0
1163 if len(yLoc) != nSrc:
1164 raise ValueError(
"yLoc must have length equal to nSrc. %f supplied vs %f",
len(yLoc), nSrc)
1167 flux = (rng.rand(nSrc)*(fluxRange - 1.) + 1.)*fluxLevel
1169 if len(flux) != nSrc:
1170 raise ValueError(
"flux must have length equal to nSrc. %f supplied vs %f",
len(flux), nSrc)
1171 sigmas = [psfSize
for src
in range(nSrc)]
1172 coordList = list(
zip(xLoc, yLoc, flux, sigmas))
1175 modelExposure = plantSources(bbox, kernelSize, skyLevel, coordList, addPoissonNoise=
False)
1177 noise = rngNoise.randn(ySize, xSize)*noiseLevel
1178 noise -= np.mean(noise)
1179 modelExposure.variance.array = np.sqrt(np.abs(modelExposure.image.array)) + noiseLevel**2
1180 modelExposure.image.array += noise
1185 modelExposure.mask &= ~modelExposure.mask.getPlaneBitMask(
"EDGE")
1187 if background
is not None:
1188 modelExposure.image += background
1189 modelExposure.maskedImage /= calibration
1190 modelExposure.info.setId(seed)
1191 if doApplyCalibration:
1192 modelExposure.maskedImage = modelExposure.photoCalib.calibrateImage(modelExposure.maskedImage)
1194 return modelExposure, sourceCat
1198 """Create a statistics control for configuring calculations on images.
1202 badMaskPlanes : `list` of `str`, optional
1203 List of mask planes to exclude from calculations.
1207 statsControl : ` lsst.afw.math.StatisticsControl`
1208 Statistics control object for configuring calculations on images.
1210 if badMaskPlanes
is None:
1211 badMaskPlanes = (
"INTRP",
"EDGE",
"DETECTED",
"SAT",
"CR",
1212 "BAD",
"NO_DATA",
"DETECTED_NEGATIVE")
1214 statsControl.setNumSigmaClip(3.)
1215 statsControl.setNumIter(3)
1216 statsControl.setAndMask(afwImage.Mask.getPlaneBitMask(badMaskPlanes))
1221 """Calculate a robust mean of the variance plane of an exposure.
1225 image : `lsst.afw.image.Image`
1226 Image or variance plane of an exposure to evaluate.
1227 mask : `lsst.afw.image.Mask`
1228 Mask plane to use for excluding pixels.
1229 statsCtrl : `lsst.afw.math.StatisticsControl`
1230 Statistics control object for configuring the calculation.
1231 statistic : `lsst.afw.math.Property`, optional
1232 The type of statistic to compute. Typical values are
1233 ``afwMath.MEANCLIP`` or ``afwMath.STDEVCLIP``.
1238 The result of the statistic calculated from the unflagged pixels.
1241 return statObj.getValue(statistic)
1245 """Compute the noise equivalent area for an image psf
1249 psf : `lsst.afw.detection.Psf`
1255 psfImg = psf.computeImage(psf.getAveragePosition())
1256 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)
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, addMaskPlanes=None)
plotKernelSpatialModel(kernel, kernelCellSet, showBadCandidates=True, numSample=128, keepPlots=True, maxCoeff=10)
_sliceWidth(image, threshold, peaks, axis)
detectTestSources(exposure, addMaskPlanes=None)
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)
showKernelBasis(kernel, frame=None)
showDiaSources(sources, exposure, isFlagged, isDipole, frame=None)
computePSFNoiseEquivalentArea(psf)
showKernelCandidates(kernelCellSet, kernel, background, frame=None, showBadCandidates=True, resids=False, kernels=False)
plotKernelCoefficients(spatialKernel, kernelCellSet, showBadCandidates=False, keepPlots=True)
getPsfFwhm(psf, average=True, position=None)
float evaluateMeanPsfFwhm(afwImage.Exposure exposure, float fwhmExposureBuffer, int fwhmExposureGrid)
computeRobustStatistics(image, mask, statsCtrl, statistic=afwMath.MEANCLIP)