1 from __future__
import absolute_import
2 from builtins
import range
24 from .
import diffimLib
32 from .makeKernelBasisList
import makeKernelBasisList
33 from .psfMatch
import PsfMatchTask, PsfMatchConfigAL
34 from .
import utils
as diUtils
37 sigma2fwhm = 2. * num.sqrt(2. * num.log(2.))
41 """!Configuration for model-to-model Psf matching"""
43 kernel = pexConfig.ConfigChoiceField(
51 padPsf = pexConfig.Field(
53 doc=
"Grow PSF dimensions to largest PSF dimension on input exposure; else clip to smallest",
59 self.kernel.active.singleKernelClipping =
False
60 self.kernel.active.kernelSumClipping =
False
61 self.kernel.active.spatialKernelClipping =
False
62 self.kernel.active.checkConditionNumber =
False
65 self.kernel.active.constantVarianceWeighting =
True
68 self.kernel.active.kernelSizeMin = 11
69 self.kernel.active.kernelSize = 11
81 \anchor ModelPsfMatchTask_
83 \brief Matching of two model Psfs, and application of the Psf-matching kernel to an input Exposure
85 \section ip_diffim_modelpsfmatch_Contents Contents
87 - \ref ip_diffim_modelpsfmatch_Purpose
88 - \ref ip_diffim_modelpsfmatch_Initialize
89 - \ref ip_diffim_modelpsfmatch_IO
90 - \ref ip_diffim_modelpsfmatch_Config
91 - \ref ip_diffim_modelpsfmatch_Metadata
92 - \ref ip_diffim_modelpsfmatch_Debug
93 - \ref ip_diffim_modelpsfmatch_Example
95 #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
97 \section ip_diffim_modelpsfmatch_Purpose Description
99 This Task differs from ImagePsfMatchTask in that it matches two Psf _models_, by realizing
100 them in an Exposure-sized SpatialCellSet and then inserting each Psf-image pair into KernelCandidates.
101 Because none of the pairs of sources that are to be matched should be invalid, all sigma clipping is
102 turned off in ModelPsfMatchConfig. And because there is no tracked _variance_ in the Psf images, the
103 debugging and logging QA info should be interpreted with caution.
105 One item of note is that the sizes of Psf models are fixed (e.g. its defined as a 21x21 matrix). When the
106 Psf-matching kernel is being solved for, the Psf "image" is convolved with each kernel basis function,
107 leading to a loss of information around the borders. This pixel loss will be problematic for the numerical
108 stability of the kernel solution if the size of the convolution kernel (set by ModelPsfMatchConfig.kernelSize)
109 is much bigger than: psfSize//2. Thus the sizes of Psf-model matching kernels are typically smaller
110 than their image-matching counterparts. If the size of the kernel is too small, the convolved stars will
111 look "boxy"; if the kernel is too large, the kernel solution will be "noisy". This is a trade-off that
112 needs careful attention for a given dataset.
114 The primary use case for this Task is in matching an Exposure to a constant-across-the-sky Psf model for the
115 purposes of image coaddition. It is important to note that in the code, the "template" Psf is the Psf
116 that the science image gets matched to. In this sense the order of template and science image are
117 reversed, compared to ImagePsfMatchTask, which operates on the template image.
119 #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
121 \section ip_diffim_modelpsfmatch_Initialize Task initialization
123 \copydoc \_\_init\_\_
125 #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
127 \section ip_diffim_modelpsfmatch_IO Invoking the Task
131 #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
133 \section ip_diffim_modelpsfmatch_Config Configuration parameters
135 See \ref ModelPsfMatchConfig
137 #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
139 \section ip_diffim_modelpsfmatch_Metadata Quantities set in Metadata
141 See \ref ip_diffim_psfmatch_Metadata "PsfMatchTask"
143 #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
145 \section ip_diffim_modelpsfmatch_Debug Debug variables
147 The \link lsst.pipe.base.cmdLineTask.CmdLineTask command line task\endlink interface supports a
148 flag \c -d/--debug to import \b debug.py from your \c PYTHONPATH. The relevant contents of debug.py
149 for this Task include:
155 di = lsstDebug.getInfo(name)
156 if name == "lsst.ip.diffim.psfMatch":
157 di.display = True # global
158 di.maskTransparency = 80 # ds9 mask transparency
159 di.displayCandidates = True # show all the candidates and residuals
160 di.displayKernelBasis = False # show kernel basis functions
161 di.displayKernelMosaic = True # show kernel realized across the image
162 di.plotKernelSpatialModel = False # show coefficients of spatial model
163 di.showBadCandidates = True # show the bad candidates (red) along with good (green)
164 elif name == "lsst.ip.diffim.modelPsfMatch":
165 di.display = True # global
166 di.maskTransparency = 30 # ds9 mask transparency
167 di.displaySpatialCells = True # show spatial cells before the fit
169 lsstDebug.Info = DebugInfo
173 Note that if you want addional logging info, you may add to your scripts:
175 import lsst.log.utils as logUtils
176 logUtils.traceSetAt("ip.diffim", 4)
179 #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
181 \section ip_diffim_modelpsfmatch_Example A complete example of using ModelPsfMatchTask
183 This code is modelPsfMatchTask.py in the examples directory, and can be run as \em e.g.
185 examples/modelPsfMatchTask.py
186 examples/modelPsfMatchTask.py --debug
187 examples/modelPsfMatchTask.py --debug --template /path/to/templateExp.fits --science /path/to/scienceExp.fits
190 \dontinclude modelPsfMatchTask.py
191 Create a subclass of ModelPsfMatchTask that accepts two exposures. Note that the "template" exposure
192 contains the Psf that will get matched to, and the "science" exposure is the one that will be convolved:
193 \skip MyModelPsfMatchTask
196 And allow the user the freedom to either run the script in default mode, or point to their own images on disk.
197 Note that these images must be readable as an lsst.afw.image.Exposure:
201 We have enabled some minor display debugging in this script via the --debug option. However, if you
202 have an lsstDebug debug.py in your PYTHONPATH you will get additional debugging displays. The following
203 block checks for this script:
207 \dontinclude modelPsfMatchTask.py
208 Finally, we call a run method that we define below. First set up a Config and modify some of the parameters.
209 In particular we don't want to "grow" the sizes of the kernel or KernelCandidates, since we are operating with
210 fixed--size images (i.e. the size of the input Psf models).
214 Make sure the images (if any) that were sent to the script exist on disk and are readable. If no images
215 are sent, make some fake data up for the sake of this example script (have a look at the code if you want
216 more details on generateFakeData):
220 Display the two images if --debug:
224 Create and run the Task:
228 And finally provide optional debugging display of the Psf-matched (via the Psf models) science image:
230 @until result.psfMatchedExposure
232 #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
235 ConfigClass = ModelPsfMatchConfig
238 """!Create a ModelPsfMatchTask
240 \param *args arguments to be passed to lsst.ip.diffim.PsfMatchTask.__init__
241 \param **kwargs keyword arguments to be passed to lsst.ip.diffim.PsfMatchTask.__init__
243 Upon initialization, the kernel configuration is defined by self.config.kernel.active. This Task
244 does have a run() method, which is the default way to call the Task.
246 PsfMatchTask.__init__(self, *args, **kwargs)
250 def run(self, exposure, referencePsfModel, kernelSum=1.0):
251 """!Psf-match an exposure to a model Psf
253 @param exposure: Exposure to Psf-match to the reference Psf model;
254 it must return a valid PSF model via exposure.getPsf()
255 @param referencePsfModel: The Psf model to match to (an lsst.afw.detection.Psf)
256 @param kernelSum: A multipicative factor to apply to the kernel sum (default=1.0)
259 - psfMatchedExposure: the Psf-matched Exposure. This has the same parent bbox, Wcs, Calib and
260 Filter as the input Exposure but no Psf. In theory the Psf should equal referencePsfModel but
261 the match is likely not exact.
262 - psfMatchingKernel: the spatially varying Psf-matching kernel
263 - kernelCellSet: SpatialCellSet used to solve for the Psf-matching kernel
264 - referencePsfModel: Validated and/or modified reference model used
266 Raise a RuntimeError if the Exposure does not contain a Psf model
268 if not exposure.hasPsf():
269 raise RuntimeError(
"exposure does not contain a Psf model")
271 maskedImage = exposure.getMaskedImage()
273 self.log.info(
"compute Psf-matching kernel")
275 kernelCellSet = result.kernelCellSet
276 referencePsfModel = result.referencePsfModel
277 width, height = referencePsfModel.getLocalKernel().getDimensions()
280 s1 = psfAttr1.computeGaussianWidth(psfAttr1.ADAPTIVE_MOMENT)
281 s2 = psfAttr2.computeGaussianWidth(psfAttr2.ADAPTIVE_MOMENT)
282 fwhm1 = s1 * sigma2fwhm
283 fwhm2 = s2 * sigma2fwhm
286 spatialSolution, psfMatchingKernel, backgroundModel = self._solve(kernelCellSet, basisList)
288 if psfMatchingKernel.isSpatiallyVarying():
289 sParameters = num.array(psfMatchingKernel.getSpatialParameters())
290 sParameters[0][0] = kernelSum
291 psfMatchingKernel.setSpatialParameters(sParameters)
293 kParameters = num.array(psfMatchingKernel.getKernelParameters())
294 kParameters[0] = kernelSum
295 psfMatchingKernel.setKernelParameters(kParameters)
297 self.log.info(
"Psf-match science exposure to reference")
298 psfMatchedExposure = afwImage.ExposureF(exposure.getBBox(), exposure.getWcs())
299 psfMatchedExposure.setFilter(exposure.getFilter())
300 psfMatchedExposure.setCalib(exposure.getCalib())
301 psfMatchedMaskedImage = psfMatchedExposure.getMaskedImage()
306 afwMath.convolve(psfMatchedMaskedImage, maskedImage, psfMatchingKernel, doNormalize)
308 self.log.info(
"done")
309 return pipeBase.Struct(psfMatchedExposure=psfMatchedExposure,
310 psfMatchingKernel=psfMatchingKernel,
311 kernelCellSet=kernelCellSet,
312 metadata=self.metadata,
313 referencePsfModel=referencePsfModel,
316 def _diagnostic(self, kernelCellSet, spatialSolution, spatialKernel, spatialBg):
317 """!Print diagnostic information on spatial kernel and background fit
319 The debugging diagnostics are not really useful here, since the images we are matching have
320 no variance. Thus override the _diagnostic method to generate no logging information"""
324 """!Build a SpatialCellSet for use with the solve method
326 @param exposure: The science exposure that will be convolved; must contain a Psf
327 @param referencePsfModel: Psf model to match to
330 -kernelCellSet: a SpatialCellSet to be used by self._solve
331 -referencePsfModel: Validated and/or modified reference model used to populate the SpatialCellSet
333 If the reference Psf model and science Psf model have different dimensions,
334 adjust the referencePsfModel (the model to which the exposure PSF will be matched)
335 to match that of the science Psf. If the science Psf dimensions vary across the image,
336 as is common with a WarpedPsf, either pad or clip (depending on config.padPsf)
337 the dimensions to be constant.
339 scienceBBox = exposure.getBBox()
340 sciencePsfModel = exposure.getPsf()
342 dimenR = referencePsfModel.getLocalKernel().getDimensions()
343 psfWidth, psfHeight = dimenR
345 maxKernelSize = min(psfWidth, psfHeight) - 1
346 if maxKernelSize % 2 == 0:
348 if self.kConfig.kernelSize > maxKernelSize:
349 raise ValueError(
"Kernel size (%d) too big to match Psfs of size %d; reduce to at least %d" % (
350 self.kConfig.kernelSize, psfWidth, maxKernelSize))
352 regionSizeX, regionSizeY = scienceBBox.getDimensions()
353 scienceX0, scienceY0 = scienceBBox.getMin()
355 sizeCellX = self.kConfig.sizeCellX
356 sizeCellY = self.kConfig.sizeCellY
364 nCellX = regionSizeX//sizeCellX
365 nCellY = regionSizeY//sizeCellY
372 for row
in range(nCellY):
373 posY = sizeCellY*row + sizeCellY//2 + scienceY0
374 for col
in range(nCellX):
375 posX = sizeCellX*col + sizeCellX//2 + scienceX0
376 widthS, heightS = sciencePsfModel.computeBBox(
afwGeom.Point2D(posX, posY)).getDimensions()
377 widthList.append(widthS)
378 heightList.append(heightS)
380 lenMax = max(max(heightList), max(widthList))
381 lenMin = min(min(heightList), min(widthList))
383 lenPsfScience = lenMax
if self.config.padPsf
else lenMin
386 if (dimenR != dimenS):
387 self.log.info(
"Adjusting dimensions of reference PSF model from %s to %s" % (dimenR, dimenS))
388 referencePsfModel = referencePsfModel.resized(lenPsfScience, lenPsfScience)
391 policy = pexConfig.makePolicy(self.kConfig)
392 for row
in range(nCellY):
394 posY = sizeCellY * row + sizeCellY//2 + scienceY0
396 for col
in range(nCellX):
398 posX = sizeCellX * col + sizeCellX//2 + scienceX0
400 log.log(
"TRACE4." + self.log.getName(), log.DEBUG,
401 "Creating Psf candidate at %.1f %.1f", posX, posY)
404 kernelImageR = referencePsfModel.computeImage(
afwGeom.Point2D(posX, posY)).convertF()
405 kernelMaskR = afwImage.MaskU(dimenR)
407 kernelVarR = afwImage.ImageF(dimenR)
409 referenceMI = afwImage.MaskedImageF(kernelImageR, kernelMaskR, kernelVarR)
412 rawKernel = sciencePsfModel.computeKernelImage(
afwGeom.Point2D(posX, posY)).convertF()
413 if rawKernel.getDimensions() == dimenR:
414 kernelImageS = rawKernel
417 kernelImageS = afwImage.ImageF(dimenR)
418 if self.config.padPsf:
420 (lenMax - rawKernel.getHeight())//2),
421 rawKernel.getDimensions())
422 kernelImageS.assign(rawKernel, bboxToPlace)
425 (rawKernel.getHeight() - lenMin)//2),
427 rawKernel.setXY0(0, 0)
428 kernelImageS = rawKernel.Factory(rawKernel, bboxToExtract)
430 kernelMaskS = afwImage.MaskU(dimenS)
432 kernelVarS = afwImage.ImageF(dimenS)
434 scienceMI = afwImage.MaskedImageF(kernelImageS, kernelMaskS, kernelVarS)
437 kc = diffimLib.makeKernelCandidate(posX, posY, scienceMI, referenceMI, policy)
438 kernelCellSet.insertCandidate(kc)
442 displaySpatialCells =
lsstDebug.Info(__name__).displaySpatialCells
444 if not maskTransparency:
447 ds9.setMaskTransparency(maskTransparency)
448 if display
and displaySpatialCells:
449 diUtils.showKernelSpatialCells(exposure.getMaskedImage(), kernelCellSet,
450 symb=
"o", ctype=ds9.CYAN, ctypeUnused=ds9.YELLOW, ctypeBad=ds9.RED,
451 size=4, frame=lsstDebug.frame, title=
"Image to be convolved")
453 return pipeBase.Struct(kernelCellSet=kernelCellSet,
454 referencePsfModel=referencePsfModel,
def _diagnostic
Print diagnostic information on spatial kernel and background fit.
A class to contain various attributes of the Psf.
def __init__
Create a ModelPsfMatchTask.
Fit spatial kernel using approximate fluxes for candidates, and solving a linear system of equations...
An integer coordinate rectangle.
A collection of SpatialCells covering an entire image.
Matching of two model Psfs, and application of the Psf-matching kernel to an input Exposure...
Configuration for model-to-model Psf matching.
def _buildCellSet
Build a SpatialCellSet for use with the solve method.
def run
Psf-match an exposure to a model Psf.
void convolve(OutImageT &convolvedImage, InImageT const &inImage, KernelT const &kernel, bool doNormalize, bool doCopyEdge=false)
Old, deprecated version of convolve.