30 from makeKernelBasisList
import makeKernelBasisList
31 from psfMatch
import PsfMatchTask, PsfMatchConfigAL
32 from .
import utils
as diUtils
35 sigma2fwhm = 2. * num.sqrt(2. * num.log(2.))
38 """!Configuration for model-to-model Psf matching"""
40 kernel = pexConfig.ConfigChoiceField(
43 AL = PsfMatchConfigAL,
50 self.kernel.active.singleKernelClipping =
False
51 self.kernel.active.kernelSumClipping =
False
52 self.kernel.active.spatialKernelClipping =
False
53 self.kernel.active.checkConditionNumber =
False
56 self.kernel.active.constantVarianceWeighting =
True
59 self.kernel.active.kernelSizeMin = 11
60 self.kernel.active.kernelSize = 11
71 \anchor ModelPsfMatchTask_
73 \brief Matching of two model Psfs, and application of the Psf-matching kernel to an input Exposure
75 \section ip_diffim_modelpsfmatch_Contents Contents
77 - \ref ip_diffim_modelpsfmatch_Purpose
78 - \ref ip_diffim_modelpsfmatch_Initialize
79 - \ref ip_diffim_modelpsfmatch_IO
80 - \ref ip_diffim_modelpsfmatch_Config
81 - \ref ip_diffim_modelpsfmatch_Metadata
82 - \ref ip_diffim_modelpsfmatch_Debug
83 - \ref ip_diffim_modelpsfmatch_Example
85 #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
87 \section ip_diffim_modelpsfmatch_Purpose Description
89 This Task differs from ImagePsfMatchTask in that it matches two Psf _models_, by realizing
90 them in an Exposure-sized SpatialCellSet and then inserting each Psf-image pair into KernelCandidates.
91 Because none of the pairs of sources that are to be matched should be invalid, all sigma clipping is
92 turned off in ModelPsfMatchConfig. And because there is no tracked _variance_ in the Psf images, the
93 debugging and logging QA info should be interpreted with caution.
95 One item of note is that the sizes of Psf models are fixed (e.g. its defined as a 21x21 matrix). When the
96 Psf-matching kernel is being solved for, the Psf "image" is convolved with each kernel basis function,
97 leading to a loss of information around the borders. This pixel loss will be problematic for the numerical
98 stability of the kernel solution if the size of the convolution kernel (set by ModelPsfMatchConfig.kernelSize)
99 is much bigger than: psfSize//2. Thus the sizes of Psf-model matching kernels are typically smaller
100 than their image-matching counterparts. If the size of the kernel is too small, the convolved stars will
101 look "boxy"; if the kernel is too large, the kernel solution will be "noisy". This is a trade-off that
102 needs careful attention for a given dataset.
104 The primary use case for this Task is in matching an Exposure to a constant-across-the-sky Psf model for the
105 purposes of image coaddition. It is important to note that in the code, the "template" Psf is the Psf
106 that the science image gets matched to. In this sense the order of template and science image are
107 reversed, compared to ImagePsfMatchTask, which operates on the template image.
109 #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
111 \section ip_diffim_modelpsfmatch_Initialize Task initialization
113 \copydoc \_\_init\_\_
115 #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
117 \section ip_diffim_modelpsfmatch_IO Invoking the Task
121 #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
123 \section ip_diffim_modelpsfmatch_Config Configuration parameters
125 See \ref ModelPsfMatchConfig
127 #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
129 \section ip_diffim_modelpsfmatch_Metadata Quantities set in Metadata
131 See \ref ip_diffim_psfmatch_Metadata "PsfMatchTask"
133 #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
135 \section ip_diffim_modelpsfmatch_Debug Debug variables
137 The \link lsst.pipe.base.cmdLineTask.CmdLineTask command line task\endlink interface supports a
138 flag \c -d/--debug to import \b debug.py from your \c PYTHONPATH. The relevant contents of debug.py
139 for this Task include:
145 di = lsstDebug.getInfo(name)
146 if name == "lsst.ip.diffim.psfMatch":
147 di.display = True # global
148 di.maskTransparency = 80 # ds9 mask transparency
149 di.displayCandidates = True # show all the candidates and residuals
150 di.displayKernelBasis = False # show kernel basis functions
151 di.displayKernelMosaic = True # show kernel realized across the image
152 di.plotKernelSpatialModel = False # show coefficients of spatial model
153 di.showBadCandidates = True # show the bad candidates (red) along with good (green)
154 elif name == "lsst.ip.diffim.modelPsfMatch":
155 di.display = True # global
156 di.maskTransparency = 30 # ds9 mask transparency
157 di.displaySpatialCells = True # show spatial cells before the fit
159 lsstDebug.Info = DebugInfo
163 Note that if you want addional logging info, you may add to your scripts:
165 import lsst.pex.logging as pexLog
166 pexLog.Trace_setVerbosity('lsst.ip.diffim', 5)
169 #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
171 \section ip_diffim_modelpsfmatch_Example A complete example of using ModelPsfMatchTask
173 This code is modelPsfMatchTask.py in the examples directory, and can be run as \em e.g.
175 examples/modelPsfMatchTask.py
176 examples/modelPsfMatchTask.py --debug
177 examples/modelPsfMatchTask.py --debug --template /path/to/templateExp.fits --science /path/to/scienceExp.fits
180 \dontinclude modelPsfMatchTask.py
181 Create a subclass of ModelPsfMatchTask that accepts two exposures. Note that the "template" exposure
182 contains the Psf that will get matched to, and the "science" exposure is the one that will be convolved:
183 \skip MyModelPsfMatchTask
186 And allow the user the freedom to either run the script in default mode, or point to their own images on disk.
187 Note that these images must be readable as an lsst.afw.image.Exposure:
191 We have enabled some minor display debugging in this script via the --debug option. However, if you
192 have an lsstDebug debug.py in your PYTHONPATH you will get additional debugging displays. The following
193 block checks for this script:
197 \dontinclude modelPsfMatchTask.py
198 Finally, we call a run method that we define below. First set up a Config and modify some of the parameters.
199 In particular we don't want to "grow" the sizes of the kernel or KernelCandidates, since we are operating with
200 fixed--size images (i.e. the size of the input Psf models).
204 Make sure the images (if any) that were sent to the script exist on disk and are readable. If no images
205 are sent, make some fake data up for the sake of this example script (have a look at the code if you want
206 more details on generateFakeData):
210 Display the two images if --debug:
214 Create and run the Task:
218 And finally provide optional debugging display of the Psf-matched (via the Psf models) science image:
220 \until result.psfMatchedExposure
222 #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
225 ConfigClass = ModelPsfMatchConfig
228 """!Create a ModelPsfMatchTask
230 \param *args arguments to be passed to lsst.ip.diffim.PsfMatchTask.__init__
231 \param **kwargs keyword arguments to be passed to lsst.ip.diffim.PsfMatchTask.__init__
233 Upon initialization, the kernel configuration is defined by self.config.kernel.active. This Task
234 does have a run() method, which is the default way to call the Task.
236 PsfMatchTask.__init__(self, *args, **kwargs)
240 def run(self, exposure, referencePsfModel, kernelSum=1.0):
241 """!Psf-match an exposure to a model Psf
243 @param exposure: Exposure to Psf-match to the reference Psf model;
244 it must return a valid PSF model via exposure.getPsf()
245 @param referencePsfModel: The Psf model to match to (an lsst.afw.detection.Psf)
246 @param kernelSum: A multipicative factor to apply to the kernel sum (default=1.0)
249 - psfMatchedExposure: the Psf-matched Exposure. This has the same parent bbox, Wcs, Calib and
250 Filter as the input Exposure but no Psf. In theory the Psf should equal referencePsfModel but
251 the match is likely not exact.
252 - psfMatchingKernel: the spatially varying Psf-matching kernel
253 - kernelCellSet: SpatialCellSet used to solve for the Psf-matching kernel
255 Raise a RuntimeError if the Exposure does not contain a Psf model
257 if not exposure.hasPsf():
258 raise RuntimeError(
"exposure does not contain a Psf model")
260 maskedImage = exposure.getMaskedImage()
262 self.log.log(pexLog.Log.INFO,
"compute Psf-matching kernel")
263 kernelCellSet = self.
_buildCellSet(exposure, referencePsfModel)
264 width, height = referencePsfModel.getLocalKernel().getDimensions()
267 s1 = psfAttr1.computeGaussianWidth(psfAttr1.ADAPTIVE_MOMENT)
268 s2 = psfAttr2.computeGaussianWidth(psfAttr2.ADAPTIVE_MOMENT)
269 fwhm1 = s1 * sigma2fwhm
270 fwhm2 = s2 * sigma2fwhm
273 spatialSolution, psfMatchingKernel, backgroundModel = self._solve(kernelCellSet, basisList)
275 if psfMatchingKernel.isSpatiallyVarying():
276 sParameters = num.array(psfMatchingKernel.getSpatialParameters())
277 sParameters[0][0] = kernelSum
278 psfMatchingKernel.setSpatialParameters(sParameters)
280 kParameters = num.array(psfMatchingKernel.getKernelParameters())
281 kParameters[0] = kernelSum
282 psfMatchingKernel.setKernelParameters(kParameters)
284 self.log.log(pexLog.Log.INFO,
"Psf-match science exposure to reference")
285 psfMatchedExposure = afwImage.ExposureF(exposure.getBBox(), exposure.getWcs())
286 psfMatchedExposure.setFilter(exposure.getFilter())
287 psfMatchedExposure.setCalib(exposure.getCalib())
288 psfMatchedMaskedImage = psfMatchedExposure.getMaskedImage()
293 afwMath.convolve(psfMatchedMaskedImage, maskedImage, psfMatchingKernel, doNormalize)
295 self.log.log(pexLog.Log.INFO,
"done")
296 return pipeBase.Struct(psfMatchedExposure=psfMatchedExposure,
297 psfMatchingKernel=psfMatchingKernel,
298 kernelCellSet=kernelCellSet,
299 metadata=self.metadata)
301 def _diagnostic(self, kernelCellSet, spatialSolution, spatialKernel, spatialBg):
302 """!Print diagnostic information on spatial kernel and background fit
304 The debugging diagnostics are not really useful here, since the images we are matching have
305 no variance. Thus override the _diagnostic method to generate no logging information"""
309 """!Build a SpatialCellSet for use with the solve method
311 @param exposure: The science exposure that will be convolved; must contain a Psf
312 @param referencePsfModel: Psf model to match to
314 @return kernelCellSet: a SpatialCellSet to be used by self._solve
316 Raise a RuntimeError if the reference Psf model and science Psf model have different dimensions
318 scienceBBox = exposure.getBBox()
319 sciencePsfModel = exposure.getPsf()
324 referencePsfModel = measAlg.KernelPsf.swigConvert(referencePsfModel)
325 sciencePsfModel = measAlg.KernelPsf.swigConvert(sciencePsfModel)
326 if referencePsfModel
is None or sciencePsfModel
is None:
327 raise RuntimeError(
"ERROR: Psf matching is only implemented for KernelPsfs")
328 if (referencePsfModel.getKernel().getDimensions() != sciencePsfModel.getKernel().getDimensions()):
330 "ERROR: Dimensions of reference Psf and science Psf different; exiting")
331 raise RuntimeError,
"ERROR: Dimensions of reference Psf and science Psf different; exiting"
333 psfWidth, psfHeight = referencePsfModel.getKernel().getDimensions()
334 maxKernelSize = min(psfWidth, psfHeight) - 1
335 if maxKernelSize % 2 == 0:
337 if self.kConfig.kernelSize > maxKernelSize:
338 raise ValueError,
"Kernel size (%d) too big to match Psfs of size %d; reduce to at least %d" % (
339 self.kConfig.kernelSize, psfWidth, maxKernelSize)
349 nParameters = sciencePsfModel.getKernel().getNSpatialParameters()
350 root = num.sqrt(9 - 8 * (1 - nParameters))
351 if (root != root // 1):
352 pexLog.Trace(self.log.getName(), 3,
"Problem inferring spatial order of image's Psf")
354 order = (root - 3) / 2
355 if (order != order // 1):
356 pexLog.Trace(self.log.getName(), 3,
"Problem inferring spatial order of image's Psf")
358 pexLog.Trace(self.log.getName(), 2,
"Spatial order of Psf = %d; matching kernel order = %d" % (
359 order, self.kConfig.spatialKernelOrder))
361 regionSizeX, regionSizeY = scienceBBox.getDimensions()
362 scienceX0, scienceY0 = scienceBBox.getMin()
364 sizeCellX = self.kConfig.sizeCellX
365 sizeCellY = self.kConfig.sizeCellY
373 nCellX = regionSizeX // sizeCellX
374 nCellY = regionSizeY // sizeCellY
375 dimenR = referencePsfModel.getKernel().getDimensions()
376 dimenS = sciencePsfModel.getKernel().getDimensions()
378 policy = pexConfig.makePolicy(self.kConfig)
379 for row
in range(nCellY):
381 posY = sizeCellY * row + sizeCellY // 2 + scienceY0
383 for col
in range(nCellX):
385 posX = sizeCellX * col + sizeCellX // 2 + scienceX0
387 pexLog.Trace(self.log.getName(), 5,
"Creating Psf candidate at %.1f %.1f" % (posX, posY))
390 kernelImageR = referencePsfModel.computeImage(
afwGeom.Point2D(posX, posY)).convertF()
391 kernelMaskR = afwImage.MaskU(dimenR)
393 kernelVarR = afwImage.ImageF(dimenR)
395 referenceMI = afwImage.MaskedImageF(kernelImageR, kernelMaskR, kernelVarR)
398 kernelImageS = sciencePsfModel.computeImage(
afwGeom.Point2D(posX, posY)).convertF()
399 kernelMaskS = afwImage.MaskU(dimenS)
401 kernelVarS = afwImage.ImageF(dimenS)
403 scienceMI = afwImage.MaskedImageF(kernelImageS, kernelMaskS, kernelVarS)
406 kc = diffimLib.makeKernelCandidate(posX, posY, scienceMI, referenceMI, policy)
407 kernelCellSet.insertCandidate(kc)
411 displaySpatialCells =
lsstDebug.Info(__name__).displaySpatialCells
413 if not maskTransparency:
416 ds9.setMaskTransparency(maskTransparency)
417 if display
and displaySpatialCells:
418 diUtils.showKernelSpatialCells(exposure.getMaskedImage(), kernelCellSet,
419 symb=
"o", ctype=ds9.CYAN, ctypeUnused=ds9.YELLOW, ctypeBad=ds9.RED,
420 size=4, frame=lsstDebug.frame, title=
"Image to be convolved")
def _diagnostic
Print diagnostic information on spatial kernel and background fit.
def __init__
Create a ModelPsfMatchTask.
limited backward compatibility to the DC2 run-time trace facilities
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.