105 def run(self, exposure, referencePsfModel, kernelSum=1.0):
106 """Psf-match an exposure to a model Psf.
110 exposure : `lsst.afw.image.Exposure`
111 Exposure to Psf-match to the reference Psf model;
112 it must return a valid PSF model via exposure.getPsf()
113 referencePsfModel : `lsst.afw.detection.Psf`
114 The Psf model to match to
115 kernelSum : `float`, optional
116 A multipicative factor to apply to the kernel sum (default=1.0)
121 - ``psfMatchedExposure`` : the Psf-matched Exposure.
122 This has the same parent bbox, Wcs, PhotoCalib and
123 Filter as the input Exposure but no Psf.
124 In theory the Psf should equal referencePsfModel but
125 the match is likely not exact.
126 - ``psfMatchingKernel`` : the spatially varying Psf-matching kernel
127 - ``kernelCellSet`` : SpatialCellSet used to solve for the Psf-matching kernel
128 - ``referencePsfModel`` : Validated and/or modified reference model used
133 if the Exposure does not contain a Psf model
135 if not exposure.hasPsf():
136 raise RuntimeError(
"exposure does not contain a Psf model")
138 maskedImage = exposure.getMaskedImage()
140 self.
log.info(
"compute Psf-matching kernel")
142 kernelCellSet = result.kernelCellSet
143 referencePsfModel = result.referencePsfModel
146 sciAvgPos = exposure.getPsf().getAveragePosition()
147 modelAvgPos = referencePsfModel.getAveragePosition()
148 fwhmScience = exposure.getPsf().computeShape(sciAvgPos).getDeterminantRadius()*sigma2fwhm
149 fwhmModel = referencePsfModel.computeShape(modelAvgPos).getDeterminantRadius()*sigma2fwhm
151 basisList = makeKernelBasisList(self.
kConfigkConfig, fwhmScience, fwhmModel, metadata=self.metadata)
152 spatialSolution, psfMatchingKernel, backgroundModel = self.
_solve(kernelCellSet, basisList)
154 if psfMatchingKernel.isSpatiallyVarying():
155 sParameters = np.array(psfMatchingKernel.getSpatialParameters())
156 sParameters[0][0] = kernelSum
157 psfMatchingKernel.setSpatialParameters(sParameters)
159 kParameters = np.array(psfMatchingKernel.getKernelParameters())
160 kParameters[0] = kernelSum
161 psfMatchingKernel.setKernelParameters(kParameters)
163 self.
log.info(
"Psf-match science exposure to reference")
164 psfMatchedExposure = afwImage.ExposureF(exposure.getBBox(), exposure.getWcs())
165 psfMatchedExposure.info.id = exposure.info.id
166 psfMatchedExposure.setFilter(exposure.getFilter())
167 psfMatchedExposure.setPhotoCalib(exposure.getPhotoCalib())
168 psfMatchedExposure.getInfo().setVisitInfo(exposure.getInfo().getVisitInfo())
169 psfMatchedExposure.setPsf(referencePsfModel)
170 psfMatchedMaskedImage = psfMatchedExposure.getMaskedImage()
175 convolutionControl.setDoNormalize(
True)
176 afwMath.convolve(psfMatchedMaskedImage, maskedImage, psfMatchingKernel, convolutionControl)
178 self.
log.info(
"done")
179 return pipeBase.Struct(psfMatchedExposure=psfMatchedExposure,
180 psfMatchingKernel=psfMatchingKernel,
181 kernelCellSet=kernelCellSet,
182 metadata=self.metadata,
193 """Build a SpatialCellSet for use with the solve method
197 exposure : `lsst.afw.image.Exposure`
198 The science exposure that will be convolved; must contain a Psf
199 referencePsfModel : `lsst.afw.detection.Psf`
200 Psf model to match to
205 - ``kernelCellSet`` : a SpatialCellSet to be used by self._solve
206 - ``referencePsfModel`` : Validated and/or modified
207 reference model used to populate the SpatialCellSet
211 If the reference Psf model and science Psf model have different dimensions,
212 adjust the referencePsfModel (the model to which the exposure PSF will be matched)
213 to match that of the science Psf. If the science Psf dimensions vary across the image,
214 as is common with a WarpedPsf, either pad or clip (depending on config.padPsf)
215 the dimensions to be constant.
217 sizeCellX = self.kConfig.sizeCellX
218 sizeCellY = self.kConfig.sizeCellY
220 scienceBBox = exposure.getBBox()
224 sciencePsfModel = exposure.getPsf()
226 dimenR = referencePsfModel.getLocalKernel(scienceBBox.getCenter()).getDimensions()
228 regionSizeX, regionSizeY = scienceBBox.getDimensions()
229 scienceX0, scienceY0 = scienceBBox.getMin()
233 nCellX = regionSizeX//sizeCellX
234 nCellY = regionSizeY//sizeCellY
236 if nCellX == 0
or nCellY == 0:
237 raise ValueError(
"Exposure dimensions=%s and sizeCell=(%s, %s). Insufficient area to match" %
238 (scienceBBox.getDimensions(), sizeCellX, sizeCellY))
244 for row
in range(nCellY):
245 posY = sizeCellY*row + sizeCellY//2 + scienceY0
246 for col
in range(nCellX):
247 posX = sizeCellX*col + sizeCellX//2 + scienceX0
248 widthS, heightS = sciencePsfModel.computeBBox(
geom.Point2D(posX, posY)).getDimensions()
249 widthList.append(widthS)
250 heightList.append(heightS)
252 psfSize =
max(
max(heightList),
max(widthList))
254 if self.config.doAutoPadPsf:
255 minPsfSize =
nextOddInteger(self.kConfig.kernelSize*self.config.autoPadPsfTo)
256 paddingPix =
max(0, minPsfSize - psfSize)
258 if self.config.padPsfBy % 2 != 0:
259 raise ValueError(
"Config padPsfBy (%i pixels) must be even number." %
260 self.config.padPsfBy)
261 paddingPix = self.config.padPsfBy
264 self.log.debug(
"Padding Science PSF from (%d, %d) to (%d, %d) pixels",
265 psfSize, psfSize, paddingPix + psfSize, paddingPix + psfSize)
266 psfSize += paddingPix
269 maxKernelSize = psfSize - 1
270 if maxKernelSize % 2 == 0:
272 if self.kConfig.kernelSize > maxKernelSize:
274 Kernel size (%d) too big to match Psfs of size %d.
275 Please reconfigure by setting one of the following:
276 1) kernel size to <= %d
279 """ % (self.kConfig.kernelSize, psfSize,
280 maxKernelSize, self.kConfig.kernelSize - maxKernelSize)
281 raise ValueError(message)
285 if (dimenR != dimenS):
287 referencePsfModel = referencePsfModel.resized(psfSize, psfSize)
288 self.log.info(
"Adjusted dimensions of reference PSF model from %s to %s", dimenR, dimenS)
289 except Exception
as e:
290 self.log.warning(
"Zero padding or clipping the reference PSF model of type %s and dimensions"
291 " %s to the science Psf dimensions %s because: %s",
292 referencePsfModel.__class__.__name__, dimenR, dimenS, e)
295 ps = pexConfig.makePropertySet(self.kConfig)
296 for row
in range(nCellY):
298 posY = sizeCellY*row + sizeCellY//2 + scienceY0
300 for col
in range(nCellX):
302 posX = sizeCellX*col + sizeCellX//2 + scienceX0
304 getTraceLogger(self.
log, 4).debug(
"Creating Psf candidate at %.1f %.1f", posX, posY)
313 kc = diffimLib.makeKernelCandidate(posX, posY, scienceMI, referenceMI, ps)
314 kernelCellSet.insertCandidate(kc)
318 displaySpatialCells =
lsstDebug.Info(__name__).displaySpatialCells
320 if not maskTransparency:
323 afwDisplay.setDefaultMaskTransparency(maskTransparency)
324 if display
and displaySpatialCells:
325 dituils.showKernelSpatialCells(exposure.getMaskedImage(), kernelCellSet,
326 symb=
"o", ctype=afwDisplay.CYAN, ctypeUnused=afwDisplay.YELLOW,
327 ctypeBad=afwDisplay.RED, size=4, frame=lsstDebug.frame,
328 title=
"Image to be convolved")
330 return pipeBase.Struct(kernelCellSet=kernelCellSet,
331 referencePsfModel=referencePsfModel,
335 """Return a MaskedImage of the a PSF Model of specified dimensions
337 rawKernel = psfModel.computeKernelImage(
geom.Point2D(posX, posY)).convertF()
338 if dimensions
is None:
339 dimensions = rawKernel.getDimensions()
340 if rawKernel.getDimensions() == dimensions:
344 kernelIm = afwImage.ImageF(dimensions)
346 (dimensions.getY() - rawKernel.getHeight())//2),
347 rawKernel.getDimensions())
348 kernelIm.assign(rawKernel, bboxToPlace)
351 kernelVar = afwImage.ImageF(dimensions, 1.0)
352 return afwImage.MaskedImageF(kernelIm, kernelMask, kernelVar)