Ejemplo n.º 1
0
import lsst.afw.detection as afwDetection
import lsst.afw.image as afwImage
import lsst.afw.geom as afwGeom
import lsst.afw.math as afwMath
import lsst.meas.algorithms as measAlg
import lsst.meas.algorithms.utils as maUtils
import lsst.afw.display.ds9 as ds9
import lsst.afw.display.utils as displayUtils

try:
    type(verbose)
except NameError:
    verbose = 0
    display = False
    
pexLog.Trace_setVerbosity("meas.algorithms.measure", verbose)

#reload(lsst.meas.algorithms.Psf); Psf = lsst.meas.algorithms.Psf

#-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

class PsfShapeHistogram(object):
    """A class to represent a histogram of (Ixx, Iyy)"""

    def __init__(self):
        self._xSize, self._ySize = 20, 20
        self._xMax, self._yMax = 15, 15
        self._psfImage = afwImage.ImageF(self._xSize, self._ySize)
        self._psfImage.set(0)

    def getImage(self):
Ejemplo n.º 2
0
import lsst.afw.geom as afwGeom
import lsst.afw.geom.ellipses as afwEllipses
import lsst.afw.math as afwMath
import lsst.afw.table as afwTable
import lsst.afw.coord as afwCoord
import lsst.afw.image as afwImage
import lsst.meas.algorithms as measAlg
import lsst.meas.extensions.photometryKron as Kron

try:
    type(verbose)
except NameError:
    verbose = 1
    display = False
    ds9Frame = 0
pexLogging.Trace_setVerbosity("meas.photometry.kron", verbose)

import lsst.afw.display.ds9 as ds9
import lsst.afw.display.utils as displayUtils

#-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

def makeGalaxy(width, height, flux, a, b, theta, dx=0.0, dy=0.0, xy0=None, xcen=None, ycen=None):
    """Make a fake galaxy image"""
    gal = afwImage.ImageF(width, height)
    if xcen is None:
        xcen = 0.5*width + dx
    if ycen is None:
        ycen = 0.5*height + dy
    I0 = flux/(2*math.pi*a*b)
Ejemplo n.º 3
0
import lsst.pex.exceptions as pexExceptions
import lsst.pex.logging as pexLogging
import lsst.pex.policy as pexPolicy
import lsst.afw.detection as afwDetection
import lsst.afw.math as afwMath
import lsst.afw.geom as afwGeom
import lsst.afw.table as afwTable
import lsst.afw.image as afwImage
import lsst.afw.coord as afwCoord
import lsst.meas.algorithms as measAlg

try:
    type(verbose)
except NameError:
    verbose = 0
pexLogging.Trace_setVerbosity("afwDetection.Measure", verbose)

try:
    type(display)
except NameError:
    display = False

import lsst.afw.display.ds9 as ds9

FwhmPerSigma = 2 * math.sqrt(2 * math.log(2))  # FWHM for an N(0, 1) Gaussian

#-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-


class MeasureSourcesTestCase(unittest.TestCase):
    """A test case for Measure"""
Ejemplo n.º 4
0
import lsst.afw.image as afwImage
import lsst.afw.math as afwMath
import lsst.ip.diffim as ipDiffim
import lsst.ip.diffim.diffimTools as diffimTools
import lsst.pex.logging as pexLogging

import lsst.afw.display.ds9 as ds9
import lsst.afw.display.utils as displayUtils

subBackground = True
display = True
fwhm = 6.8
warp = True

verbosity = 5
pexLogging.Trace_setVerbosity("lsst.ip.diffim", verbosity)

defDataDir = lsst.utils.getPackageDir('afwdata')
imageProcDir = lsst.utils.getPackageDir('ip_diffim')

if len(sys.argv) == 1:
    defSciencePath = os.path.join(defDataDir, "DC3a-Sim", "sci", "v26-e0",
                                  "v26-e0-c011-a10.sci")
    defTemplatePath = os.path.join(defDataDir, "DC3a-Sim", "sci", "v5-e0",
                                   "v5-e0-c011-a10.sci")
elif len(sys.argv) == 3:
    defSciencePath = sys.argv[1]
    defTemplatePath = sys.argv[2]
else:
    sys.exit(1)
Ejemplo n.º 5
0
    print '# Reading', fn,
    loc = dafPersist.LogicalLocation(fn)
    storageList = dafPersist.StorageList()
    additionalData = dafBase.PropertySet()
    persistence = dafPersist.Persistence.getPersistence(pexPolicy.Policy())
    storageList.append(persistence.getRetrieveStorage("BoostStorage", loc))
    psfptr = persistence.unsafeRetrieve("Psf", storageList, additionalData)
    psf = afwDet.Psf.swigConvert(psfptr)
    return psf


if __name__ == '__main__':
    import lsst.ip.diffim.diffimTools as diffimTools

    pexLog.Trace_setVerbosity("lsst.ip.diffim", 5)

    calexpPath = sys.argv[1]
    boostPath = sys.argv[2]
    sigGauss = float(sys.argv[3])

    calexp = afwImage.ExposureF(calexpPath)
    psf = pcapsf_read_boost(boostPath)
    if not calexp.hasPsf():
        calexp.setPsf(psf)

    # match to this
    gaussPsf = measAlg.DoubleGaussianPsf(psf.getKernel().getWidth(),
                                         psf.getKernel().getHeight(), sigGauss)

    config = ipDiffim.ModelPsfMatchTask.ConfigClass()
Ejemplo n.º 6
0
import lsst.utils
import lsst.utils.tests as tests
import lsst.pex.config as pexConfig
import lsst.pex.logging as logging
import lsst.afw.image as afwImage
import lsst.afw.math as afwMath
import lsst.afw.geom as afwGeom
import lsst.afw.display.ds9 as ds9
import lsst.meas.algorithms as algorithms
import lsst.meas.algorithms.defects as defects

try:
    type(verbose)
except NameError:
    verbose = 0
logging.Trace_setVerbosity("algorithms.CR", verbose)

try:
    type(display)
except NameError:
    display = False

    try:
        afwdataDir = lsst.utils.getPackageDir('afwdata')
        imageFile0 = os.path.join(afwdataDir, "CFHT", "D4", "cal-53535-i-797722_1.fits")
    except Exception:
        imageFile0 = None
    imageFile = imageFile0

#-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
Ejemplo n.º 7
0
#!/usr/bin/env python
import unittest
import numpy as num

import lsst.utils.tests as tests

import lsst.afw.geom as afwGeom
import lsst.afw.image as afwImage
import lsst.afw.math as afwMath
import lsst.ip.diffim as ipDiffim
import lsst.pex.logging as pexLog
import lsst.pex.config as pexConfig

#import lsst.afw.display.ds9 as ds9

pexLog.Trace_setVerbosity('lsst.ip.diffim', 5)


class DiffimTestCases(unittest.TestCase):
    def setUp(self):
        self.config = ipDiffim.ImagePsfMatchTask.ConfigClass()
        self.config.kernel.name = "AL"
        self.subconfig = self.config.kernel.active

        self.policy = pexConfig.makePolicy(self.subconfig)
        self.kList = ipDiffim.makeKernelBasisList(self.subconfig)

        self.ksize = self.policy.get('kernelSize')

    def makeSpatialKernel(self, order):
        basicGaussian1 = afwMath.GaussianFunction2D(2., 2., 0.)
