22from astropy
import units
as u
35from .
import MakeKernelTask, DecorrelateALKernelTask
36from lsst.utils.timer
import timeMethod
38__all__ = [
"AlardLuptonSubtractConfig",
"AlardLuptonSubtractTask",
39 "AlardLuptonPreconvolveSubtractConfig",
"AlardLuptonPreconvolveSubtractTask",
40 "SimplifiedSubtractConfig",
"SimplifiedSubtractTask",
41 "InsufficientKernelSourcesError"]
43_dimensions = (
"instrument",
"visit",
"detector")
44_defaultTemplates = {
"coaddName":
"deep",
"fakesType":
""}
48 """Raised when there are too few sources to calculate the PSF matching
52 msg = (f
"Only {nSources} sources were selected for PSF matching,"
53 f
" but {nRequired} are required.")
66 dimensions=_dimensions,
67 defaultTemplates=_defaultTemplates):
68 template = connectionTypes.Input(
69 doc=
"Input warped template to subtract.",
70 dimensions=(
"instrument",
"visit",
"detector"),
71 storageClass=
"ExposureF",
72 name=
"{fakesType}{coaddName}Diff_templateExp"
74 science = connectionTypes.Input(
75 doc=
"Input science exposure to subtract from.",
76 dimensions=(
"instrument",
"visit",
"detector"),
77 storageClass=
"ExposureF",
78 name=
"{fakesType}calexp"
80 sources = connectionTypes.Input(
81 doc=
"Sources measured on the science exposure; "
82 "used to select sources for making the matching kernel.",
83 dimensions=(
"instrument",
"visit",
"detector"),
84 storageClass=
"SourceCatalog",
87 visitSummary = connectionTypes.Input(
88 doc=(
"Per-visit catalog with final calibration objects. "
89 "These catalogs use the detector id for the catalog id, "
90 "sorted on id for fast lookup."),
91 dimensions=(
"instrument",
"visit"),
92 storageClass=
"ExposureCatalog",
93 name=
"finalVisitSummary",
98 if not config.doApplyExternalCalibrations:
103 dimensions=_dimensions,
104 defaultTemplates=_defaultTemplates):
105 difference = connectionTypes.Output(
106 doc=
"Result of subtracting convolved template from science image.",
107 dimensions=(
"instrument",
"visit",
"detector"),
108 storageClass=
"ExposureF",
109 name=
"{fakesType}{coaddName}Diff_differenceTempExp",
111 matchedTemplate = connectionTypes.Output(
112 doc=
"Warped and PSF-matched template used to create `subtractedExposure`.",
113 dimensions=(
"instrument",
"visit",
"detector"),
114 storageClass=
"ExposureF",
115 name=
"{fakesType}{coaddName}Diff_matchedExp",
117 psfMatchingKernel = connectionTypes.Output(
118 doc=
"Kernel used to PSF match the science and template images.",
119 dimensions=(
"instrument",
"visit",
"detector"),
120 storageClass=
"MatchingKernel",
121 name=
"{fakesType}{coaddName}Diff_psfMatchKernel",
123 kernelSources = connectionTypes.Output(
124 doc=
"Final selection of sources used for psf matching.",
125 dimensions=(
"instrument",
"visit",
"detector"),
126 storageClass=
"SourceCatalog",
127 name=
"{fakesType}{coaddName}Diff_psfMatchSources"
132 dimensions=_dimensions,
133 defaultTemplates=_defaultTemplates):
134 scoreExposure = connectionTypes.Output(
135 doc=
"The maximum likelihood image, used for the detection of diaSources.",
136 dimensions=(
"instrument",
"visit",
"detector"),
137 storageClass=
"ExposureF",
138 name=
"{fakesType}{coaddName}Diff_scoreExp",
140 psfMatchingKernel = connectionTypes.Output(
141 doc=
"Kernel used to PSF match the science and template images.",
142 dimensions=(
"instrument",
"visit",
"detector"),
143 storageClass=
"MatchingKernel",
144 name=
"{fakesType}{coaddName}Diff_psfScoreMatchKernel",
146 kernelSources = connectionTypes.Output(
147 doc=
"Final selection of sources used for psf matching.",
148 dimensions=(
"instrument",
"visit",
"detector"),
149 storageClass=
"SourceCatalog",
150 name=
"{fakesType}{coaddName}Diff_psfScoreMatchSources"
158class 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",
169 if config.useExistingKernel:
178 target=MakeKernelTask,
179 doc=
"Task to construct a matching kernel for convolution.",
184 doc=
"Perform diffim decorrelation to undo pixel correlation due to A&L "
185 "kernel convolution? If True, also update the diffim PSF."
188 target=DecorrelateALKernelTask,
189 doc=
"Task to decorrelate the image difference.",
194 doc=
"Raise NoWorkFound and do not attempt image subtraction if template covers less than this "
195 " fraction of pixels. Setting to 0 will always attempt image subtraction."
200 doc=
"Raise NoWorkFound if PSF-matching fails and template covers less than this fraction of pixels."
201 " If the fraction of pixels covered by the template is less than this value (and greater than"
202 " requiredTemplateFraction) this task is attempted but failure is anticipated and tolerated."
207 doc=
"Scale variance of the image difference?"
210 target=ScaleVarianceTask,
211 doc=
"Subtask to rescale the variance of the template to the statistically expected level."
214 doc=
"Subtract the background fit when solving the kernel? "
215 "It is generally better to instead subtract the background in detectAndMeasure.",
221 "Replace science Exposure's calibration objects with those"
222 " in visitSummary. Ignored if `doApplyFinalizedPsf is True."
228 target=ScienceSourceSelectorTask,
229 doc=
"Task to select sources to be used for PSF matching.",
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.",
242 doc=
"Minimum signal to noise ratio of detected sources "
243 "to use for calculating the PSF matching kernel.",
244 deprecated=
"No longer used. Will be removed after v30"
249 doc=
"Maximum signal to noise ratio of detected sources "
250 "to use for calculating the PSF matching kernel.",
251 deprecated=
"No longer used. Will be removed after v30"
256 doc=
"Exclude sources close to the edge from the kernel calculation?"
261 doc=
"Maximum number of sources to use for calculating the PSF matching kernel."
262 "Set to -1 to disable."
267 doc=
"Minimum number of sources needed for calculating the PSF matching kernel."
271 default=(
"NO_DATA",
"BAD",
"SAT",
"EDGE",
"FAKE"),
272 doc=
"Mask planes to exclude when selecting sources for PSF matching.",
273 deprecated=
"No longer used. Will be removed after v30"
277 default=(
"NO_DATA",
"BAD",
"SAT",
"EDGE"),
278 doc=
"Mask planes to interpolate over."
282 default=(
"NO_DATA",
"BAD",),
283 doc=
"Mask planes from the template to propagate to the image difference."
287 default=(
"SAT",
"INJECTED",
"INJECTED_CORE",),
288 doc=
"Mask planes from the template to propagate to the image difference"
289 "with '_TEMPLATE' appended to the name."
294 doc=
"Re-run source detection for kernel candidates if an error is"
295 " encountered while calculating the matching kernel."
302 self.
makeKernel.kernel.active.fitForBackground =
True
303 self.
makeKernel.kernel.active.spatialKernelOrder = 1
304 self.
makeKernel.kernel.active.spatialBgOrder = 2
323 pipelineConnections=AlardLuptonSubtractConnections):
326 default=
"convolveTemplate",
327 allowed={
"auto":
"Choose which image to convolve at runtime.",
328 "convolveScience":
"Only convolve the science image.",
329 "convolveTemplate":
"Only convolve the template image."},
330 doc=
"Choose which image to convolve at runtime, or require that a specific image is convolved."
335 """Compute the image difference of a science and template image using
336 the Alard & Lupton (1998) algorithm.
338 ConfigClass = AlardLuptonSubtractConfig
339 _DefaultName =
"alardLuptonSubtract"
343 self.makeSubtask(
"decorrelate")
344 self.makeSubtask(
"makeKernel")
345 self.makeSubtask(
"sourceSelector")
346 self.makeSubtask(
"fallbackSourceSelector")
347 if self.config.doScaleVariance:
348 self.makeSubtask(
"scaleVariance")
357 """Replace calibrations (psf, and ApCorrMap) on this exposure with
362 exposure : `lsst.afw.image.exposure.Exposure`
363 Input exposure to adjust calibrations.
364 visitSummary : `lsst.afw.table.ExposureCatalog`
365 Exposure catalog with external calibrations to be applied. Catalog
366 uses the detector id for the catalog id, sorted on id for fast
371 exposure : `lsst.afw.image.exposure.Exposure`
372 Exposure with adjusted calibrations.
374 detectorId = exposure.info.getDetector().getId()
376 row = visitSummary.find(detectorId)
378 self.
log.warning(
"Detector id %s not found in external calibrations catalog; "
379 "Using original calibrations.", detectorId)
382 apCorrMap = row.getApCorrMap()
384 self.
log.warning(
"Detector id %s has None for psf in "
385 "external calibrations catalog; Using original psf and aperture correction.",
387 elif apCorrMap
is None:
388 self.
log.warning(
"Detector id %s has None for apCorrMap in "
389 "external calibrations catalog; Using original psf and aperture correction.",
393 exposure.info.setApCorrMap(apCorrMap)
398 def run(self, template, science, sources, visitSummary=None):
399 """PSF match, subtract, and decorrelate two images.
403 template : `lsst.afw.image.ExposureF`
404 Template exposure, warped to match the science exposure.
405 science : `lsst.afw.image.ExposureF`
406 Science exposure to subtract from the template.
407 sources : `lsst.afw.table.SourceCatalog`
408 Identified sources on the science exposure. This catalog is used to
409 select sources in order to perform the AL PSF matching on stamp
411 visitSummary : `lsst.afw.table.ExposureCatalog`, optional
412 Exposure catalog with external calibrations to be applied. Catalog
413 uses the detector id for the catalog id, sorted on id for fast
418 results : `lsst.pipe.base.Struct`
419 ``difference`` : `lsst.afw.image.ExposureF`
420 Result of subtracting template and science.
421 ``matchedTemplate`` : `lsst.afw.image.ExposureF`
422 Warped and PSF-matched template exposure.
423 ``backgroundModel`` : `lsst.afw.math.Function2D`
424 Background model that was fit while solving for the
426 ``psfMatchingKernel`` : `lsst.afw.math.Kernel`
427 Kernel used to PSF-match the convolved image.
428 ``kernelSources` : `lsst.afw.table.SourceCatalog`
429 Sources from the input catalog that were used to construct the
435 If an unsupported convolution mode is supplied.
437 If there are too few sources to calculate the PSF matching kernel.
438 lsst.pipe.base.NoWorkFound
439 Raised if fraction of good pixels, defined as not having NO_DATA
440 set, is less then the configured requiredTemplateFraction
448 kernelResult = self.
runMakeKernel(template, science, selectSources,
449 convolveTemplate=convolveTemplate)
450 if self.config.doSubtractBackground:
451 backgroundModel = kernelResult.backgroundModel
453 backgroundModel =
None
455 subtractResults = self.
runConvolveTemplate(template, science, kernelResult.psfMatchingKernel,
456 backgroundModel=backgroundModel)
458 subtractResults = self.
runConvolveScience(template, science, kernelResult.psfMatchingKernel,
459 backgroundModel=backgroundModel)
460 subtractResults.kernelSources = kernelResult.kernelSources
462 metrics = computeDifferenceImageMetrics(science, subtractResults.difference, sources)
464 self.metadata[
"differenceFootprintRatioMean"] = metrics.differenceFootprintRatioMean
465 self.metadata[
"differenceFootprintRatioStdev"] = metrics.differenceFootprintRatioStdev
466 self.metadata[
"differenceFootprintSkyRatioMean"] = metrics.differenceFootprintSkyRatioMean
467 self.metadata[
"differenceFootprintSkyRatioStdev"] = metrics.differenceFootprintSkyRatioStdev
468 self.
log.info(
"Mean, stdev of ratio of difference to science "
469 "pixels in star footprints: %5.4f, %5.4f",
470 self.metadata[
"differenceFootprintRatioMean"],
471 self.metadata[
"differenceFootprintRatioStdev"])
473 return subtractResults
476 """Determine whether the template should be convolved with the PSF
481 template : `lsst.afw.image.ExposureF`
482 Template exposure, warped to match the science exposure.
483 science : `lsst.afw.image.ExposureF`
484 Science exposure to subtract from the template.
488 convolveTemplate : `bool`
489 Convolve the template to match the two images?
494 If an unsupported convolution mode is supplied.
496 if self.config.mode ==
"auto":
499 fwhmExposureBuffer=self.config.makeKernel.fwhmExposureBuffer,
500 fwhmExposureGrid=self.config.makeKernel.fwhmExposureGrid)
503 self.
log.info(
"Average template PSF size is greater, "
504 "but science PSF greater in one dimension: convolving template image.")
506 self.
log.info(
"Science PSF size is greater: convolving template image.")
508 self.
log.info(
"Template PSF size is greater: convolving science image.")
509 elif self.config.mode ==
"convolveTemplate":
510 self.
log.info(
"`convolveTemplate` is set: convolving template image.")
511 convolveTemplate =
True
512 elif self.config.mode ==
"convolveScience":
513 self.
log.info(
"`convolveScience` is set: convolving science image.")
514 convolveTemplate =
False
516 raise RuntimeError(
"Cannot handle AlardLuptonSubtract mode: %s", self.config.mode)
517 return convolveTemplate
520 """Construct the PSF-matching kernel.
524 template : `lsst.afw.image.ExposureF`
525 Template exposure, warped to match the science exposure.
526 science : `lsst.afw.image.ExposureF`
527 Science exposure to subtract from the template.
528 sources : `lsst.afw.table.SourceCatalog`
529 Identified sources on the science exposure. This catalog is used to
530 select sources in order to perform the AL PSF matching on stamp
532 convolveTemplate : `bool`, optional
533 Construct the matching kernel to convolve the template?
537 results : `lsst.pipe.base.Struct`
538 ``backgroundModel`` : `lsst.afw.math.Function2D`
539 Background model that was fit while solving for the
541 ``psfMatchingKernel`` : `lsst.afw.math.Kernel`
542 Kernel used to PSF-match the convolved image.
543 ``kernelSources` : `lsst.afw.table.SourceCatalog`
544 Sources from the input catalog that were used to construct the
567 kernelSources = self.makeKernel.selectKernelSources(reference, target,
568 candidateList=sources,
570 templateFwhmPix=referenceFwhmPix,
571 scienceFwhmPix=targetFwhmPix)
572 kernelResult = self.makeKernel.run(reference, target, kernelSources,
574 templateFwhmPix=referenceFwhmPix,
575 scienceFwhmPix=targetFwhmPix)
576 except Exception
as e:
577 if self.config.allowKernelSourceDetection
and convolveTemplate:
578 self.
log.warning(
"Error encountered trying to construct the matching kernel"
579 f
" Running source detection and retrying. {e}")
581 kernelResult = self.makeKernel.run(reference, target, kernelSources,
583 templateFwhmPix=referenceFwhmPix,
584 scienceFwhmPix=targetFwhmPix)
588 self.
log.warning(
"Failed to match template. Checking coverage")
591 self.config.minTemplateFractionForExpectedSuccess,
592 exceptionMessage=
"Template coverage lower than expected to succeed."
593 f
" Failure is tolerable: {e}")
596 return lsst.pipe.base.Struct(backgroundModel=kernelResult.backgroundModel,
597 psfMatchingKernel=kernelResult.psfMatchingKernel,
598 kernelSources=kernelSources)
601 """Run detection on the science image and use the template mask plane
602 to reject candidate sources.
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.
613 kernelSources : `lsst.afw.table.SourceCatalog`
614 Sources from the input catalog to use to construct the
617 kernelSize = self.makeKernel.makeKernelBasisList(
619 sigmaToFwhm = 2*np.log(2*np.sqrt(2))
620 candidateList = self.makeKernel.makeCandidateList(template, science, kernelSize,
623 sources = self.makeKernel.selectKernelSources(template, science,
624 candidateList=candidateList,
633 """Convolve the template image with a PSF-matching kernel and subtract
634 from the science image.
638 template : `lsst.afw.image.ExposureF`
639 Template exposure, warped to match the science exposure.
640 science : `lsst.afw.image.ExposureF`
641 Science exposure to subtract from the template.
642 selectSources : `lsst.afw.table.SourceCatalog`
643 Identified sources on the science exposure. This catalog is used to
644 select sources in order to perform the AL PSF matching on stamp
649 results : `lsst.pipe.base.Struct`
651 ``difference`` : `lsst.afw.image.ExposureF`
652 Result of subtracting template and science.
653 ``matchedTemplate`` : `lsst.afw.image.ExposureF`
654 Warped and PSF-matched template exposure.
655 ``backgroundModel`` : `lsst.afw.math.Function2D`
656 Background model that was fit while solving for the PSF-matching kernel
657 ``psfMatchingKernel`` : `lsst.afw.math.Kernel`
658 Kernel used to PSF-match the template to the science image.
660 self.metadata[
"convolvedExposure"] =
"Template"
664 bbox=science.getBBox(),
666 photoCalib=science.photoCalib)
668 difference =
_subtractImages(science, matchedTemplate, backgroundModel=backgroundModel)
669 correctedExposure = self.
finalize(template, science, difference,
671 templateMatched=
True)
673 return lsst.pipe.base.Struct(difference=correctedExposure,
674 matchedTemplate=matchedTemplate,
675 matchedScience=science,
676 backgroundModel=backgroundModel,
677 psfMatchingKernel=psfMatchingKernel)
680 """Convolve the science image with a PSF-matching kernel and subtract
685 template : `lsst.afw.image.ExposureF`
686 Template exposure, warped to match the science exposure.
687 science : `lsst.afw.image.ExposureF`
688 Science exposure to subtract from the template.
689 selectSources : `lsst.afw.table.SourceCatalog`
690 Identified sources on the science exposure. This catalog is used to
691 select sources in order to perform the AL PSF matching on stamp
696 results : `lsst.pipe.base.Struct`
698 ``difference`` : `lsst.afw.image.ExposureF`
699 Result of subtracting template and science.
700 ``matchedTemplate`` : `lsst.afw.image.ExposureF`
701 Warped template exposure. Note that in this case, the template
702 is not PSF-matched to the science image.
703 ``backgroundModel`` : `lsst.afw.math.Function2D`
704 Background model that was fit while solving for the PSF-matching kernel
705 ``psfMatchingKernel`` : `lsst.afw.math.Kernel`
706 Kernel used to PSF-match the science image to the template.
708 self.metadata[
"convolvedExposure"] =
"Science"
709 bbox = science.getBBox()
711 kernelImage = lsst.afw.image.ImageD(psfMatchingKernel.getDimensions())
712 norm = psfMatchingKernel.computeImage(kernelImage, doNormalize=
False)
719 matchedScience.maskedImage /= norm
720 matchedTemplate = template.clone()[bbox]
721 matchedTemplate.maskedImage /= norm
722 matchedTemplate.setPhotoCalib(science.photoCalib)
724 if backgroundModel
is not None:
725 modelParams = backgroundModel.getParameters()
727 backgroundModel.setParameters([-p
for p
in modelParams])
729 difference =
_subtractImages(matchedScience, matchedTemplate, backgroundModel=backgroundModel)
731 correctedExposure = self.
finalize(template, science, difference,
733 templateMatched=
False)
735 return lsst.pipe.base.Struct(difference=correctedExposure,
736 matchedTemplate=matchedTemplate,
737 matchedScience=matchedScience,
738 backgroundModel=backgroundModel,
739 psfMatchingKernel=psfMatchingKernel)
741 def finalize(self, template, science, difference, kernel,
742 templateMatched=True,
745 spatiallyVarying=False):
746 """Decorrelate the difference image to undo the noise correlations
747 caused by convolution.
751 template : `lsst.afw.image.ExposureF`
752 Template exposure, warped to match the science exposure.
753 science : `lsst.afw.image.ExposureF`
754 Science exposure to subtract from the template.
755 difference : `lsst.afw.image.ExposureF`
756 Result of subtracting template and science.
757 kernel : `lsst.afw.math.Kernel`
758 An (optionally spatially-varying) PSF matching kernel
759 templateMatched : `bool`, optional
760 Was the template PSF-matched to the science image?
761 preConvMode : `bool`, optional
762 Was the science image preconvolved with its own PSF
763 before PSF matching the template?
764 preConvKernel : `lsst.afw.detection.Psf`, optional
765 If not `None`, then the science image was pre-convolved with
766 (the reflection of) this kernel. Must be normalized to sum to 1.
767 spatiallyVarying : `bool`, optional
768 Compute the decorrelation kernel spatially varying across the image?
772 correctedExposure : `lsst.afw.image.ExposureF`
773 The decorrelated image difference.
775 if self.config.doDecorrelation:
776 self.
log.info(
"Decorrelating image difference.")
780 correctedExposure = self.decorrelate.run(science, template[science.getBBox()], difference, kernel,
781 templateMatched=templateMatched,
782 preConvMode=preConvMode,
783 preConvKernel=preConvKernel,
784 spatiallyVarying=spatiallyVarying).correctedExposure
786 self.
log.info(
"NOT decorrelating image difference.")
787 correctedExposure = difference
788 return correctedExposure
791 """Calculate an exposure's limiting magnitude.
793 This method uses the photometric zeropoint together with the
794 PSF size from the average position of the exposure.
798 exposure : `lsst.afw.image.Exposure`
799 The target exposure to calculate the limiting magnitude for.
800 nsigma : `float`, optional
801 The detection threshold in sigma.
802 fallbackPsfSize : `float`, optional
803 PSF FWHM to use in the event the exposure PSF cannot be retrieved.
807 maglim : `astropy.units.Quantity`
808 The limiting magnitude of the exposure, or np.nan.
810 if exposure.photoCalib
is None:
815 psf = exposure.getPsf()
816 psf_shape = psf.computeShape(psf.getAveragePosition())
818 afwDetection.InvalidPsfError,
820 if fallbackPsfSize
is not None:
821 self.
log.info(
"Unable to evaluate PSF, using fallback FWHM %f", fallbackPsfSize)
822 psf_area = np.pi*(fallbackPsfSize/2)**2
823 zeropoint = exposure.photoCalib.instFluxToMagnitude(1)
824 maglim = zeropoint - 2.5*np.log10(nsigma*np.sqrt(psf_area))
826 self.
log.info(
"Unable to evaluate PSF, setting maglim to nan")
830 psf_area = np.pi*np.sqrt(psf_shape.getIxx()*psf_shape.getIyy())
831 zeropoint = exposure.photoCalib.instFluxToMagnitude(1)
832 maglim = zeropoint - 2.5*np.log10(nsigma*np.sqrt(psf_area))
838 """Check that the WCS of the two Exposures match, the template bbox
839 contains the science bbox, and that the bands match.
843 template : `lsst.afw.image.ExposureF`
844 Template exposure, warped to match the science exposure.
845 science : `lsst.afw.image.ExposureF`
846 Science exposure to subtract from the template.
851 Raised if the WCS of the template is not equal to the science WCS,
852 if the science image is not fully contained in the template
853 bounding box, or if the bands do not match.
855 assert template.wcs == science.wcs, \
856 "Template and science exposure WCS are not identical."
857 templateBBox = template.getBBox()
858 scienceBBox = science.getBBox()
859 assert science.filter.bandLabel == template.filter.bandLabel, \
860 "Science and template exposures have different bands: %s, %s" % \
861 (science.filter, template.filter)
863 assert templateBBox.contains(scienceBBox), \
864 "Template bbox does not contain all of the science image."
870 interpolateBadMaskPlanes=False,
872 """Convolve an exposure with the given kernel.
876 exposure : `lsst.afw.Exposure`
877 exposure to convolve.
878 kernel : `lsst.afw.math.LinearCombinationKernel`
879 PSF matching kernel computed in the ``makeKernel`` subtask.
880 convolutionControl : `lsst.afw.math.ConvolutionControl`
881 Configuration for convolve algorithm.
882 bbox : `lsst.geom.Box2I`, optional
883 Bounding box to trim the convolved exposure to.
884 psf : `lsst.afw.detection.Psf`, optional
885 Point spread function (PSF) to set for the convolved exposure.
886 photoCalib : `lsst.afw.image.PhotoCalib`, optional
887 Photometric calibration of the convolved exposure.
891 convolvedExp : `lsst.afw.Exposure`
894 convolvedExposure = exposure.clone()
896 convolvedExposure.setPsf(psf)
897 if photoCalib
is not None:
898 convolvedExposure.setPhotoCalib(photoCalib)
899 if interpolateBadMaskPlanes
and self.config.badMaskPlanes
is not None:
901 self.config.badMaskPlanes)
902 self.metadata[
"nInterpolated"] = nInterp
903 convolvedImage = lsst.afw.image.MaskedImageF(convolvedExposure.getBBox())
905 convolvedExposure.setMaskedImage(convolvedImage)
907 return convolvedExposure
909 return convolvedExposure[bbox]
912 """Select sources from a catalog that meet the selection criteria.
913 The selection criteria include any configured parameters of the
914 `sourceSelector` subtask, as well as distance from the edge if
915 `restrictKernelEdgeSources` is set.
919 sources : `lsst.afw.table.SourceCatalog`
920 Input source catalog to select sources from.
921 bbox : `lsst.geom.Box2I`
922 Bounding box of the science image.
926 selectSources : `lsst.afw.table.SourceCatalog`
927 The input source catalog, with flagged and low signal-to-noise
933 If there are too few sources to compute the PSF matching kernel
934 remaining after source selection.
937 selected = self.fallbackSourceSelector.selectSources(sources).selected
939 selected = self.sourceSelector.selectSources(sources).selected
940 if self.config.restrictKernelEdgeSources:
941 rejectRadius = 2*self.config.makeKernel.kernel.active.kernelSize
942 bbox.grow(-rejectRadius)
943 bboxSelected = bbox.contains(sources.getX(), sources.getY())
944 self.
log.info(
"Rejecting %i candidate sources within %i pixels of the edge.",
945 np.count_nonzero(~bboxSelected), rejectRadius)
946 selected &= bboxSelected
947 selectSources = sources[selected].copy(deep=
True)
950 if (len(selectSources) > self.config.maxKernelSources) & (self.config.maxKernelSources > 0):
951 signalToNoise = selectSources.getPsfInstFlux()/selectSources.getPsfInstFluxErr()
952 indices = np.argsort(signalToNoise)
953 indices = indices[-self.config.maxKernelSources:]
954 selected = np.zeros(len(selectSources), dtype=bool)
955 selected[indices] =
True
956 selectSources = selectSources[selected].copy(deep=
True)
958 self.
log.info(
"%i/%i=%.1f%% of sources selected for PSF matching from the input catalog",
959 len(selectSources), len(sources), 100*len(selectSources)/len(sources))
960 if len(selectSources) < self.config.minKernelSources:
961 self.
log.error(
"Too few sources to calculate the PSF matching kernel: "
962 "%i selected but %i needed for the calculation.",
963 len(selectSources), self.config.minKernelSources)
964 if not self.config.allowKernelSourceDetection:
966 nRequired=self.config.minKernelSources)
967 self.metadata[
"nPsfSources"] = len(selectSources)
972 """Perform preparatory calculations common to all Alard&Lupton Tasks.
976 template : `lsst.afw.image.ExposureF`
977 Template exposure, warped to match the science exposure. The
978 variance plane of the template image is modified in place.
979 science : `lsst.afw.image.ExposureF`
980 Science exposure to subtract from the template. The variance plane
981 of the science image is modified in place.
982 visitSummary : `lsst.afw.table.ExposureCatalog`, optional
983 Exposure catalog with external calibrations to be applied. Catalog
984 uses the detector id for the catalog id, sorted on id for fast
988 if visitSummary
is not None:
991 template[science.getBBox()], science, self.
log,
992 requiredTemplateFraction=self.config.requiredTemplateFraction,
993 exceptionMessage=
"Not attempting subtraction. To force subtraction,"
994 " set config requiredTemplateFraction=0"
996 self.metadata[
"templateCoveragePercent"] = 100*templateCoverageFraction
998 if self.config.doScaleVariance:
1002 templateVarFactor = self.scaleVariance.run(template.maskedImage)
1003 sciVarFactor = self.scaleVariance.run(science.maskedImage)
1004 self.
log.info(
"Template variance scaling factor: %.2f", templateVarFactor)
1005 self.metadata[
"scaleTemplateVarianceFactor"] = templateVarFactor
1006 self.
log.info(
"Science variance scaling factor: %.2f", sciVarFactor)
1007 self.metadata[
"scaleScienceVarianceFactor"] = sciVarFactor
1033 self.
log.info(
"Unable to evaluate PSF at the average position. "
1034 "Evaluting PSF on a grid of points."
1038 fwhmExposureBuffer=self.config.makeKernel.fwhmExposureBuffer,
1039 fwhmExposureGrid=self.config.makeKernel.fwhmExposureGrid
1043 fwhmExposureBuffer=self.config.makeKernel.fwhmExposureBuffer,
1044 fwhmExposureGrid=self.config.makeKernel.fwhmExposureGrid
1053 if np.isnan(maglim_science):
1054 self.
log.warning(
"Limiting magnitude of the science image is NaN!")
1055 fluxlim_science = (maglim_science*u.ABmag).to_value(u.nJy)
1057 if np.isnan(maglim_template):
1058 self.
log.info(
"Cannot evaluate template limiting mag; adopting science limiting mag for diffim")
1059 maglim_diffim = maglim_science
1061 fluxlim_template = (maglim_template*u.ABmag).to_value(u.nJy)
1062 maglim_diffim = (np.sqrt(fluxlim_science**2 + fluxlim_template**2)*u.nJy).to(u.ABmag).value
1063 self.metadata[
"scienceLimitingMagnitude"] = maglim_science
1064 self.metadata[
"templateLimitingMagnitude"] = maglim_template
1065 self.metadata[
"diffimLimitingMagnitude"] = maglim_diffim
1068 """Update the science and template mask planes before differencing.
1072 template : `lsst.afw.image.Exposure`
1073 Template exposure, warped to match the science exposure.
1074 The template mask planes will be erased, except for a few specified
1076 science : `lsst.afw.image.Exposure`
1077 Science exposure to subtract from the template.
1078 The DETECTED and DETECTED_NEGATIVE mask planes of the science image
1081 self.
_clearMask(science.mask, clearMaskPlanes=[
"DETECTED",
"DETECTED_NEGATIVE"])
1088 clearMaskPlanes = [mp
for mp
in template.mask.getMaskPlaneDict().keys()
1089 if mp
not in self.config.preserveTemplateMask]
1090 renameMaskPlanes = [mp
for mp
in self.config.renameTemplateMask
1091 if mp
in template.mask.getMaskPlaneDict().keys()]
1096 if "FAKE" in science.mask.getMaskPlaneDict().keys():
1097 self.
log.info(
"Adding injected mask plane to science image")
1099 if "FAKE" in template.mask.getMaskPlaneDict().keys():
1100 self.
log.info(
"Adding injected mask plane to template image")
1102 if "INJECTED" in renameMaskPlanes:
1103 renameMaskPlanes.remove(
"INJECTED")
1104 if "INJECTED_TEMPLATE" in clearMaskPlanes:
1105 clearMaskPlanes.remove(
"INJECTED_TEMPLATE")
1107 for maskPlane
in renameMaskPlanes:
1109 self.
_clearMask(template.mask, clearMaskPlanes=clearMaskPlanes)
1113 """Rename a mask plane by adding the new name and copying the data.
1117 mask : `lsst.afw.image.Mask`
1118 The mask image to update in place.
1120 The name of the existing mask plane to copy.
1121 newMaskPlane : `str`
1122 The new name of the mask plane that will be added.
1123 If the mask plane already exists, it will be updated in place.
1125 mask.addMaskPlane(newMaskPlane)
1126 originBitMask = mask.getPlaneBitMask(maskPlane)
1127 destinationBitMask = mask.getPlaneBitMask(newMaskPlane)
1128 mask.array |= ((mask.array & originBitMask) > 0)*destinationBitMask
1131 """Clear the mask plane of an exposure.
1135 mask : `lsst.afw.image.Mask`
1136 The mask plane to erase, which will be modified in place.
1137 clearMaskPlanes : `list` of `str`, optional
1138 Erase the specified mask planes.
1139 If not supplied, the entire mask will be erased.
1141 if clearMaskPlanes
is None:
1142 clearMaskPlanes = list(mask.getMaskPlaneDict().keys())
1144 bitMaskToClear = mask.getPlaneBitMask(clearMaskPlanes)
1145 mask &= ~bitMaskToClear
1149 SubtractScoreOutputConnections):
1154 pipelineConnections=AlardLuptonPreconvolveSubtractConnections):
1159 """Subtract a template from a science image, convolving the science image
1160 before computing the kernel, and also convolving the template before
1163 ConfigClass = AlardLuptonPreconvolveSubtractConfig
1164 _DefaultName =
"alardLuptonPreconvolveSubtract"
1166 def run(self, template, science, sources, visitSummary=None):
1167 """Preconvolve the science image with its own PSF,
1168 convolve the template image with a PSF-matching kernel and subtract
1169 from the preconvolved science image.
1173 template : `lsst.afw.image.ExposureF`
1174 The template image, which has previously been warped to the science
1175 image. The template bbox will be padded by a few pixels compared to
1177 science : `lsst.afw.image.ExposureF`
1178 The science exposure.
1179 sources : `lsst.afw.table.SourceCatalog`
1180 Identified sources on the science exposure. This catalog is used to
1181 select sources in order to perform the AL PSF matching on stamp
1183 visitSummary : `lsst.afw.table.ExposureCatalog`, optional
1184 Exposure catalog with complete external calibrations. Catalog uses
1185 the detector id for the catalog id, sorted on id for fast lookup.
1189 results : `lsst.pipe.base.Struct`
1190 ``scoreExposure`` : `lsst.afw.image.ExposureF`
1191 Result of subtracting the convolved template and science
1192 images. Attached PSF is that of the original science image.
1193 ``matchedTemplate`` : `lsst.afw.image.ExposureF`
1194 Warped and PSF-matched template exposure. Attached PSF is that
1195 of the original science image.
1196 ``matchedScience`` : `lsst.afw.image.ExposureF`
1197 The science exposure after convolving with its own PSF.
1198 Attached PSF is that of the original science image.
1199 ``backgroundModel`` : `lsst.afw.math.Function2D`
1200 Background model that was fit while solving for the
1202 ``psfMatchingKernel`` : `lsst.afw.math.Kernel`
1203 Final kernel used to PSF-match the template to the science
1206 self.
_prepareInputs(template, science, visitSummary=visitSummary)
1209 scienceKernel = science.psf.getKernel()
1211 interpolateBadMaskPlanes=
True)
1212 self.metadata[
"convolvedExposure"] =
"Preconvolution"
1215 subtractResults = self.
runPreconvolve(template, science, matchedScience,
1216 selectSources, scienceKernel)
1219 self.
log.warning(
"Failed to match template. Checking coverage")
1222 self.config.minTemplateFractionForExpectedSuccess,
1223 exceptionMessage=
"Template coverage lower than expected to succeed."
1224 f
" Failure is tolerable: {e}")
1228 return subtractResults
1230 def runPreconvolve(self, template, science, matchedScience, selectSources, preConvKernel):
1231 """Convolve the science image with its own PSF, then convolve the
1232 template with a matching kernel and subtract to form the Score
1237 template : `lsst.afw.image.ExposureF`
1238 Template exposure, warped to match the science exposure.
1239 science : `lsst.afw.image.ExposureF`
1240 Science exposure to subtract from the template.
1241 matchedScience : `lsst.afw.image.ExposureF`
1242 The science exposure, convolved with the reflection of its own PSF.
1243 selectSources : `lsst.afw.table.SourceCatalog`
1244 Identified sources on the science exposure. This catalog is used to
1245 select sources in order to perform the AL PSF matching on stamp
1247 preConvKernel : `lsst.afw.math.Kernel`
1248 The reflection of the kernel that was used to preconvolve the
1249 `science` exposure. Must be normalized to sum to 1.
1253 results : `lsst.pipe.base.Struct`
1255 ``scoreExposure`` : `lsst.afw.image.ExposureF`
1256 Result of subtracting the convolved template and science
1257 images. Attached PSF is that of the original science image.
1258 ``matchedTemplate`` : `lsst.afw.image.ExposureF`
1259 Warped and PSF-matched template exposure. Attached PSF is that
1260 of the original science image.
1261 ``matchedScience`` : `lsst.afw.image.ExposureF`
1262 The science exposure after convolving with its own PSF.
1263 Attached PSF is that of the original science image.
1264 ``backgroundModel`` : `lsst.afw.math.Function2D`
1265 Background model that was fit while solving for the
1267 ``psfMatchingKernel`` : `lsst.afw.math.Kernel`
1268 Final kernel used to PSF-match the template to the science
1271 bbox = science.getBBox()
1272 innerBBox = preConvKernel.shrinkBBox(bbox)
1274 kernelSources = self.makeKernel.selectKernelSources(template[innerBBox], matchedScience[innerBBox],
1275 candidateList=selectSources,
1279 kernelResult = self.makeKernel.run(template[innerBBox], matchedScience[innerBBox], kernelSources,
1284 matchedTemplate = self.
_convolveExposure(template, kernelResult.psfMatchingKernel,
1288 interpolateBadMaskPlanes=
True,
1289 photoCalib=science.photoCalib)
1291 backgroundModel=(kernelResult.backgroundModel
1292 if self.config.doSubtractBackground
else None))
1293 correctedScore = self.
finalize(template[bbox], science, score,
1294 kernelResult.psfMatchingKernel,
1295 templateMatched=
True, preConvMode=
True,
1296 preConvKernel=preConvKernel)
1298 return lsst.pipe.base.Struct(scoreExposure=correctedScore,
1299 matchedTemplate=matchedTemplate,
1300 matchedScience=matchedScience,
1301 backgroundModel=kernelResult.backgroundModel,
1302 psfMatchingKernel=kernelResult.psfMatchingKernel,
1303 kernelSources=kernelSources)
1307 exceptionMessage=""):
1308 """Raise NoWorkFound if template coverage < requiredTemplateFraction
1312 templateExposure : `lsst.afw.image.ExposureF`
1313 The template exposure to check
1314 logger : `logging.Logger`
1315 Logger for printing output.
1316 requiredTemplateFraction : `float`, optional
1317 Fraction of pixels of the science image required to have coverage
1319 exceptionMessage : `str`, optional
1320 Message to include in the exception raised if the template coverage
1325 templateCoverageFraction: `float`
1326 Fraction of pixels in the template with data.
1330 lsst.pipe.base.NoWorkFound
1331 Raised if fraction of good pixels, defined as not having NO_DATA
1332 set, is less than the requiredTemplateFraction
1336 noTemplate = templateExposure.mask.array & templateExposure.mask.getPlaneBitMask(
'NO_DATA')
1339 noScience = scienceExposure.mask.array & scienceExposure.mask.getPlaneBitMask(
'NO_DATA')
1340 pixNoData = np.count_nonzero(noTemplate | noScience)
1341 pixGood = templateExposure.getBBox().getArea() - pixNoData
1342 templateCoverageFraction = pixGood/templateExposure.getBBox().getArea()
1343 logger.info(
"template has %d good pixels (%.1f%%)", pixGood, 100*templateCoverageFraction)
1345 if templateCoverageFraction < requiredTemplateFraction:
1346 message = (
"Insufficient Template Coverage. (%.1f%% < %.1f%%)" % (
1347 100*templateCoverageFraction,
1348 100*requiredTemplateFraction))
1349 raise lsst.pipe.base.NoWorkFound(message +
" " + exceptionMessage)
1350 return templateCoverageFraction
1354 """Subtract template from science, propagating relevant metadata.
1358 science : `lsst.afw.Exposure`
1359 The input science image.
1360 template : `lsst.afw.Exposure`
1361 The template to subtract from the science image.
1362 backgroundModel : `lsst.afw.MaskedImage`, optional
1363 Differential background model
1367 difference : `lsst.afw.Exposure`
1368 The subtracted image.
1370 difference = science.clone()
1371 if backgroundModel
is not None:
1372 difference.maskedImage -= backgroundModel
1373 difference.maskedImage -= template.maskedImage
1378 """Determine that the PSF of ``exp1`` is not wider than that of ``exp2``.
1382 exp1 : `~lsst.afw.image.Exposure`
1383 Exposure with the reference point spread function (PSF) to evaluate.
1384 exp2 : `~lsst.afw.image.Exposure`
1385 Exposure with a candidate point spread function (PSF) to evaluate.
1386 fwhmExposureBuffer : `float`
1387 Fractional buffer margin to be left out of all sides of the image
1388 during the construction of the grid to compute mean PSF FWHM in an
1389 exposure, if the PSF is not available at its average position.
1390 fwhmExposureGrid : `int`
1391 Grid size to compute the mean FWHM in an exposure, if the PSF is not
1392 available at its average position.
1396 True if ``exp1`` has a PSF that is not wider than that of ``exp2`` in
1400 shape1 = getPsfFwhm(exp1.psf, average=
False)
1401 shape2 = getPsfFwhm(exp2.psf, average=
False)
1403 shape1 = evaluateMeanPsfFwhm(exp1,
1404 fwhmExposureBuffer=fwhmExposureBuffer,
1405 fwhmExposureGrid=fwhmExposureGrid
1407 shape2 = evaluateMeanPsfFwhm(exp2,
1408 fwhmExposureBuffer=fwhmExposureBuffer,
1409 fwhmExposureGrid=fwhmExposureGrid
1411 return shape1 <= shape2
1414 xTest = shape1[0] <= shape2[0]
1415 yTest = shape1[1] <= shape2[1]
1416 return xTest | yTest
1420 pipelineConnections=SimplifiedSubtractConnections):
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."
1432 doc=
"Use a pre-existing PSF matching kernel?"
1433 "If False, source detection and measurement will be run."
1438 """Compute the image difference of a science and template image using
1439 the Alard & Lupton (1998) algorithm.
1441 ConfigClass = SimplifiedSubtractConfig
1442 _DefaultName =
"simplifiedSubtract"
1445 def run(self, template, science, visitSummary=None, inputPsfMatchingKernel=None):
1446 """PSF match, subtract, and decorrelate two images.
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
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
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.
1478 If an unsupported convolution mode is supplied.
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
1485 self.
_prepareInputs(template, science, visitSummary=visitSummary)
1489 if self.config.useExistingKernel:
1490 psfMatchingKernel = inputPsfMatchingKernel
1491 backgroundModel =
None
1492 kernelSources =
None
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
1500 backgroundModel =
None
1501 if convolveTemplate:
1503 backgroundModel=backgroundModel)
1506 backgroundModel=backgroundModel)
1507 if kernelSources
is not None:
1508 subtractResults.kernelSources = kernelSources
1509 return subtractResults
1512 """Construct the PSF-matching kernel.
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
1524 convolveTemplate : `bool`, optional
1525 Construct the matching kernel to convolve the template?
1529 results : `lsst.pipe.base.Struct`
1530 ``backgroundModel`` : `lsst.afw.math.Function2D`
1531 Background model that was fit while solving for the
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.
1539 if convolveTemplate:
1540 reference = template
1554 kernelResult = self.makeKernel.run(reference, target, kernelSources,
1556 templateFwhmPix=referenceFwhmPix,
1557 scienceFwhmPix=targetFwhmPix)
1559 self.
log.warning(
"Failed to match template. Checking coverage")
1562 self.config.minTemplateFractionForExpectedSuccess,
1563 exceptionMessage=
"Template coverage lower than expected to succeed."
1564 f
" Failure is tolerable: {e}")
1567 return lsst.pipe.base.Struct(backgroundModel=kernelResult.backgroundModel,
1568 psfMatchingKernel=kernelResult.psfMatchingKernel,
1569 kernelSources=kernelSources)
1573 """Replace masked image pixels with interpolated values.
1577 maskedImage : `lsst.afw.image.MaskedImage`
1578 Image on which to perform interpolation.
1579 badMaskPlanes : `list` of `str`
1580 List of mask planes to interpolate over.
1581 fallbackValue : `float`, optional
1582 Value to set when interpolation fails.
1587 The number of masked pixels that were replaced.
1589 imgBadMaskPlanes = [
1590 maskPlane
for maskPlane
in badMaskPlanes
if maskPlane
in maskedImage.mask.getMaskPlaneDict()
1593 image = maskedImage.image.array
1594 badPixels = (maskedImage.mask.array & maskedImage.mask.getPlaneBitMask(imgBadMaskPlanes)) > 0
1595 image[badPixels] = np.nan
1596 if fallbackValue
is None:
1597 fallbackValue = np.nanmedian(image)
1600 image[badPixels] = fallbackValue
1601 return np.sum(badPixels)
Parameters to control convolution.
runPreconvolve(self, template, science, matchedScience, selectSources, preConvKernel)
run(self, template, science, sources, visitSummary=None)
_clearMask(self, mask, clearMaskPlanes=None)
_prepareInputs(self, template, science, visitSummary=None)
chooseConvolutionMethod(self, template, science)
runConvolveTemplate(self, template, science, psfMatchingKernel, backgroundModel=None)
run(self, template, science, sources, visitSummary=None)
runConvolveScience(self, template, science, psfMatchingKernel, backgroundModel=None)
_calculateMagLim(self, exposure, nsigma=5.0, fallbackPsfSize=None)
_applyExternalCalibrations(self, exposure, visitSummary)
runMakeKernel(self, template, science, sources, convolveTemplate=True)
_sourceSelector(self, sources, bbox, fallback=False)
updateMasks(self, template, science)
_convolveExposure(self, exposure, kernel, convolutionControl, bbox=None, psf=None, photoCalib=None, interpolateBadMaskPlanes=False)
runKernelSourceDetection(self, template, science)
finalize(self, template, science, difference, kernel, templateMatched=True, preConvMode=False, preConvKernel=None, spatiallyVarying=False)
_validateExposures(template, science)
_renameMaskPlanes(mask, maskPlane, newMaskPlane)
__init__(self, *, nSources, nRequired)
__init__(self, *, config=None)
run(self, template, science, visitSummary=None, inputPsfMatchingKernel=None)
runMakeKernel(self, template, science, convolveTemplate=True)
Provides consistent interface for LSST exceptions.
Reports invalid arguments.
Reports when the result of an operation cannot be represented by the destination type.
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.
_subtractImages(science, template, backgroundModel=None)
checkTemplateIsSufficient(templateExposure, scienceExposure, logger, requiredTemplateFraction=0., exceptionMessage="")
_interpolateImage(maskedImage, badMaskPlanes, fallbackValue=None)
_shapeTest(exp1, exp2, fwhmExposureBuffer, fwhmExposureGrid)