lsst.ip.isr  17.0.1-7-g69836a1+7
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", "writeCrosstalkCoeffs"]
33 
34 
35 class CrosstalkConfig(Config):
36  """Configuration for intra-CCD crosstalk removal"""
37  minPixelToMask = Field(
38  dtype=float,
39  doc="Set crosstalk mask plane for pixels over this value",
40  default=45000
41  )
42  crosstalkMaskPlane = Field(
43  dtype=str,
44  doc="Name for crosstalk mask plane",
45  default="CROSSTALK"
46  )
47 
48 
49 class CrosstalkTask(Task):
50  """Apply intra-CCD crosstalk correction"""
51  ConfigClass = CrosstalkConfig
52 
53  def prepCrosstalk(self, dataRef):
54  """Placeholder for crosstalk preparation method, e.g., for inter-CCD crosstalk.
55 
56  Parameters
57  ----------
58  dataRef : `daf.persistence.butlerSubset.ButlerDataRef`
59  Butler reference of the detector data to be processed.
60 
61  See also
62  --------
63  lsst.obs.decam.crosstalk.DecamCrosstalkTask.prepCrosstalk
64  """
65  return
66 
67  def run(self, exposure, crosstalkSources=None):
68  """Apply intra-CCD crosstalk correction
69 
70  Parameters
71  ----------
72  exposure : `lsst.afw.image.Exposure`
73  Exposure for which to remove crosstalk.
74  crosstalkSources : `defaultdict`, optional
75  Image data and crosstalk coefficients from other CCDs/amps that are
76  sources of crosstalk in exposure.
77  The default for intra-CCD crosstalk here is None.
78 
79  Raises
80  ------
81  RuntimeError
82  Raised if called for a detector that does not have a
83  crosstalk correction
84  """
85  detector = exposure.getDetector()
86  if not detector.hasCrosstalk():
87  raise RuntimeError("Attempted to correct crosstalk without crosstalk coefficients")
88  self.log.info("Applying crosstalk correction")
89  subtractCrosstalk(exposure, minPixelToMask=self.config.minPixelToMask,
90  crosstalkStr=self.config.crosstalkMaskPlane)
91 
92 
93 # Flips required to get the corner to the lower-left
94 # (an arbitrary choice; flips are relative, so the choice of reference here is not important)
95 X_FLIP = {lsst.afw.table.LL: False, lsst.afw.table.LR: True,
96  lsst.afw.table.UL: False, lsst.afw.table.UR: True}
97 Y_FLIP = {lsst.afw.table.LL: False, lsst.afw.table.LR: False,
98  lsst.afw.table.UL: True, lsst.afw.table.UR: True}
99 
100 
102  def run(self, exposure, crosstalkSources=None):
103  self.log.info("Not performing any crosstalk correction")
104 
105 
106 def extractAmp(image, amp, corner, isTrimmed=False):
107  """Return an image of the amp
108 
109  The returned image will have the amp's readout corner in the
110  nominated `corner`.
111 
112  Parameters
113  ----------
114  image : `lsst.afw.image.Image` or `lsst.afw.image.MaskedImage`
115  Image containing the amplifier of interest.
116  amp : `lsst.afw.table.AmpInfoRecord`
117  Amplifier information.
118  corner : `lsst.afw.table.ReadoutCorner` or `None`
119  Corner in which to put the amp's readout corner, or `None` for
120  no flipping.
121  isTrimmed : `bool`
122  The image is already trimmed.
123  This should no longer be needed once DM-15409 is resolved.
124 
125  Returns
126  -------
127  output : `lsst.afw.image.Image`
128  Image of the amplifier in the standard configuration.
129  """
130  output = image[amp.getBBox() if isTrimmed else amp.getRawDataBBox()]
131  ampCorner = amp.getReadoutCorner()
132  # Flipping is necessary only if the desired configuration doesn't match what we currently have
133  xFlip = X_FLIP[corner] ^ X_FLIP[ampCorner]
134  yFlip = Y_FLIP[corner] ^ Y_FLIP[ampCorner]
135  return lsst.afw.math.flipImage(output, xFlip, yFlip)
136 
137 
138 def calculateBackground(mi, badPixels=["BAD"]):
139  """Calculate median background in image
140 
141  Getting a great background model isn't important for crosstalk correction,
142  since the crosstalk is at a low level. The median should be sufficient.
143 
144  Parameters
145  ----------
146  mi : `lsst.afw.image.MaskedImage`
147  MaskedImage for which to measure background.
148  badPixels : `list` of `str`
149  Mask planes to ignore.
150 
151  Returns
152  -------
153  bg : `float`
154  Median background level.
155  """
156  mask = mi.getMask()
158  stats.setAndMask(mask.getPlaneBitMask(badPixels))
159  return lsst.afw.math.makeStatistics(mi, lsst.afw.math.MEDIAN, stats).getValue()
160 
161 
162 def subtractCrosstalk(exposure, badPixels=["BAD"], minPixelToMask=45000, crosstalkStr="CROSSTALK"):
163  """Subtract the intra-CCD crosstalk from an exposure
164 
165  We set the mask plane indicated by ``crosstalkStr`` in a target amplifier
166  for pixels in a source amplifier that exceed `minPixelToMask`. Note that
167  the correction is applied to all pixels in the amplifier, but only those
168  that have a substantial crosstalk are masked with ``crosstalkStr``.
169 
170  The uncorrected image is used as a template for correction. This is good
171  enough if the crosstalk is small (e.g., coefficients < ~ 1e-3), but if it's
172  larger you may want to iterate.
173 
174  Parameters
175  ----------
176  exposure : `lsst.afw.image.Exposure`
177  Exposure for which to subtract crosstalk.
178  badPixels : `list` of `str`
179  Mask planes to ignore.
180  minPixelToMask : `float`
181  Minimum pixel value in source amplifier for which to set
182  ``crosstalkStr`` mask plane in target amplifier.
183  crosstalkStr : `str`
184  Mask plane name for pixels greatly modified by crosstalk.
185  """
186  mi = exposure.getMaskedImage()
187  mask = mi.getMask()
188 
189  ccd = exposure.getDetector()
190  numAmps = len(ccd)
191  coeffs = ccd.getCrosstalk()
192  assert coeffs.shape == (numAmps, numAmps)
193 
194  # Set the crosstalkStr bit for the bright pixels (those which will have significant crosstalk correction)
195  crosstalkPlane = mask.addMaskPlane(crosstalkStr)
196  footprints = lsst.afw.detection.FootprintSet(mi, lsst.afw.detection.Threshold(minPixelToMask))
197  footprints.setMask(mask, crosstalkStr)
198  crosstalk = mask.getPlaneBitMask(crosstalkStr)
199 
200  backgrounds = [calculateBackground(mi[amp.getBBox()], badPixels) for amp in ccd]
201 
202  subtrahend = mi.Factory(mi.getBBox())
203  subtrahend.set((0, 0, 0))
204  for ii, iAmp in enumerate(ccd):
205  iImage = subtrahend[iAmp.getRawDataBBox()]
206  for jj, jAmp in enumerate(ccd):
207  if ii == jj:
208  assert coeffs[ii, jj] == 0.0
209  if coeffs[ii, jj] == 0.0:
210  continue
211 
212  jImage = extractAmp(mi, jAmp, iAmp.getReadoutCorner())
213  jImage.getMask().getArray()[:] &= crosstalk # Remove all other masks
214  jImage -= backgrounds[jj]
215 
216  iImage.scaledPlus(coeffs[ii, jj], jImage)
217 
218  # Set crosstalkStr bit only for those pixels that have been significantly modified (i.e., those
219  # masked as such in 'subtrahend'), not necessarily those that are bright originally.
220  mask.clearMaskPlane(crosstalkPlane)
221  mi -= subtrahend # also sets crosstalkStr bit for bright pixels
222 
223 
224 def writeCrosstalkCoeffs(outputFileName, coeff, det=None, crosstalkName="Unknown", indent=2):
225  """Write a yaml file containing the crosstalk coefficients
226 
227  The coeff array is indexed by [i, j] where i and j are amplifiers
228  corresponding to the amplifiers in det
229 
230  Parameters
231  ----------
232  outputFileName : `str`
233  Name of output yaml file
234  coeff : `numpy.array(namp, namp)`
235  numpy array of coefficients
236  det : `lsst.afw.cameraGeom.Detector`
237  Used to provide the list of amplifier names;
238  if None use ['0', '1', ...]
239  ccdType : `str`
240  Name of CCD, used to index the yaml file
241  If all CCDs are identical could be the type (e.g. ITL)
242  indent : `int`
243  Indent width to use when writing the yaml file
244  """
245 
246  if det is None:
247  ampNames = [str(i) for i in range(coeff.shape[0])]
248  else:
249  ampNames = [a.getName() for a in det]
250 
251  assert coeff.shape == (len(ampNames), len(ampNames))
252 
253  dIndent = indent
254  indent = 0
255  with open(outputFileName, "w") as fd:
256  print(indent*" " + "crosstalk :", file=fd)
257  indent += dIndent
258  print(indent*" " + "%s :" % crosstalkName, file=fd)
259  indent += dIndent
260 
261  for i, ampNameI in enumerate(ampNames):
262  print(indent*" " + "%s : {" % ampNameI, file=fd)
263  indent += dIndent
264  print(indent*" ", file=fd, end='')
265 
266  for j, ampNameJ in enumerate(ampNames):
267  print("%s : %11.4e, " % (ampNameJ, coeff[i, j]), file=fd,
268  end='\n' + indent*" " if j%4 == 3 else '')
269  print("}", file=fd)
270 
271  indent -= dIndent
def subtractCrosstalk(exposure, badPixels=["BAD"], minPixelToMask=45000, crosstalkStr="CROSSTALK")
Definition: crosstalk.py:162
std::shared_ptr< ImageT > flipImage(ImageT const &inImage, bool flipLR, bool flipTB)
def run(self, exposure, crosstalkSources=None)
Definition: crosstalk.py:102
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:138
def run(self, exposure, crosstalkSources=None)
Definition: crosstalk.py:67
def extractAmp(image, amp, corner, isTrimmed=False)
Definition: crosstalk.py:106
def prepCrosstalk(self, dataRef)
Definition: crosstalk.py:53
def writeCrosstalkCoeffs(outputFileName, coeff, det=None, crosstalkName="Unknown", indent=2)
Definition: crosstalk.py:224