22from astropy
import units
as u
23from astropy.stats
import gaussian_fwhm_to_sigma
31 computeDifferenceImageMetrics, computePSFNoiseEquivalentArea,
32 checkMask, setSourceFootprints)
33from lsst.meas.algorithms
import ScaleVarianceTask, ScienceSourceSelectorTask
38from .
import MakeKernelTask, DecorrelateALKernelTask
39from lsst.utils.timer
import timeMethod
41__all__ = [
"AlardLuptonSubtractConfig",
"AlardLuptonSubtractTask",
42 "AlardLuptonPreconvolveSubtractConfig",
"AlardLuptonPreconvolveSubtractTask",
43 "SimplifiedSubtractConfig",
"SimplifiedSubtractTask",
44 "InsufficientKernelSourcesError"]
46_dimensions = (
"instrument",
"visit",
"detector")
47_defaultTemplates = {
"coaddName":
"deep",
"fakesType":
""}
51 """Raised when there are too few sources to calculate the PSF matching
55 msg = (f
"Only {nSources} sources were selected for PSF matching,"
56 f
" but {nRequired} are required.")
69 dimensions=_dimensions,
70 defaultTemplates=_defaultTemplates):
71 template = connectionTypes.Input(
72 doc=
"Input warped template to subtract.",
73 dimensions=(
"instrument",
"visit",
"detector"),
74 storageClass=
"ExposureF",
75 name=
"{fakesType}{coaddName}Diff_templateExp"
77 science = connectionTypes.Input(
78 doc=
"Input science exposure to subtract from.",
79 dimensions=(
"instrument",
"visit",
"detector"),
80 storageClass=
"ExposureF",
81 name=
"{fakesType}calexp"
83 sources = connectionTypes.Input(
84 doc=
"Sources measured on the science exposure; "
85 "used to select sources for making the matching kernel.",
86 dimensions=(
"instrument",
"visit",
"detector"),
87 storageClass=
"SourceCatalog",
90 visitSummary = connectionTypes.Input(
91 doc=(
"Per-visit catalog with final calibration objects. "
92 "These catalogs use the detector id for the catalog id, "
93 "sorted on id for fast lookup."),
94 dimensions=(
"instrument",
"visit"),
95 storageClass=
"ExposureCatalog",
96 name=
"finalVisitSummary",
101 if not config.doApplyExternalCalibrations:
106 dimensions=_dimensions,
107 defaultTemplates=_defaultTemplates):
108 difference = connectionTypes.Output(
109 doc=
"Result of subtracting convolved template from science image.",
110 dimensions=(
"instrument",
"visit",
"detector"),
111 storageClass=
"ExposureF",
112 name=
"{fakesType}{coaddName}Diff_differenceTempExp",
114 matchedTemplate = connectionTypes.Output(
115 doc=
"Warped and PSF-matched template used to create `subtractedExposure`.",
116 dimensions=(
"instrument",
"visit",
"detector"),
117 storageClass=
"ExposureF",
118 name=
"{fakesType}{coaddName}Diff_matchedExp",
120 psfMatchingKernel = connectionTypes.Output(
121 doc=
"Kernel used to PSF match the science and template images.",
122 dimensions=(
"instrument",
"visit",
"detector"),
123 storageClass=
"MatchingKernel",
124 name=
"{fakesType}{coaddName}Diff_psfMatchKernel",
126 kernelSources = connectionTypes.Output(
127 doc=
"Final selection of sources used for psf matching.",
128 dimensions=(
"instrument",
"visit",
"detector"),
129 storageClass=
"SourceCatalog",
130 name=
"{fakesType}{coaddName}Diff_psfMatchSources"
135 dimensions=_dimensions,
136 defaultTemplates=_defaultTemplates):
137 scoreExposure = connectionTypes.Output(
138 doc=
"The maximum likelihood image, used for the detection of diaSources.",
139 dimensions=(
"instrument",
"visit",
"detector"),
140 storageClass=
"ExposureF",
141 name=
"{fakesType}{coaddName}Diff_scoreTempExp",
143 psfMatchingKernel = connectionTypes.Output(
144 doc=
"Kernel used to PSF match the science and template images.",
145 dimensions=(
"instrument",
"visit",
"detector"),
146 storageClass=
"MatchingKernel",
147 name=
"{fakesType}{coaddName}Diff_psfScoreMatchKernel",
149 kernelSources = connectionTypes.Output(
150 doc=
"Final selection of sources used for psf matching.",
151 dimensions=(
"instrument",
"visit",
"detector"),
152 storageClass=
"SourceCatalog",
153 name=
"{fakesType}{coaddName}Diff_psfScoreMatchSources"
161class SimplifiedSubtractConnections(SubtractInputConnections, SubtractImageOutputConnections):
162 inputPsfMatchingKernel = connectionTypes.Input(
163 doc=
"Kernel used to PSF match the science and template images.",
164 dimensions=(
"instrument",
"visit",
"detector"),
165 storageClass=
"MatchingKernel",
166 name=
"{fakesType}{coaddName}Diff_psfMatchKernel",
172 if config.useExistingKernel:
180 makeKernel = lsst.pex.config.ConfigurableField(
181 target=MakeKernelTask,
182 doc=
"Task to construct a matching kernel for convolution.",
184 doDecorrelation = lsst.pex.config.Field(
187 doc=
"Perform diffim decorrelation to undo pixel correlation due to A&L "
188 "kernel convolution? If True, also update the diffim PSF."
190 decorrelate = lsst.pex.config.ConfigurableField(
191 target=DecorrelateALKernelTask,
192 doc=
"Task to decorrelate the image difference.",
194 requiredTemplateFraction = lsst.pex.config.Field(
197 doc=
"Raise NoWorkFound and do not attempt image subtraction if template covers less than this "
198 " fraction of pixels. Setting to 0 will always attempt image subtraction."
200 minTemplateFractionForExpectedSuccess = lsst.pex.config.Field(
203 doc=
"Raise NoWorkFound if PSF-matching fails and template covers less than this fraction of pixels."
204 " If the fraction of pixels covered by the template is less than this value (and greater than"
205 " requiredTemplateFraction) this task is attempted but failure is anticipated and tolerated."
207 doScaleVariance = lsst.pex.config.Field(
210 doc=
"Scale variance of the science image? Note that the template variance is NOT scaled"
211 " here. The template variance may be scaled independently in ``GetTemplateTask``."
213 scaleVariance = lsst.pex.config.ConfigurableField(
214 target=ScaleVarianceTask,
215 doc=
"Subtask to rescale the variance of the template to the statistically expected level."
217 doSubtractBackground = lsst.pex.config.Field(
218 doc=
"Subtract the background fit when solving the kernel? "
219 "It is generally better to instead subtract the background in detectAndMeasure.",
223 doApplyExternalCalibrations = lsst.pex.config.Field(
225 "Replace science Exposure's calibration objects with those"
226 " in visitSummary. Ignored if `doApplyFinalizedPsf is True."
231 sourceSelector = lsst.pex.config.ConfigurableField(
232 target=ScienceSourceSelectorTask,
233 doc=
"Task to select sources to be used for PSF matching.",
235 fallbackSourceSelector = lsst.pex.config.ConfigurableField(
236 target=ScienceSourceSelectorTask,
237 doc=
"Task to select sources to be used for PSF matching."
238 "Used only if the kernel calculation fails and"
239 "`allowKernelSourceDetection` is set. The fallback source detection"
240 " will not include all of the same plugins as the original source "
241 " detection, so not all of the same flags can be used.",
243 detectionThreshold = lsst.pex.config.Field(
246 doc=
"Minimum signal to noise ratio of detected sources "
247 "to use for calculating the PSF matching kernel.",
248 deprecated=
"No longer used. Will be removed after v30"
250 detectionThresholdMax = lsst.pex.config.Field(
253 doc=
"Maximum signal to noise ratio of detected sources "
254 "to use for calculating the PSF matching kernel.",
255 deprecated=
"No longer used. Will be removed after v30"
257 restrictKernelEdgeSources = lsst.pex.config.Field(
260 doc=
"Exclude sources close to the edge from the kernel calculation?"
262 maxKernelSources = lsst.pex.config.Field(
265 doc=
"Maximum number of sources to use for calculating the PSF matching kernel."
266 "Set to -1 to disable."
268 minKernelSources = lsst.pex.config.Field(
271 doc=
"Minimum number of sources needed for calculating the PSF matching kernel."
273 excludeMaskPlanes = lsst.pex.config.ListField(
275 default=(
"NO_DATA",
"BAD",
"SAT",
"EDGE",
"FAKE",
"HIGH_VARIANCE"),
276 doc=
"Template mask planes to exclude when selecting sources for PSF matching.",
278 badMaskPlanes = lsst.pex.config.ListField(
280 default=(
"NO_DATA",
"BAD",
"SAT",
"EDGE"),
281 doc=
"Mask planes to interpolate over."
283 preserveTemplateMask = lsst.pex.config.ListField(
285 default=(
"NO_DATA",
"BAD",
"HIGH_VARIANCE"),
286 doc=
"Mask planes from the template to propagate to the image difference."
288 renameTemplateMask = lsst.pex.config.ListField(
290 default=(
"SAT",
"INJECTED",
"INJECTED_CORE",),
291 doc=
"Mask planes from the template to propagate to the image difference"
292 "with '_TEMPLATE' appended to the name."
294 preserveMaskPlanes = lsst.pex.config.ListField(
296 default=(
"INJECTED",
"INJECTED_CORE",
"INJECTED_TEMPLATE",
"INJECTED_CORE_TEMPLATE"),
297 doc=
"Mask planes to preserve without dilation when convolving the image.",
299 allowKernelSourceDetection = lsst.pex.config.Field(
302 doc=
"Re-run source detection for kernel candidates if an error is"
303 " encountered while calculating the matching kernel."
310 self.
makeKernel.kernel.active.fitForBackground =
True
311 self.
makeKernel.kernel.active.spatialKernelOrder = 1
312 self.
makeKernel.kernel.active.spatialBgOrder = 2
315 doSignalToNoise =
True
317 signalToNoiseMinimum = 10
318 signalToNoiseMaximum = 500
339 pipelineConnections=AlardLuptonSubtractConnections):
340 mode = lsst.pex.config.ChoiceField(
342 default=
"convolveTemplate",
343 allowed={
"auto":
"Choose which image to convolve at runtime.",
344 "convolveScience":
"Only convolve the science image.",
345 "convolveTemplate":
"Only convolve the template image."},
346 doc=
"Choose which image to convolve at runtime, or require that a specific image is convolved."
351 """Compute the image difference of a science and template image using
352 the Alard & Lupton (1998) algorithm.
354 ConfigClass = AlardLuptonSubtractConfig
355 _DefaultName =
"alardLuptonSubtract"
356 usePreconvolution =
False
357 """Whether this task preconvolves the science image with its own PSF
358 before kernel-matching. Subclasses that preconvolve override this to
363 self.makeSubtask(
"decorrelate")
364 self.makeSubtask(
"makeKernel")
365 self.makeSubtask(
"sourceSelector")
366 self.makeSubtask(
"fallbackSourceSelector")
367 if self.config.doScaleVariance:
368 self.makeSubtask(
"scaleVariance")
377 """Replace calibrations (psf, and ApCorrMap) on this exposure with
382 exposure : `lsst.afw.image.exposure.Exposure`
383 Input exposure to adjust calibrations.
384 visitSummary : `lsst.afw.table.ExposureCatalog`
385 Exposure catalog with external calibrations to be applied. Catalog
386 uses the detector id for the catalog id, sorted on id for fast
391 exposure : `lsst.afw.image.exposure.Exposure`
392 Exposure with adjusted calibrations.
394 detectorId = exposure.info.getDetector().getId()
396 row = visitSummary.find(detectorId)
398 self.
log.warning(
"Detector id %s not found in external calibrations catalog; "
399 "Using original calibrations.", detectorId)
402 apCorrMap = row.getApCorrMap()
404 self.
log.warning(
"Detector id %s has None for psf in "
405 "external calibrations catalog; Using original psf and aperture correction.",
407 elif apCorrMap
is None:
408 self.
log.warning(
"Detector id %s has None for apCorrMap in "
409 "external calibrations catalog; Using original psf and aperture correction.",
413 exposure.info.setApCorrMap(apCorrMap)
418 inputs = butlerQC.get(inputRefs)
421 results = self.
run(**inputs)
422 except lsst.pipe.base.AlgorithmError
as e:
423 error = lsst.pipe.base.AnnotatedPartialOutputsError.annotate(e, self, log=self.
log)
427 butlerQC.put(results, outputRefs)
430 def run(self, template, science, sources, visitSummary=None):
431 """PSF match, subtract, and decorrelate two images.
435 template : `lsst.afw.image.ExposureF`
436 Template exposure, warped to match the science exposure.
437 science : `lsst.afw.image.ExposureF`
438 Science exposure to subtract from the template.
439 sources : `lsst.afw.table.SourceCatalog`
440 Identified sources on the science exposure. This catalog is used to
441 select sources in order to perform the AL PSF matching on stamp
443 visitSummary : `lsst.afw.table.ExposureCatalog`, optional
444 Exposure catalog with external calibrations to be applied. Catalog
445 uses the detector id for the catalog id, sorted on id for fast
450 results : `lsst.pipe.base.Struct`
451 ``difference`` : `lsst.afw.image.ExposureF`
452 Result of subtracting template and science.
453 ``matchedTemplate`` : `lsst.afw.image.ExposureF`
454 Warped and PSF-matched template exposure.
455 ``backgroundModel`` : `lsst.afw.math.Function2D`
456 Background model that was fit while solving for the
458 ``psfMatchingKernel`` : `lsst.afw.math.Kernel`
459 Kernel used to PSF-match the convolved image.
460 ``kernelSources` : `lsst.afw.table.SourceCatalog`
461 Sources from the input catalog that were used to construct the
469 kernelResult = self.
runMakeKernel(template, science, sources=sources,
470 convolveTemplate=convolveTemplate,
471 runSourceDetection=
False)
473 if self.config.doSubtractBackground:
474 backgroundModel = kernelResult.backgroundModel
476 backgroundModel =
None
478 subtractResults = self.
runConvolveTemplate(template, science, kernelResult.psfMatchingKernel,
479 backgroundModel=backgroundModel)
481 subtractResults = self.
runConvolveScience(template, science, kernelResult.psfMatchingKernel,
482 backgroundModel=backgroundModel)
483 subtractResults.kernelSources = kernelResult.kernelSources
485 metrics = computeDifferenceImageMetrics(science, subtractResults.difference, sources)
487 self.metadata[
"differenceFootprintRatioMean"] = metrics.differenceFootprintRatioMean
488 self.metadata[
"differenceFootprintRatioStdev"] = metrics.differenceFootprintRatioStdev
489 self.metadata[
"differenceFootprintSkyRatioMean"] = metrics.differenceFootprintSkyRatioMean
490 self.metadata[
"differenceFootprintSkyRatioStdev"] = metrics.differenceFootprintSkyRatioStdev
491 self.
log.info(
"Mean, stdev of ratio of difference to science "
492 "pixels in star footprints: %5.4f, %5.4f",
493 self.metadata[
"differenceFootprintRatioMean"],
494 self.metadata[
"differenceFootprintRatioStdev"])
496 return subtractResults
499 """Determine whether the template should be convolved with the PSF
504 template : `lsst.afw.image.ExposureF`
505 Template exposure, warped to match the science exposure.
506 science : `lsst.afw.image.ExposureF`
507 Science exposure to subtract from the template.
511 convolveTemplate : `bool`
512 Convolve the template to match the two images?
517 If an unsupported convolution mode is supplied.
520 raise RuntimeError(
"Choosing a convolution method is incompatible with preconvolution!")
521 if self.config.mode ==
"auto":
524 fwhmExposureBuffer=self.config.makeKernel.fwhmExposureBuffer,
525 fwhmExposureGrid=self.config.makeKernel.fwhmExposureGrid)
528 self.
log.info(
"Average template PSF size is greater, "
529 "but science PSF greater in one dimension: convolving template image.")
531 self.
log.info(
"Science PSF size is greater: convolving template image.")
533 self.
log.info(
"Template PSF size is greater: convolving science image.")
534 elif self.config.mode ==
"convolveTemplate":
535 self.
log.info(
"`convolveTemplate` is set: convolving template image.")
536 convolveTemplate =
True
537 elif self.config.mode ==
"convolveScience":
538 self.
log.info(
"`convolveScience` is set: convolving science image.")
539 convolveTemplate =
False
541 raise RuntimeError(f
"Cannot handle AlardLuptonSubtract mode: {self.config.mode}")
542 return convolveTemplate
544 def runMakeKernel(self, template, science, sources=None, convolveTemplate=True, runSourceDetection=False):
545 """Construct the PSF-matching kernel. Not used for preconvolution.
549 template : `lsst.afw.image.ExposureF`
550 Template exposure, warped to match the science exposure.
551 science : `lsst.afw.image.ExposureF`
552 Science exposure to subtract from the template.
553 sources : `lsst.afw.table.SourceCatalog`
554 Identified sources on the science exposure. This catalog is used to
555 select sources in order to perform the AL PSF matching on stamp
557 Not used if ``runSourceDetection`` is set.
558 convolveTemplate : `bool`, optional
559 Construct the matching kernel to convolve the template?
560 runSourceDetection : `bool`, optional
561 Run a minimal version of source detection to determine kernel
562 candidates? If False, a source list to select kernel candidates
563 from must be supplied.
567 results : `lsst.pipe.base.Struct`
568 ``backgroundModel`` : `lsst.afw.math.Function2D`
569 Background model that was fit while solving for the
571 ``psfMatchingKernel`` : `lsst.afw.math.Kernel`
572 Kernel used to PSF-match the convolved image.
573 ``kernelSources` : `lsst.afw.table.SourceCatalog`
574 Sources from the input catalog that were used to construct the
578 raise RuntimeError(
"Incorrect matching kernel calculation configured. "
579 "`runMakeKernel` can't be called if `usePreconvolution` is set.")
591 if runSourceDetection:
595 kernelResult = self.makeKernel.
run(reference, target, kernelSources,
597 templateFwhmPix=referenceFwhmPix,
598 scienceFwhmPix=targetFwhmPix)
600 self.
log.warning(
"Failed to match template. Checking coverage")
603 self.config.minTemplateFractionForExpectedSuccess,
604 exceptionMessage=
"Template coverage lower than expected to succeed."
605 f
" Failure is tolerable: {e}")
609 return lsst.pipe.base.Struct(backgroundModel=kernelResult.backgroundModel,
610 psfMatchingKernel=kernelResult.psfMatchingKernel,
611 kernelSources=kernelSources)
614 """Run detection on the science image and use the template mask plane
615 to reject candidate sources.
619 template : `lsst.afw.image.ExposureF`
620 Template exposure, warped to match the science exposure.
621 science : `lsst.afw.image.ExposureF`
622 Science exposure to subtract from the template.
626 kernelSources : `lsst.afw.table.SourceCatalog`
627 Sources from the input catalog to use to construct the
630 kernelSize = self.makeKernel.makeKernelBasisList(
632 sources = self.makeKernel.makeCandidateList(template, science, kernelSize,
638 """Convolve the template image with a PSF-matching kernel and subtract
639 from the science image.
643 template : `lsst.afw.image.ExposureF`
644 Template exposure, warped to match the science exposure.
645 science : `lsst.afw.image.ExposureF`
646 Science exposure to subtract from the template.
647 psfMatchingKernel : `lsst.afw.math.Kernel`
648 Kernel to be used to PSF-match the science image to the template.
649 backgroundModel : `lsst.afw.math.Function2D`, optional
650 Background model that was fit while solving for the PSF-matching
655 results : `lsst.pipe.base.Struct`
657 ``difference`` : `lsst.afw.image.ExposureF`
658 Result of subtracting template and science.
659 ``matchedTemplate`` : `lsst.afw.image.ExposureF`
660 Warped and PSF-matched template exposure.
661 ``backgroundModel`` : `lsst.afw.math.Function2D`
662 Background model that was fit while solving for the PSF-matching kernel
663 ``psfMatchingKernel`` : `lsst.afw.math.Kernel`
664 Kernel used to PSF-match the template to the science image.
666 self.metadata[
"convolvedExposure"] =
"Template"
670 bbox=science.getBBox(),
672 photoCalib=science.photoCalib)
674 difference =
_subtractImages(science, matchedTemplate, backgroundModel=backgroundModel)
675 correctedExposure = self.
finalize(template, science, difference,
677 templateMatched=
True)
679 return lsst.pipe.base.Struct(difference=correctedExposure,
680 matchedTemplate=matchedTemplate,
681 matchedScience=science,
682 backgroundModel=backgroundModel,
683 psfMatchingKernel=psfMatchingKernel)
686 """Convolve the science image with a PSF-matching kernel and subtract
691 template : `lsst.afw.image.ExposureF`
692 Template exposure, warped to match the science exposure.
693 science : `lsst.afw.image.ExposureF`
694 Science exposure to subtract from the template.
695 psfMatchingKernel : `lsst.afw.math.Kernel`
696 Kernel to be used to PSF-match the science image to the template.
697 backgroundModel : `lsst.afw.math.Function2D`, optional
698 Background model that was fit while solving for the PSF-matching
703 results : `lsst.pipe.base.Struct`
705 ``difference`` : `lsst.afw.image.ExposureF`
706 Result of subtracting template and science.
707 ``matchedTemplate`` : `lsst.afw.image.ExposureF`
708 Warped template exposure. Note that in this case, the template
709 is not PSF-matched to the science image.
710 ``backgroundModel`` : `lsst.afw.math.Function2D`
711 Background model that was fit while solving for the PSF-matching kernel
712 ``psfMatchingKernel`` : `lsst.afw.math.Kernel`
713 Kernel used to PSF-match the science image to the template.
715 self.metadata[
"convolvedExposure"] =
"Science"
716 bbox = science.getBBox()
718 kernelImage = lsst.afw.image.ImageD(psfMatchingKernel.getDimensions())
719 xcen, ycen = bbox.getCenter()
720 norm = psfMatchingKernel.computeImage(kernelImage, doNormalize=
False, x=xcen, y=ycen)
727 matchedScience.maskedImage /= norm
728 matchedTemplate = template.clone()[bbox]
729 matchedTemplate.setPhotoCalib(science.photoCalib)
731 if backgroundModel
is not None:
733 invertedBackground = backgroundModel.clone()
734 invertedBackground.setParameters([-p
for p
in backgroundModel.getParameters()])
735 backgroundModel = invertedBackground
737 difference =
_subtractImages(matchedScience, matchedTemplate, backgroundModel=backgroundModel)
739 correctedExposure = self.
finalize(template, science, difference,
741 templateMatched=
False)
743 return lsst.pipe.base.Struct(difference=correctedExposure,
744 matchedTemplate=matchedTemplate,
745 matchedScience=matchedScience,
746 backgroundModel=backgroundModel,
747 psfMatchingKernel=psfMatchingKernel)
749 def finalize(self, template, science, difference, kernel,
750 templateMatched=True,
753 spatiallyVarying=False):
754 """Decorrelate the difference image to undo the noise correlations
755 caused by convolution.
759 template : `lsst.afw.image.ExposureF`
760 Template exposure, warped to match the science exposure.
761 science : `lsst.afw.image.ExposureF`
762 Science exposure to subtract from the template.
763 difference : `lsst.afw.image.ExposureF`
764 Result of subtracting template and science.
765 kernel : `lsst.afw.math.Kernel`
766 An (optionally spatially-varying) PSF matching kernel
767 templateMatched : `bool`, optional
768 Was the template PSF-matched to the science image?
769 preConvMode : `bool`, optional
770 Was the science image preconvolved with its own PSF
771 before PSF matching the template?
772 preConvKernel : `lsst.afw.detection.Psf`, optional
773 If not `None`, then the science image was pre-convolved with
774 (the reflection of) this kernel. Must be normalized to sum to 1.
775 spatiallyVarying : `bool`, optional
776 Compute the decorrelation kernel spatially varying across the image?
780 correctedExposure : `lsst.afw.image.ExposureF`
781 The decorrelated image difference.
783 if self.config.doDecorrelation:
784 self.
log.info(
"Decorrelating image difference.")
788 correctedExposure = self.decorrelate.
run(science, template[science.getBBox()], difference, kernel,
789 templateMatched=templateMatched,
790 preConvMode=preConvMode,
791 preConvKernel=preConvKernel,
792 spatiallyVarying=spatiallyVarying).correctedExposure
794 self.
log.info(
"NOT decorrelating image difference.")
795 correctedExposure = difference
796 return correctedExposure
799 """Calculate the point-source limiting magnitude of an exposure.
801 The limit is the flux of a point source detected at ``nsigma`` in
802 the median pixel noise, integrated over the effective area of the
803 PSF, so it does not depend on the units of the pixels.
807 exposure : `lsst.afw.image.Exposure`
808 The exposure to calculate the limiting magnitude for.
809 nsigma : `float`, optional
810 The detection threshold in sigma.
811 fallbackPsfSize : `float`, optional
812 PSF FWHM in pixels to use if the exposure PSF cannot be
818 The limiting AB magnitude, or `numpy.nan` if the exposure has no
819 photometric calibration, no usable pixels, or no PSF.
821 if exposure.photoCalib
is None:
824 psfArea = computePSFNoiseEquivalentArea(exposure.getPsf())
826 afwDetection.InvalidPsfError,
828 if fallbackPsfSize
is None:
829 self.
log.info(
"Unable to evaluate PSF, setting maglim to nan")
831 self.
log.info(
"Unable to evaluate PSF, using fallback FWHM %f", fallbackPsfSize)
833 psfArea = 4*np.pi*(fallbackPsfSize/np.sqrt(8*np.log(2)))**2
835 good = (mask.array & mask.getPlaneBitMask(self.config.badMaskPlanes)) == 0
836 variance = exposure.variance.array[good]
837 variance = variance[np.isfinite(variance) & (variance > 0)]
838 if variance.size == 0:
840 fluxLim = nsigma*np.sqrt(np.median(variance)*psfArea)
841 return exposure.photoCalib.instFluxToMagnitude(fluxLim)
845 """Check that the WCS of the two Exposures match, the template bbox
846 contains the science bbox, and that the bands match.
850 template : `lsst.afw.image.ExposureF`
851 Template exposure, warped to match the science exposure.
852 science : `lsst.afw.image.ExposureF`
853 Science exposure to subtract from the template.
858 Raised if the WCS of the template is not equal to the science WCS,
859 if the science image is not fully contained in the template
860 bounding box, or if the bands do not match.
862 assert template.wcs == science.wcs, \
863 "Template and science exposure WCS are not identical."
864 templateBBox = template.getBBox()
865 scienceBBox = science.getBBox()
866 assert science.filter.bandLabel == template.filter.bandLabel, \
867 "Science and template exposures have different bands: %s, %s" % \
868 (science.filter, template.filter)
870 assert templateBBox.contains(scienceBBox), \
871 "Template bbox does not contain all of the science image."
877 interpolateBadMaskPlanes=False,
879 """Convolve an exposure with the given kernel.
883 exposure : `lsst.afw.Exposure`
884 exposure to convolve.
885 kernel : `lsst.afw.math.LinearCombinationKernel`
886 PSF matching kernel computed in the ``makeKernel`` subtask.
887 convolutionControl : `lsst.afw.math.ConvolutionControl`
888 Configuration for convolve algorithm.
889 bbox : `lsst.geom.Box2I`, optional
890 Bounding box to trim the convolved exposure to.
891 psf : `lsst.afw.detection.Psf`, optional
892 Point spread function (PSF) to set for the convolved exposure.
893 photoCalib : `lsst.afw.image.PhotoCalib`, optional
894 Photometric calibration of the convolved exposure.
895 interpolateBadMaskPlanes : `bool`, optional
896 If set, interpolate over mask planes specified in
897 ``config.badMaskPlanes`` before convolving the image.
901 convolvedExp : `lsst.afw.Exposure`
904 convolvedExposure = exposure.clone()
906 convolvedExposure.setPsf(psf)
907 if photoCalib
is not None:
908 convolvedExposure.setPhotoCalib(photoCalib)
909 if interpolateBadMaskPlanes
and self.config.badMaskPlanes
is not None:
911 self.config.badMaskPlanes)
912 self.metadata[
"nInterpolated"] = nInterp
916 preservePlanes = [mp
for mp
in self.config.preserveMaskPlanes
917 if mp
in convolvedExposure.mask.getMaskPlaneDict()]
919 mp: (convolvedExposure.mask.array
920 & convolvedExposure.mask.getPlaneBitMask(mp)) > 0
921 for mp
in preservePlanes
924 convolvedImage = lsst.afw.image.MaskedImageF(convolvedExposure.getBBox())
926 convolvedExposure.setMaskedImage(convolvedImage)
931 self.
_clearMask(convolvedExposure.mask, clearMaskPlanes=preservePlanes)
932 for maskPlane, maskSetPixels
in maskResetDict.items():
933 bit = convolvedExposure.mask.getPlaneBitMask(maskPlane)
934 convolvedExposure.mask.array[maskSetPixels] |= bit
937 return convolvedExposure
939 return convolvedExposure[bbox]
942 """Select sources from a catalog that meet the selection criteria.
943 The selection criteria include any configured parameters of the
944 `sourceSelector` subtask, as well as checking the science and template
949 template : `lsst.afw.image.ExposureF`
950 Template exposure, warped to match the science exposure.
951 science : `lsst.afw.image.ExposureF`
952 Science exposure to subtract from the template.
953 sources : `lsst.afw.table.SourceCatalog`
954 Input source catalog to select sources from.
955 fallback : `bool`, optional
956 Switch indicating the source selector is being called after
957 running the fallback source detection subtask, which does not run a
958 full set of measurement plugins and can't use the same settings for
963 kernelSources : `lsst.afw.table.SourceCatalog`
964 The input source catalog, with flagged and low signal-to-noise
965 sources removed and footprints added.
969 InsufficientKernelSourcesError
970 An AlgorithmError that is raised if there are not enough PSF
971 candidates to construct the PSF matching kernel.
974 selected = self.fallbackSourceSelector.selectSources(sources).selected
976 selected = self.sourceSelector.selectSources(sources).selected
981 selectSources = sources[selected].copy(deep=
True)
983 kernelSources = setSourceFootprints(selectSources, kernelSize=kSize)
984 bbox = science.getBBox()
989 if self.config.restrictKernelEdgeSources:
992 scienceSelected = checkMask(science.mask[bbox], kernelSources, self.config.excludeMaskPlanes)
993 templateSelected = checkMask(template.mask[bbox], kernelSources, self.config.excludeMaskPlanes)
994 maskSelected = scienceSelected & templateSelected
995 kernelSources = kernelSources[maskSelected].copy(deep=
True)
998 if (len(kernelSources) > self.config.maxKernelSources) & (self.config.maxKernelSources > 0):
999 signalToNoise = kernelSources.getPsfInstFlux()/kernelSources.getPsfInstFluxErr()
1000 indices = np.argsort(signalToNoise)
1001 indices = indices[-self.config.maxKernelSources:]
1002 selected = np.zeros(len(kernelSources), dtype=bool)
1003 selected[indices] =
True
1004 kernelSources = kernelSources[selected].copy(deep=
True)
1006 self.
log.info(
"%i/%i=%.1f%% of sources selected for PSF matching from the input catalog",
1007 len(kernelSources), len(sources), 100*len(kernelSources)/len(sources))
1008 if len(kernelSources) < self.config.minKernelSources:
1009 self.
log.error(
"Too few sources to calculate the PSF matching kernel: "
1010 "%i selected but %i needed for the calculation.",
1011 len(kernelSources), self.config.minKernelSources)
1012 if self.config.allowKernelSourceDetection
and not fallback:
1019 nRequired=self.config.minKernelSources)
1021 self.metadata[
"nPsfSources"] = len(kernelSources)
1023 return kernelSources
1026 """Perform preparatory calculations common to all Alard&Lupton Tasks.
1030 template : `lsst.afw.image.ExposureF`
1031 Template exposure, warped to match the science exposure. The
1032 variance plane of the template image is modified in place.
1033 science : `lsst.afw.image.ExposureF`
1034 Science exposure to subtract from the template. The variance plane
1035 of the science image is modified in place.
1036 visitSummary : `lsst.afw.table.ExposureCatalog`, optional
1037 Exposure catalog with external calibrations to be applied. Catalog
1038 uses the detector id for the catalog id, sorted on id for fast
1042 if visitSummary
is not None:
1045 template[science.getBBox()], science, self.
log,
1046 requiredTemplateFraction=self.config.requiredTemplateFraction,
1047 exceptionMessage=
"Not attempting subtraction. To force subtraction,"
1048 " set config requiredTemplateFraction=0"
1050 self.metadata[
"templateCoveragePercent"] = 100*templateCoverageFraction
1052 if self.config.doScaleVariance:
1057 sciVarFactor = self.scaleVariance.
run(science.maskedImage)
1058 self.
log.info(
"Science variance scaling factor: %.2f", sciVarFactor)
1059 self.metadata[
"scaleScienceVarianceFactor"] = sciVarFactor
1085 self.
log.info(
"Unable to evaluate PSF at the average position. "
1086 "Evaluting PSF on a grid of points."
1090 fwhmExposureBuffer=self.config.makeKernel.fwhmExposureBuffer,
1091 fwhmExposureGrid=self.config.makeKernel.fwhmExposureGrid
1095 fwhmExposureBuffer=self.config.makeKernel.fwhmExposureBuffer,
1096 fwhmExposureGrid=self.config.makeKernel.fwhmExposureGrid
1105 if np.isnan(maglim_science):
1106 self.
log.warning(
"Limiting magnitude of the science image is NaN!")
1107 fluxlim_science = (maglim_science*u.ABmag).to_value(u.nJy)
1109 if np.isnan(maglim_template):
1110 self.
log.info(
"Cannot evaluate template limiting mag; adopting science limiting mag for diffim")
1111 maglim_diffim = maglim_science
1113 fluxlim_template = (maglim_template*u.ABmag).to_value(u.nJy)
1114 maglim_diffim = (np.sqrt(fluxlim_science**2 + fluxlim_template**2)*u.nJy).to(u.ABmag).value
1115 self.metadata[
"scienceLimitingMagnitude"] = maglim_science
1116 self.metadata[
"templateLimitingMagnitude"] = maglim_template
1117 self.metadata[
"diffimLimitingMagnitude"] = maglim_diffim
1120 """Update the science and template mask planes before differencing.
1124 template : `lsst.afw.image.Exposure`
1125 Template exposure, warped to match the science exposure.
1126 The template mask planes will be erased, except for a few specified
1128 science : `lsst.afw.image.Exposure`
1129 Science exposure to subtract from the template.
1130 The DETECTED and DETECTED_NEGATIVE mask planes of the science image
1133 self.
_clearMask(science.mask, clearMaskPlanes=[
"DETECTED",
"DETECTED_NEGATIVE"])
1140 clearMaskPlanes = [mp
for mp
in template.mask.getMaskPlaneDict().keys()
1141 if mp
not in self.config.preserveTemplateMask]
1142 renameMaskPlanes = [mp
for mp
in self.config.renameTemplateMask
1143 if mp
in template.mask.getMaskPlaneDict().keys()]
1148 if "FAKE" in science.mask.getMaskPlaneDict().keys():
1149 self.
log.info(
"Adding injected mask plane to science image")
1151 if "FAKE" in template.mask.getMaskPlaneDict().keys():
1152 self.
log.info(
"Adding injected mask plane to template image")
1154 if "INJECTED" in renameMaskPlanes:
1155 renameMaskPlanes.remove(
"INJECTED")
1156 if "INJECTED_TEMPLATE" in clearMaskPlanes:
1157 clearMaskPlanes.remove(
"INJECTED_TEMPLATE")
1159 for maskPlane
in renameMaskPlanes:
1161 self.
_clearMask(template.mask, clearMaskPlanes=clearMaskPlanes)
1165 """Rename a mask plane by adding the new name and copying the data.
1169 mask : `lsst.afw.image.Mask`
1170 The mask image to update in place.
1172 The name of the existing mask plane to copy.
1173 newMaskPlane : `str`
1174 The new name of the mask plane that will be added.
1175 If the mask plane already exists, it will be updated in place.
1177 mask.addMaskPlane(newMaskPlane)
1178 originBitMask = mask.getPlaneBitMask(maskPlane)
1179 destinationBitMask = mask.getPlaneBitMask(newMaskPlane)
1180 mask.array |= ((mask.array & originBitMask) > 0)*destinationBitMask
1183 """Clear the mask plane of an exposure.
1187 mask : `lsst.afw.image.Mask`
1188 The mask plane to erase, which will be modified in place.
1189 clearMaskPlanes : `list` of `str`, optional
1190 Erase the specified mask planes.
1191 If not supplied, the entire mask will be erased.
1193 if clearMaskPlanes
is None:
1194 clearMaskPlanes = list(mask.getMaskPlaneDict().keys())
1196 bitMaskToClear = mask.getPlaneBitMask(clearMaskPlanes)
1197 mask &= ~bitMaskToClear
1201 SubtractScoreOutputConnections):
1206 pipelineConnections=AlardLuptonPreconvolveSubtractConnections):
1211 """Subtract a template from a science image, convolving the science image
1212 before computing the kernel, and also convolving the template before
1215 ConfigClass = AlardLuptonPreconvolveSubtractConfig
1216 _DefaultName =
"alardLuptonPreconvolveSubtract"
1217 usePreconvolution =
True
1219 def run(self, template, science, sources, visitSummary=None):
1220 """Preconvolve the science image with its own PSF,
1221 convolve the template image with a PSF-matching kernel and subtract
1222 from the preconvolved science image.
1226 template : `lsst.afw.image.ExposureF`
1227 The template image, which has previously been warped to the science
1228 image. The template bbox will be padded by a few pixels compared to
1230 science : `lsst.afw.image.ExposureF`
1231 The science exposure.
1232 sources : `lsst.afw.table.SourceCatalog`
1233 Identified sources on the science exposure. This catalog is used to
1234 select sources in order to perform the AL PSF matching on stamp
1236 visitSummary : `lsst.afw.table.ExposureCatalog`, optional
1237 Exposure catalog with complete external calibrations. Catalog uses
1238 the detector id for the catalog id, sorted on id for fast lookup.
1242 results : `lsst.pipe.base.Struct`
1243 ``scoreExposure`` : `lsst.afw.image.ExposureF`
1244 Result of subtracting the convolved template and science
1245 images. Attached PSF is that of the original science image.
1246 ``matchedTemplate`` : `lsst.afw.image.ExposureF`
1247 Warped and PSF-matched template exposure. Attached PSF is that
1248 of the original science image.
1249 ``matchedScience`` : `lsst.afw.image.ExposureF`
1250 The science exposure after convolving with its own PSF.
1251 Attached PSF is that of the original science image.
1252 ``backgroundModel`` : `lsst.afw.math.Function2D`
1253 Background model that was fit while solving for the
1255 ``psfMatchingKernel`` : `lsst.afw.math.Kernel`
1256 Final kernel used to PSF-match the template to the science
1259 self.
_prepareInputs(template, science, visitSummary=visitSummary)
1263 interpolateBadMaskPlanes=
True)
1264 self.metadata[
"convolvedExposure"] =
"Preconvolution"
1268 self.metadata[
"preconvolvedSciencePsfSize"] = self.
matchedPsfSize
1270 kernelSources = self.
_sourceSelector(template, matchedScience, sources)
1271 subtractResults = self.
runPreconvolve(template, science, matchedScience,
1272 kernelSources, convolutionKernel)
1275 self.
log.warning(
"Failed to match template. Checking coverage")
1278 self.config.minTemplateFractionForExpectedSuccess,
1279 exceptionMessage=
"Template coverage lower than expected to succeed."
1280 f
" Failure is tolerable: {e}")
1284 return subtractResults
1288 """Set the EDGE mask bit on pixels outside a known-valid region.
1292 mask : `~lsst.afw.image.Mask`
1293 Exposure mask that will be modified in place. Must have
1294 an ``EDGE`` mask plane.
1295 innerBBox : `~lsst.geom.Box2I`
1296 The valid inner region. Pixels
1297 outside this bbox will have their ``EDGE`` bit set.
1299 bbox = mask.getBBox()
1300 edgeBit = mask.getPlaneBitMask(
"EDGE")
1301 dx0 = innerBBox.getMinX() - bbox.getMinX()
1302 dx1 = bbox.getMaxX() - innerBBox.getMaxX()
1303 dy0 = innerBBox.getMinY() - bbox.getMinY()
1304 dy1 = bbox.getMaxY() - innerBBox.getMaxY()
1306 mask.array[:dy0, :] |= edgeBit
1308 mask.array[-dy1:, :] |= edgeBit
1310 mask.array[:, :dx0] |= edgeBit
1312 mask.array[:, -dx1:] |= edgeBit
1316 """Build a normalized, reflected matched-filter kernel from a PSF.
1318 Convolving an image with this kernel is equivalent to correlating
1319 the image with the PSF, so peaks in the output align with the PSF's
1320 centroid — even for asymmetric PSFs. The kernel is evaluated at the
1321 PSF's average position and returned as a constant
1322 `~lsst.afw.math.Kernel`.
1326 psf : `~lsst.afw.detection.Psf`
1327 The PSF to derive the preconvolution kernel from.
1331 kernel : `~lsst.afw.math.Kernel`
1332 The PSF reflected about both axes, normalized to sum to one.
1337 Raised if the PSF kernel has an even size along either axis.
1338 It's not possible to center an even-sized kernel.
1340 avgPos = psf.getAveragePosition()
1341 localKernel = psf.getLocalKernel(avgPos)
1342 dims = localKernel.getDimensions()
1343 if dims.x % 2 == 0
or dims.y % 2 == 0:
1345 f
"Preconvolution requires an odd-sized PSF kernel, got {dims.x}x{dims.y}. "
1347 kimg = lsst.afw.image.ImageD(dims)
1348 localKernel.computeImage(kimg, doNormalize=
True)
1351 kimg.array[...] = kimg.array[::-1, ::-1]
1354 def runPreconvolve(self, template, science, matchedScience, kernelSources, preConvKernel):
1355 """Convolve the science image with its own PSF, then convolve the
1356 template with a matching kernel and subtract to form the Score
1361 template : `lsst.afw.image.ExposureF`
1362 Template exposure, warped to match the science exposure.
1363 science : `lsst.afw.image.ExposureF`
1364 Science exposure to subtract from the template.
1365 matchedScience : `lsst.afw.image.ExposureF`
1366 The science exposure, convolved with the reflection of its own PSF.
1367 kernelSources : `lsst.afw.table.SourceCatalog`
1368 Identified sources on the science exposure. This catalog is used to
1369 select sources in order to perform the AL PSF matching on stamp
1371 preConvKernel : `lsst.afw.math.Kernel`
1372 The kernel that was used to preconvolve the ``science``
1373 exposure. Must be normalized to sum to 1.
1377 results : `lsst.pipe.base.Struct`
1379 ``scoreExposure`` : `lsst.afw.image.ExposureF`
1380 Result of subtracting the convolved template and science
1381 images. Attached PSF is that of the original science image.
1382 ``matchedTemplate`` : `lsst.afw.image.ExposureF`
1383 Warped and PSF-matched template exposure. Attached PSF is that
1384 of the original science image.
1385 ``matchedScience`` : `lsst.afw.image.ExposureF`
1386 The science exposure after convolving with its own PSF.
1387 Attached PSF is that of the original science image.
1388 ``backgroundModel`` : `lsst.afw.math.Function2D`
1389 Background model that was fit while solving for the
1391 ``psfMatchingKernel`` : `lsst.afw.math.Kernel`
1392 Final kernel used to PSF-match the template to the science
1395 bbox = science.getBBox()
1396 innerBBox = preConvKernel.shrinkBBox(bbox)
1398 kernelResult = self.makeKernel.
run(template[innerBBox], matchedScience[innerBBox], kernelSources,
1403 matchedTemplate = self.
_convolveExposure(template, kernelResult.psfMatchingKernel,
1407 interpolateBadMaskPlanes=
True,
1408 photoCalib=science.photoCalib)
1410 backgroundModel=(kernelResult.backgroundModel
1411 if self.config.doSubtractBackground
else None))
1412 correctedScore = self.
finalize(template[bbox], science, score,
1413 kernelResult.psfMatchingKernel,
1414 templateMatched=
True, preConvMode=
True,
1415 preConvKernel=preConvKernel)
1420 return lsst.pipe.base.Struct(scoreExposure=correctedScore,
1421 matchedTemplate=matchedTemplate,
1422 matchedScience=matchedScience,
1423 backgroundModel=kernelResult.backgroundModel,
1424 psfMatchingKernel=kernelResult.psfMatchingKernel,
1425 kernelSources=kernelSources)
1429 exceptionMessage=""):
1430 """Raise NoWorkFound if template coverage < requiredTemplateFraction
1434 templateExposure : `lsst.afw.image.ExposureF`
1435 The template exposure to check
1436 logger : `logging.Logger`
1437 Logger for printing output.
1438 requiredTemplateFraction : `float`, optional
1439 Fraction of pixels of the science image required to have coverage
1441 exceptionMessage : `str`, optional
1442 Message to include in the exception raised if the template coverage
1447 templateCoverageFraction: `float`
1448 Fraction of pixels in the template with data.
1452 lsst.pipe.base.NoWorkFound
1453 Raised if fraction of good pixels, defined as not having NO_DATA
1454 set, is less than the requiredTemplateFraction
1458 noTemplate = templateExposure.mask.array & templateExposure.mask.getPlaneBitMask(
'NO_DATA')
1461 noScience = scienceExposure.mask.array & scienceExposure.mask.getPlaneBitMask(
'NO_DATA')
1462 pixNoData = np.count_nonzero(noTemplate | noScience)
1463 pixGood = templateExposure.getBBox().getArea() - pixNoData
1464 templateCoverageFraction = pixGood/templateExposure.getBBox().getArea()
1465 logger.info(
"template has %d good pixels (%.1f%%)", pixGood, 100*templateCoverageFraction)
1467 if templateCoverageFraction < requiredTemplateFraction:
1468 message = (
"Insufficient Template Coverage. (%.1f%% < %.1f%%)" % (
1469 100*templateCoverageFraction,
1470 100*requiredTemplateFraction))
1471 raise lsst.pipe.base.NoWorkFound(message +
" " + exceptionMessage)
1472 return templateCoverageFraction
1476 """Subtract template from science, propagating relevant metadata.
1480 science : `lsst.afw.Exposure`
1481 The input science image.
1482 template : `lsst.afw.Exposure`
1483 The template to subtract from the science image.
1484 backgroundModel : `lsst.afw.MaskedImage`, optional
1485 Differential background model
1489 difference : `lsst.afw.Exposure`
1490 The subtracted image.
1492 difference = science.clone()
1493 if backgroundModel
is not None:
1494 difference.maskedImage -= backgroundModel
1495 difference.maskedImage -= template.maskedImage
1500 """Determine that the PSF of ``exp1`` is not wider than that of ``exp2``.
1504 exp1 : `~lsst.afw.image.Exposure`
1505 Exposure with the reference point spread function (PSF) to evaluate.
1506 exp2 : `~lsst.afw.image.Exposure`
1507 Exposure with a candidate point spread function (PSF) to evaluate.
1508 fwhmExposureBuffer : `float`
1509 Fractional buffer margin to be left out of all sides of the image
1510 during the construction of the grid to compute mean PSF FWHM in an
1511 exposure, if the PSF is not available at its average position.
1512 fwhmExposureGrid : `int`
1513 Grid size to compute the mean FWHM in an exposure, if the PSF is not
1514 available at its average position.
1518 True if ``exp1`` has a PSF that is not wider than that of ``exp2`` in
1522 shape1 = getPsfFwhm(exp1.psf, average=
False)
1523 shape2 = getPsfFwhm(exp2.psf, average=
False)
1525 shape1 = evaluateMeanPsfFwhm(exp1,
1526 fwhmExposureBuffer=fwhmExposureBuffer,
1527 fwhmExposureGrid=fwhmExposureGrid
1529 shape2 = evaluateMeanPsfFwhm(exp2,
1530 fwhmExposureBuffer=fwhmExposureBuffer,
1531 fwhmExposureGrid=fwhmExposureGrid
1533 return shape1 <= shape2
1536 xTest = shape1[0] <= shape2[0]
1537 yTest = shape1[1] <= shape2[1]
1538 return xTest | yTest
1542 pipelineConnections=SimplifiedSubtractConnections):
1543 mode = lsst.pex.config.ChoiceField(
1545 default=
"convolveTemplate",
1546 allowed={
"auto":
"Choose which image to convolve at runtime.",
1547 "convolveScience":
"Only convolve the science image.",
1548 "convolveTemplate":
"Only convolve the template image."},
1549 doc=
"Choose which image to convolve at runtime, or require that a specific image is convolved."
1551 useExistingKernel = lsst.pex.config.Field(
1554 doc=
"Use a pre-existing PSF matching kernel?"
1555 "If False, source detection and measurement will be run."
1560 """Compute the image difference of a science and template image using
1561 the Alard & Lupton (1998) algorithm.
1563 ConfigClass = SimplifiedSubtractConfig
1564 _DefaultName =
"simplifiedSubtract"
1567 def run(self, template, science, visitSummary=None, inputPsfMatchingKernel=None):
1568 """PSF match, subtract, and decorrelate two images.
1572 template : `lsst.afw.image.ExposureF`
1573 Template exposure, warped to match the science exposure.
1574 science : `lsst.afw.image.ExposureF`
1575 Science exposure to subtract from the template.
1576 visitSummary : `lsst.afw.table.ExposureCatalog`, optional
1577 Exposure catalog with external calibrations to be applied. Catalog
1578 uses the detector id for the catalog id, sorted on id for fast
1580 inputPsfMatchingKernel : `lsst.afw.math.Kernel`, optional
1581 Pre-existing PSF matching kernel to use for convolution.
1582 Required, and only used, if ``config.useExistingKernel`` is set.
1586 results : `lsst.pipe.base.Struct`
1587 ``difference`` : `lsst.afw.image.ExposureF`
1588 Result of subtracting template and science.
1589 ``matchedTemplate`` : `lsst.afw.image.ExposureF`
1590 Warped and PSF-matched template exposure.
1591 ``backgroundModel`` : `lsst.afw.math.Function2D`
1592 Background model that was fit while solving for the
1594 ``psfMatchingKernel`` : `lsst.afw.math.Kernel`
1595 Kernel used to PSF-match the convolved image.
1596 ``kernelSources` : `lsst.afw.table.SourceCatalog`
1597 Sources detected on the science image that were used to
1598 construct the PSF-matching kernel.
1602 lsst.pipe.base.NoWorkFound
1603 Raised if fraction of good pixels, defined as not having NO_DATA
1604 set, is less then the configured requiredTemplateFraction
1606 self.
_prepareInputs(template, science, visitSummary=visitSummary)
1611 if self.config.useExistingKernel:
1612 psfMatchingKernel = inputPsfMatchingKernel
1613 backgroundModel =
None
1614 kernelSources =
None
1616 kernelResult = self.
runMakeKernel(template, science, convolveTemplate=convolveTemplate,
1617 runSourceDetection=
True)
1618 psfMatchingKernel = kernelResult.psfMatchingKernel
1619 kernelSources = kernelResult.kernelSources
1620 if self.config.doSubtractBackground:
1621 backgroundModel = kernelResult.backgroundModel
1623 backgroundModel =
None
1624 if convolveTemplate:
1626 backgroundModel=backgroundModel)
1629 backgroundModel=backgroundModel)
1630 if kernelSources
is not None:
1631 subtractResults.kernelSources = kernelSources
1632 return subtractResults
1636 """Replace masked image pixels with interpolated values.
1640 maskedImage : `lsst.afw.image.MaskedImage`
1641 Image on which to perform interpolation.
1642 badMaskPlanes : `list` of `str`
1643 List of mask planes to interpolate over.
1644 fallbackValue : `float`, optional
1645 Value to set when interpolation fails.
1650 The number of masked pixels that were replaced.
1652 imgBadMaskPlanes = [
1653 maskPlane
for maskPlane
in badMaskPlanes
if maskPlane
in maskedImage.mask.getMaskPlaneDict()
1656 image = maskedImage.image.array
1657 badPixels = (maskedImage.mask.array & maskedImage.mask.getPlaneBitMask(imgBadMaskPlanes)) > 0
1658 image[badPixels] = np.nan
1659 if fallbackValue
is None:
1660 fallbackValue = np.nanmedian(image)
1663 image[badPixels] = fallbackValue
1664 return np.sum(badPixels)
_flagScoreEdge(mask, innerBBox)
run(self, template, science, sources, visitSummary=None)
runPreconvolve(self, template, science, matchedScience, kernelSources, preConvKernel)
_makePreconvolutionKernel(psf)
_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)
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())
_interpolateImage(maskedImage, badMaskPlanes, fallbackValue=None)
_subtractImages(science, template, backgroundModel=None)
checkTemplateIsSufficient(templateExposure, scienceExposure, logger, requiredTemplateFraction=0., exceptionMessage="")
_shapeTest(exp1, exp2, fwhmExposureBuffer, fwhmExposureGrid)