33from lsst.meas.algorithms
import SourceDetectionTask, SubtractBackgroundTask, WarpedPsf
35from .makeKernelBasisList
import makeKernelBasisList
36from .psfMatch
import PsfMatchTask, PsfMatchConfigDF, PsfMatchConfigAL
37from .
import utils
as diffimUtils
38from .
import diffimLib
39from .
import diffimTools
41from lsst.utils.timer
import timeMethod
43__all__ = [
"ImagePsfMatchConfig",
"ImagePsfMatchTask",
"subtractAlgorithmRegistry"]
45sigma2fwhm = 2.*np.sqrt(2.*np.log(2.))
49 """Configuration for image-to-image Psf matching.
51 kernel = pexConfig.ConfigChoiceField(
59 selectDetection = pexConfig.ConfigurableField(
60 target=SourceDetectionTask,
61 doc=
"Initial detections used to feed stars to kernel fitting",
63 selectMeasurement = pexConfig.ConfigurableField(
64 target=SingleFrameMeasurementTask,
65 doc=
"Initial measurements used to feed stars to kernel fitting",
75 self.
selectMeasurement.algorithms.names = (
'base_SdssCentroid',
'base_PsfFlux',
'base_PixelFlags',
76 'base_SdssShape',
'base_GaussianFlux',
'base_SkyCoord')
83 """Psf-match two MaskedImages or Exposures using the sources in the images.
88 Arguments to be passed to lsst.ip.diffim.PsfMatchTask.__init__
90 Keyword arguments to be passed to lsst.ip.diffim.PsfMatchTask.__init__
94 Upon initialization, the kernel configuration is defined by self.config.kernel.active.
95 The task creates an lsst.afw.math.Warper
from the subConfig self.config.kernel.active.warpingConfig.
96 A schema
for the selection
and measurement of candidate lsst.ip.diffim.KernelCandidates
is
97 defined,
and used to initize subTasks selectDetection (
for candidate detection)
and selectMeasurement
98 (
for candidate measurement).
102 Build a Psf-matching kernel using two input images, either
as MaskedImages (
in which case they need
103 to be astrometrically aligned)
or Exposures (
in which case astrometric alignment will happen by
104 default but may be turned off). This requires a list of input Sources which may be provided
105 by the calling Task;
if not, the Task will perform a coarse source detection
106 and selection
for this purpose. Sources are vetted
for signal-to-noise
and masked pixels
107 (
in both the template
and science image),
and substamps around each acceptable
108 source are extracted
and used to create an instance of KernelCandidate.
111 the order that they are called: BuildSingleKernelVisitor, KernelSumVisitor, BuildSpatialKernelVisitor,
112 and AssessSpatialKernelVisitor.
114 Sigma clipping of KernelCandidates
is performed
as follows:
116 - BuildSingleKernelVisitor, using the substamp diffim residuals
from the per-source kernel fit,
117 if PsfMatchConfig.singleKernelClipping
is True
118 - KernelSumVisitor, using the mean
and standard deviation of the kernel sum
from all candidates,
119 if PsfMatchConfig.kernelSumClipping
is True
120 - AssessSpatialKernelVisitor, using the substamp diffim ressiduals
from the spatial kernel fit,
121 if PsfMatchConfig.spatialKernelClipping
is True
123 The actual solving
for the kernel (
and differential background model) happens
in
124 lsst.ip.diffim.PsfMatchTask._solve. This involves a loop over the SpatialCellSet that first builds the
125 per-candidate matching kernel
for the requested number of KernelCandidates per cell
126 (PsfMatchConfig.nStarPerCell). The quality of this initial per-candidate difference image
is examined,
127 using moments of the pixel residuals
in the difference image normalized by the square root of the variance
128 (i.e. sigma); ideally this should follow a normal (0, 1) distribution,
129 but the rejection thresholds are set
130 by the config (PsfMatchConfig.candidateResidualMeanMax
and PsfMatchConfig.candidateResidualStdMax).
131 All candidates that
pass this initial build are then examined en masse to find the
132 mean/stdev of the kernel sums across all candidates.
133 Objects that are significantly above
or below the mean,
134 typically due to variability
or sources that are saturated
in one image but
not the other,
135 are also rejected.This threshold
is defined by PsfMatchConfig.maxKsumSigma.
136 Finally, a spatial model
is built using all currently-acceptable candidates,
137 and the spatial model used to derive a second set of (spatial) residuals
138 which are again used to reject bad candidates, using the same thresholds
as above.
142 There
is no
run() method
for this Task. Instead there are 4 methods that
143 may be used to invoke the Psf-matching. These are
144 `~lsst.ip.diffim.imagePsfMatch.ImagePsfMatchTask.matchMaskedImages`,
145 `~lsst.ip.diffim.imagePsfMatch.ImagePsfMatchTask.subtractMaskedImages`,
146 `~lsst.ip.diffim.imagePsfMatch.ImagePsfMatchTask.matchExposures`,
and
147 `~lsst.ip.diffim.imagePsfMatch.ImagePsfMatchTask.subtractExposures`.
151 input images to be misregistered
and potentially be different sizes; by default a
153 that
"match" images
return a Psf-matched image,
while the methods that
"subtract" images
154 return a Psf-matched
and template subtracted image.
156 See each method
's returned lsst.pipe.base.Struct for more details.
160 The ``pipetask`` command line interface supports a
161 flag --debug to import @b debug.py
from your PYTHONPATH. The relevant contents of debug.py
162 for this Task include:
170 if name ==
"lsst.ip.diffim.psfMatch":
172 di.maskTransparency = 80
173 di.displayCandidates =
True
174 di.displayKernelBasis =
False
175 di.displayKernelMosaic =
True
176 di.plotKernelSpatialModel =
False
177 di.showBadCandidates =
True
178 elif name ==
"lsst.ip.diffim.imagePsfMatch":
180 di.maskTransparency = 30
181 di.displayTemplate =
True
182 di.displaySciIm =
True
183 di.displaySpatialCells =
True
184 di.displayDiffIm =
True
185 di.showBadCandidates =
True
186 elif name ==
"lsst.ip.diffim.diaCatalogSourceSelector":
188 di.maskTransparency = 30
189 di.displayExposure =
True
190 di.pauseAtEnd =
False
195 Note that
if you want addional logging info, you may add to your scripts:
199 import lsst.utils.logging
as logUtils
200 logUtils.trace_set_at(
"lsst.ip.diffim", 4)
204 A complete example of using ImagePsfMatchTask
206 Create a subclass of ImagePsfMatchTask that allows us to either match exposures,
or subtract exposures:
213 ImagePsfMatchTask.__init__(self, args, kwargs)
215 def run(self, templateExp, scienceExp, mode):
216 if mode ==
"matchExposures":
218 elif mode ==
"subtractExposures":
221 And allow the user the freedom to either run the script
in default mode,
222 or point to their own images on disk.
225 We have enabled some minor display debugging
in this script via the --debug option. However,
if you
226 have an lsstDebug debug.py
in your PYTHONPATH you will get additional debugging displays. The following
227 block checks
for this script:
235 debug.lsstDebug.frame = 3
236 except ImportError
as e:
237 print(e, file=sys.stderr)
239 Finally, we call a run method that we define below.
240 First set up a Config
and modify some of the parameters.
241 E.g. use an
"Alard-Lupton" sum-of-Gaussian basis,
242 fit
for a differential background,
and use low order spatial
243 variation
in the kernel
and background:
251 config = ImagePsfMatchTask.ConfigClass()
252 config.kernel.name =
"AL"
253 config.kernel.active.fitForBackground =
True
254 config.kernel.active.spatialKernelOrder = 1
255 config.kernel.active.spatialBgOrder = 0
257 Make sure the images (
if any) that were sent to the script exist on disk
and are readable. If no images
258 are sent, make some fake data up
for the sake of this example script (have a look at the code
if you want
259 more details on generateFakeImages):
264 if args.template
is not None and args.science
is not None:
265 if not os.path.isfile(args.template):
266 raise FileNotFoundError(
"Template image %s does not exist" % (args.template))
267 if not os.path.isfile(args.science):
268 raise FileNotFoundError(
"Science image %s does not exist" % (args.science))
270 templateExp = afwImage.ExposureF(args.template)
271 except Exception
as e:
272 raise RuntimeError(
"Cannot read template image %s" % (args.template))
274 scienceExp = afwImage.ExposureF(args.science)
275 except Exception
as e:
276 raise RuntimeError(
"Cannot read science image %s" % (args.science))
278 templateExp, scienceExp = generateFakeImages()
279 config.kernel.active.sizeCellX = 128
280 config.kernel.active.sizeCellY = 128
282 Create
and run the Task:
287 psfMatchTask = MyImagePsfMatchTask(config=config)
289 result = psfMatchTask.run(templateExp, scienceExp, args.mode)
291 And
finally provide some optional debugging displays:
298 frame = debug.lsstDebug.frame + 1
301 afwDisplay.Display(frame=frame).mtv(result.matchedExposure,
302 title=
"Example script: Matched Template Image")
303 if "subtractedExposure" in result.getDict():
304 afwDisplay.Display(frame=frame + 1).mtv(result.subtractedExposure,
305 title=
"Example script: Subtracted Image")
308 ConfigClass = ImagePsfMatchConfig
311 """Create the ImagePsfMatchTask.
313 PsfMatchTask.__init__(self, *args, **kwargs)
322 self.makeSubtask(
"selectDetection", schema=self.
selectSchema)
326 """Return the FWHM in pixels of a Psf.
329 position = psf.getAveragePosition()
330 sigPix = psf.computeShape(position).getDeterminantRadius()
331 return sigPix*sigma2fwhm
335 templateFwhmPix=None, scienceFwhmPix=None,
336 candidateList=None, doWarping=True, convolveTemplate=True):
337 """Warp and PSF-match an exposure to the reference.
339 Do the following, in order:
341 - Warp templateExposure to match scienceExposure,
342 if doWarping
True and their WCSs do
not already match
343 - Determine a PSF matching kernel
and differential background model
344 that matches templateExposure to scienceExposure
345 - Convolve templateExposure by PSF matching kernel
350 Exposure to warp
and PSF-match to the reference masked image
352 Exposure whose WCS
and PSF are to be matched to
353 templateFwhmPix :`float`
354 FWHM (
in pixels) of the Psf
in the template image (image to convolve)
355 scienceFwhmPix : `float`
356 FWHM (
in pixels) of the Psf
in the science image
357 candidateList : `list`, optional
358 a list of footprints/maskedImages
for kernel candidates;
359 if `
None` then source detection
is run.
361 - Currently supported: list of Footprints
or measAlg.PsfCandidateF
364 what to do
if ``templateExposure``
and ``scienceExposure`` WCSs do
not match:
366 -
if `
True` then warp ``templateExposure`` to match ``scienceExposure``
367 -
if `
False` then
raise an Exception
369 convolveTemplate : `bool`
370 Whether to convolve the template image
or the science image:
372 -
if `
True`, ``templateExposure``
is warped
if doWarping,
373 ``templateExposure``
is convolved
374 -
if `
False`, ``templateExposure``
is warped
if doWarping,
375 ``scienceExposure``
is convolved
379 results : `lsst.pipe.base.Struct`
380 An `lsst.pipe.base.Struct` containing these fields:
382 - ``matchedImage`` : the PSF-matched exposure =
383 Warped ``templateExposure`` convolved by psfMatchingKernel. This has:
385 - the same parent bbox, Wcs
and PhotoCalib
as scienceExposure
386 - the same filter
as templateExposure
387 - no Psf (because the PSF-matching process does
not compute one)
389 - ``psfMatchingKernel`` : the PSF matching kernel
390 - ``backgroundModel`` : differential background model
391 - ``kernelCellSet`` : SpatialCellSet used to solve
for the PSF matching kernel
396 Raised
if doWarping
is False and ``templateExposure``
and
397 ``scienceExposure`` WCSs do
not match
399 if not self.
_validateWcs(templateExposure, scienceExposure):
401 self.log.info(
"Astrometrically registering template to science image")
402 templatePsf = templateExposure.getPsf()
405 scienceExposure.getWcs())
406 psfWarped = WarpedPsf(templatePsf, xyTransform)
407 templateExposure = self.
_warper.warpExposure(scienceExposure.getWcs(),
409 destBBox=scienceExposure.getBBox())
410 templateExposure.setPsf(psfWarped)
412 self.log.error(
"ERROR: Input images not registered")
413 raise RuntimeError(
"Input images not registered")
415 if templateFwhmPix
is None:
416 if not templateExposure.hasPsf():
417 self.log.warning(
"No estimate of Psf FWHM for template image")
419 templateFwhmPix = self.
getFwhmPix(templateExposure.getPsf())
420 self.log.info(
"templateFwhmPix: %s", templateFwhmPix)
422 if scienceFwhmPix
is None:
423 if not scienceExposure.hasPsf():
424 self.log.warning(
"No estimate of Psf FWHM for science image")
426 scienceFwhmPix = self.
getFwhmPix(scienceExposure.getPsf())
427 self.log.info(
"scienceFwhmPix: %s", scienceFwhmPix)
432 templateExposure, scienceExposure, kernelSize, candidateList)
434 templateExposure.getMaskedImage(), scienceExposure.getMaskedImage(), candidateList,
435 templateFwhmPix=templateFwhmPix, scienceFwhmPix=scienceFwhmPix)
439 templateExposure, scienceExposure, kernelSize, candidateList)
441 scienceExposure.getMaskedImage(), templateExposure.getMaskedImage(), candidateList,
442 templateFwhmPix=scienceFwhmPix, scienceFwhmPix=templateFwhmPix)
445 psfMatchedExposure.setFilter(templateExposure.getFilter())
446 psfMatchedExposure.setPhotoCalib(scienceExposure.getPhotoCalib())
447 results.warpedExposure = templateExposure
448 results.matchedExposure = psfMatchedExposure
453 templateFwhmPix=None, scienceFwhmPix=None):
454 """PSF-match a MaskedImage (templateMaskedImage) to a reference MaskedImage (scienceMaskedImage).
456 Do the following, in order:
458 - Determine a PSF matching kernel
and differential background model
459 that matches templateMaskedImage to scienceMaskedImage
460 - Convolve templateMaskedImage by the PSF matching kernel
465 masked image to PSF-match to the reference masked image;
466 must be warped to match the reference masked image
468 maskedImage whose PSF
is to be matched to
469 templateFwhmPix : `float`
470 FWHM (
in pixels) of the Psf
in the template image (image to convolve)
471 scienceFwhmPix : `float`
472 FWHM (
in pixels) of the Psf
in the science image
473 candidateList : `list`, optional
474 A list of footprints/maskedImages
for kernel candidates;
475 if `
None` then source detection
is run.
477 - Currently supported: list of Footprints
or measAlg.PsfCandidateF
482 An `lsst.pipe.base.Struct` containing these fields:
484 - psfMatchedMaskedImage: the PSF-matched masked image =
485 ``templateMaskedImage`` convolved
with psfMatchingKernel.
486 This has the same xy0, dimensions
and wcs
as ``scienceMaskedImage``.
487 - psfMatchingKernel: the PSF matching kernel
488 - backgroundModel: differential background model
489 - kernelCellSet: SpatialCellSet used to solve
for the PSF matching kernel
494 Raised
if input images have different dimensions
500 displaySpatialCells =
lsstDebug.Info(__name__).displaySpatialCells
502 if not maskTransparency:
505 afwDisplay.setDefaultMaskTransparency(maskTransparency)
507 if not candidateList:
508 raise RuntimeError(
"Candidate list must be populated by makeCandidateList")
510 if not self.
_validateSize(templateMaskedImage, scienceMaskedImage):
511 self.log.error(
"ERROR: Input images different size")
512 raise RuntimeError(
"Input images different size")
514 if display
and displayTemplate:
515 disp = afwDisplay.Display(frame=lsstDebug.frame)
516 disp.mtv(templateMaskedImage, title=
"Image to convolve")
519 if display
and displaySciIm:
520 disp = afwDisplay.Display(frame=lsstDebug.frame)
521 disp.mtv(scienceMaskedImage, title=
"Image to not convolve")
528 if display
and displaySpatialCells:
529 diffimUtils.showKernelSpatialCells(scienceMaskedImage, kernelCellSet,
530 symb=
"o", ctype=afwDisplay.CYAN, ctypeUnused=afwDisplay.YELLOW,
531 ctypeBad=afwDisplay.RED, size=4, frame=lsstDebug.frame,
532 title=
"Image to not convolve")
535 if templateFwhmPix
and scienceFwhmPix:
536 self.log.info(
"Matching Psf FWHM %.2f -> %.2f pix", templateFwhmPix, scienceFwhmPix)
543 bicDegrees = nbe(tmpKernelCellSet, self.log)
545 basisDegGauss=bicDegrees[0], metadata=self.metadata)
549 metadata=self.metadata)
551 spatialSolution, psfMatchingKernel, backgroundModel = self.
_solve(kernelCellSet, basisList)
553 psfMatchedMaskedImage = afwImage.MaskedImageF(templateMaskedImage.getBBox())
555 convolutionControl.setDoNormalize(
False)
556 afwMath.convolve(psfMatchedMaskedImage, templateMaskedImage, psfMatchingKernel, convolutionControl)
557 return pipeBase.Struct(
558 matchedImage=psfMatchedMaskedImage,
559 psfMatchingKernel=psfMatchingKernel,
560 backgroundModel=backgroundModel,
561 kernelCellSet=kernelCellSet,
566 templateFwhmPix=None, scienceFwhmPix=None,
567 candidateList=None, doWarping=True, convolveTemplate=True):
568 """Register, Psf-match and subtract two Exposures.
570 Do the following, in order:
572 - Warp templateExposure to match scienceExposure,
if their WCSs do
not already match
573 - Determine a PSF matching kernel
and differential background model
574 that matches templateExposure to scienceExposure
575 - PSF-match templateExposure to scienceExposure
576 - Compute subtracted exposure (see
return values
for equation).
580 templateExposure : `lsst.afw.image.ExposureF`
581 Exposure to PSF-match to scienceExposure
582 scienceExposure : `lsst.afw.image.ExposureF`
584 templateFwhmPix : `float`
585 FWHM (
in pixels) of the Psf
in the template image (image to convolve)
586 scienceFwhmPix : `float`
587 FWHM (
in pixels) of the Psf
in the science image
588 candidateList : `list`, optional
589 A list of footprints/maskedImages
for kernel candidates;
590 if `
None` then source detection
is run.
592 - Currently supported: list of Footprints
or measAlg.PsfCandidateF
595 What to do
if ``templateExposure```
and ``scienceExposure`` WCSs do
598 -
if `
True` then warp ``templateExposure`` to match ``scienceExposure``
599 -
if `
False` then
raise an Exception
601 convolveTemplate : `bool`
602 Convolve the template image
or the science image
604 -
if `
True`, ``templateExposure``
is warped
if doWarping,
605 ``templateExposure``
is convolved
606 -
if `
False`, ``templateExposure``
is warped
if doWarping,
607 ``scienceExposure
is`` convolved
611 result : `lsst.pipe.base.Struct`
612 An `lsst.pipe.base.Struct` containing these fields:
614 - ``subtractedExposure`` : subtracted Exposure
615 scienceExposure - (matchedImage + backgroundModel)
616 - ``matchedImage`` : ``templateExposure`` after warping to match
617 ``templateExposure`` (
if doWarping true),
618 and convolving
with psfMatchingKernel
619 - ``psfMatchingKernel`` : PSF matching kernel
620 - ``backgroundModel`` : differential background model
621 - ``kernelCellSet`` : SpatialCellSet used to determine PSF matching kernel
624 templateExposure=templateExposure,
625 scienceExposure=scienceExposure,
626 templateFwhmPix=templateFwhmPix,
627 scienceFwhmPix=scienceFwhmPix,
628 candidateList=candidateList,
630 convolveTemplate=convolveTemplate
633 subtractedExposure = afwImage.ExposureF(scienceExposure, deep=
True)
639 subtractedMaskedImage = subtractedExposure.maskedImage
640 subtractedMaskedImage -= results.matchedExposure.maskedImage
641 subtractedMaskedImage -= results.backgroundModel
643 subtractedMaskedImage = subtractedExposure.maskedImage
644 subtractedMaskedImage[:, :] = results.warpedExposure.maskedImage
645 subtractedMaskedImage -= results.matchedExposure.maskedImage
646 subtractedMaskedImage -= results.backgroundModel
649 subtractedMaskedImage *= -1
652 subtractedMaskedImage /= results.psfMatchingKernel.computeImage(
653 afwImage.ImageD(results.psfMatchingKernel.getDimensions()),
False)
655 subtractedExposure.setPsf(results.warpedExposure.getPsf())
661 if not maskTransparency:
664 afwDisplay.setDefaultMaskTransparency(maskTransparency)
665 if display
and displayDiffIm:
666 disp = afwDisplay.Display(frame=lsstDebug.frame)
667 disp.mtv(templateExposure, title=
"Template")
669 disp = afwDisplay.Display(frame=lsstDebug.frame)
670 disp.mtv(results.matchedExposure, title=
"Matched template")
672 disp = afwDisplay.Display(frame=lsstDebug.frame)
673 disp.mtv(scienceExposure, title=
"Science Image")
675 disp = afwDisplay.Display(frame=lsstDebug.frame)
676 disp.mtv(subtractedExposure, title=
"Difference Image")
679 results.subtractedExposure = subtractedExposure
684 templateFwhmPix=None, scienceFwhmPix=None):
685 """Psf-match and subtract two MaskedImages.
687 Do the following, in order:
689 - PSF-match templateMaskedImage to scienceMaskedImage
690 - Determine the differential background
691 - Return the difference: scienceMaskedImage
692 ((warped templateMaskedImage convolved
with psfMatchingKernel) + backgroundModel)
697 MaskedImage to PSF-match to ``scienceMaskedImage``
699 Reference MaskedImage
700 templateFwhmPix : `float`
701 FWHM (
in pixels) of the Psf
in the template image (image to convolve)
702 scienceFwhmPix : `float`
703 FWHM (
in pixels) of the Psf
in the science image
704 candidateList : `list`, optional
705 A list of footprints/maskedImages
for kernel candidates;
706 if `
None` then source detection
is run.
708 - Currently supported: list of Footprints
or measAlg.PsfCandidateF
712 results : `lsst.pipe.base.Struct`
713 An `lsst.pipe.base.Struct` containing these fields:
715 - ``subtractedMaskedImage`` : ``scienceMaskedImage`` - (matchedImage + backgroundModel)
716 - ``matchedImage`` : templateMaskedImage convolved
with psfMatchingKernel
717 - `psfMatchingKernel`` : PSF matching kernel
718 - ``backgroundModel`` : differential background model
719 - ``kernelCellSet`` : SpatialCellSet used to determine PSF matching kernel
722 if not candidateList:
723 raise RuntimeError(
"Candidate list must be populated by makeCandidateList")
726 templateMaskedImage=templateMaskedImage,
727 scienceMaskedImage=scienceMaskedImage,
728 candidateList=candidateList,
729 templateFwhmPix=templateFwhmPix,
730 scienceFwhmPix=scienceFwhmPix,
733 subtractedMaskedImage = afwImage.MaskedImageF(scienceMaskedImage,
True)
734 subtractedMaskedImage -= results.matchedImage
735 subtractedMaskedImage -= results.backgroundModel
736 results.subtractedMaskedImage = subtractedMaskedImage
742 if not maskTransparency:
745 afwDisplay.setDefaultMaskTransparency(maskTransparency)
746 if display
and displayDiffIm:
747 disp = afwDisplay.Display(frame=lsstDebug.frame)
748 disp.mtv(subtractedMaskedImage, title=
"Subtracted masked image")
754 """Get sources to use for Psf-matching.
756 This method runs detection and measurement on an exposure.
757 The returned set of sources will be used
as candidates
for
763 Exposure on which to run detection/measurement
767 Whether
or not to smooth the Exposure
with Psf before detection
769 Factory
for the generation of Source ids
774 source catalog containing candidates
for the Psf-matching
777 table = afwTable.SourceTable.make(self.
selectSchema, idFactory)
780 mi = exposure.getMaskedImage()
782 imArr = mi.getImage().getArray()
783 maskArr = mi.getMask().getArray()
784 miArr = np.ma.masked_array(imArr, mask=maskArr)
787 bkgd = fitBg.getImageF(self.
background.config.algorithm,
790 self.log.warning(
"Failed to get background model. Falling back to median background estimation")
791 bkgd = np.ma.median(miArr)
797 detRet = self.selectDetection.
run(
803 selectSources = detRet.sources
804 self.selectMeasurement.
run(measCat=selectSources, exposure=exposure)
812 """Make a list of acceptable KernelCandidates.
814 Accept or generate a list of candidate sources
for
815 Psf-matching,
and examine the Mask planes
in both of the
816 images
for indications of bad pixels
821 Exposure that will be convolved
823 Exposure that will be matched-to
825 Dimensions of the Psf-matching Kernel, used to grow detection footprints
826 candidateList : `list`, optional
827 List of Sources to examine. Elements must be of type afw.table.Source
828 or a type that wraps a Source
and has a getSource() method, such
as
829 meas.algorithms.PsfCandidateF.
833 candidateList : `list` of `dict`
834 A list of dicts having a
"source" and "footprint"
835 field
for the Sources deemed to be appropriate
for Psf
838 if candidateList
is None:
841 if len(candidateList) < 1:
842 raise RuntimeError(
"No candidates in candidateList")
844 listTypes = set(type(x)
for x
in candidateList)
845 if len(listTypes) > 1:
846 raise RuntimeError(
"Candidate list contains mixed types: %s" % [t
for t
in listTypes])
848 if not isinstance(candidateList[0], afwTable.SourceRecord):
850 candidateList[0].getSource()
851 except Exception
as e:
852 raise RuntimeError(f
"Candidate List is of type: {type(candidateList[0])} "
853 "Can only make candidate list from list of afwTable.SourceRecords, "
854 f
"measAlg.PsfCandidateF or other type with a getSource() method: {e}")
855 candidateList = [c.getSource()
for c
in candidateList]
857 candidateList = diffimTools.sourceToFootprintList(candidateList,
858 templateExposure, scienceExposure,
862 if len(candidateList) == 0:
863 raise RuntimeError(
"Cannot find any objects suitable for KernelCandidacy")
867 def makeKernelBasisList(self, targetFwhmPix=None, referenceFwhmPix=None,
868 basisDegGauss=None, basisSigmaGauss=None, metadata=None):
869 """Wrapper to set log messages for
874 targetFwhmPix : `float`, optional
875 Passed on to `lsst.ip.diffim.generateAlardLuptonBasisList`.
876 Not used for delta function basis sets.
877 referenceFwhmPix : `float`, optional
878 Passed on to `lsst.ip.diffim.generateAlardLuptonBasisList`.
879 Not used
for delta function basis sets.
880 basisDegGauss : `list` of `int`, optional
881 Passed on to `lsst.ip.diffim.generateAlardLuptonBasisList`.
882 Not used
for delta function basis sets.
883 basisSigmaGauss : `list` of `int`, optional
884 Passed on to `lsst.ip.diffim.generateAlardLuptonBasisList`.
885 Not used
for delta function basis sets.
887 Passed on to `lsst.ip.diffim.generateAlardLuptonBasisList`.
888 Not used
for delta function basis sets.
892 basisList: `list` of `lsst.afw.math.kernel.FixedKernel`
893 List of basis kernels.
896 targetFwhmPix=targetFwhmPix,
897 referenceFwhmPix=referenceFwhmPix,
898 basisDegGauss=basisDegGauss,
899 basisSigmaGauss=basisSigmaGauss,
901 if targetFwhmPix == referenceFwhmPix:
902 self.log.info(
"Target and reference psf fwhms are equal, falling back to config values")
903 elif referenceFwhmPix > targetFwhmPix:
904 self.log.info(
"Reference psf fwhm is the greater, normal convolution mode")
906 self.log.info(
"Target psf fwhm is the greater, deconvolution mode")
910 def _adaptCellSize(self, candidateList):
911 """NOT IMPLEMENTED YET.
915 def _buildCellSet(self, templateMaskedImage, scienceMaskedImage, candidateList):
916 """Build a SpatialCellSet for use with the solve method.
921 MaskedImage to PSF-matched to scienceMaskedImage
923 Reference MaskedImage
924 candidateList : `list`
925 A list of footprints/maskedImages for kernel candidates;
927 - Currently supported: list of Footprints
or measAlg.PsfCandidateF
932 a SpatialCellSet
for use
with self.
_solve
934 if not candidateList:
935 raise RuntimeError(
"Candidate list must be populated by makeCandidateList")
941 sizeCellX, sizeCellY)
945 for cand
in candidateList:
947 bbox = cand.getBBox()
949 bbox = cand[
'footprint'].getBBox()
950 tmi = afwImage.MaskedImageF(templateMaskedImage, bbox)
951 smi = afwImage.MaskedImageF(scienceMaskedImage, bbox)
955 cand = cand[
'source']
956 xPos = cand.getCentroid()[0]
957 yPos = cand.getCentroid()[1]
958 cand = diffimLib.makeKernelCandidate(xPos, yPos, tmi, smi, ps)
960 self.log.debug(
"Candidate %d at %f, %f", cand.getId(), cand.getXCenter(), cand.getYCenter())
961 kernelCellSet.insertCandidate(cand)
965 def _validateSize(self, templateMaskedImage, scienceMaskedImage):
966 """Return True if two image-like objects are the same size.
968 return templateMaskedImage.getDimensions() == scienceMaskedImage.getDimensions()
970 def _validateWcs(self, templateExposure, scienceExposure):
971 """Return True if the WCS of the two Exposures have the same origin and extent.
973 templateWcs = templateExposure.getWcs()
974 scienceWcs = scienceExposure.getWcs()
975 templateBBox = templateExposure.getBBox()
976 scienceBBox = scienceExposure.getBBox()
979 templateOrigin = templateWcs.pixelToSky(
geom.Point2D(templateBBox.getBegin()))
980 scienceOrigin = scienceWcs.pixelToSky(
geom.Point2D(scienceBBox.getBegin()))
983 templateLimit = templateWcs.pixelToSky(
geom.Point2D(templateBBox.getEnd()))
984 scienceLimit = scienceWcs.pixelToSky(
geom.Point2D(scienceBBox.getEnd()))
986 self.log.info(
"Template Wcs : %f,%f -> %f,%f",
987 templateOrigin[0], templateOrigin[1],
988 templateLimit[0], templateLimit[1])
989 self.log.info(
"Science Wcs : %f,%f -> %f,%f",
990 scienceOrigin[0], scienceOrigin[1],
991 scienceLimit[0], scienceLimit[1])
993 templateBBox =
geom.Box2D(templateOrigin.getPosition(geom.degrees),
994 templateLimit.getPosition(geom.degrees))
995 scienceBBox =
geom.Box2D(scienceOrigin.getPosition(geom.degrees),
996 scienceLimit.getPosition(geom.degrees))
997 if not (templateBBox.overlaps(scienceBBox)):
998 raise RuntimeError(
"Input images do not overlap at all")
1000 if ((templateOrigin != scienceOrigin)
1001 or (templateLimit != scienceLimit)
1002 or (templateExposure.getDimensions() != scienceExposure.getDimensions())):
1007subtractAlgorithmRegistry = pexConfig.makeRegistry(
1008 doc=
"A registry of subtraction algorithms for use as a subtask in imageDifference",
1011subtractAlgorithmRegistry.register(
'al', ImagePsfMatchTask)
def _validateWcs(self, templateExposure, scienceExposure)
def _adaptCellSize(self, candidateList)
def _buildCellSet(self, templateMaskedImage, scienceMaskedImage, candidateList)
def _validateSize(self, templateMaskedImage, scienceMaskedImage)
def subtractMaskedImages(self, templateMaskedImage, scienceMaskedImage, candidateList, templateFwhmPix=None, scienceFwhmPix=None)
def matchMaskedImages(self, templateMaskedImage, scienceMaskedImage, candidateList, templateFwhmPix=None, scienceFwhmPix=None)
def __init__(self, *args, **kwargs)
def getSelectSources(self, exposure, sigma=None, doSmooth=True, idFactory=None)
def getFwhmPix(self, psf, position=None)
def subtractExposures(self, templateExposure, scienceExposure, templateFwhmPix=None, scienceFwhmPix=None, candidateList=None, doWarping=True, convolveTemplate=True)
def matchExposures(self, templateExposure, scienceExposure, templateFwhmPix=None, scienceFwhmPix=None, candidateList=None, doWarping=True, convolveTemplate=True)
def makeKernelBasisList(self, targetFwhmPix=None, referenceFwhmPix=None, basisDegGauss=None, basisSigmaGauss=None, metadata=None)
def makeCandidateList(self, templateExposure, scienceExposure, kernelSize, candidateList=None)
def _buildCellSet(self, *args)
def _solve(self, kernelCellSet, basisList, returnOnExcept=False)
std::shared_ptr< TransformPoint2ToPoint2 > makeWcsPairTransform(SkyWcs const &src, SkyWcs const &dst)
std::shared_ptr< Exposure< ImagePixelT, MaskPixelT, VariancePixelT > > makeExposure(MaskedImage< ImagePixelT, MaskPixelT, VariancePixelT > &mimage, std::shared_ptr< geom::SkyWcs const > wcs=std::shared_ptr< geom::SkyWcs const >())
void convolve(OutImageT &convolvedImage, InImageT const &inImage, KernelT const &kernel, ConvolutionControl const &convolutionControl=ConvolutionControl())
def run(self, coaddExposures, bbox, wcs, dataIds, **kwargs)