Coverage for python/lsst/pipe/tasks/interpImage.py: 21%

Shortcuts on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

90 statements  

1# 

2# LSST Data Management System 

3# Copyright 2008-2015 AURA/LSST. 

4# 

5# This product includes software developed by the 

6# LSST Project (http://www.lsst.org/). 

7# 

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

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

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

11# (at your option) any later version. 

12# 

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

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

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

16# GNU General Public License for more details. 

17# 

18# You should have received a copy of the LSST License Statement and 

19# the GNU General Public License along with this program. If not, 

20# see <https://www.lsstcorp.org/LegalNotices/>. 

21# 

22from contextlib import contextmanager 

23import lsst.pex.config as pexConfig 

24import lsst.geom 

25import lsst.afw.image as afwImage 

26import lsst.afw.math as afwMath 

27import lsst.ip.isr as ipIsr 

28import lsst.meas.algorithms as measAlg 

29import lsst.pipe.base as pipeBase 

30 

31__all__ = ["InterpImageConfig", "InterpImageTask"] 

32 

33 

34class InterpImageConfig(pexConfig.Config): 

35 """Config for InterpImageTask 

36 """ 

37 modelPsf = measAlg.GaussianPsfFactory.makeField(doc="Model Psf factory") 

38 

39 useFallbackValueAtEdge = pexConfig.Field( 

40 dtype=bool, 

41 doc="Smoothly taper to the fallback value at the edge of the image?", 

42 default=True, 

43 ) 

44 fallbackValueType = pexConfig.ChoiceField( 

45 dtype=str, 

46 doc="Type of statistic to calculate edge fallbackValue for interpolation", 

47 allowed={ 

48 "MEAN": "mean", 

49 "MEDIAN": "median", 

50 "MEANCLIP": "clipped mean", 

51 "USER": "user value set in fallbackUserValue config", 

52 }, 

53 default="MEDIAN", 

54 ) 

55 fallbackUserValue = pexConfig.Field( 

56 dtype=float, 

57 doc="If fallbackValueType is 'USER' then use this as the fallbackValue; ignored otherwise", 

58 default=0.0, 

59 ) 

60 negativeFallbackAllowed = pexConfig.Field( 

61 dtype=bool, 

62 doc=("Allow negative values for egde interpolation fallbackValue? If False, set " 

63 "fallbackValue to max(fallbackValue, 0.0)"), 

64 default=False, 

65 ) 

66 transpose = pexConfig.Field(dtype=int, default=False, 

67 doc="Transpose image before interpolating? " 

68 "This allows the interpolation to act over columns instead of rows.") 

69 

70 def validate(self): 

71 pexConfig.Config.validate(self) 

72 if self.useFallbackValueAtEdge: 

73 if (not self.negativeFallbackAllowed and self.fallbackValueType == "USER" 

74 and self.fallbackUserValue < 0.0): 

75 raise ValueError("User supplied fallbackValue is negative (%.2f) but " 

76 "negativeFallbackAllowed is False" % self.fallbackUserValue) 

77 

78 

79class InterpImageTask(pipeBase.Task): 

80 """Interpolate over bad image pixels 

81 """ 

82 ConfigClass = InterpImageConfig 

83 _DefaultName = "interpImage" 

84 

85 def _setFallbackValue(self, mi=None): 

86 """Set the edge fallbackValue for interpolation 

87 

88 @param[in] mi input maksedImage on which to calculate the statistics 

89 Must be provided if fallbackValueType != "USER". 

90 

91 @return fallbackValue The value set/computed based on the fallbackValueType 

92 and negativeFallbackAllowed config parameters 

93 """ 

94 if self.config.fallbackValueType != 'USER': 

95 assert mi, "No maskedImage provided" 

96 if self.config.fallbackValueType == 'MEAN': 

97 fallbackValue = afwMath.makeStatistics(mi, afwMath.MEAN).getValue() 

98 elif self.config.fallbackValueType == 'MEDIAN': 

99 fallbackValue = afwMath.makeStatistics(mi, afwMath.MEDIAN).getValue() 

100 elif self.config.fallbackValueType == 'MEANCLIP': 

101 fallbackValue = afwMath.makeStatistics(mi, afwMath.MEANCLIP).getValue() 

102 elif self.config.fallbackValueType == 'USER': 

103 fallbackValue = self.config.fallbackUserValue 

104 else: 

105 raise NotImplementedError("%s : %s not implemented" % 

106 ("fallbackValueType", self.config.fallbackValueType)) 

107 

108 if not self.config.negativeFallbackAllowed and fallbackValue < 0.0: 

109 self.log.warning("Negative interpolation edge fallback value computed but " 

110 "negativeFallbackAllowed is False: setting fallbackValue to 0.0") 

111 fallbackValue = max(fallbackValue, 0.0) 

112 

113 self.log.info("fallbackValueType %s has been set to %.4f", 

114 self.config.fallbackValueType, fallbackValue) 

115 

116 return fallbackValue 

117 

118 @pipeBase.timeMethod 

119 def run(self, image, planeName=None, fwhmPixels=None, defects=None): 

