25 import lsst.pex.config 
as pexConfig
 
   35 from .makeKernelBasisList 
import makeKernelBasisList
 
   36 from .psfMatch 
import PsfMatchTask, PsfMatchConfigDF, PsfMatchConfigAL
 
   37 from . 
import utils 
as diffimUtils
 
   38 from . 
import diffimLib
 
   39 from . 
import diffimTools
 
   42 __all__ = [
"ImagePsfMatchConfig", 
"ImagePsfMatchTask", 
"subtractAlgorithmRegistry"]
 
   44 sigma2fwhm = 2.*np.sqrt(2.*np.log(2.))
 
   48     """Configuration for image-to-image Psf matching. 
   50     kernel = pexConfig.ConfigChoiceField(
 
   58     selectDetection = pexConfig.ConfigurableField(
 
   59         target=SourceDetectionTask,
 
   60         doc=
"Initial detections used to feed stars to kernel fitting",
 
   62     selectMeasurement = pexConfig.ConfigurableField(
 
   63         target=SingleFrameMeasurementTask,
 
   64         doc=
"Initial measurements used to feed stars to kernel fitting",
 
   74         self.
selectMeasurement.algorithms.names = (
'base_SdssCentroid', 
'base_PsfFlux', 
'base_PixelFlags',
 
   75                                                    'base_SdssShape', 
'base_GaussianFlux', 
'base_SkyCoord')
 
   82     """Psf-match two MaskedImages or Exposures using the sources in the images. 
   87         Arguments to be passed to lsst.ip.diffim.PsfMatchTask.__init__ 
   89         Keyword arguments to be passed to lsst.ip.diffim.PsfMatchTask.__init__ 
   93     Upon initialization, the kernel configuration is defined by self.config.kernel.active. 
   94     The task creates an lsst.afw.math.Warper from the subConfig self.config.kernel.active.warpingConfig. 
   95     A schema for the selection and measurement of candidate lsst.ip.diffim.KernelCandidates is 
   96     defined, and used to initize subTasks selectDetection (for candidate detection) and selectMeasurement 
   97     (for candidate measurement). 
  101     Build a Psf-matching kernel using two input images, either as MaskedImages (in which case they need 
  102     to be astrometrically aligned) or Exposures (in which case astrometric alignment will happen by 
  103     default but may be turned off).  This requires a list of input Sources which may be provided 
  104     by the calling Task; if not, the Task will perform a coarse source detection 
  105     and selection for this purpose. Sources are vetted for signal-to-noise and masked pixels 
  106     (in both the template and science image), and substamps around each acceptable 
  107     source are extracted and used to create an instance of KernelCandidate. 
  108     Each KernelCandidate is then placed within a lsst.afw.math.SpatialCellSet, which is used by an ensemble of 
  109     lsst.afw.math.CandidateVisitor instances to build the Psf-matching kernel.   These visitors include, in 
  110     the order that they are called: BuildSingleKernelVisitor, KernelSumVisitor, BuildSpatialKernelVisitor, 
  111     and AssessSpatialKernelVisitor. 
  113     Sigma clipping of KernelCandidates is performed as follows: 
  115     - BuildSingleKernelVisitor, using the substamp diffim residuals from the per-source kernel fit, 
  116         if PsfMatchConfig.singleKernelClipping is True 
  117     - KernelSumVisitor, using the mean and standard deviation of the kernel sum from all candidates, 
  118         if PsfMatchConfig.kernelSumClipping is True 
  119     - AssessSpatialKernelVisitor, using the substamp diffim ressiduals from the spatial kernel fit, 
  120         if PsfMatchConfig.spatialKernelClipping is True 
  122     The actual solving for the kernel (and differential background model) happens in 
  123     lsst.ip.diffim.PsfMatchTask._solve.  This involves a loop over the SpatialCellSet that first builds the 
  124     per-candidate matching kernel for the requested number of KernelCandidates per cell 
  125     (PsfMatchConfig.nStarPerCell).  The quality of this initial per-candidate difference image is examined, 
  126     using moments of the pixel residuals in the difference image normalized by the square root of the variance 
  127     (i.e. sigma); ideally this should follow a normal (0, 1) distribution, 
  128     but the rejection thresholds are set 
  129     by the config (PsfMatchConfig.candidateResidualMeanMax and PsfMatchConfig.candidateResidualStdMax). 
  130     All candidates that pass this initial build are then examined en masse to find the 
  131     mean/stdev of the kernel sums across all candidates. 
  132     Objects that are significantly above or below the mean, 
  133     typically due to variability or sources that are saturated in one image but not the other, 
  134     are also rejected.This threshold is defined by PsfMatchConfig.maxKsumSigma. 
  135     Finally, a spatial model is built using all currently-acceptable candidates, 
  136     and the spatial model used to derive a second set of (spatial) residuals 
  137     which are again used to reject bad candidates, using the same thresholds as above. 
  141     There is no run() method for this Task.  Instead there are 4 methods that 
  142     may be used to invoke the Psf-matching.  These are 
  143     `~lsst.ip.diffim.imagePsfMatch.ImagePsfMatchTask.matchMaskedImages`, 
  144     `~lsst.ip.diffim.imagePsfMatch.ImagePsfMatchTask.subtractMaskedImages`, 
  145     `~lsst.ip.diffim.imagePsfMatch.ImagePsfMatchTask.matchExposures`, and 
  146     `~lsst.ip.diffim.imagePsfMatch.ImagePsfMatchTask.subtractExposures`. 
  148     The methods that operate on lsst.afw.image.MaskedImage require that the images already be astrometrically 
  149     aligned, and are the same shape.  The methods that operate on lsst.afw.image.Exposure allow for the 
  150     input images to be misregistered and potentially be different sizes; by default a 
  151     lsst.afw.math.LanczosWarpingKernel is used to perform the astrometric alignment.  The methods 
  152     that "match" images return a Psf-matched image, while the methods that "subtract" images 
  153     return a Psf-matched and template subtracted image. 
  155     See each method's returned lsst.pipe.base.Struct for more details. 
  159     The lsst.pipe.base.cmdLineTask.CmdLineTask command line task interface supports a 
  160     flag -d/--debug to import debug.py from your PYTHONPATH.  The relevant contents of debug.py 
  161     for this Task include: 
  168             di = lsstDebug.getInfo(name) 
  169             if name == "lsst.ip.diffim.psfMatch": 
  170                 di.display = True                 # enable debug output 
  171                 di.maskTransparency = 80          # display mask transparency 
  172                 di.displayCandidates = True       # show all the candidates and residuals 
  173                 di.displayKernelBasis = False     # show kernel basis functions 
  174                 di.displayKernelMosaic = True     # show kernel realized across the image 
  175                 di.plotKernelSpatialModel = False # show coefficients of spatial model 
  176                 di.showBadCandidates = True       # show the bad candidates (red) along with good (green) 
  177             elif name == "lsst.ip.diffim.imagePsfMatch": 
  178                 di.display = True                 # enable debug output 
  179                 di.maskTransparency = 30          # display mask transparency 
  180                 di.displayTemplate = True         # show full (remapped) template 
  181                 di.displaySciIm = True            # show science image to match to 
  182                 di.displaySpatialCells = True     # show spatial cells 
  183                 di.displayDiffIm = True           # show difference image 
  184                 di.showBadCandidates = True       # show the bad candidates (red) along with good (green) 
  185             elif name == "lsst.ip.diffim.diaCatalogSourceSelector": 
  186                 di.display = False                # enable debug output 
  187                 di.maskTransparency = 30          # display mask transparency 
  188                 di.displayExposure = True         # show exposure with candidates indicated 
  189                 di.pauseAtEnd = False             # pause when done 
  191         lsstDebug.Info = DebugInfo 
  194     Note that if you want addional logging info, you may add to your scripts: 
  198         import lsst.log.utils as logUtils 
  199         logUtils.traceSetAt("ip.diffim", 4) 
  203     A complete example of using ImagePsfMatchTask 
  205     This code is imagePsfMatchTask.py in the examples directory, and can be run as e.g. 
  209         examples/imagePsfMatchTask.py --debug 
  210         examples/imagePsfMatchTask.py --debug --mode="matchExposures" 
  211         examples/imagePsfMatchTask.py --debug --template /path/to/templateExp.fits 
  212         --science /path/to/scienceExp.fits 
  214     Create a subclass of ImagePsfMatchTask that allows us to either match exposures, or subtract exposures: 
  218         class MyImagePsfMatchTask(ImagePsfMatchTask): 
  220             def __init__(self, args, kwargs): 
  221                 ImagePsfMatchTask.__init__(self, args, kwargs) 
  223             def run(self, templateExp, scienceExp, mode): 
  224                 if mode == "matchExposures": 
  225                     return self.matchExposures(templateExp, scienceExp) 
  226                 elif mode == "subtractExposures": 
  227                     return self.subtractExposures(templateExp, scienceExp) 
  229     And allow the user the freedom to either run the script in default mode, 
  230     or point to their own images on disk. 
  231     Note that these images must be readable as an lsst.afw.image.Exposure. 
  233     We have enabled some minor display debugging in this script via the --debug option.  However, if you 
  234     have an lsstDebug debug.py in your PYTHONPATH you will get additional debugging displays.  The following 
  235     block checks for this script: 
  242                 # Since I am displaying 2 images here, set the starting frame number for the LSST debug LSST 
  243                 debug.lsstDebug.frame = 3 
  244             except ImportError as e: 
  245                 print(e, file=sys.stderr) 
  247     Finally, we call a run method that we define below. 
  248     First set up a Config and modify some of the parameters. 
  249     E.g. use an "Alard-Lupton" sum-of-Gaussian basis, 
  250     fit for a differential background, and use low order spatial 
  251     variation in the kernel and background: 
  257         # Create the Config and use sum of gaussian basis 
  259         config = ImagePsfMatchTask.ConfigClass() 
  260         config.kernel.name = "AL" 
  261         config.kernel.active.fitForBackground = True 
  262         config.kernel.active.spatialKernelOrder = 1 
  263         config.kernel.active.spatialBgOrder = 0 
  265     Make sure the images (if any) that were sent to the script exist on disk and are readable.  If no images 
  266     are sent, make some fake data up for the sake of this example script (have a look at the code if you want 
  267     more details on generateFakeImages): 
  271         # Run the requested method of the Task 
  272         if args.template is not None and args.science is not None: 
  273             if not os.path.isfile(args.template): 
  274                 raise Exception("Template image %s does not exist" % (args.template)) 
  275             if not os.path.isfile(args.science): 
  276                 raise Exception("Science image %s does not exist" % (args.science)) 
  278                 templateExp = afwImage.ExposureF(args.template) 
  279             except Exception as e: 
  280                 raise Exception("Cannot read template image %s" % (args.template)) 
  282                 scienceExp = afwImage.ExposureF(args.science) 
  283             except Exception as e: 
  284                 raise Exception("Cannot read science image %s" % (args.science)) 
  286             templateExp, scienceExp = generateFakeImages() 
  287             config.kernel.active.sizeCellX = 128 
  288             config.kernel.active.sizeCellY = 128 
  290     Create and run the Task: 
  295         psfMatchTask = MyImagePsfMatchTask(config=config) 
  297         result = psfMatchTask.run(templateExp, scienceExp, args.mode) 
  299     And finally provide some optional debugging displays: 
  304         # See if the LSST debug has incremented the frame number; if not start with frame 3 
  306             frame = debug.lsstDebug.frame + 1 
  309         afwDisplay.Display(frame=frame).mtv(result.matchedExposure, 
  310                                             title="Example script: Matched Template Image") 
  311         if "subtractedExposure" in result.getDict(): 
  312             afwDisplay.Display(frame=frame + 1).mtv(result.subtractedExposure, 
  313                                                     title="Example script: Subtracted Image") 
  316     ConfigClass = ImagePsfMatchConfig
 
  319         """Create the ImagePsfMatchTask. 
  321         PsfMatchTask.__init__(self, *args, **kwargs)
 
  323         self.
_warper = afwMath.Warper.fromConfig(self.
kConfig.warpingConfig)
 
  330         self.makeSubtask(
"selectDetection", schema=self.
selectSchema)
 
  334         """Return the FWHM in pixels of a Psf. 
  336         sigPix = psf.computeShape().getDeterminantRadius()
 
  337         return sigPix*sigma2fwhm
 
  341                        templateFwhmPix=None, scienceFwhmPix=None,
 
  342                        candidateList=None, doWarping=True, convolveTemplate=True):
 
  343         """Warp and PSF-match an exposure to the reference. 
  345         Do the following, in order: 
  347         - Warp templateExposure to match scienceExposure, 
  348             if doWarping True and their WCSs do not already match 
  349         - Determine a PSF matching kernel and differential background model 
  350             that matches templateExposure to scienceExposure 
  351         - Convolve templateExposure by PSF matching kernel 
  355         templateExposure : `lsst.afw.image.Exposure` 
  356             Exposure to warp and PSF-match to the reference masked image 
  357         scienceExposure : `lsst.afw.image.Exposure` 
  358             Exposure whose WCS and PSF are to be matched to 
  359         templateFwhmPix :`float` 
  360             FWHM (in pixels) of the Psf in the template image (image to convolve) 
  361         scienceFwhmPix : `float` 
  362             FWHM (in pixels) of the Psf in the science image 
  363         candidateList : `list`, optional 
  364             a list of footprints/maskedImages for kernel candidates; 
  365             if `None` then source detection is run. 
  367             - Currently supported: list of Footprints or measAlg.PsfCandidateF 
  370             what to do if ``templateExposure`` and ``scienceExposure`` WCSs do not match: 
  372             - if `True` then warp ``templateExposure`` to match ``scienceExposure`` 
  373             - if `False` then raise an Exception 
  375         convolveTemplate : `bool` 
  376             Whether to convolve the template image or the science image: 
  378             - if `True`, ``templateExposure`` is warped if doWarping, 
  379               ``templateExposure`` is convolved 
  380             - if `False`, ``templateExposure`` is warped if doWarping, 
  381               ``scienceExposure`` is convolved 
  385         results : `lsst.pipe.base.Struct` 
  386             An `lsst.pipe.base.Struct` containing these fields: 
  388             - ``matchedImage`` : the PSF-matched exposure = 
  389                 Warped ``templateExposure`` convolved by psfMatchingKernel. This has: 
  391                 - the same parent bbox, Wcs and PhotoCalib as scienceExposure 
  392                 - the same filter as templateExposure 
  393                 - no Psf (because the PSF-matching process does not compute one) 
  395             - ``psfMatchingKernel`` : the PSF matching kernel 
  396             - ``backgroundModel`` : differential background model 
  397             - ``kernelCellSet`` : SpatialCellSet used to solve for the PSF matching kernel 
  402            Raised if doWarping is False and ``templateExposure`` and 
  403            ``scienceExposure`` WCSs do not match 
  405         if not self.
_validateWcs(templateExposure, scienceExposure):
 
  407                 self.log.
info(
"Astrometrically registering template to science image")
 
  408                 templatePsf = templateExposure.getPsf()
 
  411                                                            scienceExposure.getWcs())
 
  412                 psfWarped = 
WarpedPsf(templatePsf, xyTransform)
 
  415                                                              destBBox=scienceExposure.getBBox())
 
  416                 templateExposure.setPsf(psfWarped)
 
  418                 self.log.
error(
"ERROR: Input images not registered")
 
  419                 raise RuntimeError(
"Input images not registered")
 
  421         if templateFwhmPix 
is None:
 
  422             if not templateExposure.hasPsf():
 
  423                 self.log.
warn(
"No estimate of Psf FWHM for template image")
 
  425                 templateFwhmPix = self.
getFwhmPix(templateExposure.getPsf())
 
  426                 self.log.
info(
"templateFwhmPix: {}".
format(templateFwhmPix))
 
  428         if scienceFwhmPix 
is None:
 
  429             if not scienceExposure.hasPsf():
 
  430                 self.log.
warn(
"No estimate of Psf FWHM for science image")
 
  432                 scienceFwhmPix = self.
getFwhmPix(scienceExposure.getPsf())
 
  433                 self.log.
info(
"scienceFwhmPix: {}".
format(scienceFwhmPix))
 
  438                 templateExposure, scienceExposure, kernelSize, candidateList)
 
  440                 templateExposure.getMaskedImage(), scienceExposure.getMaskedImage(), candidateList,
 
  441                 templateFwhmPix=templateFwhmPix, scienceFwhmPix=scienceFwhmPix)
 
  445                 templateExposure, scienceExposure, kernelSize, candidateList)
 
  447                 scienceExposure.getMaskedImage(), templateExposure.getMaskedImage(), candidateList,
 
  448                 templateFwhmPix=scienceFwhmPix, scienceFwhmPix=templateFwhmPix)
 
  451         psfMatchedExposure.setFilter(templateExposure.getFilter())
 
  452         psfMatchedExposure.setPhotoCalib(scienceExposure.getPhotoCalib())
 
  453         results.warpedExposure = templateExposure
 
  454         results.matchedExposure = psfMatchedExposure
 
  459                           templateFwhmPix=None, scienceFwhmPix=None):
 
  460         """PSF-match a MaskedImage (templateMaskedImage) to a reference MaskedImage (scienceMaskedImage). 
  462         Do the following, in order: 
  464         - Determine a PSF matching kernel and differential background model 
  465             that matches templateMaskedImage to scienceMaskedImage 
  466         - Convolve templateMaskedImage by the PSF matching kernel 
  470         templateMaskedImage : `lsst.afw.image.MaskedImage` 
  471             masked image to PSF-match to the reference masked image; 
  472             must be warped to match the reference masked image 
  473         scienceMaskedImage : `lsst.afw.image.MaskedImage` 
  474             maskedImage whose PSF is to be matched to 
  475         templateFwhmPix : `float` 
  476             FWHM (in pixels) of the Psf in the template image (image to convolve) 
  477         scienceFwhmPix : `float` 
  478             FWHM (in pixels) of the Psf in the science image 
  479         candidateList : `list`, optional 
  480             A list of footprints/maskedImages for kernel candidates; 
  481             if `None` then source detection is run. 
  483             - Currently supported: list of Footprints or measAlg.PsfCandidateF 
  488         An `lsst.pipe.base.Struct` containing these fields: 
  490         - psfMatchedMaskedImage: the PSF-matched masked image = 
  491             ``templateMaskedImage`` convolved with psfMatchingKernel. 
  492             This has the same xy0, dimensions and wcs as ``scienceMaskedImage``. 
  493         - psfMatchingKernel: the PSF matching kernel 
  494         - backgroundModel: differential background model 
  495         - kernelCellSet: SpatialCellSet used to solve for the PSF matching kernel 
  500             Raised if input images have different dimensions 
  506         displaySpatialCells = 
