119 def run(self, exposure, referencePsfModel, kernelSum=1.0):
120 """Psf-match an exposure to a model Psf
124 exposure : `lsst.afw.image.Exposure`
125 Exposure to Psf-match to the reference Psf model;
126 it must return a valid PSF model via exposure.getPsf()
127 referencePsfModel : `lsst.afw.detection.Psf`
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,
207 """Build a SpatialCellSet for use with the solve method
211 exposure : `lsst.afw.image.Exposure`
212 The science exposure that will be convolved; must contain a Psf
213 referencePsfModel : `lsst.afw.detection.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.
231 sizeCellX = self.kConfig.sizeCellX
232 sizeCellY = self.kConfig.sizeCellY
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:
269 minPsfSize =
nextOddInteger(self.kConfig.kernelSize*self.config.autoPadPsfTo)
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:
286 if self.kConfig.kernelSize > maxKernelSize:
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,
294 maxKernelSize, self.kConfig.kernelSize - maxKernelSize)
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)
309 ps = pexConfig.makePropertySet(self.kConfig)
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)