Ejemplo n.º 8
0
class ImagePsfMatchTask(PsfMatchTask):
    """!
\anchor ImagePsfMatchTask_

\brief Psf-match two MaskedImages or Exposures using the sources in the images

\section ip_diffim_imagepsfmatch_Contents Contents

 - \ref ip_diffim_imagepsfmatch_Purpose
 - \ref ip_diffim_imagepsfmatch_Initialize
 - \ref ip_diffim_imagepsfmatch_IO
 - \ref ip_diffim_imagepsfmatch_Config
 - \ref ip_diffim_imagepsfmatch_Metadata
 - \ref ip_diffim_imagepsfmatch_Debug
 - \ref ip_diffim_imagepsfmatch_Example

#-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

\section ip_diffim_imagepsfmatch_Purpose   Description

Build a Psf-matching kernel using two input images, either as MaskedImages (in which case they need
    to be astrometrically aligned) or Exposures (in which case astrometric alignment will happen by
    default but may be turned off).  This requires a list of input Sources which may be provided
by the calling Task; if not, the Task will perform a coarse source detection and selection for this purpose.
Sources are vetted for signal-to-noise and masked pixels (in both the template and science image), and 
substamps around each acceptable source are extracted and used to create an instance of KernelCandidate.  
Each KernelCandidate is then placed within a lsst.afw.math.SpatialCellSet, which is used by an ensemble of 
lsst.afw.math.CandidateVisitor instances to build the Psf-matching kernel.   These visitors include, in 
the order that they are called: BuildSingleKernelVisitor, KernelSumVisitor, BuildSpatialKernelVisitor, 
and AssessSpatialKernelVisitor.  

Sigma clipping of KernelCandidates is performed as follows: 
 - BuildSingleKernelVisitor, using the substamp diffim residuals from the per-source kernel fit,
    if PsfMatchConfig.singleKernelClipping is True
 - KernelSumVisitor, using the mean and standard deviation of the kernel sum from all candidates,
    if PsfMatchConfig.kernelSumClipping is True
 - AssessSpatialKernelVisitor, using the substamp diffim ressiduals from the spatial kernel fit,
    if PsfMatchConfig.spatialKernelClipping is True

The actual solving for the kernel (and differential background model) happens in 
lsst.ip.diffim.PsfMatchTask._solve.  This involves a loop over the SpatialCellSet that first builds the
per-candidate matching kernel for the requested number of KernelCandidates per cell 
(PsfMatchConfig.nStarPerCell).  The quality of this initial per-candidate difference image is examined, 
using moments of the pixel residuals in the difference image normalized by the square root of the variance 
(i.e. sigma); ideally this should follow a normal (0, 1) distribution, but the rejection thresholds are set 
by the config (PsfMatchConfig.candidateResidualMeanMax and PsfMatchConfig.candidateResidualStdMax).  
All candidates that pass this initial build are then examined en masse to find the 
mean/stdev of the kernel sums across all candidates.  Objects that are significantly above or below the mean, 
typically due to variability or sources that are saturated in one image but not the other, are also rejected.  
This threshold is defined by PsfMatchConfig.maxKsumSigma.  Finally, a spatial model is built using all
currently-acceptable candidates, and the spatial model used to derive a second set of (spatial) residuals
which are again used to reject bad candidates, using the same thresholds as above.

#-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

\section ip_diffim_imagepsfmatch_Initialize    Task initialization

\copydoc \_\_init\_\_

#-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

\section ip_diffim_imagepsfmatch_IO        Invoking the Task

There is no run() method for this Task.  Instead there are 4 methods that
may be used to invoke the Psf-matching.  These are
\link lsst.ip.diffim.imagePsfMatch.ImagePsfMatchTask.matchMaskedImages matchMaskedImages\endlink,
\link lsst.ip.diffim.imagePsfMatch.ImagePsfMatchTask.subtractMaskedImages subtractMaskedImages\endlink,
\link lsst.ip.diffim.imagePsfMatch.ImagePsfMatchTask.matchExposures matchExposures\endlink, and 
\link lsst.ip.diffim.imagePsfMatch.ImagePsfMatchTask.subtractExposures subtractExposures\endlink.

The methods that operate on lsst.afw.image.MaskedImage require that the images already be astrometrically
aligned, and are the same shape.  The methods that operate on lsst.afw.image.Exposure allow for the 
input images to be misregistered and potentially be different sizes; by default a 
lsst.afw.math.LanczosWarpingKernel is used to perform the astrometric alignment.  The methods 
that "match" images return a Psf-matched image, while the methods that "subtract" images 
return a Psf-matched and template subtracted image.

See each method's returned lsst.pipe.base.Struct for more details.

#-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

\section ip_diffim_imagepsfmatch_Config       Configuration parameters

See \ref ImagePsfMatchConfig

#-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

\section ip_diffim_imagepsfmatch_Metadata   Quantities set in Metadata

See \ref ip_diffim_psfmatch_Metadata "PsfMatchTask"

#-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

\section ip_diffim_imagepsfmatch_Debug     Debug variables

The \link lsst.pipe.base.cmdLineTask.CmdLineTask command line task\endlink interface supports a
flag \c -d/--debug to import \b debug.py from your \c PYTHONPATH.  The relevant contents of debug.py 
for this Task include:

\code{.py}
    import sys
    import lsstDebug
    def DebugInfo(name):
        di = lsstDebug.getInfo(name)   
        if name == "lsst.ip.diffim.psfMatch":
            di.display = True                 # enable debug output
            di.maskTransparency = 80          # ds9 mask transparency
            di.displayCandidates = True       # show all the candidates and residuals
            di.displayKernelBasis = False     # show kernel basis functions
            di.displayKernelMosaic = True     # show kernel realized across the image
            di.plotKernelSpatialModel = False # show coefficients of spatial model
            di.showBadCandidates = True       # show the bad candidates (red) along with good (green)
        elif name == "lsst.ip.diffim.imagePsfMatch":
            di.display = True                 # enable debug output
            di.maskTransparency = 30          # ds9 mask transparency
            di.displayTemplate = True         # show full (remapped) template
            di.displaySciIm = True            # show science image to match to
            di.displaySpatialCells = True     # show spatial cells
            di.displayDiffIm = True           # show difference image
            di.showBadCandidates = True       # show the bad candidates (red) along with good (green) 
        elif name == "lsst.ip.diffim.diaCatalogSourceSelector":
            di.display = False                # enable debug output
            di.maskTransparency = 30          # ds9 mask transparency
            di.displayExposure = True         # show exposure with candidates indicated
            di.pauseAtEnd = False             # pause when done
        return di
    lsstDebug.Info = DebugInfo
    lsstDebug.frame = 1      
\endcode

Note that if you want addional logging info, you may add to your scripts:
\code{.py}
import lsst.pex.logging as pexLog
pexLog.Trace_setVerbosity('lsst.ip.diffim', 5)
\endcode

#-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

\section ip_diffim_imagepsfmatch_Example   A complete example of using ImagePsfMatchTask

This code is imagePsfMatchTask.py in the examples directory, and can be run as \em e.g.
\code
examples/imagePsfMatchTask.py --debug 
examples/imagePsfMatchTask.py --debug --mode="matchExposures"
examples/imagePsfMatchTask.py --debug --template /path/to/templateExp.fits --science /path/to/scienceExp.fits
\endcode

\dontinclude imagePsfMatchTask.py
Create a subclass of ImagePsfMatchTask that allows us to either match exposures, or subtract exposures:
\skip MyImagePsfMatchTask
\until self.subtractExposures

And allow the user the freedom to either run the script in default mode, or point to their own images on disk.
Note that these images must be readable as an lsst.afw.image.Exposure:
\skip main
\until parse_args

We have enabled some minor display debugging in this script via the --debug option.  However, if you 
have an lsstDebug debug.py in your PYTHONPATH you will get additional debugging displays.  The following
block checks for this script:
\skip args.debug
\until sys.stderr

\dontinclude imagePsfMatchTask.py
Finally, we call a run method that we define below.  First set up a Config and modify some of the parameters.
E.g. use an "Alard-Lupton" sum-of-Gaussian basis, fit for a differential background, and use low order spatial
variation in the kernel and background:
\skip run(args)
\until spatialBgOrder

Make sure the images (if any) that were sent to the script exist on disk and are readable.  If no images
are sent, make some fake data up for the sake of this example script (have a look at the code if you want
more details on generateFakeImages):
\skip requested
\until sizeCellY

Create and run the Task:
\skip Create
\until args.mode

And finally provide some optional debugging displays:
\skip args.debug
\until result.subtractedExposure
#-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

    """
    ConfigClass = ImagePsfMatchConfig

    def __init__(self, *args, **kwargs):
        """!Create the ImagePsfMatchTask

        \param *args arguments to be passed to lsst.ip.diffim.PsfMatchTask.__init__
        \param **kwargs keyword arguments to be passed to lsst.ip.diffim.PsfMatchTask.__init__

        Upon initialization, the kernel configuration is defined by self.config.kernel.active.
        The task creates an lsst.afw.math.Warper from the subConfig self.config.kernel.active.warpingConfig.
        A schema for the selection and measurement of candidate lsst.ip.diffim.KernelCandidates is
        defined, and used to initize subTasks selectDetection (for candidate detection) and selectMeasurement
        (for candidate measurement).
        """
        PsfMatchTask.__init__(self, *args, **kwargs)
        self.kConfig = self.config.kernel.active
        self._warper = afwMath.Warper.fromConfig(self.kConfig.warpingConfig)
        self.selectSchema = afwTable.SourceTable.makeMinimalSchema()
        self.selectAlgMetadata = dafBase.PropertyList()
        self.makeSubtask("selectDetection", schema=self.selectSchema)
        self.makeSubtask("selectMeasurement",
                         schema=self.selectSchema,
                         algMetadata=self.selectAlgMetadata)

    def getFwhmPix(self, psf):
        """!Return the FWHM in pixels of a Psf"""
        sigPix = psf.computeShape().getDeterminantRadius()
        return sigPix * sigma2fwhm

    @pipeBase.timeMethod
    def matchExposures(self,
                       templateExposure,
                       scienceExposure,
                       templateFwhmPix=None,
                       scienceFwhmPix=None,
                       candidateList=None,
                       doWarping=True,
                       convolveTemplate=True):
        """!Warp and PSF-match an exposure to the reference

        Do the following, in order:
        - Warp templateExposure to match scienceExposure,
            if doWarping True and their WCSs do not already match
        - Determine a PSF matching kernel and differential background model
            that matches templateExposure to scienceExposure
        - Convolve templateExposure by PSF matching kernel

        @param templateExposure: Exposure to warp and PSF-match to the reference masked image
        @param scienceExposure: Exposure whose WCS and PSF are to be matched to
        @param templateFwhmPix: FWHM (in pixels) of the Psf in the template image (image to convolve)
        @param scienceFwhmPix: FWHM (in pixels) of the Psf in the science image
        @param candidateList: a list of footprints/maskedImages for kernel candidates; 
                              if None then source detection is run.
            - Currently supported: list of Footprints or measAlg.PsfCandidateF
        @param doWarping: what to do if templateExposure's and scienceExposure's WCSs do not match:
            - if True then warp templateExposure to match scienceExposure
            - if False then raise an Exception
        @param convolveTemplate: convolve the template image or the science image
            - if True, templateExposure is warped if doWarping, templateExposure is convolved
            - if False, templateExposure is warped if doWarping, scienceExposure is convolved

        @return a pipeBase.Struct containing these fields:
        - matchedImage: the PSF-matched exposure =
            warped templateExposure convolved by psfMatchingKernel. This has:
            - the same parent bbox, Wcs and Calib as scienceExposure
            - the same filter as templateExposure
            - no Psf (because the PSF-matching process does not compute one)
        - psfMatchingKernel: the PSF matching kernel
        - backgroundModel: differential background model
        - kernelCellSet: SpatialCellSet used to solve for the PSF matching kernel

        Raise a RuntimeError if doWarping is False and templateExposure's and scienceExposure's
            WCSs do not match
        """
        if not self._validateWcs(templateExposure, scienceExposure):
            if doWarping:
                self.log.info(
                    "Astrometrically registering template to science image")
                templatePsf = templateExposure.getPsf()
                templateExposure = self._warper.warpExposure(
                    scienceExposure.getWcs(),
                    templateExposure,
                    destBBox=scienceExposure.getBBox())
                templateExposure.setPsf(templatePsf)
            else:
                pexLog.Trace(self.log.getName(), 1,
                             "ERROR: Input images not registered")
                raise RuntimeError("Input images not registered")
        if templateFwhmPix is None:
            if not templateExposure.hasPsf():
                self.log.warn("No estimate of Psf FWHM for template image")
            else:
                templateFwhmPix = self.getFwhmPix(templateExposure.getPsf())

        if scienceFwhmPix is None:
            if not scienceExposure.hasPsf():
                self.log.warn("No estimate of Psf FWHM for science image")
            else:
                scienceFwhmPix = self.getFwhmPix(scienceExposure.getPsf())

        kernelSize = makeKernelBasisList(self.kConfig, templateFwhmPix,
                                         scienceFwhmPix)[0].getWidth()
        candidateList = self.makeCandidateList(templateExposure,
                                               scienceExposure, kernelSize,
                                               candidateList)

        if convolveTemplate:
            results = self.matchMaskedImages(templateExposure.getMaskedImage(),
                                             scienceExposure.getMaskedImage(),
                                             candidateList,
                                             templateFwhmPix=templateFwhmPix,
                                             scienceFwhmPix=scienceFwhmPix)
        else:
            results = self.matchMaskedImages(scienceExposure.getMaskedImage(),
                                             templateExposure.getMaskedImage(),
                                             candidateList,
                                             templateFwhmPix=scienceFwhmPix,
                                             scienceFwhmPix=templateFwhmPix)

        psfMatchedExposure = afwImage.makeExposure(results.matchedImage,
                                                   scienceExposure.getWcs())
        psfMatchedExposure.setFilter(templateExposure.getFilter())
        psfMatchedExposure.setCalib(scienceExposure.getCalib())
        results.warpedExposure = templateExposure
        results.matchedExposure = psfMatchedExposure
        return results

    @pipeBase.timeMethod
    def matchMaskedImages(self,
                          templateMaskedImage,
                          scienceMaskedImage,
                          candidateList,
                          templateFwhmPix=None,
                          scienceFwhmPix=None):
        """!PSF-match a MaskedImage (templateMaskedImage) to a reference MaskedImage (scienceMaskedImage)

        Do the following, in order:
        - Determine a PSF matching kernel and differential background model
            that matches templateMaskedImage to scienceMaskedImage
        - Convolve templateMaskedImage by the PSF matching kernel

        @param templateMaskedImage: masked image to PSF-match to the reference masked image;
            must be warped to match the reference masked image
        @param scienceMaskedImage: maskedImage whose PSF is to be matched to
        @param templateFwhmPix: FWHM (in pixels) of the Psf in the template image (image to convolve)
        @param scienceFwhmPix: FWHM (in pixels) of the Psf in the science image
        @param candidateList: a list of footprints/maskedImages for kernel candidates; 
                              if None then source detection is run.
            - Currently supported: list of Footprints or measAlg.PsfCandidateF

        @return a pipeBase.Struct containing these fields:
        - psfMatchedMaskedImage: the PSF-matched masked image =
            templateMaskedImage convolved with psfMatchingKernel.
            This has the same xy0, dimensions and wcs as scienceMaskedImage.
        - psfMatchingKernel: the PSF matching kernel
        - backgroundModel: differential background model
        - kernelCellSet: SpatialCellSet used to solve for the PSF matching kernel

        Raise a RuntimeError if input images have different dimensions
        """

        import lsstDebug
        display = lsstDebug.Info(__name__).display
        displayTemplate = lsstDebug.Info(__name__).displayTemplate
        displaySciIm = lsstDebug.Info(__name__).displaySciIm
        displaySpatialCells = lsstDebug.Info(__name__).displaySpatialCells
        maskTransparency = lsstDebug.Info(__name__).maskTransparency
        if not maskTransparency:
            maskTransparency = 0
        if display:
            ds9.setMaskTransparency(maskTransparency)

        if not candidateList:
            raise RuntimeError(
                "Candidate list must be populated by makeCandidateList")

        if not self._validateSize(templateMaskedImage, scienceMaskedImage):
            pexLog.Trace(self.log.getName(), 1,
                         "ERROR: Input images different size")
            raise RuntimeError("Input images different size")

        if display and displayTemplate:
            ds9.mtv(templateMaskedImage,
                    frame=lsstDebug.frame,
                    title="Image to convolve")
            lsstDebug.frame += 1

        if display and displaySciIm:
            ds9.mtv(scienceMaskedImage,
                    frame=lsstDebug.frame,
                    title="Image to not convolve")
            lsstDebug.frame += 1

        kernelCellSet = self._buildCellSet(templateMaskedImage,
                                           scienceMaskedImage, candidateList)

        if display and displaySpatialCells:
            diUtils.showKernelSpatialCells(scienceMaskedImage,
                                           kernelCellSet,
                                           symb="o",
                                           ctype=ds9.CYAN,
                                           ctypeUnused=ds9.YELLOW,
                                           ctypeBad=ds9.RED,
                                           size=4,
                                           frame=lsstDebug.frame,
                                           title="Image to not convolve")
            lsstDebug.frame += 1

        if templateFwhmPix and scienceFwhmPix:
            self.log.info("Matching Psf FWHM %.2f -> %.2f pix" %
                          (templateFwhmPix, scienceFwhmPix))

        if self.kConfig.useBicForKernelBasis:
            tmpKernelCellSet = self._buildCellSet(templateMaskedImage,
                                                  scienceMaskedImage,
                                                  candidateList)
            nbe = diffimTools.NbasisEvaluator(self.kConfig, templateFwhmPix,
                                              scienceFwhmPix)
            bicDegrees = nbe(tmpKernelCellSet, self.log)
            basisList = makeKernelBasisList(self.kConfig,
                                            templateFwhmPix,
                                            scienceFwhmPix,
                                            alardDegGauss=bicDegrees[0],
                                            metadata=self.metadata)
            del tmpKernelCellSet
        else:
            basisList = makeKernelBasisList(self.kConfig,
                                            templateFwhmPix,
                                            scienceFwhmPix,
                                            metadata=self.metadata)

        spatialSolution, psfMatchingKernel, backgroundModel = self._solve(
            kernelCellSet, basisList)

        psfMatchedMaskedImage = afwImage.MaskedImageF(
            templateMaskedImage.getBBox())
        doNormalize = False
        afwMath.convolve(psfMatchedMaskedImage, templateMaskedImage,
                         psfMatchingKernel, doNormalize)
        return pipeBase.Struct(
            matchedImage=psfMatchedMaskedImage,
            psfMatchingKernel=psfMatchingKernel,
            backgroundModel=backgroundModel,
            kernelCellSet=kernelCellSet,
        )

    @pipeBase.timeMethod
    def subtractExposures(self,
                          templateExposure,
                          scienceExposure,
                          templateFwhmPix=None,
                          scienceFwhmPix=None,
                          candidateList=None,
                          doWarping=True,
                          convolveTemplate=True):
        """!Register, Psf-match and subtract two Exposures

        Do the following, in order:
        - Warp templateExposure to match scienceExposure, if their WCSs do not already match
        - Determine a PSF matching kernel and differential background model
            that matches templateExposure to scienceExposure
        - PSF-match templateExposure to scienceExposure
        - Compute subtracted exposure (see return values for equation).

        @param templateExposure: exposure to PSF-match to scienceExposure
        @param scienceExposure: reference Exposure
        @param templateFwhmPix: FWHM (in pixels) of the Psf in the template image (image to convolve)
        @param scienceFwhmPix: FWHM (in pixels) of the Psf in the science image
        @param candidateList: a list of footprints/maskedImages for kernel candidates;
                              if None then source detection is run.
            - Currently supported: list of Footprints or measAlg.PsfCandidateF
        @param doWarping: what to do if templateExposure's and scienceExposure's WCSs do not match:
            - if True then warp templateExposure to match scienceExposure
            - if False then raise an Exception
        @param convolveTemplate: convolve the template image or the science image
            - if True, templateExposure is warped if doWarping, templateExposure is convolved
            - if False, templateExposure is warped if doWarping, scienceExposure is convolved

        @return a pipeBase.Struct containing these fields:
        - subtractedExposure: subtracted Exposure = scienceExposure - (matchedImage + backgroundModel)
        - matchedImage: templateExposure after warping to match templateExposure (if doWarping true),
            and convolving with psfMatchingKernel
        - psfMatchingKernel: PSF matching kernel
        - backgroundModel: differential background model
        - kernelCellSet: SpatialCellSet used to determine PSF matching kernel
        """
        results = self.matchExposures(templateExposure=templateExposure,
                                      scienceExposure=scienceExposure,
                                      templateFwhmPix=templateFwhmPix,
                                      scienceFwhmPix=scienceFwhmPix,
                                      candidateList=candidateList,
                                      doWarping=doWarping,
                                      convolveTemplate=convolveTemplate)

        subtractedExposure = afwImage.ExposureF(scienceExposure, True)
        if convolveTemplate:
            subtractedMaskedImage = subtractedExposure.getMaskedImage()
            subtractedMaskedImage -= results.matchedExposure.getMaskedImage()
            subtractedMaskedImage -= results.backgroundModel
        else:
            subtractedExposure.setMaskedImage(
                results.warpedExposure.getMaskedImage())
            subtractedMaskedImage = subtractedExposure.getMaskedImage()
            subtractedMaskedImage -= results.matchedExposure.getMaskedImage()
            subtractedMaskedImage -= results.backgroundModel

            # Preserve polarity of differences
            subtractedMaskedImage *= -1

            # Place back on native photometric scale
            subtractedMaskedImage /= results.psfMatchingKernel.computeImage(
                afwImage.ImageD(results.psfMatchingKernel.getDimensions()),
                False)

        import lsstDebug
        display = lsstDebug.Info(__name__).display
        displayDiffIm = lsstDebug.Info(__name__).displayDiffIm
        maskTransparency = lsstDebug.Info(__name__).maskTransparency
        if not maskTransparency:
            maskTransparency = 0
        if display:
            ds9.setMaskTransparency(maskTransparency)
        if display and displayDiffIm:
            ds9.mtv(templateExposure, frame=lsstDebug.frame, title="Template")
            lsstDebug.frame += 1
            ds9.mtv(results.matchedExposure,
                    frame=lsstDebug.frame,
                    title="Matched template")
            lsstDebug.frame += 1
            ds9.mtv(scienceExposure,
                    frame=lsstDebug.frame,
                    title="Science Image")
            lsstDebug.frame += 1
            ds9.mtv(subtractedExposure,
                    frame=lsstDebug.frame,
                    title="Difference Image")
            lsstDebug.frame += 1

        results.subtractedExposure = subtractedExposure
        return results

    @pipeBase.timeMethod
    def subtractMaskedImages(self,
                             templateMaskedImage,
                             scienceMaskedImage,
                             candidateList,
                             templateFwhmPix=None,
                             scienceFwhmPix=None):
        """!Psf-match and subtract two MaskedImages

        Do the following, in order:
        - PSF-match templateMaskedImage to scienceMaskedImage
        - Determine the differential background
        - Return the difference: scienceMaskedImage -
            ((warped templateMaskedImage convolved with psfMatchingKernel) + backgroundModel)

        @param templateMaskedImage: MaskedImage to PSF-match to scienceMaskedImage
        @param scienceMaskedImage: reference MaskedImage
        @param templateFwhmPix: FWHM (in pixels) of the Psf in the template image (image to convolve)
        @param scienceFwhmPix: FWHM (in pixels) of the Psf in the science image
        @param candidateList: a list of footprints/maskedImages for kernel candidates;
                              if None then source detection is run.
            - Currently supported: list of Footprints or measAlg.PsfCandidateF

        @return a pipeBase.Struct containing these fields:
        - subtractedMaskedImage = scienceMaskedImage - (matchedImage + backgroundModel)
        - matchedImage: templateMaskedImage convolved with psfMatchingKernel
        - psfMatchingKernel: PSF matching kernel
        - backgroundModel: differential background model
        - kernelCellSet: SpatialCellSet used to determine PSF matching kernel
        """
        if not candidateList:
            raise RuntimeError(
                "Candidate list must be populated by makeCandidateList")

        results = self.matchMaskedImages(
            templateMaskedImage=templateMaskedImage,
            scienceMaskedImage=scienceMaskedImage,
            candidateList=candidateList,
            templateFwhmPix=templateFwhmPix,
            scienceFwhmPix=scienceFwhmPix,
        )

        subtractedMaskedImage = afwImage.MaskedImageF(scienceMaskedImage, True)
        subtractedMaskedImage -= results.matchedImage
        subtractedMaskedImage -= results.backgroundModel
        results.subtractedMaskedImage = subtractedMaskedImage

        import lsstDebug
        display = lsstDebug.Info(__name__).display
        displayDiffIm = lsstDebug.Info(__name__).displayDiffIm
        maskTransparency = lsstDebug.Info(__name__).maskTransparency
        if not maskTransparency:
            maskTransparency = 0
        if display:
            ds9.setMaskTransparency(maskTransparency)
        if display and displayDiffIm:
            ds9.mtv(subtractedMaskedImage, frame=lsstDebug.frame)
            lsstDebug.frame += 1

        return results

    def getSelectSources(self,
                         exposure,
                         sigma=None,
                         doSmooth=True,
                         idFactory=None):
        """!Get sources to use for Psf-matching

        This method runs detection and measurement on an exposure.
        The returned set of sources will be used as candidates for
        Psf-matching.

        @param exposure: Exposure on which to run detection/measurement
        @param sigma: Detection threshold
        @param doSmooth: Whether or not to smooth the Exposure with Psf before detection
        @param idFactory: Factory for the generation of Source ids

        @return source catalog containing candidates for the Psf-matching
        """

        if idFactory:
            table = afwTable.SourceTable.make(self.selectSchema, idFactory)
        else:
            table = afwTable.SourceTable.make(self.selectSchema)
        mi = exposure.getMaskedImage()

        imArr = mi.getImage().getArray()
        maskArr = mi.getMask().getArray()
        miArr = np.ma.masked_array(imArr, mask=maskArr)
        try:
            bkgd = getBackground(mi,
                                 self.kConfig.afwBackgroundConfig).getImageF()
        except Exception:
            self.log.warn(
                "Failed to get background model.  Falling back to median background estimation"
            )
            bkgd = np.ma.extras.median(miArr)

        #Take off background for detection
        mi -= bkgd
        try:
            table.setMetadata(self.selectAlgMetadata)
            detRet = self.selectDetection.makeSourceCatalog(table=table,
                                                            exposure=exposure,
                                                            sigma=sigma,
                                                            doSmooth=doSmooth)
            selectSources = detRet.sources
            self.selectMeasurement.run(measCat=selectSources,
                                       exposure=exposure)
        finally:
            # Put back on the background in case it is needed down stream
            mi += bkgd
            del bkgd
        return selectSources

    def makeCandidateList(self,
                          templateExposure,
                          scienceExposure,
                          kernelSize,
                          candidateList=None):
        """!Make a list of acceptable KernelCandidates

        Accept or generate a list of candidate sources for
        Psf-matching, and examine the Mask planes in both of the
        images for indications of bad pixels

        @param templateExposure: Exposure that will be convolved
        @param scienceExposure: Exposure that will be matched-to
        @param kernelSize: Dimensions of the Psf-matching Kernel, used to grow detection footprints
        @param candidateList: List of Sources to examine. Elements must be of type afw.table.Source
                              or a type that wraps a Source and has a getSource() method, such as
                              meas.algorithms.PsfCandidateF.

        @return a list of dicts having a "source" and "footprint"
        field for the Sources deemed to be appropriate for Psf
        matching
        """
        if candidateList is None:
            candidateList = self.getSelectSources(scienceExposure)

        if len(candidateList) < 1:
            raise RuntimeError("No candidates in candidateList")

        listTypes = set(type(x) for x in candidateList)
        if len(listTypes) > 1:
            raise RuntimeError("Candidate list contains mixed types: %s" %
                               [l for l in listTypes])

        if not isinstance(candidateList[0], afwTable.SourceRecord):
            try:
                candidateList[0].getSource()
            except Exception as e:
                raise RuntimeError(
                    "Candidate List is of type: %s. " %
                    (type(candidateList[0])) +
                    "Can only make candidate list from list of afwTable.SourceRecords, "
                    +
                    "measAlg.PsfCandidateF or other type with a getSource() method: %s"
                    % (e))
            candidateList = [c.getSource() for c in candidateList]

        candidateList = diffimTools.sourceToFootprintList(
            candidateList, templateExposure, scienceExposure, kernelSize,
            self.kConfig.detectionConfig, self.log)
        if len(candidateList) == 0:
            raise RuntimeError(
                "Cannot find any objects suitable for KernelCandidacy")

        return candidateList

    def _adaptCellSize(self, candidateList):
        """! NOT IMPLEMENTED YET"""
        return self.kConfig.sizeCellX, self.kConfig.sizeCellY

    def _buildCellSet(self, templateMaskedImage, scienceMaskedImage,
                      candidateList):
        """!Build a SpatialCellSet for use with the solve method

        @param templateMaskedImage: MaskedImage to PSF-matched to scienceMaskedImage
        @param scienceMaskedImage: reference MaskedImage
        @param candidateList: a list of footprints/maskedImages for kernel candidates;
                              if None then source detection is run.
            - Currently supported: list of Footprints or measAlg.PsfCandidateF

        @return kernelCellSet: a SpatialCellSet for use with self._solve
        """
        if not candidateList:
            raise RuntimeError(
                "Candidate list must be populated by makeCandidateList")

        sizeCellX, sizeCellY = self._adaptCellSize(candidateList)

        # Object to store the KernelCandidates for spatial modeling
        kernelCellSet = afwMath.SpatialCellSet(templateMaskedImage.getBBox(),
                                               sizeCellX, sizeCellY)

        policy = pexConfig.makePolicy(self.kConfig)
        # Place candidates within the spatial grid
        for cand in candidateList:
            bbox = cand['footprint'].getBBox()

            tmi = afwImage.MaskedImageF(templateMaskedImage, bbox)
            smi = afwImage.MaskedImageF(scienceMaskedImage, bbox)
            cand = diffimLib.makeKernelCandidate(cand['source'], tmi, smi,
                                                 policy)

            self.log.logdebug(
                "Candidate %d at %f, %f" %
                (cand.getId(), cand.getXCenter(), cand.getYCenter()))
            kernelCellSet.insertCandidate(cand)

        return kernelCellSet

    def _validateSize(self, templateMaskedImage, scienceMaskedImage):
        """!Return True if two image-like objects are the same size
        """
        return templateMaskedImage.getDimensions(
        ) == scienceMaskedImage.getDimensions()

    def _validateWcs(self, templateExposure, scienceExposure):
        """!Return True if the WCS of the two Exposures have the same origin and extent
        """
        templateWcs = templateExposure.getWcs()
        scienceWcs = scienceExposure.getWcs()
        templateBBox = templateExposure.getBBox()
        scienceBBox = scienceExposure.getBBox()

        # LLC
        templateOrigin = templateWcs.pixelToSky(
            afwGeom.Point2D(templateBBox.getBegin()))
        scienceOrigin = scienceWcs.pixelToSky(
            afwGeom.Point2D(scienceBBox.getBegin()))

        # URC
        templateLimit = templateWcs.pixelToSky(
            afwGeom.Point2D(templateBBox.getEnd()))
        scienceLimit = scienceWcs.pixelToSky(
            afwGeom.Point2D(scienceBBox.getEnd()))

        self.log.info("Template Wcs : %f,%f -> %f,%f" %
                      (templateOrigin[0], templateOrigin[1], templateLimit[0],
                       templateLimit[1]))
        self.log.info("Science Wcs : %f,%f -> %f,%f" %
                      (scienceOrigin[0], scienceOrigin[1], scienceLimit[0],
                       scienceLimit[1]))

        templateBBox = afwGeom.Box2D(templateOrigin.getPosition(),
                                     templateLimit.getPosition())
        scienceBBox = afwGeom.Box2D(scienceOrigin.getPosition(),
                                    scienceLimit.getPosition())
        if not (templateBBox.overlaps(scienceBBox)):
            raise RuntimeError("Input images do not overlap at all")

        if ((templateOrigin.getPosition() != scienceOrigin.getPosition())
                or (templateLimit.getPosition() != scienceLimit.getPosition())
                or (templateExposure.getDimensions() !=
                    scienceExposure.getDimensions())):
            return False
        return True
