184 def run(self, exposure, referencePsfModel, kernelSum=1.0):
185 """Psf-match an exposure to a model Psf.
189 exposure : `lsst.afw.image.Exposure`
190 Exposure to Psf-match to the reference Psf model;
191 it must return a valid PSF model via exposure.getPsf()
192 referencePsfModel : `lsst.afw.detection.Psf`
193 The Psf model to match to
194 kernelSum : `float`, optional
195 A multipicative factor to apply to the kernel sum (default=1.0)
200 - ``psfMatchedExposure`` : the Psf-matched Exposure.
201 This has the same parent bbox, Wcs, PhotoCalib and
202 Filter as the input Exposure but no Psf.
203 In theory the Psf should equal referencePsfModel but
204 the match is likely not exact.
205 - ``psfMatchingKernel`` : the spatially varying Psf-matching kernel
206 - ``kernelCellSet`` : SpatialCellSet used to solve for the Psf-matching kernel
207 - ``referencePsfModel`` : Validated and/or modified reference model used
212 if the Exposure does not contain a Psf model
214 if not exposure.hasPsf():
215 raise RuntimeError(
"exposure does not contain a Psf model")
217 maskedImage = exposure.getMaskedImage()
219 self.
log.info(
"compute Psf-matching kernel")
221 kernelCellSet = result.kernelCellSet
222 referencePsfModel = result.referencePsfModel
225 sciAvgPos = exposure.getPsf().getAveragePosition()
226 modelAvgPos = referencePsfModel.getAveragePosition()
232 log=self.
log).getDeterminantRadius()*sigma2fwhm
233 fwhmModel = referencePsfModel.computeShape(modelAvgPos).getDeterminantRadius()*sigma2fwhm
235 basisList = makeKernelBasisList(self.
kConfig, fwhmScience, fwhmModel, metadata=self.metadata)
236 spatialSolution, psfMatchingKernel, backgroundModel = self.
_solve(kernelCellSet, basisList)
238 if psfMatchingKernel.isSpatiallyVarying():
239 sParameters = np.array(psfMatchingKernel.getSpatialParameters())
240 sParameters[0][0] = kernelSum
241 psfMatchingKernel.setSpatialParameters(sParameters)
243 kParameters = np.array(psfMatchingKernel.getKernelParameters())
244 kParameters[0] = kernelSum
245 psfMatchingKernel.setKernelParameters(kParameters)
247 self.
log.info(
"Psf-match science exposure to reference")
248 psfMatchedExposure = afwImage.ExposureF(exposure.getBBox(), exposure.getWcs())
249 psfMatchedExposure.info.id = exposure.info.id
250 psfMatchedExposure.setFilter(exposure.getFilter())
251 psfMatchedExposure.setPhotoCalib(exposure.getPhotoCalib())
252 psfMatchedExposure.getInfo().setVisitInfo(exposure.getInfo().getVisitInfo())
253 psfMatchedExposure.setPsf(referencePsfModel)
254 psfMatchedMaskedImage = psfMatchedExposure.getMaskedImage()
259 convolutionControl.setDoNormalize(
True)
260 afwMath.convolve(psfMatchedMaskedImage, maskedImage, psfMatchingKernel, convolutionControl)
262 self.
log.info(
"done")
263 return pipeBase.Struct(psfMatchedExposure=psfMatchedExposure,
264 psfMatchingKernel=psfMatchingKernel,
265 kernelCellSet=kernelCellSet,
266 metadata=self.metadata,
277 """Build a SpatialCellSet for use with the solve method
281 exposure : `lsst.afw.image.Exposure`
282 The science exposure that will be convolved; must contain a Psf
283 referencePsfModel : `lsst.afw.detection.Psf`
284 Psf model to match to
289 - ``kernelCellSet`` : a SpatialCellSet to be used by self._solve
290 - ``referencePsfModel`` : Validated and/or modified
291 reference model used to populate the SpatialCellSet
295 If the reference Psf model and science Psf model have different dimensions,
296 adjust the referencePsfModel (the model to which the exposure PSF will be matched)
297 to match that of the science Psf. If the science Psf dimensions vary across the image,
298 as is common with a WarpedPsf, either pad or clip (depending on config.padPsf)
299 the dimensions to be constant.
301 sizeCellX = self.kConfig.sizeCellX
302 sizeCellY = self.kConfig.sizeCellY
304 scienceBBox = exposure.getBBox()
308 sciencePsfModel = exposure.getPsf()
310 dimenR = referencePsfModel.getLocalKernel(scienceBBox.getCenter()).getDimensions()
312 regionSizeX, regionSizeY = scienceBBox.getDimensions()
313 scienceX0, scienceY0 = scienceBBox.getMin()
317 nCellX = regionSizeX//sizeCellX
318 nCellY = regionSizeY//sizeCellY
320 if nCellX == 0
or nCellY == 0:
321 raise ValueError(
"Exposure dimensions=%s and sizeCell=(%s, %s). Insufficient area to match" %
322 (scienceBBox.getDimensions(), sizeCellX, sizeCellY))
328 for row
in range(nCellY):
329 posY = sizeCellY*row + sizeCellY//2 + scienceY0
330 for col
in range(nCellX):
331 posX = sizeCellX*col + sizeCellX//2 + scienceX0
333 widthS, heightS = sciencePsfModel.computeBBox(
geom.Point2D(posX, posY)).getDimensions()
334 except pexExceptions.InvalidParameterError
as err:
335 raise PsfComputeShapeError(
geom.Point2D(posX, posY))
from err
336 widthList.append(widthS)
337 heightList.append(heightS)
339 psfSize = max(max(heightList), max(widthList))
341 if self.config.doAutoPadPsf:
342 minPsfSize =
nextOddInteger(self.kConfig.kernelSize*self.config.autoPadPsfTo)
343 paddingPix = max(0, minPsfSize - psfSize)
345 if self.config.padPsfBy % 2 != 0:
346 raise ValueError(
"Config padPsfBy (%i pixels) must be even number." %
347 self.config.padPsfBy)
348 paddingPix = self.config.padPsfBy
351 self.log.debug(
"Padding Science PSF from (%d, %d) to (%d, %d) pixels",
352 psfSize, psfSize, paddingPix + psfSize, paddingPix + psfSize)
353 psfSize += paddingPix
356 maxKernelSize = psfSize - 1
357 if maxKernelSize % 2 == 0:
359 if self.kConfig.kernelSize > maxKernelSize:
361 Kernel size (%d) too big to match Psfs of size %d.
362 Please reconfigure by setting one of the following:
363 1) kernel size to <= %d
366 """ % (self.kConfig.kernelSize, psfSize,
367 maxKernelSize, self.kConfig.kernelSize - maxKernelSize)
368 raise ValueError(message)
372 if (dimenR != dimenS):
374 referencePsfModel = referencePsfModel.resized(psfSize, psfSize)
375 self.log.info(
"Adjusted dimensions of reference PSF model from %s to %s", dimenR, dimenS)
376 except Exception
as e:
377 self.log.warning(
"Zero padding or clipping the reference PSF model of type %s and dimensions"
378 " %s to the science Psf dimensions %s because: %s",
379 referencePsfModel.__class__.__name__, dimenR, dimenS, e)
382 ps = pexConfig.makePropertySet(self.kConfig)
383 for row
in range(nCellY):
385 posY = sizeCellY*row + sizeCellY//2 + scienceY0
387 for col
in range(nCellX):
389 posX = sizeCellX*col + sizeCellX//2 + scienceX0
391 getTraceLogger(self.
log, 4).debug(
"Creating Psf candidate at %.1f %.1f", posX, posY)
400 kc = diffimLib.makeKernelCandidate(posX, posY, scienceMI, referenceMI, ps)
401 kernelCellSet.insertCandidate(kc)
405 displaySpatialCells =
lsstDebug.Info(__name__).displaySpatialCells
407 if not maskTransparency:
410 afwDisplay.setDefaultMaskTransparency(maskTransparency)
411 if display
and displaySpatialCells:
412 dituils.showKernelSpatialCells(exposure.getMaskedImage(), kernelCellSet,
413 symb=
"o", ctype=afwDisplay.CYAN, ctypeUnused=afwDisplay.YELLOW,
414 ctypeBad=afwDisplay.RED, size=4, frame=lsstDebug.frame,
415 title=
"Image to be convolved")
417 return pipeBase.Struct(kernelCellSet=kernelCellSet,
418 referencePsfModel=referencePsfModel,
422 """Return a MaskedImage of the a PSF Model of specified dimensions
424 rawKernel = psfModel.computeKernelImage(
geom.Point2D(posX, posY)).convertF()
425 if dimensions
is None:
426 dimensions = rawKernel.getDimensions()
427 if rawKernel.getDimensions() == dimensions:
431 kernelIm = afwImage.ImageF(dimensions)
433 (dimensions.getY() - rawKernel.getHeight())//2),
434 rawKernel.getDimensions())
435 kernelIm.assign(rawKernel, bboxToPlace)
438 kernelVar = afwImage.ImageF(dimensions, 1.0)
439 return afwImage.MaskedImageF(kernelIm, kernelMask, kernelVar)