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]