22from astropy
import units
as u
23from astropy.stats
import gaussian_fwhm_to_sigma
31 computeDifferenceImageMetrics,
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 image difference?"
212 scaleVariance = lsst.pex.config.ConfigurableField(
213 target=ScaleVarianceTask,
214 doc=
"Subtask to rescale the variance of the template to the statistically expected level."
216 doSubtractBackground = lsst.pex.config.Field(
217 doc=
"Subtract the background fit when solving the kernel? "
218 "It is generally better to instead subtract the background in detectAndMeasure.",
222 doApplyExternalCalibrations = lsst.pex.config.Field(
224 "Replace science Exposure's calibration objects with those"
225 " in visitSummary. Ignored if `doApplyFinalizedPsf is True."
230 sourceSelector = lsst.pex.config.ConfigurableField(
231 target=ScienceSourceSelectorTask,
232 doc=
"Task to select sources to be used for PSF matching.",
234 fallbackSourceSelector = lsst.pex.config.ConfigurableField(
235 target=ScienceSourceSelectorTask,
236 doc=
"Task to select sources to be used for PSF matching."
237 "Used only if the kernel calculation fails and"
238 "`allowKernelSourceDetection` is set. The fallback source detection"
239 " will not include all of the same plugins as the original source "
240 " detection, so not all of the same flags can be used.",
242 detectionThreshold = lsst.pex.config.Field(
245 doc=
"Minimum signal to noise ratio of detected sources "
246 "to use for calculating the PSF matching kernel.",
247 deprecated=
"No longer used. Will be removed after v30"
249 detectionThresholdMax = lsst.pex.config.Field(
252 doc=
"Maximum signal to noise ratio of detected sources "
253 "to use for calculating the PSF matching kernel.",
254 deprecated=
"No longer used. Will be removed after v30"
256 restrictKernelEdgeSources = lsst.pex.config.Field(
259 doc=
"Exclude sources close to the edge from the kernel calculation?"
261 maxKernelSources = lsst.pex.config.Field(
264 doc=
"Maximum number of sources to use for calculating the PSF matching kernel."
265 "Set to -1 to disable."
267 minKernelSources = lsst.pex.config.Field(
270 doc=
"Minimum number of sources needed for calculating the PSF matching kernel."
272 excludeMaskPlanes = lsst.pex.config.ListField(
274 default=(
"NO_DATA",
"BAD",
"SAT",
"EDGE",
"FAKE",
"HIGH_VARIANCE"),
275 doc=
"Template mask planes to exclude when selecting sources for PSF matching.",
277 badMaskPlanes = lsst.pex.config.ListField(
279 default=(
"NO_DATA",
"BAD",
"SAT",
"EDGE"),
280 doc=
"Mask planes to interpolate over."
282 preserveTemplateMask = lsst.pex.config.ListField(
284 default=(
"NO_DATA",
"BAD",
"HIGH_VARIANCE"),
285 doc=
"Mask planes from the template to propagate to the image difference."
287 renameTemplateMask = lsst.pex.config.ListField(
289 default=(
"SAT",
"INJECTED",
"INJECTED_CORE",),
290 doc=
"Mask planes from the template to propagate to the image difference"
291 "with '_TEMPLATE' appended to the name."
293 preserveMaskPlanes = lsst.pex.config.ListField(
295 default=(
"INJECTED",
"INJECTED_CORE",
"INJECTED_TEMPLATE",
"INJECTED_CORE_TEMPLATE"),
296 doc=
"Mask planes to preserve without dilation when convolving the image.",
298 allowKernelSourceDetection = lsst.pex.config.Field(
301 doc=
"Re-run source detection for kernel candidates if an error is"
302 " encountered while calculating the matching kernel."
309 self.
makeKernel.kernel.active.fitForBackground =
True
310 self.
makeKernel.kernel.active.spatialKernelOrder = 1
311 self.
makeKernel.kernel.active.spatialBgOrder = 2
314 doSignalToNoise =
True
316 signalToNoiseMinimum = 10
317 signalToNoiseMaximum = 500
338 pipelineConnections=AlardLuptonSubtractConnections):
339 mode = lsst.pex.config.ChoiceField(
341 default=
"convolveTemplate",
342 allowed={
"auto":
"Choose which image to convolve at runtime.",
343 "convolveScience":
"Only convolve the science image.",
344 "convolveTemplate":
"Only convolve the template image."},
345 doc=
"Choose which image to convolve at runtime, or require that a specific image is convolved."
350 """Compute the image difference of a science and template image using
351 the Alard & Lupton (1998) algorithm.
353 ConfigClass = AlardLuptonSubtractConfig
354 _DefaultName =
"alardLuptonSubtract"
355 usePreconvolution =
False
356 """Whether this task preconvolves the science image with its own PSF
357 before kernel-matching. Subclasses that preconvolve override this to
362 self.makeSubtask(
"decorrelate")
363 self.makeSubtask(
"makeKernel")
364 self.makeSubtask(
"sourceSelector")
365 self.makeSubtask(
"fallbackSourceSelector")
366 if self.config.doScaleVariance:
367 self.makeSubtask(
"scaleVariance")
376 """Replace calibrations (psf, and ApCorrMap) on this exposure with
381 exposure : `lsst.afw.image.exposure.Exposure`
382 Input exposure to adjust calibrations.
383 visitSummary : `lsst.afw.table.ExposureCatalog`
384 Exposure catalog with external calibrations to be applied. Catalog
385 uses the detector id for the catalog id, sorted on id for fast
390 exposure : `lsst.afw.image.exposure.Exposure`
391 Exposure with adjusted calibrations.
393 detectorId = exposure.info.getDetector().getId()
395 row = visitSummary.find(detectorId)
397 self.
log.warning(
"Detector id %s not found in external calibrations catalog; "
398 "Using original calibrations.", detectorId)
401 apCorrMap = row.getApCorrMap()
403 self.
log.warning(
"Detector id %s has None for psf in "
404 "external calibrations catalog; Using original psf and aperture correction.",
406 elif apCorrMap
is None:
407 self.
log.warning(
"Detector id %s has None for apCorrMap in "
408 "external calibrations catalog; Using original psf and aperture correction.",
412 exposure.info.setApCorrMap(apCorrMap)
417 inputs = butlerQC.get(inputRefs)
420 results = self.
run(**inputs)
421 except lsst.pipe.base.AlgorithmError
as e:
422 error = lsst.pipe.base.AnnotatedPartialOutputsError.annotate(e, self, log=self.
log)
426 butlerQC.put(results, outputRefs)
429 def run(self, template, science, sources, visitSummary=None):
430 """PSF match, subtract, and decorrelate two images.
434 template : `lsst.afw.image.ExposureF`
435 Template exposure, warped to match the science exposure.
436 science : `lsst.afw.image.ExposureF`
437 Science exposure to subtract from the template.
438 sources : `lsst.afw.table.SourceCatalog`
439 Identified sources on the science exposure. This catalog is used to
440 select sources in order to perform the AL PSF matching on stamp
442 visitSummary : `lsst.afw.table.ExposureCatalog`, optional
443 Exposure catalog with external calibrations to be applied. Catalog
444 uses the detector id for the catalog id, sorted on id for fast
449 results : `lsst.pipe.base.Struct`
450 ``difference`` : `lsst.afw.image.ExposureF`
451 Result of subtracting template and science.
452 ``matchedTemplate`` : `lsst.afw.image.ExposureF`
453 Warped and PSF-matched template exposure.
454 ``backgroundModel`` : `lsst.afw.math.Function2D`
455 Background model that was fit while solving for the
457 ``psfMatchingKernel`` : `lsst.afw.math.Kernel`
458 Kernel used to PSF-match the convolved image.
459 ``kernelSources` : `lsst.afw.table.SourceCatalog`
460 Sources from the input catalog that were used to construct the
468 kernelResult = self.
runMakeKernel(template, science, sources=sources,
469 convolveTemplate=convolveTemplate,
470 runSourceDetection=
False)
472 if self.config.doSubtractBackground:
473 backgroundModel = kernelResult.backgroundModel
475 backgroundModel =
None
477 subtractResults = self.
runConvolveTemplate(template, science, kernelResult.psfMatchingKernel,
478 backgroundModel=backgroundModel)
480 subtractResults = self.
runConvolveScience(template, science, kernelResult.psfMatchingKernel,
481 backgroundModel=backgroundModel)
482 subtractResults.kernelSources = kernelResult.kernelSources
484 metrics = computeDifferenceImageMetrics(science, subtractResults.difference, sources)
486 self.metadata[
"differenceFootprintRatioMean"] = metrics.differenceFootprintRatioMean
487 self.metadata[
"differenceFootprintRatioStdev"] = metrics.differenceFootprintRatioStdev
488 self.metadata[
"differenceFootprintSkyRatioMean"] = metrics.differenceFootprintSkyRatioMean
489 self.metadata[
"differenceFootprintSkyRatioStdev"] = metrics.differenceFootprintSkyRatioStdev
490 self.
log.info(
"Mean, stdev of ratio of difference to science "
491 "pixels in star footprints: %5.4f, %5.4f",
492 self.metadata[
"differenceFootprintRatioMean"],
493 self.metadata[
"differenceFootprintRatioStdev"])
495 return subtractResults
498 """Determine whether the template should be convolved with the PSF
503 template : `lsst.afw.image.ExposureF`
504 Template exposure, warped to match the science exposure.
505 science : `lsst.afw.image.ExposureF`
506 Science exposure to subtract from the template.
510 convolveTemplate : `bool`
511 Convolve the template to match the two images?
516 If an unsupported convolution mode is supplied.
519 raise RuntimeError(
"Choosing a convolution method is incompatible with preconvolution!")
520 if self.config.mode ==
"auto":
523 fwhmExposureBuffer=self.config.makeKernel.fwhmExposureBuffer,
524 fwhmExposureGrid=self.config.makeKernel.fwhmExposureGrid)
527 self.
log.info(
"Average template PSF size is greater, "
528 "but science PSF greater in one dimension: convolving template image.")
530 self.
log.info(
"Science PSF size is greater: convolving template image.")
532 self.
log.info(
"Template PSF size is greater: convolving science image.")
533 elif self.config.mode ==
"convolveTemplate":
534 self.
log.info(
"`convolveTemplate` is set: convolving template image.")
535 convolveTemplate =
True
536 elif self.config.mode ==
"convolveScience":
537 self.
log.info(
"`convolveScience` is set: convolving science image.")
538 convolveTemplate =
False
540 raise RuntimeError(f
"Cannot handle AlardLuptonSubtract mode: {self.config.mode}")
541 return convolveTemplate
543 def runMakeKernel(self, template, science, sources=None, convolveTemplate=True, runSourceDetection=False):
544 """Construct the PSF-matching kernel. Not used for preconvolution.
548 template : `lsst.afw.image.ExposureF`
549 Template exposure, warped to match the science exposure.
550 science : `lsst.afw.image.ExposureF`
551 Science exposure to subtract from the template.
552 sources : `lsst.afw.table.SourceCatalog`
553 Identified sources on the science exposure. This catalog is used to
554 select sources in order to perform the AL PSF matching on stamp
556 Not used if ``runSourceDetection`` is set.
557 convolveTemplate : `bool`, optional
558 Construct the matching kernel to convolve the template?
559 runSourceDetection : `bool`, optional
560 Run a minimal version of source detection to determine kernel
561 candidates? If False, a source list to select kernel candidates
562 from must be supplied.
566 results : `lsst.pipe.base.Struct`
567 ``backgroundModel`` : `lsst.afw.math.Function2D`
568 Background model that was fit while solving for the
570 ``psfMatchingKernel`` : `lsst.afw.math.Kernel`
571 Kernel used to PSF-match the convolved image.
572 ``kernelSources` : `lsst.afw.table.SourceCatalog`
573 Sources from the input catalog that were used to construct the
577 raise RuntimeError(
"Incorrect matching kernel calculation configured. "
578 "`runMakeKernel` can't be called if `usePreconvolution` is set.")
590 if runSourceDetection:
594 kernelResult = self.makeKernel.
run(reference, target, kernelSources,
596 templateFwhmPix=referenceFwhmPix,
597 scienceFwhmPix=targetFwhmPix)
599 self.
log.warning(
"Failed to match template. Checking coverage")
602 self.config.minTemplateFractionForExpectedSuccess,
603 exceptionMessage=
"Template coverage lower than expected to succeed."
604 f
" Failure is tolerable: {e}")
608 return lsst.pipe.base.Struct(backgroundModel=kernelResult.backgroundModel,
609 psfMatchingKernel=kernelResult.psfMatchingKernel,
610 kernelSources=kernelSources)
613 """Run detection on the science image and use the template mask plane
614 to reject candidate sources.
618 template : `lsst.afw.image.ExposureF`
619 Template exposure, warped to match the science exposure.
620 science : `lsst.afw.image.ExposureF`
621 Science exposure to subtract from the template.
625 kernelSources : `lsst.afw.table.SourceCatalog`
626 Sources from the input catalog to use to construct the
629 kernelSize = self.makeKernel.makeKernelBasisList(
631 sources = self.makeKernel.makeCandidateList(template, science, kernelSize,
637 """Convolve the template image with a PSF-matching kernel and subtract
638 from the science image.
642 template : `lsst.afw.image.ExposureF`
643 Template exposure, warped to match the science exposure.
644 science : `lsst.afw.image.ExposureF`
645 Science exposure to subtract from the template.
646 psfMatchingKernel : `lsst.afw.math.Kernel`
647 Kernel to be used to PSF-match the science image to the template.
648 backgroundModel : `lsst.afw.math.Function2D`, optional
649 Background model that was fit while solving for the PSF-matching
654 results : `lsst.pipe.base.Struct`
656 ``difference`` : `lsst.afw.image.ExposureF`
657 Result of subtracting template and science.
658 ``matchedTemplate`` : `lsst.afw.image.ExposureF`
659 Warped and PSF-matched template exposure.
660 ``backgroundModel`` : `lsst.afw.math.Function2D`
661 Background model that was fit while solving for the PSF-matching kernel
662 ``psfMatchingKernel`` : `lsst.afw.math.Kernel`
663 Kernel used to PSF-match the template to the science image.
665 self.metadata[
"convolvedExposure"] =
"Template"
669 bbox=science.getBBox(),
671 photoCalib=science.photoCalib)
673 difference =
_subtractImages(science, matchedTemplate, backgroundModel=backgroundModel)
674 correctedExposure = self.
finalize(template, science, difference,
676 templateMatched=
True)
678 return lsst.pipe.base.Struct(difference=correctedExposure,
679 matchedTemplate=matchedTemplate,
680 matchedScience=science,
681 backgroundModel=backgroundModel,
682 psfMatchingKernel=psfMatchingKernel)
685 """Convolve the science image with a PSF-matching kernel and subtract
690 template : `lsst.afw.image.ExposureF`
691 Template exposure, warped to match the science exposure.
692 science : `lsst.afw.image.ExposureF`
693 Science exposure to subtract from the template.
694 psfMatchingKernel : `lsst.afw.math.Kernel`
695 Kernel to be used to PSF-match the science image to the template.
696 backgroundModel : `lsst.afw.math.Function2D`, optional
697 Background model that was fit while solving for the PSF-matching
702 results : `lsst.pipe.base.Struct`
704 ``difference`` : `lsst.afw.image.ExposureF`
705 Result of subtracting template and science.
706 ``matchedTemplate`` : `lsst.afw.image.ExposureF`
707 Warped template exposure. Note that in this case, the template
708 is not PSF-matched to the science image.
709 ``backgroundModel`` : `lsst.afw.math.Function2D`
710 Background model that was fit while solving for the PSF-matching kernel
711 ``psfMatchingKernel`` : `lsst.afw.math.Kernel`
712 Kernel used to PSF-match the science image to the template.
714 self.metadata[
"convolvedExposure"] =
"Science"
715 bbox = science.getBBox()
717 kernelImage = lsst.afw.image.ImageD(psfMatchingKernel.getDimensions())
718 xcen, ycen = bbox.getCenter()
719 norm = psfMatchingKernel.computeImage(kernelImage, doNormalize=
False, x=xcen, y=ycen)
726 matchedScience.maskedImage /= norm
727 matchedTemplate = template.clone()[bbox]
728 matchedTemplate.setPhotoCalib(science.photoCalib)
730 if backgroundModel
is not None:
732 invertedBackground = backgroundModel.clone()
733 invertedBackground.setParameters([-p
for p
in backgroundModel.getParameters()])
734 backgroundModel = invertedBackground
736 difference =
_subtractImages(matchedScience, matchedTemplate, backgroundModel=backgroundModel)
738 correctedExposure = self.
finalize(template, science, difference,
740 templateMatched=
False)
742 return lsst.pipe.base.Struct(difference=correctedExposure,
743 matchedTemplate=matchedTemplate,
744 matchedScience=matchedScience,
745 backgroundModel=backgroundModel,
746 psfMatchingKernel=psfMatchingKernel)
748 def finalize(self, template, science, difference, kernel,
749 templateMatched=True,
752 spatiallyVarying=False):
753 """Decorrelate the difference image to undo the noise correlations
754 caused by convolution.
758 template : `lsst.afw.image.ExposureF`
759 Template exposure, warped to match the science exposure.
760 science : `lsst.afw.image.ExposureF`
761 Science exposure to subtract from the template.
762 difference : `lsst.afw.image.ExposureF`
763 Result of subtracting template and science.
764 kernel : `lsst.afw.math.Kernel`
765 An (optionally spatially-varying) PSF matching kernel
766 templateMatched : `bool`, optional
767 Was the template PSF-matched to the science image?
768 preConvMode : `bool`, optional
769 Was the science image preconvolved with its own PSF
770 before PSF matching the template?
771 preConvKernel : `lsst.afw.detection.Psf`, optional
772 If not `None`, then the science image was pre-convolved with
773 (the reflection of) this kernel. Must be normalized to sum to 1.
774 spatiallyVarying : `bool`, optional
775 Compute the decorrelation kernel spatially varying across the image?
779 correctedExposure : `lsst.afw.image.ExposureF`
780 The decorrelated image difference.
782 if self.config.doDecorrelation:
783 self.
log.info(
"Decorrelating image difference.")
787 correctedExposure = self.decorrelate.
run(science, template[science.getBBox()], difference, kernel,
788 templateMatched=templateMatched,
789 preConvMode=preConvMode,
790 preConvKernel=preConvKernel,
791 spatiallyVarying=spatiallyVarying).correctedExposure
793 self.
log.info(
"NOT decorrelating image difference.")
794 correctedExposure = difference
795 return correctedExposure
798 """Calculate an exposure's limiting magnitude.
800 This method uses the photometric zeropoint together with the
801 PSF size from the average position of the exposure.
805 exposure : `lsst.afw.image.Exposure`
806 The target exposure to calculate the limiting magnitude for.
807 nsigma : `float`, optional
808 The detection threshold in sigma.
809 fallbackPsfSize : `float`, optional
810 PSF FWHM to use in the event the exposure PSF cannot be retrieved.
814 maglim : `astropy.units.Quantity`
815 The limiting magnitude of the exposure, or np.nan.
817 if exposure.photoCalib
is None:
820 psf = exposure.getPsf()
821 psf_shape = psf.computeShape(psf.getAveragePosition())
823 afwDetection.InvalidPsfError,
825 if fallbackPsfSize
is None:
826 self.
log.info(
"Unable to evaluate PSF, setting maglim to nan")
828 self.
log.info(
"Unable to evaluate PSF, using fallback FWHM %f", fallbackPsfSize)
829 psf_area = np.pi*(fallbackPsfSize/2)**2
832 psf_area = np.pi*np.sqrt(psf_shape.getIxx()*psf_shape.getIyy())
834 zeropoint = exposure.photoCalib.instFluxToMagnitude(1)
835 return zeropoint - 2.5*np.log10(nsigma*np.sqrt(psf_area))
839 """Check that the WCS of the two Exposures match, the template bbox
840 contains the science bbox, and that the bands match.
844 template : `lsst.afw.image.ExposureF`
845 Template exposure, warped to match the science exposure.
846 science : `lsst.afw.image.ExposureF`
847 Science exposure to subtract from the template.
852 Raised if the WCS of the template is not equal to the science WCS,
853 if the science image is not fully contained in the template
854 bounding box, or if the bands do not match.
856 assert template.wcs == science.wcs, \
857 "Template and science exposure WCS are not identical."
858 templateBBox = template.getBBox()
859 scienceBBox = science.getBBox()
860 assert science.filter.bandLabel == template.filter.bandLabel, \
861 "Science and template exposures have different bands: %s, %s" % \
862 (science.filter, template.filter)
864 assert templateBBox.contains(scienceBBox), \
865 "Template bbox does not contain all of the science image."
871 interpolateBadMaskPlanes=False,
873 """Convolve an exposure with the given kernel.
877 exposure : `lsst.afw.Exposure`
878 exposure to convolve.
879 kernel : `lsst.afw.math.LinearCombinationKernel`
880 PSF matching kernel computed in the ``makeKernel`` subtask.
881 convolutionControl : `lsst.afw.math.ConvolutionControl`
882 Configuration for convolve algorithm.
883 bbox : `lsst.geom.Box2I`, optional
884 Bounding box to trim the convolved exposure to.
885 psf : `lsst.afw.detection.Psf`, optional
886 Point spread function (PSF) to set for the convolved exposure.
887 photoCalib : `lsst.afw.image.PhotoCalib`, optional
888 Photometric calibration of the convolved exposure.
889 interpolateBadMaskPlanes : `bool`, optional
890 If set, interpolate over mask planes specified in
891 ``config.badMaskPlanes`` before convolving the image.
895 convolvedExp : `lsst.afw.Exposure`
898 convolvedExposure = exposure.clone()
900 convolvedExposure.setPsf(psf)
901 if photoCalib
is not None:
902 convolvedExposure.setPhotoCalib(photoCalib)
903 if interpolateBadMaskPlanes
and self.config.badMaskPlanes
is not None:
905 self.config.badMaskPlanes)
906 self.metadata[
"nInterpolated"] = nInterp
910 preservePlanes = [mp
for mp
in self.config.preserveMaskPlanes
911 if mp
in convolvedExposure.mask.getMaskPlaneDict()]
913 mp: (convolvedExposure.mask.array
914 & convolvedExposure.mask.getPlaneBitMask(mp)) > 0
915 for mp
in preservePlanes
918 convolvedImage = lsst.afw.image.MaskedImageF(convolvedExposure.getBBox())
920 convolvedExposure.setMaskedImage(convolvedImage)
925 self.
_clearMask(convolvedExposure.mask, clearMaskPlanes=preservePlanes)
926 for maskPlane, maskSetPixels
in maskResetDict.items():
927 bit = convolvedExposure.mask.getPlaneBitMask(maskPlane)
928 convolvedExposure.mask.array[maskSetPixels] |= bit
931 return convolvedExposure
933 return convolvedExposure[bbox]
936 """Select sources from a catalog that meet the selection criteria.
937 The selection criteria include any configured parameters of the
938 `sourceSelector` subtask, as well as checking the science and template
943 template : `lsst.afw.image.ExposureF`
944 Template exposure, warped to match the science exposure.
945 science : `lsst.afw.image.ExposureF`
946 Science exposure to subtract from the template.
947 sources : `lsst.afw.table.SourceCatalog`
948 Input source catalog to select sources from.
949 fallback : `bool`, optional
950 Switch indicating the source selector is being called after
951 running the fallback source detection subtask, which does not run a
952 full set of measurement plugins and can't use the same settings for
957 kernelSources : `lsst.afw.table.SourceCatalog`
958 The input source catalog, with flagged and low signal-to-noise
959 sources removed and footprints added.
963 InsufficientKernelSourcesError
964 An AlgorithmError that is raised if there are not enough PSF
965 candidates to construct the PSF matching kernel.
968 selected = self.fallbackSourceSelector.selectSources(sources).selected
970 selected = self.sourceSelector.selectSources(sources).selected
975 selectSources = sources[selected].copy(deep=
True)
977 kernelSources = setSourceFootprints(selectSources, kernelSize=kSize)
978 bbox = science.getBBox()
983 if self.config.restrictKernelEdgeSources:
986 scienceSelected = checkMask(science.mask[bbox], kernelSources, self.config.excludeMaskPlanes)
987 templateSelected = checkMask(template.mask[bbox], kernelSources, self.config.excludeMaskPlanes)
988 maskSelected = scienceSelected & templateSelected
989 kernelSources = kernelSources[maskSelected].copy(deep=
True)
992 if (len(kernelSources) > self.config.maxKernelSources) & (self.config.maxKernelSources > 0):
993 signalToNoise = kernelSources.getPsfInstFlux()/kernelSources.getPsfInstFluxErr()
994 indices = np.argsort(signalToNoise)
995 indices = indices[-self.config.maxKernelSources:]
996 selected = np.zeros(len(kernelSources), dtype=bool)
997 selected[indices] =
True
998 kernelSources = kernelSources[selected].copy(deep=
True)
1000 self.
log.info(
"%i/%i=%.1f%% of sources selected for PSF matching from the input catalog",
1001 len(kernelSources), len(sources), 100*len(kernelSources)/len(sources))
1002 if len(kernelSources) < self.config.minKernelSources:
1003 self.
log.error(
"Too few sources to calculate the PSF matching kernel: "
1004 "%i selected but %i needed for the calculation.",
1005 len(kernelSources), self.config.minKernelSources)
1006 if self.config.allowKernelSourceDetection
and not fallback:
1013 nRequired=self.config.minKernelSources)
1015 self.metadata[
"nPsfSources"] = len(kernelSources)
1017 return kernelSources
1020 """Perform preparatory calculations common to all Alard&Lupton Tasks.
1024 template : `lsst.afw.image.ExposureF`
1025 Template exposure, warped to match the science exposure. The
1026 variance plane of the template image is modified in place.
1027 science : `lsst.afw.image.ExposureF`
1028 Science exposure to subtract from the template. The variance plane
1029 of the science image is modified in place.
1030 visitSummary : `lsst.afw.table.ExposureCatalog`, optional
1031 Exposure catalog with external calibrations to be applied. Catalog
1032 uses the detector id for the catalog id, sorted on id for fast
1036 if visitSummary
is not None:
1039 template[science.getBBox()], science, self.
log,
1040 requiredTemplateFraction=self.config.requiredTemplateFraction,
1041 exceptionMessage=
"Not attempting subtraction. To force subtraction,"
1042 " set config requiredTemplateFraction=0"
1044 self.metadata[
"templateCoveragePercent"] = 100*templateCoverageFraction
1046 if self.config.doScaleVariance:
1050 templateVarFactor = self.scaleVariance.
run(template.maskedImage)
1051 sciVarFactor = self.scaleVariance.
run(science.maskedImage)
1052 self.
log.info(
"Template variance scaling factor: %.2f", templateVarFactor)
1053 self.metadata[
"scaleTemplateVarianceFactor"] = templateVarFactor
1054 self.
log.info(
"Science variance scaling factor: %.2f", sciVarFactor)
1055 self.metadata[
"scaleScienceVarianceFactor"] = sciVarFactor
1081 self.
log.info(
"Unable to evaluate PSF at the average position. "
1082 "Evaluting PSF on a grid of points."
1086 fwhmExposureBuffer=self.config.makeKernel.fwhmExposureBuffer,
1087 fwhmExposureGrid=self.config.makeKernel.fwhmExposureGrid
1091 fwhmExposureBuffer=self.config.makeKernel.fwhmExposureBuffer,
1092 fwhmExposureGrid=self.config.makeKernel.fwhmExposureGrid
1101 if np.isnan(maglim_science):
1102 self.
log.warning(
"Limiting magnitude of the science image is NaN!")
1103 fluxlim_science = (maglim_science*u.ABmag).to_value(u.nJy)
1105 if np.isnan(maglim_template):
1106 self.
log.info(
"Cannot evaluate template limiting mag; adopting science limiting mag for diffim")
1107 maglim_diffim = maglim_science
1109 fluxlim_template = (maglim_template*u.ABmag).to_value(u.nJy)
1110 maglim_diffim = (np.sqrt(fluxlim_science**2 + fluxlim_template**2)*u.nJy).to(u.ABmag).value
1111 self.metadata[
"scienceLimitingMagnitude"] = maglim_science
1112 self.metadata[
"templateLimitingMagnitude"] = maglim_template
1113 self.metadata[
"diffimLimitingMagnitude"] = maglim_diffim
1116 """Update the science and template mask planes before differencing.
1120 template : `lsst.afw.image.Exposure`
1121 Template exposure, warped to match the science exposure.
1122 The template mask planes will be erased, except for a few specified
1124 science : `lsst.afw.image.Exposure`
1125 Science exposure to subtract from the template.
1126 The DETECTED and DETECTED_NEGATIVE mask planes of the science image
1129 self.
_clearMask(science.mask, clearMaskPlanes=[
"DETECTED",
"DETECTED_NEGATIVE"])
1136 clearMaskPlanes = [mp
for mp
in template.mask.getMaskPlaneDict().keys()
1137 if mp
not in self.config.preserveTemplateMask]
1138 renameMaskPlanes = [mp
for mp
in self.config.renameTemplateMask
1139 if mp
in template.mask.getMaskPlaneDict().keys()]
1144 if "FAKE" in science.mask.getMaskPlaneDict().keys():
1145 self.
log.info(
"Adding injected mask plane to science image")
1147 if "FAKE" in template.mask.getMaskPlaneDict().keys():
1148 self.
log.info(
"Adding injected mask plane to template image")
1150 if "INJECTED" in renameMaskPlanes:
1151 renameMaskPlanes.remove(
"INJECTED")
1152 if "INJECTED_TEMPLATE" in clearMaskPlanes:
1153 clearMaskPlanes.remove(
"INJECTED_TEMPLATE")
1155 for maskPlane
in renameMaskPlanes:
1157 self.
_clearMask(template.mask, clearMaskPlanes=clearMaskPlanes)
1161 """Rename a mask plane by adding the new name and copying the data.
1165 mask : `lsst.afw.image.Mask`
1166 The mask image to update in place.
1168 The name of the existing mask plane to copy.
1169 newMaskPlane : `str`
1170 The new name of the mask plane that will be added.
1171 If the mask plane already exists, it will be updated in place.
1173 mask.addMaskPlane(newMaskPlane)
1174 originBitMask = mask.getPlaneBitMask(maskPlane)
1175 destinationBitMask = mask.getPlaneBitMask(newMaskPlane)
1176 mask.array |= ((mask.array & originBitMask) > 0)*destinationBitMask
1179 """Clear the mask plane of an exposure.
1183 mask : `lsst.afw.image.Mask`
1184 The mask plane to erase, which will be modified in place.
1185 clearMaskPlanes : `list` of `str`, optional
1186 Erase the specified mask planes.
1187 If not supplied, the entire mask will be erased.
1189 if clearMaskPlanes
is None:
1190 clearMaskPlanes = list(mask.getMaskPlaneDict().keys())
1192 bitMaskToClear = mask.getPlaneBitMask(clearMaskPlanes)
1193 mask &= ~bitMaskToClear
1197 SubtractScoreOutputConnections):
1202 pipelineConnections=AlardLuptonPreconvolveSubtractConnections):
1207 """Subtract a template from a science image, convolving the science image
1208 before computing the kernel, and also convolving the template before
1211 ConfigClass = AlardLuptonPreconvolveSubtractConfig
1212 _DefaultName =
"alardLuptonPreconvolveSubtract"
1213 usePreconvolution =
True
1215 def run(self, template, science, sources, visitSummary=None):
1216 """Preconvolve the science image with its own PSF,
1217 convolve the template image with a PSF-matching kernel and subtract
1218 from the preconvolved science image.
1222 template : `lsst.afw.image.ExposureF`
1223 The template image, which has previously been warped to the science
1224 image. The template bbox will be padded by a few pixels compared to
1226 science : `lsst.afw.image.ExposureF`
1227 The science exposure.
1228 sources : `lsst.afw.table.SourceCatalog`
1229 Identified sources on the science exposure. This catalog is used to
1230 select sources in order to perform the AL PSF matching on stamp
1232 visitSummary : `lsst.afw.table.ExposureCatalog`, optional
1233 Exposure catalog with complete external calibrations. Catalog uses
1234 the detector id for the catalog id, sorted on id for fast lookup.
1238 results : `lsst.pipe.base.Struct`
1239 ``scoreExposure`` : `lsst.afw.image.ExposureF`
1240 Result of subtracting the convolved template and science
1241 images. Attached PSF is that of the original science image.
1242 ``matchedTemplate`` : `lsst.afw.image.ExposureF`
1243 Warped and PSF-matched template exposure. Attached PSF is that
1244 of the original science image.
1245 ``matchedScience`` : `lsst.afw.image.ExposureF`
1246 The science exposure after convolving with its own PSF.
1247 Attached PSF is that of the original science image.
1248 ``backgroundModel`` : `lsst.afw.math.Function2D`
1249 Background model that was fit while solving for the
1251 ``psfMatchingKernel`` : `lsst.afw.math.Kernel`
1252 Final kernel used to PSF-match the template to the science
1255 self.
_prepareInputs(template, science, visitSummary=visitSummary)
1259 interpolateBadMaskPlanes=
True)
1260 self.metadata[
"convolvedExposure"] =
"Preconvolution"
1264 self.metadata[
"preconvolvedSciencePsfSize"] = self.
matchedPsfSize
1266 kernelSources = self.
_sourceSelector(template, matchedScience, sources)
1267 subtractResults = self.
runPreconvolve(template, science, matchedScience,
1268 kernelSources, convolutionKernel)
1271 self.
log.warning(
"Failed to match template. Checking coverage")
1274 self.config.minTemplateFractionForExpectedSuccess,
1275 exceptionMessage=
"Template coverage lower than expected to succeed."
1276 f
" Failure is tolerable: {e}")
1280 return subtractResults
1284 """Set the EDGE mask bit on pixels outside a known-valid region.
1288 mask : `~lsst.afw.image.Mask`
1289 Exposure mask that will be modified in place. Must have
1290 an ``EDGE`` mask plane.
1291 innerBBox : `~lsst.geom.Box2I`
1292 The valid inner region. Pixels
1293 outside this bbox will have their ``EDGE`` bit set.
1295 bbox = mask.getBBox()
1296 edgeBit = mask.getPlaneBitMask(
"EDGE")
1297 dx0 = innerBBox.getMinX() - bbox.getMinX()
1298 dx1 = bbox.getMaxX() - innerBBox.getMaxX()
1299 dy0 = innerBBox.getMinY() - bbox.getMinY()
1300 dy1 = bbox.getMaxY() - innerBBox.getMaxY()
1302 mask.array[:dy0, :] |= edgeBit
1304 mask.array[-dy1:, :] |= edgeBit
1306 mask.array[:, :dx0] |= edgeBit
1308 mask.array[:, -dx1:] |= edgeBit
1312 """Build a normalized, reflected matched-filter kernel from a PSF.
1314 Convolving an image with this kernel is equivalent to correlating
1315 the image with the PSF, so peaks in the output align with the PSF's
1316 centroid — even for asymmetric PSFs. The kernel is evaluated at the
1317 PSF's average position and returned as a constant
1318 `~lsst.afw.math.Kernel`.
1322 psf : `~lsst.afw.detection.Psf`
1323 The PSF to derive the preconvolution kernel from.
1327 kernel : `~lsst.afw.math.Kernel`
1328 The PSF reflected about both axes, normalized to sum to one.
1333 Raised if the PSF kernel has an even size along either axis.
1334 It's not possible to center an even-sized kernel.
1336 avgPos = psf.getAveragePosition()
1337 localKernel = psf.getLocalKernel(avgPos)
1338 dims = localKernel.getDimensions()
1339 if dims.x % 2 == 0
or dims.y % 2 == 0:
1341 f
"Preconvolution requires an odd-sized PSF kernel, got {dims.x}x{dims.y}. "
1343 kimg = lsst.afw.image.ImageD(dims)
1344 localKernel.computeImage(kimg, doNormalize=
True)
1347 kimg.array[...] = kimg.array[::-1, ::-1]
1350 def runPreconvolve(self, template, science, matchedScience, kernelSources, preConvKernel):
1351 """Convolve the science image with its own PSF, then convolve the
1352 template with a matching kernel and subtract to form the Score
1357 template : `lsst.afw.image.ExposureF`
1358 Template exposure, warped to match the science exposure.
1359 science : `lsst.afw.image.ExposureF`
1360 Science exposure to subtract from the template.
1361 matchedScience : `lsst.afw.image.ExposureF`
1362 The science exposure, convolved with the reflection of its own PSF.
1363 kernelSources : `lsst.afw.table.SourceCatalog`
1364 Identified sources on the science exposure. This catalog is used to
1365 select sources in order to perform the AL PSF matching on stamp
1367 preConvKernel : `lsst.afw.math.Kernel`
1368 The kernel that was used to preconvolve the ``science``
1369 exposure. Must be normalized to sum to 1.
1373 results : `lsst.pipe.base.Struct`
1375 ``scoreExposure`` : `lsst.afw.image.ExposureF`
1376 Result of subtracting the convolved template and science
1377 images. Attached PSF is that of the original science image.
1378 ``matchedTemplate`` : `lsst.afw.image.ExposureF`
1379 Warped and PSF-matched template exposure. Attached PSF is that
1380 of the original science image.
1381 ``matchedScience`` : `lsst.afw.image.ExposureF`
1382 The science exposure after convolving with its own PSF.
1383 Attached PSF is that of the original science image.
1384 ``backgroundModel`` : `lsst.afw.math.Function2D`
1385 Background model that was fit while solving for the
1387 ``psfMatchingKernel`` : `lsst.afw.math.Kernel`
1388 Final kernel used to PSF-match the template to the science
1391 bbox = science.getBBox()
1392 innerBBox = preConvKernel.shrinkBBox(bbox)
1394 kernelResult = self.makeKernel.
run(template[innerBBox], matchedScience[innerBBox], kernelSources,
1399 matchedTemplate = self.
_convolveExposure(template, kernelResult.psfMatchingKernel,
1403 interpolateBadMaskPlanes=
True,
1404 photoCalib=science.photoCalib)
1406 backgroundModel=(kernelResult.backgroundModel
1407 if self.config.doSubtractBackground
else None))
1408 correctedScore = self.
finalize(template[bbox], science, score,
1409 kernelResult.psfMatchingKernel,
1410 templateMatched=
True, preConvMode=
True,
1411 preConvKernel=preConvKernel)
1416 return lsst.pipe.base.Struct(scoreExposure=correctedScore,
1417 matchedTemplate=matchedTemplate,
1418 matchedScience=matchedScience,
1419 backgroundModel=kernelResult.backgroundModel,
1420 psfMatchingKernel=kernelResult.psfMatchingKernel,
1421 kernelSources=kernelSources)
1425 exceptionMessage=""):
1426 """Raise NoWorkFound if template coverage < requiredTemplateFraction
1430 templateExposure : `lsst.afw.image.ExposureF`
1431 The template exposure to check
1432 logger : `logging.Logger`
1433 Logger for printing output.
1434 requiredTemplateFraction : `float`, optional
1435 Fraction of pixels of the science image required to have coverage
1437 exceptionMessage : `str`, optional
1438 Message to include in the exception raised if the template coverage
1443 templateCoverageFraction: `float`
1444 Fraction of pixels in the template with data.
1448 lsst.pipe.base.NoWorkFound
1449 Raised if fraction of good pixels, defined as not having NO_DATA
1450 set, is less than the requiredTemplateFraction
1454 noTemplate = templateExposure.mask.array & templateExposure.mask.getPlaneBitMask(
'NO_DATA')
1457 noScience = scienceExposure.mask.array & scienceExposure.mask.getPlaneBitMask(
'NO_DATA')
1458 pixNoData = np.count_nonzero(noTemplate | noScience)
1459 pixGood = templateExposure.getBBox().getArea() - pixNoData
1460 templateCoverageFraction = pixGood/templateExposure.getBBox().getArea()
1461 logger.info(
"template has %d good pixels (%.1f%%)", pixGood, 100*templateCoverageFraction)
1463 if templateCoverageFraction < requiredTemplateFraction:
1464 message = (
"Insufficient Template Coverage. (%.1f%% < %.1f%%)" % (
1465 100*templateCoverageFraction,
1466 100*requiredTemplateFraction))
1467 raise lsst.pipe.base.NoWorkFound(message +
" " + exceptionMessage)
1468 return templateCoverageFraction
1472 """Subtract template from science, propagating relevant metadata.
1476 science : `lsst.afw.Exposure`
1477 The input science image.
1478 template : `lsst.afw.Exposure`
1479 The template to subtract from the science image.
1480 backgroundModel : `lsst.afw.MaskedImage`, optional
1481 Differential background model
1485 difference : `lsst.afw.Exposure`
1486 The subtracted image.
1488 difference = science.clone()
1489 if backgroundModel
is not None:
1490 difference.maskedImage -= backgroundModel
1491 difference.maskedImage -= template.maskedImage
1496 """Determine that the PSF of ``exp1`` is not wider than that of ``exp2``.
1500 exp1 : `~lsst.afw.image.Exposure`
1501 Exposure with the reference point spread function (PSF) to evaluate.
1502 exp2 : `~lsst.afw.image.Exposure`
1503 Exposure with a candidate point spread function (PSF) to evaluate.
1504 fwhmExposureBuffer : `float`
1505 Fractional buffer margin to be left out of all sides of the image
1506 during the construction of the grid to compute mean PSF FWHM in an
1507 exposure, if the PSF is not available at its average position.
1508 fwhmExposureGrid : `int`
1509 Grid size to compute the mean FWHM in an exposure, if the PSF is not
1510 available at its average position.
1514 True if ``exp1`` has a PSF that is not wider than that of ``exp2`` in
1518 shape1 = getPsfFwhm(exp1.psf, average=
False)
1519 shape2 = getPsfFwhm(exp2.psf, average=
False)
1521 shape1 = evaluateMeanPsfFwhm(exp1,
1522 fwhmExposureBuffer=fwhmExposureBuffer,
1523 fwhmExposureGrid=fwhmExposureGrid
1525 shape2 = evaluateMeanPsfFwhm(exp2,
1526 fwhmExposureBuffer=fwhmExposureBuffer,
1527 fwhmExposureGrid=fwhmExposureGrid
1529 return shape1 <= shape2
1532 xTest = shape1[0] <= shape2[0]
1533 yTest = shape1[1] <= shape2[1]
1534 return xTest | yTest
1538 pipelineConnections=SimplifiedSubtractConnections):
1539 mode = lsst.pex.config.ChoiceField(
1541 default=
"convolveTemplate",
1542 allowed={
"auto":
"Choose which image to convolve at runtime.",
1543 "convolveScience":
"Only convolve the science image.",
1544 "convolveTemplate":
"Only convolve the template image."},
1545 doc=
"Choose which image to convolve at runtime, or require that a specific image is convolved."
1547 useExistingKernel = lsst.pex.config.Field(
1550 doc=
"Use a pre-existing PSF matching kernel?"
1551 "If False, source detection and measurement will be run."
1556 """Compute the image difference of a science and template image using
1557 the Alard & Lupton (1998) algorithm.
1559 ConfigClass = SimplifiedSubtractConfig
1560 _DefaultName =
"simplifiedSubtract"
1563 def run(self, template, science, visitSummary=None, inputPsfMatchingKernel=None):
1564 """PSF match, subtract, and decorrelate two images.
1568 template : `lsst.afw.image.ExposureF`
1569 Template exposure, warped to match the science exposure.
1570 science : `lsst.afw.image.ExposureF`
1571 Science exposure to subtract from the template.
1572 visitSummary : `lsst.afw.table.ExposureCatalog`, optional
1573 Exposure catalog with external calibrations to be applied. Catalog
1574 uses the detector id for the catalog id, sorted on id for fast
1576 inputPsfMatchingKernel : `lsst.afw.math.Kernel`, optional
1577 Pre-existing PSF matching kernel to use for convolution.
1578 Required, and only used, if ``config.useExistingKernel`` is set.
1582 results : `lsst.pipe.base.Struct`
1583 ``difference`` : `lsst.afw.image.ExposureF`
1584 Result of subtracting template and science.
1585 ``matchedTemplate`` : `lsst.afw.image.ExposureF`
1586 Warped and PSF-matched template exposure.
1587 ``backgroundModel`` : `lsst.afw.math.Function2D`
1588 Background model that was fit while solving for the
1590 ``psfMatchingKernel`` : `lsst.afw.math.Kernel`
1591 Kernel used to PSF-match the convolved image.
1592 ``kernelSources` : `lsst.afw.table.SourceCatalog`
1593 Sources detected on the science image that were used to
1594 construct the PSF-matching kernel.
1598 lsst.pipe.base.NoWorkFound
1599 Raised if fraction of good pixels, defined as not having NO_DATA
1600 set, is less then the configured requiredTemplateFraction
1602 self.
_prepareInputs(template, science, visitSummary=visitSummary)
1607 if self.config.useExistingKernel:
1608 psfMatchingKernel = inputPsfMatchingKernel
1609 backgroundModel =
None
1610 kernelSources =
None
1612 kernelResult = self.
runMakeKernel(template, science, convolveTemplate=convolveTemplate,
1613 runSourceDetection=
True)
1614 psfMatchingKernel = kernelResult.psfMatchingKernel
1615 kernelSources = kernelResult.kernelSources
1616 if self.config.doSubtractBackground:
1617 backgroundModel = kernelResult.backgroundModel
1619 backgroundModel =
None
1620 if convolveTemplate:
1622 backgroundModel=backgroundModel)
1625 backgroundModel=backgroundModel)
1626 if kernelSources
is not None:
1627 subtractResults.kernelSources = kernelSources
1628 return subtractResults
1632 """Replace masked image pixels with interpolated values.
1636 maskedImage : `lsst.afw.image.MaskedImage`
1637 Image on which to perform interpolation.
1638 badMaskPlanes : `list` of `str`
1639 List of mask planes to interpolate over.
1640 fallbackValue : `float`, optional
1641 Value to set when interpolation fails.
1646 The number of masked pixels that were replaced.
1648 imgBadMaskPlanes = [
1649 maskPlane
for maskPlane
in badMaskPlanes
if maskPlane
in maskedImage.mask.getMaskPlaneDict()
1652 image = maskedImage.image.array
1653 badPixels = (maskedImage.mask.array & maskedImage.mask.getPlaneBitMask(imgBadMaskPlanes)) > 0
1654 image[badPixels] = np.nan
1655 if fallbackValue
is None:
1656 fallbackValue = np.nanmedian(image)
1659 image[badPixels] = fallbackValue
1660 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)