def testVisit(self, nCell = 3):
        bskv = ipDiffim.BuildSingleKernelVisitorF(self.kList, self.policy)

        sizeCellX = self.policy.get("sizeCellX")
        sizeCellY = self.policy.get("sizeCellY")
        
        kernelCellSet = afwMath.SpatialCellSet(afwGeom.Box2I(afwGeom.Point2I(0, 0),
                                                             afwGeom.Extent2I(sizeCellX * nCell,
                                                                              sizeCellY * nCell)),
                                               sizeCellX,
                                               sizeCellY)
        nTot = 0
        for candX in range(nCell):
            for candY in range(nCell):
                if candX == nCell // 2 and candY == nCell // 2:
                    kc = self.makeCandidate(100.0,
                                            candX * sizeCellX + sizeCellX // 2,
                                            candY * sizeCellY + sizeCellY // 2)
                else:
                    kc = self.makeCandidate(1.0,
                                            candX * sizeCellX + sizeCellX // 2,
                                            candY * sizeCellY + sizeCellY // 2)
                kernelCellSet.insertCandidate(kc)
                nTot += 1
                
        kernelCellSet.visitCandidates(bskv, 1)
        self.assertEqual(bskv.getNProcessed(), nTot)
        self.assertEqual(bskv.getNRejected(), 0)

        for cell in kernelCellSet.getCellList():
            for cand in cell.begin(False):
                cand = ipDiffim.cast_KernelCandidateF(cand)
                self.assertEqual(cand.getStatus(), afwMath.SpatialCellCandidate.GOOD)
    def testFourNoVariation(self):
        self.policy.set('constantVarianceWeighting', True)
        self.policy.set('spatialKernelOrder', 0)
        self.policy.set('spatialBgOrder', 0)

        # Place candidate footprints within the spatial grid
        for fp in self.footprints:
            bbox = fp.getBBox()
        
            # Grab the centers in the parent's coordinate system
            xC   = int(0.5 * ( bbox.getMinX() + bbox.getMaxX() ))
            yC   = int(0.5 * ( bbox.getMinY() + bbox.getMaxY() ))
        
            bbox = afwGeom.Box2I(afwGeom.Point2I(int(xC)-24, int(yC)-24), afwGeom.Extent2I(49, 49))
        
            tsmi  = afwImage.MaskedImageF(self.tmi, bbox, afwImage.LOCAL)
            ssmi  = afwImage.MaskedImageF(self.smi, bbox, afwImage.LOCAL)

            # Hotpants centroids go from -1 to 1
            if xC > 90 and yC > 90:
                cand = ipDiffim.makeKernelCandidate( (xC - 0.5 * self.smi.getWidth()) /
                                                     (0.5 * self.smi.getWidth()),
                                                     (yC - 0.5 * self.smi.getHeight()) /
                                                     (0.5 * self.smi.getHeight()),
                                                     tsmi, ssmi, self.policy)

                self.kernelCellSet.insertCandidate(cand)

        # Visitors
        bbox  = self.kernelCellSet.getBBox()        
        bsikv = ipDiffim.BuildSingleKernelVisitorF(self.basisList, self.policy)
        bspkv = ipDiffim.BuildSpatialKernelVisitorF(self.basisList, bbox, self.policy)

        for cell in self.kernelCellSet.getCellList():
            for cand in cell.begin(False): # False = include bad candidates
                cand  = ipDiffim.cast_KernelCandidateF(cand)
                bsikv.processCandidate(cand)
                bspkv.processCandidate(cand)

        HPspatialSolution = [ 0.969559,
                             -0.000223,
                             -0.198374,
                              0.000012,
                             -0.000010,
                              0.000036,
                             -0.000004,
                             -0.206751,
                              0.000012,
                              0.000004,
                              0.452304 ]

        bspkv.solveLinearEquation()
        sk, sb = bspkv.getSolutionPair()
        spatialSolution = sk.getKernelParameters()
        for i in range(len(spatialSolution)):
            self.assertAlmostEqual(HPspatialSolution[i] * self.parity[i], spatialSolution[i], 5)

        self.assertAlmostEqual(sb.getParameters()[0], HPspatialSolution[-1], 5)
 def setStatus(self, cellSet, cid, value):
     # ideally
     # cellSet.getCandidateById(id).setStatus(value)
     for cell in cellSet.getCellList():
         for cand in cell.begin(False):
             cand = ipDiffim.cast_KernelCandidateF(cand)
             if (cand.getId() == cid):
                 cand.setStatus(value)
                 return cand
    def jackknifeResample(self, psfmatch, results):
        
        kernel  = results.psfMatchingKernel
        bg      = results.backgroundModel
        cellSet = results.kernelCellSet
        
        goodList = []
        for cell in cellSet.getCellList():
            print
            for cand in cell.begin(False):
                cand = ipDiffim.cast_KernelCandidateF(cand)

                if cand.getStatus() == afwMath.SpatialCellCandidate.GOOD:
                    goodList.append(cand.getId())
                else:
                    # This is so that UNKNOWNs are not processed
                    cand.setStatus(afwMath.SpatialCellCandidate.BAD)

        nStarPerCell = self.config.nStarPerCell
        policy = pexConfig.makePolicy(self.config)
        for idx in range(len(goodList)):
            cid   = goodList[idx]

            print # clear the screen
            pexLog.Trace("lsst.ip.diffim.JackknifeResampleKernel", 1,
                         "Removing candidate %d" % (cid))
            
            cand = self.setStatus(cellSet, cid, afwMath.SpatialCellCandidate.BAD)

            # From _solve
            regionBBox = cellSet.getBBox()
            spatialkv  = ipDiffim.BuildSpatialKernelVisitorF(kernel.getKernelList(), regionBBox, policy)
            cellSet.visitCandidates(spatialkv, nStarPerCell)
            spatialkv.solveLinearEquation()
            jkKernel, jkBg = spatialkv.getSolutionPair()

            #jkResults = psfmatch._solve(cellSet, kernel.getKernelList())
            #jkKernel  = jkResults[1]
            #jkBg      = jkResults[2]

            # lots of windows
            # self.assess(cand, kernel, bg, jkKernel, jkBg, 6*idx+1)

            # only 6 windows
            self.assess(cand, kernel, bg, jkKernel, jkBg, 1)

            self.setStatus(cellSet, cid, afwMath.SpatialCellCandidate.GOOD)
    def testInsert(self):
        mi = afwImage.MaskedImageF(afwGeom.Extent2I(10, 10))
        kc = ipDiffim.makeKernelCandidate(0., 0., mi, mi, self.policy)
        kc.setStatus(afwMath.SpatialCellCandidate.GOOD)

        sizeCellX = self.policy.get("sizeCellX")
        sizeCellY = self.policy.get("sizeCellY")
        kernelCellSet = afwMath.SpatialCellSet(afwGeom.Box2I(afwGeom.Point2I(0, 0), afwGeom.Extent2I(1, 1)),
                                               sizeCellX, sizeCellY)
        kernelCellSet.insertCandidate(kc)
        nSeen = 0
        for cell in kernelCellSet.getCellList():
            for cand in cell.begin(True):
                cand  = ipDiffim.cast_KernelCandidateF(cand)
                self.assertEqual(cand.getStatus(), afwMath.SpatialCellCandidate.GOOD)
                nSeen += 1
        self.assertEqual(nSeen, 1)
    def testInsert(self):
        mi = afwImage.MaskedImageF(afwGeom.Extent2I(10, 10))
        kc = ipDiffim.makeKernelCandidate(0., 0., mi, mi, self.policy)
        kc.setStatus(afwMath.SpatialCellCandidate.GOOD)

        sizeCellX = self.policy.get("sizeCellX")
        sizeCellY = self.policy.get("sizeCellY")
        kernelCellSet = afwMath.SpatialCellSet(
            afwGeom.Box2I(afwGeom.Point2I(0, 0), afwGeom.Extent2I(1, 1)),
            sizeCellX, sizeCellY)
        kernelCellSet.insertCandidate(kc)
        nSeen = 0
        for cell in kernelCellSet.getCellList():
            for cand in cell.begin(True):
                cand = ipDiffim.cast_KernelCandidateF(cand)
                self.assertEqual(cand.getStatus(),
                                 afwMath.SpatialCellCandidate.GOOD)
                nSeen += 1
        self.assertEqual(nSeen, 1)
    def runXY0(self, poly, fitForBackground = False):
        if not self.defDataDir:
            print >> sys.stderr, "Warning: afwdata is not set up"
            return

        self.subconfig.spatialModelType = poly
        self.subconfig.fitForBackground = fitForBackground

        templateSubImage = afwImage.ExposureF(self.templateImage, self.bbox)
        scienceSubImage  = afwImage.ExposureF(self.scienceImage, self.bbox)

        # Have an XY0
        psfmatch  = ipDiffim.ImagePsfMatchTask(config=self.config)
        results1  = psfmatch.subtractExposures(templateSubImage, scienceSubImage, doWarping = True)
        spatialKernel1      = results1.psfMatchingKernel
        backgroundModel1    = results1.backgroundModel
        kernelCellSet1      = results1.kernelCellSet

        # And then take away XY0
        templateSubImage.setXY0(afwGeom.Point2I(0, 0)) 
        scienceSubImage.setXY0(afwGeom.Point2I(0, 0))
        results2  = psfmatch.subtractExposures(templateSubImage, scienceSubImage, doWarping = True)
        spatialKernel2      = results2.psfMatchingKernel
        backgroundModel2    = results2.backgroundModel
        kernelCellSet2      = results2.kernelCellSet

        # need to count up the candidates first, since its a running tally
        count = 0
        for cell in kernelCellSet1.getCellList():
            for cand1 in cell.begin(False):
                count += 1

        for cell in kernelCellSet1.getCellList():
            for cand1 in cell.begin(False):
                if cand1.getStatus() == afwMath.SpatialCellCandidate.UNKNOWN:
                    continue
                if cand1.getStatus() == afwMath.SpatialCellCandidate.BAD:
                    continue

                cand1 = ipDiffim.cast_KernelCandidateF(cand1)
                cand2 = ipDiffim.cast_KernelCandidateF(kernelCellSet2.getCandidateById(cand1.getId()+count))

                # positions are nearly the same (different at the 0.01 pixel level)
                self.assertAlmostEqual(cand1.getXCenter(), cand2.getXCenter(), delta=1e-1)
                self.assertAlmostEqual(cand1.getYCenter(), cand2.getYCenter() + self.offset, delta=1e-1)

                # kernels are the same
                im1   = cand1.getKernelImage(ipDiffim.KernelCandidateF.RECENT)
                im2   = cand2.getKernelImage(ipDiffim.KernelCandidateF.RECENT)
                for y in range(im1.getHeight()):
                    for x in range(im1.getWidth()):
                        self.assertAlmostEqual(im1.get(x, y), im2.get(x, y), delta=1e-7)

        # Spatial fits are the same
        skp1 = spatialKernel1.getSpatialParameters()
        skp2 = spatialKernel2.getSpatialParameters()
        bgp1 = backgroundModel1.getParameters()
        bgp2 = backgroundModel2.getParameters()

        # first term = kernel sum, 0, 0
        self.assertAlmostEqual(skp1[0][0], skp2[0][0], delta=1e-6)

        # On other terms, the spatial terms are the same, the zpt terms are different
        for nk in range(1, len(skp1)):

            # Zeropoint
            if poly == 'polynomial':
                self.assertNotEqual(skp1[nk][0], skp2[nk][0])
            elif poly == 'chebyshev1':
                # Cheby remaps coords, so model should be the same!
                self.assertAlmostEqual(skp1[nk][0], skp2[nk][0], delta=1e-4)
            else:
                self.fail()

            # Spatial terms
            for np in range(1, len(skp1[nk])):
                self.assertAlmostEqual(skp1[nk][np], skp2[nk][np], delta=1e-4)

        for np in range(len(bgp1)):
            self.assertAlmostEqual(bgp1[np], bgp2[np], delta=1e-4)
def makeAutoCorrelation(kernelCellSet, spatialKernel, makePlot=False):
    kImage = afwImage.ImageD(spatialKernel.getDimensions())
    ksImage = afwImage.ImageD(spatialKernel.getDimensions())
    kInfo = []

    candList = []
    for cell in kernelCellSet.getCellList():
        for cand in cell.begin(True):  # only look at non-bad candidates
            if cand.getStatus() == afwMath.SpatialCellCandidate.GOOD:
                candList.append(cand.getId())

    r = []
    d1 = []
    d2 = []

    for i in range(len(candList)):
        cand1 = ipDiffim.cast_KernelCandidateF(
            kernelCellSet.getCandidateById(candList[i]))
        x1 = cand1.getXCenter()
        y1 = cand1.getYCenter()

        # Original kernel
        kImage1 = afwImage.ImageD(spatialKernel.getDimensions())
        cand1.getKernel(ipDiffim.KernelCandidateF.ORIG).computeImage(
            kImage1, False)

        # Spatial approximation
        ksImage1 = afwImage.ImageD(spatialKernel.getDimensions())
        spatialKernel.computeImage(ksImage1, False,
                                   afwImage.indexToPosition(int(x1)),
                                   afwImage.indexToPosition(int(y1)))

        # Turn into numarrays for dot product
        ksVector1 = ksImage1.getArray().ravel()
        kVector1 = kImage1.getArray().ravel()

        for j in range(i + 1, len(candList)):
            cand2 = ipDiffim.cast_KernelCandidateF(
                kernelCellSet.getCandidateById(candList[j]))
            x2 = cand2.getXCenter()
            y2 = cand2.getYCenter()

            # Original kernel
            kImage2 = afwImage.ImageD(spatialKernel.getDimensions())
            cand2.getKernel(ipDiffim.KernelCandidateF.ORIG).computeImage(
                kImage2, False)

            # Spatial approximation
            ksImage2 = afwImage.ImageD(spatialKernel.getDimensions())
            spatialKernel.computeImage(ksImage2, False,
                                       afwImage.indexToPosition(int(x2)),
                                       afwImage.indexToPosition(int(y2)))

            # Turn into numarrays for dot product
            ksVector2 = ksImage2.getArray().ravel()
            kVector2 = kImage2.getArray().ravel()

            ###

            # Add to cross correlation functions
            r.append(num.sqrt((x1 - x2)**2 + (y1 - y2)**2))
            d1.append(correlationD1(kVector1, ksVector1, kVector2, ksVector2))
            d2.append(correlationD2(kVector1, ksVector1, kVector2, ksVector2))

    r = num.array(r)
    d1 = num.array(d1)
    d2 = num.array(d2)

    if makePlot:
        plotCrossCorrelation(r, d1, d2)

    return r, d1, d2
    def testFourVariation(self):
        self.policy.set('constantVarianceWeighting', True)
        self.policy.set('spatialKernelOrder', 1)
        self.policy.set('spatialBgOrder', 1)

        # Place candidate footprints within the spatial grid
        for fp in self.footprints:
            bbox = fp.getBBox()
        
            # Grab the centers in the parent's coordinate system
            xC   = int(0.5 * ( bbox.getMinX() + bbox.getMaxX() ))
            yC   = int(0.5 * ( bbox.getMinY() + bbox.getMaxY() ))
        
            bbox = afwGeom.Box2I(afwGeom.Point2I(int(xC)-24, int(yC)-24), afwGeom.Extent2I(49, 49))
        
            tsmi  = afwImage.MaskedImageF(self.tmi, bbox, afwImage.LOCAL)
            ssmi  = afwImage.MaskedImageF(self.smi, bbox, afwImage.LOCAL)
        
            # Hotpants centroids go from -1 to 1
            if xC > 90 and yC > 90:
                cand = ipDiffim.makeKernelCandidate( (xC - 0.5 * self.smi.getWidth()) /
                                                     (0.5 * self.smi.getWidth()),
                                                     (yC - 0.5 * self.smi.getHeight()) /
                                                     (0.5 * self.smi.getHeight()),
                                                     tsmi, ssmi, self.policy)
                self.kernelCellSet.insertCandidate(cand)

        # Visitors
        bbox  = self.kernelCellSet.getBBox()        
        bsikv = ipDiffim.BuildSingleKernelVisitorF(self.basisList, self.policy)
        bspkv = ipDiffim.BuildSpatialKernelVisitorF(self.basisList, bbox, self.policy)

        for cell in self.kernelCellSet.getCellList():
            for cand in cell.begin(False): # False = include bad candidates
                cand  = ipDiffim.cast_KernelCandidateF(cand)
                bsikv.processCandidate(cand)
                bspkv.processCandidate(cand)

        HPspatialSolution = [ [  0.969559,
                                 0.,
                                 0. ],
                              [ -0.000082,
                                -0.000620,
                                 0.000185 ],
                              [ -0.197749,
                                 0.001418,
                                -0.003321 ],
                              [  0.000002,
                                 0.000049,
                                -0.000016 ],
                              [  0.000211,
                                -0.000283,
                                -0.000397 ],
                              [  0.000034,
                                 0.000002,
                                 0.000006 ],
                              [ -0.000013,
                                 0.000041,
                                -0.000010 ],
                              [ -0.220238,
                                 0.028395,
                                 0.013148 ],
                              [  0.000019,
                                -0.000025,
                                 0.000003 ],
                              [  0.000003,
                                 0.000000,
                                 0.000005 ],
                              [  0.782113,
                                -0.910963,
                                -0.106636] ]
        
        bspkv.solveLinearEquation()
        sk, sb = bspkv.getSolutionPair()
        spatialSolution = sk.getSpatialParameters()

        for i in range(len(spatialSolution)):
            # HP and LSST switch the order x<->y
            self.assertAlmostEqual(HPspatialSolution[i][0] * self.parity[i], spatialSolution[i][0], 5)
            self.assertAlmostEqual(HPspatialSolution[i][1] * self.parity[i], spatialSolution[i][2], 5)
            self.assertAlmostEqual(HPspatialSolution[i][2] * self.parity[i], spatialSolution[i][1], 5)

        self.assertAlmostEqual(sb.getParameters()[0], HPspatialSolution[-1][0], 5)
        self.assertAlmostEqual(sb.getParameters()[1], HPspatialSolution[-1][2], 5) # x<->y
        self.assertAlmostEqual(sb.getParameters()[2], HPspatialSolution[-1][1], 5) # x<->y
    def testFourBgVariation(self):

        # OK, so these can end up a bit different due to how HP and
        # LSST represent the background in the matrix math.  HP has
        # each pixel have its own coordinate (which goes from -1 to 1
        # across the entire image, by the way), whereas we give all
        # the LSST pixels within a stamp the same coordinate.  

        # To make this comparison, I go ahead and edit the Hotpants
        # code to give each pixel the same weight.  For reference this
        # is in fillStamp() and I replace:
        #
        #        //xf = (i - rPixX2) / rPixX2;
        #        xf = (xi - rPixX2) / rPixX2;
        #
        #            //yf = (j - rPixY2) / rPixY2; 
        #            yf = (yi - rPixY2) / rPixY2; 
        
        self.policy.set('constantVarianceWeighting', True)
        self.policy.set('spatialKernelOrder', 0)
        self.policy.set('spatialBgOrder', 1)

        # Place candidate footprints within the spatial grid
        for fp in self.footprints:
            bbox = fp.getBBox()
        
            # Grab the centers in the parent's coordinate system
            xC   = int(0.5 * ( bbox.getMinX() + bbox.getMaxX() ))
            yC   = int(0.5 * ( bbox.getMinY() + bbox.getMaxY() ))
        
            bbox = afwGeom.Box2I(afwGeom.Point2I(int(xC)-24, int(yC)-24), afwGeom.Extent2I(49, 49))
        
            tsmi  = afwImage.MaskedImageF(self.tmi, bbox, afwImage.LOCAL)
            ssmi  = afwImage.MaskedImageF(self.smi, bbox, afwImage.LOCAL)
        
            # Hotpants centroids go from -1 to 1
            if xC > 90 and yC > 90:
                cand = ipDiffim.makeKernelCandidate( (xC - 0.5 * self.smi.getWidth()) /
                                                     (0.5 * self.smi.getWidth()),
                                                     (yC - 0.5 * self.smi.getHeight()) /
                                                     (0.5 * self.smi.getHeight()),
                                                     tsmi, ssmi, self.policy)
                #print 'OBJECT', cand.getId(), 'AT', xC, yC, cand.getXCenter(), cand.getYCenter()
                self.kernelCellSet.insertCandidate(cand)

        # Visitors
        bbox  = self.kernelCellSet.getBBox()
        bsikv = ipDiffim.BuildSingleKernelVisitorF(self.basisList, self.policy)
        bspkv = ipDiffim.BuildSpatialKernelVisitorF(self.basisList, bbox, self.policy)

        for cell in self.kernelCellSet.getCellList():
            for cand in cell.begin(False): # False = include bad candidates
                cand  = ipDiffim.cast_KernelCandidateF(cand)
                bsikv.processCandidate(cand)
                bspkv.processCandidate(cand)

        HPspatialSolution = [ 0.969559,
                             -0.000223,
                             -0.198374,
                              0.000012,
                             -0.000010,
                              0.000036,
                             -0.000004,
                             -0.206751,
                              0.000012,
                              0.000004,
                              [ 0.782113,
                               -0.910963,
                               -0.106636] ]
       
        
        bspkv.solveLinearEquation()
        sk, sb = bspkv.getSolutionPair()
        spatialSolution = sk.getKernelParameters()
        for i in range(len(spatialSolution)):
            self.assertAlmostEqual(HPspatialSolution[i] * self.parity[i], spatialSolution[i], 5)

        self.assertAlmostEqual(sb.getParameters()[0], HPspatialSolution[-1][0], 5)
        self.assertAlmostEqual(sb.getParameters()[1], HPspatialSolution[-1][2], 5) # x<->y
        self.assertAlmostEqual(sb.getParameters()[2], HPspatialSolution[-1][1], 5) # x<->y
Exemple #11
0
                                                       policy)
        except pexExcept.LsstCppException, e:
            pexLog.Trace("lsst.ip.diffim.createPsfMatchingKernel", 1,
                         "ERROR: Unable to calculate psf matching kernel")
            pexLog.Trace("lsst.ip.diffim.createPsfMatchingKernel", 2,
                         e.args[0].what())
            raise
        else:
            spatialKernel = kb.first
            spatialBg = kb.second

        # What is the status of the processing?
        nGood = 0
        for cell in kernelCellSet.getCellList():
            for cand in cell.begin(True):
                cand = diffim.cast_KernelCandidateF(cand)
                if cand.getStatus() == afwMath.SpatialCellCandidate.GOOD:
                    nGood += 1
        if nGood == 0:
            pexLog.Trace("lsst.ip.diffim.createPsfMatchingKernel", 1,
                         "WARNING")
        pexLog.Trace("lsst.ip.diffim.createPsfMatchingKernel", 1,
                     "Used %d kernels for spatial fit" % (nGood))

        return spatialKernel, spatialBg

        pass

    def convolve(self, exposure, kernel):
        """Convolve image with kernel
    def run(self, sensorRef, templateIdList=None):
        """Subtract an image from a template coadd and measure the result

        Steps include:
        - warp template coadd to match WCS of image
        - PSF match image to warped template
        - subtract image from PSF-matched, warped template
        - persist difference image
        - detect sources
        - measure sources

        @param sensorRef: sensor-level butler data reference, used for the following data products:
        Input only:
        - calexp
        - psf
        - ccdExposureId
        - ccdExposureId_bits
        - self.config.coaddName + "Coadd_skyMap"
        - self.config.coaddName + "Coadd"
        Input or output, depending on config:
        - self.config.coaddName + "Diff_subtractedExp"
        Output, depending on config:
        - self.config.coaddName + "Diff_matchedExp"
        - self.config.coaddName + "Diff_src"

        @return pipe_base Struct containing these fields:
        - subtractedExposure: exposure after subtracting template;
            the unpersisted version if subtraction not run but detection run
            None if neither subtraction nor detection run (i.e. nothing useful done)
        - subtractRes: results of subtraction task; None if subtraction not run
        - sources: detected and possibly measured sources; None if detection not run
        """
        self.log.info("Processing %s" % (sensorRef.dataId))

        # initialize outputs and some intermediate products
        subtractedExposure = None
        subtractRes = None
        selectSources = None
        kernelSources = None
        controlSources = None
        diaSources = None

        # We make one IdFactory that will be used by both icSrc and src datasets;
        # I don't know if this is the way we ultimately want to do things, but at least
        # this ensures the source IDs are fully unique.
        expBits = sensorRef.get("ccdExposureId_bits")
        expId = long(sensorRef.get("ccdExposureId"))
        idFactory = afwTable.IdFactory.makeSource(expId, 64 - expBits)

        # Retrieve the science image we wish to analyze
        exposure = sensorRef.get("calexp", immediate=True)
        if self.config.doAddCalexpBackground:
            mi = exposure.getMaskedImage()
            mi += sensorRef.get("calexpBackground").getImage()
        if not exposure.hasPsf():
            raise pipeBase.TaskError("Exposure has no psf")
        sciencePsf = exposure.getPsf()

        subtractedExposureName = self.config.coaddName + "Diff_differenceExp"
        templateExposure = None  # Stitched coadd exposure
        templateSources = None   # Sources on the template image
        if self.config.doSubtract:
            print templateIdList
            template = self.getTemplate.run(exposure, sensorRef, templateIdList=templateIdList)
            templateExposure = template.exposure
            templateSources = template.sources

            # compute scienceSigmaOrig: sigma of PSF of science image before pre-convolution
            ctr = afwGeom.Box2D(exposure.getBBox()).getCenter()
            psfAttr = PsfAttributes(sciencePsf, afwGeom.Point2I(ctr))
            scienceSigmaOrig = psfAttr.computeGaussianWidth(psfAttr.ADAPTIVE_MOMENT)

            # sigma of PSF of template image before warping
            ctr = afwGeom.Box2D(templateExposure.getBBox()).getCenter()
            psfAttr = PsfAttributes(templateExposure.getPsf(), afwGeom.Point2I(ctr))
            templateSigma = psfAttr.computeGaussianWidth(psfAttr.ADAPTIVE_MOMENT)

            # if requested, convolve the science exposure with its PSF
            # (properly, this should be a cross-correlation, but our code does not yet support that)
            # compute scienceSigmaPost: sigma of science exposure with pre-convolution, if done,
            # else sigma of original science exposure
            if self.config.doPreConvolve:
                convControl = afwMath.ConvolutionControl()
                # cannot convolve in place, so make a new MI to receive convolved image
                srcMI = exposure.getMaskedImage()
                destMI = srcMI.Factory(srcMI.getDimensions())
                srcPsf = sciencePsf
                if self.config.useGaussianForPreConvolution:
                    # convolve with a simplified PSF model: a double Gaussian
                    kWidth, kHeight = sciencePsf.getLocalKernel().getDimensions()
                    preConvPsf = SingleGaussianPsf(kWidth, kHeight, scienceSigmaOrig)
                else:
                    # convolve with science exposure's PSF model
                    preConvPsf = srcPsf
                afwMath.convolve(destMI, srcMI, preConvPsf.getLocalKernel(), convControl)
                exposure.setMaskedImage(destMI)
                scienceSigmaPost = scienceSigmaOrig * math.sqrt(2)
            else:
                scienceSigmaPost = scienceSigmaOrig

            # If requested, find sources in the image
            if self.config.doSelectSources:
                if not sensorRef.datasetExists("src"):
                    self.log.warn("Src product does not exist; running detection, measurement, selection")
                    # Run own detection and measurement; necessary in nightly processing
                    selectSources = self.subtract.getSelectSources(
                        exposure,
                        sigma = scienceSigmaPost,
                        doSmooth = not self.doPreConvolve,
                        idFactory = idFactory,
                    )
                else:
                    self.log.info("Source selection via src product")
                    # Sources already exist; for data release processing
                    selectSources = sensorRef.get("src")

                # Number of basis functions
                nparam = len(makeKernelBasisList(self.subtract.config.kernel.active,
                                                 referenceFwhmPix=scienceSigmaPost * FwhmPerSigma,
                                                 targetFwhmPix=templateSigma * FwhmPerSigma))

                if self.config.doAddMetrics:
                    # Modify the schema of all Sources
                    kcQa = KernelCandidateQa(nparam)
                    selectSources = kcQa.addToSchema(selectSources)

                if self.config.kernelSourcesFromRef:
                    # match exposure sources to reference catalog
                    astromRet = self.astrometer.loadAndMatch(exposure=exposure, sourceCat=selectSources)
                    matches = astromRet.matches
                elif templateSources:
                    # match exposure sources to template sources
                    matches = afwTable.matchRaDec(templateSources, selectSources, 1.0*afwGeom.arcseconds,
                                                  False)
                else:
                    raise RuntimeError("doSelectSources=True and kernelSourcesFromRef=False," +
                                       "but template sources not available. Cannot match science " +
                                       "sources with template sources. Run process* on data from " +
                                       "which templates are built.")

                kernelSources = self.sourceSelector.selectStars(exposure, selectSources,
                    matches=matches).starCat

                random.shuffle(kernelSources, random.random)
                controlSources = kernelSources[::self.config.controlStepSize]
                kernelSources = [k for i,k in enumerate(kernelSources) if i % self.config.controlStepSize]

                if self.config.doSelectDcrCatalog:
                    redSelector  = DiaCatalogSourceSelectorTask(
                        DiaCatalogSourceSelectorConfig(grMin=self.sourceSelector.config.grMax, grMax=99.999))
                    redSources   = redSelector.selectStars(exposure, selectSources, matches=matches).starCat
                    controlSources.extend(redSources)

                    blueSelector = DiaCatalogSourceSelectorTask(
                        DiaCatalogSourceSelectorConfig(grMin=-99.999, grMax=self.sourceSelector.config.grMin))
                    blueSources  = blueSelector.selectStars(exposure, selectSources, matches=matches).starCat
                    controlSources.extend(blueSources)

                if self.config.doSelectVariableCatalog:
                    varSelector = DiaCatalogSourceSelectorTask(
                        DiaCatalogSourceSelectorConfig(includeVariable=True))
                    varSources  = varSelector.selectStars(exposure, selectSources, matches=matches).starCat
                    controlSources.extend(varSources)

                self.log.info("Selected %d / %d sources for Psf matching (%d for control sample)" 
                              % (len(kernelSources), len(selectSources), len(controlSources)))
            allresids = {}
            if self.config.doUseRegister:
                self.log.info("Registering images")

                if templateSources is None:
                    # Run detection on the template, which is
                    # temporarily background-subtracted
                    templateSources = self.subtract.getSelectSources(
                        templateExposure,
                        sigma=templateSigma,
                        doSmooth=True,
                        idFactory=idFactory
                    )

                # Third step: we need to fit the relative astrometry.
                #
                wcsResults = self.fitAstrometry(templateSources, templateExposure, selectSources)
                warpedExp = self.register.warpExposure(templateExposure, wcsResults.wcs,
                                            exposure.getWcs(), exposure.getBBox())
                templateExposure = warpedExp

                # Create debugging outputs on the astrometric
                # residuals as a function of position.  Persistence
                # not yet implemented; expected on (I believe) #2636.
                if self.config.doDebugRegister:
                    # Grab matches to reference catalog
                    srcToMatch = {x.second.getId() : x.first for x in matches}

                    refCoordKey = wcsResults.matches[0].first.getTable().getCoordKey()
                    inCentroidKey = wcsResults.matches[0].second.getTable().getCentroidKey()
                    sids      = [m.first.getId() for m in wcsResults.matches]
                    positions = [m.first.get(refCoordKey) for m in wcsResults.matches]
                    residuals = [m.first.get(refCoordKey).getOffsetFrom(wcsResults.wcs.pixelToSky(
                                m.second.get(inCentroidKey))) for m in wcsResults.matches]
                    allresids = dict(zip(sids, zip(positions, residuals)))

                    cresiduals = [m.first.get(refCoordKey).getTangentPlaneOffset(
                            wcsResults.wcs.pixelToSky(
                                m.second.get(inCentroidKey))) for m in wcsResults.matches]
                    colors    = numpy.array([-2.5*numpy.log10(srcToMatch[x].get("g"))
                                              + 2.5*numpy.log10(srcToMatch[x].get("r")) 
                                              for x in sids if x in srcToMatch.keys()])
                    dlong     = numpy.array([r[0].asArcseconds() for s,r in zip(sids, cresiduals) 
                                             if s in srcToMatch.keys()])
                    dlat      = numpy.array([r[1].asArcseconds() for s,r in zip(sids, cresiduals) 
                                             if s in srcToMatch.keys()])
                    idx1      = numpy.where(colors<self.sourceSelector.config.grMin)
                    idx2      = numpy.where((colors>=self.sourceSelector.config.grMin)&
                                            (colors<=self.sourceSelector.config.grMax))
                    idx3      = numpy.where(colors>self.sourceSelector.config.grMax)
                    rms1Long  = IqrToSigma*(numpy.percentile(dlong[idx1],75)-numpy.percentile(dlong[idx1],25))
                    rms1Lat   = IqrToSigma*(numpy.percentile(dlat[idx1],75)-numpy.percentile(dlat[idx1],25))
                    rms2Long  = IqrToSigma*(numpy.percentile(dlong[idx2],75)-numpy.percentile(dlong[idx2],25))
                    rms2Lat   = IqrToSigma*(numpy.percentile(dlat[idx2],75)-numpy.percentile(dlat[idx2],25))
                    rms3Long  = IqrToSigma*(numpy.percentile(dlong[idx3],75)-numpy.percentile(dlong[idx3],25))
                    rms3Lat   = IqrToSigma*(numpy.percentile(dlat[idx3],75)-numpy.percentile(dlat[idx3],25))
                    self.log.info("Blue star offsets'': %.3f %.3f, %.3f %.3f"  % (numpy.median(dlong[idx1]), 
                                                                                  rms1Long,
                                                                                  numpy.median(dlat[idx1]), 
                                                                                  rms1Lat))
                    self.log.info("Green star offsets'': %.3f %.3f, %.3f %.3f"  % (numpy.median(dlong[idx2]), 
                                                                                   rms2Long,
                                                                                   numpy.median(dlat[idx2]), 
                                                                                   rms2Lat))
                    self.log.info("Red star offsets'': %.3f %.3f, %.3f %.3f"  % (numpy.median(dlong[idx3]), 
                                                                                 rms3Long,
                                                                                 numpy.median(dlat[idx3]), 
                                                                                 rms3Lat))

                    self.metadata.add("RegisterBlueLongOffsetMedian", numpy.median(dlong[idx1]))
                    self.metadata.add("RegisterGreenLongOffsetMedian", numpy.median(dlong[idx2]))
                    self.metadata.add("RegisterRedLongOffsetMedian", numpy.median(dlong[idx3]))
                    self.metadata.add("RegisterBlueLongOffsetStd", rms1Long)
                    self.metadata.add("RegisterGreenLongOffsetStd", rms2Long)
                    self.metadata.add("RegisterRedLongOffsetStd", rms3Long)

                    self.metadata.add("RegisterBlueLatOffsetMedian", numpy.median(dlat[idx1]))
                    self.metadata.add("RegisterGreenLatOffsetMedian", numpy.median(dlat[idx2]))
                    self.metadata.add("RegisterRedLatOffsetMedian", numpy.median(dlat[idx3]))
                    self.metadata.add("RegisterBlueLatOffsetStd", rms1Lat)
                    self.metadata.add("RegisterGreenLatOffsetStd", rms2Lat)
                    self.metadata.add("RegisterRedLatOffsetStd", rms3Lat)

            # warp template exposure to match exposure,
            # PSF match template exposure to exposure,
            # then return the difference

            #Return warped template...  Construct sourceKernelCand list after subtract
            self.log.info("Subtracting images")
            subtractRes = self.subtract.subtractExposures(
                templateExposure=templateExposure,
                scienceExposure=exposure,
                candidateList=kernelSources,
                convolveTemplate=self.config.convolveTemplate,
                doWarping=not self.config.doUseRegister
            )
            subtractedExposure = subtractRes.subtractedExposure

            if self.config.doWriteMatchedExp:
                sensorRef.put(subtractRes.matchedExposure, self.config.coaddName + "Diff_matchedExp")

        if self.config.doDetection:
            self.log.info("Running diaSource detection")
            if subtractedExposure is None:
                subtractedExposure = sensorRef.get(subtractedExposureName)

            # Get Psf from the appropriate input image if it doesn't exist
            if not subtractedExposure.hasPsf():
                if self.config.convolveTemplate:
                    subtractedExposure.setPsf(exposure.getPsf())
                else:
                    if templateExposure is None:
                        template = self.getTemplate.run(exposure, sensorRef, templateIdList=templateIdList)
                    subtractedExposure.setPsf(template.exposure.getPsf())

            # Erase existing detection mask planes
            mask  = subtractedExposure.getMaskedImage().getMask()
            mask &= ~(mask.getPlaneBitMask("DETECTED") | mask.getPlaneBitMask("DETECTED_NEGATIVE"))

            table = afwTable.SourceTable.make(self.schema, idFactory)
            table.setMetadata(self.algMetadata)
            results = self.detection.makeSourceCatalog(
                table=table,
                exposure=subtractedExposure,
                doSmooth=not self.config.doPreConvolve
                )

            if self.config.doMerge:
                fpSet = results.fpSets.positive
                fpSet.merge(results.fpSets.negative, self.config.growFootprint,
                            self.config.growFootprint, False)
                diaSources = afwTable.SourceCatalog(table)
                fpSet.makeSources(diaSources)
                self.log.info("Merging detections into %d sources" % (len(diaSources)))
            else:
                diaSources = results.sources

            if self.config.doMeasurement:
                self.log.info("Running diaSource measurement")
                self.measurement.run(diaSources, subtractedExposure)

            # Match with the calexp sources if possible
            if self.config.doMatchSources:
                if sensorRef.datasetExists("src"):
                    # Create key,val pair where key=diaSourceId and val=sourceId
                    matchRadAsec = self.config.diaSourceMatchRadius
                    matchRadPixel = matchRadAsec / exposure.getWcs().pixelScale().asArcseconds()

                    srcMatches = afwTable.matchXy(sensorRef.get("src"), diaSources, matchRadPixel, True)
                    srcMatchDict = dict([(srcMatch.second.getId(), srcMatch.first.getId()) for 
                                         srcMatch in srcMatches])
                    self.log.info("Matched %d / %d diaSources to sources" % (len(srcMatchDict),
                                                                             len(diaSources)))
                else:
                    self.log.warn("Src product does not exist; cannot match with diaSources")
                    srcMatchDict = {}

                # Create key,val pair where key=diaSourceId and val=refId
                refAstromConfig = measAstrom.AstrometryConfig()
                refAstromConfig.matcher.maxMatchDistArcSec = matchRadAsec
                refAstrometer = measAstrom.AstrometryTask(refAstromConfig)
                astromRet = refAstrometer.run(exposure=exposure, sourceCat=diaSources)
                refMatches = astromRet.matches
                if refMatches is None:
                    self.log.warn("No diaSource matches with reference catalog")
                    refMatchDict = {}
                else:
                    self.log.info("Matched %d / %d diaSources to reference catalog" % (len(refMatches),
                                                                                       len(diaSources)))
                    refMatchDict = dict([(refMatch.second.getId(), refMatch.first.getId()) for \
                                             refMatch in refMatches])

                # Assign source Ids
                for diaSource in diaSources:
                    sid = diaSource.getId()
                    if srcMatchDict.has_key(sid):
                        diaSource.set("srcMatchId", srcMatchDict[sid])
                    if refMatchDict.has_key(sid):
                        diaSource.set("refMatchId", refMatchDict[sid])

            if diaSources is not None and self.config.doWriteSources:
                sensorRef.put(diaSources, self.config.coaddName + "Diff_diaSrc")

            if self.config.doAddMetrics and self.config.doSelectSources:
                self.log.info("Evaluating metrics and control sample")

                kernelCandList = []
                for cell in subtractRes.kernelCellSet.getCellList():
                    for cand in cell.begin(False): # include bad candidates
                        kernelCandList.append(cast_KernelCandidateF(cand))

                # Get basis list to build control sample kernels
                basisList = afwMath.cast_LinearCombinationKernel(
                    kernelCandList[0].getKernel(KernelCandidateF.ORIG)).getKernelList()

                controlCandList = \
                    diffimTools.sourceTableToCandidateList(controlSources, 
                                                           subtractRes.warpedExposure, exposure,
                                                           self.config.subtract.kernel.active,
                                                           self.config.subtract.kernel.active.detectionConfig,
                                                           self.log, doBuild=True, basisList=basisList)

                kcQa.apply(kernelCandList, subtractRes.psfMatchingKernel, subtractRes.backgroundModel,
                                dof=nparam)
                kcQa.apply(controlCandList, subtractRes.psfMatchingKernel, subtractRes.backgroundModel)

                if self.config.doDetection:
                    kcQa.aggregate(selectSources, self.metadata, allresids, diaSources)
                else:
                    kcQa.aggregate(selectSources, self.metadata, allresids)

                sensorRef.put(selectSources, self.config.coaddName + "Diff_kernelSrc")

        if self.config.doWriteSubtractedExp:
            sensorRef.put(subtractedExposure, subtractedExposureName)

        self.runDebug(exposure, subtractRes, selectSources, kernelSources, diaSources)
        return pipeBase.Struct(
            subtractedExposure=subtractedExposure,
            subtractRes=subtractRes,
            sources=diaSources,
        )
    def runXY0(self, poly, fitForBackground=False):
        if not self.defDataDir:
            print >> sys.stderr, "Warning: afwdata is not set up"
            return

        self.subconfig.spatialModelType = poly
        self.subconfig.fitForBackground = fitForBackground

        templateSubImage = afwImage.ExposureF(self.templateImage, self.bbox)
        scienceSubImage = afwImage.ExposureF(self.scienceImage, self.bbox)

        # Have an XY0
        psfmatch = ipDiffim.ImagePsfMatchTask(config=self.config)
        results1 = psfmatch.subtractExposures(templateSubImage,
                                              scienceSubImage,
                                              doWarping=True)
        spatialKernel1 = results1.psfMatchingKernel
        backgroundModel1 = results1.backgroundModel
        kernelCellSet1 = results1.kernelCellSet

        # And then take away XY0
        templateSubImage.setXY0(afwGeom.Point2I(0, 0))
        scienceSubImage.setXY0(afwGeom.Point2I(0, 0))
        results2 = psfmatch.subtractExposures(templateSubImage,
                                              scienceSubImage,
                                              doWarping=True)
        spatialKernel2 = results2.psfMatchingKernel
        backgroundModel2 = results2.backgroundModel
        kernelCellSet2 = results2.kernelCellSet

        # need to count up the candidates first, since its a running tally
        count = 0
        for cell in kernelCellSet1.getCellList():
            for cand1 in cell.begin(False):
                count += 1

        for cell in kernelCellSet1.getCellList():
            for cand1 in cell.begin(False):
                if cand1.getStatus() == afwMath.SpatialCellCandidate.UNKNOWN:
                    continue
                if cand1.getStatus() == afwMath.SpatialCellCandidate.BAD:
                    continue

                cand1 = ipDiffim.cast_KernelCandidateF(cand1)
                cand2 = ipDiffim.cast_KernelCandidateF(
                    kernelCellSet2.getCandidateById(cand1.getId() + count))

                # positions are nearly the same (different at the 0.01 pixel level)
                self.assertAlmostEqual(cand1.getXCenter(),
                                       cand2.getXCenter(),
                                       delta=1e-1)
                self.assertAlmostEqual(cand1.getYCenter(),
                                       cand2.getYCenter() + self.offset,
                                       delta=1e-1)

                # kernels are the same
                im1 = cand1.getKernelImage(ipDiffim.KernelCandidateF.RECENT)
                im2 = cand2.getKernelImage(ipDiffim.KernelCandidateF.RECENT)
                for y in range(im1.getHeight()):
                    for x in range(im1.getWidth()):
                        self.assertAlmostEqual(im1.get(x, y),
                                               im2.get(x, y),
                                               delta=1e-7)

        # Spatial fits are the same
        skp1 = spatialKernel1.getSpatialParameters()
        skp2 = spatialKernel2.getSpatialParameters()
        bgp1 = backgroundModel1.getParameters()
        bgp2 = backgroundModel2.getParameters()

        # first term = kernel sum, 0, 0
        self.assertAlmostEqual(skp1[0][0], skp2[0][0], delta=1e-6)

        # On other terms, the spatial terms are the same, the zpt terms are different
        for nk in range(1, len(skp1)):

            # Zeropoint
            if poly == 'polynomial':
                self.assertNotEqual(skp1[nk][0], skp2[nk][0])
            elif poly == 'chebyshev1':
                # Cheby remaps coords, so model should be the same!
                self.assertAlmostEqual(skp1[nk][0], skp2[nk][0], delta=1e-4)
            else:
                self.fail()

            # Spatial terms
            for np in range(1, len(skp1[nk])):
                self.assertAlmostEqual(skp1[nk][np], skp2[nk][np], delta=1e-4)

        for np in range(len(bgp1)):
            self.assertAlmostEqual(bgp1[np], bgp2[np], delta=1e-4)
    sys.exit(1)

if False:
    print spatialKernel.getSpatialFunctionList()[0].toString()
    print spatialKernel.getKernelParameters()
    print spatialKernel.getSpatialParameters()
    import pdb; pdb.set_trace()

# Lets see what we got
if display:
    mos = displayUtils.Mosaic()

    # Inputs
    for cell in kernelCellSet.getCellList():
        for cand in cell.begin(False): # False = include bad candidates
            cand  = ipDiffim.cast_KernelCandidateF(cand)
            rchi2 = cand.getChi2()

            # No kernels made
            if cand.getStatus() == afwMath.SpatialCellCandidate.UNKNOWN:
                continue

            try:
                im = cand.getKernelImage(ipDiffim.KernelCandidateF.RECENT)
            except Exception:
                continue
            
            if cand.getStatus() == afwMath.SpatialCellCandidate.GOOD:
                statStr = "Good"
            elif cand.getStatus() == afwMath.SpatialCellCandidate.BAD:
                statStr = "Bad"
Exemple #15
0
    def run(self, sensorRef, templateIdList=None):
        """Subtract an image from a template coadd and measure the result

        Steps include:
        - warp template coadd to match WCS of image
        - PSF match image to warped template
        - subtract image from PSF-matched, warped template
        - persist difference image
        - detect sources
        - measure sources

        @param sensorRef: sensor-level butler data reference, used for the following data products:
        Input only:
        - calexp
        - psf
        - ccdExposureId
        - ccdExposureId_bits
        - self.config.coaddName + "Coadd_skyMap"
        - self.config.coaddName + "Coadd"
        Input or output, depending on config:
        - self.config.coaddName + "Diff_subtractedExp"
        Output, depending on config:
        - self.config.coaddName + "Diff_matchedExp"
        - self.config.coaddName + "Diff_src"

        @return pipe_base Struct containing these fields:
        - subtractedExposure: exposure after subtracting template;
            the unpersisted version if subtraction not run but detection run
            None if neither subtraction nor detection run (i.e. nothing useful done)
        - subtractRes: results of subtraction task; None if subtraction not run
        - sources: detected and possibly measured sources; None if detection not run
        """
        self.log.info("Processing %s" % (sensorRef.dataId))

        # initialize outputs and some intermediate products
        subtractedExposure = None
        subtractRes = None
        selectSources = None
        kernelSources = None
        controlSources = None
        diaSources = None

        # We make one IdFactory that will be used by both icSrc and src datasets;
        # I don't know if this is the way we ultimately want to do things, but at least
        # this ensures the source IDs are fully unique.
        expBits = sensorRef.get("ccdExposureId_bits")
        expId = long(sensorRef.get("ccdExposureId"))
        idFactory = afwTable.IdFactory.makeSource(expId, 64 - expBits)

        # Retrieve the science image we wish to analyze
        exposure = sensorRef.get("calexp", immediate=True)
        if self.config.doAddCalexpBackground:
            mi = exposure.getMaskedImage()
            mi += sensorRef.get("calexpBackground").getImage()
        if not exposure.hasPsf():
            raise pipeBase.TaskError("Exposure has no psf")
        sciencePsf = exposure.getPsf()

        subtractedExposureName = self.config.coaddName + "Diff_differenceExp"
        templateExposure = None  # Stitched coadd exposure
        templateSources = None  # Sources on the template image
        if self.config.doSubtract:
            template = self.getTemplate.run(exposure,
                                            sensorRef,
                                            templateIdList=templateIdList)
            templateExposure = template.exposure
            templateSources = template.sources

            # compute scienceSigmaOrig: sigma of PSF of science image before pre-convolution
            ctr = afwGeom.Box2D(exposure.getBBox()).getCenter()
            psfAttr = PsfAttributes(sciencePsf, afwGeom.Point2I(ctr))
            scienceSigmaOrig = psfAttr.computeGaussianWidth(
                psfAttr.ADAPTIVE_MOMENT)

            # sigma of PSF of template image before warping
            ctr = afwGeom.Box2D(templateExposure.getBBox()).getCenter()
            psfAttr = PsfAttributes(templateExposure.getPsf(),
                                    afwGeom.Point2I(ctr))
            templateSigma = psfAttr.computeGaussianWidth(
                psfAttr.ADAPTIVE_MOMENT)

            # if requested, convolve the science exposure with its PSF
            # (properly, this should be a cross-correlation, but our code does not yet support that)
            # compute scienceSigmaPost: sigma of science exposure with pre-convolution, if done,
            # else sigma of original science exposure
            if self.config.doPreConvolve:
                convControl = afwMath.ConvolutionControl()
                # cannot convolve in place, so make a new MI to receive convolved image
                srcMI = exposure.getMaskedImage()
                destMI = srcMI.Factory(srcMI.getDimensions())
                srcPsf = sciencePsf
                if self.config.useGaussianForPreConvolution:
                    # convolve with a simplified PSF model: a double Gaussian
                    kWidth, kHeight = sciencePsf.getLocalKernel(
                    ).getDimensions()
                    preConvPsf = SingleGaussianPsf(kWidth, kHeight,
                                                   scienceSigmaOrig)
                else:
                    # convolve with science exposure's PSF model
                    preConvPsf = srcPsf
                afwMath.convolve(destMI, srcMI, preConvPsf.getLocalKernel(),
                                 convControl)
                exposure.setMaskedImage(destMI)
                scienceSigmaPost = scienceSigmaOrig * math.sqrt(2)
            else:
                scienceSigmaPost = scienceSigmaOrig

            # If requested, find sources in the image
            if self.config.doSelectSources:
                if not sensorRef.datasetExists("src"):
                    self.log.warn(
                        "Src product does not exist; running detection, measurement, selection"
                    )
                    # Run own detection and measurement; necessary in nightly processing
                    selectSources = self.subtract.getSelectSources(
                        exposure,
                        sigma=scienceSigmaPost,
                        doSmooth=not self.doPreConvolve,
                        idFactory=idFactory,
                    )
                else:
                    self.log.info("Source selection via src product")
                    # Sources already exist; for data release processing
                    selectSources = sensorRef.get("src")

                # Number of basis functions
                nparam = len(
                    makeKernelBasisList(
                        self.subtract.config.kernel.active,
                        referenceFwhmPix=scienceSigmaPost * FwhmPerSigma,
                        targetFwhmPix=templateSigma * FwhmPerSigma))

                if self.config.doAddMetrics:
                    # Modify the schema of all Sources
                    kcQa = KernelCandidateQa(nparam)
                    selectSources = kcQa.addToSchema(selectSources)

                if self.config.kernelSourcesFromRef:
                    # match exposure sources to reference catalog
                    astromRet = self.astrometer.loadAndMatch(
                        exposure=exposure, sourceCat=selectSources)
                    matches = astromRet.matches
                elif templateSources:
                    # match exposure sources to template sources
                    matches = afwTable.matchRaDec(templateSources,
                                                  selectSources,
                                                  1.0 * afwGeom.arcseconds,
                                                  False)
                else:
                    raise RuntimeError(
                        "doSelectSources=True and kernelSourcesFromRef=False,"
                        +
                        "but template sources not available. Cannot match science "
                        +
                        "sources with template sources. Run process* on data from "
                        + "which templates are built.")

                kernelSources = self.sourceSelector.selectStars(
                    exposure, selectSources, matches=matches).starCat

                random.shuffle(kernelSources, random.random)
                controlSources = kernelSources[::self.config.controlStepSize]
                kernelSources = [
                    k for i, k in enumerate(kernelSources)
                    if i % self.config.controlStepSize
                ]

                if self.config.doSelectDcrCatalog:
                    redSelector = DiaCatalogSourceSelectorTask(
                        DiaCatalogSourceSelectorConfig(
                            grMin=self.sourceSelector.config.grMax,
                            grMax=99.999))
                    redSources = redSelector.selectStars(
                        exposure, selectSources, matches=matches).starCat
                    controlSources.extend(redSources)

                    blueSelector = DiaCatalogSourceSelectorTask(
                        DiaCatalogSourceSelectorConfig(
                            grMin=-99.999,
                            grMax=self.sourceSelector.config.grMin))
                    blueSources = blueSelector.selectStars(
                        exposure, selectSources, matches=matches).starCat
                    controlSources.extend(blueSources)

                if self.config.doSelectVariableCatalog:
                    varSelector = DiaCatalogSourceSelectorTask(
                        DiaCatalogSourceSelectorConfig(includeVariable=True))
                    varSources = varSelector.selectStars(
                        exposure, selectSources, matches=matches).starCat
                    controlSources.extend(varSources)

                self.log.info(
                    "Selected %d / %d sources for Psf matching (%d for control sample)"
                    % (len(kernelSources), len(selectSources),
                       len(controlSources)))
            allresids = {}
            if self.config.doUseRegister:
                self.log.info("Registering images")

                if templateSources is None:
                    # Run detection on the template, which is
                    # temporarily background-subtracted
                    templateSources = self.subtract.getSelectSources(
                        templateExposure,
                        sigma=templateSigma,
                        doSmooth=True,
                        idFactory=idFactory)

                # Third step: we need to fit the relative astrometry.
                #
                wcsResults = self.fitAstrometry(templateSources,
                                                templateExposure,
                                                selectSources)
                warpedExp = self.register.warpExposure(templateExposure,
                                                       wcsResults.wcs,
                                                       exposure.getWcs(),
                                                       exposure.getBBox())
                templateExposure = warpedExp

                # Create debugging outputs on the astrometric
                # residuals as a function of position.  Persistence
                # not yet implemented; expected on (I believe) #2636.
                if self.config.doDebugRegister:
                    # Grab matches to reference catalog
                    srcToMatch = {x.second.getId(): x.first for x in matches}

                    refCoordKey = wcsResults.matches[0].first.getTable(
                    ).getCoordKey()
                    inCentroidKey = wcsResults.matches[0].second.getTable(
                    ).getCentroidKey()
                    sids = [m.first.getId() for m in wcsResults.matches]
                    positions = [
                        m.first.get(refCoordKey) for m in wcsResults.matches
                    ]
                    residuals = [
                        m.first.get(refCoordKey).getOffsetFrom(
                            wcsResults.wcs.pixelToSky(
                                m.second.get(inCentroidKey)))
                        for m in wcsResults.matches
                    ]
                    allresids = dict(zip(sids, zip(positions, residuals)))

                    cresiduals = [
                        m.first.get(refCoordKey).getTangentPlaneOffset(
                            wcsResults.wcs.pixelToSky(
                                m.second.get(inCentroidKey)))
                        for m in wcsResults.matches
                    ]
                    colors = numpy.array([
                        -2.5 * numpy.log10(srcToMatch[x].get("g")) +
                        2.5 * numpy.log10(srcToMatch[x].get("r")) for x in sids
                        if x in srcToMatch.keys()
                    ])
                    dlong = numpy.array([
                        r[0].asArcseconds() for s, r in zip(sids, cresiduals)
                        if s in srcToMatch.keys()
                    ])
                    dlat = numpy.array([
                        r[1].asArcseconds() for s, r in zip(sids, cresiduals)
                        if s in srcToMatch.keys()
                    ])
                    idx1 = numpy.where(
                        colors < self.sourceSelector.config.grMin)
                    idx2 = numpy.where(
                        (colors >= self.sourceSelector.config.grMin)
                        & (colors <= self.sourceSelector.config.grMax))
                    idx3 = numpy.where(
                        colors > self.sourceSelector.config.grMax)
                    rms1Long = IqrToSigma * (numpy.percentile(
                        dlong[idx1], 75) - numpy.percentile(dlong[idx1], 25))
                    rms1Lat = IqrToSigma * (numpy.percentile(dlat[idx1], 75) -
                                            numpy.percentile(dlat[idx1], 25))
                    rms2Long = IqrToSigma * (numpy.percentile(
                        dlong[idx2], 75) - numpy.percentile(dlong[idx2], 25))
                    rms2Lat = IqrToSigma * (numpy.percentile(dlat[idx2], 75) -
                                            numpy.percentile(dlat[idx2], 25))
                    rms3Long = IqrToSigma * (numpy.percentile(
                        dlong[idx3], 75) - numpy.percentile(dlong[idx3], 25))
                    rms3Lat = IqrToSigma * (numpy.percentile(dlat[idx3], 75) -
                                            numpy.percentile(dlat[idx3], 25))
                    self.log.info("Blue star offsets'': %.3f %.3f, %.3f %.3f" %
                                  (numpy.median(dlong[idx1]), rms1Long,
                                   numpy.median(dlat[idx1]), rms1Lat))
                    self.log.info(
                        "Green star offsets'': %.3f %.3f, %.3f %.3f" %
                        (numpy.median(dlong[idx2]), rms2Long,
                         numpy.median(dlat[idx2]), rms2Lat))
                    self.log.info("Red star offsets'': %.3f %.3f, %.3f %.3f" %
                                  (numpy.median(dlong[idx3]), rms3Long,
                                   numpy.median(dlat[idx3]), rms3Lat))

                    self.metadata.add("RegisterBlueLongOffsetMedian",
                                      numpy.median(dlong[idx1]))
                    self.metadata.add("RegisterGreenLongOffsetMedian",
                                      numpy.median(dlong[idx2]))
                    self.metadata.add("RegisterRedLongOffsetMedian",
                                      numpy.median(dlong[idx3]))
                    self.metadata.add("RegisterBlueLongOffsetStd", rms1Long)
                    self.metadata.add("RegisterGreenLongOffsetStd", rms2Long)
                    self.metadata.add("RegisterRedLongOffsetStd", rms3Long)

                    self.metadata.add("RegisterBlueLatOffsetMedian",
                                      numpy.median(dlat[idx1]))
                    self.metadata.add("RegisterGreenLatOffsetMedian",
                                      numpy.median(dlat[idx2]))
                    self.metadata.add("RegisterRedLatOffsetMedian",
                                      numpy.median(dlat[idx3]))
                    self.metadata.add("RegisterBlueLatOffsetStd", rms1Lat)
                    self.metadata.add("RegisterGreenLatOffsetStd", rms2Lat)
                    self.metadata.add("RegisterRedLatOffsetStd", rms3Lat)

            # warp template exposure to match exposure,
            # PSF match template exposure to exposure,
            # then return the difference

            #Return warped template...  Construct sourceKernelCand list after subtract
            self.log.info("Subtracting images")
            subtractRes = self.subtract.subtractExposures(
                templateExposure=templateExposure,
                scienceExposure=exposure,
                candidateList=kernelSources,
                convolveTemplate=self.config.convolveTemplate,
                doWarping=not self.config.doUseRegister)
            subtractedExposure = subtractRes.subtractedExposure

            if self.config.doWriteMatchedExp:
                sensorRef.put(subtractRes.matchedExposure,
                              self.config.coaddName + "Diff_matchedExp")

        if self.config.doDetection:
            self.log.info("Computing diffim PSF")
            if subtractedExposure is None:
                subtractedExposure = sensorRef.get(subtractedExposureName)

            # Get Psf from the appropriate input image if it doesn't exist
            if not subtractedExposure.hasPsf():
                if self.config.convolveTemplate:
                    subtractedExposure.setPsf(exposure.getPsf())
                else:
                    if templateExposure is None:
                        template = self.getTemplate.run(
                            exposure, sensorRef, templateIdList=templateIdList)
                    subtractedExposure.setPsf(template.exposure.getPsf())

        # If doSubtract is False, then subtractedExposure was fetched from disk (above), thus it may have
        # already been decorrelated. Thus, we do not do decorrelation if doSubtract is False.
        if self.config.doDecorrelation and self.config.doSubtract:
            decorrResult = self.decorrelate.run(exposure, templateExposure,
                                                subtractedExposure,
                                                subtractRes.psfMatchingKernel)
            subtractedExposure = decorrResult.correctedExposure

        if self.config.doDetection:
            self.log.info("Running diaSource detection")
            # Erase existing detection mask planes
            mask = subtractedExposure.getMaskedImage().getMask()
            mask &= ~(mask.getPlaneBitMask("DETECTED")
                      | mask.getPlaneBitMask("DETECTED_NEGATIVE"))

            table = afwTable.SourceTable.make(self.schema, idFactory)
            table.setMetadata(self.algMetadata)
            results = self.detection.makeSourceCatalog(
                table=table,
                exposure=subtractedExposure,
                doSmooth=not self.config.doPreConvolve)

            if self.config.doMerge:
                fpSet = results.fpSets.positive
                fpSet.merge(results.fpSets.negative, self.config.growFootprint,
                            self.config.growFootprint, False)
                diaSources = afwTable.SourceCatalog(table)
                fpSet.makeSources(diaSources)
                self.log.info("Merging detections into %d sources" %
                              (len(diaSources)))
            else:
                diaSources = results.sources

            if self.config.doMeasurement:
                self.log.info("Running diaSource measurement")
                if not self.config.doDipoleFitting:
                    self.measurement.run(diaSources, subtractedExposure)
                else:
                    if self.config.doSubtract:
                        self.measurement.run(diaSources, subtractedExposure,
                                             exposure,
                                             subtractRes.matchedExposure)
                    else:
                        self.measurement.run(diaSources, subtractedExposure,
                                             exposure)

            # Match with the calexp sources if possible
            if self.config.doMatchSources:
                if sensorRef.datasetExists("src"):
                    # Create key,val pair where key=diaSourceId and val=sourceId
                    matchRadAsec = self.config.diaSourceMatchRadius
                    matchRadPixel = matchRadAsec / exposure.getWcs(
                    ).pixelScale().asArcseconds()

                    srcMatches = afwTable.matchXy(sensorRef.get("src"),
                                                  diaSources, matchRadPixel,
                                                  True)
                    srcMatchDict = dict([(srcMatch.second.getId(),
                                          srcMatch.first.getId())
                                         for srcMatch in srcMatches])
                    self.log.info("Matched %d / %d diaSources to sources" %
                                  (len(srcMatchDict), len(diaSources)))
                else:
                    self.log.warn(
                        "Src product does not exist; cannot match with diaSources"
                    )
                    srcMatchDict = {}

                # Create key,val pair where key=diaSourceId and val=refId
                refAstromConfig = measAstrom.AstrometryConfig()
                refAstromConfig.matcher.maxMatchDistArcSec = matchRadAsec
                refAstrometer = measAstrom.AstrometryTask(refAstromConfig)
                astromRet = refAstrometer.run(exposure=exposure,
                                              sourceCat=diaSources)
                refMatches = astromRet.matches
                if refMatches is None:
                    self.log.warn(
                        "No diaSource matches with reference catalog")
                    refMatchDict = {}
                else:
                    self.log.info(
                        "Matched %d / %d diaSources to reference catalog" %
                        (len(refMatches), len(diaSources)))
                    refMatchDict = dict([(refMatch.second.getId(), refMatch.first.getId()) for \
                                             refMatch in refMatches])

                # Assign source Ids
                for diaSource in diaSources:
                    sid = diaSource.getId()
                    if srcMatchDict.has_key(sid):
                        diaSource.set("srcMatchId", srcMatchDict[sid])
                    if refMatchDict.has_key(sid):
                        diaSource.set("refMatchId", refMatchDict[sid])

            if diaSources is not None and self.config.doWriteSources:
                sensorRef.put(diaSources,
                              self.config.coaddName + "Diff_diaSrc")

            if self.config.doAddMetrics and self.config.doSelectSources:
                self.log.info("Evaluating metrics and control sample")

                kernelCandList = []
                for cell in subtractRes.kernelCellSet.getCellList():
                    for cand in cell.begin(False):  # include bad candidates
                        kernelCandList.append(cast_KernelCandidateF(cand))

                # Get basis list to build control sample kernels
                basisList = afwMath.cast_LinearCombinationKernel(
                    kernelCandList[0].getKernel(
                        KernelCandidateF.ORIG)).getKernelList()

                controlCandList = \
                    diffimTools.sourceTableToCandidateList(controlSources,
                                                           subtractRes.warpedExposure, exposure,
                                                           self.config.subtract.kernel.active,
                                                           self.config.subtract.kernel.active.detectionConfig,
                                                           self.log, doBuild=True, basisList=basisList)

                kcQa.apply(kernelCandList,
                           subtractRes.psfMatchingKernel,
                           subtractRes.backgroundModel,
                           dof=nparam)
                kcQa.apply(controlCandList, subtractRes.psfMatchingKernel,
                           subtractRes.backgroundModel)

                if self.config.doDetection:
                    kcQa.aggregate(selectSources, self.metadata, allresids,
                                   diaSources)
                else:
                    kcQa.aggregate(selectSources, self.metadata, allresids)

                sensorRef.put(selectSources,
                              self.config.coaddName + "Diff_kernelSrc")

        if self.config.doWriteSubtractedExp:
            sensorRef.put(subtractedExposure, subtractedExposureName)

        self.runDebug(exposure, subtractRes, selectSources, kernelSources,
                      diaSources)
        return pipeBase.Struct(
            subtractedExposure=subtractedExposure,
            subtractRes=subtractRes,
            sources=diaSources,
        )
    def run(self, sensorRef):
        """Subtract an image from a template coadd and measure the result
    
        Steps include:
        - warp template coadd to match WCS of image
        - PSF match image to warped template
        - subtract image from PSF-matched, warped template
        - persist difference image
        - detect sources
        - measure sources
        
        @param sensorRef: sensor-level butler data reference, used for the following data products:
        Input only:
        - calexp
        - psf
        - ccdExposureId
        - ccdExposureId_bits
        - self.config.coaddName + "Coadd_skyMap"
        - self.config.coaddName + "Coadd"
        Input or output, depending on config:
        - self.config.coaddName + "Diff_subtractedExp"
        Output, depending on config:
        - self.config.coaddName + "Diff_matchedExp"
        - self.config.coaddName + "Diff_src"
            
        @return pipe_base Struct containing these fields:
        - subtractedExposure: exposure after subtracting template;
            the unpersisted version if subtraction not run but detection run
            None if neither subtraction nor detection run (i.e. nothing useful done)
        - subtractRes: results of subtraction task; None if subtraction not run
        - sources: detected and possibly measured sources; None if detection not run
        """
        self.log.info("Processing %s" % (sensorRef.dataId))

        # initialize outputs and some intermediate products
        subtractedExposure = None
        subtractRes = None
        selectSources = None
        kernelSources = None
        controlSources = None
        diaSources = None

        # We make one IdFactory that will be used by both icSrc and src datasets;
        # I don't know if this is the way we ultimately want to do things, but at least
        # this ensures the source IDs are fully unique.
        expBits = sensorRef.get("ccdExposureId_bits")
        expId = long(sensorRef.get("ccdExposureId"))
        idFactory = afwTable.IdFactory.makeSource(expId, 64 - expBits)
        
        # Retrieve the science image we wish to analyze
        exposure = sensorRef.get("calexp", immediate=True)
        if self.config.doAddCalexpBackground:
            mi = exposure.getMaskedImage()
            mi += sensorRef.get("calexpBackground").getImage()
        if not exposure.hasPsf():
            raise pipeBase.TaskError("Exposure has no psf")
        sciencePsf = exposure.getPsf()

        if self.config.useWinter2013Hacks and self.config.winter2013borderMask > 0:
            self.log.warn("USING WINTER2013 HACK: MASKING BORDER PIXELS")
            bbox = exposure.getBBox(afwImage.PARENT)
            bbox.grow(-self.config.winter2013borderMask)
            self.setEdgeBits(exposure.getMaskedImage(), bbox, 
                             exposure.getMaskedImage().getMask().getPlaneBitMask("NO_DATA"))
            
        # compute scienceSigmaOrig: sigma of PSF of science image before pre-convolution
        ctr = afwGeom.Box2D(exposure.getBBox(afwImage.PARENT)).getCenter()
        psfAttr = PsfAttributes(sciencePsf, afwGeom.Point2I(ctr))
        scienceSigmaOrig = psfAttr.computeGaussianWidth(psfAttr.ADAPTIVE_MOMENT)
        
        subtractedExposureName = self.config.coaddName + "Diff_differenceExp"
        templateExposure = None  # Stitched coadd exposure
        templateSources = None   # Sources on the template image
        if self.config.doSubtract:
            templateExposure, templateSources = self.getTemplate(exposure, sensorRef)

            # sigma of PSF of template image before warping
            ctr = afwGeom.Box2D(templateExposure.getBBox(afwImage.PARENT)).getCenter()
            psfAttr = PsfAttributes(templateExposure.getPsf(), afwGeom.Point2I(ctr))
            templateSigma = psfAttr.computeGaussianWidth(psfAttr.ADAPTIVE_MOMENT)

            # if requested, convolve the science exposure with its PSF
            # (properly, this should be a cross-correlation, but our code does not yet support that)
            # compute scienceSigmaPost: sigma of science exposure with pre-convolution, if done,
            # else sigma of original science exposure
            if self.config.doPreConvolve:
                convControl = afwMath.ConvolutionControl()
                # cannot convolve in place, so make a new MI to receive convolved image
                srcMI = exposure.getMaskedImage()
                destMI = srcMI.Factory(srcMI.getDimensions())
                srcPsf = sciencePsf
                if self.config.useGaussianForPreConvolution:
                    # convolve with a simplified PSF model: a double Gaussian
                    kWidth, kHeight = sciencePsf.getKernel().getDimensions()
                    preConvPsf = SingleGaussianPsf(kWidth, kHeight, scienceSigmaOrig)
                else:
                    # convolve with science exposure's PSF model
                    preConvPsf = psf
                afwMath.convolve(destMI, srcMI, preConvPsf.getKernel(), convControl)
                exposure.setMaskedImage(destMI)
                scienceSigmaPost = scienceSigmaOrig * math.sqrt(2)
            else:
                scienceSigmaPost = scienceSigmaOrig

            # If requested, find sources in the image
            if self.config.doSelectSources:
                if not sensorRef.datasetExists("src"):
                    self.log.warn("Src product does not exist; running detection, measurement, selection")
                    # Run own detection and measurement; necessary in nightly processing
                    selectSources = self.subtract.getSelectSources(
                        exposure, 
                        sigma = scienceSigmaPost, 
                        doSmooth = not self.doPreConvolve,
                        idFactory = idFactory,
                    )
                else:
                    self.log.info("Source selection via src product")
                    # Sources already exist; for data release processing
                    selectSources = sensorRef.get("src")

                # Number of basis functions
                nparam = len(makeKernelBasisList(self.subtract.config.kernel.active,
                                                 referenceFwhmPix = scienceSigmaPost * FwhmPerSigma,
                                                 targetFwhmPix = templateSigma * FwhmPerSigma))

                if self.config.doAddMetrics:
                    # Modify the schema of all Sources
                    self.kcQa = diUtils.KernelCandidateQa(nparam, self.log)
                    selectSources = self.kcQa.addToSchema(selectSources)

                astrometer = measAstrom.Astrometry(measAstrom.MeasAstromConfig())
                astromRet = astrometer.useKnownWcs(selectSources, exposure=exposure)
                matches = astromRet.matches
                kernelSources = self.sourceSelector.selectSources(exposure, selectSources, matches=matches)
                random.shuffle(kernelSources, random.random)
                controlSources = kernelSources[::self.config.controlStepSize]
                [kernelSources.remove(x) for x in controlSources]

                self.log.info("Selected %d / %d sources for Psf matching (%d for control sample)" \
                                  % (len(kernelSources), len(selectSources), len(controlSources)))
            allresids = {}
            if self.config.doUseRegister:
                self.log.info("Registering images")
                
                if templateSources is None:
                    # First step: we need to subtract the background out
                    # for detection and measurement.  Use large binsize
                    # for the background estimation.
                    binsize = self.config.templateBgBinSize
    
                    # Second step: we need to run detection on the
                    # background-subtracted template
                    #
                    # Estimate FWHM for detection
                    templateSources = self.subtract.getSelectSources(
                        templateExposure,
                        sigma = templateSigma,
                        doSmooth = True,
                        idFactory = idFactory,
                        binsize = binsize,
                    )

                # Third step: we need to fit the relative astrometry.
                #
                # One problem is that the SIP fits are w.r.t. CRPIX,
                # and these coadd patches have the CRPIX of the entire
                # tract, i.e. off the image.  This causes
                # register.fitWcs to fail.  A workaround for now is to
                # re-fit the Wcs which returns with a CRPIX that is on
                # the image, and *then* to fit for the relative Wcs.
                #
                # Requires low Sip order to avoid overfitting

                # useWinter2013Hacks includes us using the deep calexp
                # as the template.  In this case we don't need to
                # refit the Wcs.
                if not self.config.useWinter2013Hacks:
                    sipOrder = self.config.templateSipOrder
                    astrometer = measAstrom.Astrometry(measAstrom.MeasAstromConfig(sipOrder=sipOrder))
                    newWcs = astrometer.determineWcs(templateSources, templateExposure).getWcs()
                    results = self.register.run(templateSources, newWcs, 
                                                templateExposure.getBBox(afwImage.PARENT), selectSources)
                else:
                    if self.config.winter2013WcsShift > 0.0:
                        offset = afwGeom.Extent2D(self.config.winter2013WcsShift, 
                                                  self.config.winter2013WcsShift)
                        cKey = templateSources[0].getTable().getCentroidKey()
                        for source in templateSources:
                            centroid = source.get(cKey)
                            source.set(cKey, centroid+offset)
                    elif self.config.winter2013WcsRms > 0.0:
                        cKey = templateSources[0].getTable().getCentroidKey()
                        for source in templateSources:
                            offset = afwGeom.Extent2D(self.config.winter2013WcsRms*numpy.random.normal(), 
                                                      self.config.winter2013WcsRms*numpy.random.normal())
                            centroid = source.get(cKey)
                            source.set(cKey, centroid+offset)

                    results = self.register.run(templateSources, templateExposure.getWcs(),
                                                templateExposure.getBBox(afwImage.PARENT), selectSources)

                warpedExp = self.register.warpExposure(templateExposure, results.wcs, 
                                            exposure.getWcs(), exposure.getBBox(afwImage.PARENT))
                templateExposure = warpedExp

                # Create debugging outputs on the astrometric
                # residuals as a function of position.  Persistence
                # not yet implemented; expected on (I believe) #2636.
                if self.config.doDebugRegister:
                    refCoordKey = results.matches[0].first.getTable().getCoordKey()
                    inCentroidKey = results.matches[0].second.getTable().getCentroidKey()
                    sids      = [m.first.getId() for m in results.matches]
                    positions = [m.first.get(refCoordKey) for m in results.matches]
                    residuals = [m.first.get(refCoordKey).getOffsetFrom(
                                   results.wcs.pixelToSky(m.second.get(inCentroidKey))) for
                                 m in results.matches]
                    allresids = dict(zip(sids, zip(positions, residuals)))

            # warp template exposure to match exposure,
            # PSF match template exposure to exposure,
            # then return the difference

            #Return warped template...  Construct sourceKernelCand list after subtract
            self.log.info("Subtracting images")
            subtractRes = self.subtract.subtractExposures(
                templateExposure = templateExposure,
                scienceExposure = exposure,
                scienceFwhmPix = scienceSigmaPost * FwhmPerSigma,
                templateFwhmPix = templateSigma * FwhmPerSigma,
                candidateList = kernelSources,
                convolveTemplate = self.config.convolveTemplate,
                doWarping = not self.config.doUseRegister
            )
            subtractedExposure = subtractRes.subtractedExposure

            if self.config.doWriteMatchedExp:
                sensorRef.put(subtractRes.matchedExposure, self.config.coaddName + "Diff_matchedExp")

        if self.config.doDetection:
            self.log.info("Running diaSource detection")
            if subtractedExposure is None:
                subtractedExposure = sensorRef.get(subtractedExposureName)
            
            # Get Psf from the appropriate input image if it doesn't exist
            if not subtractedExposure.hasPsf():
                if self.config.convolveTemplate:
                    subtractedExposure.setPsf(exposure.getPsf())
                else:
                    if templateExposure is None:
                        templateExposure, templateSources = self.getTemplate(exposure, sensorRef)
                    subtractedExposure.setPsf(templateExposure.getPsf())

            # Erase existing detection mask planes
            mask  = subtractedExposure.getMaskedImage().getMask()
            mask &= ~(mask.getPlaneBitMask("DETECTED") | mask.getPlaneBitMask("DETECTED_NEGATIVE"))

            table = afwTable.SourceTable.make(self.schema, idFactory)
            table.setMetadata(self.algMetadata)
            results = self.detection.makeSourceCatalog(
                table = table,
                exposure = subtractedExposure,
                doSmooth = not self.config.doPreConvolve
                )

            if self.config.doMerge:
                fpSet = results.fpSets.positive
                fpSet.merge(results.fpSets.negative, self.config.growFootprint, 
                            self.config.growFootprint, False)
                diaSources = afwTable.SourceCatalog(table)
                fpSet.makeSources(diaSources)
                self.log.info("Merging detections into %d sources" % (len(diaSources)))
            else:
                diaSources = results.sources

            if self.config.doMeasurement:
                self.log.info("Running diaSource measurement")
                if len(diaSources) < self.config.maxDiaSourcesToMeasure:
                    self.dipolemeasurement.run(subtractedExposure, diaSources)
                else:
                    self.measurement.run(subtractedExposure, diaSources)

            # Match with the calexp sources if possible
            if self.config.doMatchSources:
                if sensorRef.datasetExists("src"):
                    # Create key,val pair where key=diaSourceId and val=sourceId
                    matchRadAsec = self.config.diaSourceMatchRadius
                    matchRadPixel = matchRadAsec / exposure.getWcs().pixelScale().asArcseconds()

                    # This does not do what I expect so I cobbled together a brute force method in python 
                    srcMatches = afwTable.matchXy(sensorRef.get("src"), diaSources, matchRadPixel, True) 
                    srcMatchDict = dict([(srcMatch.second.getId(), srcMatch.first.getId()) for \
                                             srcMatch in srcMatches])
                    self.log.info("Matched %d / %d diaSources to sources" % (len(srcMatchDict), 
                                                                             len(diaSources)))
                else:
                    self.log.warn("Src product does not exist; cannot match with diaSources")
                    srcMatchDict = {}

                # Create key,val pair where key=diaSourceId and val=refId
                astrometer = measAstrom.Astrometry(measAstrom.MeasAstromConfig(catalogMatchDist=matchRadAsec))
                astromRet = astrometer.useKnownWcs(diaSources, exposure=exposure)
                refMatches = astromRet.matches
                if refMatches is None:
                    self.log.warn("No diaSource matches with reference catalog")
                    refMatchDict = {}
                else:
                    self.log.info("Matched %d / %d diaSources to reference catalog" % (len(refMatches), 
                                                                                       len(diaSources)))
                    refMatchDict = dict([(refMatch.second.getId(), refMatch.first.getId()) for \
                                             refMatch in refMatches])

                # Assign source Ids
                for diaSource in diaSources:                    
                    sid = diaSource.getId()
                    if srcMatchDict.has_key(sid):
                        diaSource.set("srcMatchId", srcMatchDict[sid])
                    if refMatchDict.has_key(sid):
                        diaSource.set("refMatchId", refMatchDict[sid])
                        
            if diaSources is not None and self.config.doWriteSources:
                sourceWriteFlags = (0 if self.config.doWriteHeavyFootprintsInSources
                                    else afwTable.SOURCE_IO_NO_HEAVY_FOOTPRINTS)
                sensorRef.put(diaSources, self.config.coaddName + "Diff_diaSrc", flags=sourceWriteFlags)

            if self.config.doAddMetrics and self.config.doSelectSources:
                self.log.info("Evaluating metrics and control sample")

                kernelCandList = []
                for cell in subtractRes.kernelCellSet.getCellList():
                    for cand in cell.begin(False): # include bad candidates
                        kernelCandList.append(cast_KernelCandidateF(cand))

                # Get basis list to build control sample kernels
                basisList = afwMath.cast_LinearCombinationKernel(
                    kernelCandList[0].getKernel(KernelCandidateF.ORIG)).getKernelList()

                controlCandList = \
                    diffimTools.sourceTableToCandList(controlSources, subtractRes.warpedExposure, exposure, 
                                                      self.config.subtract.kernel.active, 
                                                      self.config.subtract.kernel.active.detectionConfig, 
                                                      self.log, dobuild=True, basisList=basisList)

                self.kcQa.apply(kernelCandList, subtractRes.psfMatchingKernel, subtractRes.backgroundModel, 
                                dof=nparam)
                self.kcQa.apply(controlCandList, subtractRes.psfMatchingKernel, subtractRes.backgroundModel)

                if self.config.doDetection:
                    self.kcQa.aggregate(selectSources, self.metadata, allresids, diaSources)
                else:
                    self.kcQa.aggregate(selectSources, self.metadata, allresids)

                #Persist using butler
                sensorRef.put(selectSources, self.config.coaddName + "Diff_kernelSrc")

        if self.config.doWriteSubtractedExp:
            sensorRef.put(subtractedExposure, subtractedExposureName)
 
        self.runDebug(exposure, subtractRes, selectSources, kernelSources, diaSources)
        return pipeBase.Struct(
            subtractedExposure = subtractedExposure,
            subtractRes = subtractRes,
            sources = diaSources,
        )
if False:
    print spatialKernel.getSpatialFunctionList()[0].toString()
    print spatialKernel.getKernelParameters()
    print spatialKernel.getSpatialParameters()
    import pdb
    pdb.set_trace()

# Lets see what we got
if display:
    mos = displayUtils.Mosaic()

    # Inputs
    for cell in kernelCellSet.getCellList():
        for cand in cell.begin(False):  # False = include bad candidates
            cand = ipDiffim.cast_KernelCandidateF(cand)
            rchi2 = cand.getChi2()

            # No kernels made
            if cand.getStatus() == afwMath.SpatialCellCandidate.UNKNOWN:
                continue

            try:
                im = cand.getKernelImage(ipDiffim.KernelCandidateF.RECENT)
            except Exception:
                continue

            if cand.getStatus() == afwMath.SpatialCellCandidate.GOOD:
                statStr = "Good"
            elif cand.getStatus() == afwMath.SpatialCellCandidate.BAD:
                statStr = "Bad"