Skip to content

Commit

Permalink
Merge pull request #387 from lsst/tickets/DM-46174
Browse files Browse the repository at this point in the history
DM-46174: Add an option to flip XY before CloughTocher2D interpolation
  • Loading branch information
arunkannawadi authored Sep 9, 2024
2 parents ae74b0b + 39d1079 commit 409988a
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 6 deletions.
18 changes: 16 additions & 2 deletions python/lsst/meas/algorithms/cloughTocher2DInterpolator.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,13 @@ class CloughTocher2DInterpolateConfig(Config):
default=4,
check=lambda x: x >= 1,
)
flipXY = Field[bool](
doc="Whether to flip the x and y coordinates before constructing the "
"Delaunay triangulation. This may produce a slightly different result "
"since the triangulation is not guaranteed to be invariant under "
"coordinate flips.",
default=True,
)


class CloughTocher2DInterpolateTask(Task):
Expand Down Expand Up @@ -129,14 +136,21 @@ def run(
ctUtils.updateArrayFromImage(goodpix, maskedImage.image)

# Construct the interpolant with goodpix.
if self.config.flipXY:
anchor_points = list(zip(goodpix[:, 1], goodpix[:, 0]))
query_points = badpix[:, 1::-1]
else:
anchor_points = list(zip(goodpix[:, 0], goodpix[:, 1]))
query_points = badpix[:, :2]

interpolator = CloughTocher2DInterpolator(
list(zip(goodpix[:, 0], goodpix[:, 1])),
anchor_points,
goodpix[:, 2],
fill_value=self.config.fillValue,
)

# Compute the interpolated values at bad pixel locations.
badpix[:, 2] = interpolator(badpix[:, :2])
badpix[:, 2] = interpolator(query_points)

# Fill in the bad pixels.
ctUtils.updateImageFromArray(maskedImage.image, badpix)
Expand Down
53 changes: 49 additions & 4 deletions tests/test_cloughTocher2DInterpolate.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,14 @@ def setUp(self):
self.maskedimage.mask[:, 110:111] = afwImage.Mask.getPlaneBitMask("BAD")
self.maskedimage.image[:, 110:111] = np.nan

# Set an asymmetric region as BAD
self.maskedimage.mask[41:42, 63:66] = afwImage.Mask.getPlaneBitMask("SAT")
self.maskedimage.image[41:42, 63:66] = np.nan
self.maskedimage.mask[42:43, 63:65] = afwImage.Mask.getPlaneBitMask("SAT")
self.maskedimage.image[42:43, 63:65] = np.nan
self.maskedimage.mask[44, 63] = afwImage.Mask.getPlaneBitMask("SAT")
self.maskedimage.image[44, 63] = np.nan

# Set a diagonal set of pixels as CR
for i in range(74, 78):
self.maskedimage.mask[i, i] = afwImage.Mask.getPlaneBitMask("CR")
Expand All @@ -85,15 +93,17 @@ def setUp(self):
np.random.seed(12345)
self.noise.image.array[:, :] = np.random.normal(size=self.noise.image.array.shape)

@lsst.utils.tests.methodParameters(n_runs=(1, 2))
def test_interpolation(self, n_runs: int):
@lsst.utils.tests.methodParametersProduct(n_runs=(1, 2), flipXY=(False, True))
def test_interpolation(self, n_runs: int, flipXY: bool):
"""Test that the interpolation is done correctly.
Parameters
----------
n_runs : `int`
Number of times to run the task. Running the task more than once
should have no effect.
flipXY : `bool`
Whether to set the flipXY config parameter to True.
"""
config = CloughTocher2DInterpolateTask.ConfigClass()
config.badMaskPlanes = (
Expand All @@ -103,6 +113,7 @@ def test_interpolation(self, n_runs: int):
"EDGE",
)
config.fillValue = 0.5
config.flipXY = flipXY
task = CloughTocher2DInterpolateTask(config)
for n in range(n_runs):
task.run(self.maskedimage)
Expand All @@ -127,8 +138,14 @@ def test_interpolation(self, n_runs: int):
atol=1e-08,
)

@lsst.utils.tests.methodParametersProduct(pass_badpix=(True, False), pass_goodpix=(True, False))
def test_interpolation_with_noise(self, pass_badpix: bool = True, pass_goodpix: bool = True):
@lsst.utils.tests.methodParametersProduct(
pass_badpix=(True, False),
pass_goodpix=(True, False),
flipXY=(False, True),
)
def test_interpolation_with_noise(
self, pass_badpix: bool = True, pass_goodpix: bool = True, flipXY: bool = False
):
"""Test that we can reuse the badpix and goodpix.
Parameters
Expand All @@ -137,9 +154,12 @@ def test_interpolation_with_noise(self, pass_badpix: bool = True, pass_goodpix:
Whether to pass the badpix to the task?
pass_goodpix : `bool`
Whether to pass the goodpix to the task?
flipXY : `bool`
Whether to set the flipXY config parameter to True.
"""

config = CloughTocher2DInterpolateTask.ConfigClass()
config.flipXY = flipXY
config.badMaskPlanes = (
"BAD",
"SAT",
Expand Down Expand Up @@ -171,6 +191,31 @@ def test_interpolation_with_noise(self, pass_badpix: bool = True, pass_goodpix:
atol=1e-08,
)

def test_interpolation_with_flipXY(self):
"""Test that the interpolation with both values for flipXY."""
config = CloughTocher2DInterpolateTask.ConfigClass()
config.badMaskPlanes = (
"BAD",
"SAT",
"CR",
"EDGE",
)
config.flipXY = True
task = CloughTocher2DInterpolateTask(config)
badpix_true, goodpix_true = task.run(self.maskedimage)

config.flipXY = False
task = CloughTocher2DInterpolateTask(config)
badpix_false, goodpix_false = task.run(self.maskedimage)

# Check that the locations of the bad and the good pixels, and the good
# pixel values themselves are identical.
np.testing.assert_array_equal(goodpix_false, goodpix_true)
np.testing.assert_array_equal(badpix_false[:, :2], badpix_true[:, :2])

# Check that the interpolated values at at least approximately equal.
np.testing.assert_array_equal(badpix_false[:, 2], badpix_true[:, 2])


class CloughTocher2DInterpolatorUtilsTestCase(CloughTocher2DInterpolateTestCase):
"""Test the CloughTocher2DInterpolatorUtils."""
Expand Down

0 comments on commit 409988a

Please sign in to comment.