Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
4de2803
Support partial outputs for detection on the preconvolved diffim
isullivan Apr 10, 2026
acfe806
Reflect the science image PSF for preconvolution.
isullivan Apr 21, 2026
62743a9
Raise if the kernel is not odd-sized
isullivan Apr 22, 2026
488525a
Ensure that the border of the Score image has `EDGE` mask set
isullivan Apr 21, 2026
a8dfd3f
Add background subtraction for preconvolution
isullivan Apr 22, 2026
92959d8
Add cosmic ray detection for preconvolution
isullivan Apr 22, 2026
f6b826a
The matchedTemplate should not be scaled a second time
isullivan Apr 24, 2026
2998f82
Reduce PSF size in tests to keep PSF fully contained in kernel size
isullivan Apr 24, 2026
b28cfbc
Fix discontinuity in kernel basis
isullivan Apr 24, 2026
8119893
Fix broken error message
isullivan Apr 25, 2026
61cc6fd
Evaluate psf matching kernel norm in the center of the image, not def…
isullivan Apr 25, 2026
0fc71fd
Clone the background model before inverting
isullivan Apr 25, 2026
2499cd0
Errors in calculating limiting magnitudes were being silently squashed
isullivan Apr 25, 2026
2e197df
Fix docstring
isullivan Apr 25, 2026
217a946
Persist the Score image post-detection and BG subtraction
isullivan Apr 27, 2026
cd5766b
Add option to reject bad peaks prior to measurement.
isullivan Apr 27, 2026
9ce1be2
Use smaller template coverage in test that expects kernel failure.
isullivan Apr 29, 2026
be2d8d7
Combine all fixup commits from review
isullivan May 4, 2026
4cbfe53
Clean up diffim unit tests
isullivan May 4, 2026
b1ec8e8
Pass keywords by name to `kernel.computeImage`
isullivan May 4, 2026
48c9c1d
Move duplicated PSF width checks in makeKernel to common method
isullivan May 4, 2026
964707e
Test a range of psf sizes in equal images unit test
isullivan May 4, 2026
3157ca6
Refactor psfSize calculations to make sure it is done only once
isullivan May 4, 2026
703b577
Refactor CR detection and background subtraction
isullivan May 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
285 changes: 242 additions & 43 deletions python/lsst/ip/diffim/detectAndMeasure.py

Large diffs are not rendered by default.

