3737
3838__all__ = ["AlardLuptonSubtractConfig" , "AlardLuptonSubtractTask" ,
3939 "AlardLuptonPreconvolveSubtractConfig" , "AlardLuptonPreconvolveSubtractTask" ,
40+ "SimplifiedSubtractConfig" , "SimplifiedSubtractTask" ,
4041 "InsufficientKernelSourcesError" ]
4142
4243_dimensions = ("instrument" , "visit" , "detector" )
@@ -154,6 +155,24 @@ class AlardLuptonSubtractConnections(SubtractInputConnections, SubtractImageOutp
154155 pass
155156
156157
158+ class SimplifiedSubtractConnections (SubtractInputConnections , SubtractImageOutputConnections ):
159+ inputPsfMatchingKernel = connectionTypes .Input (
160+ doc = "Kernel used to PSF match the science and template images." ,
161+ dimensions = ("instrument" , "visit" , "detector" ),
162+ storageClass = "MatchingKernel" ,
163+ name = "{fakesType}{coaddName}Diff_psfMatchKernel" ,
164+ )
165+
166+ def __init__ (self , * , config = None ):
167+ super ().__init__ (config = config )
168+ del self .sources
169+ if config .useExistingKernel :
170+ del self .psfMatchingKernel
171+ del self .kernelSources
172+ else :
173+ del self .inputPsfMatchingKernel
174+
175+
157176class AlardLuptonSubtractBaseConfig (lsst .pex .config .Config ):
158177 makeKernel = lsst .pex .config .ConfigurableField (
159178 target = MakeKernelTask ,
@@ -209,6 +228,14 @@ class AlardLuptonSubtractBaseConfig(lsst.pex.config.Config):
209228 target = ScienceSourceSelectorTask ,
210229 doc = "Task to select sources to be used for PSF matching." ,
211230 )
231+ fallbackSourceSelector = lsst .pex .config .ConfigurableField (
232+ target = ScienceSourceSelectorTask ,
233+ doc = "Task to select sources to be used for PSF matching."
234+ "Used only if the kernel calculation fails and"
235+ "`allowKernelSourceDetection` is set. The fallback source detection"
236+ " will not include all of the same plugins as the original source "
237+ " detection, so not all of the same flags can be used." ,
238+ )
212239 detectionThreshold = lsst .pex .config .Field (
213240 dtype = float ,
214241 default = 10 ,
@@ -282,6 +309,14 @@ def setDefaults(self):
282309 self .sourceSelector .doSignalToNoise = True # apply signal to noise filter
283310 self .sourceSelector .signalToNoise .minimum = 10
284311 self .sourceSelector .signalToNoise .maximum = 500
312+ self .fallbackSourceSelector .doSkySources = False # Do not include sky sources
313+ self .fallbackSourceSelector .doSignalToNoise = True # apply signal to noise filter
314+ self .fallbackSourceSelector .signalToNoise .minimum = 10
315+ # The following two configs should not be necessary to be turned on for
316+ # PSF-matching, and the fallback kernel source selection will fail if
317+ # they are set since it does not run deblending.
318+ self .fallbackSourceSelector .doIsolated = False # Do not apply isolated star selection
319+ self .fallbackSourceSelector .doRequirePrimary = False # Do not apply primary flag selection
285320
286321
287322class AlardLuptonSubtractConfig (AlardLuptonSubtractBaseConfig , lsst .pipe .base .PipelineTaskConfig ,
@@ -308,6 +343,7 @@ def __init__(self, **kwargs):
308343 self .makeSubtask ("decorrelate" )
309344 self .makeSubtask ("makeKernel" )
310345 self .makeSubtask ("sourceSelector" )
346+ self .makeSubtask ("fallbackSourceSelector" )
311347 if self .config .doScaleVariance :
312348 self .makeSubtask ("scaleVariance" )
313349
@@ -541,17 +577,7 @@ def runMakeKernel(self, template, science, sources, convolveTemplate=True):
541577 if self .config .allowKernelSourceDetection and convolveTemplate :
542578 self .log .warning ("Error encountered trying to construct the matching kernel"
543579 f" Running source detection and retrying. { e } " )
544- kernelSize = self .makeKernel .makeKernelBasisList (
545- referenceFwhmPix , targetFwhmPix )[0 ].getWidth ()
546- sigmaToFwhm = 2 * np .log (2 * np .sqrt (2 ))
547- candidateList = self .makeKernel .makeCandidateList (reference , target , kernelSize ,
548- candidateList = None ,
549- sigma = targetFwhmPix / sigmaToFwhm )
550- kernelSources = self .makeKernel .selectKernelSources (reference , target ,
551- candidateList = candidateList ,
552- preconvolved = False ,
553- templateFwhmPix = referenceFwhmPix ,
554- scienceFwhmPix = targetFwhmPix )
580+ kernelSources = self .runKernelSourceDetection (template , science )
555581 kernelResult = self .makeKernel .run (reference , target , kernelSources ,
556582 preconvolved = False ,
557583 templateFwhmPix = referenceFwhmPix ,
@@ -571,6 +597,38 @@ def runMakeKernel(self, template, science, sources, convolveTemplate=True):
571597 psfMatchingKernel = kernelResult .psfMatchingKernel ,
572598 kernelSources = kernelSources )
573599
600+ def runKernelSourceDetection (self , template , science ):
601+ """Run detection on the science image and use the template mask plane
602+ to reject candidate sources.
603+
604+ Parameters
605+ ----------
606+ template : `lsst.afw.image.ExposureF`
607+ Template exposure, warped to match the science exposure.
608+ science : `lsst.afw.image.ExposureF`
609+ Science exposure to subtract from the template.
610+
611+ Returns
612+ -------
613+ kernelSources : `lsst.afw.table.SourceCatalog`
614+ Sources from the input catalog to use to construct the
615+ PSF-matching kernel.
616+ """
617+ kernelSize = self .makeKernel .makeKernelBasisList (
618+ self .templatePsfSize , self .sciencePsfSize )[0 ].getWidth ()
619+ sigmaToFwhm = 2 * np .log (2 * np .sqrt (2 ))
620+ candidateList = self .makeKernel .makeCandidateList (template , science , kernelSize ,
621+ candidateList = None ,
622+ sigma = self .sciencePsfSize / sigmaToFwhm )
623+ sources = self .makeKernel .selectKernelSources (template , science ,
624+ candidateList = candidateList ,
625+ preconvolved = False ,
626+ templateFwhmPix = self .templatePsfSize ,
627+ scienceFwhmPix = self .sciencePsfSize )
628+
629+ # return sources
630+ return self ._sourceSelector (sources , science .getBBox (), fallback = True )
631+
574632 def runConvolveTemplate (self , template , science , psfMatchingKernel , backgroundModel = None ):
575633 """Convolve the template image with a PSF-matching kernel and subtract
576634 from the science image.
@@ -850,7 +908,7 @@ def _convolveExposure(self, exposure, kernel, convolutionControl,
850908 else :
851909 return convolvedExposure [bbox ]
852910
853- def _sourceSelector (self , sources , bbox ):
911+ def _sourceSelector (self , sources , bbox , fallback = False ):
854912 """Select sources from a catalog that meet the selection criteria.
855913 The selection criteria include any configured parameters of the
856914 `sourceSelector` subtask, as well as distance from the edge if
@@ -875,8 +933,10 @@ def _sourceSelector(self, sources, bbox):
875933 If there are too few sources to compute the PSF matching kernel
876934 remaining after source selection.
877935 """
878-
879- selected = self .sourceSelector .selectSources (sources ).selected
936+ if fallback :
937+ selected = self .fallbackSourceSelector .selectSources (sources ).selected
938+ else :
939+ selected = self .sourceSelector .selectSources (sources ).selected
880940 if self .config .restrictKernelEdgeSources :
881941 rejectRadius = 2 * self .config .makeKernel .kernel .active .kernelSize
882942 bbox .grow (- rejectRadius )
@@ -1356,6 +1416,159 @@ def _shapeTest(exp1, exp2, fwhmExposureBuffer, fwhmExposureGrid):
13561416 return xTest | yTest
13571417
13581418
1419+ class SimplifiedSubtractConfig (AlardLuptonSubtractBaseConfig , lsst .pipe .base .PipelineTaskConfig ,
1420+ pipelineConnections = SimplifiedSubtractConnections ):
1421+ mode = lsst .pex .config .ChoiceField (
1422+ dtype = str ,
1423+ default = "convolveTemplate" ,
1424+ allowed = {"auto" : "Choose which image to convolve at runtime." ,
1425+ "convolveScience" : "Only convolve the science image." ,
1426+ "convolveTemplate" : "Only convolve the template image." },
1427+ doc = "Choose which image to convolve at runtime, or require that a specific image is convolved."
1428+ )
1429+ useExistingKernel = lsst .pex .config .Field (
1430+ dtype = bool ,
1431+ default = True ,
1432+ doc = "Use a pre-existing PSF matching kernel?"
1433+ "If False, source detection and measurement will be run."
1434+ )
1435+
1436+
1437+ class SimplifiedSubtractTask (AlardLuptonSubtractTask ):
1438+ """Compute the image difference of a science and template image using
1439+ the Alard & Lupton (1998) algorithm.
1440+ """
1441+ ConfigClass = SimplifiedSubtractConfig
1442+ _DefaultName = "simplifiedSubtract"
1443+
1444+ @timeMethod
1445+ def run (self , template , science , visitSummary = None , inputPsfMatchingKernel = None ):
1446+ """PSF match, subtract, and decorrelate two images.
1447+
1448+ Parameters
1449+ ----------
1450+ template : `lsst.afw.image.ExposureF`
1451+ Template exposure, warped to match the science exposure.
1452+ science : `lsst.afw.image.ExposureF`
1453+ Science exposure to subtract from the template.
1454+ visitSummary : `lsst.afw.table.ExposureCatalog`, optional
1455+ Exposure catalog with external calibrations to be applied. Catalog
1456+ uses the detector id for the catalog id, sorted on id for fast
1457+ lookup.
1458+
1459+ Returns
1460+ -------
1461+ results : `lsst.pipe.base.Struct`
1462+ ``difference`` : `lsst.afw.image.ExposureF`
1463+ Result of subtracting template and science.
1464+ ``matchedTemplate`` : `lsst.afw.image.ExposureF`
1465+ Warped and PSF-matched template exposure.
1466+ ``backgroundModel`` : `lsst.afw.math.Function2D`
1467+ Background model that was fit while solving for the
1468+ PSF-matching kernel
1469+ ``psfMatchingKernel`` : `lsst.afw.math.Kernel`
1470+ Kernel used to PSF-match the convolved image.
1471+ ``kernelSources` : `lsst.afw.table.SourceCatalog`
1472+ Sources detected on the science image that were used to
1473+ construct the PSF-matching kernel.
1474+
1475+ Raises
1476+ ------
1477+ RuntimeError
1478+ If an unsupported convolution mode is supplied.
1479+ RuntimeError
1480+ If there are too few sources to calculate the PSF matching kernel.
1481+ lsst.pipe.base.NoWorkFound
1482+ Raised if fraction of good pixels, defined as not having NO_DATA
1483+ set, is less then the configured requiredTemplateFraction
1484+ """
1485+ self ._prepareInputs (template , science , visitSummary = visitSummary )
1486+
1487+ convolveTemplate = self .chooseConvolutionMethod (template , science )
1488+
1489+ if self .config .useExistingKernel :
1490+ psfMatchingKernel = inputPsfMatchingKernel
1491+ backgroundModel = None
1492+ kernelSources = None
1493+ else :
1494+ kernelResult = self .runMakeKernel (template , science , convolveTemplate = convolveTemplate )
1495+ psfMatchingKernel = kernelResult .psfMatchingKernel
1496+ kernelSources = kernelResult .kernelSources
1497+ if self .config .doSubtractBackground :
1498+ backgroundModel = kernelResult .backgroundModel
1499+ else :
1500+ backgroundModel = None
1501+ if convolveTemplate :
1502+ subtractResults = self .runConvolveTemplate (template , science , psfMatchingKernel ,
1503+ backgroundModel = backgroundModel )
1504+ else :
1505+ subtractResults = self .runConvolveScience (template , science , psfMatchingKernel ,
1506+ backgroundModel = backgroundModel )
1507+ if kernelSources is not None :
1508+ subtractResults .kernelSources = kernelSources
1509+ return subtractResults
1510+
1511+ def runMakeKernel (self , template , science , convolveTemplate = True ):
1512+ """Construct the PSF-matching kernel.
1513+
1514+ Parameters
1515+ ----------
1516+ template : `lsst.afw.image.ExposureF`
1517+ Template exposure, warped to match the science exposure.
1518+ science : `lsst.afw.image.ExposureF`
1519+ Science exposure to subtract from the template.
1520+ sources : `lsst.afw.table.SourceCatalog`
1521+ Identified sources on the science exposure. This catalog is used to
1522+ select sources in order to perform the AL PSF matching on stamp
1523+ images around them.
1524+ convolveTemplate : `bool`, optional
1525+ Construct the matching kernel to convolve the template?
1526+
1527+ Returns
1528+ -------
1529+ results : `lsst.pipe.base.Struct`
1530+ ``backgroundModel`` : `lsst.afw.math.Function2D`
1531+ Background model that was fit while solving for the
1532+ PSF-matching kernel
1533+ ``psfMatchingKernel`` : `lsst.afw.math.Kernel`
1534+ Kernel used to PSF-match the convolved image.
1535+ ``kernelSources` : `lsst.afw.table.SourceCatalog`
1536+ Sources from the input catalog that were used to construct the
1537+ PSF-matching kernel.
1538+ """
1539+ if convolveTemplate :
1540+ reference = template
1541+ target = science
1542+ referenceFwhmPix = self .templatePsfSize
1543+ targetFwhmPix = self .sciencePsfSize
1544+ else :
1545+ reference = science
1546+ target = template
1547+ referenceFwhmPix = self .sciencePsfSize
1548+ targetFwhmPix = self .templatePsfSize
1549+ try :
1550+ # The try..except block catches any error, and raises
1551+ # NoWorkFound if the template coverage is insufficient. Otherwise,
1552+ # the original error is raised.
1553+ kernelSources = self .runKernelSourceDetection (template , science )
1554+ kernelResult = self .makeKernel .run (reference , target , kernelSources ,
1555+ preconvolved = False ,
1556+ templateFwhmPix = referenceFwhmPix ,
1557+ scienceFwhmPix = targetFwhmPix )
1558+ except (RuntimeError , lsst .pex .exceptions .Exception ) as e :
1559+ self .log .warning ("Failed to match template. Checking coverage" )
1560+ # Raise NoWorkFound if template fraction is insufficient
1561+ checkTemplateIsSufficient (template [science .getBBox ()], science , self .log ,
1562+ self .config .minTemplateFractionForExpectedSuccess ,
1563+ exceptionMessage = "Template coverage lower than expected to succeed."
1564+ f" Failure is tolerable: { e } " )
1565+ # checkTemplateIsSufficient did not raise NoWorkFound, so raise original exception
1566+ raise e
1567+ return lsst .pipe .base .Struct (backgroundModel = kernelResult .backgroundModel ,
1568+ psfMatchingKernel = kernelResult .psfMatchingKernel ,
1569+ kernelSources = kernelSources )
1570+
1571+
13591572def _interpolateImage (maskedImage , badMaskPlanes , fallbackValue = None ):
13601573 """Replace masked image pixels with interpolated values.
13611574
0 commit comments