Ejemplo n.º 9
0
import eups
import math, numpy
import lsst.utils.tests as tests
import lsst.pex.logging as logging
import lsst.afw.detection as afwDetection
import lsst.afw.image as afwImage
import lsst.afw.geom as afwGeom
import lsst.afw.display.ds9 as ds9
import lsst.meas.algorithms as algorithms
import lsst.meas.algorithms.defects as defects

try:
    type(verbose)
except NameError:
    verbose = 0
    logging.Trace_setVerbosity("algorithms.Interp", verbose)

try:
    type(display)
except NameError:
    display = False

class interpolationTestCase(unittest.TestCase):
    """A test case for interpolation"""
    def setUp(self):
        self.FWHM = 5
        self.psf = algorithms.DoubleGaussianPsf(15, 15, self.FWHM/(2*sqrt(2*log(2))))
        maskedImageFile = os.path.join(eups.productDir("afwdata"), "CFHT", "D4", "cal-53535-i-797722_1.fits")
            
        self.mi = afwImage.MaskedImageF(maskedImageFile)
        if False:                       # use sub-image?
Ejemplo n.º 10
0
import unittest

import lsst.utils.tests as tests
import lsst.utils
import lsst.afw.geom as afwGeom
import lsst.afw.image as afwImage
import lsst.afw.math as afwMath
import lsst.ip.diffim as ipDiffim
import lsst.ip.diffim.diffimTools as diffimTools
import lsst.pex.logging as logging
import lsst.pex.config as pexConfig