120 """!Interpolate in place over pixels in a maskedImage marked as bad 

121 

122 Pixels to be interpolated are set by either a mask planeName provided 

123 by the caller OR a defects list of type `~lsst.meas.algorithms.Defects` 

124 If both are provided an exception is raised. 

125 

126 Note that the interpolation code in meas_algorithms currently doesn't 

127 use the input PSF (though it's a required argument), so it's not 

128 important to set the input PSF parameters exactly. This PSF is set 

129 here as the psf attached to the "image" (i.e if the image passed in 

130 is an Exposure). Otherwise, a psf model is created using 

131 measAlg.GaussianPsfFactory with the value of fwhmPixels (the value 

132 passed in by the caller, or the default defaultFwhm set in 

133 measAlg.GaussianPsfFactory if None). 

134 

135 @param[in,out] image MaskedImage OR Exposure to be interpolated 

136 @param[in] planeName name of mask plane over which to interpolate 

137 If None, must provide a defects list. 

138 @param[in] fwhmPixels FWHM of core star (pixels) 

139 If None the default is used, where the default 

140 is set to the exposure psf if available 

141 @param[in] defects List of defects of type ipIsr.Defects 

142 over which to interpolate. 

143 """ 

144 try: 

145 maskedImage = image.getMaskedImage() 

146 except AttributeError: 

147 maskedImage = image 

148 

149 # set defectList from defects OR mask planeName provided 

150 if planeName is None: 

151 if defects is None: 

152 raise ValueError("No defects or plane name provided") 

153 else: 

154 if not isinstance(defects, ipIsr.Defects): 

155 defectList = ipIsr.Defects(defects) 

156 else: 

157 defectList = defects 

158 planeName = "defects" 

159 else: 

160 if defects is not None: 

161 raise ValueError("Provide EITHER a planeName OR a list of defects, not both") 

162 if planeName not in maskedImage.getMask().getMaskPlaneDict(): 

163 raise ValueError("maskedImage does not contain mask plane %s" % planeName) 

164 defectList = ipIsr.Defects.fromMask(maskedImage, planeName) 

165 

166 # set psf from exposure if provided OR using modelPsf with fwhmPixels provided 

167 try: 

168 psf = image.getPsf() 

169 self.log.info("Setting psf for interpolation from image") 

170 except AttributeError: 

171 self.log.info("Creating psf model for interpolation from fwhm(pixels) = %s", 

172 str(fwhmPixels) if fwhmPixels is not None else 

173 (str(self.config.modelPsf.defaultFwhm)) + " [default]") 

174 psf = self.config.modelPsf.apply(fwhm=fwhmPixels) 

175 

176 fallbackValue = 0.0 # interpolateOverDefects needs this to be a float, regardless if it is used 

177 if self.config.useFallbackValueAtEdge: 

178 fallbackValue = self._setFallbackValue(maskedImage) 

179 

180 self.interpolateImage(maskedImage, psf, defectList, fallbackValue) 

181 

182 self.log.info("Interpolated over %d %s pixels.", len(defectList), planeName) 

183 

184 @contextmanager 

185 def transposeContext(self, maskedImage, defects): 

186 """Context manager to potentially transpose an image 

187 

188 This applies the ``transpose`` configuration setting. 

189 

190 Transposing the image allows us to interpolate along columns instead 

191 of rows, which is useful when the saturation trails are typically 

192 oriented along rows on the warped/coadded images, instead of along 

193 columns as they typically are in raw CCD images. 

194 

195 Parameters 

196 ---------- 

197 maskedImage : `lsst.afw.image.MaskedImage` 

198 Image on which to perform interpolation. 

199 defects : `lsst.meas.algorithms.Defects` 

200 List of defects to interpolate over. 

201 

202 Yields 

203 ------ 

204 useImage : `lsst.afw.image.MaskedImage` 

205 Image to use for interpolation; it may have been transposed. 

206 useDefects : `lsst.meas.algorithms.Defects` 

207 List of defects to use for interpolation; they may have been 

208 transposed. 

209 """ 

210 def transposeImage(image): 

211 """Transpose an image""" 

212 transposed = image.array.T.copy() # Copy to force row-major; required for ndarray+pybind 

213 return image.Factory(transposed, False, lsst.geom.Point2I(*reversed(image.getXY0()))) 

214 

215 useImage = maskedImage 

216 useDefects = defects 

217 if self.config.transpose: 

218 useImage = afwImage.makeMaskedImage(transposeImage(maskedImage.image), 

219 transposeImage(maskedImage.mask), 

220 transposeImage(maskedImage.variance)) 

221 useDefects = defects.transpose() 

222 yield useImage, useDefects 

223 if self.config.transpose: 

224 maskedImage.image.array = useImage.image.array.T 

225 maskedImage.mask.array = useImage.mask.array.T 

226 maskedImage.variance.array = useImage.variance.array.T 

227 

228 def interpolateImage(self, maskedImage, psf, defectList, fallbackValue): 

229 """Interpolate over defects in an image 

230 

231 Parameters 

232 ---------- 

233 maskedImage : `lsst.afw.image.MaskedImage` 

234 Image on which to perform interpolation. 

235 psf : `lsst.afw.detection.Psf` 

236 Point-spread function; currently unused. 

237 defectList : `lsst.meas.algorithms.Defects` 

238 List of defects to interpolate over. 

239 fallbackValue : `float` 

240 Value to set when interpolation fails. 

241 """ 

242 if not defectList: 

243 return 

244 with self.transposeContext(maskedImage, defectList) as (image, defects): 

245 measAlg.interpolateOverDefects(image, psf, defects, fallbackValue, 

246 self.config.useFallbackValueAtEdge)