lsst.ip.diffim g470c3a02f1+7eb6ad230c
Loading...
Searching...
No Matches
utils.py
Go to the documentation of this file.
1# This file is part of ip_diffim.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (https://www.lsst.org).
6# See the COPYRIGHT file at the top-level directory of this distribution
7# for details of code ownership.
8#
9# This program is free software: you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation, either version 3 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program. If not, see <https://www.gnu.org/licenses/>.
21
22"""Support utilities for Measuring sources"""
23
24# Export DipoleTestImage to expose fake image generating funcs
25__all__ = ["DipoleTestImage", "evaluateMeanPsfFwhm", "getPsfFwhm"]
26
27import itertools
28import numpy as np
29import lsst.geom as geom
30import lsst.afw.detection as afwDet
31import lsst.afw.display as afwDisplay
32import lsst.afw.geom as afwGeom
33import lsst.afw.image as afwImage
34import lsst.afw.math as afwMath
35import lsst.afw.table as afwTable
36import lsst.meas.algorithms as measAlg
37import lsst.meas.base as measBase
38from lsst.meas.algorithms.testUtils import plantSources
39from lsst.pex.exceptions import InvalidParameterError
40from lsst.utils.logging import getLogger
41from .dipoleFitTask import DipoleFitAlgorithm
42from . import diffimLib
43
44afwDisplay.setDefaultMaskTransparency(75)
45keptPlots = False # Have we arranged to keep spatial plots open?
46
47_LOG = getLogger(__name__)
48
49
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.
52
53 Image has the given XY0.
54 """
55 disp = afwDisplay.afwDisplay(frame=frame)
56 with disp.Buffering():
57 for s in sSet:
58 xc, yc = s.getXAstrom() - xy0[0], s.getYAstrom() - xy0[1]
59
60 if symb == "id":
61 disp.dot(str(s.getId()), xc, yc, ctype=ctype, size=size)
62 else:
63 disp.dot(symb, xc, yc, ctype=ctype, size=size)
64
65
66# Kernel display utilities
67#
68
69
70def showKernelSpatialCells(maskedIm, kernelCellSet, showChi2=False, symb="o",
71 ctype=None, ctypeUnused=None, ctypeBad=None, size=3,
72 frame=None, title="Spatial Cells"):
73 """Show the SpatialCells.
74
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
77 and size.
78 """
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)
85
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:
90 color = ctypeBad
91 elif cand.getStatus() == afwMath.SpatialCellCandidate.GOOD:
92 color = ctype
93 elif cand.getStatus() == afwMath.SpatialCellCandidate.UNKNOWN:
94 color = ctypeUnused
95 else:
96 continue
97
98 if color:
99 disp.dot(symb, xc, yc, ctype=color, size=size)
100
101 if showChi2:
102 rchi2 = cand.getChi2()
103 if rchi2 > 1e100:
104 rchi2 = np.nan
105 disp.dot("%d %.1f" % (cand.getId(), rchi2),
106 xc - size, yc - size - 4, ctype=color, size=size)
107
108
109def showDiaSources(sources, exposure, isFlagged, isDipole, frame=None):
110 """Display Dia Sources.
111 """
112 #
113 # Show us the ccandidates
114 #
115 # Too many mask planes in diffims
116 disp = afwDisplay.Display(frame=frame)
117 for plane in ("BAD", "CR", "EDGE", "INTERPOlATED", "INTRP", "SAT", "SATURATED"):
118 disp.setMaskPlaneColor(plane, color="ignore")
119
120 mos = afwDisplay.utils.Mosaic()
121 for i in range(len(sources)):
122 source = sources[i]
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())
130 if badFlag:
131 ctype = afwDisplay.RED
132 lab += "BAD"
133 if dipoleFlag:
134 ctype = afwDisplay.YELLOW
135 lab += "DIPOLE"
136 if not badFlag and not dipoleFlag:
137 ctype = afwDisplay.GREEN
138 lab += "OK"
139 mos.append(im.makeMosaic(), lab, ctype)
140 title = "Dia Sources"
141 mosaicImage = mos.makeMosaic(display=disp, title=title)
142 return mosaicImage
143
144
145def showKernelCandidates(kernelCellSet, kernel, background, frame=None, showBadCandidates=True,
146 resids=False, kernels=False):
147 """Display the Kernel candidates.
148
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.
151 """
152 #
153 # Show us the ccandidates
154 #
155 if kernels:
156 mos = afwDisplay.utils.Mosaic(gutter=5, background=0)
157 else:
158 mos = afwDisplay.utils.Mosaic(gutter=5, background=-1)
159 #
160 candidateCenters = []
161 candidateCentersBad = []
162 candidateIndex = 0
163 for cell in kernelCellSet.getCellList():
164 for cand in cell.begin(False): # include bad candidates
165 # Original difference image; if does not exist, skip candidate
166 try:
167 resid = cand.getDifferenceImage(diffimLib.KernelCandidateF.ORIG)
168 except Exception:
169 continue
170
171 rchi2 = cand.getChi2()
172 if rchi2 > 1e100:
173 rchi2 = np.nan
174
175 if not showBadCandidates and cand.isBad():
176 continue
177
178 im_resid = afwDisplay.utils.Mosaic(gutter=1, background=-0.5, mode="x")
179
180 try:
181 im = cand.getScienceMaskedImage()
182 im = im.Factory(im, True)
183 im.setXY0(cand.getScienceMaskedImage().getXY0())
184 except Exception:
185 continue
186 if (not resids and not kernels):
187 im_resid.append(im.Factory(im, True))
188 try:
189 im = cand.getTemplateMaskedImage()
190 im = im.Factory(im, True)
191 im.setXY0(cand.getTemplateMaskedImage().getXY0())
192 except Exception:
193 continue
194 if (not resids and not kernels):
195 im_resid.append(im.Factory(im, True))
196
197 # Difference image with original basis
198 if resids:
199 var = resid.variance
200 var = var.Factory(var, True)
201 np.sqrt(var.array, var.array) # inplace sqrt
202 resid = resid.image
203 resid /= var
204 bbox = kernel.shrinkBBox(resid.getBBox())
205 resid = resid.Factory(resid, bbox, deep=True)
206 elif kernels:
207 kim = cand.getKernelImage(diffimLib.KernelCandidateF.ORIG).convertF()
208 resid = kim.Factory(kim, True)
209 im_resid.append(resid)
210
211 # residuals using spatial model
212 ski = afwImage.ImageD(kernel.getDimensions())
213 kernel.computeImage(ski, False, int(cand.getXCenter()), int(cand.getYCenter()))
214 sk = afwMath.FixedKernel(ski)
215 sbg = 0.0
216 if background:
217 sbg = background(int(cand.getXCenter()), int(cand.getYCenter()))
218 sresid = cand.getDifferenceImage(sk, sbg)
219 resid = sresid
220 if resids:
221 resid = sresid.image
222 resid /= var
223 bbox = kernel.shrinkBBox(resid.getBBox())
224 resid = resid.Factory(resid, bbox, deep=True)
225 elif kernels:
226 kim = ski.convertF()
227 resid = kim.Factory(kim, True)
228 im_resid.append(resid)
229
230 im = im_resid.makeMosaic()
231
232 lab = "%d chi^2 %.1f" % (cand.getId(), rchi2)
233 ctype = afwDisplay.RED if cand.isBad() else afwDisplay.GREEN
234
235 mos.append(im, lab, ctype)
236
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())
241
242 im = cand.getScienceMaskedImage()
243 center = (candidateIndex, cand.getXCenter() - im.getX0(), cand.getYCenter() - im.getY0())
244 candidateIndex += 1
245 if cand.isBad():
246 candidateCentersBad.append(center)
247 else:
248 candidateCenters.append(center)
249
250 if resids:
251 title = "chi Diffim"
252 elif kernels:
253 title = "Kernels"
254 else:
255 title = "Candidates & residuals"
256
257 disp = afwDisplay.Display(frame=frame)
258 mosaicImage = mos.makeMosaic(display=disp, title=title)
259
260 return mosaicImage
261
262
263def showKernelBasis(kernel, frame=None):
264 """Display a Kernel's basis images.
265 """
266 mos = afwDisplay.utils.Mosaic()
267
268 for k in kernel.getKernelList():
269 im = afwImage.ImageD(k.getDimensions())
270 k.computeImage(im, False)
271 mos.append(im)
272
273 disp = afwDisplay.Display(frame=frame)
274 mos.makeMosaic(display=disp, title="Kernel Basis Images")
275
276 return mos
277
278
279
280
281def plotKernelSpatialModel(kernel, kernelCellSet, showBadCandidates=True,
282 numSample=128, keepPlots=True, maxCoeff=10):
283 """Plot the Kernel spatial model.
284 """
285 try:
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)
290 return
291
292 x0 = kernelCellSet.getBBox().getBeginX()
293 y0 = kernelCellSet.getBBox().getBeginY()
294
295 candPos = list()
296 candFits = list()
297 badPos = list()
298 badFits = list()
299 candAmps = list()
300 badAmps = list()
301 for cell in kernelCellSet.getCellList():
302 for cand in cell.begin(False):
303 if not showBadCandidates and cand.isBad():
304 continue
305 candCenter = geom.PointD(cand.getXCenter(), cand.getYCenter())
306 try:
307 im = cand.getTemplateMaskedImage()
308 except Exception:
309 continue
310
311 targetFits = badFits if cand.isBad() else candFits
312 targetPos = badPos if cand.isBad() else candPos
313 targetAmps = badAmps if cand.isBad() else candAmps
314
315 # compare original and spatial kernel coefficients
316 kp0 = np.array(cand.getKernel(diffimLib.KernelCandidateF.ORIG).getKernelParameters())
317 amp = cand.getCandidateRating()
318
319 targetFits = badFits if cand.isBad() else candFits
320 targetPos = badPos if cand.isBad() else candPos
321 targetAmps = badAmps if cand.isBad() else candAmps
322
323 targetFits.append(kp0)
324 targetPos.append(candCenter)
325 targetAmps.append(amp)
326
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)
330
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)
334 numBad = len(badPos)
335
336 xRange = np.linspace(0, kernelCellSet.getBBox().getWidth(), num=numSample)
337 yRange = np.linspace(0, kernelCellSet.getBBox().getHeight(), num=numSample)
338
339 if maxCoeff:
340 maxCoeff = min(maxCoeff, kernel.getNKernelParameters())
341 else:
342 maxCoeff = kernel.getNKernelParameters()
343
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])
347 yMin = dfGood.min()
348 yMax = dfGood.max()
349 if numBad > 0:
350 dfBad = zBad[:, k] - np.array([func(pos.getX(), pos.getY()) for pos in badPos])
351 # Can really screw up the range...
352 yMin = min([yMin, dfBad.min()])
353 yMax = max([yMax, dfBad.max()])
354 yMin -= 0.05*(yMax - yMin)
355 yMax += 0.05*(yMax - yMin)
356
357 fRange = np.ndarray((len(xRange), len(yRange)))
358 for j, yVal in enumerate(yRange):
359 for i, xVal in enumerate(xRange):
360 fRange[j][i] = func(xVal, yVal)
361
362 fig = plt.figure(k)
363
364 fig.clf()
365 try:
366 fig.canvas._tkcanvas._root().lift() # == Tk's raise, but raise is a python reserved word
367 except Exception: # protect against API changes
368 pass
369
370 fig.suptitle('Kernel component %d' % k)
371
372 # LL
373 ax = fig.add_axes((0.1, 0.05, 0.35, 0.35))
374 vmin = fRange.min() # - 0.05*np.fabs(fRange.min())
375 vmax = fRange.max() # + 0.05*np.fabs(fRange.max())
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])
382
383 # UL
384 ax = fig.add_axes((0.1, 0.55, 0.35, 0.35))
385 ax.plot(-2.5*np.log10(candAmps), zGood[:, k], 'b+')
386 if numBad > 0:
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")
391
392 # LR
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+')
398 if numBad > 0:
399 ax.plot(yBad, dfBad, 'r+')
400 ax.axhline(0.0)
401 ax.set_title('dCoeff (indiv-spatial) vs. y')
402
403 # UR
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+')
409 if numBad > 0:
410 ax.plot(xBad, dfBad, 'r+')
411 ax.axhline(0.0)
412 ax.set_title('dCoeff (indiv-spatial) vs. x')
413
414 fig.show()
415
416 global keptPlots
417 if keepPlots and not keptPlots:
418 # Keep plots open when done
419 def show():
420 print("%s: Please close plots when done." % __name__)
421 try:
422 plt.show()
423 except Exception:
424 pass
425 print("Plots closed, exiting...")
426 import atexit
427 atexit.register(show)
428 keptPlots = True
429
430
431def plotKernelCoefficients(spatialKernel, kernelCellSet, showBadCandidates=False, keepPlots=True):
432 """Plot the individual kernel candidate and the spatial kernel solution coefficients.
433
434 Parameters
435 ----------
436
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()`.
441
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.
445
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.
449
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.
453
454 Notes
455 -----
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
463 center of the image.
464
465 This function is called if ``lsst.ip.diffim.psfMatch.plotKernelCoefficients==True`` in lsstDebug. This
466 function was implemented as part of DM-17825.
467 """
468 try:
469 import matplotlib.pyplot as plt
470 except ImportError as e:
471 print("Unable to import matplotlib: %s" % e)
472 return
473
474 # Image dimensions
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()
482
483 # Plot the local solutions
484 # ----
485
486 # Grid size
487 nX = 8
488 nY = 8
489 wCell = wImage / nX
490 hCell = hImage / nY
491
492 fig = plt.figure()
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(
495 wspace=0, hspace=0))
496
497 # Bottom left panel is for bottom left part of the image
498 arrAx = arrAx[::-1, :]
499
500 allParams = []
501 for cell in kernelCellSet.getCellList():
502 cellBBox = geom.Box2D(cell.getBBox())
503 # Determine which panel this spatial cell belongs to
504 iX = int((cellBBox.getCenterX() - x0)//wCell)
505 iY = int((cellBBox.getCenterY() - y0)//hCell)
506
507 for cand in cell.begin(False):
508 try:
509 kernel = cand.getKernel(cand.ORIG)
510 except Exception:
511 continue
512
513 if not showBadCandidates and cand.isBad():
514 continue
515
516 nKernelParams = kernel.getNKernelParameters()
517 kernelParams = np.array(kernel.getKernelParameters())
518 allParams.append(kernelParams)
519
520 if cand.isBad():
521 color = 'red'
522 else:
523 color = None
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')
528
529 # Plot histogram of the local parameters and the global solution at the image center
530 # ----
531
532 spatialFuncs = spatialKernel.getSpatialFunctionList()
533 nKernelParams = spatialKernel.getNKernelParameters()
534 nX = 8
535 fig = plt.figure()
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')
548
549 # Plot grid of the spatial solution
550 # ----
551
552 nX = 8
553 nY = 8
554 wCell = wImage / nX
555 hCell = hImage / nY
556 x0 += wCell / 2
557 y0 += hCell / 2
558
559 fig = plt.figure()
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(
562 wspace=0, hspace=0))
563 arrAx = arrAx[::-1, :]
564 kernelParams = np.zeros(nKernelParams, dtype=float)
565
566 for iX in range(nX):
567 for iY in range(nY):
568 x = x0 + iX * wCell
569 y = y0 + iY * hCell
570 # Evaluate the spatial solution functions for this x,y location
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')
574
575 global keptPlots
576 if keepPlots and not keptPlots:
577 # Keep plots open when done
578 def show():
579 print("%s: Please close plots when done." % __name__)
580 try:
581 plt.show()
582 except Exception:
583 pass
584 print("Plots closed, exiting...")
585 import atexit
586 atexit.register(show)
587 keptPlots = True
588
589
590def showKernelMosaic(bbox, kernel, nx=7, ny=None, frame=None, title=None,
591 showCenter=True, showEllipticity=True):
592 """Show a mosaic of Kernel images.
593 """
594 mos = afwDisplay.utils.Mosaic()
595
596 x0 = bbox.getBeginX()
597 y0 = bbox.getBeginY()
598 width = bbox.getWidth()
599 height = bbox.getHeight()
600
601 if not ny:
602 ny = int(nx*float(height)/width + 0.5)
603 if not ny:
604 ny = 1
605
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)
618
619 centers = []
620 shapes = []
621 for iy in range(ny):
622 for ix in range(nx):
623 x = int(ix*(width - 1)/(nx - 1)) + x0
624 y = int(iy*(height - 1)/(ny - 1)) + y0
625
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 ""
629 mos.append(im, lab)
630
631 # SdssCentroidAlgorithm.measure requires an exposure of floats
633
634 w, h = im.getWidth(), im.getHeight()
635 centerX = im.getX0() + w//2
636 centerY = im.getY0() + h//2
637 src = table.makeRecord()
638 spans = afwGeom.SpanSet(exp.getBBox())
639 foot = afwDet.Footprint(spans)
640 foot.addPeak(centerX, centerY, 1)
641 src.setFootprint(foot)
642
643 try: # The centroider requires a psf, so this will fail if none is attached to exp
644 centroider.measure(src, exp)
645 centers.append((src.getX(), src.getY()))
646
647 shaper.measure(src, exp)
648 shapes.append((src.getIxx(), src.getIxy(), src.getIyy()))
649 except Exception:
650 pass
651
652 disp = afwDisplay.Display(frame=frame)
653 mos.makeMosaic(display=disp, title=title if title else "Model Kernel", mode=nx)
654
655 if centers and frame is not None:
656 disp = afwDisplay.Display(frame=frame)
657 i = 0
658 with disp.Buffering():
659 for cen, shape in zip(centers, shapes):
660 bbox = mos.getBBox(i)
661 i += 1
662 xc, yc = cen[0] + bbox.getMinX(), cen[1] + bbox.getMinY()
663 if showCenter:
664 disp.dot("+", xc, yc, ctype=afwDisplay.BLUE)
665
666 if showEllipticity:
667 ixx, ixy, iyy = shape
668 disp.dot("@:%g,%g,%g" % (ixx, ixy, iyy), xc, yc, ctype=afwDisplay.RED)
669
670 return mos
671
672
673def plotWhisker(results, newWcs):
674 """Plot whisker diagram of astromeric offsets between results.matches.
675 """
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]
679 residuals = [m.first.get(refCoordKey).getOffsetFrom(
680 newWcs.pixelToSky(m.second.get(inCentroidKey))) for
681 m in results.matches]
682 import matplotlib.pyplot as plt
683 fig = plt.figure()
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]
694 distance.append(0.2)
695 distance = np.asarray(distance)[xidxs]
696 # NOTE: This assumes that the bearing is measured positive from +RA through North.
697 # From the documentation this is not clear.
698 bearing = [x[0].asRadians() for x in residuals]
699 bearing.append(0)
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")
705 plt.show()
706
707
708class DipoleTestImage(object):
709 """Utility class for dipole measurement testing.
710
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).
714 """
715
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):
718 self.w = w
719 self.h = h
720 self.xcenPos = xcenPos
721 self.ycenPos = ycenPos
722 self.xcenNeg = xcenNeg
723 self.ycenNeg = ycenNeg
724 self.psfSigma = psfSigma
725 self.flux = flux
726 self.fluxNeg = fluxNeg
727 if fluxNeg is None:
728 self.fluxNeg = self.flux
729 self.noise = noise
730 self.gradientParams = gradientParams
731 self._makeDipoleImage()
732
734 """Generate an exposure and catalog with the given dipole source(s).
735 """
736 # Must seed the pos/neg images with different values to ensure they get different noise realizations
737 posImage, posCatalog = self._makeStarImage(
738 xc=self.xcenPos, yc=self.ycenPos, flux=self.flux, randomSeed=111)
739
740 negImage, negCatalog = self._makeStarImage(
741 xc=self.xcenNeg, yc=self.ycenNeg, flux=self.fluxNeg, randomSeed=222)
742
743 dipole = posImage.clone()
744 di = dipole.getMaskedImage()
745 di -= negImage.getMaskedImage()
746
747 self.diffim, self.posImage, self.posCatalog, self.negImage, self.negCatalog \
748 = dipole, posImage, posCatalog, negImage, negCatalog
749
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).
752 """
753 from lsst.meas.base.tests import TestDataset
754 bbox = geom.Box2I(geom.Point2I(0, 0), geom.Point2I(self.w - 1, self.h - 1))
755 dataset = TestDataset(bbox, psfSigma=self.psfSigma, threshold=1.)
756
757 for i in range(len(xc)):
758 dataset.addSource(instFlux=flux[i], centroid=geom.Point2D(xc[i], yc[i]))
759
760 if schema is None:
761 schema = TestDataset.makeMinimalSchema()
762 exposure, catalog = dataset.realize(noise=self.noise, schema=schema, randomSeed=randomSeed)
763
764 if self.gradientParams is not None:
765 y, x = np.mgrid[:self.w, :self.h]
766 gp = self.gradientParams
767 gradient = gp[0] + gp[1]*x + gp[2]*y
768 if len(self.gradientParams) > 3: # it includes a set of 2nd-order polynomial params
769 gradient += gp[3]*x*y + gp[4]*x*x + gp[5]*y*y
770 imgArr = exposure.image.array
771 imgArr += gradient
772
773 return exposure, catalog
774
775 def fitDipoleSource(self, source, **kwds):
776 alg = DipoleFitAlgorithm(self.diffim, self.posImage, self.negImage)
777 fitResult = alg.fitDipole(source, **kwds)
778 return fitResult
779
780 def detectDipoleSources(self, doMerge=True, diffim=None, detectSigma=5.5, grow=3, minBinSize=32):
781 """Utility function for detecting dipoles.
782
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.
786
787 Parameters
788 ----------
789 doMerge : `bool`
790 Whether to merge the positive and negagive detections into a single
791 source table.
792 diffim : `lsst.afw.image.exposure.exposure.ExposureF`
793 Difference image on which to perform detection.
794 detectSigma : `float`
795 Threshold for object detection.
796 grow : `int`
797 Number of pixels to grow the footprints before merging.
798 minBinSize : `int`
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.)
804
805 Returns
806 -------
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
812 returned.
813 """
814 if diffim is None:
815 diffim = self.diffim
816
817 # Start with a minimal schema - only the fields all SourceCatalogs need
818 schema = afwTable.SourceTable.makeMinimalSchema()
819
820 # Customize the detection task a bit (optional)
821 detectConfig = measAlg.SourceDetectionConfig()
822 detectConfig.returnOriginalFootprints = False # should be the default
823
824 # code from imageDifference.py:
825 detectConfig.thresholdPolarity = "both"
826 detectConfig.thresholdValue = detectSigma
827 # detectConfig.nSigmaToGrow = psfSigma
828 detectConfig.reEstimateBackground = True # if False, will fail often for faint sources on gradients?
829 detectConfig.thresholdType = "pixel_stdev"
830 detectConfig.excludeMaskPlanes = ["EDGE"]
831 # Test images are often quite small, so may need to adjust background binSize
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)
835
836 # Create the detection task. We pass the schema so the task can declare a few flag fields
837 detectTask = measAlg.SourceDetectionTask(schema, config=detectConfig)
838
839 table = afwTable.SourceTable.make(schema)
840 catalog = detectTask.run(table, diffim)
841
842 # Now do the merge.
843 if doMerge:
844 fpSet = catalog.positive
845 fpSet.merge(catalog.negative, grow, grow, False)
846 sources = afwTable.SourceCatalog(table)
847 fpSet.makeSources(sources)
848
849 return sources
850
851 else:
852 return detectTask, schema
853
854
855def _sliceWidth(image, threshold, peaks, axis):
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))
859 return high - low
860
861
862def getPsfFwhm(psf, average=True, position=None):
863 """Directly calculate the horizontal and vertical widths
864 of a PSF at half its maximum value.
865
866 Parameters
867 ----------
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.
875
876 Returns
877 -------
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.
882
883 See Also
884 --------
885 evaluateMeanPsfFwhm
886 """
887 if position is None:
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)
892 width = _sliceWidth(image, peak/2., peakLocs, axis=0), _sliceWidth(image, peak/2., peakLocs, axis=1)
893 return np.nanmean(width) if average else width
894
895
896def evaluateMeanPsfFwhm(exposure: afwImage.Exposure,
897 fwhmExposureBuffer: float, fwhmExposureGrid: int) -> float:
898 """Get the mean PSF FWHM by evaluating it on a grid within an exposure.
899
900 Parameters
901 ----------
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
908 exposure.
909 fwhmExposureGrid : `int`
910 Grid size to compute the mean FWHM in an exposure.
911
912 Returns
913 -------
914 meanFwhm : `float`
915 The mean PSF FWHM on the exposure.
916
917 Raises
918 ------
919 ValueError
920 Raised if the PSF cannot be computed at any of the grid points.
921
922 See Also
923 --------
924 `getPsfFwhm`
925 `computeAveragePsf`
926 """
927
928 psf = exposure.psf
929
930 bbox = exposure.getBBox()
931 xmax, ymax = bbox.getMax()
932 xmin, ymin = bbox.getMin()
933
934 xbuffer = fwhmExposureBuffer*(xmax-xmin)
935 ybuffer = fwhmExposureBuffer*(ymax-ymin)
936
937 width = []
938 for (x, y) in itertools.product(np.linspace(xmin+xbuffer, xmax-xbuffer, fwhmExposureGrid),
939 np.linspace(ymin+ybuffer, ymax-ybuffer, fwhmExposureGrid)
940 ):
941 pos = geom.Point2D(x, y)
942 try:
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)
946 continue
947
948 width.append(fwhm)
949
950 if not width:
951 raise ValueError("Unable to compute PSF FWHM at any position on the exposure.")
952
953 return np.nanmean(width)
954
955
956def computeAveragePsf(exposure: afwImage.Exposure,
957 psfExposureBuffer: float, psfExposureGrid: int) -> afwImage.ImageD:
958 """Get the average PSF by evaluating it on a grid within an exposure.
959
960 Parameters
961 ----------
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
968 exposure.
969 psfExposureGrid : `int`
970 Grid size to compute the average PSF in an exposure.
971
972 Returns
973 -------
974 psfImage : `~lsst.afw.image.Image`
975 The average PSF across the exposure.
976
977 Raises
978 ------
979 ValueError
980 Raised if the PSF cannot be computed at any of the grid points.
981
982 See Also
983 --------
984 `evaluateMeanPsfFwhm`
985 """
986
987 psf = exposure.psf
988
989 bbox = exposure.getBBox()
990 xmax, ymax = bbox.getMax()
991 xmin, ymin = bbox.getMin()
992
993 xbuffer = psfExposureBuffer*(xmax-xmin)
994 ybuffer = psfExposureBuffer*(ymax-ymin)
995
996 nImg = 0
997 psfArray = None
998 for (x, y) in itertools.product(np.linspace(xmin+xbuffer, xmax-xbuffer, psfExposureGrid),
999 np.linspace(ymin+ybuffer, ymax-ybuffer, psfExposureGrid)
1000 ):
1001 pos = geom.Point2D(x, y)
1002 try:
1003 singleImage = psf.computeKernelImage(pos)
1004 except InvalidParameterError:
1005 _LOG.debug("Unable to compute PSF image at position (%f, %f).", x, y)
1006 continue
1007
1008 if psfArray is None:
1009 psfArray = singleImage.array
1010 else:
1011 psfArray += singleImage.array
1012 nImg += 1
1013
1014 if psfArray is None:
1015 raise ValueError("Unable to compute PSF image at any position on the exposure.")
1016
1017 psfImage = afwImage.ImageD(psfArray/nImg)
1018 return psfImage
1019
1020
1021def detectTestSources(exposure):
1022 """Minimal source detection wrapper suitable for unit tests.
1023
1024 Parameters
1025 ----------
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
1030 Returns
1031 -------
1032 selectSources :
1033 Source catalog containing candidates
1034 """
1035
1036 schema = afwTable.SourceTable.makeMinimalSchema()
1037 selectDetection = measAlg.SourceDetectionTask(schema=schema)
1038 selectMeasurement = measBase.SingleFrameMeasurementTask(schema=schema)
1039 table = afwTable.SourceTable.make(schema)
1040
1041 detRet = selectDetection.run(
1042 table=table,
1043 exposure=exposure,
1044 sigma=None, # The appropriate sigma is calculated from the PSF
1045 doSmooth=True
1046 )
1047 exposure.mask.addMaskPlane("STREAK") # add empty streak mask plane in lieu of maskStreaksTask
1048 exposure.mask.addMaskPlane("INJECTED") # add empty injected mask plane
1049 exposure.mask.addMaskPlane("INJECTED_TEMPLATE") # add empty injected template mask plane
1050
1051 selectSources = detRet.sources
1052 selectMeasurement.run(measCat=selectSources, exposure=exposure)
1053
1054 return selectSources
1055
1056
1058 """Make a fake, affine Wcs.
1059 """
1060 crpix = geom.Point2D(123.45, 678.9)
1061 crval = geom.SpherePoint(0.1, 0.1, geom.degrees)
1062 cdMatrix = np.array([[5.19513851e-05, -2.81124812e-07],
1063 [-3.25186974e-07, -5.19112119e-05]])
1064 return afwGeom.makeSkyWcs(crpix, crval, cdMatrix)
1065
1066
1067def makeTestImage(seed=5, nSrc=20, psfSize=2., noiseLevel=5.,
1068 noiseSeed=6, fluxLevel=500., fluxRange=2.,
1069 kernelSize=32, templateBorderSize=0,
1070 background=None,
1071 xSize=256,
1072 ySize=256,
1073 x0=12345,
1074 y0=67890,
1075 calibration=1.,
1076 doApplyCalibration=False,
1077 xLoc=None,
1078 yLoc=None,
1079 flux=None,
1080 clearEdgeMask=False,
1081 ):
1082 """Make a reproduceable PSF-convolved exposure for testing.
1083
1084 Parameters
1085 ----------
1086 seed : `int`, optional
1087 Seed value to initialize the random number generator for sources.
1088 nSrc : `int`, optional
1089 Number of sources to simulate.
1090 psfSize : `float`, optional
1091 Width of the PSF of the simulated sources, in pixels.
1092 noiseLevel : `float`, optional
1093 Standard deviation of the noise to add to each pixel.
1094 noiseSeed : `int`, optional
1095 Seed value to initialize the random number generator for noise.
1096 fluxLevel : `float`, optional
1097 Reference flux of the simulated sources.
1098 fluxRange : `float`, optional
1099 Range in flux amplitude of the simulated sources.
1100 kernelSize : `int`, optional
1101 Size in pixels of the kernel for simulating sources.
1102 templateBorderSize : `int`, optional
1103 Size in pixels of the image border used to pad the image.
1104 background : `lsst.afw.math.Chebyshev1Function2D`, optional
1105 Optional background to add to the output image.
1106 xSize, ySize : `int`, optional
1107 Size in pixels of the simulated image.
1108 x0, y0 : `int`, optional
1109 Origin of the image.
1110 calibration : `float`, optional
1111 Conversion factor between instFlux and nJy.
1112 doApplyCalibration : `bool`, optional
1113 Apply the photometric calibration and return the image in nJy?
1114 xLoc, yLoc : `list` of `float`, optional
1115 User-specified coordinates of the simulated sources.
1116 If specified, must have length equal to ``nSrc``
1117 flux : `list` of `float`, optional
1118 User-specified fluxes of the simulated sources.
1119 If specified, must have length equal to ``nSrc``
1120 clearEdgeMask : `bool`, optional
1121 Clear the "EDGE" mask plane after source detection.
1122
1123 Returns
1124 -------
1125 modelExposure : `lsst.afw.image.Exposure`
1126 The model image, with the mask and variance planes.
1127 sourceCat : `lsst.afw.table.SourceCatalog`
1128 Catalog of sources detected on the model image.
1129
1130 Raises
1131 ------
1132 ValueError
1133 If `xloc`, `yloc`, or `flux` are supplied with inconsistant lengths.
1134 """
1135 # Distance from the inner edge of the bounding box to avoid placing test
1136 # sources in the model images.
1137 bufferSize = kernelSize/2 + templateBorderSize + 1
1138
1139 bbox = geom.Box2I(geom.Point2I(x0, y0), geom.Extent2I(xSize, ySize))
1140 if templateBorderSize > 0:
1141 bbox.grow(templateBorderSize)
1142
1143 rng = np.random.RandomState(seed)
1144 rngNoise = np.random.RandomState(noiseSeed)
1145 x0, y0 = bbox.getBegin()
1146 xSize, ySize = bbox.getDimensions()
1147 if xLoc is None:
1148 xLoc = rng.rand(nSrc)*(xSize - 2*bufferSize) + bufferSize + x0
1149 else:
1150 if len(xLoc) != nSrc:
1151 raise ValueError("xLoc must have length equal to nSrc. %f supplied vs %f", len(xLoc), nSrc)
1152 if yLoc is None:
1153 yLoc = rng.rand(nSrc)*(ySize - 2*bufferSize) + bufferSize + y0
1154 else:
1155 if len(yLoc) != nSrc:
1156 raise ValueError("yLoc must have length equal to nSrc. %f supplied vs %f", len(yLoc), nSrc)
1157
1158 if flux is None:
1159 flux = (rng.rand(nSrc)*(fluxRange - 1.) + 1.)*fluxLevel
1160 else:
1161 if len(flux) != nSrc:
1162 raise ValueError("flux must have length equal to nSrc. %f supplied vs %f", len(flux), nSrc)
1163 sigmas = [psfSize for src in range(nSrc)]
1164 coordList = list(zip(xLoc, yLoc, flux, sigmas))
1165 skyLevel = 0
1166 # Don't use the built in poisson noise: it modifies the global state of numpy random
1167 modelExposure = plantSources(bbox, kernelSize, skyLevel, coordList, addPoissonNoise=False)
1168 modelExposure.setWcs(makeFakeWcs())
1169 noise = rngNoise.randn(ySize, xSize)*noiseLevel
1170 noise -= np.mean(noise)
1171 modelExposure.variance.array = np.sqrt(np.abs(modelExposure.image.array)) + noiseLevel**2
1172 modelExposure.image.array += noise
1173
1174 # Run source detection to set up the mask plane
1175 sourceCat = detectTestSources(modelExposure)
1176 if clearEdgeMask:
1177 modelExposure.mask &= ~modelExposure.mask.getPlaneBitMask("EDGE")
1178 modelExposure.setPhotoCalib(afwImage.PhotoCalib(calibration, 0., bbox))
1179 if background is not None:
1180 modelExposure.image += background
1181 modelExposure.maskedImage /= calibration
1182 modelExposure.info.setId(seed)
1183 if doApplyCalibration:
1184 modelExposure.maskedImage = modelExposure.photoCalib.calibrateImage(modelExposure.maskedImage)
1185
1186 return modelExposure, sourceCat
1187
1188
1189def makeStats(badMaskPlanes=None):
1190 """Create a statistics control for configuring calculations on images.
1191
1192 Parameters
1193 ----------
1194 badMaskPlanes : `list` of `str`, optional
1195 List of mask planes to exclude from calculations.
1196
1197 Returns
1198 -------
1199 statsControl : ` lsst.afw.math.StatisticsControl`
1200 Statistics control object for configuring calculations on images.
1201 """
1202 if badMaskPlanes is None:
1203 badMaskPlanes = ("INTRP", "EDGE", "DETECTED", "SAT", "CR",
1204 "BAD", "NO_DATA", "DETECTED_NEGATIVE")
1205 statsControl = afwMath.StatisticsControl()
1206 statsControl.setNumSigmaClip(3.)
1207 statsControl.setNumIter(3)
1208 statsControl.setAndMask(afwImage.Mask.getPlaneBitMask(badMaskPlanes))
1209 return statsControl
1210
1211
1212def computeRobustStatistics(image, mask, statsCtrl, statistic=afwMath.MEANCLIP):
1213 """Calculate a robust mean of the variance plane of an exposure.
1214
1215 Parameters
1216 ----------
1217 image : `lsst.afw.image.Image`
1218 Image or variance plane of an exposure to evaluate.
1219 mask : `lsst.afw.image.Mask`
1220 Mask plane to use for excluding pixels.
1221 statsCtrl : `lsst.afw.math.StatisticsControl`
1222 Statistics control object for configuring the calculation.
1223 statistic : `lsst.afw.math.Property`, optional
1224 The type of statistic to compute. Typical values are
1225 ``afwMath.MEANCLIP`` or ``afwMath.STDEVCLIP``.
1226
1227 Returns
1228 -------
1229 value : `float`
1230 The result of the statistic calculated from the unflagged pixels.
1231 """
1232 statObj = afwMath.makeStatistics(image, mask, statistic, statsCtrl)
1233 return statObj.getValue(statistic)
1234
1235
1237 """Compute the noise equivalent area for an image psf
1238
1239 Parameters
1240 ----------
1241 psf : `lsst.afw.detection.Psf`
1242
1243 Returns
1244 -------
1245 nea : `float`
1246 """
1247 psfImg = psf.computeImage(psf.getAveragePosition())
1248 nea = 1./np.sum(psfImg.array**2)
1249 return nea
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)
Definition utils.py:780
_makeStarImage(self, xc=[15.3], yc=[18.6], flux=[2500], schema=None, randomSeed=None)
Definition utils.py:750
__init__(self, w=101, h=101, xcenPos=[27.], ycenPos=[25.], xcenNeg=[23.], ycenNeg=[25.], psfSigma=2., flux=[30000.], fluxNeg=None, noise=10., gradientParams=None)
Definition utils.py:717
fitDipoleSource(self, source, **kwds)
Definition utils.py:775
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)
Definition utils.py:957
plotKernelSpatialModel(kernel, kernelCellSet, showBadCandidates=True, numSample=128, keepPlots=True, maxCoeff=10)
Definition utils.py:282
_sliceWidth(image, threshold, peaks, axis)
Definition utils.py:855
makeStats(badMaskPlanes=None)
Definition utils.py:1189
showKernelMosaic(bbox, kernel, nx=7, ny=None, frame=None, title=None, showCenter=True, showEllipticity=True)
Definition utils.py:591
showSourceSet(sSet, xy0=(0, 0), frame=0, ctype=afwDisplay.GREEN, symb="+", size=2)
Definition utils.py:50
showKernelSpatialCells(maskedIm, kernelCellSet, showChi2=False, symb="o", ctype=None, ctypeUnused=None, ctypeBad=None, size=3, frame=None, title="Spatial Cells")
Definition utils.py:72
plotWhisker(results, newWcs)
Definition utils.py:673
showKernelBasis(kernel, frame=None)
Definition utils.py:263
detectTestSources(exposure)
Definition utils.py:1021
showDiaSources(sources, exposure, isFlagged, isDipole, frame=None)
Definition utils.py:109
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)
Definition utils.py:1081
computePSFNoiseEquivalentArea(psf)
Definition utils.py:1236
showKernelCandidates(kernelCellSet, kernel, background, frame=None, showBadCandidates=True, resids=False, kernels=False)
Definition utils.py:146
plotKernelCoefficients(spatialKernel, kernelCellSet, showBadCandidates=False, keepPlots=True)
Definition utils.py:431
getPsfFwhm(psf, average=True, position=None)
Definition utils.py:862
float evaluateMeanPsfFwhm(afwImage.Exposure exposure, float fwhmExposureBuffer, int fwhmExposureGrid)
Definition utils.py:897
computeRobustStatistics(image, mask, statsCtrl, statistic=afwMath.MEANCLIP)
Definition utils.py:1212