22 __all__ = [
"backgroundSubtract",
"writeKernelCellSet",
"sourceToFootprintList",
"NbasisEvaluator"]
27 from collections
import Counter
31 from .
import diffimLib
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 with np.errstate(invalid=
'ignore'):
88 intNoiseArr = rand.poisson(imArr)
90 noiseArr[:, :] = intNoiseArr.astype(noiseArr.dtype)
101 kCoeffs = ((1.0, 0.0, 0.0),
102 (0.005, -0.000001, 0.000001),
103 (0.005, 0.000004, 0.000004),
104 (-0.001, -0.000030, 0.000030),
105 (-0.001, 0.000015, 0.000015),
106 (-0.005, -0.000050, 0.000050))
111 deltaFunctionCounts=1.e4, tGaussianWidth=1.0,
112 addNoise=True, bgValue=100., display=False):
113 """Generate test template and science images with sources. 117 sizeCell : `int`, optional 118 Size of the square spatial cells in pixels. 119 nCell : `int`, optional 120 Number of adjacent spatial cells in both direction in both images. 121 deltaFunctionCounts : `float`, optional 122 Flux value for the template image sources. 123 tGaussianWidth : `float`, optional 124 Sigma of the generated Gaussian PSF sources in the template image. 125 addNoise : `bool`, optional 126 If `True`, Poisson noise is added to both the generated template 128 bgValue : `float`, optional 129 Background level to be added to the generated science image. 130 display : `bool`, optional 131 If `True` displays the generated template and science images by 132 `lsst.afw.display.Display`. 136 - The generated images consist of adjacent ``nCell x nCell`` cells, each 137 of pixel size ``sizeCell x sizeCell``. 138 - The sources in the science image are generated by convolving the 139 template by ``sKernel``. ``sKernel`` is a spatial `LinearCombinationKernel` 140 of hard wired kernel bases functions. The linear combination has first 141 order polynomial spatial dependence with polynomial parameters from ``fakeCoeffs()``. 142 - The template image sources are generated in the center of each spatial 143 cell from one pixel, set to `deltaFunctionCounts` counts, then convolved 144 by a 2D Gaussian with sigma of `tGaussianWidth` along each axis. 145 - The sources are also returned in ``kernelCellSet`` each source is "detected" 146 exactly at the center of a cell. 150 tMi : `lsst.afw.image.MaskedImage` 151 Generated template image. 152 sMi : `lsst.afw.image.MaskedImage` 153 Generated science image. 154 sKernel : `lsst.afw.math.LinearCombinationKernel` 155 The spatial kernel used to generate the sources in the science image. 156 kernelCellSet : `lsst.afw.math.SpatialCellSet` 157 Cell grid of `lsst.afw.math.SpatialCell` instances, containing 158 `lsst.ip.diffim.KernelCandidate` instances around all the generated sources 159 in the science image. 160 configFake : `lsst.ip.diffim.ImagePsfMatchConfig` 161 Config instance used in the image generation. 163 from .
import imagePsfMatch
165 configFake.kernel.name =
"AL" 166 subconfigFake = configFake.kernel.active
167 subconfigFake.alardNGauss = 1
168 subconfigFake.alardSigGauss = [2.5, ]
169 subconfigFake.alardDegGauss = [2, ]
170 subconfigFake.sizeCellX = sizeCell
171 subconfigFake.sizeCellY = sizeCell
172 subconfigFake.spatialKernelOrder = 1
173 subconfigFake.spatialModelType =
"polynomial" 174 subconfigFake.singleKernelClipping =
False 175 subconfigFake.spatialKernelClipping =
False 177 subconfigFake.fitForBackground =
True 179 policyFake = pexConfig.makePolicy(subconfigFake)
182 kSize = subconfigFake.kernelSize
185 gaussKernelWidth = sizeCell//2
190 spatialKernelWidth = kSize
193 border = (gaussKernelWidth + spatialKernelWidth)//2
196 totalSize = nCell*sizeCell + 2*border
198 for x
in range(nCell):
199 for y
in range(nCell):
200 tim[x*sizeCell + sizeCell//2 + border - 1,
201 y*sizeCell + sizeCell//2 + border - 1,
202 afwImage.LOCAL] = deltaFunctionCounts
205 gaussFunction = afwMath.GaussianFunction2D(tGaussianWidth, tGaussianWidth)
207 cim = afwImage.ImageF(tim.getDimensions())
212 bbox = gaussKernel.shrinkBBox(tim.getBBox(afwImage.LOCAL))
213 tim = afwImage.ImageF(tim, bbox, afwImage.LOCAL)
217 polyFunc = afwMath.PolynomialFunction2D(1)
219 nToUse = min(len(kCoeffs), len(basisList))
223 sKernel.setSpatialParameters(kCoeffs[:nToUse])
224 sim = afwImage.ImageF(tim.getDimensions())
228 bbox = sKernel.shrinkBBox(sim.getBBox(afwImage.LOCAL))
234 tim += 2*np.abs(np.min(tim.getArray()))
242 sim = afwImage.ImageF(sim, bbox, afwImage.LOCAL)
243 svar = afwImage.ImageF(sim,
True)
246 sMi = afwImage.MaskedImageF(sim, smask, svar)
248 tim = afwImage.ImageF(tim, bbox, afwImage.LOCAL)
249 tvar = afwImage.ImageF(tim,
True)
252 tMi = afwImage.MaskedImageF(tim, tmask, tvar)
256 afwDisplay.Display(frame=1).mtv(tMi)
257 afwDisplay.Display(frame=2).mtv(sMi)
265 stampHalfWidth = 2*kSize
266 for x
in range(nCell):
267 for y
in range(nCell):
268 xCoord = x*sizeCell + sizeCell//2
269 yCoord = y*sizeCell + sizeCell//2
271 yCoord - stampHalfWidth)
273 yCoord + stampHalfWidth)
275 tsi = afwImage.MaskedImageF(tMi, bbox, origin=afwImage.LOCAL)
276 ssi = afwImage.MaskedImageF(sMi, bbox, origin=afwImage.LOCAL)
278 kc = diffimLib.makeKernelCandidate(xCoord, yCoord, tsi, ssi, policyFake)
279 kernelCellSet.insertCandidate(kc)
283 return tMi, sMi, sKernel, kernelCellSet, configFake
291 """Subtract the background from masked images. 295 config : TODO: DM-17458 297 maskedImages : `list` of `lsst.afw.image.MaskedImage` 307 algorithm = config.algorithm
308 binsize = config.binSize
309 undersample = config.undersampleStyle
311 bctrl.setUndersampleStyle(undersample)
312 for maskedImage
in maskedImages:
313 bctrl.setNxSample(maskedImage.getWidth()//binsize + 1)
314 bctrl.setNySample(maskedImage.getHeight()//binsize + 1)
315 image = maskedImage.getImage()
318 image -= backobj.getImageF()
319 backgrounds.append(backobj.getImageF())
323 logger = Log.getLogger(
"ip.diffim.backgroundSubtract")
324 logger.debug(
"Total time for background subtraction : %.2f s", (t1 - t0))
337 kernelCellSet : TODO: DM-17458 339 psfMatchingKernel : TODO: DM-17458 341 backgroundModel : TODO: DM-17458 343 outdir : TODO: DM-17458 346 if not os.path.isdir(outdir):
349 for cell
in kernelCellSet.getCellList():
350 for cand
in cell.begin(
False):
351 if cand.getStatus() == afwMath.SpatialCellCandidate.GOOD:
352 xCand = int(cand.getXCenter())
353 yCand = int(cand.getYCenter())
354 idCand = cand.getId()
355 diffIm = cand.getDifferenceImage(diffimLib.KernelCandidateF.ORIG)
356 kernel = cand.getKernelImage(diffimLib.KernelCandidateF.ORIG)
357 diffIm.writeFits(os.path.join(outdir,
'diffim_c%d_x%d_y%d.fits' % (idCand, xCand, yCand)))
358 kernel.writeFits(os.path.join(outdir,
'kernel_c%d_x%d_y%d.fits' % (idCand, xCand, yCand)))
361 ski = afwImage.ImageD(kernel.getDimensions())
362 psfMatchingKernel.computeImage(ski,
False, xCand, yCand)
364 sbg = backgroundModel(xCand, yCand)
365 sdmi = cand.getDifferenceImage(sk, sbg)
366 sdmi.writeFits(os.path.join(outdir,
'sdiffim_c%d_x%d_y%d.fits' % (idCand, xCand, yCand)))
374 """Convert a list of sources for the PSF-matching Kernel to Footprints. 378 candidateInList : TODO: DM-17458 379 Input list of Sources 380 templateExposure : TODO: DM-17458 381 Template image, to be checked for Mask bits in Source Footprint 382 scienceExposure : TODO: DM-17458 383 Science image, to be checked for Mask bits in Source Footprint 384 kernelSize : TODO: DM-17458 386 config : TODO: DM-17458 387 Config that defines the Mask planes that indicate an invalid Source and Bbox grow radius 393 candidateOutList : `list` 394 a list of dicts having a "source" and "footprint" field, to be used for Psf-matching 403 Takes an input list of Sources that were selected to constrain 404 the Psf-matching Kernel and turns them into a List of Footprints, 405 which are used to seed a set of KernelCandidates. The function 406 checks both the template and science image for masked pixels, 407 rejecting the Source if certain Mask bits (defined in config) are 408 set within the Footprint. 411 candidateOutList = []
412 fsb = diffimLib.FindSetBitsU()
414 for mp
in config.badMaskPlanes:
415 badBitMask |= afwImage.Mask.getPlaneBitMask(mp)
416 bbox = scienceExposure.getBBox()
419 if config.scaleByFwhm:
420 fpGrowPix = int(config.fpGrowKernelScaling*kernelSize + 0.5)
422 fpGrowPix = config.fpGrowPix
423 log.info(
"Growing %d kernel candidate stars by %d pixels", len(candidateInList), fpGrowPix)
425 for kernelCandidate
in candidateInList:
426 if not type(kernelCandidate) == afwTable.SourceRecord:
427 raise RuntimeError(
"Candiate not of type afwTable.SourceRecord")
430 center =
geom.Point2I(scienceExposure.getWcs().skyToPixel(kernelCandidate.getCoord()))
431 if center[0] < bbox.getMinX()
or center[0] > bbox.getMaxX():
433 if center[1] < bbox.getMinY()
or center[1] > bbox.getMaxY():
436 xmin = center[0] - fpGrowPix
437 xmax = center[0] + fpGrowPix
438 ymin = center[1] - fpGrowPix
439 ymax = center[1] + fpGrowPix
442 if (xmin - bbox.getMinX()) < 0:
443 xmax += (xmin - bbox.getMinX())
444 xmin -= (xmin - bbox.getMinX())
445 if (ymin - bbox.getMinY()) < 0:
446 ymax += (ymin - bbox.getMinY())
447 ymin -= (ymin - bbox.getMinY())
448 if (bbox.getMaxX() - xmax) < 0:
449 xmin -= (bbox.getMaxX() - xmax)
450 xmax += (bbox.getMaxX() - xmax)
451 if (bbox.getMaxY() - ymax) < 0:
452 ymin -= (bbox.getMaxY() - ymax)
453 ymax += (bbox.getMaxY() - ymax)
454 if xmin > xmax
or ymin > ymax:
459 fsb.apply(afwImage.MaskedImageF(templateExposure.getMaskedImage(), kbbox, deep=
False).getMask())
461 fsb.apply(afwImage.MaskedImageF(scienceExposure.getMaskedImage(), kbbox, deep=
False).getMask())
466 if not((bm1 & badBitMask)
or (bm2 & badBitMask)):
467 candidateOutList.append({
'source': kernelCandidate,
469 log.info(
"Selected %d / %d sources for KernelCandidacy", len(candidateOutList), len(candidateInList))
470 return candidateOutList
474 basisList, doBuild=False):
475 """Convert a list of Sources into KernelCandidates. 477 The KernelCandidates are used for fitting the Psf-matching kernel. 481 sourceTable : TODO: DM-17458 483 templateExposure : TODO: DM-17458 485 scienceExposure : TODO: DM-17458 487 kConfig : TODO: DM-17458 489 dConfig : TODO: DM-17458 493 basisList : TODO: DM-17458 495 doBuild : `bool`, optional 503 kernelSize = basisList[0].getWidth()
505 kernelSize, dConfig, log)
508 if doBuild
and not basisList:
511 policy = pexConfig.makePolicy(kConfig)
512 visitor = diffimLib.BuildSingleKernelVisitorF(basisList, policy)
514 policy = pexConfig.makePolicy(kConfig)
515 for cand
in footprintList:
516 bbox = cand[
'footprint'].getBBox()
517 tmi = afwImage.MaskedImageF(templateExposure.getMaskedImage(), bbox)
518 smi = afwImage.MaskedImageF(scienceExposure.getMaskedImage(), bbox)
519 kCand = diffimLib.makeKernelCandidate(cand[
'source'], tmi, smi, policy)
521 visitor.processCandidate(kCand)
522 kCand.setStatus(afwMath.SpatialCellCandidate.UNKNOWN)
523 candList.append(kCand)
533 """A functor to evaluate the Bayesian Information Criterion for the number of basis sets 534 going into the kernel fitting""" 536 def __init__(self, psfMatchConfig, psfFwhmPixTc, psfFwhmPixTnc):
541 raise RuntimeError(
"BIC only implemnted for AL (alard lupton) basis")
546 for d1i
in range(1, d1 + 1):
547 for d2i
in range(1, d2 + 1):
548 for d3i
in range(1, d3 + 1):
549 dList = [d1i, d2i, d3i]
553 visitor = diffimLib.BuildSingleKernelVisitorF(kList, pexConfig.makePolicy(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]
def makeKernelBasisList(config, targetFwhmPix=None, referenceFwhmPix=None, basisDegGauss=None, metadata=None)
std::shared_ptr< Background > makeBackground(ImageT const &img, BackgroundControl const &bgCtrl)
Statistics makeStatistics(lsst::afw::math::MaskedVector< EntryT > const &mv, std::vector< WeightPixel > const &vweights, int const flags, StatisticsControl const &sctrl=StatisticsControl())
void randomGaussianImage(ImageT *image, Random &rand)
void convolve(OutImageT &convolvedImage, InImageT const &inImage, KernelT const &kernel, bool doNormalize, bool doCopyEdge=false)