22 __all__ = [
"backgroundSubtract",
"writeKernelCellSet",
"sourceToFootprintList",
"NbasisEvaluator"]
27 from collections
import Counter
31 from .
import diffimLib
41 import lsst.pex.config
as pexConfig
42 from .makeKernelBasisList
import makeKernelBasisList
56 rdmImage = img.Factory(img.getDimensions())
62 """Return a Poisson noise image based on im
66 im : `lsst.afw.image.Image`
67 image; the output image has the same dtype, dimensions, and shape
68 and its expectation value is the value of ``im`` at each pixel
72 noiseIm : `lsst.afw.image.Image`
73 Newly constructed image instance, same type as ``im``.
77 - Warning: This uses an undocumented numpy API (the documented API
78 uses a single float expectation value instead of an array).
80 - Uses numpy.random; you may wish to call numpy.random.seed first.
82 import numpy.random
as rand
84 noiseIm = im.Factory(im.getBBox())
85 noiseArr = noiseIm.getArray()
87 intNoiseArr = rand.poisson(np.where(np.isfinite(imArr), imArr, 0.0))
89 noiseArr[:, :] = intNoiseArr.astype(noiseArr.dtype)
100 kCoeffs = ((1.0, 0.0, 0.0),
101 (0.005, -0.000001, 0.000001),
102 (0.005, 0.000004, 0.000004),
103 (-0.001, -0.000030, 0.000030),
104 (-0.001, 0.000015, 0.000015),
105 (-0.005, -0.000050, 0.000050))
110 deltaFunctionCounts=1.e4, tGaussianWidth=1.0,
111 addNoise=True, bgValue=100., display=False):
112 """Generate test template and science images with sources.
116 sizeCell : `int`, optional
117 Size of the square spatial cells in pixels.
118 nCell : `int`, optional
119 Number of adjacent spatial cells in both direction in both images.
120 deltaFunctionCounts : `float`, optional
121 Flux value for the template image sources.
122 tGaussianWidth : `float`, optional
123 Sigma of the generated Gaussian PSF sources in the template image.
124 addNoise : `bool`, optional
125 If `True`, Poisson noise is added to both the generated template
127 bgValue : `float`, optional
128 Background level to be added to the generated science image.
129 display : `bool`, optional
130 If `True` displays the generated template and science images by
131 `lsst.afw.display.Display`.
135 - The generated images consist of adjacent ``nCell x nCell`` cells, each
136 of pixel size ``sizeCell x sizeCell``.
137 - The sources in the science image are generated by convolving the
138 template by ``sKernel``. ``sKernel`` is a spatial `LinearCombinationKernel`
139 of hard wired kernel bases functions. The linear combination has first
140 order polynomial spatial dependence with polynomial parameters from ``fakeCoeffs()``.
141 - The template image sources are generated in the center of each spatial
142 cell from one pixel, set to `deltaFunctionCounts` counts, then convolved
143 by a 2D Gaussian with sigma of `tGaussianWidth` along each axis.
144 - The sources are also returned in ``kernelCellSet`` each source is "detected"
145 exactly at the center of a cell.
149 tMi : `lsst.afw.image.MaskedImage`
150 Generated template image.
151 sMi : `lsst.afw.image.MaskedImage`
152 Generated science image.
153 sKernel : `lsst.afw.math.LinearCombinationKernel`
154 The spatial kernel used to generate the sources in the science image.
155 kernelCellSet : `lsst.afw.math.SpatialCellSet`
156 Cell grid of `lsst.afw.math.SpatialCell` instances, containing
157 `lsst.ip.diffim.KernelCandidate` instances around all the generated sources
158 in the science image.
159 configFake : `lsst.ip.diffim.ImagePsfMatchConfig`
160 Config instance used in the image generation.
162 from .
import imagePsfMatch
164 configFake.kernel.name =
"AL"
165 subconfigFake = configFake.kernel.active
166 subconfigFake.alardNGauss = 1
167 subconfigFake.alardSigGauss = [2.5, ]
168 subconfigFake.alardDegGauss = [2, ]
169 subconfigFake.sizeCellX = sizeCell
170 subconfigFake.sizeCellY = sizeCell
171 subconfigFake.spatialKernelOrder = 1
172 subconfigFake.spatialModelType =
"polynomial"
173 subconfigFake.singleKernelClipping =
False
174 subconfigFake.spatialKernelClipping =
False
176 subconfigFake.fitForBackground =
True
178 psFake = pexConfig.makePropertySet(subconfigFake)
181 kSize = subconfigFake.kernelSize
184 gaussKernelWidth = sizeCell//2
189 spatialKernelWidth = kSize
192 border = (gaussKernelWidth + spatialKernelWidth)//2
195 totalSize = nCell*sizeCell + 2*border
197 for x
in range(nCell):
198 for y
in range(nCell):
199 tim[x*sizeCell + sizeCell//2 + border - 1,
200 y*sizeCell + sizeCell//2 + border - 1,
201 afwImage.LOCAL] = deltaFunctionCounts
204 gaussFunction = afwMath.GaussianFunction2D(tGaussianWidth, tGaussianWidth)
206 cim = afwImage.ImageF(tim.getDimensions())
211 bbox = gaussKernel.shrinkBBox(tim.getBBox(afwImage.LOCAL))
212 tim = afwImage.ImageF(tim, bbox, afwImage.LOCAL)
216 polyFunc = afwMath.PolynomialFunction2D(1)
218 nToUse =
min(len(kCoeffs), len(basisList))
222 sKernel.setSpatialParameters(kCoeffs[:nToUse])
223 sim = afwImage.ImageF(tim.getDimensions())
227 bbox = sKernel.shrinkBBox(sim.getBBox(afwImage.LOCAL))
233 tim += 2*np.abs(np.min(tim.getArray()))
241 sim = afwImage.ImageF(sim, bbox, afwImage.LOCAL)
242 svar = afwImage.ImageF(sim,
True)
245 sMi = afwImage.MaskedImageF(sim, smask, svar)
247 tim = afwImage.ImageF(tim, bbox, afwImage.LOCAL)
248 tvar = afwImage.ImageF(tim,
True)
251 tMi = afwImage.MaskedImageF(tim, tmask, tvar)
255 afwDisplay.Display(frame=1).
mtv(tMi)
256 afwDisplay.Display(frame=2).
mtv(sMi)
264 stampHalfWidth = 2*kSize
265 for x
in range(nCell):
266 for y
in range(nCell):
267 xCoord = x*sizeCell + sizeCell//2
268 yCoord = y*sizeCell + sizeCell//2
270 yCoord - stampHalfWidth)
272 yCoord + stampHalfWidth)
274 tsi = afwImage.MaskedImageF(tMi, bbox, origin=afwImage.LOCAL)
275 ssi = afwImage.MaskedImageF(sMi, bbox, origin=afwImage.LOCAL)
277 kc = diffimLib.makeKernelCandidate(xCoord, yCoord, tsi, ssi, psFake)
278 kernelCellSet.insertCandidate(kc)
282 return tMi, sMi, sKernel, kernelCellSet, configFake
290 """Subtract the background from masked images.
294 config : TODO: DM-17458
296 maskedImages : `list` of `lsst.afw.image.MaskedImage`
306 algorithm = config.algorithm
307 binsize = config.binSize
308 undersample = config.undersampleStyle
310 bctrl.setUndersampleStyle(undersample)
311 for maskedImage
in maskedImages:
312 bctrl.setNxSample(maskedImage.getWidth()//binsize + 1)
313 bctrl.setNySample(maskedImage.getHeight()//binsize + 1)
314 image = maskedImage.getImage()
317 image -= backobj.getImageF()
318 backgrounds.append(backobj.getImageF())
322 logger = Log.getLogger(
"ip.diffim.backgroundSubtract")
323 logger.debug(
"Total time for background subtraction : %.2f s", (t1 - t0))
336 kernelCellSet : TODO: DM-17458
338 psfMatchingKernel : TODO: DM-17458
340 backgroundModel : TODO: DM-17458
342 outdir : TODO: DM-17458
345 if not os.path.isdir(outdir):
348 for cell
in kernelCellSet.getCellList():
349 for cand
in cell.begin(
False):
350 if cand.getStatus() == afwMath.SpatialCellCandidate.GOOD:
351 xCand = int(cand.getXCenter())
352 yCand = int(cand.getYCenter())
353 idCand = cand.getId()
354 diffIm = cand.getDifferenceImage(diffimLib.KernelCandidateF.ORIG)
355 kernel = cand.getKernelImage(diffimLib.KernelCandidateF.ORIG)
356 diffIm.writeFits(os.path.join(outdir,
'diffim_c%d_x%d_y%d.fits' % (idCand, xCand, yCand)))
357 kernel.writeFits(os.path.join(outdir,
'kernel_c%d_x%d_y%d.fits' % (idCand, xCand, yCand)))
360 ski = afwImage.ImageD(kernel.getDimensions())
361 psfMatchingKernel.computeImage(ski,
False, xCand, yCand)
363 sbg = backgroundModel(xCand, yCand)
364 sdmi = cand.getDifferenceImage(sk, sbg)
365 sdmi.writeFits(os.path.join(outdir,
'sdiffim_c%d_x%d_y%d.fits' % (idCand, xCand, yCand)))
373 """Convert a list of sources for the PSF-matching Kernel to Footprints.
377 candidateInList : TODO: DM-17458
378 Input list of Sources
379 templateExposure : TODO: DM-17458
380 Template image, to be checked for Mask bits in Source Footprint
381 scienceExposure : TODO: DM-17458
382 Science image, to be checked for Mask bits in Source Footprint
383 kernelSize : TODO: DM-17458
385 config : TODO: DM-17458
386 Config that defines the Mask planes that indicate an invalid Source and Bbox grow radius
392 candidateOutList : `list`
393 a list of dicts having a "source" and "footprint" field, to be used for Psf-matching
402 Takes an input list of Sources that were selected to constrain
403 the Psf-matching Kernel and turns them into a List of Footprints,
404 which are used to seed a set of KernelCandidates. The function
405 checks both the template and science image for masked pixels,
406 rejecting the Source if certain Mask bits (defined in config) are
407 set within the Footprint.
410 candidateOutList = []
411 fsb = diffimLib.FindSetBitsU()
413 for mp
in config.badMaskPlanes:
414 badBitMask |= afwImage.Mask.getPlaneBitMask(mp)
415 bbox = scienceExposure.getBBox()
418 if config.scaleByFwhm:
419 fpGrowPix = int(config.fpGrowKernelScaling*kernelSize + 0.5)
421 fpGrowPix = config.fpGrowPix
422 log.info(
"Growing %d kernel candidate stars by %d pixels", len(candidateInList), fpGrowPix)
424 for kernelCandidate
in candidateInList:
426 raise RuntimeError(
"Candiate not of type afwTable.SourceRecord")
429 center =
geom.Point2I(scienceExposure.getWcs().skyToPixel(kernelCandidate.getCoord()))
430 if center[0] < bbox.getMinX()
or center[0] > bbox.getMaxX():
432 if center[1] < bbox.getMinY()
or center[1] > bbox.getMaxY():
435 xmin = center[0] - fpGrowPix
436 xmax = center[0] + fpGrowPix
437 ymin = center[1] - fpGrowPix
438 ymax = center[1] + fpGrowPix
441 if (xmin - bbox.getMinX()) < 0:
442 xmax += (xmin - bbox.getMinX())
443 xmin -= (xmin - bbox.getMinX())
444 if (ymin - bbox.getMinY()) < 0:
445 ymax += (ymin - bbox.getMinY())
446 ymin -= (ymin - bbox.getMinY())
447 if (bbox.getMaxX() - xmax) < 0:
448 xmin -= (bbox.getMaxX() - xmax)
449 xmax += (bbox.getMaxX() - xmax)
450 if (bbox.getMaxY() - ymax) < 0:
451 ymin -= (bbox.getMaxY() - ymax)
452 ymax += (bbox.getMaxY() - ymax)
453 if xmin > xmax
or ymin > ymax:
458 fsb.apply(afwImage.MaskedImageF(templateExposure.getMaskedImage(), kbbox, deep=
False).getMask())
460 fsb.apply(afwImage.MaskedImageF(scienceExposure.getMaskedImage(), kbbox, deep=
False).getMask())
465 if not((bm1 & badBitMask)
or (bm2 & badBitMask)):
466 candidateOutList.append({
'source': kernelCandidate,
468 log.info(
"Selected %d / %d sources for KernelCandidacy", len(candidateOutList), len(candidateInList))
469 return candidateOutList
473 basisList, doBuild=False):
474 """Convert a list of Sources into KernelCandidates.
476 The KernelCandidates are used for fitting the Psf-matching kernel.
480 sourceTable : TODO: DM-17458
482 templateExposure : TODO: DM-17458
484 scienceExposure : TODO: DM-17458
486 kConfig : TODO: DM-17458
488 dConfig : TODO: DM-17458
492 basisList : TODO: DM-17458
494 doBuild : `bool`, optional
502 kernelSize = basisList[0].getWidth()
504 kernelSize, dConfig, log)
507 if doBuild
and not basisList:
510 ps = pexConfig.makePropertySet(kConfig)
511 visitor = diffimLib.BuildSingleKernelVisitorF(basisList, ps)
513 ps = pexConfig.makePropertySet(kConfig)
514 for cand
in footprintList:
515 bbox = cand[
'footprint'].getBBox()
516 tmi = afwImage.MaskedImageF(templateExposure.getMaskedImage(), bbox)
517 smi = afwImage.MaskedImageF(scienceExposure.getMaskedImage(), bbox)
518 kCand = diffimLib.makeKernelCandidate(cand[
'source'], tmi, smi, ps)
520 visitor.processCandidate(kCand)
521 kCand.setStatus(afwMath.SpatialCellCandidate.UNKNOWN)
522 candList.append(kCand)
532 """A functor to evaluate the Bayesian Information Criterion for the number of basis sets
533 going into the kernel fitting"""
535 def __init__(self, psfMatchConfig, psfFwhmPixTc, psfFwhmPixTnc):
540 raise RuntimeError(
"BIC only implemnted for AL (alard lupton) basis")
545 for d1i
in range(1, d1 + 1):
546 for d2i
in range(1, d2 + 1):
547 for d3i
in range(1, d3 + 1):
548 dList = [d1i, d2i, d3i]
552 visitor = diffimLib.BuildSingleKernelVisitorF(kList,
553 pexConfig.makePropertySet(bicConfig))
554 visitor.setSkipBuilt(
False)
555 kernelCellSet.visitCandidates(visitor, bicConfig.nStarPerCell)
557 for cell
in kernelCellSet.getCellList():
558 for cand
in cell.begin(
False):
559 if cand.getStatus() != afwMath.SpatialCellCandidate.GOOD:
561 diffIm = cand.getDifferenceImage(diffimLib.KernelCandidateF.RECENT)
562 bbox = cand.getKernel(diffimLib.KernelCandidateF.RECENT).shrinkBBox(
563 diffIm.getBBox(afwImage.LOCAL))
564 diffIm =
type(diffIm)(diffIm, bbox,
True)
565 chi2 = diffIm.getImage().getArray()**2/diffIm.getVariance().getArray()
566 n = chi2.shape[0]*chi2.shape[1]
567 bic = np.sum(chi2) + k*np.log(n)
568 if cand.getId()
not in bicArray:
569 bicArray[cand.getId()] = {}
570 bicArray[cand.getId()][(d1i, d2i, d3i)] = bic
573 for candId
in bicArray:
574 cconfig, cvals =
list(bicArray[candId].
keys()),
list(bicArray[candId].values())
575 idx = np.argsort(cvals)
576 bestConfig = cconfig[idx[0]]
577 bestConfigs.append(bestConfig)
579 counter = Counter(bestConfigs).most_common(3)
580 log.info(
"B.I.C. prefers basis complexity %s %d times; %s %d times; %s %d times",
581 counter[0][0], counter[0][1],
582 counter[1][0], counter[1][1],
583 counter[2][0], counter[2][1])
584 return counter[0][0], counter[1][0], counter[2][0]