24from .
import diffimLib
31from lsst.utils.logging
import getTraceLogger
32from lsst.utils.timer
import timeMethod
33from .makeKernelBasisList
import makeKernelBasisList
34from .psfMatch
import PsfMatchTask, PsfMatchConfigAL
35from .
import utils
as dituils
37__all__ = (
"ModelPsfMatchTask",
"ModelPsfMatchConfig")
39sigma2fwhm = 2.*np.sqrt(2.*np.log(2.))
43 nextInt = int(np.ceil(x))
44 return nextInt + 1
if nextInt%2 == 0
else nextInt
48 """Configuration for model-to-model Psf matching"""
50 kernel = pexConfig.ConfigChoiceField(
57 doAutoPadPsf = pexConfig.Field(
59 doc=(
"If too small, automatically pad the science Psf? "
60 "Pad to smallest dimensions appropriate for the matching kernel dimensions, "
61 "as specified by autoPadPsfTo. If false, pad by the padPsfBy config."),
64 autoPadPsfTo = pexConfig.RangeField(
66 doc=(
"Minimum Science Psf dimensions as a fraction of matching kernel dimensions. "
67 "If the dimensions of the Psf to be matched are less than the "
68 "matching kernel dimensions * autoPadPsfTo, pad Science Psf to this size. "
69 "Ignored if doAutoPadPsf=False."),
74 padPsfBy = pexConfig.Field(
76 doc=
"Pixels (even) to pad Science Psf by before matching. Ignored if doAutoPadPsf=True",
82 self.
kernel.active.singleKernelClipping =
False
83 self.
kernel.active.kernelSumClipping =
False
84 self.
kernel.active.spatialKernelClipping =
False
85 self.
kernel.active.checkConditionNumber =
False
88 self.
kernel.active.constantVarianceWeighting =
True
91 self.
kernel.active.scaleByFwhm =
False
95 """Matching of two model Psfs, and application of the Psf-matching kernel to an input Exposure
98 ConfigClass = ModelPsfMatchConfig
101 """Create a ModelPsfMatchTask
106 arguments to be passed to lsst.ip.diffim.PsfMatchTask.__init__
108 keyword arguments to be passed to lsst.ip.diffim.PsfMatchTask.__init__
112 Upon initialization, the kernel configuration is defined by self.config.kernel.active. This Task
113 does have a run() method, which
is the default way to call the Task.
115 PsfMatchTask.__init__(self, *args, **kwargs)
119 def run(self, exposure, referencePsfModel, kernelSum=1.0):
120 """Psf-match an exposure to a model Psf
125 Exposure to Psf-match to the reference Psf model;
126 it must return a valid PSF model via exposure.getPsf()
128 The Psf model to match to
129 kernelSum : `float`, optional
130 A multipicative factor to apply to the kernel sum (default=1.0)
135 - ``psfMatchedExposure`` : the Psf-matched Exposure.
136 This has the same parent bbox, Wcs, PhotoCalib
and
137 Filter
as the input Exposure but no Psf.
138 In theory the Psf should equal referencePsfModel but
139 the match
is likely
not exact.
140 - ``psfMatchingKernel`` : the spatially varying Psf-matching kernel
141 - ``kernelCellSet`` : SpatialCellSet used to solve
for the Psf-matching kernel
142 - ``referencePsfModel`` : Validated
and/
or modified reference model used
147 if the Exposure does
not contain a Psf model
149 if not exposure.hasPsf():
150 raise RuntimeError(
"exposure does not contain a Psf model")
152 maskedImage = exposure.getMaskedImage()
154 self.
log.info(
"compute Psf-matching kernel")
156 kernelCellSet = result.kernelCellSet
157 referencePsfModel = result.referencePsfModel
160 sciAvgPos = exposure.getPsf().getAveragePosition()
161 modelAvgPos = referencePsfModel.getAveragePosition()
162 fwhmScience = exposure.getPsf().computeShape(sciAvgPos).getDeterminantRadius()*sigma2fwhm
163 fwhmModel = referencePsfModel.computeShape(modelAvgPos).getDeterminantRadius()*sigma2fwhm
165 basisList = makeKernelBasisList(self.
kConfigkConfig, fwhmScience, fwhmModel, metadata=self.metadata)
166 spatialSolution, psfMatchingKernel, backgroundModel = self.
_solve(kernelCellSet, basisList)
168 if psfMatchingKernel.isSpatiallyVarying():
169 sParameters = np.array(psfMatchingKernel.getSpatialParameters())
170 sParameters[0][0] = kernelSum
171 psfMatchingKernel.setSpatialParameters(sParameters)
173 kParameters = np.array(psfMatchingKernel.getKernelParameters())
174 kParameters[0] = kernelSum
175 psfMatchingKernel.setKernelParameters(kParameters)
177 self.
log.info(
"Psf-match science exposure to reference")
178 psfMatchedExposure = afwImage.ExposureF(exposure.getBBox(), exposure.getWcs())
179 psfMatchedExposure.info.id = exposure.info.id
180 psfMatchedExposure.setFilter(exposure.getFilter())
181 psfMatchedExposure.setPhotoCalib(exposure.getPhotoCalib())
182 psfMatchedExposure.getInfo().setVisitInfo(exposure.getInfo().getVisitInfo())
183 psfMatchedExposure.setPsf(referencePsfModel)
184 psfMatchedMaskedImage = psfMatchedExposure.getMaskedImage()
189 convolutionControl.setDoNormalize(
True)
190 afwMath.convolve(psfMatchedMaskedImage, maskedImage, psfMatchingKernel, convolutionControl)
192 self.
log.info(
"done")
193 return pipeBase.Struct(psfMatchedExposure=psfMatchedExposure,
194 psfMatchingKernel=psfMatchingKernel,
195 kernelCellSet=kernelCellSet,
196 metadata=self.metadata,
199 def _diagnostic(self, kernelCellSet, spatialSolution, spatialKernel, spatialBg):
200 """Print diagnostic information on spatial kernel and background fit
202 The debugging diagnostics are not really useful here, since the images we are matching have
203 no variance. Thus override the _diagnostic method to generate no logging information
"""
207 """Build a SpatialCellSet for use with the solve method
212 The science exposure that will be convolved; must contain a Psf
214 Psf model to match to
219 - ``kernelCellSet`` : a SpatialCellSet to be used by self._solve
220 - ``referencePsfModel`` : Validated and/
or modified
221 reference model used to populate the SpatialCellSet
225 If the reference Psf model
and science Psf model have different dimensions,
226 adjust the referencePsfModel (the model to which the exposure PSF will be matched)
227 to match that of the science Psf. If the science Psf dimensions vary across the image,
228 as is common
with a WarpedPsf, either pad
or clip (depending on config.padPsf)
229 the dimensions to be constant.
234 scienceBBox = exposure.getBBox()
238 sciencePsfModel = exposure.getPsf()
240 dimenR = referencePsfModel.getLocalKernel(scienceBBox.getCenter()).getDimensions()
242 regionSizeX, regionSizeY = scienceBBox.getDimensions()
243 scienceX0, scienceY0 = scienceBBox.getMin()
247 nCellX = regionSizeX//sizeCellX
248 nCellY = regionSizeY//sizeCellY
250 if nCellX == 0
or nCellY == 0:
251 raise ValueError(
"Exposure dimensions=%s and sizeCell=(%s, %s). Insufficient area to match" %
252 (scienceBBox.getDimensions(), sizeCellX, sizeCellY))
258 for row
in range(nCellY):
259 posY = sizeCellY*row + sizeCellY//2 + scienceY0
260 for col
in range(nCellX):
261 posX = sizeCellX*col + sizeCellX//2 + scienceX0
262 widthS, heightS = sciencePsfModel.computeBBox(
geom.Point2D(posX, posY)).getDimensions()
263 widthList.append(widthS)
264 heightList.append(heightS)
266 psfSize =
max(
max(heightList),
max(widthList))
268 if self.config.doAutoPadPsf:
270 paddingPix =
max(0, minPsfSize - psfSize)
272 if self.config.padPsfBy % 2 != 0:
273 raise ValueError(
"Config padPsfBy (%i pixels) must be even number." %
274 self.config.padPsfBy)
275 paddingPix = self.config.padPsfBy
278 self.
log.debug(
"Padding Science PSF from (%d, %d) to (%d, %d) pixels",
279 psfSize, psfSize, paddingPix + psfSize, paddingPix + psfSize)
280 psfSize += paddingPix
283 maxKernelSize = psfSize - 1
284 if maxKernelSize % 2 == 0:
288 Kernel size (%d) too big to match Psfs of size %d.
289 Please reconfigure by setting one of the following:
290 1) kernel size to <= %d
293 """ % (self.kConfig.kernelSize, psfSize,
295 raise ValueError(message)
299 if (dimenR != dimenS):
301 referencePsfModel = referencePsfModel.resized(psfSize, psfSize)
302 self.
log.info(
"Adjusted dimensions of reference PSF model from %s to %s", dimenR, dimenS)
303 except Exception
as e:
304 self.
log.warning(
"Zero padding or clipping the reference PSF model of type %s and dimensions"
305 " %s to the science Psf dimensions %s because: %s",
306 referencePsfModel.__class__.__name__, dimenR, dimenS, e)
310 for row
in range(nCellY):
312 posY = sizeCellY*row + sizeCellY//2 + scienceY0
314 for col
in range(nCellX):
316 posX = sizeCellX*col + sizeCellX//2 + scienceX0
318 getTraceLogger(self.
log, 4).debug(
"Creating Psf candidate at %.1f %.1f", posX, posY)
327 kc = diffimLib.makeKernelCandidate(posX, posY, scienceMI, referenceMI, ps)
328 kernelCellSet.insertCandidate(kc)
332 displaySpatialCells =
lsstDebug.Info(__name__).displaySpatialCells
334 if not maskTransparency:
337 afwDisplay.setDefaultMaskTransparency(maskTransparency)
338 if display
and displaySpatialCells:
339 dituils.showKernelSpatialCells(exposure.getMaskedImage(), kernelCellSet,
340 symb=
"o", ctype=afwDisplay.CYAN, ctypeUnused=afwDisplay.YELLOW,
341 ctypeBad=afwDisplay.RED, size=4, frame=lsstDebug.frame,
342 title=
"Image to be convolved")
344 return pipeBase.Struct(kernelCellSet=kernelCellSet,
345 referencePsfModel=referencePsfModel,
349 """Return a MaskedImage of the a PSF Model of specified dimensions
351 rawKernel = psfModel.computeKernelImage(geom.Point2D(posX, posY)).convertF()
352 if dimensions
is None:
353 dimensions = rawKernel.getDimensions()
354 if rawKernel.getDimensions() == dimensions:
358 kernelIm = afwImage.ImageF(dimensions)
360 (dimensions.getY() - rawKernel.getHeight())//2),
361 rawKernel.getDimensions())
362 kernelIm.assign(rawKernel, bboxToPlace)
365 kernelVar = afwImage.ImageF(dimensions, 1.0)
366 return afwImage.MaskedImageF(kernelIm, kernelMask, kernelVar)
A polymorphic base class for representing an image's Point Spread Function.
A class to contain the data, WCS, and other information needed to describe an image of the sky.
Represent a 2-dimensional array of bitmask pixels.
Parameters to control convolution.
A collection of SpatialCells covering an entire image.
An integer coordinate rectangle.
_buildCellSet(self, exposure, referencePsfModel)
__init__(self, *args, **kwargs)
_diagnostic(self, kernelCellSet, spatialSolution, spatialKernel, spatialBg)
_makePsfMaskedImage(self, psfModel, posX, posY, dimensions=None)
_solve(self, kernelCellSet, basisList, returnOnExcept=False)
_buildCellSet(self, *args)
void convolve(OutImageT &convolvedImage, InImageT const &inImage, KernelT const &kernel, ConvolutionControl const &convolutionControl=ConvolutionControl())
Convolve an Image or MaskedImage with a Kernel, setting pixels of an existing output image.