lsst.ip.isr  15.0-5-g23e394c+17
crosstalk.py
Go to the documentation of this file.
1 #
2 # LSST Data Management System
3 # Copyright 2008-2017 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 #
22 """
23 Apply intra-CCD crosstalk corrections
24 """
25 
26 import lsst.afw.math
27 import lsst.afw.table
28 import lsst.afw.detection
29 from lsst.pex.config import Config, Field
30 from lsst.pipe.base import Task
31 
32 __all__ = ["CrosstalkConfig", "CrosstalkTask", "subtractCrosstalk"]
33 
34 
35 class CrosstalkConfig(Config):
36  """Configuration for intra-CCD crosstalk removal"""
37  minPixelToMask = Field(dtype=float, default=45000,
38  doc="Set crosstalk mask plane for pixels over this value")
39  crosstalkMaskPlane = Field(dtype=str, default="CROSSTALK", doc="Name for crosstalk mask plane")
40 
41 
42 class CrosstalkTask(Task):
43  """Apply intra-CCD crosstalk correction"""
44  ConfigClass = CrosstalkConfig
45 
46  def prepCrosstalk(self, dataRef):
47  """Placeholder for crosstalk preparation method, e.g., for inter-CCD crosstalk.
48 
49  See also
50  --------
51  lsst.obs.decam.crosstalk.DecamCrosstalkTask.prepCrosstalk
52  """
53  return
54 
55  def run(self, exposure, crosstalkSources=None):
56  """Apply intra-CCD crosstalk correction
57 
58  Parameters
59  ----------
60  exposure : `lsst.afw.image.Exposure`
61  Exposure for which to remove crosstalk.
62  crosstalkSources : `defaultdict`, optional
63  Image data and crosstalk coefficients from other CCDs/amps that are
64  sources of crosstalk in exposure.
65  The default for intra-CCD crosstalk here is None.
66  """
67  detector = exposure.getDetector()
68  if not detector.hasCrosstalk():
69  self.log.warn("Crosstalk correction skipped: no crosstalk coefficients for detector")
70  return
71  self.log.info("Applying crosstalk correction")
72  subtractCrosstalk(exposure, minPixelToMask=self.config.minPixelToMask,
73  crosstalkStr=self.config.crosstalkMaskPlane)
74 
75 
76 # Flips required to get the corner to the lower-left
77 # (an arbitrary choice; flips are relative, so the choice of reference here is not important)
78 X_FLIP = {lsst.afw.table.LL: False, lsst.afw.table.LR: True,
79  lsst.afw.table.UL: False, lsst.afw.table.UR: True}
80 Y_FLIP = {lsst.afw.table.LL: False, lsst.afw.table.LR: False,
81  lsst.afw.table.UL: True, lsst.afw.table.UR: True}
82 
83 
84 def extractAmp(image, amp, corner):
85  """Return an image of the amp
86 
87  The returned image will have the amp's readout corner in the
88  nominated `corner`.
89 
90  Parameters
91  ----------
92  image : `lsst.afw.image.Image` or `lsst.afw.image.MaskedImage`
93  Image containing the amplifier of interest.
94  amp : `lsst.afw.table.AmpInfoRecord`
95  Amplifier information.
96  corner : `lsst.afw.table.ReadoutCorner` or `None`
97  Corner in which to put the amp's readout corner, or `None` for
98  no flipping.
99 
100  Returns
101  -------
102  output : `lsst.afw.image.Image`
103  Image of the amplifier in the standard configuration.
104  """
105  output = image.Factory(image, amp.getBBox())
106  ampCorner = amp.getReadoutCorner()
107  # Flipping is necessary only if the desired configuration doesn't match what we currently have
108  xFlip = X_FLIP[corner] ^ X_FLIP[ampCorner]
109  yFlip = Y_FLIP[corner] ^ Y_FLIP[ampCorner]
110  return lsst.afw.math.flipImage(output, xFlip, yFlip)
111 
112 
113 def calculateBackground(mi, badPixels=["BAD"]):
114  """Calculate median background in image
115 
116  Getting a great background model isn't important for crosstalk correction,
117  since the crosstalk is at a low level. The median should be sufficient.
118 
119  Parameters
120  ----------
121  mi : `lsst.afw.image.MaskedImage`
122  MaskedImage for which to measure background.
123  badPixels : `list` of `str`
124  Mask planes to ignore.
125 
126  Returns
127  -------
128  bg : `float`
129  Median background level.
130  """
131  mask = mi.getMask()
133  stats.setAndMask(mask.getPlaneBitMask(badPixels))
134  return lsst.afw.math.makeStatistics(mi, lsst.afw.math.MEDIAN, stats).getValue()
135 
136 
137 def subtractCrosstalk(exposure, badPixels=["BAD"], minPixelToMask=45000, crosstalkStr="CROSSTALK"):
138  """Subtract the intra-CCD crosstalk from an exposure
139 
140  We set the mask plane indicated by ``crosstalkStr`` in a target amplifier
141  for pixels in a source amplifier that exceed `minPixelToMask`. Note that
142  the correction is applied to all pixels in the amplifier, but only those
143  that have a substantial crosstalk are masked with ``crosstalkStr``.
144 
145  The uncorrected image is used as a template for correction. This is good
146  enough if the crosstalk is small (e.g., coefficients < ~ 1e-3), but if it's
147  larger you may want to iterate.
148 
149  Parameters
150  ----------
151  exposure : `lsst.afw.image.Exposure`
152  Exposure for which to subtract crosstalk.
153  badPixels : `list` of `str`
154  Mask planes to ignore.
155  minPixelToMask : `float`
156  Minimum pixel value in source amplifier for which to set
157  ``crosstalkStr`` mask plane in target amplifier.
158  crosstalkStr : `str`
159  Mask plane name for pixels greatly modified by crosstalk.
160  """
161  mi = exposure.getMaskedImage()
162  mask = mi.getMask()
163 
164  ccd = exposure.getDetector()
165  numAmps = len(ccd)
166  coeffs = ccd.getCrosstalk()
167  assert coeffs.shape == (numAmps, numAmps)
168 
169  # Set the crosstalkStr bit for the bright pixels (those which will have significant crosstalk correction)
170  crosstalkPlane = mask.addMaskPlane(crosstalkStr)
171  footprints = lsst.afw.detection.FootprintSet(mi, lsst.afw.detection.Threshold(minPixelToMask))
172  footprints.setMask(mask, crosstalkStr)
173  crosstalk = mask.getPlaneBitMask(crosstalkStr)
174 
175  backgrounds = [calculateBackground(mi.Factory(mi, amp.getBBox()), badPixels) for amp in ccd]
176 
177  subtrahend = mi.Factory(mi.getBBox())
178  subtrahend.set((0, 0, 0))
179  for ii, iAmp in enumerate(ccd):
180  iImage = subtrahend.Factory(subtrahend, iAmp.getBBox())
181  for jj, jAmp in enumerate(ccd):
182  if ii == jj:
183  assert coeffs[ii, jj] == 0.0
184  if coeffs[ii, jj] == 0.0:
185  continue
186 
187  jImage = extractAmp(mi, jAmp, iAmp.getReadoutCorner())
188  jImage.getMask().getArray()[:] &= crosstalk # Remove all other masks
189  jImage -= backgrounds[jj]
190 
191  iImage.scaledPlus(coeffs[ii, jj], jImage)
192 
193  # Set crosstalkStr bit only for those pixels that have been significantly modified (i.e., those
194  # masked as such in 'subtrahend'), not necessarily those that are bright originally.
195  mask.clearMaskPlane(crosstalkPlane)
196  mi -= subtrahend # also sets crosstalkStr bit for bright pixels
def subtractCrosstalk(exposure, badPixels=["BAD"], minPixelToMask=45000, crosstalkStr="CROSSTALK")
Definition: crosstalk.py:137
def extractAmp(image, amp, corner)
Definition: crosstalk.py:84
std::shared_ptr< ImageT > flipImage(ImageT const &inImage, bool flipLR, bool flipTB)
Statistics makeStatistics(lsst::afw::math::MaskedVector< EntryT > const &mv, std::vector< WeightPixel > const &vweights, int const flags, StatisticsControl const &sctrl=StatisticsControl())
def calculateBackground(mi, badPixels=["BAD"])
Definition: crosstalk.py:113
def run(self, exposure, crosstalkSources=None)
Definition: crosstalk.py:55
def prepCrosstalk(self, dataRef)
Definition: crosstalk.py:46