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

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
30__all__ = ["InterpImageConfig", "InterpImageTask"]
33class InterpImageConfig(pexConfig.Config):
34 """Config for InterpImageTask
35 """
36 modelPsf = measAlg.GaussianPsfFactory.makeField(doc="Model Psf factory")
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.")
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)
78class InterpImageTask(pipeBase.Task):
79 """Interpolate over bad image pixels
80 """
81 ConfigClass = InterpImageConfig
82 _DefaultName = "interpImage"
84 def _setFallbackValue(self, mi=None):
85 """Set the edge fallbackValue for interpolation
87 @param[in] mi input maksedImage on which to calculate the statistics
88 Must be provided if fallbackValueType != "USER".
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))
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)
112 self.log.info("fallbackValueType %s has been set to %.4f" %
113 (self.config.fallbackValueType, fallbackValue))
115 return fallbackValue
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
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.
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).
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
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)
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)
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)
179 self.interpolateImage(maskedImage, psf, defectList, fallbackValue)
181 self.log.info("Interpolated over %d %s pixels." % (len(defectList), planeName))
183 @contextmanager
184 def transposeContext(self, maskedImage, defects):
185 """Context manager to potentially transpose an image
187 This applies the ``transpose`` configuration setting.
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.
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.
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())))
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
227 def interpolateImage(self, maskedImage, psf, defectList, fallbackValue):
228 """Interpolate over defects in an image
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)