lsstDebug.Info(__name__).displaySpatialCells
 
  508         if not maskTransparency:
 
  511             afwDisplay.setDefaultMaskTransparency(maskTransparency)
 
  513         if not candidateList:
 
  514             raise RuntimeError(
"Candidate list must be populated by makeCandidateList")
 
  516         if not self.
_validateSize(templateMaskedImage, scienceMaskedImage):
 
  517             self.log.
error(
"ERROR: Input images different size")
 
  518             raise RuntimeError(
"Input images different size")
 
  520         if display 
and displayTemplate:
 
  521             disp = afwDisplay.Display(frame=lsstDebug.frame)
 
  522             disp.mtv(templateMaskedImage, title=
"Image to convolve")
 
  525         if display 
and displaySciIm:
 
  526             disp = afwDisplay.Display(frame=lsstDebug.frame)
 
  527             disp.mtv(scienceMaskedImage, title=
"Image to not convolve")
 
  534         if display 
and displaySpatialCells:
 
  535             diffimUtils.showKernelSpatialCells(scienceMaskedImage, kernelCellSet,
 
  536                                                symb=
"o", ctype=afwDisplay.CYAN, ctypeUnused=afwDisplay.YELLOW,
 
  537                                                ctypeBad=afwDisplay.RED, size=4, frame=lsstDebug.frame,
 
  538                                                title=
"Image to not convolve")
 
  541         if templateFwhmPix 
