29from lsst.utils.introspection
import find_outside_stacklevel
36from .
import MakeKernelTask, DecorrelateALKernelTask
37from lsst.utils.timer
import timeMethod
39__all__ = [
"AlardLuptonSubtractConfig",
"AlardLuptonSubtractTask",
40 "AlardLuptonPreconvolveSubtractConfig",
"AlardLuptonPreconvolveSubtractTask"]
42_dimensions = (
"instrument",
"visit",
"detector")
43_defaultTemplates = {
"coaddName":
"deep",
"fakesType":
""}
47 dimensions=_dimensions,
48 defaultTemplates=_defaultTemplates):
49 template = connectionTypes.Input(
50 doc=
"Input warped template to subtract.",
51 dimensions=(
"instrument",
"visit",
"detector"),
52 storageClass=
"ExposureF",
53 name=
"{fakesType}{coaddName}Diff_templateExp"
55 science = connectionTypes.Input(
56 doc=
"Input science exposure to subtract from.",
57 dimensions=(
"instrument",
"visit",
"detector"),
58 storageClass=
"ExposureF",
59 name=
"{fakesType}calexp"
61 sources = connectionTypes.Input(
62 doc=
"Sources measured on the science exposure; "
63 "used to select sources for making the matching kernel.",
64 dimensions=(
"instrument",
"visit",
"detector"),
65 storageClass=
"SourceCatalog",
68 finalizedPsfApCorrCatalog = connectionTypes.Input(
69 doc=(
"Per-visit finalized psf models and aperture correction maps. "
70 "These catalogs use the detector id for the catalog id, "
71 "sorted on id for fast lookup."),
72 dimensions=(
"instrument",
"visit"),
73 storageClass=
"ExposureCatalog",
74 name=
"finalVisitSummary",
77 "Deprecated in favor of visitSummary. Will be removed after v26."
80 visitSummary = connectionTypes.Input(
81 doc=(
"Per-visit catalog with final calibration objects. "
82 "These catalogs use the detector id for the catalog id, "
83 "sorted on id for fast lookup."),
84 dimensions=(
"instrument",
"visit"),
85 storageClass=
"ExposureCatalog",
86 name=
"finalVisitSummary",
91 if not config.doApplyFinalizedPsf:
92 self.inputs.remove(
"finalizedPsfApCorrCatalog")
93 if not config.doApplyExternalCalibrations
or config.doApplyFinalizedPsf:
98 dimensions=_dimensions,
99 defaultTemplates=_defaultTemplates):
100 difference = connectionTypes.Output(
101 doc=
"Result of subtracting convolved template from science image.",
102 dimensions=(
"instrument",
"visit",
"detector"),
103 storageClass=
"ExposureF",
104 name=
"{fakesType}{coaddName}Diff_differenceTempExp",
106 matchedTemplate = connectionTypes.Output(
107 doc=
"Warped and PSF-matched template used to create `subtractedExposure`.",
108 dimensions=(
"instrument",
"visit",
"detector"),
109 storageClass=
"ExposureF",
110 name=
"{fakesType}{coaddName}Diff_matchedExp",
115 dimensions=_dimensions,
116 defaultTemplates=_defaultTemplates):
117 scoreExposure = connectionTypes.Output(
118 doc=
"The maximum likelihood image, used for the detection of diaSources.",
119 dimensions=(
"instrument",
"visit",
"detector"),
120 storageClass=
"ExposureF",
121 name=
"{fakesType}{coaddName}Diff_scoreExp",
131 target=MakeKernelTask,
132 doc=
"Task to construct a matching kernel for convolution.",
137 doc=
"Perform diffim decorrelation to undo pixel correlation due to A&L "
138 "kernel convolution? If True, also update the diffim PSF."
141 target=DecorrelateALKernelTask,
142 doc=
"Task to decorrelate the image difference.",
147 doc=
"Abort task if template covers less than this fraction of pixels."
148 " Setting to 0 will always attempt image subtraction."
153 doc=
"Scale variance of the image difference?"
156 target=ScaleVarianceTask,
157 doc=
"Subtask to rescale the variance of the template to the statistically expected level."
160 doc=
"Subtract the background fit when solving the kernel?",
165 doc=
"Replace science Exposure's psf and aperture correction map"
166 " with those in finalizedPsfApCorrCatalog.",
171 "Deprecated in favor of doApplyExternalCalibrations. "
172 "Will be removed after v26."
177 "Replace science Exposure's calibration objects with those"
178 " in visitSummary. Ignored if `doApplyFinalizedPsf is True."
186 doc=
"Minimum signal to noise ratio of detected sources "
187 "to use for calculating the PSF matching kernel."
191 doc=
"Flags that, if set, the associated source should not "
192 "be used to determine the PSF matching kernel.",
193 default=(
"sky_source",
"slot_Centroid_flag",
194 "slot_ApFlux_flag",
"slot_PsfFlux_flag", ),
198 default=(
"NO_DATA",
"BAD",
"SAT",
"EDGE"),
199 doc=
"Mask planes to exclude when selecting sources for PSF matching."
203 default=(
"NO_DATA",
"BAD",
"SAT"),
204 doc=
"Mask planes from the template to propagate to the image difference."
210 self.
makeKernel.kernel.active.spatialKernelOrder = 1
211 self.
makeKernel.kernel.active.spatialBgOrder = 2
215 pipelineConnections=AlardLuptonSubtractConnections):
218 default=
"convolveTemplate",
219 allowed={
"auto":
"Choose which image to convolve at runtime.",
220 "convolveScience":
"Only convolve the science image.",
221 "convolveTemplate":
"Only convolve the template image."},
222 doc=
"Choose which image to convolve at runtime, or require that a specific image is convolved."
227 """Compute the image difference of a science and template image using
228 the Alard & Lupton (1998) algorithm.
230 ConfigClass = AlardLuptonSubtractConfig
231 _DefaultName = "alardLuptonSubtract"
235 self.makeSubtask(
"decorrelate")
236 self.makeSubtask(
"makeKernel")
237 if self.config.doScaleVariance:
238 self.makeSubtask(
"scaleVariance")
247 """Replace calibrations (psf, and ApCorrMap) on this exposure with
252 exposure : `lsst.afw.image.exposure.Exposure`
253 Input exposure to adjust calibrations.
255 Exposure catalog with external calibrations to be applied. Catalog
256 uses the detector id
for the catalog id, sorted on id
for fast
261 exposure : `lsst.afw.image.exposure.Exposure`
262 Exposure
with adjusted calibrations.
264 detectorId = exposure.info.getDetector().getId()
266 row = visitSummary.find(detectorId)
268 self.
log.warning(
"Detector id %s not found in external calibrations catalog; "
269 "Using original calibrations.", detectorId)
272 apCorrMap = row.getApCorrMap()
274 self.
log.warning(
"Detector id %s has None for psf in "
275 "external calibrations catalog; Using original psf and aperture correction.",
277 elif apCorrMap
is None:
278 self.
log.warning(
"Detector id %s has None for apCorrMap in "
279 "external calibrations catalog; Using original psf and aperture correction.",
283 exposure.info.setApCorrMap(apCorrMap)
288 def run(self, template, science, sources, finalizedPsfApCorrCatalog=None,
290 """PSF match, subtract, and decorrelate two images.
294 template : `lsst.afw.image.ExposureF`
295 Template exposure, warped to match the science exposure.
296 science : `lsst.afw.image.ExposureF`
297 Science exposure to subtract from the template.
299 Identified sources on the science exposure. This catalog
is used to
300 select sources
in order to perform the AL PSF matching on stamp
303 Exposure catalog
with finalized psf models
and aperture correction
304 maps to be applied. Catalog uses the detector id
for the catalog
305 id, sorted on id
for fast lookup. Deprecated
in favor of
306 ``visitSummary``,
and will be removed after v26.
308 Exposure catalog
with external calibrations to be applied. Catalog
309 uses the detector id
for the catalog id, sorted on id
for fast
310 lookup. Ignored (
for temporary backwards compatibility)
if
311 ``finalizedPsfApCorrCatalog``
is provided.
315 results : `lsst.pipe.base.Struct`
316 ``difference`` : `lsst.afw.image.ExposureF`
317 Result of subtracting template
and science.
318 ``matchedTemplate`` : `lsst.afw.image.ExposureF`
319 Warped
and PSF-matched template exposure.
320 ``backgroundModel`` : `lsst.afw.math.Function2D`
321 Background model that was fit
while solving
for the
324 Kernel used to PSF-match the convolved image.
329 If an unsupported convolution mode
is supplied.
331 If there are too few sources to calculate the PSF matching kernel.
332 lsst.pipe.base.NoWorkFound
333 Raised
if fraction of good pixels, defined
as not having NO_DATA
334 set,
is less then the configured requiredTemplateFraction
337 if finalizedPsfApCorrCatalog
is not None:
339 "The finalizedPsfApCorrCatalog argument is deprecated in favor of the visitSummary "
340 "argument, and will be removed after v26.",
342 stacklevel=find_outside_stacklevel(
"lsst.ip.diffim"),
344 visitSummary = finalizedPsfApCorrCatalog
349 fwhmExposureBuffer = self.config.makeKernel.fwhmExposureBuffer
350 fwhmExposureGrid = self.config.makeKernel.fwhmExposureGrid
359 templatePsfSize = getPsfFwhm(template.psf)
360 sciencePsfSize = getPsfFwhm(science.psf)
361 except InvalidParameterError:
362 self.
log.info(
"Unable to evaluate PSF at the average position. "
363 "Evaluting PSF on a grid of points."
365 templatePsfSize = evaluateMeanPsfFwhm(template,
366 fwhmExposureBuffer=fwhmExposureBuffer,
367 fwhmExposureGrid=fwhmExposureGrid
369 sciencePsfSize = evaluateMeanPsfFwhm(science,
370 fwhmExposureBuffer=fwhmExposureBuffer,
371 fwhmExposureGrid=fwhmExposureGrid
373 self.
log.info(
"Science PSF FWHM: %f pixels", sciencePsfSize)
374 self.
log.info(
"Template PSF FWHM: %f pixels", templatePsfSize)
377 if self.config.mode ==
"auto":
380 fwhmExposureBuffer=fwhmExposureBuffer,
381 fwhmExposureGrid=fwhmExposureGrid)
383 if sciencePsfSize < templatePsfSize:
384 self.
log.info(
"Average template PSF size is greater, "
385 "but science PSF greater in one dimension: convolving template image.")
387 self.
log.info(
"Science PSF size is greater: convolving template image.")
389 self.
log.info(
"Template PSF size is greater: convolving science image.")
390 elif self.config.mode ==
"convolveTemplate":
391 self.
log.info(
"`convolveTemplate` is set: convolving template image.")
392 convolveTemplate =
True
393 elif self.config.mode ==
"convolveScience":
394 self.
log.info(
"`convolveScience` is set: convolving science image.")
395 convolveTemplate =
False
397 raise RuntimeError(
"Cannot handle AlardLuptonSubtract mode: %s", self.config.mode)
404 return subtractResults
407 """Convolve the template image with a PSF-matching kernel and subtract
408 from the science image.
412 template : `lsst.afw.image.ExposureF`
413 Template exposure, warped to match the science exposure.
414 science : `lsst.afw.image.ExposureF`
415 Science exposure to subtract
from the template.
417 Identified sources on the science exposure. This catalog
is used to
418 select sources
in order to perform the AL PSF matching on stamp
423 results : `lsst.pipe.base.Struct`
425 ``difference`` : `lsst.afw.image.ExposureF`
426 Result of subtracting template
and science.
427 ``matchedTemplate`` : `lsst.afw.image.ExposureF`
428 Warped
and PSF-matched template exposure.
429 ``backgroundModel`` : `lsst.afw.math.Function2D`
430 Background model that was fit
while solving
for the PSF-matching kernel
432 Kernel used to PSF-match the template to the science image.
434 kernelSources = self.makeKernel.selectKernelSources(template, science,
435 candidateList=selectSources,
437 kernelResult = self.makeKernel.run(template, science, kernelSources,
440 matchedTemplate = self.
_convolveExposure(template, kernelResult.psfMatchingKernel,
442 bbox=science.getBBox(),
444 photoCalib=science.photoCalib)
447 backgroundModel=(kernelResult.backgroundModel
448 if self.config.doSubtractBackground
else None))
449 correctedExposure = self.
finalize(template, science, difference,
450 kernelResult.psfMatchingKernel,
451 templateMatched=
True)
453 return lsst.pipe.base.Struct(difference=correctedExposure,
454 matchedTemplate=matchedTemplate,
455 matchedScience=science,
456 backgroundModel=kernelResult.backgroundModel,
457 psfMatchingKernel=kernelResult.psfMatchingKernel)
460 """Convolve the science image with a PSF-matching kernel and subtract the template image.
464 template : `lsst.afw.image.ExposureF`
465 Template exposure, warped to match the science exposure.
466 science : `lsst.afw.image.ExposureF`
467 Science exposure to subtract from the template.
469 Identified sources on the science exposure. This catalog
is used to
470 select sources
in order to perform the AL PSF matching on stamp
475 results : `lsst.pipe.base.Struct`
477 ``difference`` : `lsst.afw.image.ExposureF`
478 Result of subtracting template
and science.
479 ``matchedTemplate`` : `lsst.afw.image.ExposureF`
480 Warped template exposure. Note that
in this case, the template
481 is not PSF-matched to the science image.
482 ``backgroundModel`` : `lsst.afw.math.Function2D`
483 Background model that was fit
while solving
for the PSF-matching kernel
485 Kernel used to PSF-match the science image to the template.
487 bbox = science.getBBox()
488 kernelSources = self.makeKernel.selectKernelSources(science, template,
489 candidateList=selectSources,
491 kernelResult = self.makeKernel.run(science, template, kernelSources,
493 modelParams = kernelResult.backgroundModel.getParameters()
495 kernelResult.backgroundModel.setParameters([-p
for p
in modelParams])
497 kernelImage = lsst.afw.image.ImageD(kernelResult.psfMatchingKernel.getDimensions())
498 norm = kernelResult.psfMatchingKernel.computeImage(kernelImage, doNormalize=
False)
505 matchedScience.maskedImage /= norm
506 matchedTemplate = template.clone()[bbox]
507 matchedTemplate.maskedImage /= norm
508 matchedTemplate.setPhotoCalib(science.photoCalib)
511 backgroundModel=(kernelResult.backgroundModel
512 if self.config.doSubtractBackground
else None))
514 correctedExposure = self.
finalize(template, science, difference,
515 kernelResult.psfMatchingKernel,
516 templateMatched=
False)
518 return lsst.pipe.base.Struct(difference=correctedExposure,
519 matchedTemplate=matchedTemplate,
520 matchedScience=matchedScience,
521 backgroundModel=kernelResult.backgroundModel,
522 psfMatchingKernel=kernelResult.psfMatchingKernel,)
524 def finalize(self, template, science, difference, kernel,
525 templateMatched=True,
528 spatiallyVarying=False):
529 """Decorrelate the difference image to undo the noise correlations
530 caused by convolution.
534 template : `lsst.afw.image.ExposureF`
535 Template exposure, warped to match the science exposure.
536 science : `lsst.afw.image.ExposureF`
537 Science exposure to subtract from the template.
538 difference : `lsst.afw.image.ExposureF`
539 Result of subtracting template
and science.
541 An (optionally spatially-varying) PSF matching kernel
542 templateMatched : `bool`, optional
543 Was the template PSF-matched to the science image?
544 preConvMode : `bool`, optional
545 Was the science image preconvolved
with its own PSF
546 before PSF matching the template?
548 If
not `
None`, then the science image was pre-convolved
with
549 (the reflection of) this kernel. Must be normalized to sum to 1.
550 spatiallyVarying : `bool`, optional
551 Compute the decorrelation kernel spatially varying across the image?
555 correctedExposure : `lsst.afw.image.ExposureF`
556 The decorrelated image difference.
560 mask = difference.mask
561 mask &= ~(mask.getPlaneBitMask(
"DETECTED") | mask.getPlaneBitMask(
"DETECTED_NEGATIVE"))
567 template[science.getBBox()].mask.array[...] = difference.mask.array[...]
568 if self.config.doDecorrelation:
569 self.
log.info(
"Decorrelating image difference.")
573 template[science.getBBox()].mask.array[...] = difference.mask.array[...]
574 correctedExposure = self.decorrelate.run(science, template[science.getBBox()], difference, kernel,
575 templateMatched=templateMatched,
576 preConvMode=preConvMode,
577 preConvKernel=preConvKernel,
578 spatiallyVarying=spatiallyVarying).correctedExposure
580 self.
log.info(
"NOT decorrelating image difference.")
581 correctedExposure = difference
582 return correctedExposure
586 """Check that the WCS of the two Exposures match, and the template bbox
587 contains the science bbox.
591 template : `lsst.afw.image.ExposureF`
592 Template exposure, warped to match the science exposure.
593 science : `lsst.afw.image.ExposureF`
594 Science exposure to subtract from the template.
599 Raised
if the WCS of the template
is not equal to the science WCS,
600 or if the science image
is not fully contained
in the template
603 assert template.wcs == science.wcs,\
604 "Template and science exposure WCS are not identical."
605 templateBBox = template.getBBox()
606 scienceBBox = science.getBBox()
608 assert templateBBox.contains(scienceBBox),\
609 "Template bbox does not contain all of the science image."
616 """Convolve an exposure with the given kernel.
620 exposure : `lsst.afw.Exposure`
621 exposure to convolve.
623 PSF matching kernel computed in the ``makeKernel`` subtask.
625 Configuration
for convolve algorithm.
627 Bounding box to trim the convolved exposure to.
629 Point spread function (PSF) to set
for the convolved exposure.
631 Photometric calibration of the convolved exposure.
635 convolvedExp : `lsst.afw.Exposure`
638 convolvedExposure = exposure.clone()
640 convolvedExposure.setPsf(psf)
641 if photoCalib
is not None:
642 convolvedExposure.setPhotoCalib(photoCalib)
643 convolvedImage = lsst.afw.image.MaskedImageF(exposure.getBBox())
645 convolvedExposure.setMaskedImage(convolvedImage)
647 return convolvedExposure
649 return convolvedExposure[bbox]
652 """Select sources from a catalog that meet the selection criteria.
657 Input source catalog to select sources from.
659 The image mask plane to use to reject sources
660 based on their location on the ccd.
665 The input source catalog,
with flagged
and low signal-to-noise
671 If there are too few sources to compute the PSF matching kernel
672 remaining after source selection.
674 flags = np.ones(len(sources), dtype=bool)
675 for flag
in self.config.badSourceFlags:
677 flags *= ~sources[flag]
678 except Exception
as e:
679 self.
log.warning(
"Could not apply source flag: %s", e)
680 sToNFlag = (sources.getPsfInstFlux()/sources.getPsfInstFluxErr()) > self.config.detectionThreshold
682 flags *= self.
_checkMask(mask, sources, self.config.badMaskPlanes)
683 selectSources = sources[flags]
684 self.
log.info(
"%i/%i=%.1f%% of sources selected for PSF matching from the input catalog",
685 len(selectSources), len(sources), 100*len(selectSources)/len(sources))
686 if len(selectSources) < self.config.makeKernel.nStarPerCell:
687 self.
log.error(
"Too few sources to calculate the PSF matching kernel: "
688 "%i selected but %i needed for the calculation.",
689 len(selectSources), self.config.makeKernel.nStarPerCell)
690 raise RuntimeError(
"Cannot compute PSF matching kernel: too few sources selected.")
692 return selectSources.copy(deep=
True)
696 """Exclude sources that are located on masked pixels.
701 The image mask plane to use to reject sources
702 based on the location of their centroid on the ccd.
704 The source catalog to evaluate.
705 badMaskPlanes : `list` of `str`
706 List of the names of the mask planes to exclude.
710 flags : `numpy.ndarray` of `bool`
711 Array indicating whether each source in the catalog should be
712 kept (
True)
or rejected (
False) based on the value of the
713 mask plane at its location.
716 xv = np.rint(sources.getX() - mask.getX0())
717 yv = np.rint(sources.getY() - mask.getY0())
719 mv = mask.array[yv.astype(int), xv.astype(int)]
720 flags = np.bitwise_and(mv, badPixelMask) == 0
724 """Perform preparatory calculations common to all Alard&Lupton Tasks.
728 template : `lsst.afw.image.ExposureF`
729 Template exposure, warped to match the science exposure. The
730 variance plane of the template image is modified
in place.
731 science : `lsst.afw.image.ExposureF`
732 Science exposure to subtract
from the template. The variance plane
733 of the science image
is modified
in place.
735 Exposure catalog
with external calibrations to be applied. Catalog
736 uses the detector id
for the catalog id, sorted on id
for fast
740 if visitSummary
is not None:
743 requiredTemplateFraction=self.config.requiredTemplateFraction)
745 if self.config.doScaleVariance:
749 templateVarFactor = self.scaleVariance.run(template.maskedImage)
750 sciVarFactor = self.scaleVariance.run(science.maskedImage)
751 self.
log.info(
"Template variance scaling factor: %.2f", templateVarFactor)
752 self.metadata.add(
"scaleTemplateVarianceFactor", templateVarFactor)
753 self.
log.info(
"Science variance scaling factor: %.2f", sciVarFactor)
754 self.metadata.add(
"scaleScienceVarianceFactor", sciVarFactor)
758 """Clear the mask plane of the template.
762 template : `lsst.afw.image.ExposureF`
763 Template exposure, warped to match the science exposure.
764 The mask plane will be modified in place.
767 clearMaskPlanes = [maskplane for maskplane
in mask.getMaskPlaneDict().keys()
768 if maskplane
not in self.config.preserveTemplateMask]
770 bitMaskToClear = mask.getPlaneBitMask(clearMaskPlanes)
771 mask &= ~bitMaskToClear
775 SubtractScoreOutputConnections):
780 pipelineConnections=AlardLuptonPreconvolveSubtractConnections):
785 """Subtract a template from a science image, convolving the science image
786 before computing the kernel, and also convolving the template before
789 ConfigClass = AlardLuptonPreconvolveSubtractConfig
790 _DefaultName = "alardLuptonPreconvolveSubtract"
792 def run(self, template, science, sources, finalizedPsfApCorrCatalog=None, visitSummary=None):
793 """Preconvolve the science image with its own PSF,
794 convolve the template image with a PSF-matching kernel
and subtract
795 from the preconvolved science image.
799 template : `lsst.afw.image.ExposureF`
800 The template image, which has previously been warped to the science
801 image. The template bbox will be padded by a few pixels compared to
803 science : `lsst.afw.image.ExposureF`
804 The science exposure.
806 Identified sources on the science exposure. This catalog
is used to
807 select sources
in order to perform the AL PSF matching on stamp
810 Exposure catalog
with finalized psf models
and aperture correction
811 maps to be applied. Catalog uses the detector id
for the catalog
812 id, sorted on id
for fast lookup. Deprecated
in favor of
813 ``visitSummary``,
and will be removed after v26.
815 Exposure catalog
with complete external calibrations. Catalog uses
816 the detector id
for the catalog id, sorted on id
for fast lookup.
817 Ignored (
for temporary backwards compatibility)
if
818 ``finalizedPsfApCorrCatalog``
is provided.
822 results : `lsst.pipe.base.Struct`
823 ``scoreExposure`` : `lsst.afw.image.ExposureF`
824 Result of subtracting the convolved template
and science
825 images. Attached PSF
is that of the original science image.
826 ``matchedTemplate`` : `lsst.afw.image.ExposureF`
827 Warped
and PSF-matched template exposure. Attached PSF
is that
828 of the original science image.
829 ``matchedScience`` : `lsst.afw.image.ExposureF`
830 The science exposure after convolving
with its own PSF.
831 Attached PSF
is that of the original science image.
832 ``backgroundModel`` : `lsst.afw.math.Function2D`
833 Background model that was fit
while solving
for the
836 Final kernel used to PSF-match the template to the science
839 if finalizedPsfApCorrCatalog
is not None:
841 "The finalizedPsfApCorrCatalog argument is deprecated in favor of the visitSummary "
842 "argument, and will be removed after v26.",
844 stacklevel=find_outside_stacklevel(
"lsst.ip.diffim"),
846 visitSummary = finalizedPsfApCorrCatalog
851 scienceKernel = science.psf.getKernel()
855 subtractResults = self.
runPreconvolve(template, science, matchedScience, selectSources, scienceKernel)
857 return subtractResults
859 def runPreconvolve(self, template, science, matchedScience, selectSources, preConvKernel):
860 """Convolve the science image with its own PSF, then convolve the
861 template with a matching kernel
and subtract to form the Score
866 template : `lsst.afw.image.ExposureF`
867 Template exposure, warped to match the science exposure.
868 science : `lsst.afw.image.ExposureF`
869 Science exposure to subtract
from the template.
870 matchedScience : `lsst.afw.image.ExposureF`
871 The science exposure, convolved
with the reflection of its own PSF.
873 Identified sources on the science exposure. This catalog
is used to
874 select sources
in order to perform the AL PSF matching on stamp
877 The reflection of the kernel that was used to preconvolve the
878 `science` exposure. Must be normalized to sum to 1.
882 results : `lsst.pipe.base.Struct`
884 ``scoreExposure`` : `lsst.afw.image.ExposureF`
885 Result of subtracting the convolved template
and science
886 images. Attached PSF
is that of the original science image.
887 ``matchedTemplate`` : `lsst.afw.image.ExposureF`
888 Warped
and PSF-matched template exposure. Attached PSF
is that
889 of the original science image.
890 ``matchedScience`` : `lsst.afw.image.ExposureF`
891 The science exposure after convolving
with its own PSF.
892 Attached PSF
is that of the original science image.
893 ``backgroundModel`` : `lsst.afw.math.Function2D`
894 Background model that was fit
while solving
for the
897 Final kernel used to PSF-match the template to the science
900 bbox = science.getBBox()
901 innerBBox = preConvKernel.shrinkBBox(bbox)
903 kernelSources = self.makeKernel.selectKernelSources(template[innerBBox], matchedScience[innerBBox],
904 candidateList=selectSources,
906 kernelResult = self.makeKernel.run(template[innerBBox], matchedScience[innerBBox], kernelSources,
909 matchedTemplate = self.
_convolveExposure(template, kernelResult.psfMatchingKernel,
913 photoCalib=science.photoCalib)
915 backgroundModel=(kernelResult.backgroundModel
916 if self.config.doSubtractBackground
else None))
917 correctedScore = self.
finalize(template[bbox], science, score,
918 kernelResult.psfMatchingKernel,
919 templateMatched=
True, preConvMode=
True,
920 preConvKernel=preConvKernel)
922 return lsst.pipe.base.Struct(scoreExposure=correctedScore,
923 matchedTemplate=matchedTemplate,
924 matchedScience=matchedScience,
925 backgroundModel=kernelResult.backgroundModel,
926 psfMatchingKernel=kernelResult.psfMatchingKernel)
930 """Raise NoWorkFound if template coverage < requiredTemplateFraction
934 templateExposure : `lsst.afw.image.ExposureF`
935 The template exposure to check
937 Logger for printing output.
938 requiredTemplateFraction : `float`, optional
939 Fraction of pixels of the science image required to have coverage
944 lsst.pipe.base.NoWorkFound
945 Raised
if fraction of good pixels, defined
as not having NO_DATA
946 set,
is less then the configured requiredTemplateFraction
950 pixNoData = np.count_nonzero(templateExposure.mask.array
951 & templateExposure.mask.getPlaneBitMask(
'NO_DATA'))
952 pixGood = templateExposure.getBBox().getArea() - pixNoData
953 logger.info(
"template has %d good pixels (%.1f%%)", pixGood,
954 100*pixGood/templateExposure.getBBox().getArea())
956 if pixGood/templateExposure.getBBox().getArea() < requiredTemplateFraction:
957 message = (
"Insufficient Template Coverage. (%.1f%% < %.1f%%) Not attempting subtraction. "
958 "To force subtraction, set config requiredTemplateFraction=0." % (
959 100*pixGood/templateExposure.getBBox().getArea(),
960 100*requiredTemplateFraction))
961 raise lsst.pipe.base.NoWorkFound(message)
965 """Subtract template from science, propagating relevant metadata.
969 science : `lsst.afw.Exposure`
970 The input science image.
971 template : `lsst.afw.Exposure`
972 The template to subtract from the science image.
973 backgroundModel : `lsst.afw.MaskedImage`, optional
974 Differential background model
978 difference : `lsst.afw.Exposure`
979 The subtracted image.
981 difference = science.clone()
982 if backgroundModel
is not None:
983 difference.maskedImage -= backgroundModel
984 difference.maskedImage -= template.maskedImage
988def _shapeTest(exp1, exp2, fwhmExposureBuffer, fwhmExposureGrid):
989 """Determine that the PSF of ``exp1`` is not wider than that of ``exp2``.
994 Exposure with the reference point spread function (PSF) to evaluate.
996 Exposure
with a candidate point spread function (PSF) to evaluate.
997 fwhmExposureBuffer : `float`
998 Fractional buffer margin to be left out of all sides of the image
999 during the construction of the grid to compute mean PSF FWHM
in an
1000 exposure,
if the PSF
is not available at its average position.
1001 fwhmExposureGrid : `int`
1002 Grid size to compute the mean FWHM
in an exposure,
if the PSF
is not
1003 available at its average position.
1007 True if ``exp1`` has a PSF that
is not wider than that of ``exp2``
in
1011 shape1 = getPsfFwhm(exp1.psf, average=
False)
1012 shape2 = getPsfFwhm(exp2.psf, average=
False)
1013 except InvalidParameterError:
1014 shape1 = evaluateMeanPsfFwhm(exp1,
1015 fwhmExposureBuffer=fwhmExposureBuffer,
1016 fwhmExposureGrid=fwhmExposureGrid
1018 shape2 = evaluateMeanPsfFwhm(exp2,
1019 fwhmExposureBuffer=fwhmExposureBuffer,
1020 fwhmExposureGrid=fwhmExposureGrid
1022 return shape1 <= shape2
1025 xTest = shape1[0] <= shape2[0]
1026 yTest = shape1[1] <= shape2[1]
1027 return xTest | yTest
A polymorphic base class for representing an image's Point Spread Function.
A class to contain the data, WCS, and other information needed to describe an image of the sky.
Represent a 2-dimensional array of bitmask pixels.
static MaskPixelT getPlaneBitMask(const std::vector< std::string > &names)
Return the bitmask corresponding to a vector of plane names OR'd together.
The photometric calibration of an exposure.
Parameters to control convolution.
Kernels are used for convolution with MaskedImages and (eventually) Images.
A kernel that is a linear combination of fixed basis kernels.
Custom catalog class for ExposureRecord/Table.
An integer coordinate rectangle.
runPreconvolve(self, template, science, matchedScience, selectSources, preConvKernel)
_prepareInputs(self, template, science, visitSummary=None)
_checkMask(mask, sources, badMaskPlanes)
runConvolveTemplate(self, template, science, selectSources)
_applyExternalCalibrations(self, exposure, visitSummary)
_clearMask(self, template)
_sourceSelector(self, sources, mask)
_convolveExposure(exposure, kernel, convolutionControl, bbox=None, psf=None, photoCalib=None)
runConvolveScience(self, template, science, selectSources)
finalize(self, template, science, difference, kernel, templateMatched=True, preConvMode=False, preConvKernel=None, spatiallyVarying=False)
_validateExposures(template, science)
This static class includes a variety of methods for interacting with the the logging module.
void convolve(OutImageT &convolvedImage, InImageT const &inImage, KernelT const &kernel, ConvolutionControl const &convolutionControl=ConvolutionControl())
Convolve an Image or MaskedImage with a Kernel, setting pixels of an existing output image.
_subtractImages(science, template, backgroundModel=None)
checkTemplateIsSufficient(templateExposure, logger, requiredTemplateFraction=0.)
_shapeTest(exp1, exp2, fwhmExposureBuffer, fwhmExposureGrid)