35 from .makeKernelBasisList
import makeKernelBasisList
36 from .psfMatch
import PsfMatchTask, PsfMatchConfigDF, PsfMatchConfigAL
37 from .
import utils
as diffimUtils
38 from .
import diffimLib
39 from .
import diffimTools
41 from lsst.utils.timer
import timeMethod
43 __all__ = [
"ImagePsfMatchConfig",
"ImagePsfMatchTask",
"subtractAlgorithmRegistry"]
45 sigma2fwhm = 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.
selectMeasurementselectMeasurement.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.
109 Each KernelCandidate is then placed within a lsst.afw.math.SpatialCellSet, which is used by an ensemble of
110 lsst.afw.math.CandidateVisitor instances to build the Psf-matching kernel. These visitors include, in
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`.
149 The methods that operate on lsst.afw.image.MaskedImage require that the images already be astrometrically
150 aligned, and are the same shape. The methods that operate on lsst.afw.image.Exposure allow for the
151 input images to be misregistered and potentially be different sizes; by default a
152 lsst.afw.math.LanczosWarpingKernel is used to perform the astrometric alignment. The methods
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 lsst.pipe.base.cmdLineTask.CmdLineTask command line task interface supports a
161 flag -d/--debug to import debug.py from your PYTHONPATH. The relevant contents of debug.py
162 for this Task include:
169 di = lsstDebug.getInfo(name)
170 if name == "lsst.ip.diffim.psfMatch":
171 di.display = True # enable debug output
172 di.maskTransparency = 80 # display mask transparency
173 di.displayCandidates = True # show all the candidates and residuals
174 di.displayKernelBasis = False # show kernel basis functions
175 di.displayKernelMosaic = True # show kernel realized across the image
176 di.plotKernelSpatialModel = False # show coefficients of spatial model
177 di.showBadCandidates = True # show the bad candidates (red) along with good (green)
178 elif name == "lsst.ip.diffim.imagePsfMatch":
179 di.display = True # enable debug output
180 di.maskTransparency = 30 # display mask transparency
181 di.displayTemplate = True # show full (remapped) template
182 di.displaySciIm = True # show science image to match to
183 di.displaySpatialCells = True # show spatial cells
184 di.displayDiffIm = True # show difference image
185 di.showBadCandidates = True # show the bad candidates (red) along with good (green)
186 elif name == "lsst.ip.diffim.diaCatalogSourceSelector":
187 di.display = False # enable debug output
188 di.maskTransparency = 30 # display mask transparency
189 di.displayExposure = True # show exposure with candidates indicated
190 di.pauseAtEnd = False # pause when done
192 lsstDebug.Info = DebugInfo
195 Note that if you want addional logging info, you may add to your scripts:
199 import lsst.log.utils as logUtils
200 logUtils.traceSetAt("lsst.ip.diffim", 4)
204 A complete example of using ImagePsfMatchTask
206 This code is imagePsfMatchTask.py in the examples directory, and can be run as e.g.
210 examples/imagePsfMatchTask.py --debug
211 examples/imagePsfMatchTask.py --debug --mode="matchExposures"
212 examples/imagePsfMatchTask.py --debug --template /path/to/templateExp.fits
213 --science /path/to/scienceExp.fits
215 Create a subclass of ImagePsfMatchTask that allows us to either match exposures, or subtract exposures:
219 class MyImagePsfMatchTask(ImagePsfMatchTask):
221 def __init__(self, args, kwargs):
222 ImagePsfMatchTask.__init__(self, args, kwargs)
224 def run(self, templateExp, scienceExp, mode):
225 if mode == "matchExposures":
226 return self.matchExposures(templateExp, scienceExp)
227 elif mode == "subtractExposures":
228 return self.subtractExposures(templateExp, scienceExp)
230 And allow the user the freedom to either run the script in default mode,
231 or point to their own images on disk.
232 Note that these images must be readable as an lsst.afw.image.Exposure.
234 We have enabled some minor display debugging in this script via the --debug option. However, if you
235 have an lsstDebug debug.py in your PYTHONPATH you will get additional debugging displays. The following
236 block checks for this script:
243 # Since I am displaying 2 images here, set the starting frame number for the LSST debug LSST
244 debug.lsstDebug.frame = 3
245 except ImportError as e:
246 print(e, file=sys.stderr)
248 Finally, we call a run method that we define below.
249 First set up a Config and modify some of the parameters.
250 E.g. use an "Alard-Lupton" sum-of-Gaussian basis,
251 fit for a differential background, and use low order spatial
252 variation in the kernel and background:
258 # Create the Config and use sum of gaussian basis
260 config = ImagePsfMatchTask.ConfigClass()
261 config.kernel.name = "AL"
262 config.kernel.active.fitForBackground = True
263 config.kernel.active.spatialKernelOrder = 1
264 config.kernel.active.spatialBgOrder = 0
266 Make sure the images (if any) that were sent to the script exist on disk and are readable. If no images
267 are sent, make some fake data up for the sake of this example script (have a look at the code if you want
268 more details on generateFakeImages):
272 # Run the requested method of the Task
273 if args.template is not None and args.science is not None:
274 if not os.path.isfile(args.template):
275 raise FileNotFoundError("Template image %s does not exist" % (args.template))
276 if not os.path.isfile(args.science):
277 raise FileNotFoundError("Science image %s does not exist" % (args.science))
279 templateExp = afwImage.ExposureF(args.template)
280 except Exception as e:
281 raise RuntimeError("Cannot read template image %s" % (args.template))
283 scienceExp = afwImage.ExposureF(args.science)
284 except Exception as e:
285 raise RuntimeError("Cannot read science image %s" % (args.science))
287 templateExp, scienceExp = generateFakeImages()
288 config.kernel.active.sizeCellX = 128
289 config.kernel.active.sizeCellY = 128
291 Create and run the Task:
296 psfMatchTask = MyImagePsfMatchTask(config=config)
298 result = psfMatchTask.run(templateExp, scienceExp, args.mode)
300 And finally provide some optional debugging displays:
305 # See if the LSST debug has incremented the frame number; if not start with frame 3
307 frame = debug.lsstDebug.frame + 1
310 afwDisplay.Display(frame=frame).mtv(result.matchedExposure,
311 title="Example script: Matched Template Image")
312 if "subtractedExposure" in result.getDict():
313 afwDisplay.Display(frame=frame + 1).mtv(result.subtractedExposure,
314 title="Example script: Subtracted Image")
317 ConfigClass = ImagePsfMatchConfig
320 """Create the ImagePsfMatchTask.
322 PsfMatchTask.__init__(self, *args, **kwargs)
329 self.
selectSchemaselectSchema = afwTable.SourceTable.makeMinimalSchema()
331 self.makeSubtask(
"selectDetection", schema=self.
selectSchemaselectSchema)
335 """Return the FWHM in pixels of a Psf.
337 sigPix = psf.computeShape().getDeterminantRadius()
338 return sigPix*sigma2fwhm
342 templateFwhmPix=None, scienceFwhmPix=None,
343 candidateList=None, doWarping=True, convolveTemplate=True):
344 """Warp and PSF-match an exposure to the reference.
346 Do the following, in order:
348 - Warp templateExposure to match scienceExposure,
349 if doWarping True and their WCSs do not already match
350 - Determine a PSF matching kernel and differential background model
351 that matches templateExposure to scienceExposure
352 - Convolve templateExposure by PSF matching kernel
356 templateExposure : `lsst.afw.image.Exposure`
357 Exposure to warp and PSF-match to the reference masked image
358 scienceExposure : `lsst.afw.image.Exposure`
359 Exposure whose WCS and PSF are to be matched to
360 templateFwhmPix :`float`
361 FWHM (in pixels) of the Psf in the template image (image to convolve)
362 scienceFwhmPix : `float`
363 FWHM (in pixels) of the Psf in the science image
364 candidateList : `list`, optional
365 a list of footprints/maskedImages for kernel candidates;
366 if `None` then source detection is run.
368 - Currently supported: list of Footprints or measAlg.PsfCandidateF
371 what to do if ``templateExposure`` and ``scienceExposure`` WCSs do not match:
373 - if `True` then warp ``templateExposure`` to match ``scienceExposure``
374 - if `False` then raise an Exception
376 convolveTemplate : `bool`
377 Whether to convolve the template image or the science image:
379 - if `True`, ``templateExposure`` is warped if doWarping,
380 ``templateExposure`` is convolved
381 - if `False`, ``templateExposure`` is warped if doWarping,
382 ``scienceExposure`` is convolved
386 results : `lsst.pipe.base.Struct`
387 An `lsst.pipe.base.Struct` containing these fields:
389 - ``matchedImage`` : the PSF-matched exposure =
390 Warped ``templateExposure`` convolved by psfMatchingKernel. This has:
392 - the same parent bbox, Wcs and PhotoCalib as scienceExposure
393 - the same filter as templateExposure
394 - no Psf (because the PSF-matching process does not compute one)
396 - ``psfMatchingKernel`` : the PSF matching kernel
397 - ``backgroundModel`` : differential background model
398 - ``kernelCellSet`` : SpatialCellSet used to solve for the PSF matching kernel
403 Raised if doWarping is False and ``templateExposure`` and
404 ``scienceExposure`` WCSs do not match
406 if not self.
_validateWcs_validateWcs(templateExposure, scienceExposure):
408 self.log.
info(
"Astrometrically registering template to science image")
409 templatePsf = templateExposure.getPsf()
412 scienceExposure.getWcs())
413 psfWarped =
WarpedPsf(templatePsf, xyTransform)
416 destBBox=scienceExposure.getBBox())
417 templateExposure.setPsf(psfWarped)
419 self.log.
error(
"ERROR: Input images not registered")
420 raise RuntimeError(
"Input images not registered")
422 if templateFwhmPix
is None:
423 if not templateExposure.hasPsf():
424 self.log.
warning(
"No estimate of Psf FWHM for template image")
426 templateFwhmPix = self.
getFwhmPixgetFwhmPix(templateExposure.getPsf())
427 self.log.
info(
"templateFwhmPix: %s", templateFwhmPix)
429 if scienceFwhmPix
is None:
430 if not scienceExposure.hasPsf():
431 self.log.
warning(
"No estimate of Psf FWHM for science image")
433 scienceFwhmPix = self.
getFwhmPixgetFwhmPix(scienceExposure.getPsf())
434 self.log.
info(
"scienceFwhmPix: %s", scienceFwhmPix)
437 kernelSize = self.
makeKernelBasisListmakeKernelBasisList(templateFwhmPix, scienceFwhmPix)[0].getWidth()
439 templateExposure, scienceExposure, kernelSize, candidateList)
441 templateExposure.getMaskedImage(), scienceExposure.getMaskedImage(), candidateList,
442 templateFwhmPix=templateFwhmPix, scienceFwhmPix=scienceFwhmPix)
444 kernelSize = self.
makeKernelBasisListmakeKernelBasisList(scienceFwhmPix, templateFwhmPix)[0].getWidth()
446 templateExposure, scienceExposure, kernelSize, candidateList)
448 scienceExposure.getMaskedImage(), templateExposure.getMaskedImage(), candidateList,
449 templateFwhmPix=scienceFwhmPix, scienceFwhmPix=templateFwhmPix)
452 psfMatchedExposure.setFilterLabel(templateExposure.getFilterLabel())
453 psfMatchedExposure.setPhotoCalib(scienceExposure.getPhotoCalib())
454 results.warpedExposure = templateExposure
455 results.matchedExposure = psfMatchedExposure
460 templateFwhmPix=None, scienceFwhmPix=None):
461 """PSF-match a MaskedImage (templateMaskedImage) to a reference MaskedImage (scienceMaskedImage).
463 Do the following, in order:
465 - Determine a PSF matching kernel and differential background model
466 that matches templateMaskedImage to scienceMaskedImage
467 - Convolve templateMaskedImage by the PSF matching kernel
471 templateMaskedImage : `lsst.afw.image.MaskedImage`
472 masked image to PSF-match to the reference masked image;
473 must be warped to match the reference masked image
474 scienceMaskedImage : `lsst.afw.image.MaskedImage`
475 maskedImage whose PSF is to be matched to
476 templateFwhmPix : `float`
477 FWHM (in pixels) of the Psf in the template image (image to convolve)
478 scienceFwhmPix : `float`
479 FWHM (in pixels) of the Psf in the science image
480 candidateList : `list`, optional
481 A list of footprints/maskedImages for kernel candidates;
482 if `None` then source detection is run.
484 - Currently supported: list of Footprints or measAlg.PsfCandidateF
489 An `lsst.pipe.base.Struct` containing these fields:
491 - psfMatchedMaskedImage: the PSF-matched masked image =
492 ``templateMaskedImage`` convolved with psfMatchingKernel.
493 This has the same xy0, dimensions and wcs as ``scienceMaskedImage``.
494 - psfMatchingKernel: the PSF matching kernel
495 - backgroundModel: differential background model
496 - kernelCellSet: SpatialCellSet used to solve for the PSF matching kernel
501 Raised if input images have different dimensions
507 displaySpatialCells =
lsstDebug.Info(__name__).displaySpatialCells
509 if not maskTransparency:
512 afwDisplay.setDefaultMaskTransparency(maskTransparency)
514 if not candidateList:
515 raise RuntimeError(
"Candidate list must be populated by makeCandidateList")
517 if not self.
_validateSize_validateSize(templateMaskedImage, scienceMaskedImage):
518 self.log.
error(
"ERROR: Input images different size")
519 raise RuntimeError(
"Input images different size")
521 if display
and displayTemplate:
522 disp = afwDisplay.Display(frame=lsstDebug.frame)
523 disp.mtv(templateMaskedImage, title=
"Image to convolve")
526 if display
and displaySciIm:
527 disp = afwDisplay.Display(frame=lsstDebug.frame)
528 disp.mtv(scienceMaskedImage, title=
"Image to not convolve")
535 if display
and displaySpatialCells:
536 diffimUtils.showKernelSpatialCells(scienceMaskedImage, kernelCellSet,
537 symb=
"o", ctype=afwDisplay.CYAN, ctypeUnused=afwDisplay.YELLOW,
538 ctypeBad=afwDisplay.RED, size=4, frame=lsstDebug.frame,
539 title=
"Image to not convolve")
542 if templateFwhmPix
and scienceFwhmPix:
543 self.log.
info(
"Matching Psf FWHM %.2f -> %.2f pix", templateFwhmPix, scienceFwhmPix)
550 bicDegrees = nbe(tmpKernelCellSet, self.log)
552 basisDegGauss=bicDegrees[0], metadata=self.metadata)
556 metadata=self.metadata)
558 spatialSolution, psfMatchingKernel, backgroundModel = self.
_solve_solve(kernelCellSet, basisList)
560 psfMatchedMaskedImage = afwImage.MaskedImageF(templateMaskedImage.getBBox())
562 convolutionControl.setDoNormalize(
False)
563 afwMath.convolve(psfMatchedMaskedImage, templateMaskedImage, psfMatchingKernel, convolutionControl)
564 return pipeBase.Struct(
565 matchedImage=psfMatchedMaskedImage,
566 psfMatchingKernel=psfMatchingKernel,
567 backgroundModel=backgroundModel,
568 kernelCellSet=kernelCellSet,
573 templateFwhmPix=None, scienceFwhmPix=None,
574 candidateList=None, doWarping=True, convolveTemplate=True):
575 """Register, Psf-match and subtract two Exposures.
577 Do the following, in order:
579 - Warp templateExposure to match scienceExposure, if their WCSs do not already match
580 - Determine a PSF matching kernel and differential background model
581 that matches templateExposure to scienceExposure
582 - PSF-match templateExposure to scienceExposure
583 - Compute subtracted exposure (see return values for equation).
587 templateExposure : `lsst.afw.image.ExposureF`
588 Exposure to PSF-match to scienceExposure
589 scienceExposure : `lsst.afw.image.ExposureF`
591 templateFwhmPix : `float`
592 FWHM (in pixels) of the Psf in the template image (image to convolve)
593 scienceFwhmPix : `float`
594 FWHM (in pixels) of the Psf in the science image
595 candidateList : `list`, optional
596 A list of footprints/maskedImages for kernel candidates;
597 if `None` then source detection is run.
599 - Currently supported: list of Footprints or measAlg.PsfCandidateF
602 What to do if ``templateExposure``` and ``scienceExposure`` WCSs do
605 - if `True` then warp ``templateExposure`` to match ``scienceExposure``
606 - if `False` then raise an Exception
608 convolveTemplate : `bool`
609 Convolve the template image or the science image
611 - if `True`, ``templateExposure`` is warped if doWarping,
612 ``templateExposure`` is convolved
613 - if `False`, ``templateExposure`` is warped if doWarping,
614 ``scienceExposure is`` convolved
618 result : `lsst.pipe.base.Struct`
619 An `lsst.pipe.base.Struct` containing these fields:
621 - ``subtractedExposure`` : subtracted Exposure
622 scienceExposure - (matchedImage + backgroundModel)
623 - ``matchedImage`` : ``templateExposure`` after warping to match
624 ``templateExposure`` (if doWarping true),
625 and convolving with psfMatchingKernel
626 - ``psfMatchingKernel`` : PSF matching kernel
627 - ``backgroundModel`` : differential background model
628 - ``kernelCellSet`` : SpatialCellSet used to determine PSF matching kernel
631 templateExposure=templateExposure,
632 scienceExposure=scienceExposure,
633 templateFwhmPix=templateFwhmPix,
634 scienceFwhmPix=scienceFwhmPix,
635 candidateList=candidateList,
637 convolveTemplate=convolveTemplate
640 subtractedExposure = afwImage.ExposureF(scienceExposure, deep=
True)
646 subtractedMaskedImage = subtractedExposure.maskedImage
647 subtractedMaskedImage -= results.matchedExposure.maskedImage
648 subtractedMaskedImage -= results.backgroundModel
650 subtractedMaskedImage = subtractedExposure.maskedImage
651 subtractedMaskedImage[:, :] = results.warpedExposure.maskedImage
652 subtractedMaskedImage -= results.matchedExposure.maskedImage
653 subtractedMaskedImage -= results.backgroundModel
656 subtractedMaskedImage *= -1
659 subtractedMaskedImage /= results.psfMatchingKernel.computeImage(
660 afwImage.ImageD(results.psfMatchingKernel.getDimensions()),
False)
662 subtractedExposure.setPsf(results.warpedExposure.getPsf())
668 if not maskTransparency:
671 afwDisplay.setDefaultMaskTransparency(maskTransparency)
672 if display
and displayDiffIm:
673 disp = afwDisplay.Display(frame=lsstDebug.frame)
674 disp.mtv(templateExposure, title=
"Template")
676 disp = afwDisplay.Display(frame=lsstDebug.frame)
677 disp.mtv(results.matchedExposure, title=
"Matched template")
679 disp = afwDisplay.Display(frame=lsstDebug.frame)
680 disp.mtv(scienceExposure, title=
"Science Image")
682 disp = afwDisplay.Display(frame=lsstDebug.frame)
683 disp.mtv(subtractedExposure, title=
"Difference Image")
686 results.subtractedExposure = subtractedExposure
691 templateFwhmPix=None, scienceFwhmPix=None):
692 """Psf-match and subtract two MaskedImages.
694 Do the following, in order:
696 - PSF-match templateMaskedImage to scienceMaskedImage
697 - Determine the differential background
698 - Return the difference: scienceMaskedImage
699 ((warped templateMaskedImage convolved with psfMatchingKernel) + backgroundModel)
703 templateMaskedImage : `lsst.afw.image.MaskedImage`
704 MaskedImage to PSF-match to ``scienceMaskedImage``
705 scienceMaskedImage : `lsst.afw.image.MaskedImage`
706 Reference MaskedImage
707 templateFwhmPix : `float`
708 FWHM (in pixels) of the Psf in the template image (image to convolve)
709 scienceFwhmPix : `float`
710 FWHM (in pixels) of the Psf in the science image
711 candidateList : `list`, optional
712 A list of footprints/maskedImages for kernel candidates;
713 if `None` then source detection is run.
715 - Currently supported: list of Footprints or measAlg.PsfCandidateF
719 results : `lsst.pipe.base.Struct`
720 An `lsst.pipe.base.Struct` containing these fields:
722 - ``subtractedMaskedImage`` : ``scienceMaskedImage`` - (matchedImage + backgroundModel)
723 - ``matchedImage`` : templateMaskedImage convolved with psfMatchingKernel
724 - `psfMatchingKernel`` : PSF matching kernel
725 - ``backgroundModel`` : differential background model
726 - ``kernelCellSet`` : SpatialCellSet used to determine PSF matching kernel
729 if not candidateList:
730 raise RuntimeError(
"Candidate list must be populated by makeCandidateList")
733 templateMaskedImage=templateMaskedImage,
734 scienceMaskedImage=scienceMaskedImage,
735 candidateList=candidateList,
736 templateFwhmPix=templateFwhmPix,
737 scienceFwhmPix=scienceFwhmPix,
740 subtractedMaskedImage = afwImage.MaskedImageF(scienceMaskedImage,
True)
741 subtractedMaskedImage -= results.matchedImage
742 subtractedMaskedImage -= results.backgroundModel
743 results.subtractedMaskedImage = subtractedMaskedImage
749 if not maskTransparency:
752 afwDisplay.setDefaultMaskTransparency(maskTransparency)
753 if display
and displayDiffIm:
754 disp = afwDisplay.Display(frame=lsstDebug.frame)
755 disp.mtv(subtractedMaskedImage, title=
"Subtracted masked image")
761 """Get sources to use for Psf-matching.
763 This method runs detection and measurement on an exposure.
764 The returned set of sources will be used as candidates for
769 exposure : `lsst.afw.image.Exposure`
770 Exposure on which to run detection/measurement
774 Whether or not to smooth the Exposure with Psf before detection
776 Factory for the generation of Source ids
781 source catalog containing candidates for the Psf-matching
784 table = afwTable.SourceTable.make(self.
selectSchemaselectSchema, idFactory)
786 table = afwTable.SourceTable.make(self.
selectSchemaselectSchema)
787 mi = exposure.getMaskedImage()
789 imArr = mi.getImage().getArray()
790 maskArr = mi.getMask().getArray()
791 miArr = np.ma.masked_array(imArr, mask=maskArr)
793 fitBg = self.
backgroundbackground.fitBackground(mi)
794 bkgd = fitBg.getImageF(self.
backgroundbackground.config.algorithm,
795 self.
backgroundbackground.config.undersampleStyle)
797 self.log.
warning(
"Failed to get background model. Falling back to median background estimation")
798 bkgd = np.ma.median(miArr)
804 detRet = self.selectDetection.
run(
810 selectSources = detRet.sources
811 self.selectMeasurement.
run(measCat=selectSources, exposure=exposure)
819 """Make a list of acceptable KernelCandidates.
821 Accept or generate a list of candidate sources for
822 Psf-matching, and examine the Mask planes in both of the
823 images for indications of bad pixels
827 templateExposure : `lsst.afw.image.Exposure`
828 Exposure that will be convolved
829 scienceExposure : `lsst.afw.image.Exposure`
830 Exposure that will be matched-to
832 Dimensions of the Psf-matching Kernel, used to grow detection footprints
833 candidateList : `list`, optional
834 List of Sources to examine. Elements must be of type afw.table.Source
835 or a type that wraps a Source and has a getSource() method, such as
836 meas.algorithms.PsfCandidateF.
840 candidateList : `list` of `dict`
841 A list of dicts having a "source" and "footprint"
842 field for the Sources deemed to be appropriate for Psf
845 if candidateList
is None:
848 if len(candidateList) < 1:
849 raise RuntimeError(
"No candidates in candidateList")
851 listTypes =
set(
type(x)
for x
in candidateList)
852 if len(listTypes) > 1:
853 raise RuntimeError(
"Candidate list contains mixed types: %s" % [t
for t
in listTypes])
857 candidateList[0].getSource()
858 except Exception
as e:
859 raise RuntimeError(f
"Candidate List is of type: {type(candidateList[0])} "
860 "Can only make candidate list from list of afwTable.SourceRecords, "
861 f
"measAlg.PsfCandidateF or other type with a getSource() method: {e}")
862 candidateList = [c.getSource()
for c
in candidateList]
864 candidateList = diffimTools.sourceToFootprintList(candidateList,
865 templateExposure, scienceExposure,
869 if len(candidateList) == 0:
870 raise RuntimeError(
"Cannot find any objects suitable for KernelCandidacy")
875 basisDegGauss=None, basisSigmaGauss=None, metadata=None):
876 """Wrapper to set log messages for
877 `lsst.ip.diffim.makeKernelBasisList`.
881 targetFwhmPix : `float`, optional
882 Passed on to `lsst.ip.diffim.generateAlardLuptonBasisList`.
883 Not used for delta function basis sets.
884 referenceFwhmPix : `float`, optional
885 Passed on to `lsst.ip.diffim.generateAlardLuptonBasisList`.
886 Not used for delta function basis sets.
887 basisDegGauss : `list` of `int`, optional
888 Passed on to `lsst.ip.diffim.generateAlardLuptonBasisList`.
889 Not used for delta function basis sets.
890 basisSigmaGauss : `list` of `int`, optional
891 Passed on to `lsst.ip.diffim.generateAlardLuptonBasisList`.
892 Not used for delta function basis sets.
893 metadata : `lsst.daf.base.PropertySet`, optional
894 Passed on to `lsst.ip.diffim.generateAlardLuptonBasisList`.
895 Not used for delta function basis sets.
899 basisList: `list` of `lsst.afw.math.kernel.FixedKernel`
900 List of basis kernels.
903 targetFwhmPix=targetFwhmPix,
904 referenceFwhmPix=referenceFwhmPix,
905 basisDegGauss=basisDegGauss,
906 basisSigmaGauss=basisSigmaGauss,
908 if targetFwhmPix == referenceFwhmPix:
909 self.log.
info(
"Target and reference psf fwhms are equal, falling back to config values")
910 elif referenceFwhmPix > targetFwhmPix:
911 self.log.
info(
"Reference psf fwhm is the greater, normal convolution mode")
913 self.log.
info(
"Target psf fwhm is the greater, deconvolution mode")
917 def _adaptCellSize(self, candidateList):
918 """NOT IMPLEMENTED YET.
922 def _buildCellSet(self, templateMaskedImage, scienceMaskedImage, candidateList):
923 """Build a SpatialCellSet for use with the solve method.
927 templateMaskedImage : `lsst.afw.image.MaskedImage`
928 MaskedImage to PSF-matched to scienceMaskedImage
929 scienceMaskedImage : `lsst.afw.image.MaskedImage`
930 Reference MaskedImage
931 candidateList : `list`
932 A list of footprints/maskedImages for kernel candidates;
934 - Currently supported: list of Footprints or measAlg.PsfCandidateF
938 kernelCellSet : `lsst.afw.math.SpatialCellSet`
939 a SpatialCellSet for use with self._solve
941 if not candidateList:
942 raise RuntimeError(
"Candidate list must be populated by makeCandidateList")
944 sizeCellX, sizeCellY = self.
_adaptCellSize_adaptCellSize(candidateList)
948 sizeCellX, sizeCellY)
952 for cand
in candidateList:
954 bbox = cand.getBBox()
956 bbox = cand[
'footprint'].getBBox()
957 tmi = afwImage.MaskedImageF(templateMaskedImage, bbox)
958 smi = afwImage.MaskedImageF(scienceMaskedImage, bbox)
962 cand = cand[
'source']
963 xPos = cand.getCentroid()[0]
964 yPos = cand.getCentroid()[1]
965 cand = diffimLib.makeKernelCandidate(xPos, yPos, tmi, smi, ps)
967 self.log.
debug(
"Candidate %d at %f, %f", cand.getId(), cand.getXCenter(), cand.getYCenter())
968 kernelCellSet.insertCandidate(cand)
972 def _validateSize(self, templateMaskedImage, scienceMaskedImage):
973 """Return True if two image-like objects are the same size.
975 return templateMaskedImage.getDimensions() == scienceMaskedImage.getDimensions()
977 def _validateWcs(self, templateExposure, scienceExposure):
978 """Return True if the WCS of the two Exposures have the same origin and extent.
980 templateWcs = templateExposure.getWcs()
981 scienceWcs = scienceExposure.getWcs()
982 templateBBox = templateExposure.getBBox()
983 scienceBBox = scienceExposure.getBBox()
986 templateOrigin = templateWcs.pixelToSky(
geom.Point2D(templateBBox.getBegin()))
987 scienceOrigin = scienceWcs.pixelToSky(
geom.Point2D(scienceBBox.getBegin()))
990 templateLimit = templateWcs.pixelToSky(
geom.Point2D(templateBBox.getEnd()))
991 scienceLimit = scienceWcs.pixelToSky(
geom.Point2D(scienceBBox.getEnd()))
993 self.log.
info(
"Template Wcs : %f,%f -> %f,%f",
994 templateOrigin[0], templateOrigin[1],
995 templateLimit[0], templateLimit[1])
996 self.log.
info(
"Science Wcs : %f,%f -> %f,%f",
997 scienceOrigin[0], scienceOrigin[1],
998 scienceLimit[0], scienceLimit[1])
1000 templateBBox =
geom.Box2D(templateOrigin.getPosition(geom.degrees),
1001 templateLimit.getPosition(geom.degrees))
1002 scienceBBox =
geom.Box2D(scienceOrigin.getPosition(geom.degrees),
1003 scienceLimit.getPosition(geom.degrees))
1004 if not (templateBBox.overlaps(scienceBBox)):
1005 raise RuntimeError(
"Input images do not overlap at all")
1007 if ((templateOrigin != scienceOrigin)
1008 or (templateLimit != scienceLimit)
1009 or (templateExposure.getDimensions() != scienceExposure.getDimensions())):
1014 subtractAlgorithmRegistry = pexConfig.makeRegistry(
1015 doc=
"A registry of subtraction algorithms for use as a subtask in imageDifference",
1018 subtractAlgorithmRegistry.register(
'al', ImagePsfMatchTask)
Parameters to control convolution.
A collection of SpatialCells covering an entire image.
Record class that contains measurements made on a single exposure.
Class for storing ordered metadata with comments.
A floating-point coordinate rectangle geometry.
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)
A Psf class that maps an arbitrary Psf through a coordinate transformation.
daf::base::PropertySet * set
std::shared_ptr< TransformPoint2ToPoint2 > makeWcsPairTransform(SkyWcs const &src, SkyWcs const &dst)
A Transform obtained by putting two SkyWcs objects "back to back".
Backwards-compatibility support for depersisting the old Calib (FluxMag0/FluxMag0Err) objects.
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 >())
A function to return an Exposure of the correct type (cf.
void convolve(OutImageT &convolvedImage, InImageT const &inImage, KernelT const &kernel, ConvolutionControl const &convolutionControl=ConvolutionControl())
Convolve an Image or MaskedImage with a Kernel, setting pixels of an existing output image.
int warpExposure(DestExposureT &destExposure, SrcExposureT const &srcExposure, WarpingControl const &control, typename DestExposureT::MaskedImageT::SinglePixel padValue=lsst::afw::math::edgePixel< typename DestExposureT::MaskedImageT >(typename lsst::afw::image::detail::image_traits< typename DestExposureT::MaskedImageT >::image_category()))
Warp (remap) one exposure to another.
def run(self, coaddExposures, bbox, wcs)
Fit spatial kernel using approximate fluxes for candidates, and solving a linear system of equations.