Coverage for python/lsst/pipe/tasks/repair.py: 21%
91 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-07-18 10:37 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2023-07-18 10:37 +0000
1# This file is part of pipe_tasks.
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/>.
22__all__ = ["RepairConfig", "RepairTask"]
24import lsst.pex.config as pexConfig
25import lsst.afw.math as afwMath
26import lsst.afw.detection as afwDet
27import lsst.meas.algorithms as measAlg
28import lsst.pipe.base as pipeBase
29from lsstDebug import getDebugFrame
30import lsst.afw.display as afwDisplay
31from lsst.pipe.tasks.interpImage import InterpImageTask
32from lsst.utils.timer import timeMethod
35class RepairConfig(pexConfig.Config):
36 doInterpolate = pexConfig.Field(
37 dtype=bool,
38 doc="Interpolate over defects? (ignored unless you provide a list of defects)",
39 default=True,
40 )
41 doCosmicRay = pexConfig.Field(
42 dtype=bool,
43 doc="Find and mask out cosmic rays?",
44 default=True,
45 )
46 cosmicray = pexConfig.ConfigField(
47 dtype=measAlg.FindCosmicRaysConfig,
48 doc="Options for finding and masking cosmic rays",
49 )
50 interp = pexConfig.ConfigurableField(
51 target=InterpImageTask,
52 doc="Interpolate over bad image pixels",
53 )
55 def setDefaults(self):
56 self.interp.useFallbackValueAtEdge = True
57 self.interp.fallbackValueType = "MEANCLIP"
58 self.interp.negativeFallbackAllowed = True
61class RepairTask(pipeBase.Task):
62 """Repair an exposures defects and cosmic rays via interpolation.
64 This task operates on an lsst.afw.image.Exposure in place to
65 interpolate over a set of `~lsst.meas.algorithms.Defect` objects.
67 It will also, optionally, find and interpolate any cosmic rays in the lsst.afw.image.Exposure.
69 Notes
70 -----
71 Debugging:
72 The available debug variables in RepairTask are:
74 display :
75 A dictionary containing debug point names as keys with frame number as value. Valid keys are:
76 repair.before :
77 display image before any repair is done
78 repair.after :
79 display image after cosmic ray and defect correction
80 displayCR :
81 If True, display the exposure on ds9's frame 1 and overlay bounding boxes around detects CRs.
83 To investigate the pipe_tasks_repair_Debug, put something like
85 .. code-block :: none
87 import lsstDebug
88 def DebugInfo(name):
89 di = lsstDebug.getInfo(name) # N.b. lsstDebug.Info(name) would call us recursively
90 if name == "lsst.pipe.tasks.repair":
91 di.display = {'repair.before':2, 'repair.after':3}
92 di.displayCR = True
93 return di
95 lsstDebug.Info = DebugInfo
96 into your debug.py file and run runRepair.py with the --debug flag.
98 Conversion notes:
99 Display code should be updated once we settle on a standard way of controlling what is displayed.
100 """
102 ConfigClass = RepairConfig
103 _DefaultName = "repair"
105 def __init__(self, **kwargs):
106 pipeBase.Task.__init__(self, **kwargs)
107 if self.config.doInterpolate:
108 self.makeSubtask("interp")
110 @timeMethod
111 def run(self, exposure, defects=None, keepCRs=None):
112 """Repair an Exposure's defects and cosmic rays.
114 Parameters
115 ----------
116 exposure : `lsst.afw.image.Exposure`
117 Exposure must have a valid Psf.
118 Modified in place.
119 defects : `lsst.meas.algorithms.DefectListT` or `None`, optional
120 If `None`, do no defect correction.
121 keepCRs : `Unknown` or `None`, optional
122 Don't interpolate over the CR pixels (defer to ``RepairConfig`` if `None`).
124 Raises
125 ------
126 AssertionError
127 Raised if any of the following occur:
128 - No exposure provided.
129 - The object provided as exposure evaluates to False.
130 - No PSF provided.
131 - The Exposure has no associated Psf.
132 """
133 assert exposure, "No exposure provided"
134 psf = exposure.getPsf()
135 assert psf, "No PSF provided"
137 frame = getDebugFrame(self._display, "repair.before")
138 if frame:
139 afwDisplay.Display(frame).mtv(exposure)
141 if defects is not None and self.config.doInterpolate:
142 self.interp.run(exposure, defects=defects)
144 if self.config.doCosmicRay:
145 self.cosmicRay(exposure, keepCRs=keepCRs)
147 frame = getDebugFrame(self._display, "repair.after")
148 if frame:
149 afwDisplay.Display(frame).mtv(exposure)
151 def cosmicRay(self, exposure, keepCRs=None):
152 """Mask cosmic rays.
154 Parameters
155 ----------
156 exposure : `lsst.afw.image.Exposure`
157 Exposure to process.
158 keepCRs : `Unknown` or `None`, optional
159 Don't interpolate over the CR pixels (defer to ``pex_config`` if `None`).
160 """
161 import lsstDebug
162 display = lsstDebug.Info(__name__).display
163 displayCR = lsstDebug.Info(__name__).displayCR
165 assert exposure, "No exposure provided"
166 psf = exposure.getPsf()
167 assert psf, "No psf provided"
169 # Blow away old mask
170 try:
171 mask = exposure.getMaskedImage().getMask()
172 crBit = mask.getMaskPlane("CR")
173 mask.clearMaskPlane(crBit)
174 except Exception:
175 pass
177 exposure0 = exposure # initial value of exposure
178 binSize = self.config.cosmicray.background.binSize
179 nx, ny = exposure.getWidth()/binSize, exposure.getHeight()/binSize
180 # Treat constant background as a special case to avoid the extra complexity in calling
181 # measAlg.SubtractBackgroundTask().
182 if nx*ny <= 1:
183 medianBg = afwMath.makeStatistics(exposure.getMaskedImage(), afwMath.MEDIAN).getValue()
184 modelBg = None
185 else:
186 # make a deep copy of the exposure before subtracting its background,
187 # because this routine is only allowed to modify the exposure by setting mask planes
188 # and interpolating over defects, not changing the background level
189 exposure = exposure.Factory(exposure, True)
190 subtractBackgroundTask = measAlg.SubtractBackgroundTask(config=self.config.cosmicray.background)
191 modelBg = subtractBackgroundTask.run(exposure).background
192 medianBg = 0.0
194 if keepCRs is None:
195 keepCRs = self.config.cosmicray.keepCRs
196 try:
197 crs = measAlg.findCosmicRays(exposure.getMaskedImage(), psf, medianBg,
198 pexConfig.makePropertySet(self.config.cosmicray), keepCRs)
199 if modelBg:
200 # Add back background image
201 img = exposure.getMaskedImage()
202 img += modelBg.getImageF()
203 del img
204 # Replace original image with CR subtracted image
205 exposure0.setMaskedImage(exposure.getMaskedImage())
207 except Exception:
208 if display:
209 afwDisplay.Display().mtv(exposure0, title="Failed CR")
210 raise
212 num = 0
213 if crs is not None:
214 mask = exposure0.getMaskedImage().getMask()
215 crBit = mask.getPlaneBitMask("CR")
216 afwDet.setMaskFromFootprintList(mask, crs, crBit)
217 num = len(crs)
219 if display and displayCR:
220 disp = afwDisplay.Display()
221 disp.incrDefaultFrame()
222 disp.mtv(exposure0, title="Post-CR")
224 with disp.Buffering():
225 for cr in crs:
226 afwDisplay.utils.drawBBox(cr.getBBox(), borderWidth=0.55)
228 self.log.info("Identified %s cosmic rays.", num)