lsst.ip.diffim gf800f83d46+fe13c1df23
subtractImages.py
Go to the documentation of this file.
1# This file is part of ip_diffim.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (https://www.lsst.org).
6# See the COPYRIGHT file at the top-level directory of this distribution
7# for details of code ownership.
8#
9# This program is free software: you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation, either version 3 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program. If not, see <https://www.gnu.org/licenses/>.
21
22import numpy as np
23
24import lsst.afw.image
25import lsst.afw.math
26import lsst.geom
27from lsst.ip.diffim.utils import getPsfFwhm
28from lsst.meas.algorithms import ScaleVarianceTask
29import lsst.pex.config
30import lsst.pipe.base
31from lsst.pipe.base import connectionTypes
32from . import MakeKernelTask, DecorrelateALKernelTask
33
34__all__ = ["AlardLuptonSubtractConfig", "AlardLuptonSubtractTask"]
35
36_dimensions = ("instrument", "visit", "detector")
37_defaultTemplates = {"coaddName": "deep", "fakesType": ""}
38
39
40class SubtractInputConnections(lsst.pipe.base.PipelineTaskConnections,
41 dimensions=_dimensions,
42 defaultTemplates=_defaultTemplates):
43 template = connectionTypes.Input(
44 doc="Input warped template to subtract.",
45 dimensions=("instrument", "visit", "detector"),
46 storageClass="ExposureF",
47 name="{fakesType}{coaddName}Diff_templateExp"
48 )
49 science = connectionTypes.Input(
50 doc="Input science exposure to subtract from.",
51 dimensions=("instrument", "visit", "detector"),
52 storageClass="ExposureF",
53 name="{fakesType}calexp"
54 )
55 sources = connectionTypes.Input(
56 doc="Sources measured on the science exposure; "
57 "used to select sources for making the matching kernel.",
58 dimensions=("instrument", "visit", "detector"),
59 storageClass="SourceCatalog",
60 name="{fakesType}src"
61 )
62 finalizedPsfApCorrCatalog = connectionTypes.Input(
63 doc=("Per-visit finalized psf models and aperture correction maps. "
64 "These catalogs use the detector id for the catalog id, "
65 "sorted on id for fast lookup."),
66 dimensions=("instrument", "visit"),
67 storageClass="ExposureCatalog",
68 name="finalized_psf_ap_corr_catalog",
69 )
70
71
72class SubtractImageOutputConnections(lsst.pipe.base.PipelineTaskConnections,
73 dimensions=_dimensions,
74 defaultTemplates=_defaultTemplates):
75 difference = connectionTypes.Output(
76 doc="Result of subtracting convolved template from science image.",
77 dimensions=("instrument", "visit", "detector"),
78 storageClass="ExposureF",
79 name="{fakesType}{coaddName}Diff_differenceTempExp",
80 )
81 matchedTemplate = connectionTypes.Output(
82 doc="Warped and PSF-matched template used to create `subtractedExposure`.",
83 dimensions=("instrument", "visit", "detector"),
84 storageClass="ExposureF",
85 name="{fakesType}{coaddName}Diff_matchedExp",
86 )
87
88
90
91 def __init__(self, *, config=None):
92 super().__init__(config=config)
93 if not config.doApplyFinalizedPsf:
94 self.inputs.remove("finalizedPsfApCorrCatalog")
95
96
97class AlardLuptonSubtractConfig(lsst.pipe.base.PipelineTaskConfig,
98 pipelineConnections=AlardLuptonSubtractConnections):
99 mode = lsst.pex.config.ChoiceField(
100 dtype=str,
101 default="auto",
102 allowed={"auto": "Choose which image to convolve at runtime.",
103 "convolveScience": "Only convolve the science image.",
104 "convolveTemplate": "Only convolve the template image."},
105 doc="Choose which image to convolve at runtime, or require that a specific image is convolved."
106 )
107 makeKernel = lsst.pex.config.ConfigurableField(
108 target=MakeKernelTask,
109 doc="Task to construct a matching kernel for convolution.",
110 )
111 doDecorrelation = lsst.pex.config.Field(
112 dtype=bool,
113 default=True,
114 doc="Perform diffim decorrelation to undo pixel correlation due to A&L "
115 "kernel convolution? If True, also update the diffim PSF."
116 )
117 decorrelate = lsst.pex.config.ConfigurableField(
118 target=DecorrelateALKernelTask,
119 doc="Task to decorrelate the image difference.",
120 )
121 requiredTemplateFraction = lsst.pex.config.Field(
122 dtype=float,
123 default=0.1,
124 doc="Abort task if template covers less than this fraction of pixels."
125 " Setting to 0 will always attempt image subtraction."
126 )
127 doScaleVariance = lsst.pex.config.Field(
128 dtype=bool,
129 default=True,
130 doc="Scale variance of the image difference?"
131 )
132 scaleVariance = lsst.pex.config.ConfigurableField(
133 target=ScaleVarianceTask,
134 doc="Subtask to rescale the variance of the template to the statistically expected level."
135 )
136 doSubtractBackground = lsst.pex.config.Field(
137 doc="Subtract the background fit when solving the kernel?",
138 dtype=bool,
139 default=True,
140 )
141 doApplyFinalizedPsf = lsst.pex.config.Field(
142 doc="Replace science Exposure's psf and aperture correction map"
143 " with those in finalizedPsfApCorrCatalog.",
144 dtype=bool,
145 default=False,
146 )
147
148 forceCompatibility = lsst.pex.config.Field(
149 dtype=bool,
150 default=False,
151 doc="Set up and run diffim using settings that ensure the results"
152 "are compatible with the old version in pipe_tasks.",
153 deprecated="This option is only for backwards compatibility purposes"
154 " and will be removed after v24.",
155 )
156
157 def setDefaults(self):
158 self.makeKernel.kernel.name = "AL"
159 self.makeKernel.kernel.active.fitForBackground = self.doSubtractBackground
160 self.makeKernel.kernel.active.spatialKernelOrder = 1
161 self.makeKernel.kernel.active.spatialBgOrder = 2
162
163 def validate(self):
164 if self.forceCompatibility:
165 self.mode = "convolveTemplate"
166
167
168class AlardLuptonSubtractTask(lsst.pipe.base.PipelineTask):
169 """Compute the image difference of a science and template image using
170 the Alard & Lupton (1998) algorithm.
171 """
172 ConfigClass = AlardLuptonSubtractConfig
173 _DefaultName = "alardLuptonSubtract"
174
175 def __init__(self, **kwargs):
176 super().__init__(**kwargs)
177 self.makeSubtask("decorrelate")
178 self.makeSubtask("makeKernel")
179 if self.config.doScaleVariance:
180 self.makeSubtask("scaleVariance")
181
183 # Normalization is an extra, unnecessary, calculation and will result
184 # in mis-subtraction of the images if there are calibration errors.
185 self.convolutionControl.setDoNormalize(False)
186 self.convolutionControl.setDoCopyEdge(True)
187
188 def _applyExternalCalibrations(self, exposure, finalizedPsfApCorrCatalog):
189 """Replace calibrations (psf, and ApCorrMap) on this exposure with external ones.".
190
191 Parameters
192 ----------
193 exposure : `lsst.afw.image.exposure.Exposure`
194 Input exposure to adjust calibrations.
195 finalizedPsfApCorrCatalog : `lsst.afw.table.ExposureCatalog`
196 Exposure catalog with finalized psf models and aperture correction
197 maps to be applied if config.doApplyFinalizedPsf=True. Catalog uses
198 the detector id for the catalog id, sorted on id for fast lookup.
199
200 Returns
201 -------
202 exposure : `lsst.afw.image.exposure.Exposure`
203 Exposure with adjusted calibrations.
204 """
205 detectorId = exposure.info.getDetector().getId()
206
207 row = finalizedPsfApCorrCatalog.find(detectorId)
208 if row is None:
209 self.log.warning("Detector id %s not found in finalizedPsfApCorrCatalog; "
210 "Using original psf.", detectorId)
211 else:
212 psf = row.getPsf()
213 apCorrMap = row.getApCorrMap()
214 if psf is None:
215 self.log.warning("Detector id %s has None for psf in "
216 "finalizedPsfApCorrCatalog; Using original psf and aperture correction.",
217 detectorId)
218 elif apCorrMap is None:
219 self.log.warning("Detector id %s has None for apCorrMap in "
220 "finalizedPsfApCorrCatalog; Using original psf and aperture correction.",
221 detectorId)
222 else:
223 exposure.setPsf(psf)
224 exposure.info.setApCorrMap(apCorrMap)
225
226 return exposure
227
228 def run(self, template, science, sources, finalizedPsfApCorrCatalog=None):
229 """PSF match, subtract, and decorrelate two images.
230
231 Parameters
232 ----------
233 template : `lsst.afw.image.ExposureF`
234 Template exposure, warped to match the science exposure.
235 science : `lsst.afw.image.ExposureF`
236 Science exposure to subtract from the template.
238 Identified sources on the science exposure. This catalog is used to
239 select sources in order to perform the AL PSF matching on stamp
240 images around them.
241 finalizedPsfApCorrCatalog : `lsst.afw.table.ExposureCatalog`, optional
242 Exposure catalog with finalized psf models and aperture correction
243 maps to be applied if config.doApplyFinalizedPsf=True. Catalog uses
244 the detector id for the catalog id, sorted on id for fast lookup.
245
246 Returns
247 -------
248 results : `lsst.pipe.base.Struct`
249 ``difference`` : `lsst.afw.image.ExposureF`
250 Result of subtracting template and science.
251 ``matchedTemplate`` : `lsst.afw.image.ExposureF`
252 Warped and PSF-matched template exposure.
253 ``backgroundModel`` : `lsst.afw.math.Chebyshev1Function2D`
254 Background model that was fit while solving for the PSF-matching kernel
255 ``psfMatchingKernel`` : `lsst.afw.math.Kernel`
256 Kernel used to PSF-match the convolved image.
257
258 Raises
259 ------
260 RuntimeError
261 If an unsupported convolution mode is supplied.
262 lsst.pipe.base.NoWorkFound
263 Raised if fraction of good pixels, defined as not having NO_DATA
264 set, is less then the configured requiredTemplateFraction
265 """
266 self._validateExposures(template, science)
267 if self.config.doApplyFinalizedPsf:
268 self._applyExternalCalibrations(science,
269 finalizedPsfApCorrCatalog=finalizedPsfApCorrCatalog)
270 checkTemplateIsSufficient(template, self.log,
271 requiredTemplateFraction=self.config.requiredTemplateFraction)
272 if self.config.forceCompatibility:
273 # Compatibility option to maintain old functionality
274 # This should be removed in the future!
275 self.log.warning("Running with `config.forceCompatibility=True`")
276 sources = None
277 sciencePsfSize = getPsfFwhm(science.psf)
278 templatePsfSize = getPsfFwhm(template.psf)
279 self.log.info("Science PSF size: %f", sciencePsfSize)
280 self.log.info("Template PSF size: %f", templatePsfSize)
281 if self.config.mode == "auto":
282 if sciencePsfSize < templatePsfSize:
283 self.log.info("Template PSF size is greater: convolving science image.")
284 convolveTemplate = False
285 else:
286 self.log.info("Science PSF size is greater: convolving template image.")
287 convolveTemplate = True
288 elif self.config.mode == "convolveTemplate":
289 self.log.info("`convolveTemplate` is set: convolving template image.")
290 convolveTemplate = True
291 elif self.config.mode == "convolveScience":
292 self.log.info("`convolveScience` is set: convolving science image.")
293 convolveTemplate = False
294 else:
295 raise RuntimeError("Cannot handle AlardLuptonSubtract mode: %s", self.config.mode)
296
297 if self.config.doScaleVariance and ~self.config.forceCompatibility:
298 # Scale the variance of the template and science images before
299 # convolution, subtraction, or decorrelation so that they have the
300 # correct ratio.
301 templateVarFactor = self.scaleVariance.run(template.maskedImage)
302 sciVarFactor = self.scaleVariance.run(science.maskedImage)
303 self.log.info("Template variance scaling factor: %.2f", templateVarFactor)
304 self.metadata.add("scaleTemplateVarianceFactor", templateVarFactor)
305 self.log.info("Science variance scaling factor: %.2f", sciVarFactor)
306 self.metadata.add("scaleScienceVarianceFactor", sciVarFactor)
307
308 kernelSources = self.makeKernel.selectKernelSources(template, science,
309 candidateList=sources,
310 preconvolved=False)
311 if convolveTemplate:
312 subtractResults = self.runConvolveTemplate(template, science, kernelSources)
313 else:
314 subtractResults = self.runConvolveScience(template, science, kernelSources)
315
316 if self.config.doScaleVariance and self.config.forceCompatibility:
317 # The old behavior scaled the variance of the final image difference.
318 diffimVarFactor = self.scaleVariance.run(subtractResults.difference.maskedImage)
319 self.log.info("Diffim variance scaling factor: %.2f", diffimVarFactor)
320 self.metadata.add("scaleDiffimVarianceFactor", diffimVarFactor)
321
322 return subtractResults
323
324 def runConvolveTemplate(self, template, science, sources):
325 """Convolve the template image with a PSF-matching kernel and subtract
326 from the science image.
327
328 Parameters
329 ----------
330 template : `lsst.afw.image.ExposureF`
331 Template exposure, warped to match the science exposure.
332 science : `lsst.afw.image.ExposureF`
333 Science exposure to subtract from the template.
335 Identified sources on the science exposure. This catalog is used to
336 select sources in order to perform the AL PSF matching on stamp
337 images around them.
338
339 Returns
340 -------
341 results : `lsst.pipe.base.Struct`
342
343 ``difference`` : `lsst.afw.image.ExposureF`
344 Result of subtracting template and science.
345 ``matchedTemplate`` : `lsst.afw.image.ExposureF`
346 Warped and PSF-matched template exposure.
347 ``backgroundModel`` : `lsst.afw.math.Chebyshev1Function2D`
348 Background model that was fit while solving for the PSF-matching kernel
349 ``psfMatchingKernel`` : `lsst.afw.math.Kernel`
350 Kernel used to PSF-match the template to the science image.
351 """
352 if self.config.forceCompatibility:
353 # Compatibility option to maintain old behavior
354 # This should be removed in the future!
355 template = template[science.getBBox()]
356 kernelResult = self.makeKernel.run(template, science, sources, preconvolved=False)
357
358 matchedTemplate = self._convolveExposure(template, kernelResult.psfMatchingKernel,
360 bbox=science.getBBox(),
361 psf=science.psf,
362 photoCalib=science.getPhotoCalib())
363 difference = _subtractImages(science, matchedTemplate,
364 backgroundModel=(kernelResult.backgroundModel
365 if self.config.doSubtractBackground else None))
366 correctedExposure = self.finalize(template, science, difference, kernelResult.psfMatchingKernel,
367 templateMatched=True)
368
369 return lsst.pipe.base.Struct(difference=correctedExposure,
370 matchedTemplate=matchedTemplate,
371 matchedScience=science,
372 backgroundModel=kernelResult.backgroundModel,
373 psfMatchingKernel=kernelResult.psfMatchingKernel)
374
375 def runConvolveScience(self, template, science, sources):
376 """Convolve the science image with a PSF-matching kernel and subtract the template image.
377
378 Parameters
379 ----------
380 template : `lsst.afw.image.ExposureF`
381 Template exposure, warped to match the science exposure.
382 science : `lsst.afw.image.ExposureF`
383 Science exposure to subtract from the template.
385 Identified sources on the science exposure. This catalog is used to
386 select sources in order to perform the AL PSF matching on stamp
387 images around them.
388
389 Returns
390 -------
391 results : `lsst.pipe.base.Struct`
392
393 ``difference`` : `lsst.afw.image.ExposureF`
394 Result of subtracting template and science.
395 ``matchedTemplate`` : `lsst.afw.image.ExposureF`
396 Warped template exposure. Note that in this case, the template
397 is not PSF-matched to the science image.
398 ``backgroundModel`` : `lsst.afw.math.Chebyshev1Function2D`
399 Background model that was fit while solving for the PSF-matching kernel
400 ``psfMatchingKernel`` : `lsst.afw.math.Kernel`
401 Kernel used to PSF-match the science image to the template.
402 """
403 if self.config.forceCompatibility:
404 # Compatibility option to maintain old behavior
405 # This should be removed in the future!
406 template = template[science.getBBox()]
407 kernelResult = self.makeKernel.run(science, template, sources, preconvolved=False)
408 modelParams = kernelResult.backgroundModel.getParameters()
409 # We must invert the background model if the matching kernel is solved for the science image.
410 kernelResult.backgroundModel.setParameters([-p for p in modelParams])
411
412 kernelImage = lsst.afw.image.ImageD(kernelResult.psfMatchingKernel.getDimensions())
413 norm = kernelResult.psfMatchingKernel.computeImage(kernelImage, doNormalize=False)
414
415 matchedScience = self._convolveExposure(science, kernelResult.psfMatchingKernel,
417 psf=template.psf)
418
419 # Place back on native photometric scale
420 matchedScience.maskedImage /= norm
421 matchedTemplate = template.clone()[science.getBBox()]
422 matchedTemplate.maskedImage /= norm
423 matchedTemplate.setPhotoCalib(science.getPhotoCalib())
424
425 difference = _subtractImages(matchedScience, matchedTemplate,
426 backgroundModel=(kernelResult.backgroundModel
427 if self.config.doSubtractBackground else None))
428
429 correctedExposure = self.finalize(template, science, difference, kernelResult.psfMatchingKernel,
430 templateMatched=False)
431
432 return lsst.pipe.base.Struct(difference=correctedExposure,
433 matchedTemplate=matchedTemplate,
434 matchedScience=matchedScience,
435 backgroundModel=kernelResult.backgroundModel,
436 psfMatchingKernel=kernelResult.psfMatchingKernel,)
437
438 def finalize(self, template, science, difference, kernel,
439 templateMatched=True,
440 preConvMode=False,
441 preConvKernel=None,
442 spatiallyVarying=False):
443 """Decorrelate the difference image to undo the noise correlations
444 caused by convolution.
445
446 Parameters
447 ----------
448 template : `lsst.afw.image.ExposureF`
449 Template exposure, warped to match the science exposure.
450 science : `lsst.afw.image.ExposureF`
451 Science exposure to subtract from the template.
452 difference : `lsst.afw.image.ExposureF`
453 Result of subtracting template and science.
454 kernel : `lsst.afw.math.Kernel`
455 An (optionally spatially-varying) PSF matching kernel
456 templateMatched : `bool`, optional
457 Was the template PSF-matched to the science image?
458 preConvMode : `bool`, optional
459 Was the science image preconvolved with its own PSF
460 before PSF matching the template?
461 preConvKernel : `lsst.afw.detection.Psf`, optional
462 If not `None`, then the science image was pre-convolved with
463 (the reflection of) this kernel. Must be normalized to sum to 1.
464 spatiallyVarying : `bool`, optional
465 Compute the decorrelation kernel spatially varying across the image?
466
467 Returns
468 -------
469 correctedExposure : `lsst.afw.image.ExposureF`
470 The decorrelated image difference.
471 """
472 # Erase existing detection mask planes.
473 # We don't want the detection mask from the science image
474 mask = difference.mask
475 mask &= ~(mask.getPlaneBitMask("DETECTED") | mask.getPlaneBitMask("DETECTED_NEGATIVE"))
476
477 if self.config.doDecorrelation:
478 self.log.info("Decorrelating image difference.")
479 correctedExposure = self.decorrelate.run(science, template[science.getBBox()], difference, kernel,
480 templateMatched=templateMatched,
481 preConvMode=preConvMode,
482 preConvKernel=preConvKernel,
483 spatiallyVarying=spatiallyVarying).correctedExposure
484 else:
485 self.log.info("NOT decorrelating image difference.")
486 correctedExposure = difference
487 return correctedExposure
488
489 @staticmethod
490 def _validateExposures(template, science):
491 """Check that the WCS of the two Exposures match, and the template bbox
492 contains the science bbox.
493
494 Parameters
495 ----------
496 template : `lsst.afw.image.ExposureF`
497 Template exposure, warped to match the science exposure.
498 science : `lsst.afw.image.ExposureF`
499 Science exposure to subtract from the template.
500
501 Raises
502 ------
503 AssertionError
504 Raised if the WCS of the template is not equal to the science WCS,
505 or if the science image is not fully contained in the template
506 bounding box.
507 """
508 assert template.wcs == science.wcs,\
509 "Template and science exposure WCS are not identical."
510 templateBBox = template.getBBox()
511 scienceBBox = science.getBBox()
512
513 assert templateBBox.contains(scienceBBox),\
514 "Template bbox does not contain all of the science image."
515
516 @staticmethod
517 def _convolveExposure(exposure, kernel, convolutionControl,
518 bbox=None,
519 psf=None,
520 photoCalib=None):
521 """Convolve an exposure with the given kernel.
522
523 Parameters
524 ----------
525 exposure : `lsst.afw.Exposure`
526 exposure to convolve.
528 PSF matching kernel computed in the ``makeKernel`` subtask.
529 convolutionControl : `lsst.afw.math.ConvolutionControl`
530 Configuration for convolve algorithm.
531 bbox : `lsst.geom.Box2I`, optional
532 Bounding box to trim the convolved exposure to.
533 psf : `lsst.afw.detection.Psf`, optional
534 Point spread function (PSF) to set for the convolved exposure.
535 photoCalib : `lsst.afw.image.PhotoCalib`, optional
536 Photometric calibration of the convolved exposure.
537
538 Returns
539 -------
540 convolvedExp : `lsst.afw.Exposure`
541 The convolved image.
542 """
543 convolvedExposure = exposure.clone()
544 if psf is not None:
545 convolvedExposure.setPsf(psf)
546 if photoCalib is not None:
547 convolvedExposure.setPhotoCalib(photoCalib)
548 convolvedImage = lsst.afw.image.MaskedImageF(exposure.getBBox())
549 lsst.afw.math.convolve(convolvedImage, exposure.maskedImage, kernel, convolutionControl)
550 convolvedExposure.setMaskedImage(convolvedImage)
551 if bbox is None:
552 return convolvedExposure
553 else:
554 return convolvedExposure[bbox]
555
556
557def checkTemplateIsSufficient(templateExposure, logger, requiredTemplateFraction=0.):
558 """Raise NoWorkFound if template coverage < requiredTemplateFraction
559
560 Parameters
561 ----------
562 templateExposure : `lsst.afw.image.ExposureF`
563 The template exposure to check
564 logger : `lsst.log.Log`
565 Logger for printing output.
566 requiredTemplateFraction : `float`, optional
567 Fraction of pixels of the science image required to have coverage
568 in the template.
569
570 Raises
571 ------
572 lsst.pipe.base.NoWorkFound
573 Raised if fraction of good pixels, defined as not having NO_DATA
574 set, is less then the configured requiredTemplateFraction
575 """
576 # Count the number of pixels with the NO_DATA mask bit set
577 # counting NaN pixels is insufficient because pixels without data are often intepolated over)
578 pixNoData = np.count_nonzero(templateExposure.mask.array
579 & templateExposure.mask.getPlaneBitMask('NO_DATA'))
580 pixGood = templateExposure.getBBox().getArea() - pixNoData
581 logger.info("template has %d good pixels (%.1f%%)", pixGood,
582 100*pixGood/templateExposure.getBBox().getArea())
583
584 if pixGood/templateExposure.getBBox().getArea() < requiredTemplateFraction:
585 message = ("Insufficient Template Coverage. (%.1f%% < %.1f%%) Not attempting subtraction. "
586 "To force subtraction, set config requiredTemplateFraction=0." % (
587 100*pixGood/templateExposure.getBBox().getArea(),
588 100*requiredTemplateFraction))
589 raise lsst.pipe.base.NoWorkFound(message)
590
591
592def _subtractImages(science, template, backgroundModel=None):
593 """Subtract template from science, propagating relevant metadata.
594
595 Parameters
596 ----------
597 science : `lsst.afw.Exposure`
598 The input science image.
599 template : `lsst.afw.Exposure`
600 The template to subtract from the science image.
601 backgroundModel : `lsst.afw.MaskedImage`, optional
602 Differential background model
603
604 Returns
605 -------
606 difference : `lsst.afw.Exposure`
607 The subtracted image.
608 """
609 difference = science.clone()
610 if backgroundModel is not None:
611 difference.maskedImage -= backgroundModel
612 difference.maskedImage -= template.maskedImage
613 return difference
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 runConvolveScience(self, template, science, sources)
def _applyExternalCalibrations(self, exposure, finalizedPsfApCorrCatalog)
def runConvolveTemplate(self, template, science, sources)
def run(self, template, science, sources, finalizedPsfApCorrCatalog=None)
void convolve(OutImageT &convolvedImage, InImageT const &inImage, KernelT const &kernel, ConvolutionControl const &convolutionControl=ConvolutionControl())
def checkTemplateIsSufficient(templateExposure, logger, requiredTemplateFraction=0.)
def getPsfFwhm(psf)
Definition: utils.py:1083