22from astropy
import units
as u
23from astropy.stats
import gaussian_fwhm_to_sigma
31 checkMask, setSourceFootprints)
32from lsst.meas.algorithms
import ScaleVarianceTask, ScienceSourceSelectorTask
37from .
import MakeKernelTask, DecorrelateALKernelTask
38from lsst.utils.timer
import timeMethod
40__all__ = [
"AlardLuptonSubtractConfig",
"AlardLuptonSubtractTask",
41 "AlardLuptonPreconvolveSubtractConfig",
"AlardLuptonPreconvolveSubtractTask",
42 "SimplifiedSubtractConfig",
"SimplifiedSubtractTask",
43 "InsufficientKernelSourcesError"]
45_dimensions = (
"instrument",
"visit",
"detector")
46_defaultTemplates = {
"coaddName":
"deep",
"fakesType":
""}
50 """Raised when there are too few sources to calculate the PSF matching
54 msg = (f
"Only {nSources} sources were selected for PSF matching,"
55 f
" but {nRequired} are required.")
68 dimensions=_dimensions,
69 defaultTemplates=_defaultTemplates):
70 template = connectionTypes.Input(
71 doc=
"Input warped template to subtract.",
72 dimensions=(
"instrument",
"visit",
"detector"),
73 storageClass=
"ExposureF",
74 name=
"{fakesType}{coaddName}Diff_templateExp"
76 science = connectionTypes.Input(
77 doc=
"Input science exposure to subtract from.",
78 dimensions=(
"instrument",
"visit",
"detector"),
79 storageClass=
"ExposureF",
80 name=
"{fakesType}calexp"
82 sources = connectionTypes.Input(
83 doc=
"Sources measured on the science exposure; "
84 "used to select sources for making the matching kernel.",
85 dimensions=(
"instrument",
"visit",
"detector"),
86 storageClass=
"SourceCatalog",
89 visitSummary = connectionTypes.Input(
90 doc=(
"Per-visit catalog with final calibration objects. "
91 "These catalogs use the detector id for the catalog id, "
92 "sorted on id for fast lookup."),
93 dimensions=(
"instrument",
"visit"),
94 storageClass=
"ExposureCatalog",
95 name=
"finalVisitSummary",
100 if not config.doApplyExternalCalibrations:
105 dimensions=_dimensions,
106 defaultTemplates=_defaultTemplates):
107 difference = connectionTypes.Output(
108 doc=
"Result of subtracting convolved template from science image.",
109 dimensions=(
"instrument",
"visit",
"detector"),
110 storageClass=
"ExposureF",
111 name=
"{fakesType}{coaddName}Diff_differenceTempExp",
113 matchedTemplate = connectionTypes.Output(
114 doc=
"Warped and PSF-matched template used to create `subtractedExposure`.",
115 dimensions=(
"instrument",
"visit",
"detector"),
116 storageClass=
"ExposureF",
117 name=
"{fakesType}{coaddName}Diff_matchedExp",
119 psfMatchingKernel = connectionTypes.Output(
120 doc=
"Kernel used to PSF match the science and template images.",
121 dimensions=(
"instrument",
"visit",
"detector"),
122 storageClass=
"MatchingKernel",
123 name=
"{fakesType}{coaddName}Diff_psfMatchKernel",
125 kernelSources = connectionTypes.Output(
126 doc=
"Final selection of sources used for psf matching.",
127 dimensions=(
"instrument",
"visit",
"detector"),
128 storageClass=
"SourceCatalog",
129 name=
"{fakesType}{coaddName}Diff_psfMatchSources"
134 dimensions=_dimensions,
135 defaultTemplates=_defaultTemplates):
136 scoreExposure = connectionTypes.Output(
137 doc=
"The maximum likelihood image, used for the detection of diaSources.",
138 dimensions=(
"instrument",
"visit",
"detector"),
139 storageClass=
"ExposureF",
140 name=
"{fakesType}{coaddName}Diff_scoreExp",
142 psfMatchingKernel = connectionTypes.Output(
143 doc=
"Kernel used to PSF match the science and template images.",
144 dimensions=(
"instrument",
"visit",
"detector"),
145 storageClass=
"MatchingKernel",
146 name=
"{fakesType}{coaddName}Diff_psfScoreMatchKernel",
148 kernelSources = connectionTypes.Output(
149 doc=
"Final selection of sources used for psf matching.",
150 dimensions=(
"instrument",
"visit",
"detector"),
151 storageClass=
"SourceCatalog",
152 name=
"{fakesType}{coaddName}Diff_psfScoreMatchSources"
160class SimplifiedSubtractConnections(SubtractInputConnections, SubtractImageOutputConnections):
161 inputPsfMatchingKernel = connectionTypes.Input(
162 doc=
"Kernel used to PSF match the science and template images.",
163 dimensions=(
"instrument",
"visit",
"detector"),
164 storageClass=
"MatchingKernel",
165 name=
"{fakesType}{coaddName}Diff_psfMatchKernel",
171 if config.useExistingKernel:
179 makeKernel = lsst.pex.config.ConfigurableField(
180 target=MakeKernelTask,
181 doc=
"Task to construct a matching kernel for convolution.",
183 doDecorrelation = lsst.pex.config.Field(
186 doc=
"Perform diffim decorrelation to undo pixel correlation due to A&L "
187 "kernel convolution? If True, also update the diffim PSF."
189 decorrelate = lsst.pex.config.ConfigurableField(
190 target=DecorrelateALKernelTask,
191 doc=
"Task to decorrelate the image difference.",
193 requiredTemplateFraction = lsst.pex.config.Field(
196 doc=
"Raise NoWorkFound and do not attempt image subtraction if template covers less than this "
197 " fraction of pixels. Setting to 0 will always attempt image subtraction."
199 minTemplateFractionForExpectedSuccess = lsst.pex.config.Field(
202 doc=
"Raise NoWorkFound if PSF-matching fails and template covers less than this fraction of pixels."
203 " If the fraction of pixels covered by the template is less than this value (and greater than"
204 " requiredTemplateFraction) this task is attempted but failure is anticipated and tolerated."
206 doScaleVariance = lsst.pex.config.Field(
209 doc=
"Scale variance of the image difference?"
211 scaleVariance = lsst.pex.config.ConfigurableField(
212 target=ScaleVarianceTask,
213 doc=
"Subtask to rescale the variance of the template to the statistically expected level."
215 doSubtractBackground = lsst.pex.config.Field(
216 doc=
"Subtract the background fit when solving the kernel? "
217 "It is generally better to instead subtract the background in detectAndMeasure.",
221 doApplyExternalCalibrations = lsst.pex.config.Field(
223 "Replace science Exposure's calibration objects with those"
224 " in visitSummary. Ignored if `doApplyFinalizedPsf is True."
229 sourceSelector = lsst.pex.config.ConfigurableField(
230 target=ScienceSourceSelectorTask,
231 doc=
"Task to select sources to be used for PSF matching.",
233 fallbackSourceSelector = lsst.pex.config.ConfigurableField(
234 target=ScienceSourceSelectorTask,
235 doc=
"Task to select sources to be used for PSF matching."
236 "Used only if the kernel calculation fails and"
237 "`allowKernelSourceDetection` is set. The fallback source detection"
238 " will not include all of the same plugins as the original source "
239 " detection, so not all of the same flags can be used.",
241 detectionThreshold = lsst.pex.config.Field(
244 doc=
"Minimum signal to noise ratio of detected sources "
245 "to use for calculating the PSF matching kernel.",
246 deprecated=
"No longer used. Will be removed after v30"
248 detectionThresholdMax = lsst.pex.config.Field(
251 doc=
"Maximum signal to noise ratio of detected sources "
252 "to use for calculating the PSF matching kernel.",
253 deprecated=
"No longer used. Will be removed after v30"
255 restrictKernelEdgeSources = lsst.pex.config.Field(
258 doc=
"Exclude sources close to the edge from the kernel calculation?"
260 maxKernelSources = lsst.pex.config.Field(
263 doc=
"Maximum number of sources to use for calculating the PSF matching kernel."
264 "Set to -1 to disable."
266 minKernelSources = lsst.pex.config.Field(
269 doc=
"Minimum number of sources needed for calculating the PSF matching kernel."
271 excludeMaskPlanes = lsst.pex.config.ListField(
273 default=(
"NO_DATA",
"BAD",
"SAT",
"EDGE",
"FAKE",
"HIGH_VARIANCE"),
274 doc=
"Template mask planes to exclude when selecting sources for PSF matching.",
276 badMaskPlanes = lsst.pex.config.ListField(
278 default=(
"NO_DATA",
"BAD",
"SAT",
"EDGE"),
279 doc=
"Mask planes to interpolate over."
281 preserveTemplateMask = lsst.pex.config.ListField(
283 default=(
"NO_DATA",
"BAD",
"HIGH_VARIANCE"),
284 doc=
"Mask planes from the template to propagate to the image difference."
286 renameTemplateMask = lsst.pex.config.ListField(
288 default=(
"SAT",
"INJECTED",
"INJECTED_CORE",),
289 doc=
"Mask planes from the template to propagate to the image difference"
290 "with '_TEMPLATE' appended to the name."
292 allowKernelSourceDetection = lsst.pex.config.Field(
295 doc=
"Re-run source detection for kernel candidates if an error is"
296 " encountered while calculating the matching kernel."
303 self.
makeKernel.kernel.active.fitForBackground =
True
304 self.
makeKernel.kernel.active.spatialKernelOrder = 1
305 self.
makeKernel.kernel.active.spatialBgOrder = 2
308 doSignalToNoise =
True
310 signalToNoiseMinimum = 10
311 signalToNoiseMaximum = 500
332 pipelineConnections=AlardLuptonSubtractConnections):
333 mode = lsst.pex.config.ChoiceField(
335 default=
"convolveTemplate",
336 allowed={
"auto":
"Choose which image to convolve at runtime.",
337 "convolveScience":
"Only convolve the science image.",
338 "convolveTemplate":
"Only convolve the template image."},
339 doc=
"Choose which image to convolve at runtime, or require that a specific image is convolved."
344 """Compute the image difference of a science and template image using
345 the Alard & Lupton (1998) algorithm.
347 ConfigClass = AlardLuptonSubtractConfig
348 _DefaultName =
"alardLuptonSubtract"
352 self.makeSubtask(
"decorrelate")
353 self.makeSubtask(
"makeKernel")
354 self.makeSubtask(
"sourceSelector")
355 self.makeSubtask(
"fallbackSourceSelector")
356 if self.config.doScaleVariance:
357 self.makeSubtask(
"scaleVariance")
366 """Replace calibrations (psf, and ApCorrMap) on this exposure with
371 exposure : `lsst.afw.image.exposure.Exposure`
372 Input exposure to adjust calibrations.
373 visitSummary : `lsst.afw.table.ExposureCatalog`
374 Exposure catalog with external calibrations to be applied. Catalog
375 uses the detector id for the catalog id, sorted on id for fast
380 exposure : `lsst.afw.image.exposure.Exposure`
381 Exposure with adjusted calibrations.
383 detectorId = exposure.info.getDetector().getId()
385 row = visitSummary.find(detectorId)
387 self.
log.warning(
"Detector id %s not found in external calibrations catalog; "
388 "Using original calibrations.", detectorId)
391 apCorrMap = row.getApCorrMap()
393 self.
log.warning(
"Detector id %s has None for psf in "
394 "external calibrations catalog; Using original psf and aperture correction.",
396 elif apCorrMap
is None:
397 self.
log.warning(
"Detector id %s has None for apCorrMap in "
398 "external calibrations catalog; Using original psf and aperture correction.",
402 exposure.info.setApCorrMap(apCorrMap)
407 inputs = butlerQC.get(inputRefs)
410 results = self.
run(**inputs)
411 except lsst.pipe.base.AlgorithmError
as e:
412 error = lsst.pipe.base.AnnotatedPartialOutputsError.annotate(e, self, log=self.
log)
416 butlerQC.put(results, outputRefs)
419 def run(self, template, science, sources, visitSummary=None):
420 """PSF match, subtract, and decorrelate two images.
424 template : `lsst.afw.image.ExposureF`
425 Template exposure, warped to match the science exposure.
426 science : `lsst.afw.image.ExposureF`
427 Science exposure to subtract from the template.
428 sources : `lsst.afw.table.SourceCatalog`
429 Identified sources on the science exposure. This catalog is used to
430 select sources in order to perform the AL PSF matching on stamp
432 visitSummary : `lsst.afw.table.ExposureCatalog`, optional
433 Exposure catalog with external calibrations to be applied. Catalog
434 uses the detector id for the catalog id, sorted on id for fast
439 results : `lsst.pipe.base.Struct`
440 ``difference`` : `lsst.afw.image.ExposureF`
441 Result of subtracting template and science.
442 ``matchedTemplate`` : `lsst.afw.image.ExposureF`
443 Warped and PSF-matched template exposure.
444 ``backgroundModel`` : `lsst.afw.math.Function2D`
445 Background model that was fit while solving for the
447 ``psfMatchingKernel`` : `lsst.afw.math.Kernel`
448 Kernel used to PSF-match the convolved image.
449 ``kernelSources` : `lsst.afw.table.SourceCatalog`
450 Sources from the input catalog that were used to construct the
457 kernelResult = self.
runMakeKernel(template, science, sources=sources,
458 convolveTemplate=convolveTemplate,
459 runSourceDetection=
False)
461 if self.config.doSubtractBackground:
462 backgroundModel = kernelResult.backgroundModel
464 backgroundModel =
None
466 subtractResults = self.
runConvolveTemplate(template, science, kernelResult.psfMatchingKernel,
467 backgroundModel=backgroundModel)
469 subtractResults = self.
runConvolveScience(template, science, kernelResult.psfMatchingKernel,
470 backgroundModel=backgroundModel)
471 subtractResults.kernelSources = kernelResult.kernelSources
473 metrics = computeDifferenceImageMetrics(science, subtractResults.difference, sources)
475 self.metadata[
"differenceFootprintRatioMean"] = metrics.differenceFootprintRatioMean
476 self.metadata[
"differenceFootprintRatioStdev"] = metrics.differenceFootprintRatioStdev
477 self.metadata[
"differenceFootprintSkyRatioMean"] = metrics.differenceFootprintSkyRatioMean
478 self.metadata[
"differenceFootprintSkyRatioStdev"] = metrics.differenceFootprintSkyRatioStdev
479 self.
log.info(
"Mean, stdev of ratio of difference to science "
480 "pixels in star footprints: %5.4f, %5.4f",
481 self.metadata[
"differenceFootprintRatioMean"],
482 self.metadata[
"differenceFootprintRatioStdev"])
484 return subtractResults
487 """Determine whether the template should be convolved with the PSF
492 template : `lsst.afw.image.ExposureF`
493 Template exposure, warped to match the science exposure.
494 science : `lsst.afw.image.ExposureF`
495 Science exposure to subtract from the template.
499 convolveTemplate : `bool`
500 Convolve the template to match the two images?
505 If an unsupported convolution mode is supplied.
507 if self.config.mode ==
"auto":
510 fwhmExposureBuffer=self.config.makeKernel.fwhmExposureBuffer,
511 fwhmExposureGrid=self.config.makeKernel.fwhmExposureGrid)
514 self.
log.info(
"Average template PSF size is greater, "
515 "but science PSF greater in one dimension: convolving template image.")
517 self.
log.info(
"Science PSF size is greater: convolving template image.")
519 self.
log.info(
"Template PSF size is greater: convolving science image.")
520 elif self.config.mode ==
"convolveTemplate":
521 self.
log.info(
"`convolveTemplate` is set: convolving template image.")
522 convolveTemplate =
True
523 elif self.config.mode ==
"convolveScience":
524 self.
log.info(
"`convolveScience` is set: convolving science image.")
525 convolveTemplate =
False
527 raise RuntimeError(
"Cannot handle AlardLuptonSubtract mode: %s", self.config.mode)
528 return convolveTemplate
530 def runMakeKernel(self, template, science, sources=None, convolveTemplate=True, runSourceDetection=False):
531 """Construct the PSF-matching kernel.
535 template : `lsst.afw.image.ExposureF`
536 Template exposure, warped to match the science exposure.
537 science : `lsst.afw.image.ExposureF`
538 Science exposure to subtract from the template.
539 sources : `lsst.afw.table.SourceCatalog`
540 Identified sources on the science exposure. This catalog is used to
541 select sources in order to perform the AL PSF matching on stamp
543 Not used if ``runSourceDetection`` is set.
544 convolveTemplate : `bool`, optional
545 Construct the matching kernel to convolve the template?
546 runSourceDetection : `bool`, optional
547 Run a minimal version of source detection to determine kernel
548 candidates? If False, a source list to select kernel candidates
549 from must be supplied.
553 results : `lsst.pipe.base.Struct`
554 ``backgroundModel`` : `lsst.afw.math.Function2D`
555 Background model that was fit while solving for the
557 ``psfMatchingKernel`` : `lsst.afw.math.Kernel`
558 Kernel used to PSF-match the convolved image.
559 ``kernelSources` : `lsst.afw.table.SourceCatalog`
560 Sources from the input catalog that were used to construct the
575 if runSourceDetection:
579 kernelResult = self.makeKernel.
run(reference, target, kernelSources,
581 templateFwhmPix=referenceFwhmPix,
582 scienceFwhmPix=targetFwhmPix)
584 self.
log.warning(
"Failed to match template. Checking coverage")
587 self.config.minTemplateFractionForExpectedSuccess,
588 exceptionMessage=
"Template coverage lower than expected to succeed."
589 f
" Failure is tolerable: {e}")
593 return lsst.pipe.base.Struct(backgroundModel=kernelResult.backgroundModel,
594 psfMatchingKernel=kernelResult.psfMatchingKernel,
595 kernelSources=kernelSources)
598 """Run detection on the science image and use the template mask plane
599 to reject candidate sources.
603 template : `lsst.afw.image.ExposureF`
604 Template exposure, warped to match the science exposure.
605 science : `lsst.afw.image.ExposureF`
606 Science exposure to subtract from the template.
610 kernelSources : `lsst.afw.table.SourceCatalog`
611 Sources from the input catalog to use to construct the
614 kernelSize = self.makeKernel.makeKernelBasisList(
616 candidateList = self.makeKernel.makeCandidateList(template, science, kernelSize,
619 sources = self.makeKernel.selectKernelSources(template, science,
620 candidateList=candidateList,
629 """Convolve the template image with a PSF-matching kernel and subtract
630 from the science image.
634 template : `lsst.afw.image.ExposureF`
635 Template exposure, warped to match the science exposure.
636 science : `lsst.afw.image.ExposureF`
637 Science exposure to subtract from the template.
638 psfMatchingKernel : `lsst.afw.math.Kernel`
639 Kernel to be used to PSF-match the science image to the template.
640 backgroundModel : `lsst.afw.math.Function2D`, optional
641 Background model that was fit while solving for the PSF-matching
646 results : `lsst.pipe.base.Struct`
648 ``difference`` : `lsst.afw.image.ExposureF`
649 Result of subtracting template and science.
650 ``matchedTemplate`` : `lsst.afw.image.ExposureF`
651 Warped and PSF-matched template exposure.
652 ``backgroundModel`` : `lsst.afw.math.Function2D`
653 Background model that was fit while solving for the PSF-matching kernel
654 ``psfMatchingKernel`` : `lsst.afw.math.Kernel`
655 Kernel used to PSF-match the template to the science image.
657 self.metadata[
"convolvedExposure"] =
"Template"
661 bbox=science.getBBox(),
663 photoCalib=science.photoCalib)
665 difference =
_subtractImages(science, matchedTemplate, backgroundModel=backgroundModel)
666 correctedExposure = self.
finalize(template, science, difference,
668 templateMatched=
True)
670 return lsst.pipe.base.Struct(difference=correctedExposure,
671 matchedTemplate=matchedTemplate,
672 matchedScience=science,
673 backgroundModel=backgroundModel,
674 psfMatchingKernel=psfMatchingKernel)
677 """Convolve the science image with a PSF-matching kernel and subtract
682 template : `lsst.afw.image.ExposureF`
683 Template exposure, warped to match the science exposure.
684 science : `lsst.afw.image.ExposureF`
685 Science exposure to subtract from the template.
686 psfMatchingKernel : `lsst.afw.math.Kernel`
687 Kernel to be used to PSF-match the science image to the template.
688 backgroundModel : `lsst.afw.math.Function2D`, optional
689 Background model that was fit while solving for the PSF-matching
694 results : `lsst.pipe.base.Struct`
696 ``difference`` : `lsst.afw.image.ExposureF`
697 Result of subtracting template and science.
698 ``matchedTemplate`` : `lsst.afw.image.ExposureF`
699 Warped template exposure. Note that in this case, the template
700 is not PSF-matched to the science image.
701 ``backgroundModel`` : `lsst.afw.math.Function2D`
702 Background model that was fit while solving for the PSF-matching kernel
703 ``psfMatchingKernel`` : `lsst.afw.math.Kernel`
704 Kernel used to PSF-match the science image to the template.
706 self.metadata[
"convolvedExposure"] =
"Science"
707 bbox = science.getBBox()
709 kernelImage = lsst.afw.image.ImageD(psfMatchingKernel.getDimensions())
710 norm = psfMatchingKernel.computeImage(kernelImage, doNormalize=
False)
717 matchedScience.maskedImage /= norm
718 matchedTemplate = template.clone()[bbox]
719 matchedTemplate.maskedImage /= norm
720 matchedTemplate.setPhotoCalib(science.photoCalib)
722 if backgroundModel
is not None:
723 modelParams = backgroundModel.getParameters()
725 backgroundModel.setParameters([-p
for p
in modelParams])
727 difference =
_subtractImages(matchedScience, matchedTemplate, backgroundModel=backgroundModel)
729 correctedExposure = self.
finalize(template, science, difference,
731 templateMatched=
False)
733 return lsst.pipe.base.Struct(difference=correctedExposure,
734 matchedTemplate=matchedTemplate,
735 matchedScience=matchedScience,
736 backgroundModel=backgroundModel,
737 psfMatchingKernel=psfMatchingKernel)
739 def finalize(self, template, science, difference, kernel,
740 templateMatched=True,
743 spatiallyVarying=False):
744 """Decorrelate the difference image to undo the noise correlations
745 caused by convolution.
749 template : `lsst.afw.image.ExposureF`
750 Template exposure, warped to match the science exposure.
751 science : `lsst.afw.image.ExposureF`
752 Science exposure to subtract from the template.
753 difference : `lsst.afw.image.ExposureF`
754 Result of subtracting template and science.
755 kernel : `lsst.afw.math.Kernel`
756 An (optionally spatially-varying) PSF matching kernel
757 templateMatched : `bool`, optional
758 Was the template PSF-matched to the science image?
759 preConvMode : `bool`, optional
760 Was the science image preconvolved with its own PSF
761 before PSF matching the template?
762 preConvKernel : `lsst.afw.detection.Psf`, optional
763 If not `None`, then the science image was pre-convolved with
764 (the reflection of) this kernel. Must be normalized to sum to 1.
765 spatiallyVarying : `bool`, optional
766 Compute the decorrelation kernel spatially varying across the image?
770 correctedExposure : `lsst.afw.image.ExposureF`
771 The decorrelated image difference.
773 if self.config.doDecorrelation:
774 self.
log.info(
"Decorrelating image difference.")
778 correctedExposure = self.decorrelate.
run(science, template[science.getBBox()], difference, kernel,
779 templateMatched=templateMatched,
780 preConvMode=preConvMode,
781 preConvKernel=preConvKernel,
782 spatiallyVarying=spatiallyVarying).correctedExposure
784 self.
log.info(
"NOT decorrelating image difference.")
785 correctedExposure = difference
786 return correctedExposure
789 """Calculate an exposure's limiting magnitude.
791 This method uses the photometric zeropoint together with the
792 PSF size from the average position of the exposure.
796 exposure : `lsst.afw.image.Exposure`
797 The target exposure to calculate the limiting magnitude for.
798 nsigma : `float`, optional
799 The detection threshold in sigma.
800 fallbackPsfSize : `float`, optional
801 PSF FWHM to use in the event the exposure PSF cannot be retrieved.
805 maglim : `astropy.units.Quantity`
806 The limiting magnitude of the exposure, or np.nan.
808 if exposure.photoCalib
is None:
813 psf = exposure.getPsf()
814 psf_shape = psf.computeShape(psf.getAveragePosition())
816 afwDetection.InvalidPsfError,
818 if fallbackPsfSize
is not None:
819 self.
log.info(
"Unable to evaluate PSF, using fallback FWHM %f", fallbackPsfSize)
820 psf_area = np.pi*(fallbackPsfSize/2)**2
821 zeropoint = exposure.photoCalib.instFluxToMagnitude(1)
822 maglim = zeropoint - 2.5*np.log10(nsigma*np.sqrt(psf_area))
824 self.
log.info(
"Unable to evaluate PSF, setting maglim to nan")
828 psf_area = np.pi*np.sqrt(psf_shape.getIxx()*psf_shape.getIyy())
829 zeropoint = exposure.photoCalib.instFluxToMagnitude(1)
830 maglim = zeropoint - 2.5*np.log10(nsigma*np.sqrt(psf_area))
836 """Check that the WCS of the two Exposures match, the template bbox
837 contains the science bbox, and that the bands match.
841 template : `lsst.afw.image.ExposureF`
842 Template exposure, warped to match the science exposure.
843 science : `lsst.afw.image.ExposureF`
844 Science exposure to subtract from the template.
849 Raised if the WCS of the template is not equal to the science WCS,
850 if the science image is not fully contained in the template
851 bounding box, or if the bands do not match.
853 assert template.wcs == science.wcs, \
854 "Template and science exposure WCS are not identical."
855 templateBBox = template.getBBox()
856 scienceBBox = science.getBBox()
857 assert science.filter.bandLabel == template.filter.bandLabel, \
858 "Science and template exposures have different bands: %s, %s" % \
859 (science.filter, template.filter)
861 assert templateBBox.contains(scienceBBox), \
862 "Template bbox does not contain all of the science image."
868 interpolateBadMaskPlanes=False,
870 """Convolve an exposure with the given kernel.
874 exposure : `lsst.afw.Exposure`
875 exposure to convolve.
876 kernel : `lsst.afw.math.LinearCombinationKernel`
877 PSF matching kernel computed in the ``makeKernel`` subtask.
878 convolutionControl : `lsst.afw.math.ConvolutionControl`
879 Configuration for convolve algorithm.
880 bbox : `lsst.geom.Box2I`, optional
881 Bounding box to trim the convolved exposure to.
882 psf : `lsst.afw.detection.Psf`, optional
883 Point spread function (PSF) to set for the convolved exposure.
884 photoCalib : `lsst.afw.image.PhotoCalib`, optional
885 Photometric calibration of the convolved exposure.
886 interpolateBadMaskPlanes : `bool`, optional
887 If set, interpolate over mask planes specified in
888 ``config.badMaskPlanes`` before convolving the image.
892 convolvedExp : `lsst.afw.Exposure`
895 convolvedExposure = exposure.clone()
897 convolvedExposure.setPsf(psf)
898 if photoCalib
is not None:
899 convolvedExposure.setPhotoCalib(photoCalib)
900 if interpolateBadMaskPlanes
and self.config.badMaskPlanes
is not None:
902 self.config.badMaskPlanes)
903 self.metadata[
"nInterpolated"] = nInterp
904 convolvedImage = lsst.afw.image.MaskedImageF(convolvedExposure.getBBox())
906 convolvedExposure.setMaskedImage(convolvedImage)
908 return convolvedExposure
910 return convolvedExposure[bbox]
912 def _sourceSelector(self, template, science, sources, fallback=False, preconvolved=False):
913 """Select sources from a catalog that meet the selection criteria.
914 The selection criteria include any configured parameters of the
915 `sourceSelector` subtask, as well as checking the science and template
920 template : `lsst.afw.image.ExposureF`
921 Template exposure, warped to match the science exposure.
922 science : `lsst.afw.image.ExposureF`
923 Science exposure to subtract from the template.
924 sources : `lsst.afw.table.SourceCatalog`
925 Input source catalog to select sources from.
926 fallback : `bool`, optional
927 Switch indicating the source selector is being called after
928 running the fallback source detection subtask, which does not run a
929 full set of measurement plugins and can't use the same settings for
931 preconvolved : `bool`, optional
932 If set, exclude a wider buffer around the edge of the image to
933 account for an extra convolution.
937 kernelSources : `lsst.afw.table.SourceCatalog`
938 The input source catalog, with flagged and low signal-to-noise
939 sources removed and footprints added.
943 InsufficientKernelSourcesError
944 An AlgorithmError that is raised if there are not enough PSF
945 candidates to construct the PSF matching kernel.
948 selected = self.fallbackSourceSelector.selectSources(sources).selected
950 selected = self.sourceSelector.selectSources(sources).selected
952 kSize = self.makeKernel.makeKernelBasisList(self.
templatePsfSize, sciencePsfSize)[0].getWidth()
953 selectSources = sources[selected].copy(deep=
True)
955 kernelSources = setSourceFootprints(selectSources, kernelSize=kSize)
956 bbox = science.getBBox()
959 if self.config.restrictKernelEdgeSources:
962 scienceSelected = checkMask(science.mask[bbox], kernelSources, self.config.excludeMaskPlanes)
963 templateSelected = checkMask(template.mask[bbox], kernelSources, self.config.excludeMaskPlanes)
964 maskSelected = scienceSelected & templateSelected
965 kernelSources = kernelSources[maskSelected].copy(deep=
True)
968 if (len(kernelSources) > self.config.maxKernelSources) & (self.config.maxKernelSources > 0):
969 signalToNoise = kernelSources.getPsfInstFlux()/kernelSources.getPsfInstFluxErr()
970 indices = np.argsort(signalToNoise)
971 indices = indices[-self.config.maxKernelSources:]
972 selected = np.zeros(len(kernelSources), dtype=bool)
973 selected[indices] =
True
974 kernelSources = kernelSources[selected].copy(deep=
True)
976 self.
log.info(
"%i/%i=%.1f%% of sources selected for PSF matching from the input catalog",
977 len(kernelSources), len(sources), 100*len(kernelSources)/len(sources))
978 if len(kernelSources) < self.config.minKernelSources:
979 self.
log.error(
"Too few sources to calculate the PSF matching kernel: "
980 "%i selected but %i needed for the calculation.",
981 len(kernelSources), self.config.minKernelSources)
982 if self.config.allowKernelSourceDetection
and not fallback:
989 nRequired=self.config.minKernelSources)
991 self.metadata[
"nPsfSources"] = len(kernelSources)
996 """Perform preparatory calculations common to all Alard&Lupton Tasks.
1000 template : `lsst.afw.image.ExposureF`
1001 Template exposure, warped to match the science exposure. The
1002 variance plane of the template image is modified in place.
1003 science : `lsst.afw.image.ExposureF`
1004 Science exposure to subtract from the template. The variance plane
1005 of the science image is modified in place.
1006 visitSummary : `lsst.afw.table.ExposureCatalog`, optional
1007 Exposure catalog with external calibrations to be applied. Catalog
1008 uses the detector id for the catalog id, sorted on id for fast
1012 if visitSummary
is not None:
1015 template[science.getBBox()], science, self.
log,
1016 requiredTemplateFraction=self.config.requiredTemplateFraction,
1017 exceptionMessage=
"Not attempting subtraction. To force subtraction,"
1018 " set config requiredTemplateFraction=0"
1020 self.metadata[
"templateCoveragePercent"] = 100*templateCoverageFraction
1022 if self.config.doScaleVariance:
1026 templateVarFactor = self.scaleVariance.
run(template.maskedImage)
1027 sciVarFactor = self.scaleVariance.
run(science.maskedImage)
1028 self.
log.info(
"Template variance scaling factor: %.2f", templateVarFactor)
1029 self.metadata[
"scaleTemplateVarianceFactor"] = templateVarFactor
1030 self.
log.info(
"Science variance scaling factor: %.2f", sciVarFactor)
1031 self.metadata[
"scaleScienceVarianceFactor"] = sciVarFactor
1057 self.
log.info(
"Unable to evaluate PSF at the average position. "
1058 "Evaluting PSF on a grid of points."
1062 fwhmExposureBuffer=self.config.makeKernel.fwhmExposureBuffer,
1063 fwhmExposureGrid=self.config.makeKernel.fwhmExposureGrid
1067 fwhmExposureBuffer=self.config.makeKernel.fwhmExposureBuffer,
1068 fwhmExposureGrid=self.config.makeKernel.fwhmExposureGrid
1077 if np.isnan(maglim_science):
1078 self.
log.warning(
"Limiting magnitude of the science image is NaN!")
1079 fluxlim_science = (maglim_science*u.ABmag).to_value(u.nJy)
1081 if np.isnan(maglim_template):
1082 self.
log.info(
"Cannot evaluate template limiting mag; adopting science limiting mag for diffim")
1083 maglim_diffim = maglim_science
1085 fluxlim_template = (maglim_template*u.ABmag).to_value(u.nJy)
1086 maglim_diffim = (np.sqrt(fluxlim_science**2 + fluxlim_template**2)*u.nJy).to(u.ABmag).value
1087 self.metadata[
"scienceLimitingMagnitude"] = maglim_science
1088 self.metadata[
"templateLimitingMagnitude"] = maglim_template
1089 self.metadata[
"diffimLimitingMagnitude"] = maglim_diffim
1092 """Update the science and template mask planes before differencing.
1096 template : `lsst.afw.image.Exposure`
1097 Template exposure, warped to match the science exposure.
1098 The template mask planes will be erased, except for a few specified
1100 science : `lsst.afw.image.Exposure`
1101 Science exposure to subtract from the template.
1102 The DETECTED and DETECTED_NEGATIVE mask planes of the science image
1105 self.
_clearMask(science.mask, clearMaskPlanes=[
"DETECTED",
"DETECTED_NEGATIVE"])
1112 clearMaskPlanes = [mp
for mp
in template.mask.getMaskPlaneDict().keys()
1113 if mp
not in self.config.preserveTemplateMask]
1114 renameMaskPlanes = [mp
for mp
in self.config.renameTemplateMask
1115 if mp
in template.mask.getMaskPlaneDict().keys()]
1120 if "FAKE" in science.mask.getMaskPlaneDict().keys():
1121 self.
log.info(
"Adding injected mask plane to science image")
1123 if "FAKE" in template.mask.getMaskPlaneDict().keys():
1124 self.
log.info(
"Adding injected mask plane to template image")
1126 if "INJECTED" in renameMaskPlanes:
1127 renameMaskPlanes.remove(
"INJECTED")
1128 if "INJECTED_TEMPLATE" in clearMaskPlanes:
1129 clearMaskPlanes.remove(
"INJECTED_TEMPLATE")
1131 for maskPlane
in renameMaskPlanes:
1133 self.
_clearMask(template.mask, clearMaskPlanes=clearMaskPlanes)
1137 """Rename a mask plane by adding the new name and copying the data.
1141 mask : `lsst.afw.image.Mask`
1142 The mask image to update in place.
1144 The name of the existing mask plane to copy.
1145 newMaskPlane : `str`
1146 The new name of the mask plane that will be added.
1147 If the mask plane already exists, it will be updated in place.
1149 mask.addMaskPlane(newMaskPlane)
1150 originBitMask = mask.getPlaneBitMask(maskPlane)
1151 destinationBitMask = mask.getPlaneBitMask(newMaskPlane)
1152 mask.array |= ((mask.array & originBitMask) > 0)*destinationBitMask
1155 """Clear the mask plane of an exposure.
1159 mask : `lsst.afw.image.Mask`
1160 The mask plane to erase, which will be modified in place.
1161 clearMaskPlanes : `list` of `str`, optional
1162 Erase the specified mask planes.
1163 If not supplied, the entire mask will be erased.
1165 if clearMaskPlanes
is None:
1166 clearMaskPlanes = list(mask.getMaskPlaneDict().keys())
1168 bitMaskToClear = mask.getPlaneBitMask(clearMaskPlanes)
1169 mask &= ~bitMaskToClear
1173 SubtractScoreOutputConnections):
1178 pipelineConnections=AlardLuptonPreconvolveSubtractConnections):
1183 """Subtract a template from a science image, convolving the science image
1184 before computing the kernel, and also convolving the template before
1187 ConfigClass = AlardLuptonPreconvolveSubtractConfig
1188 _DefaultName =
"alardLuptonPreconvolveSubtract"
1190 def run(self, template, science, sources, visitSummary=None):
1191 """Preconvolve the science image with its own PSF,
1192 convolve the template image with a PSF-matching kernel and subtract
1193 from the preconvolved science image.
1197 template : `lsst.afw.image.ExposureF`
1198 The template image, which has previously been warped to the science
1199 image. The template bbox will be padded by a few pixels compared to
1201 science : `lsst.afw.image.ExposureF`
1202 The science exposure.
1203 sources : `lsst.afw.table.SourceCatalog`
1204 Identified sources on the science exposure. This catalog is used to
1205 select sources in order to perform the AL PSF matching on stamp
1207 visitSummary : `lsst.afw.table.ExposureCatalog`, optional
1208 Exposure catalog with complete external calibrations. Catalog uses
1209 the detector id for the catalog id, sorted on id for fast lookup.
1213 results : `lsst.pipe.base.Struct`
1214 ``scoreExposure`` : `lsst.afw.image.ExposureF`
1215 Result of subtracting the convolved template and science
1216 images. Attached PSF is that of the original science image.
1217 ``matchedTemplate`` : `lsst.afw.image.ExposureF`
1218 Warped and PSF-matched template exposure. Attached PSF is that
1219 of the original science image.
1220 ``matchedScience`` : `lsst.afw.image.ExposureF`
1221 The science exposure after convolving with its own PSF.
1222 Attached PSF is that of the original science image.
1223 ``backgroundModel`` : `lsst.afw.math.Function2D`
1224 Background model that was fit while solving for the
1226 ``psfMatchingKernel`` : `lsst.afw.math.Kernel`
1227 Final kernel used to PSF-match the template to the science
1230 self.
_prepareInputs(template, science, visitSummary=visitSummary)
1233 scienceKernel = science.psf.getKernel()
1235 interpolateBadMaskPlanes=
True)
1236 self.metadata[
"convolvedExposure"] =
"Preconvolution"
1238 kernelSources = self.
_sourceSelector(template, matchedScience, sources, preconvolved=
True)
1239 subtractResults = self.
runPreconvolve(template, science, matchedScience,
1240 kernelSources, scienceKernel)
1243 self.
log.warning(
"Failed to match template. Checking coverage")
1246 self.config.minTemplateFractionForExpectedSuccess,
1247 exceptionMessage=
"Template coverage lower than expected to succeed."
1248 f
" Failure is tolerable: {e}")
1252 return subtractResults
1254 def runPreconvolve(self, template, science, matchedScience, kernelSources, preConvKernel):
1255 """Convolve the science image with its own PSF, then convolve the
1256 template with a matching kernel and subtract to form the Score
1261 template : `lsst.afw.image.ExposureF`
1262 Template exposure, warped to match the science exposure.
1263 science : `lsst.afw.image.ExposureF`
1264 Science exposure to subtract from the template.
1265 matchedScience : `lsst.afw.image.ExposureF`
1266 The science exposure, convolved with the reflection of its own PSF.
1267 kernelSources : `lsst.afw.table.SourceCatalog`
1268 Identified sources on the science exposure. This catalog is used to
1269 select sources in order to perform the AL PSF matching on stamp
1271 preConvKernel : `lsst.afw.math.Kernel`
1272 The reflection of the kernel that was used to preconvolve the
1273 `science` exposure. Must be normalized to sum to 1.
1277 results : `lsst.pipe.base.Struct`
1279 ``scoreExposure`` : `lsst.afw.image.ExposureF`
1280 Result of subtracting the convolved template and science
1281 images. Attached PSF is that of the original science image.
1282 ``matchedTemplate`` : `lsst.afw.image.ExposureF`
1283 Warped and PSF-matched template exposure. Attached PSF is that
1284 of the original science image.
1285 ``matchedScience`` : `lsst.afw.image.ExposureF`
1286 The science exposure after convolving with its own PSF.
1287 Attached PSF is that of the original science image.
1288 ``backgroundModel`` : `lsst.afw.math.Function2D`
1289 Background model that was fit while solving for the
1291 ``psfMatchingKernel`` : `lsst.afw.math.Kernel`
1292 Final kernel used to PSF-match the template to the science
1295 bbox = science.getBBox()
1296 innerBBox = preConvKernel.shrinkBBox(bbox)
1298 kernelResult = self.makeKernel.
run(template[innerBBox], matchedScience[innerBBox], kernelSources,
1303 matchedTemplate = self.
_convolveExposure(template, kernelResult.psfMatchingKernel,
1307 interpolateBadMaskPlanes=
True,
1308 photoCalib=science.photoCalib)
1310 backgroundModel=(kernelResult.backgroundModel
1311 if self.config.doSubtractBackground
else None))
1312 correctedScore = self.
finalize(template[bbox], science, score,
1313 kernelResult.psfMatchingKernel,
1314 templateMatched=
True, preConvMode=
True,
1315 preConvKernel=preConvKernel)
1317 return lsst.pipe.base.Struct(scoreExposure=correctedScore,
1318 matchedTemplate=matchedTemplate,
1319 matchedScience=matchedScience,
1320 backgroundModel=kernelResult.backgroundModel,
1321 psfMatchingKernel=kernelResult.psfMatchingKernel,
1322 kernelSources=kernelSources)
1326 exceptionMessage=""):
1327 """Raise NoWorkFound if template coverage < requiredTemplateFraction
1331 templateExposure : `lsst.afw.image.ExposureF`
1332 The template exposure to check
1333 logger : `logging.Logger`
1334 Logger for printing output.
1335 requiredTemplateFraction : `float`, optional
1336 Fraction of pixels of the science image required to have coverage
1338 exceptionMessage : `str`, optional
1339 Message to include in the exception raised if the template coverage
1344 templateCoverageFraction: `float`
1345 Fraction of pixels in the template with data.
1349 lsst.pipe.base.NoWorkFound
1350 Raised if fraction of good pixels, defined as not having NO_DATA
1351 set, is less than the requiredTemplateFraction
1355 noTemplate = templateExposure.mask.array & templateExposure.mask.getPlaneBitMask(
'NO_DATA')
1358 noScience = scienceExposure.mask.array & scienceExposure.mask.getPlaneBitMask(
'NO_DATA')
1359 pixNoData = np.count_nonzero(noTemplate | noScience)
1360 pixGood = templateExposure.getBBox().getArea() - pixNoData
1361 templateCoverageFraction = pixGood/templateExposure.getBBox().getArea()
1362 logger.info(
"template has %d good pixels (%.1f%%)", pixGood, 100*templateCoverageFraction)
1364 if templateCoverageFraction < requiredTemplateFraction:
1365 message = (
"Insufficient Template Coverage. (%.1f%% < %.1f%%)" % (
1366 100*templateCoverageFraction,
1367 100*requiredTemplateFraction))
1368 raise lsst.pipe.base.NoWorkFound(message +
" " + exceptionMessage)
1369 return templateCoverageFraction
1373 """Subtract template from science, propagating relevant metadata.
1377 science : `lsst.afw.Exposure`
1378 The input science image.
1379 template : `lsst.afw.Exposure`
1380 The template to subtract from the science image.
1381 backgroundModel : `lsst.afw.MaskedImage`, optional
1382 Differential background model
1386 difference : `lsst.afw.Exposure`
1387 The subtracted image.
1389 difference = science.clone()
1390 if backgroundModel
is not None:
1391 difference.maskedImage -= backgroundModel
1392 difference.maskedImage -= template.maskedImage
1397 """Determine that the PSF of ``exp1`` is not wider than that of ``exp2``.
1401 exp1 : `~lsst.afw.image.Exposure`
1402 Exposure with the reference point spread function (PSF) to evaluate.
1403 exp2 : `~lsst.afw.image.Exposure`
1404 Exposure with a candidate point spread function (PSF) to evaluate.
1405 fwhmExposureBuffer : `float`
1406 Fractional buffer margin to be left out of all sides of the image
1407 during the construction of the grid to compute mean PSF FWHM in an
1408 exposure, if the PSF is not available at its average position.
1409 fwhmExposureGrid : `int`
1410 Grid size to compute the mean FWHM in an exposure, if the PSF is not
1411 available at its average position.
1415 True if ``exp1`` has a PSF that is not wider than that of ``exp2`` in
1419 shape1 = getPsfFwhm(exp1.psf, average=
False)
1420 shape2 = getPsfFwhm(exp2.psf, average=
False)
1422 shape1 = evaluateMeanPsfFwhm(exp1,
1423 fwhmExposureBuffer=fwhmExposureBuffer,
1424 fwhmExposureGrid=fwhmExposureGrid
1426 shape2 = evaluateMeanPsfFwhm(exp2,
1427 fwhmExposureBuffer=fwhmExposureBuffer,
1428 fwhmExposureGrid=fwhmExposureGrid
1430 return shape1 <= shape2
1433 xTest = shape1[0] <= shape2[0]
1434 yTest = shape1[1] <= shape2[1]
1435 return xTest | yTest
1439 pipelineConnections=SimplifiedSubtractConnections):
1440 mode = lsst.pex.config.ChoiceField(
1442 default=
"convolveTemplate",
1443 allowed={
"auto":
"Choose which image to convolve at runtime.",
1444 "convolveScience":
"Only convolve the science image.",
1445 "convolveTemplate":
"Only convolve the template image."},
1446 doc=
"Choose which image to convolve at runtime, or require that a specific image is convolved."
1448 useExistingKernel = lsst.pex.config.Field(
1451 doc=
"Use a pre-existing PSF matching kernel?"
1452 "If False, source detection and measurement will be run."
1457 """Compute the image difference of a science and template image using
1458 the Alard & Lupton (1998) algorithm.
1460 ConfigClass = SimplifiedSubtractConfig
1461 _DefaultName =
"simplifiedSubtract"
1464 def run(self, template, science, visitSummary=None, inputPsfMatchingKernel=None):
1465 """PSF match, subtract, and decorrelate two images.
1469 template : `lsst.afw.image.ExposureF`
1470 Template exposure, warped to match the science exposure.
1471 science : `lsst.afw.image.ExposureF`
1472 Science exposure to subtract from the template.
1473 visitSummary : `lsst.afw.table.ExposureCatalog`, optional
1474 Exposure catalog with external calibrations to be applied. Catalog
1475 uses the detector id for the catalog id, sorted on id for fast
1477 inputPsfMatchingKernel : `lsst.afw.math.Kernel`, optional
1478 Pre-existing PSF matching kernel to use for convolution.
1479 Required, and only used, if ``config.useExistingKernel`` is set.
1483 results : `lsst.pipe.base.Struct`
1484 ``difference`` : `lsst.afw.image.ExposureF`
1485 Result of subtracting template and science.
1486 ``matchedTemplate`` : `lsst.afw.image.ExposureF`
1487 Warped and PSF-matched template exposure.
1488 ``backgroundModel`` : `lsst.afw.math.Function2D`
1489 Background model that was fit while solving for the
1491 ``psfMatchingKernel`` : `lsst.afw.math.Kernel`
1492 Kernel used to PSF-match the convolved image.
1493 ``kernelSources` : `lsst.afw.table.SourceCatalog`
1494 Sources detected on the science image that were used to
1495 construct the PSF-matching kernel.
1499 lsst.pipe.base.NoWorkFound
1500 Raised if fraction of good pixels, defined as not having NO_DATA
1501 set, is less then the configured requiredTemplateFraction
1503 self.
_prepareInputs(template, science, visitSummary=visitSummary)
1507 if self.config.useExistingKernel:
1508 psfMatchingKernel = inputPsfMatchingKernel
1509 backgroundModel =
None
1510 kernelSources =
None
1512 kernelResult = self.
runMakeKernel(template, science, convolveTemplate=convolveTemplate,
1513 runSourceDetection=
True)
1514 psfMatchingKernel = kernelResult.psfMatchingKernel
1515 kernelSources = kernelResult.kernelSources
1516 if self.config.doSubtractBackground:
1517 backgroundModel = kernelResult.backgroundModel
1519 backgroundModel =
None
1520 if convolveTemplate:
1522 backgroundModel=backgroundModel)
1525 backgroundModel=backgroundModel)
1526 if kernelSources
is not None:
1527 subtractResults.kernelSources = kernelSources
1528 return subtractResults
1532 """Replace masked image pixels with interpolated values.
1536 maskedImage : `lsst.afw.image.MaskedImage`
1537 Image on which to perform interpolation.
1538 badMaskPlanes : `list` of `str`
1539 List of mask planes to interpolate over.
1540 fallbackValue : `float`, optional
1541 Value to set when interpolation fails.
1546 The number of masked pixels that were replaced.
1548 imgBadMaskPlanes = [
1549 maskPlane
for maskPlane
in badMaskPlanes
if maskPlane
in maskedImage.mask.getMaskPlaneDict()
1552 image = maskedImage.image.array
1553 badPixels = (maskedImage.mask.array & maskedImage.mask.getPlaneBitMask(imgBadMaskPlanes)) > 0
1554 image[badPixels] = np.nan
1555 if fallbackValue
is None:
1556 fallbackValue = np.nanmedian(image)
1559 image[badPixels] = fallbackValue
1560 return np.sum(badPixels)
run(self, template, science, sources, visitSummary=None)
runPreconvolve(self, template, science, matchedScience, kernelSources, preConvKernel)
_clearMask(self, mask, clearMaskPlanes=None)
_prepareInputs(self, template, science, visitSummary=None)
chooseConvolutionMethod(self, template, science)
runConvolveTemplate(self, template, science, psfMatchingKernel, backgroundModel=None)
runQuantum(self, butlerQC, inputRefs, outputRefs)
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)
updateMasks(self, template, science)
runMakeKernel(self, template, science, sources=None, convolveTemplate=True, runSourceDetection=False)
_convolveExposure(self, exposure, kernel, convolutionControl, bbox=None, psf=None, photoCalib=None, interpolateBadMaskPlanes=False)
_sourceSelector(self, template, science, sources, fallback=False, preconvolved=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)
void convolve(OutImageT &convolvedImage, InImageT const &inImage, KernelT const &kernel, ConvolutionControl const &convolutionControl=ConvolutionControl())
_subtractImages(science, template, backgroundModel=None)
checkTemplateIsSufficient(templateExposure, scienceExposure, logger, requiredTemplateFraction=0., exceptionMessage="")
_interpolateImage(maskedImage, badMaskPlanes, fallbackValue=None)
_shapeTest(exp1, exp2, fwhmExposureBuffer, fwhmExposureGrid)