import lsst.afw.display.ds9 as ds9

verbosity = 7
logging.Trace_setVerbosity('lsst.ip.diffim', verbosity)

display = True
writefits = False

# This one compares DeltaFunction kernels of different types; iterate lambdaVal for different strengths

CFHTTORUN = 'cal-53535-i-797722_1'


class DiffimTestCases(unittest.TestCase):

    # D = I - (K.x.T + bg)
    def setUp(self, CFHT=True):
        lambdaValue = 1.0
Ejemplo n.º 11
0
import pdb  # we may want to say pdb.set_trace()
import unittest

import numpy

import eups
import lsst.afw.image as afwImage
import lsst.afw.math as afwMath
import lsst.utils.tests as utilsTests
import lsst.pex.logging as pexLog
import lsst.pex.exceptions as pexEx
import lsst.coadd.kaiser as coaddKaiser
import lsst.afw.image.testUtils as imTestUtils

Verbosity = 0  # increase to see trace
pexLog.Trace_setVerbosity("lsst.coadd.kaiser", Verbosity)

dataDir = eups.productDir("afwdata")
if not dataDir:
    raise RuntimeError("Must set up afwdata to run these tests")

InputImageNameSmall = "small_MI_img.fits"

currDir = os.path.abspath(os.path.dirname(__file__))
inFilePathSmallImage = os.path.join(dataDir, InputImageNameSmall)
#-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-


