419 idFactory=
None, calexpBackgroundExposure=
None, subtractedExposure=
None):
420 """PSF matches, subtract two images and perform detection on the difference image. 424 exposure : `lsst.afw.image.ExposureF`, optional 425 The science exposure, the minuend in the image subtraction. 426 Can be None only if ``config.doSubtract==False``. 427 selectSources : `lsst.afw.table.SourceCatalog`, optional 428 Identified sources on the science exposure. This catalog is used to 429 select sources in order to perform the AL PSF matching on stamp images 430 around them. The selection steps depend on config options and whether 431 ``templateSources`` and ``matchingSources`` specified. 432 templateExposure : `lsst.afw.image.ExposureF`, optional 433 The template to be subtracted from ``exposure`` in the image subtraction. 434 The template exposure should cover the same sky area as the science exposure. 435 It is either a stich of patches of a coadd skymap image or a calexp 436 of the same pointing as the science exposure. Can be None only 437 if ``config.doSubtract==False`` and ``subtractedExposure`` is not None. 438 templateSources : `lsst.afw.table.SourceCatalog`, optional 439 Identified sources on the template exposure. 440 idFactory : `lsst.afw.table.IdFactory` 441 Generator object to assign ids to detected sources in the difference image. 442 calexpBackgroundExposure : `lsst.afw.image.ExposureF`, optional 443 Background exposure to be added back to the science exposure 444 if ``config.doAddCalexpBackground==True`` 445 subtractedExposure : `lsst.afw.image.ExposureF`, optional 446 If ``config.doSubtract==False`` and ``config.doDetection==True``, 447 performs the post subtraction source detection only on this exposure. 448 Otherwise should be None. 452 results : `lsst.pipe.base.Struct` 453 ``subtractedExposure`` : `lsst.afw.image.ExposureF` 455 ``matchedExposure`` : `lsst.afw.image.ExposureF` 456 The matched PSF exposure. 457 ``subtractRes`` : `lsst.pipe.base.Struct` 458 The returned result structure of the ImagePsfMatchTask subtask. 459 ``diaSources`` : `lsst.afw.table.SourceCatalog` 460 The catalog of detected sources. 461 ``selectSources`` : `lsst.afw.table.SourceCatalog` 462 The input source catalog with optionally added Qa information. 466 The following major steps are included: 468 - warp template coadd to match WCS of image 469 - PSF match image to warped template 470 - subtract image from PSF-matched, warped template 474 For details about the image subtraction configuration modes 475 see `lsst.ip.diffim`. 478 controlSources =
None 482 if self.config.doAddCalexpBackground:
483 mi = exposure.getMaskedImage()
484 mi += calexpBackgroundExposure.getImage()
486 if not exposure.hasPsf():
487 raise pipeBase.TaskError(
"Exposure has no psf")
488 sciencePsf = exposure.getPsf()
490 if self.config.doSubtract:
491 if self.config.doScaleTemplateVariance:
492 templateVarFactor = self.scaleVariance.
run(
493 templateExposure.getMaskedImage())
494 self.metadata.add(
"scaleTemplateVarianceFactor", templateVarFactor)
496 if self.config.subtract.name ==
'zogy':
497 subtractRes = self.subtract.subtractExposures(templateExposure, exposure,
499 spatiallyVarying=self.config.doSpatiallyVarying,
500 doPreConvolve=self.config.doPreConvolve)
501 subtractedExposure = subtractRes.subtractedExposure
503 elif self.config.subtract.name ==
'al':
505 scienceSigmaOrig = sciencePsf.computeShape().getDeterminantRadius()
506 templateSigma = templateExposure.getPsf().computeShape().getDeterminantRadius()
514 if self.config.doPreConvolve:
517 srcMI = exposure.getMaskedImage()
518 destMI = srcMI.Factory(srcMI.getDimensions())
520 if self.config.useGaussianForPreConvolution:
522 kWidth, kHeight = sciencePsf.getLocalKernel().getDimensions()
523 preConvPsf = SingleGaussianPsf(kWidth, kHeight, scienceSigmaOrig)
528 exposure.setMaskedImage(destMI)
529 scienceSigmaPost = scienceSigmaOrig*math.sqrt(2)
531 scienceSigmaPost = scienceSigmaOrig
536 if self.config.doSelectSources:
537 if selectSources
is None:
538 self.log.
warn(
"Src product does not exist; running detection, measurement, selection")
540 selectSources = self.subtract.getSelectSources(
542 sigma=scienceSigmaPost,
543 doSmooth=
not self.doPreConvolve,
547 if self.config.doAddMetrics:
551 referenceFwhmPix=scienceSigmaPost*FwhmPerSigma,
552 targetFwhmPix=templateSigma*FwhmPerSigma))
559 kcQa = KernelCandidateQa(nparam)
560 selectSources = kcQa.addToSchema(selectSources)
561 if self.config.kernelSourcesFromRef:
563 astromRet = self.astrometer.loadAndMatch(exposure=exposure, sourceCat=selectSources)
564 matches = astromRet.matches
565 elif templateSources:
568 mc.findOnlyClosest =
False 572 raise RuntimeError(
"doSelectSources=True and kernelSourcesFromRef=False," 573 "but template sources not available. Cannot match science " 574 "sources with template sources. Run process* on data from " 575 "which templates are built.")
577 kernelSources = self.sourceSelector.
run(selectSources, exposure=exposure,
578 matches=matches).sourceCat
579 random.shuffle(kernelSources, random.random)
580 controlSources = kernelSources[::self.config.controlStepSize]
581 kernelSources = [k
for i, k
in enumerate(kernelSources)
582 if i % self.config.controlStepSize]
584 if self.config.doSelectDcrCatalog:
585 redSelector = DiaCatalogSourceSelectorTask(
586 DiaCatalogSourceSelectorConfig(grMin=self.sourceSelector.config.grMax,
588 redSources = redSelector.selectStars(exposure, selectSources, matches=matches).starCat
589 controlSources.extend(redSources)
591 blueSelector = DiaCatalogSourceSelectorTask(
592 DiaCatalogSourceSelectorConfig(grMin=-99.999,
593 grMax=self.sourceSelector.config.grMin))
594 blueSources = blueSelector.selectStars(exposure, selectSources,
595 matches=matches).starCat
596 controlSources.extend(blueSources)
598 if self.config.doSelectVariableCatalog:
599 varSelector = DiaCatalogSourceSelectorTask(
600 DiaCatalogSourceSelectorConfig(includeVariable=
True))
601 varSources = varSelector.selectStars(exposure, selectSources, matches=matches).starCat
602 controlSources.extend(varSources)
604 self.log.
info(
"Selected %d / %d sources for Psf matching (%d for control sample)" 605 % (len(kernelSources), len(selectSources), len(controlSources)))
609 if self.config.doUseRegister:
610 self.log.
info(
"Registering images")
612 if templateSources
is None:
616 templateSigma = templateExposure.getPsf().computeShape().getDeterminantRadius()
617 templateSources = self.subtract.getSelectSources(
626 wcsResults = self.fitAstrometry(templateSources, templateExposure, selectSources)
627 warpedExp = self.register.
warpExposure(templateExposure, wcsResults.wcs,
628 exposure.getWcs(), exposure.getBBox())
629 templateExposure = warpedExp
634 if self.config.doDebugRegister:
636 srcToMatch = {x.second.getId(): x.first
for x
in matches}
638 refCoordKey = wcsResults.matches[0].first.getTable().getCoordKey()
639 inCentroidKey = wcsResults.matches[0].second.getTable().getCentroidKey()
640 sids = [m.first.getId()
for m
in wcsResults.matches]
641 positions = [m.first.get(refCoordKey)
for m
in wcsResults.matches]
642 residuals = [m.first.get(refCoordKey).getOffsetFrom(wcsResults.wcs.pixelToSky(
643 m.second.get(inCentroidKey)))
for m
in wcsResults.matches]
644 allresids = dict(zip(sids, zip(positions, residuals)))
646 cresiduals = [m.first.get(refCoordKey).getTangentPlaneOffset(
647 wcsResults.wcs.pixelToSky(
648 m.second.get(inCentroidKey)))
for m
in wcsResults.matches]
649 colors = numpy.array([-2.5*numpy.log10(srcToMatch[x].get(
"g")) +
650 2.5*numpy.log10(srcToMatch[x].get(
"r")) 651 for x
in sids
if x
in srcToMatch.keys()])
652 dlong = numpy.array([r[0].asArcseconds()
for s, r
in zip(sids, cresiduals)
653 if s
in srcToMatch.keys()])
654 dlat = numpy.array([r[1].asArcseconds()
for s, r
in zip(sids, cresiduals)
655 if s
in srcToMatch.keys()])
656 idx1 = numpy.where(colors < self.sourceSelector.config.grMin)
657 idx2 = numpy.where((colors >= self.sourceSelector.config.grMin) &
658 (colors <= self.sourceSelector.config.grMax))
659 idx3 = numpy.where(colors > self.sourceSelector.config.grMax)
660 rms1Long = IqrToSigma*(
661 (numpy.percentile(dlong[idx1], 75) - numpy.percentile(dlong[idx1], 25)))
662 rms1Lat = IqrToSigma*(numpy.percentile(dlat[idx1], 75) -
663 numpy.percentile(dlat[idx1], 25))
664 rms2Long = IqrToSigma*(
665 (numpy.percentile(dlong[idx2], 75) - numpy.percentile(dlong[idx2], 25)))
666 rms2Lat = IqrToSigma*(numpy.percentile(dlat[idx2], 75) -
667 numpy.percentile(dlat[idx2], 25))
668 rms3Long = IqrToSigma*(
669 (numpy.percentile(dlong[idx3], 75) - numpy.percentile(dlong[idx3], 25)))
670 rms3Lat = IqrToSigma*(numpy.percentile(dlat[idx3], 75) -
671 numpy.percentile(dlat[idx3], 25))
672 self.log.
info(
"Blue star offsets'': %.3f %.3f, %.3f %.3f" %
673 (numpy.median(dlong[idx1]), rms1Long,
674 numpy.median(dlat[idx1]), rms1Lat))
675 self.log.
info(
"Green star offsets'': %.3f %.3f, %.3f %.3f" %
676 (numpy.median(dlong[idx2]), rms2Long,
677 numpy.median(dlat[idx2]), rms2Lat))
678 self.log.
info(
"Red star offsets'': %.3f %.3f, %.3f %.3f" %
679 (numpy.median(dlong[idx3]), rms3Long,
680 numpy.median(dlat[idx3]), rms3Lat))
682 self.metadata.add(
"RegisterBlueLongOffsetMedian", numpy.median(dlong[idx1]))
683 self.metadata.add(
"RegisterGreenLongOffsetMedian", numpy.median(dlong[idx2]))
684 self.metadata.add(
"RegisterRedLongOffsetMedian", numpy.median(dlong[idx3]))
685 self.metadata.add(
"RegisterBlueLongOffsetStd", rms1Long)
686 self.metadata.add(
"RegisterGreenLongOffsetStd", rms2Long)
687 self.metadata.add(
"RegisterRedLongOffsetStd", rms3Long)
689 self.metadata.add(
"RegisterBlueLatOffsetMedian", numpy.median(dlat[idx1]))
690 self.metadata.add(
"RegisterGreenLatOffsetMedian", numpy.median(dlat[idx2]))
691 self.metadata.add(
"RegisterRedLatOffsetMedian", numpy.median(dlat[idx3]))
692 self.metadata.add(
"RegisterBlueLatOffsetStd", rms1Lat)
693 self.metadata.add(
"RegisterGreenLatOffsetStd", rms2Lat)
694 self.metadata.add(
"RegisterRedLatOffsetStd", rms3Lat)
701 self.log.
info(
"Subtracting images")
702 subtractRes = self.subtract.subtractExposures(
703 templateExposure=templateExposure,
704 scienceExposure=exposure,
705 candidateList=kernelSources,
706 convolveTemplate=self.config.convolveTemplate,
707 doWarping=
not self.config.doUseRegister
709 subtractedExposure = subtractRes.subtractedExposure
711 if self.config.doDetection:
712 self.log.
info(
"Computing diffim PSF")
715 if not subtractedExposure.hasPsf():
716 if self.config.convolveTemplate:
717 subtractedExposure.setPsf(exposure.getPsf())
719 subtractedExposure.setPsf(templateExposure.getPsf())
726 if self.config.doDecorrelation
and self.config.doSubtract:
728 if preConvPsf
is not None:
729 preConvKernel = preConvPsf.getLocalKernel()
730 if self.config.convolveTemplate:
731 self.log.
info(
"Decorrelation after template image convolution")
732 decorrResult = self.decorrelate.
run(exposure, templateExposure,
734 subtractRes.psfMatchingKernel,
735 spatiallyVarying=self.config.doSpatiallyVarying,
736 preConvKernel=preConvKernel)
738 self.log.
info(
"Decorrelation after science image convolution")
739 decorrResult = self.decorrelate.
run(templateExposure, exposure,
741 subtractRes.psfMatchingKernel,
742 spatiallyVarying=self.config.doSpatiallyVarying,
743 preConvKernel=preConvKernel)
744 subtractedExposure = decorrResult.correctedExposure
748 if self.config.doDetection:
749 self.log.
info(
"Running diaSource detection")
751 mask = subtractedExposure.getMaskedImage().getMask()
752 mask &= ~(mask.getPlaneBitMask(
"DETECTED") | mask.getPlaneBitMask(
"DETECTED_NEGATIVE"))
754 table = afwTable.SourceTable.make(self.schema, idFactory)
755 table.setMetadata(self.algMetadata)
756 results = self.detection.
run(
758 exposure=subtractedExposure,
759 doSmooth=
not self.config.doPreConvolve
762 if self.config.doMerge:
763 fpSet = results.fpSets.positive
764 fpSet.merge(results.fpSets.negative, self.config.growFootprint,
765 self.config.growFootprint,
False)
767 fpSet.makeSources(diaSources)
768 self.log.
info(
"Merging detections into %d sources" % (len(diaSources)))
770 diaSources = results.sources
772 if self.config.doMeasurement:
773 newDipoleFitting = self.config.doDipoleFitting
774 self.log.
info(
"Running diaSource measurement: newDipoleFitting=%r", newDipoleFitting)
775 if not newDipoleFitting:
777 self.measurement.
run(diaSources, subtractedExposure)
780 if self.config.doSubtract
and 'matchedExposure' in subtractRes.getDict():
781 self.measurement.
run(diaSources, subtractedExposure, exposure,
782 subtractRes.matchedExposure)
784 self.measurement.
run(diaSources, subtractedExposure, exposure)
786 if self.config.doForcedMeasurement:
789 forcedSources = self.forcedMeasurement.generateMeasCat(
790 exposure, diaSources, subtractedExposure.getWcs())
791 self.forcedMeasurement.
run(forcedSources, exposure, diaSources, subtractedExposure.getWcs())
793 mapper.addMapping(forcedSources.schema.find(
"base_PsfFlux_instFlux")[0],
794 "ip_diffim_forced_PsfFlux_instFlux",
True)
795 mapper.addMapping(forcedSources.schema.find(
"base_PsfFlux_instFluxErr")[0],
796 "ip_diffim_forced_PsfFlux_instFluxErr",
True)
797 mapper.addMapping(forcedSources.schema.find(
"base_PsfFlux_area")[0],
798 "ip_diffim_forced_PsfFlux_area",
True)
799 mapper.addMapping(forcedSources.schema.find(
"base_PsfFlux_flag")[0],
800 "ip_diffim_forced_PsfFlux_flag",
True)
801 mapper.addMapping(forcedSources.schema.find(
"base_PsfFlux_flag_noGoodPixels")[0],
802 "ip_diffim_forced_PsfFlux_flag_noGoodPixels",
True)
803 mapper.addMapping(forcedSources.schema.find(
"base_PsfFlux_flag_edge")[0],
804 "ip_diffim_forced_PsfFlux_flag_edge",
True)
805 for diaSource, forcedSource
in zip(diaSources, forcedSources):
806 diaSource.assign(forcedSource, mapper)
809 if self.config.doMatchSources:
810 if selectSources
is not None:
812 matchRadAsec = self.config.diaSourceMatchRadius
813 matchRadPixel = matchRadAsec/exposure.getWcs().getPixelScale().asArcseconds()
816 srcMatchDict = dict([(srcMatch.second.getId(), srcMatch.first.getId())
for 817 srcMatch
in srcMatches])
818 self.log.
info(
"Matched %d / %d diaSources to sources" % (len(srcMatchDict),
821 self.log.
warn(
"Src product does not exist; cannot match with diaSources")
825 refAstromConfig = AstrometryConfig()
826 refAstromConfig.matcher.maxMatchDistArcSec = matchRadAsec
827 refAstrometer = AstrometryTask(refAstromConfig)
828 astromRet = refAstrometer.run(exposure=exposure, sourceCat=diaSources)
829 refMatches = astromRet.matches
830 if refMatches
is None:
831 self.log.
warn(
"No diaSource matches with reference catalog")
834 self.log.
info(
"Matched %d / %d diaSources to reference catalog" % (len(refMatches),
836 refMatchDict = dict([(refMatch.second.getId(), refMatch.first.getId())
for 837 refMatch
in refMatches])
840 for diaSource
in diaSources:
841 sid = diaSource.getId()
842 if sid
in srcMatchDict:
843 diaSource.set(
"srcMatchId", srcMatchDict[sid])
844 if sid
in refMatchDict:
845 diaSource.set(
"refMatchId", refMatchDict[sid])
847 if self.config.doAddMetrics
and self.config.doSelectSources:
848 self.log.
info(
"Evaluating metrics and control sample")
851 for cell
in subtractRes.kernelCellSet.getCellList():
852 for cand
in cell.begin(
False):
853 kernelCandList.append(cand)
856 basisList = kernelCandList[0].getKernel(KernelCandidateF.ORIG).getKernelList()
857 nparam = len(kernelCandList[0].getKernel(KernelCandidateF.ORIG).getKernelParameters())
860 diffimTools.sourceTableToCandidateList(controlSources,
861 subtractRes.warpedExposure, exposure,
862 self.config.subtract.kernel.active,
863 self.config.subtract.kernel.active.detectionConfig,
864 self.log, doBuild=
True, basisList=basisList))
866 KernelCandidateQa.apply(kernelCandList, subtractRes.psfMatchingKernel,
867 subtractRes.backgroundModel, dof=nparam)
868 KernelCandidateQa.apply(controlCandList, subtractRes.psfMatchingKernel,
869 subtractRes.backgroundModel)
871 if self.config.doDetection:
872 KernelCandidateQa.aggregate(selectSources, self.metadata, allresids, diaSources)
874 KernelCandidateQa.aggregate(selectSources, self.metadata, allresids)
876 self.runDebug(exposure, subtractRes, selectSources, kernelSources, diaSources)
877 return pipeBase.Struct(
878 subtractedExposure=subtractedExposure,
879 matchedExposure=subtractRes.matchedExposure,
880 subtractRes=subtractRes,
881 diaSources=diaSources,
882 selectSources=selectSources
def makeKernelBasisList(config, targetFwhmPix=None, referenceFwhmPix=None, basisDegGauss=None, metadata=None)
A mapping between the keys of two Schemas, used to copy data between them.
Parameters to control convolution.
template SourceMatchVector matchRaDec(SourceCatalog const &, lsst::geom::Angle, MatchControl const &)
Pass parameters to algorithms that match list of sources.
def run(self, skyInfo, tempExpRefList, imageScalerList, weightList, altMaskList=None, mask=None, supplementaryData=None)
void convolve(OutImageT &convolvedImage, InImageT const &inImage, KernelT const &kernel, bool doNormalize, bool doCopyEdge=false)
Old, deprecated version of convolve.
SourceMatchVector matchXy(SourceCatalog const &cat, double radius, bool symmetric)
Compute all tuples (s1,s2,d) where s1 != s2, s1 and s2 both belong to cat, and d, the distance betwee...