23 Apply intra-CCD crosstalk corrections
25 from __future__
import absolute_import, division, print_function
27 __all__ = [
"CrosstalkConfig",
"CrosstalkTask",
"subtractCrosstalk"]
29 from builtins
import zip
35 import lsst.afw.detection
37 from lsst.pex.config
import Config, Field, ListField
38 from lsst.pipe.base
import Task
42 """Configuration for intra-CCD crosstalk removal"""
43 minPixelToMask = Field(dtype=float, default=45000,
44 doc=
"Set crosstalk mask plane for pixels over this value")
45 crosstalkMaskPlane = Field(dtype=str, default=
"CROSSTALK", doc=
"Name for crosstalk mask plane")
49 """Apply intra-CCD crosstalk correction"""
50 ConfigClass = CrosstalkConfig
52 def run(self, exposure):
53 """Apply intra-CCD crosstalk correction
57 exposure : `lsst.afw.image.Exposure`
58 Exposure for which to remove crosstalk.
60 detector = exposure.getDetector()
61 if not detector.hasCrosstalk():
62 self.log.warn(
"Crosstalk correction skipped: no crosstalk coefficients for detector")
64 self.log.info(
"Applying crosstalk correction")
65 numAmps = len(exposure.getDetector())
67 crosstalkStr=self.config.crosstalkMaskPlane)
72 X_FLIP = {lsst.afw.table.LL:
False, lsst.afw.table.LR:
True,
73 lsst.afw.table.UL:
False, lsst.afw.table.UR:
True}
74 Y_FLIP = {lsst.afw.table.LL:
False, lsst.afw.table.LR:
False,
75 lsst.afw.table.UL:
True, lsst.afw.table.UR:
True}
79 """Return an image of the amp
81 The returned image will have the amp's readout corner in the
86 image : `lsst.afw.image.Image` or `lsst.afw.image.MaskedImage`
87 Image containing the amplifier of interest.
88 amp : `lsst.afw.table.AmpInfoRecord`
89 Amplifier information.
90 corner : `lsst.afw.table.ReadoutCorner` or `None`
91 Corner in which to put the amp's readout corner, or `None` for
96 output : `lsst.afw.image.Image`
97 Image of the amplifier in the standard configuration.
99 output = image.Factory(image, amp.getBBox())
100 ampCorner = amp.getReadoutCorner()
102 xFlip = X_FLIP[corner] ^ X_FLIP[ampCorner]
103 yFlip = Y_FLIP[corner] ^ Y_FLIP[ampCorner]
104 return lsst.afw.math.flipImage(output, xFlip, yFlip)
108 """Calculate median background in image
110 Getting a great background model isn't important for crosstalk correction,
111 since the crosstalk is at a low level. The median should be sufficient.
115 mi : `lsst.afw.image.MaskedImage`
116 MaskedImage for which to measure background.
117 badPixels : `list` of `str`
118 Mask planes to ignore.
123 Median background level.
126 stats = lsst.afw.math.StatisticsControl()
127 stats.setAndMask(mask.getPlaneBitMask(badPixels))
128 return lsst.afw.math.makeStatistics(mi, lsst.afw.math.MEDIAN, stats).getValue()
131 def subtractCrosstalk(exposure, badPixels=["BAD"], minPixelToMask=45000, crosstalkStr="CROSSTALK"):
132 """Subtract the intra-CCD crosstalk from an exposure
134 We set the mask plane indicated by ``crosstalkStr`` in a target amplifier
135 for pixels in a source amplifier that exceed `minPixelToMask`. Note that
136 the correction is applied to all pixels in the amplifier, but only those
137 that have a substantial crosstalk are masked with ``crosstalkStr``.
139 The uncorrected image is used as a template for correction. This is good
140 enough if the crosstalk is small (e.g., coefficients < ~ 1e-3), but if it's
141 larger you may want to iterate.
145 exposure : `lsst.afw.image.Exposure`
146 Exposure for which to subtract crosstalk.
147 badPixels : `list` of `str`
148 Mask planes to ignore.
149 minPixelToMask : `float`
150 Minimum pixel value in source amplifier for which to set
151 ``crosstalkStr`` mask plane in target amplifier.
153 Mask plane name for pixels greatly modified by crosstalk.
155 mi = exposure.getMaskedImage()
158 ccd = exposure.getDetector()
160 coeffs = ccd.getCrosstalk()
161 assert coeffs.shape == (numAmps, numAmps)
164 crosstalkPlane = mask.addMaskPlane(crosstalkStr)
165 footprints = lsst.afw.detection.FootprintSet(mi, lsst.afw.detection.Threshold(minPixelToMask))
166 footprints.setMask(mask, crosstalkStr)
167 crosstalk = mask.getPlaneBitMask(crosstalkStr)
169 backgrounds = [
calculateBackground(mi.Factory(mi, amp.getBBox()), badPixels)
for amp
in ccd]
171 subtrahend = mi.Factory(mi.getBBox())
172 subtrahend.set((0, 0, 0))
173 for ii, iAmp
in enumerate(ccd):
174 iImage = subtrahend.Factory(subtrahend, iAmp.getBBox())
175 for jj, jAmp
in enumerate(ccd):
177 assert coeffs[ii, jj] == 0.0
178 if coeffs[ii, jj] == 0.0:
181 jImage =
extractAmp(mi, jAmp, iAmp.getReadoutCorner())
182 jImage.getMask().getArray()[:] &= crosstalk
183 jImage -= backgrounds[jj]
185 iImage.scaledPlus(coeffs[ii, jj], jImage)
189 mask.clearMaskPlane(crosstalkPlane)