110 changes: 61 additions & 49 deletions python/lsst/ip/diffim/makeKernel.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,9 @@ def run(self, template, science, kernelSources, preconvolved=False,
Typically the output of `MakeKernelTask.selectKernelSources`.
preconvolved : `bool`, optional
Was the science image convolved with its own PSF?
templateFwhmPix, scienceFwhmPix : `float`, optional
FWHM of the template or science PSF, in pixels.
Will be recalculated if not specified.

Returns
-------
Expand All @@ -133,31 +136,9 @@ def run(self, template, science, kernelSources, preconvolved=False,
Spatially varying background-matching function.
"""
kernelCellSet = self._buildCellSet(template.maskedImage, science.maskedImage, kernelSources)
if (scienceFwhmPix is None) or (templateFwhmPix is None):
# Calling getPsfFwhm on template.psf fails on some rare occasions when
# the template has no input exposures at the average position of the
# stars. So we try getPsfFwhm first on template, and if that fails we
# evaluate the PSF on a grid specified by fwhmExposure* fields.
# To keep consistent definitions for PSF size on the template and
# science images, we use the same method for both.
try:
templateFwhmPix = getPsfFwhm(template.psf)
scienceFwhmPix = getPsfFwhm(science.psf)
except (InvalidParameterError, RangeError):
self.log.debug("Unable to evaluate PSF at the average position. "
"Evaluting PSF on a grid of points."
)
templateFwhmPix = evaluateMeanPsfFwhm(template,
fwhmExposureBuffer=self.config.fwhmExposureBuffer,
fwhmExposureGrid=self.config.fwhmExposureGrid
)
scienceFwhmPix = evaluateMeanPsfFwhm(science,
fwhmExposureBuffer=self.config.fwhmExposureBuffer,
fwhmExposureGrid=self.config.fwhmExposureGrid
)

if preconvolved:
scienceFwhmPix *= np.sqrt(2)
templateFwhmPix, scienceFwhmPix = self._checkPsfWidths(template, science,
templateFwhmPix, scienceFwhmPix,
preconvolved=False)
basisList = self.makeKernelBasisList(templateFwhmPix, scienceFwhmPix,
metadata=self.metadata)
spatialSolution, psfMatchingKernel, backgroundModel = self._solve(kernelCellSet, basisList)
Expand All @@ -180,36 +161,18 @@ def selectKernelSources(self, template, science, candidateList=None, preconvolve
Sources to check as possible kernel candidates.
preconvolved : `bool`, optional
Was the science image convolved with its own PSF?
templateFwhmPix, scienceFwhmPix : `float`, optional
FWHM of the template or science PSF, in pixels.
Will be recalculated if not specified.

Returns
-------
kernelSources : `lsst.afw.table.SourceCatalog`
Kernel candidates with appropriate sized footprints.
"""
if (scienceFwhmPix is None) or (templateFwhmPix is None):
# Calling getPsfFwhm on template.psf fails on some rare occasions when
# the template has no input exposures at the average position of the
# stars. So we try getPsfFwhm first on template, and if that fails we
# evaluate the PSF on a grid specified by fwhmExposure* fields.
# To keep consistent definitions for PSF size on the template and
# science images, we use the same method for both.
try:
templateFwhmPix = getPsfFwhm(template.psf)
scienceFwhmPix = getPsfFwhm(science.psf)
except (InvalidParameterError, RangeError):
self.log.debug("Unable to evaluate PSF at the average position. "
"Evaluting PSF on a grid of points."
)
templateFwhmPix = evaluateMeanPsfFwhm(template,
fwhmExposureBuffer=self.config.fwhmExposureBuffer,
fwhmExposureGrid=self.config.fwhmExposureGrid
)
scienceFwhmPix = evaluateMeanPsfFwhm(science,
fwhmExposureBuffer=self.config.fwhmExposureBuffer,
fwhmExposureGrid=self.config.fwhmExposureGrid
)
if preconvolved:
scienceFwhmPix *= np.sqrt(2)
templateFwhmPix, scienceFwhmPix = self._checkPsfWidths(template, science,
templateFwhmPix, scienceFwhmPix,
preconvolved=False)
kernelSize = self.makeKernelBasisList(templateFwhmPix, scienceFwhmPix)[0].getWidth()
kernelSources = self.makeCandidateList(template, science, kernelSize,
candidateList=candidateList,
Expand Down Expand Up @@ -452,3 +415,52 @@ def _adaptCellSize(self, candidateList):
New dimensions to use for the kernel.
"""
return self.kConfig.sizeCellX, self.kConfig.sizeCellY

def _checkPsfWidths(self, template, science, templateFwhmPix, scienceFwhmPix, preconvolved=False):
"""Check the science and template FWHM.

Parameters
----------
template : `lsst.afw.image.Exposure`
Exposure that will be convolved.
science : `lsst.afw.image.Exposure`
The exposure that will be matched.
templateFwhmPix, scienceFwhmPix : `float`, optional
FWHM of the template or science PSF, in pixels.
Will be recalculated if not specified.
preconvolved : `bool`, optional
Was the science image convolved with its own PSF?

Returns
-------
templateFwhmPix, scienceFwhmPix : `float`
The desired PSF FWHM
"""
if (scienceFwhmPix is None) or (templateFwhmPix is None):
# Calling getPsfFwhm on template.psf fails on some rare occasions when
# the template has no input exposures at the average position of the
# stars. So we try getPsfFwhm first on template, and if that fails we
# evaluate the PSF on a grid specified by fwhmExposure* fields.
# To keep consistent definitions for PSF size on the template and
# science images, we use the same method for both.
try:
templateFwhmPix = getPsfFwhm(template.psf)
scienceFwhmPix = getPsfFwhm(science.psf)
except (InvalidParameterError, RangeError):
self.log.debug("Unable to evaluate PSF at the average position. "
"Evaluting PSF on a grid of points."
)
templateFwhmPix = evaluateMeanPsfFwhm(template,
fwhmExposureBuffer=self.config.fwhmExposureBuffer,
fwhmExposureGrid=self.config.fwhmExposureGrid
)
scienceFwhmPix = evaluateMeanPsfFwhm(science,
fwhmExposureBuffer=self.config.fwhmExposureBuffer,
fwhmExposureGrid=self.config.fwhmExposureGrid
)
# Apply the Gaussian approximation FWHM boost only when
# the size was auto-computed. An explicit ``scienceFwhmPix`` is
# assumed already to reflect the preconvolved effective width.
if preconvolved:
scienceFwhmPix *= np.sqrt(2)
return templateFwhmPix, scienceFwhmPix
11 changes: 6 additions & 5 deletions python/lsst/ip/diffim/makeKernelBasisList.py
Original file line number Diff line number Diff line change
Expand Up @@ -291,11 +291,12 @@ def _calculateBasisSigmas(referenceSigma, targetSigma, basisMinSigma, basisGauss
if kernelSigma < basisMinSigma:
kernelSigma = basisMinSigma

# If more than one gaussian is requested and kernelSigma is more than a
# factor of basisGaussBeta larger than the minimum sigma, use
# kernelSigma/basisGaussBeta as the size of the first gaussian.
if ((kernelSigma/basisGaussBeta) > basisMinSigma) & (basisNGauss > 1):
basisSigmaGauss = [kernelSigma/basisGaussBeta, ]
# The smallest basis Gaussian sigma is kernelSigma/basisGaussBeta to center
# the geometric basis spacing around kernelSigma, but bounded below by
# basisMinSigma. Keep the larger of the two so that we don't have a
# discontinuous jump in the AL basis and unstable kernel solutions.
if basisNGauss > 1:
basisSigmaGauss = [max(basisMinSigma, kernelSigma/basisGaussBeta), ]
else:
basisSigmaGauss = [kernelSigma, ]

Expand Down
6 changes: 4 additions & 2 deletions python/lsst/ip/diffim/psfMatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -595,7 +595,9 @@ def _diagnostic(self, kernelCellSet, spatialSolution, spatialKernel, spatialBg):
"""
# What is the final kernel sum
kImage = afwImage.ImageD(spatialKernel.getDimensions())
kSum = spatialKernel.computeImage(kImage, False)
# Evaluate the kernel at the cell-set center, not at the world origin.
xcen, ycen = kernelCellSet.getBBox().getCenter()
kSum = spatialKernel.computeImage(kImage, doNormalize=False, x=xcen, y=ycen)
self.log.info("Final spatial kernel sum %.3f", kSum)

# Look at how well conditioned the matrix is
Expand Down Expand Up @@ -773,7 +775,7 @@ def _createPcaBasis(self, kernelCellSet, nStarPerCell, ps):
for j in range(nToUse):
# Check for NaNs?
kimage = afwImage.ImageD(pcaBasisList[j].getDimensions())
pcaBasisList[j].computeImage(kimage, False)
pcaBasisList[j].computeImage(kimage, doNormalize=False)
if not (True in np.isnan(kimage.array)):
trimBasisList.append(pcaBasisList[j])

Expand Down
Loading
Loading