Skip to content
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
16 changes: 12 additions & 4 deletions python/lsst/ip/diffim/computeSpatiallySampledMetrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@ class SpatiallySampledMetricsConnections(pipeBase.PipelineTaskConnections,
def __init__(self, *, config=None):
super().__init__(config=config)
if config.image_type == "future":
self.science = dataclasses.replace(self.science, storageClass="VisitImage")
self.template = dataclasses.replace(self.template, storageClass="DifferenceImage")
self.difference = dataclasses.replace(self.difference, storageClass="DifferenceImage")
self.psfMatchingKernel = dataclasses.replace(self.psfMatchingKernel,
storageClass="ConvolutionKernel")

Expand All @@ -109,11 +112,13 @@ class SpatiallySampledMetricsConfig(pipeBase.PipelineTaskConfig,
doc="Generate QA metric sources",
)
image_type = pexConfig.ChoiceField[str](
doc="Type of the input PSF matching kernel. The input images are read"
" as lsst.afw.image.ExposureF either way.",
doc="Type of the input images and PSF matching kernel. run() takes the"
" legacy types either way.",
allowed={
"legacy": "Read lsst.afw.math.Kernel.",
"future": "Read lsst.images.convolution_kernels.ConvolutionKernel.",
"legacy": "Read lsst.afw.image.ExposureF and lsst.afw.math.Kernel.",
"future": "Read lsst.images.VisitImage for the science image,"
" lsst.images.DifferenceImage for the template and difference"
" image, and lsst.images.convolution_kernels.ConvolutionKernel.",
},
optional=False,
default="legacy",
Expand Down Expand Up @@ -237,6 +242,9 @@ def __init__(self, **kwargs):
def runQuantum(self, butlerQC, inputRefs, outputRefs):
inputs = butlerQC.get(inputRefs)
if self.config.image_type == "future":
inputs["science"] = inputs["science"].to_legacy()
inputs["template"] = inputs["template"].to_legacy()
inputs["difference"] = inputs["difference"].to_legacy()
inputs["psfMatchingKernel"] = inputs["psfMatchingKernel"].to_legacy()
outputs = self.run(**inputs)
butlerQC.put(outputs, outputRefs)
Expand Down
66 changes: 47 additions & 19 deletions tests/test_computeSpatiallySampledMetrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,19 @@
import types
import unittest

import astropy.units as u
import numpy as np

import lsst.afw.cameraGeom.testUtils
import lsst.afw.image
import lsst.afw.table
from lsst.daf.butler import DimensionUniverse
import lsst.ip.diffim
import lsst.utils.tests
from lsst.images import DifferenceImage, VisitImage
from lsst.images.convolution_kernels import ImageBasisConvolutionKernel

from utils import makeTestImage
from utils import makeTestExposureRecord, makeTestImage, makeTestVisitInfo


class ComputeSpatiallySampledMetricsTest(lsst.utils.tests.TestCase):
Expand Down Expand Up @@ -92,40 +96,64 @@ def testMaskPlaneNotInImage(self):
self.assertTrue(np.all(np.isfinite(metrics[f"{present.lower()}_mask_fraction"])))

def testConnections(self):
"""``image_type`` sets the storage class of the kernel input only."""
for imageType, kernelStorageClass in (("legacy", "MatchingKernel"),
("future", "ConvolutionKernel")):
"""``image_type`` sets the storage class of the image and kernel
inputs.
"""
expected = {
"legacy": {"science": "ExposureF", "template": "ExposureF", "difference": "ExposureF",
"psfMatchingKernel": "MatchingKernel"},
"future": {"science": "VisitImage", "template": "DifferenceImage",
"difference": "DifferenceImage", "psfMatchingKernel": "ConvolutionKernel"},
}
for imageType, storageClasses in expected.items():
with self.subTest(imageType=imageType):
config = lsst.ip.diffim.SpatiallySampledMetricsTask.ConfigClass()
config.image_type = imageType
connections = config.ConnectionsClass(config=config)
self.assertEqual(connections.psfMatchingKernel.storageClass, kernelStorageClass)
for name in ("science", "template", "difference"):
self.assertEqual(getattr(connections, name).storageClass, "ExposureF")
for name, storageClass in storageClasses.items():
self.assertEqual(getattr(connections, name).storageClass, storageClass)

def _makeFutureInputs(self):
"""Convert the images and kernel made in `setUp` to the types the
``future`` mode reads.

Returns
-------
inputs : `dict` [`str`, `object`]
The converted inputs, keyed by connection name.
"""
detector = list(lsst.afw.cameraGeom.testUtils.CameraWrapper().camera)[0]
visit = 12345
exposureRecord = makeTestExposureRecord(DimensionUniverse(), visit=visit)
inputs = {}
for name, imageType in (("science", VisitImage), ("template", DifferenceImage),
("difference", DifferenceImage)):
exposure = getattr(self, name).clone()
exposure.setDetector(detector)
exposure.info.setVisitInfo(makeTestVisitInfo(visit))
inputs[name] = imageType.from_legacy(exposure, unit=u.nJy, exposure_record=exposureRecord)
inputs["psfMatchingKernel"] = ImageBasisConvolutionKernel.from_legacy(self.psfMatchingKernel)
return inputs

def testRunQuantumFuture(self):
"""``runQuantum`` converts a future kernel back to the legacy type,
"""``runQuantum`` converts the future inputs back to the legacy types,
giving the same metrics as a legacy run.
"""
inputs = {"science": self.science, "template": self.template, "difference": self.difference,
"diaSources": self.diaSources}
legacyInputs = {"science": self.science, "template": self.template, "difference": self.difference,
"psfMatchingKernel": self.psfMatchingKernel}
metrics = {}
for imageType, kernel in (
("legacy", self.psfMatchingKernel),
("future", ImageBasisConvolutionKernel.from_legacy(self.psfMatchingKernel)),
):
for imageType, inputs in (("legacy", legacyInputs), ("future", self._makeFutureInputs())):
config = lsst.ip.diffim.SpatiallySampledMetricsTask.ConfigClass()
config.image_type = imageType
task = lsst.ip.diffim.SpatiallySampledMetricsTask(config=config)
butlerQC = _RecordingQuantumContext()
task.runQuantum(butlerQC, types.SimpleNamespace(**inputs, psfMatchingKernel=kernel), None)
task.runQuantum(butlerQC, types.SimpleNamespace(**inputs, diaSources=self.diaSources), None)
metrics[imageType] = butlerQC.put_values.spatiallySampledMetrics

self.assertGreater(len(metrics["legacy"]), 0)
for name in ("psfMatchingKernel_sum", "psfMatchingKernel_dx", "psfMatchingKernel_dy",
"psfMatchingKernel_residualNorm"):
np.testing.assert_allclose(metrics["future"][name], metrics["legacy"][name],
rtol=1e-6, err_msg=name)
self.assertEqual(metrics["future"].colnames, metrics["legacy"].colnames)
for name in metrics["legacy"].colnames:
np.testing.assert_array_equal(metrics["future"][name], metrics["legacy"][name], err_msg=name)


class _RecordingQuantumContext:
Expand Down
Loading