def refMedian(inArr):
    """Compute the median of an array of any shape.
    """
Ejemplo n.º 12
0
class ModelPsfMatchTask(PsfMatchTask):
    """!
\anchor ModelPsfMatchTask_

\brief Matching of two model Psfs, and application of the Psf-matching kernel to an input Exposure

\section ip_diffim_modelpsfmatch_Contents Contents

 - \ref ip_diffim_modelpsfmatch_Purpose
 - \ref ip_diffim_modelpsfmatch_Initialize
 - \ref ip_diffim_modelpsfmatch_IO
 - \ref ip_diffim_modelpsfmatch_Config
 - \ref ip_diffim_modelpsfmatch_Metadata
 - \ref ip_diffim_modelpsfmatch_Debug
 - \ref ip_diffim_modelpsfmatch_Example

#-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

\section ip_diffim_modelpsfmatch_Purpose   Description

This Task differs from ImagePsfMatchTask in that it matches two Psf _models_, by realizing
them in an Exposure-sized SpatialCellSet and then inserting each Psf-image pair into KernelCandidates.  
Because none of the pairs of sources that are to be matched should be invalid, all sigma clipping is 
turned off in ModelPsfMatchConfig.  And because there is no tracked _variance_ in the Psf images, the 
debugging and logging QA info should be interpreted with caution.

One item of note is that the sizes of Psf models are fixed (e.g. its defined as a 21x21 matrix).  When the 
Psf-matching kernel is being solved for, the Psf "image" is convolved with each kernel basis function,
leading to a loss of information around the borders.  This pixel loss will be problematic for the numerical
stability of the kernel solution if the size of the convolution kernel (set by ModelPsfMatchConfig.kernelSize)
is much bigger than: psfSize//2.  Thus the sizes of Psf-model matching kernels are typically smaller
than their image-matching counterparts.  If the size of the kernel is too small, the convolved stars will
look "boxy"; if the kernel is too large, the kernel solution will be "noisy".  This is a trade-off that
needs careful attention for a given dataset.

The primary use case for this Task is in matching an Exposure to a constant-across-the-sky Psf model for the 
purposes of image coaddition.  It is important to note that in the code, the "template" Psf is the Psf
that the science image gets matched to.  In this sense the order of template and science image are 
reversed, compared to ImagePsfMatchTask, which operates on the template image.

#-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

\section ip_diffim_modelpsfmatch_Initialize    Task initialization

\copydoc \_\_init\_\_

#-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

\section ip_diffim_modelpsfmatch_IO        Invoking the Task

\copydoc run

#-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

\section ip_diffim_modelpsfmatch_Config       Configuration parameters

See \ref ModelPsfMatchConfig

#-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

\section ip_diffim_modelpsfmatch_Metadata   Quantities set in Metadata

See \ref ip_diffim_psfmatch_Metadata "PsfMatchTask"

#-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

\section ip_diffim_modelpsfmatch_Debug     Debug variables

The \link lsst.pipe.base.cmdLineTask.CmdLineTask command line task\endlink interface supports a
flag \c -d/--debug to import \b debug.py from your \c PYTHONPATH.  The relevant contents of debug.py 
for this Task include:

\code{.py}
    import sys
    import lsstDebug
    def DebugInfo(name):
        di = lsstDebug.getInfo(name)   
        if name == "lsst.ip.diffim.psfMatch":
            di.display = True                 # global
            di.maskTransparency = 80          # ds9 mask transparency
            di.displayCandidates = True       # show all the candidates and residuals
            di.displayKernelBasis = False     # show kernel basis functions
            di.displayKernelMosaic = True     # show kernel realized across the image
            di.plotKernelSpatialModel = False # show coefficients of spatial model
            di.showBadCandidates = True       # show the bad candidates (red) along with good (green)
        elif name == "lsst.ip.diffim.modelPsfMatch":
            di.display = True                 # global
            di.maskTransparency = 30          # ds9 mask transparency
            di.displaySpatialCells = True     # show spatial cells before the fit
        return di
    lsstDebug.Info = DebugInfo
    lsstDebug.frame = 1      
\endcode

Note that if you want addional logging info, you may add to your scripts:
\code{.py}
import lsst.pex.logging as pexLog
pexLog.Trace_setVerbosity('lsst.ip.diffim', 5)
\endcode

#-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

\section ip_diffim_modelpsfmatch_Example   A complete example of using ModelPsfMatchTask

This code is modelPsfMatchTask.py in the examples directory, and can be run as \em e.g.
\code
examples/modelPsfMatchTask.py
examples/modelPsfMatchTask.py --debug 
examples/modelPsfMatchTask.py --debug --template /path/to/templateExp.fits --science /path/to/scienceExp.fits
\endcode

\dontinclude modelPsfMatchTask.py
Create a subclass of ModelPsfMatchTask that accepts two exposures.  Note that the "template" exposure
contains the Psf that will get matched to, and the "science" exposure is the one that will be convolved:
\skip MyModelPsfMatchTask
\until return

And allow the user the freedom to either run the script in default mode, or point to their own images on disk.
Note that these images must be readable as an lsst.afw.image.Exposure:
\skip main
\until parse_args

We have enabled some minor display debugging in this script via the --debug option.  However, if you 
have an lsstDebug debug.py in your PYTHONPATH you will get additional debugging displays.  The following
block checks for this script:
\skip args.debug
\until sys.stderr

\dontinclude modelPsfMatchTask.py
Finally, we call a run method that we define below.  First set up a Config and modify some of the parameters.
In particular we don't want to "grow" the sizes of the kernel or KernelCandidates, since we are operating with
fixed--size images (i.e. the size of the input Psf models).
\skip run(args)
\until False

Make sure the images (if any) that were sent to the script exist on disk and are readable.  If no images
are sent, make some fake data up for the sake of this example script (have a look at the code if you want
more details on generateFakeData):
\skip requested
\until sizeCellY

Display the two images if --debug:
\skip args.debug
\until Science

Create and run the Task:
\skip Create
\until result

And finally provide optional debugging display of the Psf-matched (via the Psf models) science image:
\skip args.debug
\until result.psfMatchedExposure

#-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

    """
    ConfigClass = ModelPsfMatchConfig

    def __init__(self, *args, **kwargs):
        """!Create a ModelPsfMatchTask

        \param *args arguments to be passed to lsst.ip.diffim.PsfMatchTask.__init__
        \param **kwargs keyword arguments to be passed to lsst.ip.diffim.PsfMatchTask.__init__

        Upon initialization, the kernel configuration is defined by self.config.kernel.active.  This Task
        does have a run() method, which is the default way to call the Task.
        """
        PsfMatchTask.__init__(self, *args, **kwargs)
        self.kConfig = self.config.kernel.active

    @pipeBase.timeMethod
    def run(self, exposure, referencePsfModel, kernelSum=1.0):
        """!Psf-match an exposure to a model Psf

        @param exposure: Exposure to Psf-match to the reference Psf model;
            it must return a valid PSF model via exposure.getPsf()
        @param referencePsfModel: The Psf model to match to (an lsst.afw.detection.Psf)
        @param kernelSum: A multipicative factor to apply to the kernel sum (default=1.0)

        @return
        - psfMatchedExposure: the Psf-matched Exposure.  This has the same parent bbox, Wcs, Calib and 
            Filter as the input Exposure but no Psf.  In theory the Psf should equal referencePsfModel but 
            the match is likely not exact.
        - psfMatchingKernel: the spatially varying Psf-matching kernel
        - kernelCellSet: SpatialCellSet used to solve for the Psf-matching kernel

        Raise a RuntimeError if the Exposure does not contain a Psf model
        """
        if not exposure.hasPsf():
            raise RuntimeError("exposure does not contain a Psf model")

        maskedImage = exposure.getMaskedImage()

        self.log.log(pexLog.Log.INFO, "compute Psf-matching kernel")
        kernelCellSet = self._buildCellSet(exposure, referencePsfModel)
        width, height = referencePsfModel.getLocalKernel().getDimensions()
        psfAttr1 = measAlg.PsfAttributes(exposure.getPsf(), width // 2,
                                         height // 2)
        psfAttr2 = measAlg.PsfAttributes(referencePsfModel, width // 2,
                                         height // 2)
        s1 = psfAttr1.computeGaussianWidth(
            psfAttr1.ADAPTIVE_MOMENT)  # gaussian sigma in pixels
        s2 = psfAttr2.computeGaussianWidth(
            psfAttr2.ADAPTIVE_MOMENT)  # gaussian sigma in pixels
        fwhm1 = s1 * sigma2fwhm  # science Psf
        fwhm2 = s2 * sigma2fwhm  # template Psf

        basisList = makeKernelBasisList(self.kConfig,
                                        fwhm1,
                                        fwhm2,
                                        metadata=self.metadata)
        spatialSolution, psfMatchingKernel, backgroundModel = self._solve(
            kernelCellSet, basisList)

        if psfMatchingKernel.isSpatiallyVarying():
            sParameters = num.array(psfMatchingKernel.getSpatialParameters())
            sParameters[0][0] = kernelSum
            psfMatchingKernel.setSpatialParameters(sParameters)
        else:
            kParameters = num.array(psfMatchingKernel.getKernelParameters())
            kParameters[0] = kernelSum
            psfMatchingKernel.setKernelParameters(kParameters)

        self.log.log(pexLog.Log.INFO,
                     "Psf-match science exposure to reference")
        psfMatchedExposure = afwImage.ExposureF(exposure.getBBox(),
                                                exposure.getWcs())
        psfMatchedExposure.setFilter(exposure.getFilter())
        psfMatchedExposure.setCalib(exposure.getCalib())
        psfMatchedMaskedImage = psfMatchedExposure.getMaskedImage()

        # Normalize the psf-matching kernel while convolving since its magnitude is meaningless
        # when PSF-matching one model to another.
        doNormalize = True
        afwMath.convolve(psfMatchedMaskedImage, maskedImage, psfMatchingKernel,
                         doNormalize)

        self.log.log(pexLog.Log.INFO, "done")
        return pipeBase.Struct(psfMatchedExposure=psfMatchedExposure,
                               psfMatchingKernel=psfMatchingKernel,
                               kernelCellSet=kernelCellSet,
                               metadata=self.metadata)

    def _diagnostic(self, kernelCellSet, spatialSolution, spatialKernel,
                    spatialBg):
        """!Print diagnostic information on spatial kernel and background fit

        The debugging diagnostics are not really useful here, since the images we are matching have
        no variance.  Thus override the _diagnostic method to generate no logging information"""
        return

    def _buildCellSet(self, exposure, referencePsfModel):
        """!Build a SpatialCellSet for use with the solve method

        @param exposure: The science exposure that will be convolved; must contain a Psf
        @param referencePsfModel: Psf model to match to

        @return kernelCellSet: a SpatialCellSet to be used by self._solve

        Raise a RuntimeError if the reference Psf model and science Psf model have different dimensions
        """
        scienceBBox = exposure.getBBox()
        sciencePsfModel = exposure.getPsf()
        # The Psf base class does not support getKernel() in general, as there are some Psf
        # classes for which this is not meaningful.
        # Many Psfs we use in practice are KernelPsfs, and this algorithm will work fine for them,
        # but someday it should probably be modified to support arbitrary Psfs.
        referencePsfModel = measAlg.KernelPsf.swigConvert(referencePsfModel)
        sciencePsfModel = measAlg.KernelPsf.swigConvert(sciencePsfModel)
        if referencePsfModel is None or sciencePsfModel is None:
            raise RuntimeError(
                "ERROR: Psf matching is only implemented for KernelPsfs")
        if (referencePsfModel.getKernel().getDimensions() !=
                sciencePsfModel.getKernel().getDimensions()):
            pexLog.Trace(
                self.log.getName(), 1,
                "ERROR: Dimensions of reference Psf and science Psf different; exiting"
            )
            raise RuntimeError, "ERROR: Dimensions of reference Psf and science Psf different; exiting"

        psfWidth, psfHeight = referencePsfModel.getKernel().getDimensions()
        maxKernelSize = min(psfWidth, psfHeight) - 1
        if maxKernelSize % 2 == 0:
            maxKernelSize -= 1
        if self.kConfig.kernelSize > maxKernelSize:
            raise ValueError, "Kernel size (%d) too big to match Psfs of size %d; reduce to at least %d" % (
                self.kConfig.kernelSize, psfWidth, maxKernelSize)

        # Infer spatial order of Psf model!
        #
        # Infer from the number of spatial parameters.
        # (O + 1) * (O + 2) / 2 = N
        # O^2 + 3 * O + 2 * (1 - N) = 0
        #
        # Roots are [-3 +/- sqrt(9 - 8 * (1 - N))] / 2
        #
        nParameters = sciencePsfModel.getKernel().getNSpatialParameters()
        root = num.sqrt(9 - 8 * (1 - nParameters))
        if (root != root // 1):  # We know its an integer solution
            pexLog.Trace(self.log.getName(), 3,
                         "Problem inferring spatial order of image's Psf")
        else:
            order = (root - 3) / 2
            if (order != order // 1):
                pexLog.Trace(self.log.getName(), 3,
                             "Problem inferring spatial order of image's Psf")
            else:
                pexLog.Trace(
                    self.log.getName(), 2,
                    "Spatial order of Psf = %d; matching kernel order = %d" %
                    (order, self.kConfig.spatialKernelOrder))

        regionSizeX, regionSizeY = scienceBBox.getDimensions()
        scienceX0, scienceY0 = scienceBBox.getMin()

        sizeCellX = self.kConfig.sizeCellX
        sizeCellY = self.kConfig.sizeCellY

        kernelCellSet = afwMath.SpatialCellSet(
            afwGeom.Box2I(afwGeom.Point2I(scienceX0, scienceY0),
                          afwGeom.Extent2I(regionSizeX, regionSizeY)),
            sizeCellX, sizeCellY)

        nCellX = regionSizeX // sizeCellX
        nCellY = regionSizeY // sizeCellY
        dimenR = referencePsfModel.getKernel().getDimensions()
        dimenS = sciencePsfModel.getKernel().getDimensions()

        policy = pexConfig.makePolicy(self.kConfig)
        for row in range(nCellY):
            # place at center of cell
            posY = sizeCellY * row + sizeCellY // 2 + scienceY0

            for col in range(nCellX):
                # place at center of cell
                posX = sizeCellX * col + sizeCellX // 2 + scienceX0

                pexLog.Trace(
                    self.log.getName(), 5,
                    "Creating Psf candidate at %.1f %.1f" % (posX, posY))

                # reference kernel image, at location of science subimage
                kernelImageR = referencePsfModel.computeImage(
                    afwGeom.Point2D(posX, posY)).convertF()
                kernelMaskR = afwImage.MaskU(dimenR)
                kernelMaskR.set(0)
                kernelVarR = afwImage.ImageF(dimenR)
                kernelVarR.set(1.0)
                referenceMI = afwImage.MaskedImageF(kernelImageR, kernelMaskR,
                                                    kernelVarR)

                # kernel image we are going to convolve
                kernelImageS = sciencePsfModel.computeImage(
                    afwGeom.Point2D(posX, posY)).convertF()
                kernelMaskS = afwImage.MaskU(dimenS)
                kernelMaskS.set(0)
                kernelVarS = afwImage.ImageF(dimenS)
                kernelVarS.set(1.0)
                scienceMI = afwImage.MaskedImageF(kernelImageS, kernelMaskS,
                                                  kernelVarS)

                # The image to convolve is the science image, to the reference Psf.
                kc = diffimLib.makeKernelCandidate(posX, posY, scienceMI,
                                                   referenceMI, policy)
                kernelCellSet.insertCandidate(kc)

        import lsstDebug
        display = lsstDebug.Info(__name__).display
        displaySpatialCells = lsstDebug.Info(__name__).displaySpatialCells
        maskTransparency = lsstDebug.Info(__name__).maskTransparency
        if not maskTransparency:
            maskTransparency = 0
        if display:
            ds9.setMaskTransparency(maskTransparency)
        if display and displaySpatialCells:
            diUtils.showKernelSpatialCells(exposure.getMaskedImage(),
                                           kernelCellSet,
                                           symb="o",
                                           ctype=ds9.CYAN,
                                           ctypeUnused=ds9.YELLOW,
                                           ctypeBad=ds9.RED,
                                           size=4,
                                           frame=lsstDebug.frame,
                                           title="Image to be convolved")
            lsstDebug.frame += 1
        return kernelCellSet