Skip to content

Commit

Permalink
Merge pull request #296 from lsst/tickets/DM-36071
Browse files Browse the repository at this point in the history
DM-36071: Deprecate kernelSize* parameters in PSF determiner tasks
  • Loading branch information
arunkannawadi committed Oct 14, 2022
2 parents f5d53e8 + caa1230 commit 169e04b
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 63 deletions.
16 changes: 6 additions & 10 deletions python/lsst/meas/algorithms/makePsfCandidates.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,13 @@


class MakePsfCandidatesConfig(pexConfig.Config):
kernelSize = pexConfig.Field(
doc="size of the kernel to create",
dtype=int,
kernelSize = pexConfig.Field[int](
doc="Size of the postage stamp in pixels (excluding the border) around each star that is extracted "
"for fitting. Should be odd and preferably at least 25.",
default=25,
)
borderWidth = pexConfig.Field(
doc="number of pixels to ignore around the edge of PSF candidate postage stamps",
dtype=int,
borderWidth = pexConfig.Field[int](
doc="Number of pixels to ignore around the edge of PSF candidate postage stamps",
default=0,
)

Expand All @@ -51,9 +50,6 @@ class MakePsfCandidatesTask(pipeBase.Task):
ConfigClass = MakePsfCandidatesConfig
_DefaultName = "makePsfCandidates"

def __init__(self, **kwds):
pipeBase.Task.__init__(self, **kwds)

def run(self, starCat, exposure, psfCandidateField=None):
"""Make a list of PSF candidates from a star catalog.
Expand All @@ -64,7 +60,7 @@ def run(self, starCat, exposure, psfCandidateField=None):
``lsst.meas.algorithms.starSelector.run()``.
exposure : `lsst.afw.image.Exposure`
The exposure containing the sources.
psfCandidateField : `str` or None
psfCandidateField : `str`, optional
Name of flag field to set True for PSF candidates, or None to not
set a field; the field is left unchanged for non-candidates.
Expand Down
75 changes: 35 additions & 40 deletions python/lsst/meas/algorithms/pcaPsfDeterminer.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,97 +68,84 @@ def numCandidatesToReject(numBadCandidates, numIter, totalIter):


class PcaPsfDeterminerConfig(BasePsfDeterminerTask.ConfigClass):
nonLinearSpatialFit = pexConfig.Field(
nonLinearSpatialFit = pexConfig.Field[bool](
doc="Use non-linear fitter for spatial variation of Kernel",
dtype=bool,
default=False,
)
nEigenComponents = pexConfig.Field(
nEigenComponents = pexConfig.Field[int](
doc="number of eigen components for PSF kernel creation",
dtype=int,
default=4,
check=lambda x: x >= 1
)
spatialOrder = pexConfig.Field(
spatialOrder = pexConfig.Field[int](
doc="specify spatial order for PSF kernel creation",
dtype=int,
default=2,
)
sizeCellX = pexConfig.Field(
sizeCellX = pexConfig.Field[int](
doc="size of cell used to determine PSF (pixels, column direction)",
dtype=int,
default=256,
# minValue = 10,
check=lambda x: x >= 10,
)
sizeCellY = pexConfig.Field(
sizeCellY = pexConfig.Field[int](
doc="size of cell used to determine PSF (pixels, row direction)",
dtype=int,
default=sizeCellX.default,
# minValue = 10,
check=lambda x: x >= 10,
)
nStarPerCell = pexConfig.Field(
nStarPerCell = pexConfig.Field[int](
doc="number of stars per psf cell for PSF kernel creation",
dtype=int,
default=3,
)
borderWidth = pexConfig.Field(
borderWidth = pexConfig.Field[int](
doc="Number of pixels to ignore around the edge of PSF candidate postage stamps",
dtype=int,
default=0,
)
nStarPerCellSpatialFit = pexConfig.Field(
nStarPerCellSpatialFit = pexConfig.Field[int](
doc="number of stars per psf Cell for spatial fitting",
dtype=int,
default=5,
)
constantWeight = pexConfig.Field(
constantWeight = pexConfig.Field[bool](
doc="Should each PSF candidate be given the same weight, independent of magnitude?",
dtype=bool,
default=True,
)
nIterForPsf = pexConfig.Field(
nIterForPsf = pexConfig.Field[int](
doc="number of iterations of PSF candidate star list",
dtype=int,
default=3,
)
tolerance = pexConfig.Field(
tolerance = pexConfig.Field[float](
doc="tolerance of spatial fitting",
dtype=float,
default=1e-2,
)
lam = pexConfig.Field(
lam = pexConfig.Field[float](
doc="floor for variance is lam*data",
dtype=float,
default=0.05,
)
reducedChi2ForPsfCandidates = pexConfig.Field(
reducedChi2ForPsfCandidates = pexConfig.Field[float](
doc="for psf candidate evaluation",
dtype=float,
default=2.0,
)
spatialReject = pexConfig.Field(
spatialReject = pexConfig.Field[float](
doc="Rejection threshold (stdev) for candidates based on spatial fit",
dtype=float,
default=3.0,
)
pixelThreshold = pexConfig.Field(
pixelThreshold = pexConfig.Field[float](
doc="Threshold (stdev) for rejecting extraneous pixels around candidate; applied if positive",
dtype=float,
default=0.0,
)
doRejectBlends = pexConfig.Field(
doRejectBlends = pexConfig.Field[bool](
doc="Reject candidates that are blended?",
dtype=bool,
default=False,
)
doMaskBlends = pexConfig.Field(
doMaskBlends = pexConfig.Field[bool](
doc="Mask blends in image?",
dtype=bool,
default=True,
)

def setDefaults(self):
super().setDefaults()
self.stampSize = 41


class PcaPsfDeterminerTask(BasePsfDeterminerTask):
"""A measurePsfTask psf estimator.
Expand Down Expand Up @@ -275,13 +262,19 @@ def determinePsf(self, exposure, psfCandidateList, metadata=None, flagKey=None):
raise RuntimeError("No usable PSF candidates supplied")
nEigenComponents = self.config.nEigenComponents # initial version

if self.config.kernelSize >= 15:
self.log.warning("WARNING: NOT scaling kernelSize by stellar quadrupole moment "
# TODO: DM-36311: Keep only the if block below.
if self.config.stampSize:
actualKernelSize = int(self.config.stampSize)
elif self.config.kernelSize >= 15:
self.log.warning("NOT scaling kernelSize by stellar quadrupole moment "
"because config.kernelSize=%s >= 15; "
"using config.kernelSize as the width, instead",
self.config.kernelSize)
actualKernelSize = int(self.config.kernelSize)
else:
self.log.warning("scaling kernelSize by stellar quadrupole moment "
"because config.kernelSize=%s < 15. This behavior is deprecated.",
self.config.kernelSize)
medSize = numpy.median(sizes)
actualKernelSize = 2*int(self.config.kernelSize*math.sqrt(medSize) + 0.5) + 1
if actualKernelSize < self.config.kernelSizeMin:
Expand All @@ -293,9 +286,11 @@ def determinePsf(self, exposure, psfCandidateList, metadata=None, flagKey=None):
print("Median size=%s" % (medSize,))
self.log.trace("Kernel size=%s", actualKernelSize)

# Set size of image returned around candidate
psfCandidateList[0].setHeight(actualKernelSize)
psfCandidateList[0].setWidth(actualKernelSize)
if actualKernelSize > psfCandidateList[0].getWidth():
self.log.warning("Using a region (%d x %d) larger than kernelSize (%d) set while making PSF "
"candidates. Consider setting a larger value for kernelSize for "
"`makePsfCandidates` to avoid this warning.",
actualKernelSize, actualKernelSize, psfCandidateList[0].getWidth())

if self.config.doRejectBlends:
# Remove blended candidates completely
Expand Down Expand Up @@ -431,7 +426,7 @@ def determinePsf(self, exposure, psfCandidateList, metadata=None, flagKey=None):
for cand in cell.begin(False):
candCenter = lsst.geom.PointD(cand.getXCenter(), cand.getYCenter())
try:
im = cand.getMaskedImage(kernel.getWidth(), kernel.getHeight())
im = cand.getMaskedImage(actualKernelSize, actualKernelSize)
except Exception:
continue

Expand Down
52 changes: 40 additions & 12 deletions python/lsst/meas/algorithms/psfDeterminer.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,22 +34,48 @@ class BasePsfDeterminerConfig(pexConfig.Config):
This is fairly sparse; more fields can be moved here once it is clear they are universal.
"""
kernelSize = pexConfig.Field(
doc="radius of the kernel to create, relative to the square root of the stellar quadrupole moments",
dtype=float,
default=10.0,
stampSize = pexConfig.Field[int](
doc="Size of the postage stamp (in native pixels) to render the PSF model. Should be odd.",
default=None,
optional=True,
check=lambda x: (x > 0) & (x % 2 == 1),
)
kernelSizeMin = pexConfig.Field(
doc="Minimum radius of the kernel",
dtype=int,
kernelSize = pexConfig.Field[int](
doc="Size of the kernel to create. "
"If less than 15, then the median of the square root of the "
"stellar quadrupole moments is multiplied by kernelSize and "
"used as the kernel size.",
default=None,
optional=True,
deprecated="This field is deprecated and will be removed after v25. "
"Use stampSize instead.",
)
kernelSizeMin = pexConfig.Field[int](
doc="Minimum radius of the kernel. Relevant only if kernelSize < 15.",
default=25,
deprecated="This field is deprecated and will be removed after v25.",
)
kernelSizeMax = pexConfig.Field(
doc="Maximum radius of the kernel",
dtype=int,
kernelSizeMax = pexConfig.Field[int](
doc="Maximum radius of the kernel. Relevant only if kernelSize < 15.",
default=45,
deprecated="This field is deprecated and will be removed after v25.",
)

def validate(self):
# TODO: DM-36311: Lines involving kernelSize* (or the entire method)
# may be removed after v25.
super().validate()
# Ensure that stampSize and kernelSize are not contradictory.
if self.stampSize is not None and self.kernelSize is not None:
raise pexConfig.FieldValidationError(self.__class__.kernelSize, self,
"Only one of stampSize (preferable) and kernelSize "
"(deprecated) must be set."
)
if self.kernelSizeMin > self.kernelSizeMax:
raise pexConfig.FieldValidationError(self.__class__.kernelSizeMax, self,
"kernelSizeMin must be <= kernelSizeMax"
)


class BasePsfDeterminerTask(pipeBase.Task, metaclass=abc.ABCMeta):
"""Base class for PSF determiners
Expand All @@ -75,7 +101,7 @@ def __init__(self, config, schema=None, **kwds):
pipeBase.Task.__init__(self, config=config, **kwds)

@abc.abstractmethod
def determinePsf(self, exposure, psfCandidateList, metadata=None):
def determinePsf(self, exposure, psfCandidateList, metadata=None, flagKey=None):
"""Determine a PSF model.
Parameters
Expand All @@ -86,8 +112,10 @@ def determinePsf(self, exposure, psfCandidateList, metadata=None):
A sequence of PSF candidates; typically obtained by
detecting sources and then running them through a star
selector.
metadata : `str`
metadata : `str`, optional
A place to save interesting items.
flagKey: `lsst.afw.table.Key`, optional
Schema key used to mark sources actually used in PSF determination.
Returns
-------
Expand Down
2 changes: 1 addition & 1 deletion tests/test_psfDetermination.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ def setupDeterminer(self, exposure=None, nEigenComponents=2, starSelectorAlg="ob
psfDeterminerConfig.sizeCellY = height//3
psfDeterminerConfig.nEigenComponents = nEigenComponents
psfDeterminerConfig.spatialOrder = 1
psfDeterminerConfig.kernelSizeMin = 31
psfDeterminerConfig.stampSize = 31
psfDeterminerConfig.nStarPerCell = 0
psfDeterminerConfig.nStarPerCellSpatialFit = 0 # unlimited
self.psfDeterminer = psfDeterminerTask(psfDeterminerConfig)
Expand Down

0 comments on commit 169e04b

Please sign in to comment.