32from .
import MakeKernelTask, DecorrelateALKernelTask
34__all__ = [
"AlardLuptonSubtractConfig",
"AlardLuptonSubtractTask"]
36_dimensions = (
"instrument",
"visit",
"detector")
37_defaultTemplates = {
"coaddName":
"deep",
"fakesType":
""}
41 dimensions=_dimensions,
42 defaultTemplates=_defaultTemplates):
43 template = connectionTypes.Input(
44 doc=
"Input warped template to subtract.",
45 dimensions=(
"instrument",
"visit",
"detector"),
46 storageClass=
"ExposureF",
47 name=
"{fakesType}{coaddName}Diff_templateExp"
49 science = connectionTypes.Input(
50 doc=
"Input science exposure to subtract from.",
51 dimensions=(
"instrument",
"visit",
"detector"),
52 storageClass=
"ExposureF",
53 name=
"{fakesType}calexp"
55 sources = connectionTypes.Input(
56 doc=
"Sources measured on the science exposure; "
57 "used to select sources for making the matching kernel.",
58 dimensions=(
"instrument",
"visit",
"detector"),
59 storageClass=
"SourceCatalog",
65 dimensions=_dimensions,
66 defaultTemplates=_defaultTemplates):
67 difference = connectionTypes.Output(
68 doc=
"Result of subtracting convolved template from science image.",
69 dimensions=(
"instrument",
"visit",
"detector"),
70 storageClass=
"ExposureF",
71 name=
"{fakesType}{coaddName}Diff_differenceTempExp",
73 matchedTemplate = connectionTypes.Output(
74 doc=
"Warped and PSF-matched template used to create `subtractedExposure`.",
75 dimensions=(
"instrument",
"visit",
"detector"),
76 storageClass=
"ExposureF",
77 name=
"{fakesType}{coaddName}Diff_matchedExp",
86 pipelineConnections=AlardLuptonSubtractConnections):
90 allowed={
"auto":
"Choose which image to convolve at runtime.",
91 "convolveScience":
"Only convolve the science image.",
92 "convolveTemplate":
"Only convolve the template image."},
93 doc=
"Choose which image to convolve at runtime, or require that a specific image is convolved."
96 target=MakeKernelTask,
97 doc=
"Task to construct a matching kernel for convolution.",
102 doc=
"Perform diffim decorrelation to undo pixel correlation due to A&L "
103 "kernel convolution? If True, also update the diffim PSF."
106 target=DecorrelateALKernelTask,
107 doc=
"Task to decorrelate the image difference.",
112 doc=
"Abort task if template covers less than this fraction of pixels."
113 " Setting to 0 will always attempt image subtraction."
118 doc=
"Scale variance of the image difference?"
121 target=ScaleVarianceTask,
122 doc=
"Subtask to rescale the variance of the template to the statistically expected level."
125 doc=
"Subtract the background fit when solving the kernel?",
133 doc=
"Set up and run diffim using settings that ensure the results"
134 "are compatible with the old version in pipe_tasks.",
135 deprecated=
"This option is only for backwards compatibility purposes"
136 " and will be removed after v24.",
142 self.
makeKernelmakeKernel.kernel.active.spatialKernelOrder = 1
143 self.
makeKernelmakeKernel.kernel.active.spatialBgOrder = 2
147 self.
modemode =
"convolveTemplate"
151 """Compute the image difference of a science and template image using
152 the Alard & Lupton (1998) algorithm.
154 ConfigClass = AlardLuptonSubtractConfig
155 _DefaultName = "alardLuptonSubtract"
159 self.makeSubtask(
"decorrelate")
160 self.makeSubtask(
"makeKernel")
161 if self.config.doScaleVariance:
162 self.makeSubtask(
"scaleVariance")
170 def run(self, template, science, sources):
171 """PSF match, subtract, and decorrelate two images.
175 template : `lsst.afw.image.ExposureF`
176 Template exposure, warped to match the science exposure.
177 science : `lsst.afw.image.ExposureF`
178 Science exposure to subtract from the template.
180 Identified sources on the science exposure. This catalog
is used to
181 select sources
in order to perform the AL PSF matching on stamp
186 results : `lsst.pipe.base.Struct`
187 ``difference`` : `lsst.afw.image.ExposureF`
188 Result of subtracting template
and science.
189 ``matchedTemplate`` : `lsst.afw.image.ExposureF`
190 Warped
and PSF-matched template exposure.
191 ``backgroundModel`` : `lsst.afw.math.Chebyshev1Function2D`
192 Background model that was fit
while solving
for the PSF-matching kernel
194 Kernel used to PSF-match the convolved image.
199 If an unsupported convolution mode
is supplied.
200 lsst.pipe.base.NoWorkFound
201 Raised
if fraction of good pixels, defined
as not having NO_DATA
202 set,
is less then the configured requiredTemplateFraction
206 requiredTemplateFraction=self.config.requiredTemplateFraction)
207 if self.config.forceCompatibility:
210 self.log.
warning(
"Running with `config.forceCompatibility=True`")
214 self.log.
info(
"Science PSF size: %f", sciencePsfSize)
215 self.log.
info(
"Template PSF size: %f", templatePsfSize)
216 if self.config.mode ==
"auto":
217 if sciencePsfSize < templatePsfSize:
218 self.log.
info(
"Template PSF size is greater: convolving science image.")
219 convolveTemplate =
False
221 self.log.
info(
"Science PSF size is greater: convolving template image.")
222 convolveTemplate =
True
223 elif self.config.mode ==
"convolveTemplate":
224 self.log.
info(
"`convolveTemplate` is set: convolving template image.")
225 convolveTemplate =
True
226 elif self.config.mode ==
"convolveScience":
227 self.log.
info(
"`convolveScience` is set: convolving science image.")
228 convolveTemplate =
False
230 raise RuntimeError(
"Cannot handle AlardLuptonSubtract mode: %s", self.config.mode)
232 if self.config.doScaleVariance
and ~self.config.forceCompatibility:
236 templateVarFactor = self.scaleVariance.
run(template.maskedImage)
237 sciVarFactor = self.scaleVariance.
run(science.maskedImage)
238 self.log.
info(
"Template variance scaling factor: %.2f", templateVarFactor)
239 self.metadata.add(
"scaleTemplateVarianceFactor", templateVarFactor)
240 self.log.
info(
"Science variance scaling factor: %.2f", sciVarFactor)
241 self.metadata.add(
"scaleScienceVarianceFactor", sciVarFactor)
243 kernelSources = self.makeKernel.selectKernelSources(template, science,
244 candidateList=sources,
247 subtractResults = self.
runConvolveTemplaterunConvolveTemplate(template, science, kernelSources)
249 subtractResults = self.
runConvolveSciencerunConvolveScience(template, science, kernelSources)
251 if self.config.doScaleVariance
and self.config.forceCompatibility:
253 diffimVarFactor = self.scaleVariance.
run(subtractResults.difference.maskedImage)
254 self.log.
info(
"Diffim variance scaling factor: %.2f", diffimVarFactor)
255 self.metadata.add(
"scaleDiffimVarianceFactor", diffimVarFactor)
257 return subtractResults
260 """Convolve the template image with a PSF-matching kernel and subtract
261 from the science image.
265 template : `lsst.afw.image.ExposureF`
266 Template exposure, warped to match the science exposure.
267 science : `lsst.afw.image.ExposureF`
268 Science exposure to subtract
from the template.
270 Identified sources on the science exposure. This catalog
is used to
271 select sources
in order to perform the AL PSF matching on stamp
276 results : `lsst.pipe.base.Struct`
278 ``difference`` : `lsst.afw.image.ExposureF`
279 Result of subtracting template
and science.
280 ``matchedTemplate`` : `lsst.afw.image.ExposureF`
281 Warped
and PSF-matched template exposure.
282 ``backgroundModel`` : `lsst.afw.math.Chebyshev1Function2D`
283 Background model that was fit
while solving
for the PSF-matching kernel
285 Kernel used to PSF-match the template to the science image.
287 if self.config.forceCompatibility:
290 template = template[science.getBBox()]
291 kernelResult = self.makeKernel.
run(template, science, sources, preconvolved=
False)
293 matchedTemplate = self.
_convolveExposure_convolveExposure(template, kernelResult.psfMatchingKernel,
295 bbox=science.getBBox(),
297 photoCalib=science.getPhotoCalib())
298 difference = _subtractImages(science, matchedTemplate,
299 backgroundModel=(kernelResult.backgroundModel
300 if self.config.doSubtractBackground
else None))
301 correctedExposure = self.
finalizefinalize(template, science, difference, kernelResult.psfMatchingKernel,
302 templateMatched=
True)
304 return lsst.pipe.base.Struct(difference=correctedExposure,
305 matchedTemplate=matchedTemplate,
306 matchedScience=science,
307 backgroundModel=kernelResult.backgroundModel,
308 psfMatchingKernel=kernelResult.psfMatchingKernel)
311 """Convolve the science image with a PSF-matching kernel and subtract the template image.
315 template : `lsst.afw.image.ExposureF`
316 Template exposure, warped to match the science exposure.
317 science : `lsst.afw.image.ExposureF`
318 Science exposure to subtract from the template.
320 Identified sources on the science exposure. This catalog
is used to
321 select sources
in order to perform the AL PSF matching on stamp
326 results : `lsst.pipe.base.Struct`
328 ``difference`` : `lsst.afw.image.ExposureF`
329 Result of subtracting template
and science.
330 ``matchedTemplate`` : `lsst.afw.image.ExposureF`
331 Warped template exposure. Note that
in this case, the template
332 is not PSF-matched to the science image.
333 ``backgroundModel`` : `lsst.afw.math.Chebyshev1Function2D`
334 Background model that was fit
while solving
for the PSF-matching kernel
336 Kernel used to PSF-match the science image to the template.
338 if self.config.forceCompatibility:
341 template = template[science.getBBox()]
342 kernelResult = self.makeKernel.
run(science, template, sources, preconvolved=
False)
343 modelParams = kernelResult.backgroundModel.getParameters()
345 kernelResult.backgroundModel.setParameters([-p
for p
in modelParams])
347 kernelImage = lsst.afw.image.ImageD(kernelResult.psfMatchingKernel.getDimensions())
348 norm = kernelResult.psfMatchingKernel.computeImage(kernelImage, doNormalize=
False)
350 matchedScience = self.
_convolveExposure_convolveExposure(science, kernelResult.psfMatchingKernel,
355 matchedScience.maskedImage /= norm
356 matchedTemplate = template.clone()[science.getBBox()]
357 matchedTemplate.maskedImage /= norm
358 matchedTemplate.setPhotoCalib(science.getPhotoCalib())
360 difference = _subtractImages(matchedScience, matchedTemplate,
361 backgroundModel=(kernelResult.backgroundModel
362 if self.config.doSubtractBackground
else None))
364 correctedExposure = self.
finalizefinalize(template, science, difference, kernelResult.psfMatchingKernel,
365 templateMatched=
False)
367 return lsst.pipe.base.Struct(difference=correctedExposure,
368 matchedTemplate=matchedTemplate,
369 matchedScience=matchedScience,
370 backgroundModel=kernelResult.backgroundModel,
371 psfMatchingKernel=kernelResult.psfMatchingKernel,)
373 def finalize(self, template, science, difference, kernel,
374 templateMatched=True,
377 spatiallyVarying=False):
378 """Decorrelate the difference image to undo the noise correlations
379 caused by convolution.
383 template : `lsst.afw.image.ExposureF`
384 Template exposure, warped to match the science exposure.
385 science : `lsst.afw.image.ExposureF`
386 Science exposure to subtract from the template.
387 difference : `lsst.afw.image.ExposureF`
388 Result of subtracting template
and science.
390 An (optionally spatially-varying) PSF matching kernel
391 templateMatched : `bool`, optional
392 Was the template PSF-matched to the science image?
393 preConvMode : `bool`, optional
394 Was the science image preconvolved
with its own PSF
395 before PSF matching the template?
397 If
not `
None`, then the science image was pre-convolved
with
398 (the reflection of) this kernel. Must be normalized to sum to 1.
399 spatiallyVarying : `bool`, optional
400 Compute the decorrelation kernel spatially varying across the image?
404 correctedExposure : `lsst.afw.image.ExposureF`
405 The decorrelated image difference.
409 mask = difference.mask
410 mask &= ~(mask.getPlaneBitMask(
"DETECTED") | mask.getPlaneBitMask(
"DETECTED_NEGATIVE"))
412 if self.config.doDecorrelation:
413 self.log.
info(
"Decorrelating image difference.")
414 correctedExposure = self.decorrelate.
run(science, template[science.getBBox()], difference, kernel,
415 templateMatched=templateMatched,
416 preConvMode=preConvMode,
417 preConvKernel=preConvKernel,
418 spatiallyVarying=spatiallyVarying).correctedExposure
420 self.log.
info(
"NOT decorrelating image difference.")
421 correctedExposure = difference
422 return correctedExposure
425 def _validateExposures(template, science):
426 """Check that the WCS of the two Exposures match, and the template bbox
427 contains the science bbox.
431 template : `lsst.afw.image.ExposureF`
432 Template exposure, warped to match the science exposure.
433 science : `lsst.afw.image.ExposureF`
434 Science exposure to subtract from the template.
439 Raised
if the WCS of the template
is not equal to the science WCS,
440 or if the science image
is not fully contained
in the template
443 assert template.wcs == science.wcs,\
444 "Template and science exposure WCS are not identical."
445 templateBBox = template.getBBox()
446 scienceBBox = science.getBBox()
448 assert templateBBox.contains(scienceBBox),\
449 "Template bbox does not contain all of the science image."
452 def _convolveExposure(exposure, kernel, convolutionControl,
456 """Convolve an exposure with the given kernel.
460 exposure : `lsst.afw.Exposure`
461 exposure to convolve.
463 PSF matching kernel computed in the ``makeKernel`` subtask.
465 Configuration
for convolve algorithm.
467 Bounding box to trim the convolved exposure to.
469 Point spread function (PSF) to set
for the convolved exposure.
471 Photometric calibration of the convolved exposure.
475 convolvedExp : `lsst.afw.Exposure`
478 convolvedExposure = exposure.clone()
480 convolvedExposure.setPsf(psf)
481 if photoCalib
is not None:
482 convolvedExposure.setPhotoCalib(photoCalib)
483 convolvedImage = lsst.afw.image.MaskedImageF(exposure.getBBox())
485 convolvedExposure.setMaskedImage(convolvedImage)
487 return convolvedExposure
489 return convolvedExposure[bbox]
493 """Raise NoWorkFound if template coverage < requiredTemplateFraction
497 templateExposure : `lsst.afw.image.ExposureF`
498 The template exposure to check
500 Logger for printing output.
501 requiredTemplateFraction : `float`, optional
502 Fraction of pixels of the science image required to have coverage
507 lsst.pipe.base.NoWorkFound
508 Raised
if fraction of good pixels, defined
as not having NO_DATA
509 set,
is less then the configured requiredTemplateFraction
513 pixNoData = np.count_nonzero(templateExposure.mask.array
514 & templateExposure.mask.getPlaneBitMask(
'NO_DATA'))
515 pixGood = templateExposure.getBBox().getArea() - pixNoData
516 logger.info(
"template has %d good pixels (%.1f%%)", pixGood,
517 100*pixGood/templateExposure.getBBox().getArea())
519 if pixGood/templateExposure.getBBox().getArea() < requiredTemplateFraction:
520 message = (
"Insufficient Template Coverage. (%.1f%% < %.1f%%) Not attempting subtraction. "
521 "To force subtraction, set config requiredTemplateFraction=0." % (
522 100*pixGood/templateExposure.getBBox().getArea(),
523 100*requiredTemplateFraction))
524 raise lsst.pipe.base.NoWorkFound(message)
527def _subtractImages(science, template, backgroundModel=None):
528 """Subtract template from science, propagating relevant metadata.
532 science : `lsst.afw.Exposure`
533 The input science image.
534 template : `lsst.afw.Exposure`
535 The template to subtract from the science image.
536 backgroundModel : `lsst.afw.MaskedImage`, optional
537 Differential background model
541 difference : `lsst.afw.Exposure`
542 The subtracted image.
544 difference = science.clone()
545 if backgroundModel
is not None:
546 difference.maskedImage -= backgroundModel
547 difference.maskedImage -= template.maskedImage
A polymorphic base class for representing an image's Point Spread Function.
The photometric calibration of an exposure.
Parameters to control convolution.
Kernels are used for convolution with MaskedImages and (eventually) Images.
A kernel that is a linear combination of fixed basis kernels.
An integer coordinate rectangle.
def finalize(self, template, science, difference, kernel, templateMatched=True, preConvMode=False, preConvKernel=None, spatiallyVarying=False)
def _convolveExposure(exposure, kernel, convolutionControl, bbox=None, psf=None, photoCalib=None)
def runConvolveScience(self, template, science, sources)
def _validateExposures(template, science)
def run(self, template, science, sources)
def runConvolveTemplate(self, template, science, sources)
def __init__(self, **kwargs)
This static class includes a variety of methods for interacting with the the logging module.
Backwards-compatibility support for depersisting the old Calib (FluxMag0/FluxMag0Err) objects.
void convolve(OutImageT &convolvedImage, InImageT const &inImage, KernelT const &kernel, ConvolutionControl const &convolutionControl=ConvolutionControl())
Convolve an Image or MaskedImage with a Kernel, setting pixels of an existing output image.
def checkTemplateIsSufficient(templateExposure, logger, requiredTemplateFraction=0.)
Fit spatial kernel using approximate fluxes for candidates, and solving a linear system of equations.