32import lsst.pipe.base
as pipeBase
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
42__all__ = [
"ImagePsfMatchConfig",
"ImagePsfMatchTask",
"subtractAlgorithmRegistry"]
44sigma2fwhm = 2.*np.sqrt(2.*np.log(2.))
48 """Configuration for image-to-image Psf matching.
50 kernel = pexConfig.ConfigChoiceField(
58 selectDetection = pexConfig.ConfigurableField(
59 target=SourceDetectionTask,
60 doc=
"Initial detections used to feed stars to kernel fitting",
62 selectMeasurement = pexConfig.ConfigurableField(
63 target=SingleFrameMeasurementTask,
64 doc=
"Initial measurements used to feed stars to kernel fitting",
74 self.
selectMeasurementselectMeasurement.algorithms.names = (
'base_SdssCentroid',
'base_PsfFlux',
'base_PixelFlags',
75 'base_SdssShape',
'base_GaussianFlux',
'base_SkyCoord')
82 """Psf-match two MaskedImages or Exposures using the sources in the images.
87 Arguments to be passed to lsst.ip.diffim.PsfMatchTask.__init__
89 Keyword arguments to be passed to lsst.ip.diffim.PsfMatchTask.__init__
93 Upon initialization, the kernel configuration is defined by self.config.kernel.active.
94 The task creates an lsst.afw.math.Warper
from the subConfig self.config.kernel.active.warpingConfig.
95 A schema
for the selection
and measurement of candidate lsst.ip.diffim.KernelCandidates
is
96 defined,
and used to initize subTasks selectDetection (
for candidate detection)
and selectMeasurement
97 (
for candidate measurement).
101 Build a Psf-matching kernel using two input images, either
as MaskedImages (
in which case they need
102 to be astrometrically aligned)
or Exposures (
in which case astrometric alignment will happen by
103 default but may be turned off). This requires a list of input Sources which may be provided
104 by the calling Task;
if not, the Task will perform a coarse source detection
105 and selection
for this purpose. Sources are vetted
for signal-to-noise
and masked pixels
106 (
in both the template
and science image),
and substamps around each acceptable
107 source are extracted
and used to create an instance of KernelCandidate.
110 the order that they are called: BuildSingleKernelVisitor, KernelSumVisitor, BuildSpatialKernelVisitor,
111 and AssessSpatialKernelVisitor.
113 Sigma clipping of KernelCandidates
is performed
as follows:
115 - BuildSingleKernelVisitor, using the substamp diffim residuals
from the per-source kernel fit,
116 if PsfMatchConfig.singleKernelClipping
is True
117 - KernelSumVisitor, using the mean
and standard deviation of the kernel sum
from all candidates,
118 if PsfMatchConfig.kernelSumClipping
is True
119 - AssessSpatialKernelVisitor, using the substamp diffim ressiduals
from the spatial kernel fit,
120 if PsfMatchConfig.spatialKernelClipping
is True
122 The actual solving
for the kernel (
and differential background model) happens
in
123 lsst.ip.diffim.PsfMatchTask._solve. This involves a loop over the SpatialCellSet that first builds the
124 per-candidate matching kernel
for the requested number of KernelCandidates per cell
125 (PsfMatchConfig.nStarPerCell). The quality of this initial per-candidate difference image
is examined,
126 using moments of the pixel residuals
in the difference image normalized by the square root of the variance
127 (i.e. sigma); ideally this should follow a normal (0, 1) distribution,
128 but the rejection thresholds are set
129 by the config (PsfMatchConfig.candidateResidualMeanMax
and PsfMatchConfig.candidateResidualStdMax).
130 All candidates that
pass this initial build are then examined en masse to find the
131 mean/stdev of the kernel sums across all candidates.
132 Objects that are significantly above
or below the mean,
133 typically due to variability
or sources that are saturated
in one image but
not the other,
134 are also rejected.This threshold
is defined by PsfMatchConfig.maxKsumSigma.
135 Finally, a spatial model
is built using all currently-acceptable candidates,
136 and the spatial model used to derive a second set of (spatial) residuals
137 which are again used to reject bad candidates, using the same thresholds
as above.
141 There
is no
run() method
for this Task. Instead there are 4 methods that
142 may be used to invoke the Psf-matching. These are
143 `~lsst.ip.diffim.imagePsfMatch.ImagePsfMatchTask.matchMaskedImages`,
144 `~lsst.ip.diffim.imagePsfMatch.ImagePsfMatchTask.subtractMaskedImages`,
145 `~lsst.ip.diffim.imagePsfMatch.ImagePsfMatchTask.matchExposures`,
and
146 `~lsst.ip.diffim.imagePsfMatch.ImagePsfMatchTask.subtractExposures`.
150 input images to be misregistered
and potentially be different sizes; by default a
152 that
"match" images
return a Psf-matched image,
while the methods that
"subtract" images
153 return a Psf-matched
and template subtracted image.
155 See each method
's returned lsst.pipe.base.Struct for more details.
159 The lsst.pipe.base.cmdLineTask.CmdLineTask command line task interface supports a
160 flag -d/--debug to import debug.py
from your PYTHONPATH. The relevant contents of debug.py
161 for this Task include:
169 if name ==
"lsst.ip.diffim.psfMatch":
171 di.maskTransparency = 80
172 di.displayCandidates =
True
173 di.displayKernelBasis =
False
174 di.displayKernelMosaic =
True
175 di.plotKernelSpatialModel =
False
176 di.showBadCandidates =
True
177 elif name ==
"lsst.ip.diffim.imagePsfMatch":
179 di.maskTransparency = 30
180 di.displayTemplate =
True
181 di.displaySciIm =
True
182 di.displaySpatialCells =
True
183 di.displayDiffIm =
True
184 di.showBadCandidates =
True
185 elif name ==
"lsst.ip.diffim.diaCatalogSourceSelector":
187 di.maskTransparency = 30
188 di.displayExposure =
True
189 di.pauseAtEnd =
False
194 Note that
if you want addional logging info, you may add to your scripts:
199 logUtils.traceSetAt(
"ip.diffim", 4)
203 A complete example of using ImagePsfMatchTask
205 This code
is imagePsfMatchTask.py
in the examples directory,
and can be run
as e.g.
209 examples/imagePsfMatchTask.py --debug
210 examples/imagePsfMatchTask.py --debug --mode=
"matchExposures"
211 examples/imagePsfMatchTask.py --debug --template /path/to/templateExp.fits
212 --science /path/to/scienceExp.fits
214 Create a subclass of ImagePsfMatchTask that allows us to either match exposures,
or subtract exposures:
221 ImagePsfMatchTask.__init__(self, args, kwargs)
223 def run(self, templateExp, scienceExp, mode):
224 if mode ==
"matchExposures":
226 elif mode ==
"subtractExposures":
229 And allow the user the freedom to either run the script
in default mode,
230 or point to their own images on disk.
233 We have enabled some minor display debugging
in this script via the --debug option. However,
if you
234 have an lsstDebug debug.py
in your PYTHONPATH you will get additional debugging displays. The following
235 block checks
for this script:
243 debug.lsstDebug.frame = 3
244 except ImportError
as e:
245 print(e, file=sys.stderr)
247 Finally, we call a run method that we define below.
248 First set up a Config
and modify some of the parameters.
249 E.g. use an
"Alard-Lupton" sum-of-Gaussian basis,
250 fit
for a differential background,
and use low order spatial
251 variation
in the kernel
and background:
259 config = ImagePsfMatchTask.ConfigClass()
260 config.kernel.name =
"AL"
261 config.kernel.active.fitForBackground =
True
262 config.kernel.active.spatialKernelOrder = 1
263 config.kernel.active.spatialBgOrder = 0
265 Make sure the images (
if any) that were sent to the script exist on disk
and are readable. If no images
266 are sent, make some fake data up
for the sake of this example script (have a look at the code
if you want
267 more details on generateFakeImages):
272 if args.template
is not None and args.science
is not None:
273 if not os.path.isfile(args.template):
274 raise FileNotFoundError(
"Template image %s does not exist" % (args.template))
275 if not os.path.isfile(args.science):
276 raise FileNotFoundError(
"Science image %s does not exist" % (args.science))
278 templateExp = afwImage.ExposureF(args.template)
279 except Exception
as e:
280 raise RuntimeError(
"Cannot read template image %s" % (args.template))
282 scienceExp = afwImage.ExposureF(args.science)
283 except Exception
as e:
284 raise RuntimeError(
"Cannot read science image %s" % (args.science))
286 templateExp, scienceExp = generateFakeImages()
287 config.kernel.active.sizeCellX = 128
288 config.kernel.active.sizeCellY = 128
290 Create
and run the Task:
295 psfMatchTask = MyImagePsfMatchTask(config=config)
297 result = psfMatchTask.run(templateExp, scienceExp, args.mode)
299 And
finally provide some optional debugging displays:
306 frame = debug.lsstDebug.frame + 1
309 afwDisplay.Display(frame=frame).mtv(result.matchedExposure,
310 title=
"Example script: Matched Template Image")
311 if "subtractedExposure" in result.getDict():
312 afwDisplay.Display(frame=frame + 1).mtv(result.subtractedExposure,
313 title=
"Example script: Subtracted Image")
316 ConfigClass = ImagePsfMatchConfig
319 """Create the ImagePsfMatchTask.
321 PsfMatchTask.__init__(self, *args, **kwargs)
328 self.
selectSchemaselectSchema = afwTable.SourceTable.makeMinimalSchema()
330 self.makeSubtask(
"selectDetection", schema=self.
selectSchemaselectSchema)
334 """Return the FWHM in pixels of a Psf.
336 sigPix = psf.computeShape().getDeterminantRadius()
337 return sigPix*sigma2fwhm
341 templateFwhmPix=None, scienceFwhmPix=None,
342 candidateList=None, doWarping=True, convolveTemplate=True):
343 """Warp and PSF-match an exposure to the reference.
345 Do the following, in order:
347 - Warp templateExposure to match scienceExposure,
348 if doWarping
True and their WCSs do
not already match
349 - Determine a PSF matching kernel
and differential background model
350 that matches templateExposure to scienceExposure
351 - Convolve templateExposure by PSF matching kernel
356 Exposure to warp
and PSF-match to the reference masked image
358 Exposure whose WCS
and PSF are to be matched to
359 templateFwhmPix :`float`
360 FWHM (
in pixels) of the Psf
in the template image (image to convolve)
361 scienceFwhmPix : `float`
362 FWHM (
in pixels) of the Psf
in the science image
363 candidateList : `list`, optional
364 a list of footprints/maskedImages
for kernel candidates;
365 if `
None` then source detection
is run.
367 - Currently supported: list of Footprints
or measAlg.PsfCandidateF
370 what to do
if ``templateExposure``
and ``scienceExposure`` WCSs do
not match:
372 -
if `
True` then warp ``templateExposure`` to match ``scienceExposure``
373 -
if `
False` then
raise an Exception
375 convolveTemplate : `bool`
376 Whether to convolve the template image
or the science image:
378 -
if `
True`, ``templateExposure``
is warped
if doWarping,
379 ``templateExposure``
is convolved
380 -
if `
False`, ``templateExposure``
is warped
if doWarping,
381 ``scienceExposure``
is convolved
385 results : `lsst.pipe.base.Struct`
386 An `lsst.pipe.base.Struct` containing these fields:
388 - ``matchedImage`` : the PSF-matched exposure =
389 Warped ``templateExposure`` convolved by psfMatchingKernel. This has:
391 - the same parent bbox, Wcs
and PhotoCalib
as scienceExposure
392 - the same filter
as templateExposure
393 - no Psf (because the PSF-matching process does
not compute one)
395 - ``psfMatchingKernel`` : the PSF matching kernel
396 - ``backgroundModel`` : differential background model
397 - ``kernelCellSet`` : SpatialCellSet used to solve
for the PSF matching kernel
402 Raised
if doWarping
is False and ``templateExposure``
and
403 ``scienceExposure`` WCSs do
not match
405 if not self.
_validateWcs_validateWcs(templateExposure, scienceExposure):
407 self.log.info(
"Astrometrically registering template to science image")
408 templatePsf = templateExposure.getPsf()
411 scienceExposure.getWcs())
412 psfWarped = WarpedPsf(templatePsf, xyTransform)
413 templateExposure = self.
_warper_warper.warpExposure(scienceExposure.getWcs(),
415 destBBox=scienceExposure.getBBox())
416 templateExposure.setPsf(psfWarped)
418 self.log.error(
"ERROR: Input images not registered")
419 raise RuntimeError(
"Input images not registered")
421 if templateFwhmPix
is None:
422 if not templateExposure.hasPsf():
423 self.log.warning(
"No estimate of Psf FWHM for template image")
425 templateFwhmPix = self.
getFwhmPixgetFwhmPix(templateExposure.getPsf())
426 self.log.info(
"templateFwhmPix: %s", templateFwhmPix)
428 if scienceFwhmPix
is None:
429 if not scienceExposure.hasPsf():
430 self.log.warning(
"No estimate of Psf FWHM for science image")
432 scienceFwhmPix = self.
getFwhmPixgetFwhmPix(scienceExposure.getPsf())
433 self.log.info(
"scienceFwhmPix: %s", scienceFwhmPix)
436 kernelSize = self.
makeKernelBasisListmakeKernelBasisList(templateFwhmPix, scienceFwhmPix)[0].getWidth()
438 templateExposure, scienceExposure, kernelSize, candidateList)
440 templateExposure.getMaskedImage(), scienceExposure.getMaskedImage(), candidateList,
441 templateFwhmPix=templateFwhmPix, scienceFwhmPix=scienceFwhmPix)
443 kernelSize = self.
makeKernelBasisListmakeKernelBasisList(scienceFwhmPix, templateFwhmPix)[0].getWidth()
445 templateExposure, scienceExposure, kernelSize, candidateList)
447 scienceExposure.getMaskedImage(), templateExposure.getMaskedImage(), candidateList,
448 templateFwhmPix=scienceFwhmPix, scienceFwhmPix=templateFwhmPix)
451 psfMatchedExposure.setFilterLabel(templateExposure.getFilterLabel())
452 psfMatchedExposure.setPhotoCalib(scienceExposure.getPhotoCalib())
453 results.warpedExposure = templateExposure
454 results.matchedExposure = psfMatchedExposure
459 templateFwhmPix=None, scienceFwhmPix=None):
460 """PSF-match a MaskedImage (templateMaskedImage) to a reference MaskedImage (scienceMaskedImage).
462 Do the following, in order:
464 - Determine a PSF matching kernel
and differential background model
465 that matches templateMaskedImage to scienceMaskedImage
466 - Convolve templateMaskedImage by the PSF matching kernel
471 masked image to PSF-match to the reference masked image;
472 must be warped to match the reference masked image
474 maskedImage whose PSF
is to be matched to
475 templateFwhmPix : `float`
476 FWHM (
in pixels) of the Psf
in the template image (image to convolve)
477 scienceFwhmPix : `float`
478 FWHM (
in pixels) of the Psf
in the science image
479 candidateList : `list`, optional
480 A list of footprints/maskedImages
for kernel candidates;
481 if `
None` then source detection
is run.
483 - Currently supported: list of Footprints
or measAlg.PsfCandidateF
488 An `lsst.pipe.base.Struct` containing these fields:
490 - psfMatchedMaskedImage: the PSF-matched masked image =
491 ``templateMaskedImage`` convolved
with psfMatchingKernel.
492 This has the same xy0, dimensions
and wcs
as ``scienceMaskedImage``.
493 - psfMatchingKernel: the PSF matching kernel
494 - backgroundModel: differential background model
495 - kernelCellSet: SpatialCellSet used to solve
for the PSF matching kernel
500 Raised
if input images have different dimensions
506 displaySpatialCells =
lsstDebug.Info(__name__).displaySpatialCells
508 if not maskTransparency:
511 afwDisplay.setDefaultMaskTransparency(maskTransparency)
513 if not candidateList:
514 raise RuntimeError(
"Candidate list must be populated by makeCandidateList")
516 if not self.
_validateSize_validateSize(templateMaskedImage, scienceMaskedImage):
517 self.log.error(
"ERROR: Input images different size")
518 raise RuntimeError(
"Input images different size")
520 if display
and displayTemplate:
521 disp = afwDisplay.Display(frame=lsstDebug.frame)
522 disp.mtv(templateMaskedImage, title=
"Image to convolve")
525 if display
and displaySciIm:
526 disp = afwDisplay.Display(frame=lsstDebug.frame)
527 disp.mtv(scienceMaskedImage, title=
"Image to not convolve")
534 if display
and displaySpatialCells:
535 diffimUtils.showKernelSpatialCells(scienceMaskedImage, kernelCellSet,
536 symb=
"o", ctype=afwDisplay.CYAN, ctypeUnused=afwDisplay.YELLOW,
537 ctypeBad=afwDisplay.RED, size=4, frame=lsstDebug.frame,
538 title=
"Image to not convolve")
541 if templateFwhmPix
and scienceFwhmPix:
542 self.log.info(
"Matching Psf FWHM %.2f -> %.2f pix", templateFwhmPix, scienceFwhmPix)
549 bicDegrees = nbe(tmpKernelCellSet, self.log)
551 basisDegGauss=bicDegrees[0], metadata=self.metadata)
555 metadata=self.metadata)
557 spatialSolution, psfMatchingKernel, backgroundModel = self.
_solve_solve(kernelCellSet, basisList)
559 psfMatchedMaskedImage = afwImage.MaskedImageF(templateMaskedImage.getBBox())
561 convolutionControl.setDoNormalize(
False)
562 afwMath.convolve(psfMatchedMaskedImage, templateMaskedImage, psfMatchingKernel, convolutionControl)
563 return pipeBase.Struct(
564 matchedImage=psfMatchedMaskedImage,
565 psfMatchingKernel=psfMatchingKernel,
566 backgroundModel=backgroundModel,
567 kernelCellSet=kernelCellSet,
572 templateFwhmPix=None, scienceFwhmPix=None,
573 candidateList=None, doWarping=True, convolveTemplate=True):
574 """Register, Psf-match and subtract two Exposures.
576 Do the following, in order:
578 - Warp templateExposure to match scienceExposure,
if their WCSs do
not already match
579 - Determine a PSF matching kernel
and differential background model
580 that matches templateExposure to scienceExposure
581 - PSF-match templateExposure to scienceExposure
582 - Compute subtracted exposure (see
return values
for equation).
586 templateExposure : `lsst.afw.image.ExposureF`
587 Exposure to PSF-match to scienceExposure
588 scienceExposure : `lsst.afw.image.ExposureF`
590 templateFwhmPix : `float`
591 FWHM (
in pixels) of the Psf
in the template image (image to convolve)
592 scienceFwhmPix : `float`
593 FWHM (
in pixels) of the Psf
in the science image
594 candidateList : `list`, optional
595 A list of footprints/maskedImages
for kernel candidates;
596 if `
None` then source detection
is run.
598 - Currently supported: list of Footprints
or measAlg.PsfCandidateF
601 What to do
if ``templateExposure```
and ``scienceExposure`` WCSs do
604 -
if `
True` then warp ``templateExposure`` to match ``scienceExposure``
605 -
if `
False` then
raise an Exception
607 convolveTemplate : `bool`
608 Convolve the template image
or the science image
610 -
if `
True`, ``templateExposure``
is warped
if doWarping,
611 ``templateExposure``
is convolved
612 -
if `
False`, ``templateExposure``
is warped
if doWarping,
613 ``scienceExposure
is`` convolved
617 result : `lsst.pipe.base.Struct`
618 An `lsst.pipe.base.Struct` containing these fields:
620 - ``subtractedExposure`` : subtracted Exposure
621 scienceExposure - (matchedImage + backgroundModel)
622 - ``matchedImage`` : ``templateExposure`` after warping to match
623 ``templateExposure`` (
if doWarping true),
624 and convolving
with psfMatchingKernel
625 - ``psfMatchingKernel`` : PSF matching kernel
626 - ``backgroundModel`` : differential background model
627 - ``kernelCellSet`` : SpatialCellSet used to determine PSF matching kernel
630 templateExposure=templateExposure,
631 scienceExposure=scienceExposure,
632 templateFwhmPix=templateFwhmPix,
633 scienceFwhmPix=scienceFwhmPix,
634 candidateList=candidateList,
636 convolveTemplate=convolveTemplate
639 subtractedExposure = afwImage.ExposureF(scienceExposure, deep=
True)
645 subtractedMaskedImage = subtractedExposure.maskedImage
646 subtractedMaskedImage -= results.matchedExposure.maskedImage
647 subtractedMaskedImage -= results.backgroundModel
649 subtractedMaskedImage = subtractedExposure.maskedImage
650 subtractedMaskedImage[:, :] = results.warpedExposure.maskedImage
651 subtractedMaskedImage -= results.matchedExposure.maskedImage
652 subtractedMaskedImage -= results.backgroundModel
655 subtractedMaskedImage *= -1
658 subtractedMaskedImage /= results.psfMatchingKernel.computeImage(
659 afwImage.ImageD(results.psfMatchingKernel.getDimensions()),
False)
661 subtractedExposure.setPsf(results.warpedExposure.getPsf())
667 if not maskTransparency:
670 afwDisplay.setDefaultMaskTransparency(maskTransparency)
671 if display
and displayDiffIm:
672 disp = afwDisplay.Display(frame=lsstDebug.frame)
673 disp.mtv(templateExposure, title=
"Template")
675 disp = afwDisplay.Display(frame=lsstDebug.frame)
676 disp.mtv(results.matchedExposure, title=
"Matched template")
678 disp = afwDisplay.Display(frame=lsstDebug.frame)
679 disp.mtv(scienceExposure, title=
"Science Image")
681 disp = afwDisplay.Display(frame=lsstDebug.frame)
682 disp.mtv(subtractedExposure, title=
"Difference Image")
685 results.subtractedExposure = subtractedExposure
690 templateFwhmPix=None, scienceFwhmPix=None):
691 """Psf-match and subtract two MaskedImages.
693 Do the following, in order:
695 - PSF-match templateMaskedImage to scienceMaskedImage
696 - Determine the differential background
697 - Return the difference: scienceMaskedImage
698 ((warped templateMaskedImage convolved
with psfMatchingKernel) + backgroundModel)
703 MaskedImage to PSF-match to ``scienceMaskedImage``
705 Reference MaskedImage
706 templateFwhmPix : `float`
707 FWHM (
in pixels) of the Psf
in the template image (image to convolve)
708 scienceFwhmPix : `float`
709 FWHM (
in pixels) of the Psf
in the science image
710 candidateList : `list`, optional
711 A list of footprints/maskedImages
for kernel candidates;
712 if `
None` then source detection
is run.
714 - Currently supported: list of Footprints
or measAlg.PsfCandidateF
718 results : `lsst.pipe.base.Struct`
719 An `lsst.pipe.base.Struct` containing these fields:
721 - ``subtractedMaskedImage`` : ``scienceMaskedImage`` - (matchedImage + backgroundModel)
722 - ``matchedImage`` : templateMaskedImage convolved
with psfMatchingKernel
723 - `psfMatchingKernel`` : PSF matching kernel
724 - ``backgroundModel`` : differential background model
725 - ``kernelCellSet`` : SpatialCellSet used to determine PSF matching kernel
728 if not candidateList:
729 raise RuntimeError(
"Candidate list must be populated by makeCandidateList")
732 templateMaskedImage=templateMaskedImage,
733 scienceMaskedImage=scienceMaskedImage,
734 candidateList=candidateList,
735 templateFwhmPix=templateFwhmPix,
736 scienceFwhmPix=scienceFwhmPix,
739 subtractedMaskedImage = afwImage.MaskedImageF(scienceMaskedImage,
True)
740 subtractedMaskedImage -= results.matchedImage
741 subtractedMaskedImage -= results.backgroundModel
742 results.subtractedMaskedImage = subtractedMaskedImage
748 if not maskTransparency:
751 afwDisplay.setDefaultMaskTransparency(maskTransparency)
752 if display
and displayDiffIm:
753 disp = afwDisplay.Display(frame=lsstDebug.frame)
754 disp.mtv(subtractedMaskedImage, title=
"Subtracted masked image")
760 """Get sources to use for Psf-matching.
762 This method runs detection and measurement on an exposure.
763 The returned set of sources will be used
as candidates
for
769 Exposure on which to run detection/measurement
773 Whether
or not to smooth the Exposure
with Psf before detection
775 Factory
for the generation of Source ids
780 source catalog containing candidates
for the Psf-matching
783 table = afwTable.SourceTable.make(self.
selectSchemaselectSchema, idFactory)
785 table = afwTable.SourceTable.make(self.
selectSchemaselectSchema)
786 mi = exposure.getMaskedImage()
788 imArr = mi.getImage().getArray()
789 maskArr = mi.getMask().getArray()
790 miArr = np.ma.masked_array(imArr, mask=maskArr)
792 fitBg = self.
backgroundbackground.fitBackground(mi)
793 bkgd = fitBg.getImageF(self.
backgroundbackground.config.algorithm,
794 self.
backgroundbackground.config.undersampleStyle)
796 self.log.warning(
"Failed to get background model. Falling back to median background estimation")
797 bkgd = np.ma.median(miArr)
803 detRet = self.selectDetection.
run(
809 selectSources = detRet.sources
810 self.selectMeasurement.
run(measCat=selectSources, exposure=exposure)
818 """Make a list of acceptable KernelCandidates.
820 Accept or generate a list of candidate sources
for
821 Psf-matching,
and examine the Mask planes
in both of the
822 images
for indications of bad pixels
827 Exposure that will be convolved
829 Exposure that will be matched-to
831 Dimensions of the Psf-matching Kernel, used to grow detection footprints
832 candidateList : `list`, optional
833 List of Sources to examine. Elements must be of type afw.table.Source
834 or a type that wraps a Source
and has a getSource() method, such
as
835 meas.algorithms.PsfCandidateF.
839 candidateList : `list` of `dict`
840 A list of dicts having a
"source" and "footprint"
841 field
for the Sources deemed to be appropriate
for Psf
844 if candidateList
is None:
847 if len(candidateList) < 1:
848 raise RuntimeError(
"No candidates in candidateList")
850 listTypes = set(type(x)
for x
in candidateList)
851 if len(listTypes) > 1:
852 raise RuntimeError(
"Candidate list contains mixed types: %s" % [t
for t
in listTypes])
854 if not isinstance(candidateList[0], afwTable.SourceRecord):
856 candidateList[0].getSource()
857 except Exception
as e:
858 raise RuntimeError(f
"Candidate List is of type: {type(candidateList[0])} "
859 "Can only make candidate list from list of afwTable.SourceRecords, "
860 f
"measAlg.PsfCandidateF or other type with a getSource() method: {e}")
861 candidateList = [c.getSource()
for c
in candidateList]
863 candidateList = diffimTools.sourceToFootprintList(candidateList,
864 templateExposure, scienceExposure,
868 if len(candidateList) == 0:
869 raise RuntimeError(
"Cannot find any objects suitable for KernelCandidacy")
874 basisDegGauss=None, basisSigmaGauss=None, metadata=None):
875 """Wrapper to set log messages for
880 targetFwhmPix : `float`, optional
881 Passed on to `lsst.ip.diffim.generateAlardLuptonBasisList`.
882 Not used for delta function basis sets.
883 referenceFwhmPix : `float`, optional
884 Passed on to `lsst.ip.diffim.generateAlardLuptonBasisList`.
885 Not used
for delta function basis sets.
886 basisDegGauss : `list` of `int`, optional
887 Passed on to `lsst.ip.diffim.generateAlardLuptonBasisList`.
888 Not used
for delta function basis sets.
889 basisSigmaGauss : `list` of `int`, optional
890 Passed on to `lsst.ip.diffim.generateAlardLuptonBasisList`.
891 Not used
for delta function basis sets.
893 Passed on to `lsst.ip.diffim.generateAlardLuptonBasisList`.
894 Not used
for delta function basis sets.
898 basisList: `list` of `lsst.afw.math.kernel.FixedKernel`
899 List of basis kernels.
902 targetFwhmPix=targetFwhmPix,
903 referenceFwhmPix=referenceFwhmPix,
904 basisDegGauss=basisDegGauss,
905 basisSigmaGauss=basisSigmaGauss,
907 if targetFwhmPix == referenceFwhmPix:
908 self.log.info(
"Target and reference psf fwhms are equal, falling back to config values")
909 elif referenceFwhmPix > targetFwhmPix:
910 self.log.info(
"Reference psf fwhm is the greater, normal convolution mode")
912 self.log.info(
"Target psf fwhm is the greater, deconvolution mode")
916 def _adaptCellSize(self, candidateList):
917 """NOT IMPLEMENTED YET.
921 def _buildCellSet(self, templateMaskedImage, scienceMaskedImage, candidateList):
922 """Build a SpatialCellSet for use with the solve method.
927 MaskedImage to PSF-matched to scienceMaskedImage
929 Reference MaskedImage
930 candidateList : `list`
931 A list of footprints/maskedImages for kernel candidates;
933 - Currently supported: list of Footprints
or measAlg.PsfCandidateF
938 a SpatialCellSet
for use
with self.
_solve_solve
940 if not candidateList:
941 raise RuntimeError(
"Candidate list must be populated by makeCandidateList")
943 sizeCellX, sizeCellY = self.
_adaptCellSize_adaptCellSize(candidateList)
947 sizeCellX, sizeCellY)
951 for cand
in candidateList:
953 bbox = cand.getBBox()
955 bbox = cand[
'footprint'].getBBox()
956 tmi = afwImage.MaskedImageF(templateMaskedImage, bbox)
957 smi = afwImage.MaskedImageF(scienceMaskedImage, bbox)
961 cand = cand[
'source']
962 xPos = cand.getCentroid()[0]
963 yPos = cand.getCentroid()[1]
964 cand = diffimLib.makeKernelCandidate(xPos, yPos, tmi, smi, ps)
966 self.log.debug(
"Candidate %d at %f, %f", cand.getId(), cand.getXCenter(), cand.getYCenter())
967 kernelCellSet.insertCandidate(cand)
971 def _validateSize(self, templateMaskedImage, scienceMaskedImage):
972 """Return True if two image-like objects are the same size.
974 return templateMaskedImage.getDimensions() == scienceMaskedImage.getDimensions()
976 def _validateWcs(self, templateExposure, scienceExposure):
977 """Return True if the WCS of the two Exposures have the same origin and extent.
979 templateWcs = templateExposure.getWcs()
980 scienceWcs = scienceExposure.getWcs()
981 templateBBox = templateExposure.getBBox()
982 scienceBBox = scienceExposure.getBBox()
985 templateOrigin = templateWcs.pixelToSky(
geom.Point2D(templateBBox.getBegin()))
986 scienceOrigin = scienceWcs.pixelToSky(
geom.Point2D(scienceBBox.getBegin()))
989 templateLimit = templateWcs.pixelToSky(
geom.Point2D(templateBBox.getEnd()))
990 scienceLimit = scienceWcs.pixelToSky(
geom.Point2D(scienceBBox.getEnd()))
992 self.log.info(
"Template Wcs : %f,%f -> %f,%f",
993 templateOrigin[0], templateOrigin[1],
994 templateLimit[0], templateLimit[1])
995 self.log.info(
"Science Wcs : %f,%f -> %f,%f",
996 scienceOrigin[0], scienceOrigin[1],
997 scienceLimit[0], scienceLimit[1])
999 templateBBox =
geom.Box2D(templateOrigin.getPosition(geom.degrees),
1000 templateLimit.getPosition(geom.degrees))
1001 scienceBBox =
geom.Box2D(scienceOrigin.getPosition(geom.degrees),
1002 scienceLimit.getPosition(geom.degrees))
1003 if not (templateBBox.overlaps(scienceBBox)):
1004 raise RuntimeError(
"Input images do not overlap at all")
1006 if ((templateOrigin != scienceOrigin)
1007 or (templateLimit != scienceLimit)
1008 or (templateExposure.getDimensions() != scienceExposure.getDimensions())):
1013subtractAlgorithmRegistry = pexConfig.makeRegistry(
1014 doc=
"A registry of subtraction algorithms for use as a subtask in imageDifference",
1017subtractAlgorithmRegistry.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)
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)