New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
DM-32411: Make WarpedPsf delegate to source PSF's computeImage. #280
Conversation
I've been playing around with the following script to see how this PR is working: import lsst.afw.detection as afwDetection
import lsst.afw.image as afwImage
import lsst.afw.math as afwMath
import lsst.afw.geom as afwGeom
import lsst.geom as geom
import numpy as np
from lsst.meas.algorithms import WarpedPsf
class PyGaussianPsf(afwDetection.Psf):
# Like afwDetection.GaussianPsf, but handles computeImage exactly instead of
# via interpolation. This is a subminimal implementation. It works for the
# tests here but isn't fully functional as a Psf class.
def __init__(self, width, height, sigma):
afwDetection.Psf.__init__(self, isFixed=False)
self.dimensions = geom.Extent2I(width, height)
self.sigma = sigma
def _doComputeKernelImage(self, position=None, color=None):
bbox = self.computeBBox(position, color)
img = afwImage.Image(bbox, dtype=np.float64)
x, y = np.ogrid[bbox.minY:bbox.maxY+1, bbox.minX:bbox.maxX+1]
rsqr = x**2 + y**2
img.array[:] = np.exp(-0.5*rsqr/self.sigma**2)
img.array /= np.sum(img.array)
return img
def _doComputeImage(self, position=None, color=None):
bbox = self.computeBBox(position, color)
img = afwImage.Image(bbox, dtype=np.float64)
y, x = np.ogrid[float(bbox.minY):bbox.maxY+1, bbox.minX:bbox.maxX+1]
x -= (position.x - np.floor(position.x+0.5))
y -= (position.y - np.floor(position.y+0.5))
rsqr = x**2 + y**2
img.array[:] = np.exp(-0.5*rsqr/self.sigma**2)
img.array /= np.sum(img.array)
img.setXY0(geom.Point2I(
img.getX0() + np.floor(position.x+0.5),
img.getY0() + np.floor(position.y+0.5)
))
return img
def _doComputeBBox(self, position=None, color=None):
dims = self.dimensions
return geom.Box2I(geom.Point2I(-dims/2), dims)
def _doComputeShape(self, position=None, color=None):
return afwGeom.ellipses.Quadrupole(self.sigma**2, self.sigma**2, 0.0)
psf = PyGaussianPsf(15, 15, 2.0)
# psf = afwDetection.GaussianPsf(15, 15, 2.0)
transform = afwGeom.makeTransform(geom.AffineTransform([0.1, 0.1]))
warpingControl = afwMath.WarpingControl("lanczos3")
wPsf = WarpedPsf(psf, transform, warpingControl)
trueImg = psf.computeImage(geom.Point2D(0.0, 0.5))
warpedImg = wPsf.computeImage(geom.Point2D(0.0, 0.5))
print(warpedImg.array.shape) One thing I've noticed is that the |
Thinking about this some more... for a given |
At some level, the motivation for this change is not so much to avoid interpolation as to ensure the interpolation of the real image is the same as the interpolation of the PSF model. So even if we had a
Is this just for |
For both |
Ok, thanks for the confirmation. I'll go get a fix on the branch ASAP. |
d393e8c
to
5b53e2a
Compare
Josh's script now returns (17, 17) whereas the |
I don't anticipate anything concrete going wrong; I was leaning on the side of growing too much rather than growing too little, but there are effectively two places where we round up in that routine, so it's also not unreasonable to change that one to round down. Please go ahead and commit that change if it seems to make sizes stay more consistent. |
Instead of warping the result of a computeKernelImage call with a transform that does not shift at all, we now warp the result of a call to computeImage with a transform that includes a shift to (0, 0). When the source PSF has a custom computeImge implementation that can do better than just Lanczos-shift its own computeKernelImage result, this make the warping operation more like what stars on the corresponding images experience.
This may change the boxes slightly in other ways, too, but not by more than a pixel on each side.
76da96e
to
4620c95
Compare
No description provided.