Skip to content
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

Add a image-based diffim quality metric #371

Merged
merged 1 commit into from
Jan 18, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
73 changes: 72 additions & 1 deletion python/lsst/ip/diffim/subtractImages.py
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,6 @@ def run(self, template, science, sources, visitSummary=None):
Raised if fraction of good pixels, defined as not having NO_DATA
set, is less then the configured requiredTemplateFraction
"""

self._prepareInputs(template, science, visitSummary=visitSummary)

# In the event that getPsfFwhm fails, evaluate the PSF on a grid.
Expand Down Expand Up @@ -465,8 +464,80 @@ def run(self, template, science, sources, visitSummary=None):
# checkTemplateIsSufficient did not raise NoWorkFound, so raise original exception
raise e

self.computeImageMetrics(science, subtractResults.difference, sources)

return subtractResults

def computeImageMetrics(self, science, difference, stars):
r"""Compute quality metrics (saved to the task metadata) on the
difference image, at the locations of detected stars on the science
image. This restricts the metric to locations that should be
well-subtracted.

Parameters
----------
science : `lsst.afw.image.ExposureF`
Science exposure that was subtracted.
difference : `lsst.afw.image.ExposureF`
Result of subtracting template and science.
stars : `lsst.afw.table.SourceCatalog`
Good calibration sources detected on science image; these
footprints are what the metrics are computed on.

Notes
-----
The task metadata does not include docstrings, so descriptions of the
computed metrics are given here:

differenceFootprintRatioMean
Mean of the ratio of the absolute value of the difference image
(with the mean absolute value of the sky regions on the difference
image removed) to the science image, computed in the footprints
of stars detected on the science image (the sums below are of the
pixels in each star or sky footprint):
:math:`\mathrm{mean}_{footprints}((\sum |difference| -
\mathrm{mean}(\sum |difference_{sky}|)) / \sum science)`
differenceFootprintRatioStdev
Standard Deviation across footprints of the above ratio.
differenceFootprintSkyRatioMean
Mean of the ratio of the absolute value of sky source regions on
the difference image to the science image (the sum below is of the
pixels in each sky source footprint):
:math:`\mathrm{mean}_{footprints}(\sum |difference_{sky}| / \sum science_{sky})`
differenceFootprintSkyRatioStdev
Standard Deivation across footprints of the above sky ratio.
"""
def footprint_mean(sources, sky=0):
"""Compute ratio of the absolute value of the diffim to the science
image, within each source footprint, subtracting the sky from the
diffim values if provided.
"""
n = len(sources)
science_footprints = np.zeros(n)
difference_footprints = np.zeros(n)
ratio = np.zeros(n)
for i, record in enumerate(sources):
footprint = record.getFootprint()
heavy = lsst.afw.detection.makeHeavyFootprint(footprint, science.maskedImage)
heavy_diff = lsst.afw.detection.makeHeavyFootprint(footprint, difference.maskedImage)
science_footprints[i] = heavy.getImageArray().sum()
difference_footprints[i] = abs(heavy_diff.getImageArray()).sum()
ratio[i] = (difference_footprints[i] - sky) / science_footprints[i]
return science_footprints, difference_footprints, ratio

sky = stars["sky_source"]
sky_science, sky_difference, sky_ratio = footprint_mean(stars[sky])
science_footprints, difference_footprints, ratio = footprint_mean(stars[~sky], sky_difference.mean())

self.metadata["differenceFootprintRatioMean"] = ratio.mean()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please also print this as an INFO level log message.

self.metadata["differenceFootprintRatioStdev"] = ratio.std()
self.metadata["differenceFootprintSkyRatioMean"] = sky_ratio.mean()
self.metadata["differenceFootprintSkyRatioStdev"] = sky_ratio.std()
self.log.info("Mean, stdev of ratio of difference to science "
"pixels in star footprints: %5.4f, %5.4f",
self.metadata["differenceFootprintRatioMean"],
self.metadata["differenceFootprintRatioStdev"])

def runConvolveTemplate(self, template, science, selectSources):
"""Convolve the template image with a PSF-matching kernel and subtract
from the science image.
Expand Down
16 changes: 16 additions & 0 deletions tests/test_subtractTask.py
Original file line number Diff line number Diff line change
Expand Up @@ -895,6 +895,18 @@ def test_metadata_metrics(self):
template_good, _ = makeTestImage(psfSize=2.4, doApplyCalibration=True)
template_bad, _ = makeTestImage(psfSize=9.5, doApplyCalibration=True)

# Add a few sky objects; sky footprints are needed for some metrics.
config = measAlg.SkyObjectsTask.ConfigClass()
config.nSources = 3
skyTask = measAlg.SkyObjectsTask(config=config, name="skySources")
skyTask.skySourceKey = sources.schema["sky_source"].asKey()
skyTask.run(science.mask, 10, catalog=sources)
sources = sources.copy(deep=True)
# Add centroids, since these sources were added post-measurement.
for record in sources[sources["sky_source"]]:
record["truth_x"] = record.getFootprint().getPeaks()[0].getFx()
record["truth_y"] = record.getFootprint().getPeaks()[0].getFy()

# The metadata fields are attached to the subtractTask, so we do
# need to run that; run it for both "good" and "bad" seeing templates

Expand Down Expand Up @@ -955,6 +967,10 @@ def test_metadata_metrics(self):
self.assertIn('scienceLimitingMagnitude', subtractTask_good.metadata)
self.assertIn('templateLimitingMagnitude', subtractTask_good.metadata)

# The mean ratio metric should be much worse on the "bad" subtraction.
self.assertLess(subtractTask_good.metadata['differenceFootprintRatioMean'], 0.2)
self.assertGreater(subtractTask_bad.metadata['differenceFootprintRatioMean'], 1.0)


class AlardLuptonPreconvolveSubtractTest(AlardLuptonSubtractTestBase, lsst.utils.tests.TestCase):
subtractTask = subtractImages.AlardLuptonPreconvolveSubtractTask
Expand Down
Loading