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)
327 templateFwhmPix=None, scienceFwhmPix=None,
328 candidateList=None, doWarping=True, convolveTemplate=True):
329 """Warp and PSF-match an exposure to the reference.
331 Do the following, in order:
333 - Warp templateExposure to match scienceExposure,
334 if doWarping
True and their WCSs do
not already match
335 - Determine a PSF matching kernel
and differential background model
336 that matches templateExposure to scienceExposure
337 - Convolve templateExposure by PSF matching kernel
342 Exposure to warp
and PSF-match to the reference masked image
344 Exposure whose WCS
and PSF are to be matched to
345 templateFwhmPix :`float`
346 FWHM (
in pixels) of the Psf
in the template image (image to convolve)
347 scienceFwhmPix : `float`
348 FWHM (
in pixels) of the Psf
in the science image
349 candidateList : `list`, optional
350 a list of footprints/maskedImages
for kernel candidates;
351 if `
None` then source detection
is run.
353 - Currently supported: list of Footprints
or measAlg.PsfCandidateF
356 what to do
if ``templateExposure``
and ``scienceExposure`` WCSs do
not match:
358 -
if `
True` then warp ``templateExposure`` to match ``scienceExposure``
359 -
if `
False` then
raise an Exception
361 convolveTemplate : `bool`
362 Whether to convolve the template image
or the science image:
364 -
if `
True`, ``templateExposure``
is warped
if doWarping,
365 ``templateExposure``
is convolved
366 -
if `
False`, ``templateExposure``
is warped
if doWarping,
367 ``scienceExposure``
is convolved
371 results : `lsst.pipe.base.Struct`
372 An `lsst.pipe.base.Struct` containing these fields:
374 - ``matchedImage`` : the PSF-matched exposure =
375 Warped ``templateExposure`` convolved by psfMatchingKernel. This has:
377 - the same parent bbox, Wcs
and PhotoCalib
as scienceExposure
378 - the same filter
as templateExposure
379 - no Psf (because the PSF-matching process does
not compute one)
381 - ``psfMatchingKernel`` : the PSF matching kernel
382 - ``backgroundModel`` : differential background model
383 - ``kernelCellSet`` : SpatialCellSet used to solve
for the PSF matching kernel
388 Raised
if doWarping
is False and ``templateExposure``
and
389 ``scienceExposure`` WCSs do
not match
391 if not self.
_validateWcs(templateExposure, scienceExposure):
393 self.log.info(
"Astrometrically registering template to science image")
394 templatePsf = templateExposure.getPsf()
397 scienceExposure.getWcs())
398 psfWarped = WarpedPsf(templatePsf, xyTransform)
399 templateExposure = self.
_warper.warpExposure(scienceExposure.getWcs(),
401 destBBox=scienceExposure.getBBox())
402 templateExposure.setPsf(psfWarped)
404 self.log.error(
"ERROR: Input images not registered")
405 raise RuntimeError(
"Input images not registered")
407 if templateFwhmPix
is None:
408 if not templateExposure.hasPsf():
409 self.log.warning(
"No estimate of Psf FWHM for template image")
411 templateFwhmPix = diffimUtils.getPsfFwhm(templateExposure.psf)
412 self.log.info(
"templateFwhmPix: %s", templateFwhmPix)
414 if scienceFwhmPix
is None:
415 if not scienceExposure.hasPsf():
416 self.log.warning(
"No estimate of Psf FWHM for science image")
418 scienceFwhmPix = diffimUtils.getPsfFwhm(scienceExposure.psf)
419 self.log.info(
"scienceFwhmPix: %s", scienceFwhmPix)
424 templateExposure, scienceExposure, kernelSize, candidateList)
426 templateExposure.getMaskedImage(), scienceExposure.getMaskedImage(), candidateList,
427 templateFwhmPix=templateFwhmPix, scienceFwhmPix=scienceFwhmPix)
431 templateExposure, scienceExposure, kernelSize, candidateList)
433 scienceExposure.getMaskedImage(), templateExposure.getMaskedImage(), candidateList,
434 templateFwhmPix=scienceFwhmPix, scienceFwhmPix=templateFwhmPix)
437 psfMatchedExposure.setFilter(templateExposure.getFilter())
438 psfMatchedExposure.setPhotoCalib(scienceExposure.getPhotoCalib())
439 results.warpedExposure = templateExposure
440 results.matchedExposure = psfMatchedExposure
445 templateFwhmPix=None, scienceFwhmPix=None):
446 """PSF-match a MaskedImage (templateMaskedImage) to a reference MaskedImage (scienceMaskedImage).
448 Do the following, in order:
450 - Determine a PSF matching kernel
and differential background model
451 that matches templateMaskedImage to scienceMaskedImage
452 - Convolve templateMaskedImage by the PSF matching kernel
457 masked image to PSF-match to the reference masked image;
458 must be warped to match the reference masked image
460 maskedImage whose PSF
is to be matched to
461 templateFwhmPix : `float`
462 FWHM (
in pixels) of the Psf
in the template image (image to convolve)
463 scienceFwhmPix : `float`
464 FWHM (
in pixels) of the Psf
in the science image
465 candidateList : `list`, optional
466 A list of footprints/maskedImages
for kernel candidates;
467 if `
None` then source detection
is run.
469 - Currently supported: list of Footprints
or measAlg.PsfCandidateF
474 An `lsst.pipe.base.Struct` containing these fields:
476 - psfMatchedMaskedImage: the PSF-matched masked image =
477 ``templateMaskedImage`` convolved
with psfMatchingKernel.
478 This has the same xy0, dimensions
and wcs
as ``scienceMaskedImage``.
479 - psfMatchingKernel: the PSF matching kernel
480 - backgroundModel: differential background model
481 - kernelCellSet: SpatialCellSet used to solve
for the PSF matching kernel
486 Raised
if input images have different dimensions
492 displaySpatialCells =
lsstDebug.Info(__name__).displaySpatialCells
494 if not maskTransparency:
497 afwDisplay.setDefaultMaskTransparency(maskTransparency)
499 if not candidateList:
500 raise RuntimeError(
"Candidate list must be populated by makeCandidateList")
502 if not self.
_validateSize(templateMaskedImage, scienceMaskedImage):
503 self.log.error(
"ERROR: Input images different size")
504 raise RuntimeError(
"Input images different size")
506 if display
and displayTemplate:
507 disp = afwDisplay.Display(frame=lsstDebug.frame)
508 disp.mtv(templateMaskedImage, title=
"Image to convolve")
511 if display
and displaySciIm:
512 disp = afwDisplay.Display(frame=lsstDebug.frame)
513 disp.mtv(scienceMaskedImage, title=
"Image to not convolve")
520 if display
and displaySpatialCells:
521 diffimUtils.showKernelSpatialCells(scienceMaskedImage, kernelCellSet,
522 symb=
"o", ctype=afwDisplay.CYAN, ctypeUnused=afwDisplay.YELLOW,
523 ctypeBad=afwDisplay.RED, size=4, frame=lsstDebug.frame,
524 title=
"Image to not convolve")
527 if templateFwhmPix
and scienceFwhmPix:
528 self.log.info(
"Matching Psf FWHM %.2f -> %.2f pix", templateFwhmPix, scienceFwhmPix)
535 bicDegrees = nbe(tmpKernelCellSet, self.log)
537 basisDegGauss=bicDegrees[0], metadata=self.metadata)
541 metadata=self.metadata)
543 spatialSolution, psfMatchingKernel, backgroundModel = self.
_solve(kernelCellSet, basisList)
545 psfMatchedMaskedImage = afwImage.MaskedImageF(templateMaskedImage.getBBox())
547 convolutionControl.setDoNormalize(
False)
548 afwMath.convolve(psfMatchedMaskedImage, templateMaskedImage, psfMatchingKernel, convolutionControl)
549 return pipeBase.Struct(
550 matchedImage=psfMatchedMaskedImage,
551 psfMatchingKernel=psfMatchingKernel,
552 backgroundModel=backgroundModel,
553 kernelCellSet=kernelCellSet,
558 templateFwhmPix=None, scienceFwhmPix=None,
559 candidateList=None, doWarping=True, convolveTemplate=True):
560 """Register, Psf-match and subtract two Exposures.
562 Do the following, in order:
564 - Warp templateExposure to match scienceExposure,
if their WCSs do
not already match
565 - Determine a PSF matching kernel
and differential background model
566 that matches templateExposure to scienceExposure
567 - PSF-match templateExposure to scienceExposure
568 - Compute subtracted exposure (see
return values
for equation).
572 templateExposure : `lsst.afw.image.ExposureF`
573 Exposure to PSF-match to scienceExposure
574 scienceExposure : `lsst.afw.image.ExposureF`
576 templateFwhmPix : `float`
577 FWHM (
in pixels) of the Psf
in the template image (image to convolve)
578 scienceFwhmPix : `float`
579 FWHM (
in pixels) of the Psf
in the science image
580 candidateList : `list`, optional
581 A list of footprints/maskedImages
for kernel candidates;
582 if `
None` then source detection
is run.
584 - Currently supported: list of Footprints
or measAlg.PsfCandidateF
587 What to do
if ``templateExposure```
and ``scienceExposure`` WCSs do
590 -
if `
True` then warp ``templateExposure`` to match ``scienceExposure``
591 -
if `
False` then
raise an Exception
593 convolveTemplate : `bool`
594 Convolve the template image
or the science image
596 -
if `
True`, ``templateExposure``
is warped
if doWarping,
597 ``templateExposure``
is convolved
598 -
if `
False`, ``templateExposure``
is warped
if doWarping,
599 ``scienceExposure
is`` convolved
603 result : `lsst.pipe.base.Struct`
604 An `lsst.pipe.base.Struct` containing these fields:
606 - ``subtractedExposure`` : subtracted Exposure
607 scienceExposure - (matchedImage + backgroundModel)
608 - ``matchedImage`` : ``templateExposure`` after warping to match
609 ``templateExposure`` (
if doWarping true),
610 and convolving
with psfMatchingKernel
611 - ``psfMatchingKernel`` : PSF matching kernel
612 - ``backgroundModel`` : differential background model
613 - ``kernelCellSet`` : SpatialCellSet used to determine PSF matching kernel
616 templateExposure=templateExposure,
617 scienceExposure=scienceExposure,
618 templateFwhmPix=templateFwhmPix,
619 scienceFwhmPix=scienceFwhmPix,
620 candidateList=candidateList,
622 convolveTemplate=convolveTemplate
625 subtractedExposure = afwImage.ExposureF(scienceExposure, deep=
True)
631 subtractedMaskedImage = subtractedExposure.maskedImage
632 subtractedMaskedImage -= results.matchedExposure.maskedImage
633 subtractedMaskedImage -= results.backgroundModel
635 subtractedMaskedImage = subtractedExposure.maskedImage
636 subtractedMaskedImage[:, :] = results.warpedExposure.maskedImage
637 subtractedMaskedImage -= results.matchedExposure.maskedImage
638 subtractedMaskedImage -= results.backgroundModel
641 subtractedMaskedImage *= -1
644 subtractedMaskedImage /= results.psfMatchingKernel.computeImage(
645 afwImage.ImageD(results.psfMatchingKernel.getDimensions()),
False)
647 subtractedExposure.setPsf(results.warpedExposure.getPsf())
653 if not maskTransparency:
656 afwDisplay.setDefaultMaskTransparency(maskTransparency)
657 if display
and displayDiffIm:
658 disp = afwDisplay.Display(frame=lsstDebug.frame)
659 disp.mtv(templateExposure, title=
"Template")
661 disp = afwDisplay.Display(frame=lsstDebug.frame)
662 disp.mtv(results.matchedExposure, title=
"Matched template")
664 disp = afwDisplay.Display(frame=lsstDebug.frame)
665 disp.mtv(scienceExposure, title=
"Science Image")
667 disp = afwDisplay.Display(frame=lsstDebug.frame)
668 disp.mtv(subtractedExposure, title=
"Difference Image")
671 results.subtractedExposure = subtractedExposure
676 templateFwhmPix=None, scienceFwhmPix=None):
677 """Psf-match and subtract two MaskedImages.
679 Do the following, in order:
681 - PSF-match templateMaskedImage to scienceMaskedImage
682 - Determine the differential background
683 - Return the difference: scienceMaskedImage
684 ((warped templateMaskedImage convolved
with psfMatchingKernel) + backgroundModel)
689 MaskedImage to PSF-match to ``scienceMaskedImage``
691 Reference MaskedImage
692 templateFwhmPix : `float`
693 FWHM (
in pixels) of the Psf
in the template image (image to convolve)
694 scienceFwhmPix : `float`
695 FWHM (
in pixels) of the Psf
in the science image
696 candidateList : `list`, optional
697 A list of footprints/maskedImages
for kernel candidates;
698 if `
None` then source detection
is run.
700 - Currently supported: list of Footprints
or measAlg.PsfCandidateF
704 results : `lsst.pipe.base.Struct`
705 An `lsst.pipe.base.Struct` containing these fields:
707 - ``subtractedMaskedImage`` : ``scienceMaskedImage`` - (matchedImage + backgroundModel)
708 - ``matchedImage`` : templateMaskedImage convolved
with psfMatchingKernel
709 - `psfMatchingKernel`` : PSF matching kernel
710 - ``backgroundModel`` : differential background model
711 - ``kernelCellSet`` : SpatialCellSet used to determine PSF matching kernel
714 if not candidateList:
715 raise RuntimeError(
"Candidate list must be populated by makeCandidateList")
718 templateMaskedImage=templateMaskedImage,
719 scienceMaskedImage=scienceMaskedImage,
720 candidateList=candidateList,
721 templateFwhmPix=templateFwhmPix,
722 scienceFwhmPix=scienceFwhmPix,
725 subtractedMaskedImage = afwImage.MaskedImageF(scienceMaskedImage,
True)
726 subtractedMaskedImage -= results.matchedImage
727 subtractedMaskedImage -= results.backgroundModel
728 results.subtractedMaskedImage = subtractedMaskedImage
734 if not maskTransparency:
737 afwDisplay.setDefaultMaskTransparency(maskTransparency)
738 if display
and displayDiffIm:
739 disp = afwDisplay.Display(frame=lsstDebug.frame)
740 disp.mtv(subtractedMaskedImage, title=
"Subtracted masked image")
746 """Get sources to use for Psf-matching.
748 This method runs detection and measurement on an exposure.
749 The returned set of sources will be used
as candidates
for
755 Exposure on which to run detection/measurement
759 Whether
or not to smooth the Exposure
with Psf before detection
761 Factory
for the generation of Source ids
766 source catalog containing candidates
for the Psf-matching
769 table = afwTable.SourceTable.make(self.
selectSchema, idFactory)
772 mi = exposure.getMaskedImage()
774 imArr = mi.getImage().getArray()
775 maskArr = mi.getMask().getArray()
776 miArr = np.ma.masked_array(imArr, mask=maskArr)
779 bkgd = fitBg.getImageF(self.
background.config.algorithm,
782 self.log.warning(
"Failed to get background model. Falling back to median background estimation")
783 bkgd = np.ma.median(miArr)
789 detRet = self.selectDetection.
run(
795 selectSources = detRet.sources
796 self.selectMeasurement.
run(measCat=selectSources, exposure=exposure)
804 """Make a list of acceptable KernelCandidates.
806 Accept or generate a list of candidate sources
for
807 Psf-matching,
and examine the Mask planes
in both of the
808 images
for indications of bad pixels
813 Exposure that will be convolved
815 Exposure that will be matched-to
817 Dimensions of the Psf-matching Kernel, used to grow detection footprints
818 candidateList : `list`, optional
819 List of Sources to examine. Elements must be of type afw.table.Source
820 or a type that wraps a Source
and has a getSource() method, such
as
821 meas.algorithms.PsfCandidateF.
825 candidateList : `list` of `dict`
826 A list of dicts having a
"source" and "footprint"
827 field
for the Sources deemed to be appropriate
for Psf
830 if candidateList
is None:
833 if len(candidateList) < 1:
834 raise RuntimeError(
"No candidates in candidateList")
836 listTypes = set(type(x)
for x
in candidateList)
837 if len(listTypes) > 1:
838 raise RuntimeError(
"Candidate list contains mixed types: %s" % [t
for t
in listTypes])
840 if not isinstance(candidateList[0], afwTable.SourceRecord):
842 candidateList[0].getSource()
843 except Exception
as e:
844 raise RuntimeError(f
"Candidate List is of type: {type(candidateList[0])} "
845 "Can only make candidate list from list of afwTable.SourceRecords, "
846 f
"measAlg.PsfCandidateF or other type with a getSource() method: {e}")
847 candidateList = [c.getSource()
for c
in candidateList]
849 candidateList = diffimTools.sourceToFootprintList(candidateList,
850 templateExposure, scienceExposure,
854 if len(candidateList) == 0:
855 raise RuntimeError(
"Cannot find any objects suitable for KernelCandidacy")
859 def makeKernelBasisList(self, targetFwhmPix=None, referenceFwhmPix=None,
860 basisDegGauss=None, basisSigmaGauss=None, metadata=None):
861 """Wrapper to set log messages for
866 targetFwhmPix : `float`, optional
867 Passed on to `lsst.ip.diffim.generateAlardLuptonBasisList`.
868 Not used for delta function basis sets.
869 referenceFwhmPix : `float`, optional
870 Passed on to `lsst.ip.diffim.generateAlardLuptonBasisList`.
871 Not used
for delta function basis sets.
872 basisDegGauss : `list` of `int`, optional
873 Passed on to `lsst.ip.diffim.generateAlardLuptonBasisList`.
874 Not used
for delta function basis sets.
875 basisSigmaGauss : `list` of `int`, optional
876 Passed on to `lsst.ip.diffim.generateAlardLuptonBasisList`.
877 Not used
for delta function basis sets.
879 Passed on to `lsst.ip.diffim.generateAlardLuptonBasisList`.
880 Not used
for delta function basis sets.
884 basisList: `list` of `lsst.afw.math.kernel.FixedKernel`
885 List of basis kernels.
888 targetFwhmPix=targetFwhmPix,
889 referenceFwhmPix=referenceFwhmPix,
890 basisDegGauss=basisDegGauss,
891 basisSigmaGauss=basisSigmaGauss,
893 if targetFwhmPix == referenceFwhmPix:
894 self.log.info(
"Target and reference psf fwhms are equal, falling back to config values")
895 elif referenceFwhmPix > targetFwhmPix:
896 self.log.info(
"Reference psf fwhm is the greater, normal convolution mode")
898 self.log.info(
"Target psf fwhm is the greater, deconvolution mode")
902 def _adaptCellSize(self, candidateList):
903 """NOT IMPLEMENTED YET.
907 def _buildCellSet(self, templateMaskedImage, scienceMaskedImage, candidateList):
908 """Build a SpatialCellSet for use with the solve method.
913 MaskedImage to PSF-matched to scienceMaskedImage
915 Reference MaskedImage
916 candidateList : `list`
917 A list of footprints/maskedImages for kernel candidates;
919 - Currently supported: list of Footprints
or measAlg.PsfCandidateF
924 a SpatialCellSet
for use
with self.
_solve
926 if not candidateList:
927 raise RuntimeError(
"Candidate list must be populated by makeCandidateList")
933 sizeCellX, sizeCellY)
937 for cand
in candidateList:
939 bbox = cand.getBBox()
941 bbox = cand[
'footprint'].getBBox()
942 tmi = afwImage.MaskedImageF(templateMaskedImage, bbox)
943 smi = afwImage.MaskedImageF(scienceMaskedImage, bbox)
947 cand = cand[
'source']
948 xPos = cand.getCentroid()[0]
949 yPos = cand.getCentroid()[1]
950 cand = diffimLib.makeKernelCandidate(xPos, yPos, tmi, smi, ps)
952 self.log.debug(
"Candidate %d at %f, %f", cand.getId(), cand.getXCenter(), cand.getYCenter())
953 kernelCellSet.insertCandidate(cand)
957 def _validateSize(self, templateMaskedImage, scienceMaskedImage):
958 """Return True if two image-like objects are the same size.
960 return templateMaskedImage.getDimensions() == scienceMaskedImage.getDimensions()
962 def _validateWcs(self, templateExposure, scienceExposure):
963 """Return True if the WCS of the two Exposures have the same origin and extent.
965 templateWcs = templateExposure.getWcs()
966 scienceWcs = scienceExposure.getWcs()
967 templateBBox = templateExposure.getBBox()
968 scienceBBox = scienceExposure.getBBox()
971 templateOrigin = templateWcs.pixelToSky(
geom.Point2D(templateBBox.getBegin()))
972 scienceOrigin = scienceWcs.pixelToSky(
geom.Point2D(scienceBBox.getBegin()))
975 templateLimit = templateWcs.pixelToSky(
geom.Point2D(templateBBox.getEnd()))
976 scienceLimit = scienceWcs.pixelToSky(
geom.Point2D(scienceBBox.getEnd()))
978 self.log.info(
"Template Wcs : %f,%f -> %f,%f",
979 templateOrigin[0], templateOrigin[1],
980 templateLimit[0], templateLimit[1])
981 self.log.info(
"Science Wcs : %f,%f -> %f,%f",
982 scienceOrigin[0], scienceOrigin[1],
983 scienceLimit[0], scienceLimit[1])
985 templateBBox =
geom.Box2D(templateOrigin.getPosition(geom.degrees),
986 templateLimit.getPosition(geom.degrees))
987 scienceBBox =
geom.Box2D(scienceOrigin.getPosition(geom.degrees),
988 scienceLimit.getPosition(geom.degrees))
989 if not (templateBBox.overlaps(scienceBBox)):
990 raise RuntimeError(
"Input images do not overlap at all")
992 if ((templateOrigin != scienceOrigin)
993 or (templateLimit != scienceLimit)
994 or (templateExposure.getDimensions() != scienceExposure.getDimensions())):
999subtractAlgorithmRegistry = pexConfig.makeRegistry(
1000 doc=
"A registry of subtraction algorithms for use as a subtask in imageDifference",
1003subtractAlgorithmRegistry.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 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)