131 def run(self, exposure, referencePsfModel, kernelSum=1.0):
132 """Psf-match an exposure to a model Psf.
136 exposure : `lsst.afw.image.Exposure`
137 Exposure to Psf-match to the reference Psf model;
138 it must return a valid PSF model via exposure.getPsf()
139 referencePsfModel : `lsst.afw.detection.Psf`
140 The Psf model to match to
141 kernelSum : `float`, optional
142 A multipicative factor to apply to the kernel sum (default=1.0)
147 - ``psfMatchedExposure`` : the Psf-matched Exposure.
148 This has the same parent bbox, Wcs, PhotoCalib and
149 Filter as the input Exposure but no Psf.
150 In theory the Psf should equal referencePsfModel but
151 the match is likely not exact.
152 - ``psfMatchingKernel`` : the spatially varying Psf-matching kernel
153 - ``kernelCellSet`` : SpatialCellSet used to solve for the Psf-matching kernel
154 - ``referencePsfModel`` : Validated and/or modified reference model used
159 if the Exposure does not contain a Psf model
161 if not exposure.hasPsf():
162 raise RuntimeError(
"exposure does not contain a Psf model")
164 maskedImage = exposure.getMaskedImage()
166 self.
log.info(
"compute Psf-matching kernel")
168 kernelCellSet = result.kernelCellSet
169 referencePsfModel = result.referencePsfModel
172 sciAvgPos = exposure.getPsf().getAveragePosition()
173 modelAvgPos = referencePsfModel.getAveragePosition()
175 fwhmScience = exposure.getPsf().computeShape(sciAvgPos).getDeterminantRadius()*sigma2fwhm
176 except pexExceptions.RangeError:
178 f
"Unable to compute the FWHM of the science Psf at {sciAvgPos}"
179 "due to an unexpectedly large transform."
181 except pexExceptions.Exception:
183 fwhmModel = referencePsfModel.computeShape(modelAvgPos).getDeterminantRadius()*sigma2fwhm
185 basisList = makeKernelBasisList(self.
kConfig, fwhmScience, fwhmModel, metadata=self.metadata)
186 spatialSolution, psfMatchingKernel, backgroundModel = self.
_solve(kernelCellSet, basisList)
188 if psfMatchingKernel.isSpatiallyVarying():
189 sParameters = np.array(psfMatchingKernel.getSpatialParameters())
190 sParameters[0][0] = kernelSum
191 psfMatchingKernel.setSpatialParameters(sParameters)
193 kParameters = np.array(psfMatchingKernel.getKernelParameters())
194 kParameters[0] = kernelSum
195 psfMatchingKernel.setKernelParameters(kParameters)
197 self.
log.info(
"Psf-match science exposure to reference")
198 psfMatchedExposure = afwImage.ExposureF(exposure.getBBox(), exposure.getWcs())
199 psfMatchedExposure.info.id = exposure.info.id
200 psfMatchedExposure.setFilter(exposure.getFilter())
201 psfMatchedExposure.setPhotoCalib(exposure.getPhotoCalib())
202 psfMatchedExposure.getInfo().setVisitInfo(exposure.getInfo().getVisitInfo())
203 psfMatchedExposure.setPsf(referencePsfModel)
204 psfMatchedMaskedImage = psfMatchedExposure.getMaskedImage()
209 convolutionControl.setDoNormalize(
True)
210 afwMath.convolve(psfMatchedMaskedImage, maskedImage, psfMatchingKernel, convolutionControl)
212 self.
log.info(
"done")
213 return pipeBase.Struct(psfMatchedExposure=psfMatchedExposure,
214 psfMatchingKernel=psfMatchingKernel,
215 kernelCellSet=kernelCellSet,
216 metadata=self.metadata,
227 """Build a SpatialCellSet for use with the solve method
231 exposure : `lsst.afw.image.Exposure`
232 The science exposure that will be convolved; must contain a Psf
233 referencePsfModel : `lsst.afw.detection.Psf`
234 Psf model to match to
239 - ``kernelCellSet`` : a SpatialCellSet to be used by self._solve
240 - ``referencePsfModel`` : Validated and/or modified
241 reference model used to populate the SpatialCellSet
245 If the reference Psf model and science Psf model have different dimensions,
246 adjust the referencePsfModel (the model to which the exposure PSF will be matched)
247 to match that of the science Psf. If the science Psf dimensions vary across the image,
248 as is common with a WarpedPsf, either pad or clip (depending on config.padPsf)
249 the dimensions to be constant.
251 sizeCellX = self.kConfig.sizeCellX
252 sizeCellY = self.kConfig.sizeCellY
254 scienceBBox = exposure.getBBox()
258 sciencePsfModel = exposure.getPsf()
260 dimenR = referencePsfModel.getLocalKernel(scienceBBox.getCenter()).getDimensions()
262 regionSizeX, regionSizeY = scienceBBox.getDimensions()
263 scienceX0, scienceY0 = scienceBBox.getMin()
267 nCellX = regionSizeX//sizeCellX
268 nCellY = regionSizeY//sizeCellY
270 if nCellX == 0
or nCellY == 0:
271 raise ValueError(
"Exposure dimensions=%s and sizeCell=(%s, %s). Insufficient area to match" %
272 (scienceBBox.getDimensions(), sizeCellX, sizeCellY))
278 for row
in range(nCellY):
279 posY = sizeCellY*row + sizeCellY//2 + scienceY0
280 for col
in range(nCellX):
281 posX = sizeCellX*col + sizeCellX//2 + scienceX0
282 widthS, heightS = sciencePsfModel.computeBBox(
geom.Point2D(posX, posY)).getDimensions()
283 widthList.append(widthS)
284 heightList.append(heightS)
286 psfSize = max(max(heightList), max(widthList))
288 if self.config.doAutoPadPsf:
289 minPsfSize =
nextOddInteger(self.kConfig.kernelSize*self.config.autoPadPsfTo)
290 paddingPix = max(0, minPsfSize - psfSize)
292 if self.config.padPsfBy % 2 != 0:
293 raise ValueError(
"Config padPsfBy (%i pixels) must be even number." %
294 self.config.padPsfBy)
295 paddingPix = self.config.padPsfBy
298 self.log.debug(
"Padding Science PSF from (%d, %d) to (%d, %d) pixels",
299 psfSize, psfSize, paddingPix + psfSize, paddingPix + psfSize)
300 psfSize += paddingPix
303 maxKernelSize = psfSize - 1
304 if maxKernelSize % 2 == 0:
306 if self.kConfig.kernelSize > maxKernelSize:
308 Kernel size (%d) too big to match Psfs of size %d.
309 Please reconfigure by setting one of the following:
310 1) kernel size to <= %d
313 """ % (self.kConfig.kernelSize, psfSize,
314 maxKernelSize, self.kConfig.kernelSize - maxKernelSize)
315 raise ValueError(message)
319 if (dimenR != dimenS):
321 referencePsfModel = referencePsfModel.resized(psfSize, psfSize)
322 self.log.info(
"Adjusted dimensions of reference PSF model from %s to %s", dimenR, dimenS)
323 except Exception
as e:
324 self.log.warning(
"Zero padding or clipping the reference PSF model of type %s and dimensions"
325 " %s to the science Psf dimensions %s because: %s",
326 referencePsfModel.__class__.__name__, dimenR, dimenS, e)
329 ps = pexConfig.makePropertySet(self.kConfig)
330 for row
in range(nCellY):
332 posY = sizeCellY*row + sizeCellY//2 + scienceY0
334 for col
in range(nCellX):
336 posX = sizeCellX*col + sizeCellX//2 + scienceX0
338 getTraceLogger(self.
log, 4).debug(
"Creating Psf candidate at %.1f %.1f", posX, posY)
347 kc = diffimLib.makeKernelCandidate(posX, posY, scienceMI, referenceMI, ps)
348 kernelCellSet.insertCandidate(kc)
352 displaySpatialCells =
lsstDebug.Info(__name__).displaySpatialCells
354 if not maskTransparency:
357 afwDisplay.setDefaultMaskTransparency(maskTransparency)
358 if display
and displaySpatialCells:
359 dituils.showKernelSpatialCells(exposure.getMaskedImage(), kernelCellSet,
360 symb=
"o", ctype=afwDisplay.CYAN, ctypeUnused=afwDisplay.YELLOW,
361 ctypeBad=afwDisplay.RED, size=4, frame=lsstDebug.frame,
362 title=
"Image to be convolved")
364 return pipeBase.Struct(kernelCellSet=kernelCellSet,
365 referencePsfModel=referencePsfModel,
369 """Return a MaskedImage of the a PSF Model of specified dimensions
371 rawKernel = psfModel.computeKernelImage(
geom.Point2D(posX, posY)).convertF()
372 if dimensions
is None:
373 dimensions = rawKernel.getDimensions()
374 if rawKernel.getDimensions() == dimensions:
378 kernelIm = afwImage.ImageF(dimensions)
380 (dimensions.getY() - rawKernel.getHeight())//2),
381 rawKernel.getDimensions())
382 kernelIm.assign(rawKernel, bboxToPlace)
385 kernelVar = afwImage.ImageF(dimensions, 1.0)
386 return afwImage.MaskedImageF(kernelIm, kernelMask, kernelVar)