133 def run(self, exposure, referencePsfModel, kernelSum=1.0):
134 """Psf-match an exposure to a model Psf.
138 exposure : `lsst.afw.image.Exposure`
139 Exposure to Psf-match to the reference Psf model;
140 it must return a valid PSF model via exposure.getPsf()
141 referencePsfModel : `lsst.afw.detection.Psf`
142 The Psf model to match to
143 kernelSum : `float`, optional
144 A multipicative factor to apply to the kernel sum (default=1.0)
149 - ``psfMatchedExposure`` : the Psf-matched Exposure.
150 This has the same parent bbox, Wcs, PhotoCalib and
151 Filter as the input Exposure but no Psf.
152 In theory the Psf should equal referencePsfModel but
153 the match is likely not exact.
154 - ``psfMatchingKernel`` : the spatially varying Psf-matching kernel
155 - ``kernelCellSet`` : SpatialCellSet used to solve for the Psf-matching kernel
156 - ``referencePsfModel`` : Validated and/or modified reference model used
161 if the Exposure does not contain a Psf model
163 if not exposure.hasPsf():
164 raise RuntimeError(
"exposure does not contain a Psf model")
166 maskedImage = exposure.getMaskedImage()
168 self.
log.info(
"compute Psf-matching kernel")
170 kernelCellSet = result.kernelCellSet
171 referencePsfModel = result.referencePsfModel
174 sciAvgPos = exposure.getPsf().getAveragePosition()
175 modelAvgPos = referencePsfModel.getAveragePosition()
177 fwhmScience = exposure.getPsf().computeShape(sciAvgPos).getDeterminantRadius()*sigma2fwhm
178 except pexExceptions.RangeError:
180 f
"Unable to compute the FWHM of the science Psf at {sciAvgPos}"
181 "due to an unexpectedly large transform."
183 except pexExceptions.Exception:
185 fwhmModel = referencePsfModel.computeShape(modelAvgPos).getDeterminantRadius()*sigma2fwhm
187 basisList = makeKernelBasisList(self.
kConfig, fwhmScience, fwhmModel, metadata=self.metadata)
188 spatialSolution, psfMatchingKernel, backgroundModel = self.
_solve(kernelCellSet, basisList)
190 if psfMatchingKernel.isSpatiallyVarying():
191 sParameters = np.array(psfMatchingKernel.getSpatialParameters())
192 sParameters[0][0] = kernelSum
193 psfMatchingKernel.setSpatialParameters(sParameters)
195 kParameters = np.array(psfMatchingKernel.getKernelParameters())
196 kParameters[0] = kernelSum
197 psfMatchingKernel.setKernelParameters(kParameters)
199 self.
log.info(
"Psf-match science exposure to reference")
200 psfMatchedExposure = afwImage.ExposureF(exposure.getBBox(), exposure.getWcs())
201 psfMatchedExposure.info.id = exposure.info.id
202 psfMatchedExposure.setFilter(exposure.getFilter())
203 psfMatchedExposure.setPhotoCalib(exposure.getPhotoCalib())
204 psfMatchedExposure.getInfo().setVisitInfo(exposure.getInfo().getVisitInfo())
205 psfMatchedExposure.setPsf(referencePsfModel)
206 psfMatchedMaskedImage = psfMatchedExposure.getMaskedImage()
211 convolutionControl.setDoNormalize(
True)
212 afwMath.convolve(psfMatchedMaskedImage, maskedImage, psfMatchingKernel, convolutionControl)
214 self.
log.info(
"done")
215 return pipeBase.Struct(psfMatchedExposure=psfMatchedExposure,
216 psfMatchingKernel=psfMatchingKernel,
217 kernelCellSet=kernelCellSet,
218 metadata=self.metadata,
229 """Build a SpatialCellSet for use with the solve method
233 exposure : `lsst.afw.image.Exposure`
234 The science exposure that will be convolved; must contain a Psf
235 referencePsfModel : `lsst.afw.detection.Psf`
236 Psf model to match to
241 - ``kernelCellSet`` : a SpatialCellSet to be used by self._solve
242 - ``referencePsfModel`` : Validated and/or modified
243 reference model used to populate the SpatialCellSet
247 If the reference Psf model and science Psf model have different dimensions,
248 adjust the referencePsfModel (the model to which the exposure PSF will be matched)
249 to match that of the science Psf. If the science Psf dimensions vary across the image,
250 as is common with a WarpedPsf, either pad or clip (depending on config.padPsf)
251 the dimensions to be constant.
253 sizeCellX = self.kConfig.sizeCellX
254 sizeCellY = self.kConfig.sizeCellY
256 scienceBBox = exposure.getBBox()
260 sciencePsfModel = exposure.getPsf()
262 dimenR = referencePsfModel.getLocalKernel(scienceBBox.getCenter()).getDimensions()
264 regionSizeX, regionSizeY = scienceBBox.getDimensions()
265 scienceX0, scienceY0 = scienceBBox.getMin()
269 nCellX = regionSizeX//sizeCellX
270 nCellY = regionSizeY//sizeCellY
272 if nCellX == 0
or nCellY == 0:
273 raise ValueError(
"Exposure dimensions=%s and sizeCell=(%s, %s). Insufficient area to match" %
274 (scienceBBox.getDimensions(), sizeCellX, sizeCellY))
280 for row
in range(nCellY):
281 posY = sizeCellY*row + sizeCellY//2 + scienceY0
282 for col
in range(nCellX):
283 posX = sizeCellX*col + sizeCellX//2 + scienceX0
284 widthS, heightS = sciencePsfModel.computeBBox(
geom.Point2D(posX, posY)).getDimensions()
285 widthList.append(widthS)
286 heightList.append(heightS)
288 psfSize = max(max(heightList), max(widthList))
290 if self.config.doAutoPadPsf:
291 minPsfSize =
nextOddInteger(self.kConfig.kernelSize*self.config.autoPadPsfTo)
292 paddingPix = max(0, minPsfSize - psfSize)
294 if self.config.padPsfBy % 2 != 0:
295 raise ValueError(
"Config padPsfBy (%i pixels) must be even number." %
296 self.config.padPsfBy)
297 paddingPix = self.config.padPsfBy
300 self.log.debug(
"Padding Science PSF from (%d, %d) to (%d, %d) pixels",
301 psfSize, psfSize, paddingPix + psfSize, paddingPix + psfSize)
302 psfSize += paddingPix
305 maxKernelSize = psfSize - 1
306 if maxKernelSize % 2 == 0:
308 if self.kConfig.kernelSize > maxKernelSize:
310 Kernel size (%d) too big to match Psfs of size %d.
311 Please reconfigure by setting one of the following:
312 1) kernel size to <= %d
315 """ % (self.kConfig.kernelSize, psfSize,
316 maxKernelSize, self.kConfig.kernelSize - maxKernelSize)
317 raise ValueError(message)
321 if (dimenR != dimenS):
323 referencePsfModel = referencePsfModel.resized(psfSize, psfSize)
324 self.log.info(
"Adjusted dimensions of reference PSF model from %s to %s", dimenR, dimenS)
325 except Exception
as e:
326 self.log.warning(
"Zero padding or clipping the reference PSF model of type %s and dimensions"
327 " %s to the science Psf dimensions %s because: %s",
328 referencePsfModel.__class__.__name__, dimenR, dimenS, e)
331 ps = pexConfig.makePropertySet(self.kConfig)
332 for row
in range(nCellY):
334 posY = sizeCellY*row + sizeCellY//2 + scienceY0
336 for col
in range(nCellX):
338 posX = sizeCellX*col + sizeCellX//2 + scienceX0
340 getTraceLogger(self.
log, 4).debug(
"Creating Psf candidate at %.1f %.1f", posX, posY)
349 kc = diffimLib.makeKernelCandidate(posX, posY, scienceMI, referenceMI, ps)
350 kernelCellSet.insertCandidate(kc)
354 displaySpatialCells =
lsstDebug.Info(__name__).displaySpatialCells
356 if not maskTransparency:
359 afwDisplay.setDefaultMaskTransparency(maskTransparency)
360 if display
and displaySpatialCells:
361 dituils.showKernelSpatialCells(exposure.getMaskedImage(), kernelCellSet,
362 symb=
"o", ctype=afwDisplay.CYAN, ctypeUnused=afwDisplay.YELLOW,
363 ctypeBad=afwDisplay.RED, size=4, frame=lsstDebug.frame,
364 title=
"Image to be convolved")
366 return pipeBase.Struct(kernelCellSet=kernelCellSet,
367 referencePsfModel=referencePsfModel,
371 """Return a MaskedImage of the a PSF Model of specified dimensions
373 rawKernel = psfModel.computeKernelImage(
geom.Point2D(posX, posY)).convertF()
374 if dimensions
is None:
375 dimensions = rawKernel.getDimensions()
376 if rawKernel.getDimensions() == dimensions:
380 kernelIm = afwImage.ImageF(dimensions)
382 (dimensions.getY() - rawKernel.getHeight())//2),
383 rawKernel.getDimensions())
384 kernelIm.assign(rawKernel, bboxToPlace)
387 kernelVar = afwImage.ImageF(dimensions, 1.0)
388 return afwImage.MaskedImageF(kernelIm, kernelMask, kernelVar)