Coverage for python / lsst / meas / algorithms / cloughTocher2DInterpolator.py: 47%

26 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-04-28 08:52 +0000

1# This file is part of meas_algorithms. 

2# 

3# Developed for the LSST Data Management System. 

4# This product includes software developed by the LSST Project 

5# (https://www.lsst.org). 

6# See the COPYRIGHT file at the top-level directory of this distribution 

7# for details of code ownership. 

8# 

9# This program is free software: you can redistribute it and/or modify 

10# it under the terms of the GNU General Public License as published by 

11# the Free Software Foundation, either version 3 of the License, or 

12# (at your option) any later version. 

13# 

14# This program is distributed in the hope that it will be useful, 

15# but WITHOUT ANY WARRANTY; without even the implied warranty of 

16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

17# GNU General Public License for more details. 

18# 

19# You should have received a copy of the GNU General Public License 

20# along with this program. If not, see <https://www.gnu.org/licenses/>. 

21 

22__all__ = ( 

23 "CloughTocher2DInterpolateConfig", 

24 "CloughTocher2DInterpolateTask", 

25) 

26 

27 

28from lsst.pex.config import Config, Field, ListField 

29from lsst.pipe.base import Task 

30from scipy.interpolate import CloughTocher2DInterpolator 

31 

32from . import CloughTocher2DInterpolatorUtils as ctUtils 

33 

34 

35class CloughTocher2DInterpolateConfig(Config): 

36 """Config for CloughTocher2DInterpolateTask.""" 

37 

38 badMaskPlanes = ListField[str]( 

39 doc="List of mask planes to interpolate over.", 

40 default=["BAD", "SAT", "CR"], 

41 ) 

42 fillValue = Field[float]( 

43 doc="Constant value to fill outside of the convex hull of the good " 

44 "pixels. A long (longer than twice the ``interpLength``) streak of " 

45 "bad pixels at an edge will be set to this value.", 

46 default=0.0, 

47 ) 

48 interpLength = Field[int]( 

49 doc="Maximum number of pixels away from a bad pixel to include in " 

50 "building the interpolant. Must be greater than or equal to 1.", 

51 default=4, 

52 check=lambda x: x >= 1, 

53 ) 

54 flipXY = Field[bool]( 

55 doc="Whether to flip the x and y coordinates before constructing the " 

56 "Delaunay triangulation. This may produce a slightly different result " 

57 "since the triangulation is not guaranteed to be invariant under " 

58 "coordinate flips.", 

59 default=True, 

60 ) 

61 

62 

63class CloughTocher2DInterpolateTask(Task): 

64 """Interpolated over bad pixels using CloughTocher2DInterpolator. 

65 

66 Pixels with mask bits set to any of those listed ``badMaskPlanes`` config 

67 are considered bad and are interpolated over. All good (non-bad) pixels 

68 within ``interpLength`` pixels of a bad pixel in either direction are used 

69 to construct the interpolant. An extended streak of bad pixels at an edge, 

70 longer than ``interpLength``, is set to `fillValue`` specified in config. 

71 """ 

72 

73 ConfigClass = CloughTocher2DInterpolateConfig 

74 _DefaultName = "cloughTocher2DInterpolate" 

75 

76 def run( 

77 self, 

78 maskedImage, 

79 badpix=None, 

80 goodpix=None, 

81 ): 

82 """Interpolate over bad pixels in a masked image. 

83 

84 This modifies the ``image`` attribute of the ``maskedImage`` in place. 

85 This method returns, and accepts, the coordinates of the bad pixels 

86 that were interpolated over, and the coordinates and values of the 

87 good pixels that were used to construct the interpolant. This avoids 

88 having to search for the bad and the good pixels repeatedly when the 

89 mask plane is shared among many images, as would be the case with 

90 noise realizations. 

91 

92 Parameters 

93 ---------- 

94 maskedImage : `~lsst.afw.image.MaskedImageF` 

95 Image on which to perform interpolation (and modify in-place). 

96 badpix: `numpy.ndarray`, optional 

97 N x 3 numpy array, where N is the number of bad pixels. 

98 The coordinates of the bad pixels to interpolate over in the first 

99 two columns, and the pixel values (unused) in the third column. 

100 If None, then the coordinates of the bad pixels are determined by 

101 an exhaustive search over the image. If ``goodpix`` is not 

102 provided, then this parameter is ignored. 

103 goodpix: `numpy.ndarray`, optional 

104 M x 3 numpy array, where M is the number of good pixels. 

105 The first two columns are the coordinates of the good pixels around 

106 ``badpix`` that must be included when constructing the interpolant. 

107 the interpolant. The values are populated from the image plane of 

108 the ``maskedImage`` and returned (provided values will be ignored). 

109 If ``badpix`` is not provided, then this parameter is ignored. 

110 

111 Returns 

112 ------- 

113 badpix: `numpy.ndarray` 

114 N x 3 numpy array, where N is the number of bad pixels. 

115 The coordinates of the bad pixels that were interpolated over are 

116 in the first two columns, and the corresponding pixel values in the 

117 third. If ``badpix`` was provided, this is the same as the input. 

118 goodpix: `numpy.ndarray` 

119 M x 3 numpy array, where M is the number of bad pixels. 

120 The coordinates of the good pixels that were used to construct the 

121 interpolant arein the first two columns, and the corresponding 

122 pixel values in the third. If ``goodpix`` was provided, the first 

123 two columns are same as the input, with the third column updated 

124 with the pixel values from the image plane of the ``maskedImage``. 

125 """ 

126 

127 if badpix is None or goodpix is None: 

128 badpix, goodpix = ctUtils.findGoodPixelsAroundBadPixels( 

129 maskedImage, 

130 self.config.badMaskPlanes, 

131 buffer=self.config.interpLength, 

132 ) 

133 else: 

134 # Even if badpix and goodpix is provided, make sure to update 

135 # the values of goodpix. 

136 ctUtils.updateArrayFromImage(goodpix, maskedImage.image) 

137 

138 # Construct the interpolant with goodpix. 

139 if self.config.flipXY: 

140 anchor_points = list(zip(goodpix[:, 1], goodpix[:, 0])) 

141 query_points = badpix[:, 1::-1] 

142 else: 

143 anchor_points = list(zip(goodpix[:, 0], goodpix[:, 1])) 

144 query_points = badpix[:, :2] 

145 

146 interpolator = CloughTocher2DInterpolator( 

147 anchor_points, 

148 goodpix[:, 2], 

149 fill_value=self.config.fillValue, 

150 ) 

151 

152 # Compute the interpolated values at bad pixel locations. 

153 badpix[:, 2] = interpolator(query_points) 

154 

155 # Fill in the bad pixels. 

156 ctUtils.updateImageFromArray(maskedImage.image, badpix) 

157 

158 return badpix, goodpix