22 __all__ = [
"ImagePsfMatchConfig",
"ImagePsfMatchTask",
"subtractAlgorithmRegistry"]
32 import lsst.pipe.base
as pipeBase
33 from lsst.meas.algorithms
import SourceDetectionTask, SubtractBackgroundTask
35 from .makeKernelBasisList
import makeKernelBasisList
36 from .psfMatch
import PsfMatchTask, PsfMatchConfigDF, PsfMatchConfigAL
37 from .
import utils
as dituils
38 from .
import diffimLib
39 from .
import diffimTools
42 sigma2fwhm = 2. * np.sqrt(2. * np.log(2.))
46 """Configuration for image-to-image Psf matching""" 47 kernel = pexConfig.ConfigChoiceField(
55 selectDetection = pexConfig.ConfigurableField(
56 target=SourceDetectionTask,
57 doc=
"Initial detections used to feed stars to kernel fitting",
59 selectMeasurement = pexConfig.ConfigurableField(
60 target=SingleFrameMeasurementTask,
61 doc=
"Initial measurements used to feed stars to kernel fitting",
71 self.
selectMeasurement.algorithms.names = (
'base_SdssCentroid',
'base_PsfFlux',
'base_PixelFlags',
72 'base_SdssShape',
'base_GaussianFlux',
'base_SkyCoord')
79 """Psf-match two MaskedImages or Exposures using the sources in the images 84 arguments to be passed to lsst.ip.diffim.PsfMatchTask.__init__ 86 keyword arguments to be passed to lsst.ip.diffim.PsfMatchTask.__init__ 90 Upon initialization, the kernel configuration is defined by self.config.kernel.active. 91 The task creates an lsst.afw.math.Warper from the subConfig self.config.kernel.active.warpingConfig. 92 A schema for the selection and measurement of candidate lsst.ip.diffim.KernelCandidates is 93 defined, and used to initize subTasks selectDetection (for candidate detection) and selectMeasurement 94 (for candidate measurement). 98 Build a Psf-matching kernel using two input images, either as MaskedImages (in which case they need 99 to be astrometrically aligned) or Exposures (in which case astrometric alignment will happen by 100 default but may be turned off). This requires a list of input Sources which may be provided 101 by the calling Task; if not, the Task will perform a coarse source detection 102 and selection for this purpose. Sources are vetted for signal-to-noise and masked pixels 103 (in both the template and science image), and substamps around each acceptable 104 source are extracted and used to create an instance of KernelCandidate. 105 Each KernelCandidate is then placed within a lsst.afw.math.SpatialCellSet, which is used by an ensemble of 106 lsst.afw.math.CandidateVisitor instances to build the Psf-matching kernel. These visitors include, in 107 the order that they are called: BuildSingleKernelVisitor, KernelSumVisitor, BuildSpatialKernelVisitor, 108 and AssessSpatialKernelVisitor. 110 Sigma clipping of KernelCandidates is performed as follows: 112 - BuildSingleKernelVisitor, using the substamp diffim residuals from the per-source kernel fit, 113 if PsfMatchConfig.singleKernelClipping is True 114 - KernelSumVisitor, using the mean and standard deviation of the kernel sum from all candidates, 115 if PsfMatchConfig.kernelSumClipping is True 116 - AssessSpatialKernelVisitor, using the substamp diffim ressiduals from the spatial kernel fit, 117 if PsfMatchConfig.spatialKernelClipping is True 119 The actual solving for the kernel (and differential background model) happens in 120 lsst.ip.diffim.PsfMatchTask._solve. This involves a loop over the SpatialCellSet that first builds the 121 per-candidate matching kernel for the requested number of KernelCandidates per cell 122 (PsfMatchConfig.nStarPerCell). The quality of this initial per-candidate difference image is examined, 123 using moments of the pixel residuals in the difference image normalized by the square root of the variance 124 (i.e. sigma); ideally this should follow a normal (0, 1) distribution, 125 but the rejection thresholds are set 126 by the config (PsfMatchConfig.candidateResidualMeanMax and PsfMatchConfig.candidateResidualStdMax). 127 All candidates that pass this initial build are then examined en masse to find the 128 mean/stdev of the kernel sums across all candidates. 129 Objects that are significantly above or below the mean, 130 typically due to variability or sources that are saturated in one image but not the other, 131 are also rejected.This threshold is defined by PsfMatchConfig.maxKsumSigma. 132 Finally, a spatial model is built using all currently-acceptable candidates, 133 and the spatial model used to derive a second set of (spatial) residuals 134 which are again used to reject bad candidates, using the same thresholds as above. 138 There is no run() method for this Task. Instead there are 4 methods that 139 may be used to invoke the Psf-matching. These are 140 lsst.ip.diffim.imagePsfMatch.ImagePsfMatchTask.matchMaskedImages matchMaskedImages 141 lsst.ip.diffim.imagePsfMatch.ImagePsfMatchTask.subtractMaskedImages subtractMaskedImages, 142 lsst.ip.diffim.imagePsfMatch.ImagePsfMatchTask.matchExposures matchExposures, and 143 lsst.ip.diffim.imagePsfMatch.ImagePsfMatchTask.subtractExposures subtractExposures. 145 The methods that operate on lsst.afw.image.MaskedImage require that the images already be astrometrically 146 aligned, and are the same shape. The methods that operate on lsst.afw.image.Exposure allow for the 147 input images to be misregistered and potentially be different sizes; by default a 148 lsst.afw.math.LanczosWarpingKernel is used to perform the astrometric alignment. The methods 149 that "match" images return a Psf-matched image, while the methods that "subtract" images 150 return a Psf-matched and template subtracted image. 152 See each method's returned lsst.pipe.base.Struct for more details. 156 The lsst.pipe.base.cmdLineTask.CmdLineTask command line task interface supports a 157 flag -d/--debug to import debug.py from your PYTHONPATH. The relevant contents of debug.py 158 for this Task include: 165 di = lsstDebug.getInfo(name) 166 if name == "lsst.ip.diffim.psfMatch": 167 di.display = True # enable debug output 168 di.maskTransparency = 80 # ds9 mask transparency 169 di.displayCandidates = True # show all the candidates and residuals 170 di.displayKernelBasis = False # show kernel basis functions 171 di.displayKernelMosaic = True # show kernel realized across the image 172 di.plotKernelSpatialModel = False # show coefficients of spatial model 173 di.showBadCandidates = True # show the bad candidates (red) along with good (green) 174 elif name == "lsst.ip.diffim.imagePsfMatch": 175 di.display = True # enable debug output 176 di.maskTransparency = 30 # ds9 mask transparency 177 di.displayTemplate = True # show full (remapped) template 178 di.displaySciIm = True # show science image to match to 179 di.displaySpatialCells = True # show spatial cells 180 di.displayDiffIm = True # show difference image 181 di.showBadCandidates = True # show the bad candidates (red) along with good (green) 182 elif name == "lsst.ip.diffim.diaCatalogSourceSelector": 183 di.display = False # enable debug output 184 di.maskTransparency = 30 # ds9 mask transparency 185 di.displayExposure = True # show exposure with candidates indicated 186 di.pauseAtEnd = False # pause when done 188 lsstDebug.Info = DebugInfo 191 Note that if you want addional logging info, you may add to your scripts: 195 import lsst.log.utils as logUtils 196 logUtils.traceSetAt("ip.diffim", 4) 200 A complete example of using ImagePsfMatchTask 202 This code is imagePsfMatchTask.py in the examples directory, and can be run as e.g. 206 examples/imagePsfMatchTask.py --debug 207 examples/imagePsfMatchTask.py --debug --mode="matchExposures" 208 examples/imagePsfMatchTask.py --debug --template /path/to/templateExp.fits 209 --science /path/to/scienceExp.fits 211 Create a subclass of ImagePsfMatchTask that allows us to either match exposures, or subtract exposures: 215 class MyImagePsfMatchTask(ImagePsfMatchTask): 217 def __init__(self, args, kwargs): 218 ImagePsfMatchTask.__init__(self, args, kwargs) 220 def run(self, templateExp, scienceExp, mode): 221 if mode == "matchExposures": 222 return self.matchExposures(templateExp, scienceExp) 223 elif mode == "subtractExposures": 224 return self.subtractExposures(templateExp, scienceExp) 226 And allow the user the freedom to either run the script in default mode, 227 or point to their own images on disk. 228 Note that these images must be readable as an lsst.afw.image.Exposure. 230 We have enabled some minor display debugging in this script via the --debug option. However, if you 231 have an lsstDebug debug.py in your PYTHONPATH you will get additional debugging displays. The following 232 block checks for this script: 239 # Since I am displaying 2 images here, set the starting frame number for the LSST debug LSST 240 debug.lsstDebug.frame = 3 241 except ImportError as e: 242 print(e, file=sys.stderr) 244 Finally, we call a run method that we define below. 245 First set up a Config and modify some of the parameters. 246 E.g. use an "Alard-Lupton" sum-of-Gaussian basis, 247 fit for a differential background, and use low order spatial 248 variation in the kernel and background: 254 # Create the Config and use sum of gaussian basis 256 config = ImagePsfMatchTask.ConfigClass() 257 config.kernel.name = "AL" 258 config.kernel.active.fitForBackground = True 259 config.kernel.active.spatialKernelOrder = 1 260 config.kernel.active.spatialBgOrder = 0 262 Make sure the images (if any) that were sent to the script exist on disk and are readable. If no images 263 are sent, make some fake data up for the sake of this example script (have a look at the code if you want 264 more details on generateFakeImages): 268 # Run the requested method of the Task 269 if args.template is not None and args.science is not None: 270 if not os.path.isfile(args.template): 271 raise Exception("Template image %s does not exist" % (args.template)) 272 if not os.path.isfile(args.science): 273 raise Exception("Science image %s does not exist" % (args.science)) 275 templateExp = afwImage.ExposureF(args.template) 276 except Exception as e: 277 raise Exception("Cannot read template image %s" % (args.template)) 279 scienceExp = afwImage.ExposureF(args.science) 280 except Exception as e: 281 raise Exception("Cannot read science image %s" % (args.science)) 283 templateExp, scienceExp = generateFakeImages() 284 config.kernel.active.sizeCellX = 128 285 config.kernel.active.sizeCellY = 128 287 Create and run the Task: 292 psfMatchTask = MyImagePsfMatchTask(config=config) 294 result = psfMatchTask.run(templateExp, scienceExp, args.mode) 296 And finally provide some optional debugging displays: 301 # See if the LSST debug has incremented the frame number; if not start with frame 3 303 frame = debug.lsstDebug.frame + 1 306 ds9.mtv(result.matchedExposure, frame=frame, title="Example script: Matched Template Image") 307 if "subtractedExposure" in result.getDict(): 308 ds9.mtv(result.subtractedExposure, frame=frame+1, title="Example script: Subtracted Image") 312 ConfigClass = ImagePsfMatchConfig
315 """Create the ImagePsfMatchTask 318 PsfMatchTask.__init__(self, *args, **kwargs)
320 self.
_warper = afwMath.Warper.fromConfig(self.
kConfig.warpingConfig)
323 self.
background = SubtractBackgroundTask(config=self.
kConfig.afwBackgroundConfig, name=
"background",
327 self.makeSubtask(
"selectDetection", schema=self.
selectSchema)
331 """Return the FWHM in pixels of a Psf""" 332 sigPix = psf.computeShape().getDeterminantRadius()
333 return sigPix * sigma2fwhm
337 templateFwhmPix=None, scienceFwhmPix=None,
338 candidateList=None, doWarping=True, convolveTemplate=True):
339 """Warp and PSF-match an exposure to the reference 341 Do the following, in order: 343 - Warp templateExposure to match scienceExposure, 344 if doWarping True and their WCSs do not already match 345 - Determine a PSF matching kernel and differential background model 346 that matches templateExposure to scienceExposure 347 - Convolve templateExposure by PSF matching kernel 351 templateExposure : `lsst.afw.image.Exposure` 352 Exposure to warp and PSF-match to the reference masked image 353 scienceExposure : `lsst.afw.image.Exposure` 354 Exposure whose WCS and PSF are to be matched to 355 templateFwhmPix :`float` 356 FWHM (in pixels) of the Psf in the template image (image to convolve) 357 scienceFwhmPix : `float` 358 FWHM (in pixels) of the Psf in the science image 359 candidateList : `list`, optional 360 a list of footprints/maskedImages for kernel candidates; 361 if None then source detection is run. 363 - Currently supported: list of Footprints or measAlg.PsfCandidateF 366 what to do if templateExposure's and scienceExposure's WCSs do not match: 368 - if True then warp templateExposure to match scienceExposure 369 - if False then raise an Exception 371 convolveTemplate : `bool` 372 convolve the template image or the science image: 374 - if True, templateExposure is warped if doWarping, templateExposure is convolved 375 - if False, templateExposure is warped if doWarping, scienceExposure is convolved 380 a `lsst.pipe.base.Struct` containing these fields: 382 - ``matchedImage`` : the PSF-matched exposure = 383 warped templateExposure convolved by psfMatchingKernel. This has: 385 - the same parent bbox, Wcs and Calib as scienceExposure 386 - the same filter as templateExposure 387 - no Psf (because the PSF-matching process does not compute one) 389 - ``psfMatchingKernel`` : the PSF matching kernel 390 - ``backgroundModel`` : differential background model 391 - ``kernelCellSet`` : SpatialCellSet used to solve for the PSF matching kernel 396 if doWarping is False and templateExposure's and scienceExposure's 399 if not self.
_validateWcs(templateExposure, scienceExposure):
401 self.log.info(
"Astrometrically registering template to science image")
402 templatePsf = templateExposure.getPsf()
403 templateExposure = self.
_warper.warpExposure(scienceExposure.getWcs(),
405 destBBox=scienceExposure.getBBox())
406 templateExposure.setPsf(templatePsf)
408 self.log.error(
"ERROR: Input images not registered")
409 raise RuntimeError(
"Input images not registered")
411 if templateFwhmPix
is None:
412 if not templateExposure.hasPsf():
413 self.log.warn(
"No estimate of Psf FWHM for template image")
415 templateFwhmPix = self.
getFwhmPix(templateExposure.getPsf())
416 self.log.info(
"templateFwhmPix: {}".format(templateFwhmPix))
418 if scienceFwhmPix
is None:
419 if not scienceExposure.hasPsf():
420 self.log.warn(
"No estimate of Psf FWHM for science image")
422 scienceFwhmPix = self.
getFwhmPix(scienceExposure.getPsf())
423 self.log.info(
"scienceFwhmPix: {}".format(scienceFwhmPix))
426 candidateList = self.
makeCandidateList(templateExposure, scienceExposure, kernelSize, candidateList)
430 templateExposure.getMaskedImage(), scienceExposure.getMaskedImage(), candidateList,
431 templateFwhmPix=templateFwhmPix, scienceFwhmPix=scienceFwhmPix)
434 scienceExposure.getMaskedImage(), templateExposure.getMaskedImage(), candidateList,
435 templateFwhmPix=scienceFwhmPix, scienceFwhmPix=templateFwhmPix)
438 psfMatchedExposure.setFilter(templateExposure.getFilter())
439 psfMatchedExposure.setCalib(scienceExposure.getCalib())
440 results.warpedExposure = templateExposure
441 results.matchedExposure = psfMatchedExposure
445 def matchMaskedImages(self, templateMaskedImage, scienceMaskedImage, candidateList,
446 templateFwhmPix=None, scienceFwhmPix=None):
447 """PSF-match a MaskedImage (templateMaskedImage) to a reference MaskedImage (scienceMaskedImage) 449 Do the following, in order: 451 - Determine a PSF matching kernel and differential background model 452 that matches templateMaskedImage to scienceMaskedImage 453 - Convolve templateMaskedImage by the PSF matching kernel 457 templateMaskedImage : `lsst.afw.image.MaskedImage` 458 masked image to PSF-match to the reference masked image; 459 must be warped to match the reference masked image 460 scienceMaskedImage : `lsst.afw.image.MaskedImage` 461 maskedImage whose PSF is to be matched to 462 templateFwhmPix : `float` 463 FWHM (in pixels) of the Psf in the template image (image to convolve) 464 scienceFwhmPix : `float` 465 FWHM (in pixels) of the Psf in the science image 466 candidateList : `list`, optional 467 a list of footprints/maskedImages for kernel candidates; 468 if None then source detection is run. 470 - Currently supported: list of Footprints or measAlg.PsfCandidateF 475 a `lsst.pipe.base.Struct` containing these fields: 477 - psfMatchedMaskedImage: the PSF-matched masked image = 478 templateMaskedImage convolved with psfMatchingKernel. 479 This has the same xy0, dimensions and wcs as scienceMaskedImage. 480 - psfMatchingKernel: the PSF matching kernel 481 - backgroundModel: differential background model 482 - kernelCellSet: SpatialCellSet used to solve for the PSF matching kernel 487 if input images have different dimensions 494 displaySpatialCells =
lsstDebug.Info(__name__).displaySpatialCells
496 if not maskTransparency:
499 ds9.setMaskTransparency(maskTransparency)
501 if not candidateList:
502 raise RuntimeError(
"Candidate list must be populated by makeCandidateList")
504 if not self.
_validateSize(templateMaskedImage, scienceMaskedImage):
505 self.log.error(
"ERROR: Input images different size")
506 raise RuntimeError(
"Input images different size")
508 if display
and displayTemplate:
509 ds9.mtv(templateMaskedImage, frame=lsstDebug.frame, title=
"Image to convolve")
512 if display
and displaySciIm:
513 ds9.mtv(scienceMaskedImage, frame=lsstDebug.frame, title=
"Image to not convolve")
520 if display
and displaySpatialCells:
521 dituils.showKernelSpatialCells(scienceMaskedImage, kernelCellSet,
522 symb=
"o", ctype=ds9.CYAN, ctypeUnused=ds9.YELLOW, ctypeBad=ds9.RED,
523 size=4, frame=lsstDebug.frame, title=
"Image to not convolve")
526 if templateFwhmPix
and scienceFwhmPix:
527 self.log.info(
"Matching Psf FWHM %.2f -> %.2f pix", templateFwhmPix, scienceFwhmPix)
529 if self.
kConfig.useBicForKernelBasis:
534 bicDegrees = nbe(tmpKernelCellSet, self.log)
536 alardDegGauss=bicDegrees[0], metadata=self.metadata)
540 metadata=self.metadata)
542 spatialSolution, psfMatchingKernel, backgroundModel = self.
_solve(kernelCellSet, basisList)
544 psfMatchedMaskedImage = afwImage.MaskedImageF(templateMaskedImage.getBBox())
546 afwMath.convolve(psfMatchedMaskedImage, templateMaskedImage, psfMatchingKernel, doNormalize)
547 return pipeBase.Struct(
548 matchedImage=psfMatchedMaskedImage,
549 psfMatchingKernel=psfMatchingKernel,
550 backgroundModel=backgroundModel,
551 kernelCellSet=kernelCellSet,
556 templateFwhmPix=None, scienceFwhmPix=None,
557 candidateList=None, doWarping=True, convolveTemplate=True):
558 """Register, Psf-match and subtract two Exposures 560 Do the following, in order: 562 - Warp templateExposure to match scienceExposure, if their WCSs do not already match 563 - Determine a PSF matching kernel and differential background model 564 that matches templateExposure to scienceExposure 565 - PSF-match templateExposure to scienceExposure 566 - Compute subtracted exposure (see return values for equation). 570 templateExposure : `lsst.afw.image.Exposure` 571 exposure to PSF-match to scienceExposure 572 scienceExposure : `lsst.afw.image.Exposure` 574 templateFwhmPix : `float` 575 FWHM (in pixels) of the Psf in the template image (image to convolve) 576 scienceFwhmPix : `float` 577 FWHM (in pixels) of the Psf in the science image 578 candidateList : `list`, optional 579 a list of footprints/maskedImages for kernel candidates; 580 if None then source detection is run. 582 - Currently supported: list of Footprints or measAlg.PsfCandidateF 585 what to do if templateExposure's and scienceExposure's WCSs do not match: 587 - if True then warp templateExposure to match scienceExposure 588 - if False then raise an Exception 590 convolveTemplate : `bool` 591 convolve the template image or the science image 593 - if True, templateExposure is warped if doWarping, templateExposure is convolved 594 - if False, templateExposure is warped if doWarping, scienceExposure is convolved 599 a `lsst.pipe.base.Struct` containing these fields: 601 - ``subtractedExposure`` : subtracted Exposure 602 scienceExposure - (matchedImage + backgroundModel) 603 - ``matchedImage`` : templateExposure after warping to match templateExposure (if doWarping true), 604 and convolving with psfMatchingKernel 605 - ``psfMatchingKernel`` : PSF matching kernel 606 - ``backgroundModel`` : differential background model 607 - ``kernelCellSet`` : SpatialCellSet used to determine PSF matching kernel 611 templateExposure=templateExposure,
612 scienceExposure=scienceExposure,
613 templateFwhmPix=templateFwhmPix,
614 scienceFwhmPix=scienceFwhmPix,
615 candidateList=candidateList,
617 convolveTemplate=convolveTemplate
620 subtractedExposure = afwImage.ExposureF(scienceExposure,
True)
622 subtractedMaskedImage = subtractedExposure.getMaskedImage()
623 subtractedMaskedImage -= results.matchedExposure.getMaskedImage()
624 subtractedMaskedImage -= results.backgroundModel
626 subtractedExposure.setMaskedImage(results.warpedExposure.getMaskedImage())
627 subtractedMaskedImage = subtractedExposure.getMaskedImage()
628 subtractedMaskedImage -= results.matchedExposure.getMaskedImage()
629 subtractedMaskedImage -= results.backgroundModel
632 subtractedMaskedImage *= -1
635 subtractedMaskedImage /= results.psfMatchingKernel.computeImage(
636 afwImage.ImageD(results.psfMatchingKernel.getDimensions()),
False)
642 if not maskTransparency:
645 ds9.setMaskTransparency(maskTransparency)
646 if display
and displayDiffIm:
647 ds9.mtv(templateExposure, frame=lsstDebug.frame, title=
"Template")
649 ds9.mtv(results.matchedExposure, frame=lsstDebug.frame, title=
"Matched template")
651 ds9.mtv(scienceExposure, frame=lsstDebug.frame, title=
"Science Image")
653 ds9.mtv(subtractedExposure, frame=lsstDebug.frame, title=
"Difference Image")
656 results.subtractedExposure = subtractedExposure
661 templateFwhmPix=None, scienceFwhmPix=None):
662 """Psf-match and subtract two MaskedImages 664 Do the following, in order: 666 - PSF-match templateMaskedImage to scienceMaskedImage 667 - Determine the differential background 668 - Return the difference: scienceMaskedImage 669 ((warped templateMaskedImage convolved with psfMatchingKernel) + backgroundModel) 673 templateMaskedImage : `lsst.afw.image.MaskedImage` 674 MaskedImage to PSF-match to scienceMaskedImage 675 scienceMaskedImage : `lsst.afw.image.MaskedImage` 676 reference MaskedImage 677 templateFwhmPix : `float` 678 FWHM (in pixels) of the Psf in the template image (image to convolve) 679 scienceFwhmPix : `float` 680 FWHM (in pixels) of the Psf in the science image 681 candidateList : `list`, optional 682 a list of footprints/maskedImages for kernel candidates; 683 if None then source detection is run. 685 - Currently supported: list of Footprints or measAlg.PsfCandidateF 690 a `lsst.pipe.base.Struct` containing these fields: 692 - ``subtractedMaskedImage`` : scienceMaskedImage - (matchedImage + backgroundModel) 693 - ``matchedImage`` : templateMaskedImage convolved with psfMatchingKernel 694 - `psfMatchingKernel`` : PSF matching kernel 695 - ``backgroundModel`` : differential background model 696 - ``kernelCellSet`` : SpatialCellSet used to determine PSF matching kernel 699 if not candidateList:
700 raise RuntimeError(
"Candidate list must be populated by makeCandidateList")
703 templateMaskedImage=templateMaskedImage,
704 scienceMaskedImage=scienceMaskedImage,
705 candidateList=candidateList,
706 templateFwhmPix=templateFwhmPix,
707 scienceFwhmPix=scienceFwhmPix,
710 subtractedMaskedImage = afwImage.MaskedImageF(scienceMaskedImage,
True)
711 subtractedMaskedImage -= results.matchedImage
712 subtractedMaskedImage -= results.backgroundModel
713 results.subtractedMaskedImage = subtractedMaskedImage
719 if not maskTransparency:
722 ds9.setMaskTransparency(maskTransparency)
723 if display
and displayDiffIm:
724 ds9.mtv(subtractedMaskedImage, frame=lsstDebug.frame)
730 """Get sources to use for Psf-matching 732 This method runs detection and measurement on an exposure. 733 The returned set of sources will be used as candidates for 738 exposure : `lsst.afw.image.Exposure` 739 Exposure on which to run detection/measurement 743 Whether or not to smooth the Exposure with Psf before detection 745 Factory for the generation of Source ids 750 source catalog containing candidates for the Psf-matching 754 table = afwTable.SourceTable.make(self.
selectSchema, idFactory)
757 mi = exposure.getMaskedImage()
759 imArr = mi.getImage().getArray()
760 maskArr = mi.getMask().getArray()
761 miArr = np.ma.masked_array(imArr, mask=maskArr)
763 bkgd = self.
background.fitBackground(mi).getImageF()
765 self.log.warn(
"Failed to get background model. Falling back to median background estimation")
766 bkgd = np.ma.extras.median(miArr)
772 detRet = self.selectDetection.makeSourceCatalog(
778 selectSources = detRet.sources
779 self.selectMeasurement.run(measCat=selectSources, exposure=exposure)
787 """Make a list of acceptable KernelCandidates 789 Accept or generate a list of candidate sources for 790 Psf-matching, and examine the Mask planes in both of the 791 images for indications of bad pixels 795 templateExposure : `lsst.afw.image.Exposure` 796 Exposure that will be convolved 797 scienceExposure : `lsst.afw.image.Exposure` 798 Exposure that will be matched-to 800 Dimensions of the Psf-matching Kernel, used to grow detection footprints 801 candidateList : `list`, optional 802 List of Sources to examine. Elements must be of type afw.table.Source 803 or a type that wraps a Source and has a getSource() method, such as 804 meas.algorithms.PsfCandidateF. 808 candidateList : `list` of `dict` 809 a list of dicts having a "source" and "footprint" 810 field for the Sources deemed to be appropriate for Psf 813 if candidateList
is None:
816 if len(candidateList) < 1:
817 raise RuntimeError(
"No candidates in candidateList")
819 listTypes = set(type(x)
for x
in candidateList)
820 if len(listTypes) > 1:
821 raise RuntimeError(
"Candidate list contains mixed types: %s" % [l
for l
in listTypes])
823 if not isinstance(candidateList[0], afwTable.SourceRecord):
825 candidateList[0].getSource()
826 except Exception
as e:
827 raise RuntimeError(
"Candidate List is of type: %s. " % (type(candidateList[0])) +
828 "Can only make candidate list from list of afwTable.SourceRecords, " +
829 "measAlg.PsfCandidateF or other type with a getSource() method: %s" % (e))
830 candidateList = [c.getSource()
for c
in candidateList]
832 candidateList = diffimTools.sourceToFootprintList(candidateList,
833 templateExposure, scienceExposure,
837 if len(candidateList) == 0:
838 raise RuntimeError(
"Cannot find any objects suitable for KernelCandidacy")
842 def _adaptCellSize(self, candidateList):
843 """NOT IMPLEMENTED YET""" 846 def _buildCellSet(self, templateMaskedImage, scienceMaskedImage, candidateList):
847 """Build a SpatialCellSet for use with the solve method 851 templateMaskedImage : `lsst.afw.image.MaskedImage` 852 MaskedImage to PSF-matched to scienceMaskedImage 853 scienceMaskedImage : `lsst.afw.image.MaskedImage` 854 reference MaskedImage 855 candidateList : `list` 856 a list of footprints/maskedImages for kernel candidates; 858 - Currently supported: list of Footprints or measAlg.PsfCandidateF 863 a SpatialCellSet for use with self._solve 865 if not candidateList:
866 raise RuntimeError(
"Candidate list must be populated by makeCandidateList")
872 sizeCellX, sizeCellY)
874 policy = pexConfig.makePolicy(self.
kConfig)
876 for cand
in candidateList:
877 bbox = cand[
'footprint'].getBBox()
879 tmi = afwImage.MaskedImageF(templateMaskedImage, bbox)
880 smi = afwImage.MaskedImageF(scienceMaskedImage, bbox)
881 cand = diffimLib.makeKernelCandidate(cand[
'source'], tmi, smi, policy)
883 self.log.debug(
"Candidate %d at %f, %f", cand.getId(), cand.getXCenter(), cand.getYCenter())
884 kernelCellSet.insertCandidate(cand)
888 def _validateSize(self, templateMaskedImage, scienceMaskedImage):
889 """Return True if two image-like objects are the same size 891 return templateMaskedImage.getDimensions() == scienceMaskedImage.getDimensions()
893 def _validateWcs(self, templateExposure, scienceExposure):
894 """Return True if the WCS of the two Exposures have the same origin and extent 896 templateWcs = templateExposure.getWcs()
897 scienceWcs = scienceExposure.getWcs()
898 templateBBox = templateExposure.getBBox()
899 scienceBBox = scienceExposure.getBBox()
902 templateOrigin = templateWcs.pixelToSky(afwGeom.Point2D(templateBBox.getBegin()))
903 scienceOrigin = scienceWcs.pixelToSky(afwGeom.Point2D(scienceBBox.getBegin()))
906 templateLimit = templateWcs.pixelToSky(afwGeom.Point2D(templateBBox.getEnd()))
907 scienceLimit = scienceWcs.pixelToSky(afwGeom.Point2D(scienceBBox.getEnd()))
909 self.log.info(
"Template Wcs : %f,%f -> %f,%f",
910 templateOrigin[0], templateOrigin[1],
911 templateLimit[0], templateLimit[1])
912 self.log.info(
"Science Wcs : %f,%f -> %f,%f",
913 scienceOrigin[0], scienceOrigin[1],
914 scienceLimit[0], scienceLimit[1])
916 templateBBox = afwGeom.Box2D(templateOrigin.getPosition(afwGeom.degrees),
917 templateLimit.getPosition(afwGeom.degrees))
918 scienceBBox = afwGeom.Box2D(scienceOrigin.getPosition(afwGeom.degrees),
919 scienceLimit.getPosition(afwGeom.degrees))
920 if not (templateBBox.overlaps(scienceBBox)):
921 raise RuntimeError(
"Input images do not overlap at all")
923 if ((templateOrigin != scienceOrigin)
or 924 (templateLimit != scienceLimit)
or 925 (templateExposure.getDimensions() != scienceExposure.getDimensions())):
930 subtractAlgorithmRegistry = pexConfig.makeRegistry(
931 doc=
"A registry of subtraction algorithms for use as a subtask in imageDifference",
934 subtractAlgorithmRegistry.register(
'al', ImagePsfMatchTask)
def makeKernelBasisList(config, targetFwhmPix=None, referenceFwhmPix=None, basisDegGauss=None, metadata=None)
def getSelectSources(self, exposure, sigma=None, doSmooth=True, idFactory=None)
def _validateSize(self, templateMaskedImage, scienceMaskedImage)
def _buildCellSet(self, templateMaskedImage, scienceMaskedImage, candidateList)
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 >())
def _adaptCellSize(self, candidateList)
def __init__(self, args, kwargs)
def matchMaskedImages(self, templateMaskedImage, scienceMaskedImage, candidateList, templateFwhmPix=None, scienceFwhmPix=None)
def _solve(self, kernelCellSet, basisList, returnOnExcept=False)
def matchExposures(self, templateExposure, scienceExposure, templateFwhmPix=None, scienceFwhmPix=None, candidateList=None, doWarping=True, convolveTemplate=True)
def makeCandidateList(self, templateExposure, scienceExposure, kernelSize, candidateList=None)
def subtractMaskedImages(self, templateMaskedImage, scienceMaskedImage, candidateList, templateFwhmPix=None, scienceFwhmPix=None)
void convolve(OutImageT &convolvedImage, InImageT const &inImage, KernelT const &kernel, bool doNormalize, bool doCopyEdge=false)
def getFwhmPix(self, psf)
def subtractExposures(self, templateExposure, scienceExposure, templateFwhmPix=None, scienceFwhmPix=None, candidateList=None, doWarping=True, convolveTemplate=True)
def _validateWcs(self, templateExposure, scienceExposure)