Hide keyboard shortcuts

Hot-keys 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

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.meas.algorithms as measAlg 

28import lsst.pipe.base as pipeBase 

29 

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

31 

32 

33class InterpImageConfig(pexConfig.Config): 

34 """Config for InterpImageTask 

35 """ 

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

37 

38 useFallbackValueAtEdge = pexConfig.Field( 

39 dtype=bool, 

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

41 default=True, 

42 ) 

43 fallbackValueType = pexConfig.ChoiceField( 

44 dtype=str, 

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

46 allowed={ 

47 "MEAN": "mean", 

48 "MEDIAN": "median", 

49 "MEANCLIP": "clipped mean", 

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

51 }, 

52 default="MEDIAN", 

53 ) 

54 fallbackUserValue = pexConfig.Field( 

55 dtype=float, 

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

57 default=0.0, 

58 ) 

59 negativeFallbackAllowed = pexConfig.Field( 

60 dtype=bool, 

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

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

63 default=False, 

64 ) 

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

66 doc="Transpose image before interpolating? " 

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

68 

69 def validate(self): 

70 pexConfig.Config.validate(self) 

71 if self.useFallbackValueAtEdge: 71 ↛ exitline 71 didn't return from function 'validate', because the condition on line 71 was never false

72 if (not self.negativeFallbackAllowed and self.fallbackValueType == "USER" 72 ↛ exitline 72 didn't return from function 'validate', because the condition on line 72 was never false

73 and self.fallbackUserValue < 0.0): 

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

75 "negativeFallbackAllowed is False" % self.fallbackUserValue) 

76 

77 

78class InterpImageTask(pipeBase.Task): 

79 """Interpolate over bad image pixels 

80 """ 

81 ConfigClass = InterpImageConfig 

82 _DefaultName = "interpImage" 

83 

84 def _setFallbackValue(self, mi=None): 

85 """Set the edge fallbackValue for interpolation 

86 

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

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

89 

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

91 and negativeFallbackAllowed config parameters 

92 """ 

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

94 assert mi, "No maskedImage provided" 

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

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

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

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

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

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

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

102 fallbackValue = self.config.fallbackUserValue 

103 else: 

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

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

106 

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

108 self.log.warn("Negative interpolation edge fallback value computed but " 

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

110 fallbackValue = max(fallbackValue, 0.0) 

111 

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

113 (self.config.fallbackValueType, fallbackValue)) 

114 

115 return fallbackValue 

116 

117 @pipeBase.timeMethod 

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

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

120 

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

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

123 If both are provided an exception is raised. 

124 

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

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

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

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

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

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

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

132 measAlg.GaussianPsfFactory if None). 

133 

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

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

136 If None, must provide a defects list. 

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

138 If None the default is used, where the default 

139 is set to the exposure psf if available 

140 @param[in] defects List of defects of type measAlg.Defects 

141 over which to interpolate. 

142 """ 

143 try: 

144 maskedImage = image.getMaskedImage() 

145 except AttributeError: 

146 maskedImage = image 

147 

148 # set defectList from defects OR mask planeName provided 

149 if planeName is None: 

150 if defects is None: 150 ↛ 151line 150 didn't jump to line 151, because the condition on line 150 was never true

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

152 else: 

153 if not isinstance(defects, measAlg.Defects): 153 ↛ 154line 153 didn't jump to line 154, because the condition on line 153 was never true

154 defectList = measAlg.Defects(defects) 

155 else: 

156 defectList = defects 

157 planeName = "defects" 

158 else: 

159 if defects is not None: 

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

161 if planeName not in maskedImage.getMask().getMaskPlaneDict(): 161 ↛ 162line 161 didn't jump to line 162, because the condition on line 161 was never true

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

163 defectList = measAlg.Defects.fromMask(maskedImage, planeName) 

164 

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

166 try: 

167 psf = image.getPsf() 

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

169 except AttributeError: 

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

171 (str(fwhmPixels) if fwhmPixels is not None else 

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

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

174 

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

176 if self.config.useFallbackValueAtEdge: 

177 fallbackValue = self._setFallbackValue(maskedImage) 

178 

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

180 

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

182 

183 @contextmanager 

184 def transposeContext(self, maskedImage, defects): 

185 """Context manager to potentially transpose an image 

186 

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

188 

189 Transposing the image allows us to interpolate along columns instead 

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

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

192 columns as they typically are in raw CCD images. 

193 

194 Parameters 

195 ---------- 

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

197 Image on which to perform interpolation. 

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

199 List of defects to interpolate over. 

200 

201 Yields 

202 ------ 

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

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

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

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

207 transposed. 

208 """ 

209 def transposeImage(image): 

210 """Transpose an image""" 

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

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

213 

214 useImage = maskedImage 

215 useDefects = defects 

216 if self.config.transpose: 

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

218 transposeImage(maskedImage.mask), 

219 transposeImage(maskedImage.variance)) 

220 useDefects = defects.transpose() 

221 yield useImage, useDefects 

222 if self.config.transpose: 

223 maskedImage.image.array = useImage.image.array.T 

224 maskedImage.mask.array = useImage.mask.array.T 

225 maskedImage.variance.array = useImage.variance.array.T 

226 

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

228 """Interpolate over defects in an image 

229 

230 Parameters 

231 ---------- 

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

233 Image on which to perform interpolation. 

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

235 Point-spread function; currently unused. 

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

237 List of defects to interpolate over. 

238 fallbackValue : `float` 

239 Value to set when interpolation fails. 

240 """ 

241 if not defectList: 241 ↛ 242line 241 didn't jump to line 242, because the condition on line 241 was never true

242 return 

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

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

245 self.config.useFallbackValueAtEdge)