and scienceFwhmPix:
 
  542             self.log.
info(
"Matching Psf FWHM %.2f -> %.2f pix", templateFwhmPix, scienceFwhmPix)
 
  544         if self.
kConfig.useBicForKernelBasis:
 
  549             bicDegrees = nbe(tmpKernelCellSet, self.log)
 
  551                                             alardDegGauss=bicDegrees[0], metadata=self.metadata)
 
  555                                             metadata=self.metadata)
 
  557         spatialSolution, psfMatchingKernel, backgroundModel = self.
_solve(kernelCellSet, basisList)
 
  559         psfMatchedMaskedImage = afwImage.MaskedImageF(templateMaskedImage.getBBox())
 
  561         afwMath.convolve(psfMatchedMaskedImage, templateMaskedImage, psfMatchingKernel, doNormalize)
 
  562         return pipeBase.Struct(
 
  563             matchedImage=psfMatchedMaskedImage,
 
  564             psfMatchingKernel=psfMatchingKernel,
 
  565             backgroundModel=backgroundModel,
 
  566             kernelCellSet=kernelCellSet,
 
  571                           templateFwhmPix=None, scienceFwhmPix=None,
 
  572                           candidateList=None, doWarping=True, convolveTemplate=True):
 
  573         """Register, Psf-match and subtract two Exposures. 
  575         Do the following, in order: 
  577         - Warp templateExposure to match scienceExposure, if their WCSs do not already match 
  578         - Determine a PSF matching kernel and differential background model 
  579             that matches templateExposure to scienceExposure 
  580         - PSF-match templateExposure to scienceExposure 
  581         - Compute subtracted exposure (see return values for equation). 
  585         templateExposure : `lsst.afw.image.Exposure` 
  586             Exposure to PSF-match to scienceExposure 
  587         scienceExposure : `lsst.afw.image.Exposure` 
  589         templateFwhmPix : `float` 
  590             FWHM (in pixels) of the Psf in the template image (image to convolve) 
  591         scienceFwhmPix : `float` 
  592             FWHM (in pixels) of the Psf in the science image 
  593         candidateList : `list`, optional 
  594             A list of footprints/maskedImages for kernel candidates; 
  595             if `None` then source detection is run. 
  597             - Currently supported: list of Footprints or measAlg.PsfCandidateF 
  600             What to do if ``templateExposure``` and ``scienceExposure`` WCSs do 
  603             - if `True` then warp ``templateExposure`` to match ``scienceExposure`` 
  604             - if `False` then raise an Exception 
  606         convolveTemplate : `bool` 
  607             Convolve the template image or the science image 
  609             - if `True`, ``templateExposure`` is warped if doWarping, 
  610               ``templateExposure`` is convolved 
  611             - if `False`, ``templateExposure`` is warped if doWarping, 
  612               ``scienceExposure is`` convolved 
  616         result : `lsst.pipe.base.Struct` 
  617             An `lsst.pipe.base.Struct` containing these fields: 
  619             - ``subtractedExposure`` : subtracted Exposure 
  620                 scienceExposure - (matchedImage + backgroundModel) 
  621             - ``matchedImage`` : ``templateExposure`` after warping to match 
  622                                  ``templateExposure`` (if doWarping true), 
  623                                  and convolving with psfMatchingKernel 
  624             - ``psfMatchingKernel`` : PSF matching kernel 
  625             - ``backgroundModel`` : differential background model 
  626             - ``kernelCellSet`` : SpatialCellSet used to determine PSF matching kernel 
  629             templateExposure=templateExposure,
 
  630             scienceExposure=scienceExposure,
 
  631             templateFwhmPix=templateFwhmPix,
 
  632             scienceFwhmPix=scienceFwhmPix,
 
  633             candidateList=candidateList,
 
  635             convolveTemplate=convolveTemplate
 
  638         subtractedExposure = afwImage.ExposureF(scienceExposure, 
True)
 
  640             subtractedMaskedImage = subtractedExposure.getMaskedImage()
 
  641             subtractedMaskedImage -= results.matchedExposure.getMaskedImage()
 
  642             subtractedMaskedImage -= results.backgroundModel
 
  644             subtractedExposure.setMaskedImage(results.warpedExposure.getMaskedImage())
 
  645             subtractedMaskedImage = subtractedExposure.getMaskedImage()
 
  646             subtractedMaskedImage -= results.matchedExposure.getMaskedImage()
 
  647             subtractedMaskedImage -= results.backgroundModel
 
  650             subtractedMaskedImage *= -1
 
  653             subtractedMaskedImage /= results.psfMatchingKernel.computeImage(
 
  654                 afwImage.ImageD(results.psfMatchingKernel.getDimensions()), 
False)
 
  660         if not maskTransparency:
 
  663             afwDisplay.setDefaultMaskTransparency(maskTransparency)
 
  664         if display 
and displayDiffIm:
 
  665             disp = afwDisplay.Display(frame=lsstDebug.frame)
 
  666             disp.mtv(templateExposure, title=
"Template")
 
  668             disp = afwDisplay.Display(frame=lsstDebug.frame)
 
  669             disp.mtv(results.matchedExposure, title=
"Matched template")
 
  671             disp = afwDisplay.Display(frame=lsstDebug.frame)
 
  672             disp.mtv(scienceExposure, title=
"Science Image")
 
  674             disp = afwDisplay.Display(frame=lsstDebug.frame)
 
  675             disp.mtv(subtractedExposure, title=
"Difference Image")
 
  678         results.subtractedExposure = subtractedExposure
 
  683                              templateFwhmPix=None, scienceFwhmPix=None):
 
  684         """Psf-match and subtract two MaskedImages. 
  686         Do the following, in order: 
  688         - PSF-match templateMaskedImage to scienceMaskedImage 
  689         - Determine the differential background 
  690         - Return the difference: scienceMaskedImage 
  691             ((warped templateMaskedImage convolved with psfMatchingKernel) + backgroundModel) 
  695         templateMaskedImage : `lsst.afw.image.MaskedImage` 
  696             MaskedImage to PSF-match to ``scienceMaskedImage`` 
  697         scienceMaskedImage : `lsst.afw.image.MaskedImage` 
  698             Reference MaskedImage 
  699         templateFwhmPix : `float` 
  700             FWHM (in pixels) of the Psf in the template image (image to convolve) 
  701         scienceFwhmPix : `float` 
  702             FWHM (in pixels) of the Psf in the science image 
  703         candidateList : `list`, optional 
  704             A list of footprints/maskedImages for kernel candidates; 
  705             if `None` then source detection is run. 
  707             - Currently supported: list of Footprints or measAlg.PsfCandidateF 
  711         results : `lsst.pipe.base.Struct` 
  712             An `lsst.pipe.base.Struct` containing these fields: 
  714             - ``subtractedMaskedImage`` : ``scienceMaskedImage`` - (matchedImage + backgroundModel) 
  715             - ``matchedImage`` : templateMaskedImage convolved with psfMatchingKernel 
  716             - `psfMatchingKernel`` : PSF matching kernel 
  717             - ``backgroundModel`` : differential background model 
  718             - ``kernelCellSet`` : SpatialCellSet used to determine PSF matching kernel 
  721         if not candidateList:
 
  722             raise RuntimeError(
"Candidate list must be populated by makeCandidateList")
 
  725             templateMaskedImage=templateMaskedImage,
 
  726             scienceMaskedImage=scienceMaskedImage,
 
  727             candidateList=candidateList,
 
  728             templateFwhmPix=templateFwhmPix,
 
  729             scienceFwhmPix=scienceFwhmPix,
 
  732         subtractedMaskedImage = afwImage.MaskedImageF(scienceMaskedImage, 
True)
 
  733         subtractedMaskedImage -= results.matchedImage
 
  734         subtractedMaskedImage -= results.backgroundModel
 
  735         results.subtractedMaskedImage = subtractedMaskedImage
 
  741         if not maskTransparency:
 
  744             afwDisplay.setDefaultMaskTransparency(maskTransparency)
 
  745         if display 
and displayDiffIm:
 
  746             disp = afwDisplay.Display(frame=lsstDebug.frame)
 
  747             disp.mtv(subtractedMaskedImage, title=
"Subtracted masked image")
 
  753         """Get sources to use for Psf-matching. 
  755         This method runs detection and measurement on an exposure. 
  756         The returned set of sources will be used as candidates for 
  761         exposure : `lsst.afw.image.Exposure` 
  762             Exposure on which to run detection/measurement 
  766             Whether or not to smooth the Exposure with Psf before detection 
  768             Factory for the generation of Source ids 
  773             source catalog containing candidates for the Psf-matching 
  776             table = afwTable.SourceTable.make(self.
selectSchema, idFactory)
 
  779         mi = exposure.getMaskedImage()
 
  781         imArr = mi.getImage().getArray()
 
  782         maskArr = mi.getMask().getArray()
 
  783         miArr = np.ma.masked_array(imArr, mask=maskArr)
 
  786             bkgd = fitBg.getImageF(self.
background.config.algorithm,
 
  789             self.log.
warn(
"Failed to get background model.  Falling back to median background estimation")
 
  790             bkgd = np.ma.extras.median(miArr)
 
  796             detRet = self.selectDetection.
run(
 
  802             selectSources = detRet.sources
 
  803             self.selectMeasurement.
run(measCat=selectSources, exposure=exposure)
 
  811         """Make a list of acceptable KernelCandidates. 
  813         Accept or generate a list of candidate sources for 
  814         Psf-matching, and examine the Mask planes in both of the 
  815         images for indications of bad pixels 
  819         templateExposure : `lsst.afw.image.Exposure` 
  820             Exposure that will be convolved 
  821         scienceExposure : `lsst.afw.image.Exposure` 
  822             Exposure that will be matched-to 
  824             Dimensions of the Psf-matching Kernel, used to grow detection footprints 
  825         candidateList : `list`, optional 
  826             List of Sources to examine. Elements must be of type afw.table.Source 
  827             or a type that wraps a Source and has a getSource() method, such as 
  828             meas.algorithms.PsfCandidateF. 
  832         candidateList : `list` of `dict` 
  833             A list of dicts having a "source" and "footprint" 
  834             field for the Sources deemed to be appropriate for Psf 
  837         if candidateList 
is None:
 
  840         if len(candidateList) < 1:
 
  841             raise RuntimeError(
"No candidates in candidateList")
 
  843         listTypes = 
set(
type(x) 
for x 
in candidateList)
 
  844         if len(listTypes) > 1:
 
  845             raise RuntimeError(
"Candidate list contains mixed types: %s" % [l 
for l 
in listTypes])
 
  849                 candidateList[0].getSource()
 
  850             except Exception 
as e:
 
  851                 raise RuntimeError(f
"Candidate List is of type: {type(candidateList[0])} " 
  852                                    "Can only make candidate list from list of afwTable.SourceRecords, " 
  853                                    f
"measAlg.PsfCandidateF or other type with a getSource() method: {e}")
 
  854             candidateList = [c.getSource() 
for c 
in candidateList]
 
  856         candidateList = diffimTools.sourceToFootprintList(candidateList,
 
  857                                                           templateExposure, scienceExposure,
 
  861         if len(candidateList) == 0:
 
  862             raise RuntimeError(
"Cannot find any objects suitable for KernelCandidacy")
 
  866     def _adaptCellSize(self, candidateList):
 
  867         """NOT IMPLEMENTED YET. 
  871     def _buildCellSet(self, templateMaskedImage, scienceMaskedImage, candidateList):
 
  872         """Build a SpatialCellSet for use with the solve method. 
  876         templateMaskedImage : `lsst.afw.image.MaskedImage` 
  877             MaskedImage to PSF-matched to scienceMaskedImage 
  878         scienceMaskedImage : `lsst.afw.image.MaskedImage` 
  879             Reference MaskedImage 
  880         candidateList : `list` 
  881             A list of footprints/maskedImages for kernel candidates; 
  883             - Currently supported: list of Footprints or measAlg.PsfCandidateF 
  887         kernelCellSet : `lsst.afw.math.SpatialCellSet` 
  888             a SpatialCellSet for use with self._solve 
  890         if not candidateList:
 
  891             raise RuntimeError(
"Candidate list must be populated by makeCandidateList")
 
  897                                                sizeCellX, sizeCellY)
 
  899         ps = pexConfig.makePropertySet(self.
kConfig)
 
  901         for cand 
in candidateList:
 
  903                 bbox = cand.getBBox()
 
  905                 bbox = cand[
'footprint'].getBBox()
 
  906             tmi = afwImage.MaskedImageF(templateMaskedImage, bbox)
 
  907             smi = afwImage.MaskedImageF(scienceMaskedImage, bbox)
 
  911                     cand = cand[
'source']
 
  912             xPos = cand.getCentroid()[0]
 
  913             yPos = cand.getCentroid()[1]
 
  914             cand = diffimLib.makeKernelCandidate(xPos, yPos, tmi, smi, ps)
 
  916             self.log.
debug(
"Candidate %d at %f, %f", cand.getId(), cand.getXCenter(), cand.getYCenter())
 
  917             kernelCellSet.insertCandidate(cand)
 
  921     def _validateSize(self, templateMaskedImage, scienceMaskedImage):
 
  922         """Return True if two image-like objects are the same size. 
  924         return templateMaskedImage.getDimensions() == scienceMaskedImage.getDimensions()
 
  926     def _validateWcs(self, templateExposure, scienceExposure):
 
  927         """Return True if the WCS of the two Exposures have the same origin and extent. 
  929         templateWcs = templateExposure.getWcs()
 
  930         scienceWcs = scienceExposure.getWcs()
 
  931         templateBBox = templateExposure.getBBox()
 
  932         scienceBBox = scienceExposure.getBBox()
 
  935         templateOrigin = templateWcs.pixelToSky(
geom.Point2D(templateBBox.getBegin()))
 
  936         scienceOrigin = scienceWcs.pixelToSky(
geom.Point2D(scienceBBox.getBegin()))
 
  939         templateLimit = templateWcs.pixelToSky(
geom.Point2D(templateBBox.getEnd()))
 
  940         scienceLimit = scienceWcs.pixelToSky(
geom.Point2D(scienceBBox.getEnd()))
 
  942         self.log.
info(
"Template Wcs : %f,%f -> %f,%f",
 
  943                       templateOrigin[0], templateOrigin[1],
 
  944                       templateLimit[0], templateLimit[1])
 
  945         self.log.
info(
"Science Wcs : %f,%f -> %f,%f",
 
  946                       scienceOrigin[0], scienceOrigin[1],
 
  947                       scienceLimit[0], scienceLimit[1])
 
  949         templateBBox = 
geom.Box2D(templateOrigin.getPosition(geom.degrees),
 
  950                                   templateLimit.getPosition(geom.degrees))
 
  951         scienceBBox = 
geom.Box2D(scienceOrigin.getPosition(geom.degrees),
 
  952                                  scienceLimit.getPosition(geom.degrees))
 
  953         if not (templateBBox.overlaps(scienceBBox)):
 
  954             raise RuntimeError(
"Input images do not overlap at all")
 
  956         if ((templateOrigin != scienceOrigin)
 
  957             or (templateLimit != scienceLimit)
 
  958                 or (templateExposure.getDimensions() != scienceExposure.getDimensions())):
 
  963 subtractAlgorithmRegistry = pexConfig.makeRegistry(
 
  964     doc=
"A registry of subtraction algorithms for use as a subtask in imageDifference",
 
  967 subtractAlgorithmRegistry.register(
'al', ImagePsfMatchTask)