28from lsst.meas.algorithms
import ScaleVarianceTask
33from .
import MakeKernelTask, DecorrelateALKernelTask
34from lsst.utils.timer
import timeMethod
36__all__ = [
"AlardLuptonSubtractConfig",
"AlardLuptonSubtractTask",
37 "AlardLuptonPreconvolveSubtractConfig",
"AlardLuptonPreconvolveSubtractTask"]
39_dimensions = (
"instrument",
"visit",
"detector")
40_defaultTemplates = {
"coaddName":
"deep",
"fakesType":
""}
44 dimensions=_dimensions,
45 defaultTemplates=_defaultTemplates):
46 template = connectionTypes.Input(
47 doc=
"Input warped template to subtract.",
48 dimensions=(
"instrument",
"visit",
"detector"),
49 storageClass=
"ExposureF",
50 name=
"{fakesType}{coaddName}Diff_templateExp"
52 science = connectionTypes.Input(
53 doc=
"Input science exposure to subtract from.",
54 dimensions=(
"instrument",
"visit",
"detector"),
55 storageClass=
"ExposureF",
56 name=
"{fakesType}calexp"
58 sources = connectionTypes.Input(
59 doc=
"Sources measured on the science exposure; "
60 "used to select sources for making the matching kernel.",
61 dimensions=(
"instrument",
"visit",
"detector"),
62 storageClass=
"SourceCatalog",
65 finalizedPsfApCorrCatalog = connectionTypes.Input(
66 doc=(
"Per-visit finalized psf models and aperture correction maps. "
67 "These catalogs use the detector id for the catalog id, "
68 "sorted on id for fast lookup."),
69 dimensions=(
"instrument",
"visit"),
70 storageClass=
"ExposureCatalog",
71 name=
"finalVisitSummary",
76 if not config.doApplyFinalizedPsf:
77 self.inputs.remove(
"finalizedPsfApCorrCatalog")
81 dimensions=_dimensions,
82 defaultTemplates=_defaultTemplates):
83 difference = connectionTypes.Output(
84 doc=
"Result of subtracting convolved template from science image.",
85 dimensions=(
"instrument",
"visit",
"detector"),
86 storageClass=
"ExposureF",
87 name=
"{fakesType}{coaddName}Diff_differenceTempExp",
89 matchedTemplate = connectionTypes.Output(
90 doc=
"Warped and PSF-matched template used to create `subtractedExposure`.",
91 dimensions=(
"instrument",
"visit",
"detector"),
92 storageClass=
"ExposureF",
93 name=
"{fakesType}{coaddName}Diff_matchedExp",
98 dimensions=_dimensions,
99 defaultTemplates=_defaultTemplates):
100 scoreExposure = connectionTypes.Output(
101 doc=
"The maximum likelihood image, used for the detection of diaSources.",
102 dimensions=(
"instrument",
"visit",
"detector"),
103 storageClass=
"ExposureF",
104 name=
"{fakesType}{coaddName}Diff_scoreExp",
113 makeKernel = lsst.pex.config.ConfigurableField(
114 target=MakeKernelTask,
115 doc=
"Task to construct a matching kernel for convolution.",
117 doDecorrelation = lsst.pex.config.Field(
120 doc=
"Perform diffim decorrelation to undo pixel correlation due to A&L "
121 "kernel convolution? If True, also update the diffim PSF."
123 decorrelate = lsst.pex.config.ConfigurableField(
124 target=DecorrelateALKernelTask,
125 doc=
"Task to decorrelate the image difference.",
127 requiredTemplateFraction = lsst.pex.config.Field(
130 doc=
"Abort task if template covers less than this fraction of pixels."
131 " Setting to 0 will always attempt image subtraction."
133 doScaleVariance = lsst.pex.config.Field(
136 doc=
"Scale variance of the image difference?"
138 scaleVariance = lsst.pex.config.ConfigurableField(
139 target=ScaleVarianceTask,
140 doc=
"Subtask to rescale the variance of the template to the statistically expected level."
142 doSubtractBackground = lsst.pex.config.Field(
143 doc=
"Subtract the background fit when solving the kernel?",
147 doApplyFinalizedPsf = lsst.pex.config.Field(
148 doc=
"Replace science Exposure's psf and aperture correction map"
149 " with those in finalizedPsfApCorrCatalog.",
153 detectionThreshold = lsst.pex.config.Field(
156 doc=
"Minimum signal to noise ration of detected sources "
157 "to use for calculating the PSF matching kernel."
159 badSourceFlags = lsst.pex.config.ListField(
161 doc=
"Flags that, if set, the associated source should not "
162 "be used to determine the PSF matching kernel.",
163 default=(
"sky_source",
"slot_Centroid_flag",
164 "slot_ApFlux_flag",
"slot_PsfFlux_flag", ),
166 badMaskPlanes = lsst.pex.config.ListField(
168 default=(
"NO_DATA",
"BAD",
"SAT",
"EDGE"),
169 doc=
"Mask planes to exclude when selecting sources for PSF matching."
175 self.
makeKernel.kernel.active.spatialKernelOrder = 1
176 self.
makeKernel.kernel.active.spatialBgOrder = 2
180 pipelineConnections=AlardLuptonSubtractConnections):
181 mode = lsst.pex.config.ChoiceField(
183 default=
"convolveTemplate",
184 allowed={
"auto":
"Choose which image to convolve at runtime.",
185 "convolveScience":
"Only convolve the science image.",
186 "convolveTemplate":
"Only convolve the template image."},
187 doc=
"Choose which image to convolve at runtime, or require that a specific image is convolved."
192 """Compute the image difference of a science and template image using
193 the Alard & Lupton (1998) algorithm.
195 ConfigClass = AlardLuptonSubtractConfig
196 _DefaultName = "alardLuptonSubtract"
200 self.makeSubtask(
"decorrelate")
201 self.makeSubtask(
"makeKernel")
202 if self.config.doScaleVariance:
203 self.makeSubtask(
"scaleVariance")
211 def _applyExternalCalibrations(self, exposure, finalizedPsfApCorrCatalog):
212 """Replace calibrations (psf, and ApCorrMap) on this exposure with external ones.".
216 exposure : `lsst.afw.image.exposure.Exposure`
217 Input exposure to adjust calibrations.
219 Exposure catalog with finalized psf models
and aperture correction
220 maps to be applied
if config.doApplyFinalizedPsf=
True. Catalog uses
221 the detector id
for the catalog id, sorted on id
for fast lookup.
225 exposure : `lsst.afw.image.exposure.Exposure`
226 Exposure
with adjusted calibrations.
228 detectorId = exposure.info.getDetector().getId()
230 row = finalizedPsfApCorrCatalog.find(detectorId)
232 self.log.warning(
"Detector id %s not found in finalizedPsfApCorrCatalog; "
233 "Using original psf.", detectorId)
236 apCorrMap = row.getApCorrMap()
238 self.log.warning(
"Detector id %s has None for psf in "
239 "finalizedPsfApCorrCatalog; Using original psf and aperture correction.",
241 elif apCorrMap
is None:
242 self.log.warning(
"Detector id %s has None for apCorrMap in "
243 "finalizedPsfApCorrCatalog; Using original psf and aperture correction.",
247 exposure.info.setApCorrMap(apCorrMap)
252 def run(self, template, science, sources, finalizedPsfApCorrCatalog=None):
253 """PSF match, subtract, and decorrelate two images.
257 template : `lsst.afw.image.ExposureF`
258 Template exposure, warped to match the science exposure.
259 science : `lsst.afw.image.ExposureF`
260 Science exposure to subtract from the template.
262 Identified sources on the science exposure. This catalog
is used to
263 select sources
in order to perform the AL PSF matching on stamp
266 Exposure catalog
with finalized psf models
and aperture correction
267 maps to be applied
if config.doApplyFinalizedPsf=
True. Catalog uses
268 the detector id
for the catalog id, sorted on id
for fast lookup.
272 results : `lsst.pipe.base.Struct`
273 ``difference`` : `lsst.afw.image.ExposureF`
274 Result of subtracting template
and science.
275 ``matchedTemplate`` : `lsst.afw.image.ExposureF`
276 Warped
and PSF-matched template exposure.
277 ``backgroundModel`` : `lsst.afw.math.Function2D`
278 Background model that was fit
while solving
for the PSF-matching kernel
280 Kernel used to PSF-match the convolved image.
285 If an unsupported convolution mode
is supplied.
287 If there are too few sources to calculate the PSF matching kernel.
288 lsst.pipe.base.NoWorkFound
289 Raised
if fraction of good pixels, defined
as not having NO_DATA
290 set,
is less then the configured requiredTemplateFraction
293 finalizedPsfApCorrCatalog=finalizedPsfApCorrCatalog)
296 fwhmExposureBuffer = self.config.makeKernel.fwhmExposureBuffer
297 fwhmExposureGrid = self.config.makeKernel.fwhmExposureGrid
306 templatePsfSize = getPsfFwhm(template.psf)
307 sciencePsfSize = getPsfFwhm(science.psf)
308 except InvalidParameterError:
309 self.log.info(
"Unable to evaluate PSF at the average position. "
310 "Evaluting PSF on a grid of points."
312 templatePsfSize = evaluateMeanPsfFwhm(template,
313 fwhmExposureBuffer=fwhmExposureBuffer,
314 fwhmExposureGrid=fwhmExposureGrid
316 sciencePsfSize = evaluateMeanPsfFwhm(science,
317 fwhmExposureBuffer=fwhmExposureBuffer,
318 fwhmExposureGrid=fwhmExposureGrid
320 self.log.info(
"Science PSF FWHM: %f pixels", sciencePsfSize)
321 self.log.info(
"Template PSF FWHM: %f pixels", templatePsfSize)
324 if self.config.mode ==
"auto":
325 convolveTemplate = _shapeTest(template,
327 fwhmExposureBuffer=fwhmExposureBuffer,
328 fwhmExposureGrid=fwhmExposureGrid)
330 if sciencePsfSize < templatePsfSize:
331 self.log.info(
"Average template PSF size is greater, "
332 "but science PSF greater in one dimension: convolving template image.")
334 self.log.info(
"Science PSF size is greater: convolving template image.")
336 self.log.info(
"Template PSF size is greater: convolving science image.")
337 elif self.config.mode ==
"convolveTemplate":
338 self.log.info(
"`convolveTemplate` is set: convolving template image.")
339 convolveTemplate =
True
340 elif self.config.mode ==
"convolveScience":
341 self.log.info(
"`convolveScience` is set: convolving science image.")
342 convolveTemplate =
False
344 raise RuntimeError(
"Cannot handle AlardLuptonSubtract mode: %s", self.config.mode)
351 return subtractResults
354 """Convolve the template image with a PSF-matching kernel and subtract
355 from the science image.
359 template : `lsst.afw.image.ExposureF`
360 Template exposure, warped to match the science exposure.
361 science : `lsst.afw.image.ExposureF`
362 Science exposure to subtract
from the template.
364 Identified sources on the science exposure. This catalog
is used to
365 select sources
in order to perform the AL PSF matching on stamp
370 results : `lsst.pipe.base.Struct`
372 ``difference`` : `lsst.afw.image.ExposureF`
373 Result of subtracting template
and science.
374 ``matchedTemplate`` : `lsst.afw.image.ExposureF`
375 Warped
and PSF-matched template exposure.
376 ``backgroundModel`` : `lsst.afw.math.Function2D`
377 Background model that was fit
while solving
for the PSF-matching kernel
379 Kernel used to PSF-match the template to the science image.
381 kernelSources = self.makeKernel.selectKernelSources(template, science,
382 candidateList=selectSources,
384 kernelResult = self.makeKernel.
run(template, science, kernelSources,
387 matchedTemplate = self.
_convolveExposure(template, kernelResult.psfMatchingKernel,
389 bbox=science.getBBox(),
391 photoCalib=science.photoCalib)
393 difference = _subtractImages(science, matchedTemplate,
394 backgroundModel=(kernelResult.backgroundModel
395 if self.config.doSubtractBackground
else None))
396 correctedExposure = self.
finalize(template, science, difference,
397 kernelResult.psfMatchingKernel,
398 templateMatched=
True)
400 return lsst.pipe.base.Struct(difference=correctedExposure,
401 matchedTemplate=matchedTemplate,
402 matchedScience=science,
403 backgroundModel=kernelResult.backgroundModel,
404 psfMatchingKernel=kernelResult.psfMatchingKernel)
407 """Convolve the science image with a PSF-matching kernel and subtract the template image.
411 template : `lsst.afw.image.ExposureF`
412 Template exposure, warped to match the science exposure.
413 science : `lsst.afw.image.ExposureF`
414 Science exposure to subtract from the template.
416 Identified sources on the science exposure. This catalog
is used to
417 select sources
in order to perform the AL PSF matching on stamp
422 results : `lsst.pipe.base.Struct`
424 ``difference`` : `lsst.afw.image.ExposureF`
425 Result of subtracting template
and science.
426 ``matchedTemplate`` : `lsst.afw.image.ExposureF`
427 Warped template exposure. Note that
in this case, the template
428 is not PSF-matched to the science image.
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 science image to the template.
434 bbox = science.getBBox()
435 kernelSources = self.makeKernel.selectKernelSources(science, template,
436 candidateList=selectSources,
438 kernelResult = self.makeKernel.
run(science, template, kernelSources,
440 modelParams = kernelResult.backgroundModel.getParameters()
442 kernelResult.backgroundModel.setParameters([-p
for p
in modelParams])
444 kernelImage = lsst.afw.image.ImageD(kernelResult.psfMatchingKernel.getDimensions())
445 norm = kernelResult.psfMatchingKernel.computeImage(kernelImage, doNormalize=
False)
452 matchedScience.maskedImage /= norm
453 matchedTemplate = template.clone()[bbox]
454 matchedTemplate.maskedImage /= norm
455 matchedTemplate.setPhotoCalib(science.photoCalib)
457 difference = _subtractImages(matchedScience, matchedTemplate,
458 backgroundModel=(kernelResult.backgroundModel
459 if self.config.doSubtractBackground
else None))
461 correctedExposure = self.
finalize(template, science, difference,
462 kernelResult.psfMatchingKernel,
463 templateMatched=
False)
465 return lsst.pipe.base.Struct(difference=correctedExposure,
466 matchedTemplate=matchedTemplate,
467 matchedScience=matchedScience,
468 backgroundModel=kernelResult.backgroundModel,
469 psfMatchingKernel=kernelResult.psfMatchingKernel,)
471 def finalize(self, template, science, difference, kernel,
472 templateMatched=True,
475 spatiallyVarying=False):
476 """Decorrelate the difference image to undo the noise correlations
477 caused by convolution.
481 template : `lsst.afw.image.ExposureF`
482 Template exposure, warped to match the science exposure.
483 science : `lsst.afw.image.ExposureF`
484 Science exposure to subtract from the template.
485 difference : `lsst.afw.image.ExposureF`
486 Result of subtracting template
and science.
488 An (optionally spatially-varying) PSF matching kernel
489 templateMatched : `bool`, optional
490 Was the template PSF-matched to the science image?
491 preConvMode : `bool`, optional
492 Was the science image preconvolved
with its own PSF
493 before PSF matching the template?
495 If
not `
None`, then the science image was pre-convolved
with
496 (the reflection of) this kernel. Must be normalized to sum to 1.
497 spatiallyVarying : `bool`, optional
498 Compute the decorrelation kernel spatially varying across the image?
502 correctedExposure : `lsst.afw.image.ExposureF`
503 The decorrelated image difference.
507 mask = difference.mask
508 mask &= ~(mask.getPlaneBitMask(
"DETECTED") | mask.getPlaneBitMask(
"DETECTED_NEGATIVE"))
510 if self.config.doDecorrelation:
511 self.log.info(
"Decorrelating image difference.")
512 correctedExposure = self.decorrelate.
run(science, template[science.getBBox()], difference, kernel,
513 templateMatched=templateMatched,
514 preConvMode=preConvMode,
515 preConvKernel=preConvKernel,
516 spatiallyVarying=spatiallyVarying).correctedExposure
518 self.log.info(
"NOT decorrelating image difference.")
519 correctedExposure = difference
520 return correctedExposure
523 def _validateExposures(template, science):
524 """Check that the WCS of the two Exposures match, and the template bbox
525 contains the science bbox.
529 template : `lsst.afw.image.ExposureF`
530 Template exposure, warped to match the science exposure.
531 science : `lsst.afw.image.ExposureF`
532 Science exposure to subtract from the template.
537 Raised
if the WCS of the template
is not equal to the science WCS,
538 or if the science image
is not fully contained
in the template
541 assert template.wcs == science.wcs,\
542 "Template and science exposure WCS are not identical."
543 templateBBox = template.getBBox()
544 scienceBBox = science.getBBox()
546 assert templateBBox.contains(scienceBBox),\
547 "Template bbox does not contain all of the science image."
550 def _convolveExposure(exposure, kernel, convolutionControl,
554 """Convolve an exposure with the given kernel.
558 exposure : `lsst.afw.Exposure`
559 exposure to convolve.
561 PSF matching kernel computed in the ``makeKernel`` subtask.
563 Configuration
for convolve algorithm.
565 Bounding box to trim the convolved exposure to.
567 Point spread function (PSF) to set
for the convolved exposure.
569 Photometric calibration of the convolved exposure.
573 convolvedExp : `lsst.afw.Exposure`
576 convolvedExposure = exposure.clone()
578 convolvedExposure.setPsf(psf)
579 if photoCalib
is not None:
580 convolvedExposure.setPhotoCalib(photoCalib)
581 convolvedImage = lsst.afw.image.MaskedImageF(exposure.getBBox())
583 convolvedExposure.setMaskedImage(convolvedImage)
585 return convolvedExposure
587 return convolvedExposure[bbox]
589 def _sourceSelector(self, sources, mask):
590 """Select sources from a catalog that meet the selection criteria.
595 Input source catalog to select sources from.
597 The image mask plane to use to reject sources
598 based on their location on the ccd.
603 The input source catalog,
with flagged
and low signal-to-noise
609 If there are too few sources to compute the PSF matching kernel
610 remaining after source selection.
612 flags = np.ones(len(sources), dtype=bool)
613 for flag
in self.config.badSourceFlags:
615 flags *= ~sources[flag]
616 except Exception
as e:
617 self.log.warning(
"Could not apply source flag: %s", e)
618 sToNFlag = (sources.getPsfInstFlux()/sources.getPsfInstFluxErr()) > self.config.detectionThreshold
620 flags *= self.
_checkMask(mask, sources, self.config.badMaskPlanes)
621 selectSources = sources[flags]
622 self.log.info(
"%i/%i=%.1f%% of sources selected for PSF matching from the input catalog",
623 len(selectSources), len(sources), 100*len(selectSources)/len(sources))
624 if len(selectSources) < self.config.makeKernel.nStarPerCell:
625 self.log.error(
"Too few sources to calculate the PSF matching kernel: "
626 "%i selected but %i needed for the calculation.",
627 len(selectSources), self.config.makeKernel.nStarPerCell)
628 raise RuntimeError(
"Cannot compute PSF matching kernel: too few sources selected.")
630 return selectSources.copy(deep=
True)
633 def _checkMask(mask, sources, badMaskPlanes):
634 """Exclude sources that are located on masked pixels.
639 The image mask plane to use to reject sources
640 based on the location of their centroid on the ccd.
642 The source catalog to evaluate.
643 badMaskPlanes : `list` of `str`
644 List of the names of the mask planes to exclude.
648 flags : `numpy.ndarray` of `bool`
649 Array indicating whether each source in the catalog should be
650 kept (
True)
or rejected (
False) based on the value of the
651 mask plane at its location.
654 xv = np.rint(sources.getX() - mask.getX0())
655 yv = np.rint(sources.getY() - mask.getY0())
657 mv = mask.array[yv.astype(int), xv.astype(int)]
658 flags = np.bitwise_and(mv, badPixelMask) == 0
661 def _prepareInputs(self, template, science,
662 finalizedPsfApCorrCatalog=None):
663 """Perform preparatory calculations common to all Alard&Lupton Tasks.
667 template : `lsst.afw.image.ExposureF`
668 Template exposure, warped to match the science exposure.
669 The variance plane of the template image is modified
in place.
670 science : `lsst.afw.image.ExposureF`
671 Science exposure to subtract
from the template.
672 The variance plane of the science image
is modified
in place.
674 Exposure catalog
with finalized psf models
and aperture correction
675 maps to be applied
if config.doApplyFinalizedPsf=
True. Catalog uses
676 the detector id
for the catalog id, sorted on id
for fast lookup.
679 if self.config.doApplyFinalizedPsf:
681 finalizedPsfApCorrCatalog=finalizedPsfApCorrCatalog)
683 requiredTemplateFraction=self.config.requiredTemplateFraction)
685 if self.config.doScaleVariance:
689 templateVarFactor = self.scaleVariance.
run(template.maskedImage)
690 sciVarFactor = self.scaleVariance.
run(science.maskedImage)
691 self.log.info(
"Template variance scaling factor: %.2f", templateVarFactor)
692 self.metadata.add(
"scaleTemplateVarianceFactor", templateVarFactor)
693 self.log.info(
"Science variance scaling factor: %.2f", sciVarFactor)
694 self.metadata.add(
"scaleScienceVarianceFactor", sciVarFactor)
698 SubtractScoreOutputConnections):
703 pipelineConnections=AlardLuptonPreconvolveSubtractConnections):
708 """Subtract a template from a science image, convolving the science image
709 before computing the kernel, and also convolving the template before
712 ConfigClass = AlardLuptonPreconvolveSubtractConfig
713 _DefaultName = "alardLuptonPreconvolveSubtract"
715 def run(self, template, science, sources, finalizedPsfApCorrCatalog=None):
716 """Preconvolve the science image with its own PSF,
717 convolve the template image with a PSF-matching kernel
and subtract
718 from the preconvolved science image.
722 template : `lsst.afw.image.ExposureF`
723 The template image, which has previously been warped to
724 the science image. The template bbox will be padded by a few pixels
725 compared to the science bbox.
726 science : `lsst.afw.image.ExposureF`
727 The science exposure.
729 Identified sources on the science exposure. This catalog
is used to
730 select sources
in order to perform the AL PSF matching on stamp
733 Exposure catalog
with finalized psf models
and aperture correction
734 maps to be applied
if config.doApplyFinalizedPsf=
True. Catalog uses
735 the detector id
for the catalog id, sorted on id
for fast lookup.
739 results : `lsst.pipe.base.Struct`
740 ``scoreExposure`` : `lsst.afw.image.ExposureF`
741 Result of subtracting the convolved template
and science images.
742 Attached PSF
is that of the original science image.
743 ``matchedTemplate`` : `lsst.afw.image.ExposureF`
744 Warped
and PSF-matched template exposure.
745 Attached PSF
is that of the original science image.
746 ``matchedScience`` : `lsst.afw.image.ExposureF`
747 The science exposure after convolving
with its own PSF.
748 Attached PSF
is that of the original science image.
749 ``backgroundModel`` : `lsst.afw.math.Function2D`
750 Background model that was fit
while solving
for the PSF-matching kernel
752 Final kernel used to PSF-match the template to the science image.
755 finalizedPsfApCorrCatalog=finalizedPsfApCorrCatalog)
758 scienceKernel = science.psf.getKernel()
762 subtractResults = self.
runPreconvolve(template, science, matchedScience, selectSources, scienceKernel)
764 return subtractResults
766 def runPreconvolve(self, template, science, matchedScience, selectSources, preConvKernel):
767 """Convolve the science image with its own PSF, then convolve the
768 template with a matching kernel
and subtract to form the Score exposure.
772 template : `lsst.afw.image.ExposureF`
773 Template exposure, warped to match the science exposure.
774 science : `lsst.afw.image.ExposureF`
775 Science exposure to subtract
from the template.
776 matchedScience : `lsst.afw.image.ExposureF`
777 The science exposure, convolved
with the reflection of its own PSF.
779 Identified sources on the science exposure. This catalog
is used to
780 select sources
in order to perform the AL PSF matching on stamp
783 The reflection of the kernel that was used to preconvolve
784 the `science` exposure.
785 Must be normalized to sum to 1.
789 results : `lsst.pipe.base.Struct`
791 ``scoreExposure`` : `lsst.afw.image.ExposureF`
792 Result of subtracting the convolved template
and science images.
793 Attached PSF
is that of the original science image.
794 ``matchedTemplate`` : `lsst.afw.image.ExposureF`
795 Warped
and PSF-matched template exposure.
796 Attached PSF
is that of the original science image.
797 ``matchedScience`` : `lsst.afw.image.ExposureF`
798 The science exposure after convolving
with its own PSF.
799 Attached PSF
is that of the original science image.
800 ``backgroundModel`` : `lsst.afw.math.Function2D`
801 Background model that was fit
while solving
for the PSF-matching kernel
803 Final kernel used to PSF-match the template to the science image.
805 bbox = science.getBBox()
806 innerBBox = preConvKernel.shrinkBBox(bbox)
808 kernelSources = self.makeKernel.selectKernelSources(template[innerBBox], science[innerBBox],
809 candidateList=selectSources,
811 kernelResult = self.makeKernel.
run(template[innerBBox], matchedScience[innerBBox], kernelSources,
814 matchedTemplate = self.
_convolveExposure(template, kernelResult.psfMatchingKernel,
818 photoCalib=science.photoCalib)
819 score = _subtractImages(matchedScience, matchedTemplate,
820 backgroundModel=(kernelResult.backgroundModel
821 if self.config.doSubtractBackground
else None))
822 correctedScore = self.
finalize(template[bbox], science, score,
823 kernelResult.psfMatchingKernel,
824 templateMatched=
True, preConvMode=
True,
825 preConvKernel=preConvKernel)
827 return lsst.pipe.base.Struct(scoreExposure=correctedScore,
828 matchedTemplate=matchedTemplate,
829 matchedScience=matchedScience,
830 backgroundModel=kernelResult.backgroundModel,
831 psfMatchingKernel=kernelResult.psfMatchingKernel)
835 """Raise NoWorkFound if template coverage < requiredTemplateFraction
839 templateExposure : `lsst.afw.image.ExposureF`
840 The template exposure to check
842 Logger for printing output.
843 requiredTemplateFraction : `float`, optional
844 Fraction of pixels of the science image required to have coverage
849 lsst.pipe.base.NoWorkFound
850 Raised
if fraction of good pixels, defined
as not having NO_DATA
851 set,
is less then the configured requiredTemplateFraction
855 pixNoData = np.count_nonzero(templateExposure.mask.array
856 & templateExposure.mask.getPlaneBitMask(
'NO_DATA'))
857 pixGood = templateExposure.getBBox().getArea() - pixNoData
858 logger.info(
"template has %d good pixels (%.1f%%)", pixGood,
859 100*pixGood/templateExposure.getBBox().getArea())
861 if pixGood/templateExposure.getBBox().getArea() < requiredTemplateFraction:
862 message = (
"Insufficient Template Coverage. (%.1f%% < %.1f%%) Not attempting subtraction. "
863 "To force subtraction, set config requiredTemplateFraction=0." % (
864 100*pixGood/templateExposure.getBBox().getArea(),
865 100*requiredTemplateFraction))
866 raise lsst.pipe.base.NoWorkFound(message)
869def _subtractImages(science, template, backgroundModel=None):
870 """Subtract template from science, propagating relevant metadata.
874 science : `lsst.afw.Exposure`
875 The input science image.
876 template : `lsst.afw.Exposure`
877 The template to subtract from the science image.
878 backgroundModel : `lsst.afw.MaskedImage`, optional
879 Differential background model
883 difference : `lsst.afw.Exposure`
884 The subtracted image.
886 difference = science.clone()
887 if backgroundModel
is not None:
888 difference.maskedImage -= backgroundModel
889 difference.maskedImage -= template.maskedImage
893def _shapeTest(exp1, exp2, fwhmExposureBuffer, fwhmExposureGrid):
894 """Determine that the PSF of ``exp1`` is not wider than that of ``exp2``.
899 Exposure with the reference point spread function (PSF) to evaluate.
901 Exposure
with a candidate point spread function (PSF) to evaluate.
902 fwhmExposureBuffer : `float`
903 Fractional buffer margin to be left out of all sides of the image
904 during the construction of the grid to compute mean PSF FWHM
in an
905 exposure,
if the PSF
is not available at its average position.
906 fwhmExposureGrid : `int`
907 Grid size to compute the mean FWHM
in an exposure,
if the PSF
is not
908 available at its average position.
912 True if ``exp1`` has a PSF that
is not wider than that of ``exp2``
in
916 shape1 = getPsfFwhm(exp1.psf, average=
False)
917 shape2 = getPsfFwhm(exp2.psf, average=
False)
918 except InvalidParameterError:
919 shape1 = evaluateMeanPsfFwhm(exp1,
920 fwhmExposureBuffer=fwhmExposureBuffer,
921 fwhmExposureGrid=fwhmExposureGrid
923 shape2 = evaluateMeanPsfFwhm(exp2,
924 fwhmExposureBuffer=fwhmExposureBuffer,
925 fwhmExposureGrid=fwhmExposureGrid
927 return shape1 <= shape2
930 xTest = shape1[0] <= shape2[0]
931 yTest = shape1[1] <= shape2[1]
static MaskPixelT getPlaneBitMask(const std::vector< std::string > &names)
def runPreconvolve(self, template, science, matchedScience, selectSources, preConvKernel)
def run(self, template, science, sources, finalizedPsfApCorrCatalog=None)
def _sourceSelector(self, sources, mask)
def finalize(self, template, science, difference, kernel, templateMatched=True, preConvMode=False, preConvKernel=None, spatiallyVarying=False)
def _convolveExposure(exposure, kernel, convolutionControl, bbox=None, psf=None, photoCalib=None)
def runConvolveTemplate(self, template, science, selectSources)
def _applyExternalCalibrations(self, exposure, finalizedPsfApCorrCatalog)
def _checkMask(mask, sources, badMaskPlanes)
def _validateExposures(template, science)
def _prepareInputs(self, template, science, finalizedPsfApCorrCatalog=None)
def __init__(self, **kwargs)
def run(self, template, science, sources, finalizedPsfApCorrCatalog=None)
def runConvolveScience(self, template, science, selectSources)
void convolve(OutImageT &convolvedImage, InImageT const &inImage, KernelT const &kernel, ConvolutionControl const &convolutionControl=ConvolutionControl())
def checkTemplateIsSufficient(templateExposure, logger, requiredTemplateFraction=0.)