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
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"
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"