21 from __future__
import absolute_import, division, print_function
25 import lsst.daf.base
as dafBase
26 import lsst.pex.config
as pexConfig
27 import lsst.afw.image
as afwImage
28 import lsst.afw.math
as afwMath
29 import lsst.afw.geom
as afwGeom
30 import lsst.afw.table
as afwTable
31 import lsst.pipe.base
as pipeBase
32 from lsst.meas.algorithms
import SourceDetectionTask, SubtractBackgroundTask
33 from lsst.meas.base
import SingleFrameMeasurementTask
34 from .makeKernelBasisList
import makeKernelBasisList
35 from .psfMatch
import PsfMatchTask, PsfMatchConfigDF, PsfMatchConfigAL
36 from .
import utils
as diUtils
37 from .
import diffimLib
38 from .
import diffimTools
39 import lsst.afw.display.ds9
as ds9
41 sigma2fwhm = 2. * np.sqrt(2. * np.log(2.))
45 """!Configuration for image-to-image Psf matching"""
46 kernel = pexConfig.ConfigChoiceField(
54 selectDetection = pexConfig.ConfigurableField(
55 target=SourceDetectionTask,
56 doc=
"Initial detections used to feed stars to kernel fitting",
58 selectMeasurement = pexConfig.ConfigurableField(
59 target=SingleFrameMeasurementTask,
60 doc=
"Initial measurements used to feed stars to kernel fitting",
65 self.selectDetection.reEstimateBackground =
False
66 self.selectDetection.thresholdValue = 10.0
69 self.selectMeasurement.algorithms.names.clear()
70 self.selectMeasurement.algorithms.names = (
'base_SdssCentroid',
'base_PsfFlux',
'base_PixelFlags',
71 'base_SdssShape',
'base_GaussianFlux',
'base_SkyCoord')
72 self.selectMeasurement.slots.modelFlux =
None
73 self.selectMeasurement.slots.apFlux =
None
74 self.selectMeasurement.slots.calibFlux =
None
86 \anchor ImagePsfMatchTask_
88 \brief Psf-match two MaskedImages or Exposures using the sources in the images
90 \section ip_diffim_imagepsfmatch_Contents Contents
92 - \ref ip_diffim_imagepsfmatch_Purpose
93 - \ref ip_diffim_imagepsfmatch_Initialize
94 - \ref ip_diffim_imagepsfmatch_IO
95 - \ref ip_diffim_imagepsfmatch_Config
96 - \ref ip_diffim_imagepsfmatch_Metadata
97 - \ref ip_diffim_imagepsfmatch_Debug
98 - \ref ip_diffim_imagepsfmatch_Example
100 #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
102 \section ip_diffim_imagepsfmatch_Purpose Description
104 Build a Psf-matching kernel using two input images, either as MaskedImages (in which case they need
105 to be astrometrically aligned) or Exposures (in which case astrometric alignment will happen by
106 default but may be turned off). This requires a list of input Sources which may be provided
107 by the calling Task; if not, the Task will perform a coarse source detection and selection for this purpose.
108 Sources are vetted for signal-to-noise and masked pixels (in both the template and science image), and
109 substamps around each acceptable source are extracted and used to create an instance of KernelCandidate.
110 Each KernelCandidate is then placed within a lsst.afw.math.SpatialCellSet, which is used by an ensemble of
111 lsst.afw.math.CandidateVisitor instances to build the Psf-matching kernel. These visitors include, in
112 the order that they are called: BuildSingleKernelVisitor, KernelSumVisitor, BuildSpatialKernelVisitor,
113 and AssessSpatialKernelVisitor.
115 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, 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. Objects that are significantly above or below the mean,
132 typically due to variability or sources that are saturated in one image but not the other, are also rejected.
133 This threshold is defined by PsfMatchConfig.maxKsumSigma. Finally, a spatial model is built using all
134 currently-acceptable candidates, and the spatial model used to derive a second set of (spatial) residuals
135 which are again used to reject bad candidates, using the same thresholds as above.
137 #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
139 \section ip_diffim_imagepsfmatch_Initialize Task initialization
141 \copydoc \_\_init\_\_
143 #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
145 \section ip_diffim_imagepsfmatch_IO Invoking the Task
147 There is no run() method for this Task. Instead there are 4 methods that
148 may be used to invoke the Psf-matching. These are
149 \link lsst.ip.diffim.imagePsfMatch.ImagePsfMatchTask.matchMaskedImages matchMaskedImages\endlink,
150 \link lsst.ip.diffim.imagePsfMatch.ImagePsfMatchTask.subtractMaskedImages subtractMaskedImages\endlink,
151 \link lsst.ip.diffim.imagePsfMatch.ImagePsfMatchTask.matchExposures matchExposures\endlink, and
152 \link lsst.ip.diffim.imagePsfMatch.ImagePsfMatchTask.subtractExposures subtractExposures\endlink.
154 The methods that operate on lsst.afw.image.MaskedImage require that the images already be astrometrically
155 aligned, and are the same shape. The methods that operate on lsst.afw.image.Exposure allow for the
156 input images to be misregistered and potentially be different sizes; by default a
157 lsst.afw.math.LanczosWarpingKernel is used to perform the astrometric alignment. The methods
158 that "match" images return a Psf-matched image, while the methods that "subtract" images
159 return a Psf-matched and template subtracted image.
161 See each method's returned lsst.pipe.base.Struct for more details.
163 #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
165 \section ip_diffim_imagepsfmatch_Config Configuration parameters
167 See \ref ImagePsfMatchConfig
169 #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
171 \section ip_diffim_imagepsfmatch_Metadata Quantities set in Metadata
173 See \ref ip_diffim_psfmatch_Metadata "PsfMatchTask"
175 #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
177 \section ip_diffim_imagepsfmatch_Debug Debug variables
179 The \link lsst.pipe.base.cmdLineTask.CmdLineTask command line task\endlink interface supports a
180 flag \c -d/--debug to import \b debug.py from your \c PYTHONPATH. The relevant contents of debug.py
181 for this Task include:
187 di = lsstDebug.getInfo(name)
188 if name == "lsst.ip.diffim.psfMatch":
189 di.display = True # enable debug output
190 di.maskTransparency = 80 # ds9 mask transparency
191 di.displayCandidates = True # show all the candidates and residuals
192 di.displayKernelBasis = False # show kernel basis functions
193 di.displayKernelMosaic = True # show kernel realized across the image
194 di.plotKernelSpatialModel = False # show coefficients of spatial model
195 di.showBadCandidates = True # show the bad candidates (red) along with good (green)
196 elif name == "lsst.ip.diffim.imagePsfMatch":
197 di.display = True # enable debug output
198 di.maskTransparency = 30 # ds9 mask transparency
199 di.displayTemplate = True # show full (remapped) template
200 di.displaySciIm = True # show science image to match to
201 di.displaySpatialCells = True # show spatial cells
202 di.displayDiffIm = True # show difference image
203 di.showBadCandidates = True # show the bad candidates (red) along with good (green)
204 elif name == "lsst.ip.diffim.diaCatalogSourceSelector":
205 di.display = False # enable debug output
206 di.maskTransparency = 30 # ds9 mask transparency
207 di.displayExposure = True # show exposure with candidates indicated
208 di.pauseAtEnd = False # pause when done
210 lsstDebug.Info = DebugInfo
214 Note that if you want addional logging info, you may add to your scripts:
216 import lsst.log.utils as logUtils
217 logUtils.traceSetAt("ip.diffim", 4)
220 #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
222 \section ip_diffim_imagepsfmatch_Example A complete example of using ImagePsfMatchTask
224 This code is imagePsfMatchTask.py in the examples directory, and can be run as \em e.g.
226 examples/imagePsfMatchTask.py --debug
227 examples/imagePsfMatchTask.py --debug --mode="matchExposures"
228 examples/imagePsfMatchTask.py --debug --template /path/to/templateExp.fits --science /path/to/scienceExp.fits
231 \dontinclude imagePsfMatchTask.py
232 Create a subclass of ImagePsfMatchTask that allows us to either match exposures, or subtract exposures:
233 \skip MyImagePsfMatchTask
234 @until self.subtractExposures
236 And allow the user the freedom to either run the script in default mode, or point to their own images on disk.
237 Note that these images must be readable as an lsst.afw.image.Exposure:
241 We have enabled some minor display debugging in this script via the --debug option. However, if you
242 have an lsstDebug debug.py in your PYTHONPATH you will get additional debugging displays. The following
243 block checks for this script:
247 \dontinclude imagePsfMatchTask.py
248 Finally, we call a run method that we define below. First set up a Config and modify some of the parameters.
249 E.g. use an "Alard-Lupton" sum-of-Gaussian basis, fit for a differential background, and use low order spatial
250 variation in the kernel and background:
252 @until spatialBgOrder
254 Make sure the images (if any) that were sent to the script exist on disk and are readable. If no images
255 are sent, make some fake data up for the sake of this example script (have a look at the code if you want
256 more details on generateFakeImages):
260 Create and run the Task:
264 And finally provide some optional debugging displays:
266 @until result.subtractedExposure
267 #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
270 ConfigClass = ImagePsfMatchConfig
273 """!Create the ImagePsfMatchTask
275 \param *args arguments to be passed to lsst.ip.diffim.PsfMatchTask.__init__
276 \param **kwargs keyword arguments to be passed to lsst.ip.diffim.PsfMatchTask.__init__
278 Upon initialization, the kernel configuration is defined by self.config.kernel.active.
279 The task creates an lsst.afw.math.Warper from the subConfig self.config.kernel.active.warpingConfig.
280 A schema for the selection and measurement of candidate lsst.ip.diffim.KernelCandidates is
281 defined, and used to initize subTasks selectDetection (for candidate detection) and selectMeasurement
282 (for candidate measurement).
284 PsfMatchTask.__init__(self, *args, **kwargs)
286 self.
_warper = afwMath.Warper.fromConfig(self.kConfig.warpingConfig)
289 self.
background = SubtractBackgroundTask(config=self.kConfig.afwBackgroundConfig, name=
"background",
293 self.makeSubtask(
"selectDetection", schema=self.
selectSchema)
297 """!Return the FWHM in pixels of a Psf"""
298 sigPix = psf.computeShape().getDeterminantRadius()
299 return sigPix * sigma2fwhm
303 templateFwhmPix=
None, scienceFwhmPix=
None,
304 candidateList=
None, doWarping=
True, convolveTemplate=
True):
305 """!Warp and PSF-match an exposure to the reference
307 Do the following, in order:
308 - Warp templateExposure to match scienceExposure,
309 if doWarping True and their WCSs do not already match
310 - Determine a PSF matching kernel and differential background model
311 that matches templateExposure to scienceExposure
312 - Convolve templateExposure by PSF matching kernel
314 @param templateExposure: Exposure to warp and PSF-match to the reference masked image
315 @param scienceExposure: Exposure whose WCS and PSF are to be matched to
316 @param templateFwhmPix: FWHM (in pixels) of the Psf in the template image (image to convolve)
317 @param scienceFwhmPix: FWHM (in pixels) of the Psf in the science image
318 @param candidateList: a list of footprints/maskedImages for kernel candidates;
319 if None then source detection is run.
320 - Currently supported: list of Footprints or measAlg.PsfCandidateF
321 @param doWarping: what to do if templateExposure's and scienceExposure's WCSs do not match:
322 - if True then warp templateExposure to match scienceExposure
323 - if False then raise an Exception
324 @param convolveTemplate: convolve the template image or the science image
325 - if True, templateExposure is warped if doWarping, templateExposure is convolved
326 - if False, templateExposure is warped if doWarping, scienceExposure is convolved
328 @return a pipeBase.Struct containing these fields:
329 - matchedImage: the PSF-matched exposure =
330 warped templateExposure convolved by psfMatchingKernel. This has:
331 - the same parent bbox, Wcs and Calib as scienceExposure
332 - the same filter as templateExposure
333 - no Psf (because the PSF-matching process does not compute one)
334 - psfMatchingKernel: the PSF matching kernel
335 - backgroundModel: differential background model
336 - kernelCellSet: SpatialCellSet used to solve for the PSF matching kernel
338 Raise a RuntimeError if doWarping is False and templateExposure's and scienceExposure's
341 if not self.
_validateWcs(templateExposure, scienceExposure):
343 self.log.info(
"Astrometrically registering template to science image")
344 templatePsf = templateExposure.getPsf()
345 templateExposure = self._warper.warpExposure(scienceExposure.getWcs(),
347 destBBox=scienceExposure.getBBox())
348 templateExposure.setPsf(templatePsf)
350 self.log.error(
"ERROR: Input images not registered")
351 raise RuntimeError(
"Input images not registered")
353 if templateFwhmPix
is None:
354 if not templateExposure.hasPsf():
355 self.log.warn(
"No estimate of Psf FWHM for template image")
357 templateFwhmPix = self.
getFwhmPix(templateExposure.getPsf())
358 self.log.info(
"templateFwhmPix: {}".format(templateFwhmPix))
360 if scienceFwhmPix
is None:
361 if not scienceExposure.hasPsf():
362 self.log.warn(
"No estimate of Psf FWHM for science image")
364 scienceFwhmPix = self.
getFwhmPix(scienceExposure.getPsf())
365 self.log.info(
"scienceFwhmPix: {}".format(scienceFwhmPix))
368 candidateList = self.
makeCandidateList(templateExposure, scienceExposure, kernelSize, candidateList)
372 templateExposure.getMaskedImage(), scienceExposure.getMaskedImage(), candidateList,
373 templateFwhmPix=templateFwhmPix, scienceFwhmPix=scienceFwhmPix)
376 scienceExposure.getMaskedImage(), templateExposure.getMaskedImage(), candidateList,
377 templateFwhmPix=scienceFwhmPix, scienceFwhmPix=templateFwhmPix)
379 psfMatchedExposure = afwImage.makeExposure(results.matchedImage, scienceExposure.getWcs())
380 psfMatchedExposure.setFilter(templateExposure.getFilter())
381 psfMatchedExposure.setCalib(scienceExposure.getCalib())
382 results.warpedExposure = templateExposure
383 results.matchedExposure = psfMatchedExposure
387 def matchMaskedImages(self, templateMaskedImage, scienceMaskedImage, candidateList,
388 templateFwhmPix=
None, scienceFwhmPix=
None):
389 """!PSF-match a MaskedImage (templateMaskedImage) to a reference MaskedImage (scienceMaskedImage)
391 Do the following, in order:
392 - Determine a PSF matching kernel and differential background model
393 that matches templateMaskedImage to scienceMaskedImage
394 - Convolve templateMaskedImage by the PSF matching kernel
396 @param templateMaskedImage: masked image to PSF-match to the reference masked image;
397 must be warped to match the reference masked image
398 @param scienceMaskedImage: maskedImage whose PSF is to be matched to
399 @param templateFwhmPix: FWHM (in pixels) of the Psf in the template image (image to convolve)
400 @param scienceFwhmPix: FWHM (in pixels) of the Psf in the science image
401 @param candidateList: a list of footprints/maskedImages for kernel candidates;
402 if None then source detection is run.
403 - Currently supported: list of Footprints or measAlg.PsfCandidateF
405 @return a pipeBase.Struct containing these fields:
406 - psfMatchedMaskedImage: the PSF-matched masked image =
407 templateMaskedImage convolved with psfMatchingKernel.
408 This has the same xy0, dimensions and wcs as scienceMaskedImage.
409 - psfMatchingKernel: the PSF matching kernel
410 - backgroundModel: differential background model
411 - kernelCellSet: SpatialCellSet used to solve for the PSF matching kernel
413 Raise a RuntimeError if input images have different dimensions
417 display = lsstDebug.Info(__name__).display
418 displayTemplate = lsstDebug.Info(__name__).displayTemplate
419 displaySciIm = lsstDebug.Info(__name__).displaySciIm
420 displaySpatialCells = lsstDebug.Info(__name__).displaySpatialCells
421 maskTransparency = lsstDebug.Info(__name__).maskTransparency
422 if not maskTransparency:
425 ds9.setMaskTransparency(maskTransparency)
427 if not candidateList:
428 raise RuntimeError(
"Candidate list must be populated by makeCandidateList")
430 if not self.
_validateSize(templateMaskedImage, scienceMaskedImage):
431 self.log.error(
"ERROR: Input images different size")
432 raise RuntimeError(
"Input images different size")
434 if display
and displayTemplate:
435 ds9.mtv(templateMaskedImage, frame=lsstDebug.frame, title=
"Image to convolve")
438 if display
and displaySciIm:
439 ds9.mtv(scienceMaskedImage, frame=lsstDebug.frame, title=
"Image to not convolve")
446 if display
and displaySpatialCells:
447 diUtils.showKernelSpatialCells(scienceMaskedImage, kernelCellSet,
448 symb=
"o", ctype=ds9.CYAN, ctypeUnused=ds9.YELLOW, ctypeBad=ds9.RED,
449 size=4, frame=lsstDebug.frame, title=
"Image to not convolve")
452 if templateFwhmPix
and scienceFwhmPix:
453 self.log.info(
"Matching Psf FWHM %.2f -> %.2f pix", templateFwhmPix, scienceFwhmPix)
455 if self.kConfig.useBicForKernelBasis:
460 bicDegrees = nbe(tmpKernelCellSet, self.log)
462 alardDegGauss=bicDegrees[0], metadata=self.metadata)
466 metadata=self.metadata)
468 spatialSolution, psfMatchingKernel, backgroundModel = self._solve(kernelCellSet, basisList)
470 psfMatchedMaskedImage = afwImage.MaskedImageF(templateMaskedImage.getBBox())
472 afwMath.convolve(psfMatchedMaskedImage, templateMaskedImage, psfMatchingKernel, doNormalize)
473 return pipeBase.Struct(
474 matchedImage=psfMatchedMaskedImage,
475 psfMatchingKernel=psfMatchingKernel,
476 backgroundModel=backgroundModel,
477 kernelCellSet=kernelCellSet,
482 templateFwhmPix=
None, scienceFwhmPix=
None,
483 candidateList=
None, doWarping=
True, convolveTemplate=
True):
484 """!Register, Psf-match and subtract two Exposures
486 Do the following, in order:
487 - Warp templateExposure to match scienceExposure, if their WCSs do not already match
488 - Determine a PSF matching kernel and differential background model
489 that matches templateExposure to scienceExposure
490 - PSF-match templateExposure to scienceExposure
491 - Compute subtracted exposure (see return values for equation).
493 @param templateExposure: exposure to PSF-match to scienceExposure
494 @param scienceExposure: reference Exposure
495 @param templateFwhmPix: FWHM (in pixels) of the Psf in the template image (image to convolve)
496 @param scienceFwhmPix: FWHM (in pixels) of the Psf in the science image
497 @param candidateList: a list of footprints/maskedImages for kernel candidates;
498 if None then source detection is run.
499 - Currently supported: list of Footprints or measAlg.PsfCandidateF
500 @param doWarping: what to do if templateExposure's and scienceExposure's WCSs do not match:
501 - if True then warp templateExposure to match scienceExposure
502 - if False then raise an Exception
503 @param convolveTemplate: convolve the template image or the science image
504 - if True, templateExposure is warped if doWarping, templateExposure is convolved
505 - if False, templateExposure is warped if doWarping, scienceExposure is convolved
507 @return a pipeBase.Struct containing these fields:
508 - subtractedExposure: subtracted Exposure = scienceExposure - (matchedImage + backgroundModel)
509 - matchedImage: templateExposure after warping to match templateExposure (if doWarping true),
510 and convolving with psfMatchingKernel
511 - psfMatchingKernel: PSF matching kernel
512 - backgroundModel: differential background model
513 - kernelCellSet: SpatialCellSet used to determine PSF matching kernel
516 templateExposure=templateExposure,
517 scienceExposure=scienceExposure,
518 templateFwhmPix=templateFwhmPix,
519 scienceFwhmPix=scienceFwhmPix,
520 candidateList=candidateList,
522 convolveTemplate=convolveTemplate
525 subtractedExposure = afwImage.ExposureF(scienceExposure,
True)
527 subtractedMaskedImage = subtractedExposure.getMaskedImage()
528 subtractedMaskedImage -= results.matchedExposure.getMaskedImage()
529 subtractedMaskedImage -= results.backgroundModel
531 subtractedExposure.setMaskedImage(results.warpedExposure.getMaskedImage())
532 subtractedMaskedImage = subtractedExposure.getMaskedImage()
533 subtractedMaskedImage -= results.matchedExposure.getMaskedImage()
534 subtractedMaskedImage -= results.backgroundModel
537 subtractedMaskedImage *= -1
540 subtractedMaskedImage /= results.psfMatchingKernel.computeImage(
541 afwImage.ImageD(results.psfMatchingKernel.getDimensions()),
False)
544 display = lsstDebug.Info(__name__).display
545 displayDiffIm = lsstDebug.Info(__name__).displayDiffIm
546 maskTransparency = lsstDebug.Info(__name__).maskTransparency
547 if not maskTransparency:
550 ds9.setMaskTransparency(maskTransparency)
551 if display
and displayDiffIm:
552 ds9.mtv(templateExposure, frame=lsstDebug.frame, title=
"Template")
554 ds9.mtv(results.matchedExposure, frame=lsstDebug.frame, title=
"Matched template")
556 ds9.mtv(scienceExposure, frame=lsstDebug.frame, title=
"Science Image")
558 ds9.mtv(subtractedExposure, frame=lsstDebug.frame, title=
"Difference Image")
561 results.subtractedExposure = subtractedExposure
566 templateFwhmPix=
None, scienceFwhmPix=
None):
567 """!Psf-match and subtract two MaskedImages
569 Do the following, in order:
570 - PSF-match templateMaskedImage to scienceMaskedImage
571 - Determine the differential background
572 - Return the difference: scienceMaskedImage -
573 ((warped templateMaskedImage convolved with psfMatchingKernel) + backgroundModel)
575 @param templateMaskedImage: MaskedImage to PSF-match to scienceMaskedImage
576 @param scienceMaskedImage: reference MaskedImage
577 @param templateFwhmPix: FWHM (in pixels) of the Psf in the template image (image to convolve)
578 @param scienceFwhmPix: FWHM (in pixels) of the Psf in the science image
579 @param candidateList: a list of footprints/maskedImages for kernel candidates;
580 if None then source detection is run.
581 - Currently supported: list of Footprints or measAlg.PsfCandidateF
583 @return a pipeBase.Struct containing these fields:
584 - subtractedMaskedImage = scienceMaskedImage - (matchedImage + backgroundModel)
585 - matchedImage: templateMaskedImage convolved with psfMatchingKernel
586 - psfMatchingKernel: PSF matching kernel
587 - backgroundModel: differential background model
588 - kernelCellSet: SpatialCellSet used to determine PSF matching kernel
590 if not candidateList:
591 raise RuntimeError(
"Candidate list must be populated by makeCandidateList")
594 templateMaskedImage=templateMaskedImage,
595 scienceMaskedImage=scienceMaskedImage,
596 candidateList=candidateList,
597 templateFwhmPix=templateFwhmPix,
598 scienceFwhmPix=scienceFwhmPix,
601 subtractedMaskedImage = afwImage.MaskedImageF(scienceMaskedImage,
True)
602 subtractedMaskedImage -= results.matchedImage
603 subtractedMaskedImage -= results.backgroundModel
604 results.subtractedMaskedImage = subtractedMaskedImage
607 display = lsstDebug.Info(__name__).display
608 displayDiffIm = lsstDebug.Info(__name__).displayDiffIm
609 maskTransparency = lsstDebug.Info(__name__).maskTransparency
610 if not maskTransparency:
613 ds9.setMaskTransparency(maskTransparency)
614 if display
and displayDiffIm:
615 ds9.mtv(subtractedMaskedImage, frame=lsstDebug.frame)
621 """!Get sources to use for Psf-matching
623 This method runs detection and measurement on an exposure.
624 The returned set of sources will be used as candidates for
627 @param exposure: Exposure on which to run detection/measurement
628 @param sigma: Detection threshold
629 @param doSmooth: Whether or not to smooth the Exposure with Psf before detection
630 @param idFactory: Factory for the generation of Source ids
632 @return source catalog containing candidates for the Psf-matching
636 table = afwTable.SourceTable.make(self.
selectSchema, idFactory)
639 mi = exposure.getMaskedImage()
641 imArr = mi.getImage().getArray()
642 maskArr = mi.getMask().getArray()
643 miArr = np.ma.masked_array(imArr, mask=maskArr)
645 bkgd = self.background.fitBackground(mi).getImageF()
647 self.log.warn(
"Failed to get background model. Falling back to median background estimation")
648 bkgd = np.ma.extras.median(miArr)
654 detRet = self.selectDetection.makeSourceCatalog(
660 selectSources = detRet.sources
661 self.selectMeasurement.run(measCat=selectSources, exposure=exposure)
669 """!Make a list of acceptable KernelCandidates
671 Accept or generate a list of candidate sources for
672 Psf-matching, and examine the Mask planes in both of the
673 images for indications of bad pixels
675 @param templateExposure: Exposure that will be convolved
676 @param scienceExposure: Exposure that will be matched-to
677 @param kernelSize: Dimensions of the Psf-matching Kernel, used to grow detection footprints
678 @param candidateList: List of Sources to examine. Elements must be of type afw.table.Source
679 or a type that wraps a Source and has a getSource() method, such as
680 meas.algorithms.PsfCandidateF.
682 @return a list of dicts having a "source" and "footprint"
683 field for the Sources deemed to be appropriate for Psf
686 if candidateList
is None:
689 if len(candidateList) < 1:
690 raise RuntimeError(
"No candidates in candidateList")
692 listTypes = set(type(x)
for x
in candidateList)
693 if len(listTypes) > 1:
694 raise RuntimeError(
"Candidate list contains mixed types: %s" % [l
for l
in listTypes])
696 if not isinstance(candidateList[0], afwTable.SourceRecord):
698 candidateList[0].getSource()
699 except Exception
as e:
700 raise RuntimeError(
"Candidate List is of type: %s. " % (type(candidateList[0])) +
701 "Can only make candidate list from list of afwTable.SourceRecords, " +
702 "measAlg.PsfCandidateF or other type with a getSource() method: %s" % (e))
703 candidateList = [c.getSource()
for c
in candidateList]
705 candidateList = diffimTools.sourceToFootprintList(candidateList,
706 templateExposure, scienceExposure,
708 self.kConfig.detectionConfig,
710 if len(candidateList) == 0:
711 raise RuntimeError(
"Cannot find any objects suitable for KernelCandidacy")
716 """! NOT IMPLEMENTED YET"""
717 return self.kConfig.sizeCellX, self.kConfig.sizeCellY
719 def _buildCellSet(self, templateMaskedImage, scienceMaskedImage, candidateList):
720 """!Build a SpatialCellSet for use with the solve method
722 @param templateMaskedImage: MaskedImage to PSF-matched to scienceMaskedImage
723 @param scienceMaskedImage: reference MaskedImage
724 @param candidateList: a list of footprints/maskedImages for kernel candidates;
725 if None then source detection is run.
726 - Currently supported: list of Footprints or measAlg.PsfCandidateF
728 @return kernelCellSet: a SpatialCellSet for use with self._solve
730 if not candidateList:
731 raise RuntimeError(
"Candidate list must be populated by makeCandidateList")
736 kernelCellSet = afwMath.SpatialCellSet(templateMaskedImage.getBBox(),
737 sizeCellX, sizeCellY)
739 policy = pexConfig.makePolicy(self.
kConfig)
741 for cand
in candidateList:
742 bbox = cand[
'footprint'].getBBox()
744 tmi = afwImage.MaskedImageF(templateMaskedImage, bbox)
745 smi = afwImage.MaskedImageF(scienceMaskedImage, bbox)
746 cand = diffimLib.makeKernelCandidate(cand[
'source'], tmi, smi, policy)
748 self.log.debug(
"Candidate %d at %f, %f", cand.getId(), cand.getXCenter(), cand.getYCenter())
749 kernelCellSet.insertCandidate(cand)
754 """!Return True if two image-like objects are the same size
756 return templateMaskedImage.getDimensions() == scienceMaskedImage.getDimensions()
759 """!Return True if the WCS of the two Exposures have the same origin and extent
761 templateWcs = templateExposure.getWcs()
762 scienceWcs = scienceExposure.getWcs()
763 templateBBox = templateExposure.getBBox()
764 scienceBBox = scienceExposure.getBBox()
767 templateOrigin = templateWcs.pixelToSky(afwGeom.Point2D(templateBBox.getBegin()))
768 scienceOrigin = scienceWcs.pixelToSky(afwGeom.Point2D(scienceBBox.getBegin()))
771 templateLimit = templateWcs.pixelToSky(afwGeom.Point2D(templateBBox.getEnd()))
772 scienceLimit = scienceWcs.pixelToSky(afwGeom.Point2D(scienceBBox.getEnd()))
774 self.log.info(
"Template Wcs : %f,%f -> %f,%f",
775 templateOrigin[0], templateOrigin[1],
776 templateLimit[0], templateLimit[1])
777 self.log.info(
"Science Wcs : %f,%f -> %f,%f",
778 scienceOrigin[0], scienceOrigin[1],
779 scienceLimit[0], scienceLimit[1])
781 templateBBox = afwGeom.Box2D(templateOrigin.getPosition(), templateLimit.getPosition())
782 scienceBBox = afwGeom.Box2D(scienceOrigin.getPosition(), scienceLimit.getPosition())
783 if not (templateBBox.overlaps(scienceBBox)):
784 raise RuntimeError(
"Input images do not overlap at all")
786 if ((templateOrigin.getPosition() != scienceOrigin.getPosition())
or
787 (templateLimit.getPosition() != scienceLimit.getPosition())
or
788 (templateExposure.getDimensions() != scienceExposure.getDimensions())):
793 subtractAlgorithmRegistry = pexConfig.makeRegistry(
794 doc=
"A registry of subtraction algorithms for use as a subtask in imageDifference",
797 subtractAlgorithmRegistry.register(
'al', ImagePsfMatchTask)
def getFwhmPix
Return the FWHM in pixels of a Psf.
def getSelectSources
Get sources to use for Psf-matching.
def matchMaskedImages
PSF-match a MaskedImage (templateMaskedImage) to a reference MaskedImage (scienceMaskedImage) ...
Configuration for image-to-image Psf matching.
def subtractMaskedImages
Psf-match and subtract two MaskedImages.
def makeCandidateList
Make a list of acceptable KernelCandidates.
Psf-match two MaskedImages or Exposures using the sources in the images.
def _validateSize
Return True if two image-like objects are the same size.
def _buildCellSet
Build a SpatialCellSet for use with the solve method.
def _validateWcs
Return True if the WCS of the two Exposures have the same origin and extent.
def subtractExposures
Register, Psf-match and subtract two Exposures.
def _adaptCellSize
NOT IMPLEMENTED YET.
def matchExposures
Warp and PSF-match an exposure to the reference.
def __init__
Create the ImagePsfMatchTask.