23 Apply intra-detector crosstalk corrections 33 __all__ = [
"CrosstalkConfig",
"CrosstalkTask",
"subtractCrosstalk",
"writeCrosstalkCoeffs",
38 """Configuration for intra-detector crosstalk removal.""" 41 doc=
"Set crosstalk mask plane for pixels over this value.",
46 doc=
"Name for crosstalk mask plane.",
51 doc=
"Type of background subtraction to use when applying correction.",
54 "None":
"Do no background subtraction.",
55 "AMP":
"Subtract amplifier-by-amplifier background levels.",
56 "DETECTOR":
"Subtract detector level background." 61 doc=
"Ignore the detector crosstalk information in favor of CrosstalkConfig values?",
66 doc=(
"Amplifier-indexed crosstalk coefficients to use. This should be arranged as a 1 x nAmp**2 " 67 "list of coefficients, such that when reshaped by crosstalkShape, the result is nAmp x nAmp. " 68 "This matrix should be structured so CT * [amp0 amp1 amp2 ...]^T returns the column " 69 "vector [corr0 corr1 corr2 ...]^T."),
74 doc=
"Shape of the coefficient array. This should be equal to [nAmp, nAmp].",
79 """Return a 2-D numpy array of crosstalk coefficients in the proper shape. 83 detector : `lsst.afw.cameraGeom.detector` 84 Detector that is to be crosstalk corrected. 88 coeffs : `numpy.ndarray` 89 Crosstalk coefficients that can be used to correct the detector. 94 Raised if no coefficients could be generated from this detector/configuration. 98 if detector
is not None:
100 if coeffs.shape != (nAmp, nAmp):
101 raise RuntimeError(
"Constructed crosstalk coeffients do not match detector shape. " +
102 f
"{coeffs.shape} {nAmp}")
104 elif detector
is not None and detector.hasCrosstalk()
is True:
106 return detector.getCrosstalk()
108 raise RuntimeError(
"Attempted to correct crosstalk without crosstalk coefficients")
111 """Return a boolean indicating if crosstalk coefficients exist. 115 detector : `lsst.afw.cameraGeom.detector` 116 Detector that is to be crosstalk corrected. 120 hasCrosstalk : `bool` 121 True if this detector/configuration has crosstalk coefficients defined. 125 elif detector
is not None and detector.hasCrosstalk()
is True:
132 """Apply intra-detector crosstalk correction.""" 133 ConfigClass = CrosstalkConfig
134 _DefaultName =
'isrCrosstalk' 137 """Placeholder for crosstalk preparation method, e.g., for inter-detector crosstalk. 141 dataRef : `daf.persistence.butlerSubset.ButlerDataRef` 142 Butler reference of the detector data to be processed. 146 lsst.obs.decam.crosstalk.DecamCrosstalkTask.prepCrosstalk 150 def run(self, exposure, crosstalkSources=None, isTrimmed=False):
151 """Apply intra-detector crosstalk correction 155 exposure : `lsst.afw.image.Exposure` 156 Exposure for which to remove crosstalk. 157 crosstalkSources : `defaultdict`, optional 158 Image data and crosstalk coefficients from other detectors/amps that are 159 sources of crosstalk in exposure. 160 The default for intra-detector crosstalk here is None. 162 The image is already trimmed. 163 This should no longer be needed once DM-15409 is resolved. 168 Raised if called for a detector that does not have a 171 detector = exposure.getDetector()
172 if not self.config.hasCrosstalk(detector=detector):
173 raise RuntimeError(
"Attempted to correct crosstalk without crosstalk coefficients")
174 coeffs = self.config.getCrosstalk(detector=detector)
176 self.log.
info(
"Applying crosstalk correction.")
178 minPixelToMask=self.config.minPixelToMask,
179 crosstalkStr=self.config.crosstalkMaskPlane, isTrimmed=isTrimmed,
180 backgroundMethod=self.config.crosstalkBackgroundMethod)
185 X_FLIP = {lsst.afw.table.LL:
False, lsst.afw.table.LR:
True,
186 lsst.afw.table.UL:
False, lsst.afw.table.UR:
True}
187 Y_FLIP = {lsst.afw.table.LL:
False, lsst.afw.table.LR:
False,
188 lsst.afw.table.UL:
True, lsst.afw.table.UR:
True}
192 def run(self, exposure, crosstalkSources=None):
193 self.
log.
info(
"Not performing any crosstalk correction")
197 """Return an image of the amp 199 The returned image will have the amp's readout corner in the 204 image : `lsst.afw.image.Image` or `lsst.afw.image.MaskedImage` 205 Image containing the amplifier of interest. 206 amp : `lsst.afw.table.AmpInfoRecord` 207 Amplifier information. 208 corner : `lsst.afw.table.ReadoutCorner` or `None` 209 Corner in which to put the amp's readout corner, or `None` for 212 The image is already trimmed. 213 This should no longer be needed once DM-15409 is resolved. 217 output : `lsst.afw.image.Image` 218 Image of the amplifier in the standard configuration. 220 output = image[amp.getBBox()
if isTrimmed
else amp.getRawDataBBox()]
221 ampCorner = amp.getReadoutCorner()
223 xFlip = X_FLIP[corner] ^ X_FLIP[ampCorner]
224 yFlip = Y_FLIP[corner] ^ Y_FLIP[ampCorner]
229 """Calculate median background in image 231 Getting a great background model isn't important for crosstalk correction, 232 since the crosstalk is at a low level. The median should be sufficient. 236 mi : `lsst.afw.image.MaskedImage` 237 MaskedImage for which to measure background. 238 badPixels : `list` of `str` 239 Mask planes to ignore. 244 Median background level. 248 stats.setAndMask(mask.getPlaneBitMask(badPixels))
253 badPixels=["BAD"], minPixelToMask=45000,
254 crosstalkStr="CROSSTALK", isTrimmed=False,
255 backgroundMethod="None"):
256 """Subtract the intra-detector crosstalk from an exposure 258 We set the mask plane indicated by ``crosstalkStr`` in a target amplifier 259 for pixels in a source amplifier that exceed `minPixelToMask`. Note that 260 the correction is applied to all pixels in the amplifier, but only those 261 that have a substantial crosstalk are masked with ``crosstalkStr``. 263 The uncorrected image is used as a template for correction. This is good 264 enough if the crosstalk is small (e.g., coefficients < ~ 1e-3), but if it's 265 larger you may want to iterate. 267 This method needs unittests (DM-18876), but such testing requires 268 DM-18610 to allow the test detector to have the crosstalk 273 exposure : `lsst.afw.image.Exposure` 274 Exposure for which to subtract crosstalk. 275 crosstalkCoeffs : `numpy.ndarray` 276 Coefficients to use to correct crosstalk. 277 badPixels : `list` of `str` 278 Mask planes to ignore. 279 minPixelToMask : `float` 280 Minimum pixel value (relative to the background level) in 281 source amplifier for which to set ``crosstalkStr`` mask plane 284 Mask plane name for pixels greatly modified by crosstalk. 286 The image is already trimmed. 287 This should no longer be needed once DM-15409 is resolved. 288 backgroundMethod : `str` 289 Method used to subtract the background. "AMP" uses 290 amplifier-by-amplifier background levels, "DETECTOR" uses full 291 exposure/maskedImage levels. Any other value results in no 292 background subtraction. 294 mi = exposure.getMaskedImage()
297 ccd = exposure.getDetector()
299 if crosstalkCoeffs
is None:
300 coeffs = ccd.getCrosstalk()
302 coeffs = crosstalkCoeffs
303 assert coeffs.shape == (numAmps, numAmps)
311 backgrounds = [0.0
for amp
in ccd]
312 if backgroundMethod
is None:
314 elif backgroundMethod ==
"AMP":
316 elif backgroundMethod ==
"DETECTOR":
320 crosstalkPlane = mask.addMaskPlane(crosstalkStr)
322 thresholdBackground))
323 footprints.setMask(mask, crosstalkStr)
324 crosstalk = mask.getPlaneBitMask(crosstalkStr)
327 subtrahend = mi.Factory(mi.getBBox())
328 subtrahend.set((0, 0, 0))
329 for ii, iAmp
in enumerate(ccd):
330 iImage = subtrahend[iAmp.getBBox()
if isTrimmed
else iAmp.getRawDataBBox()]
331 for jj, jAmp
in enumerate(ccd):
333 assert coeffs[ii, jj] == 0.0
334 if coeffs[ii, jj] == 0.0:
337 jImage =
extractAmp(mi, jAmp, iAmp.getReadoutCorner(), isTrimmed)
338 jImage.getMask().getArray()[:] &= crosstalk
339 jImage -= backgrounds[jj]
341 iImage.scaledPlus(coeffs[ii, jj], jImage)
345 mask.clearMaskPlane(crosstalkPlane)
350 """Write a yaml file containing the crosstalk coefficients 352 The coeff array is indexed by [i, j] where i and j are amplifiers 353 corresponding to the amplifiers in det 357 outputFileName : `str` 358 Name of output yaml file 359 coeff : `numpy.array(namp, namp)` 360 numpy array of coefficients 361 det : `lsst.afw.cameraGeom.Detector` 362 Used to provide the list of amplifier names; 363 if None use ['0', '1', ...] 365 Name of detector, used to index the yaml file 366 If all detectors are identical could be the type (e.g. ITL) 368 Indent width to use when writing the yaml file 372 ampNames = [
str(i)
for i
in range(coeff.shape[0])]
374 ampNames = [a.getName()
for a
in det]
376 assert coeff.shape == (len(ampNames), len(ampNames))
380 with open(outputFileName,
"w")
as fd:
381 print(indent*
" " +
"crosstalk :", file=fd)
383 print(indent*
" " +
"%s :" % crosstalkName, file=fd)
386 for i, ampNameI
in enumerate(ampNames):
387 print(indent*
" " +
"%s : {" % ampNameI, file=fd)
389 print(indent*
" ", file=fd, end=
'')
391 for j, ampNameJ
in enumerate(ampNames):
392 print(
"%s : %11.4e, " % (ampNameJ, coeff[i, j]), file=fd,
393 end=
'\n' + indent*
" " if j%4 == 3
else '')
def run(self, exposure, crosstalkSources=None, isTrimmed=False)
std::shared_ptr< ImageT > flipImage(ImageT const &inImage, bool flipLR, bool flipTB)
Flip an image left–right and/or top–bottom.
A Threshold is used to pass a threshold value to detection algorithms.
def run(self, exposure, crosstalkSources=None)
Statistics makeStatistics(lsst::afw::math::MaskedVector< EntryT > const &mv, std::vector< WeightPixel > const &vweights, int const flags, StatisticsControl const &sctrl=StatisticsControl())
The makeStatistics() overload to handle lsst::afw::math::MaskedVector<>
Pass parameters to a Statistics object.
def calculateBackground(mi, badPixels=["BAD"])
def hasCrosstalk(self, detector=None)
def extractAmp(image, amp, corner, isTrimmed=False)
def prepCrosstalk(self, dataRef)
def getCrosstalk(self, detector=None)
def subtractCrosstalk(exposure, crosstalkCoeffs=None, badPixels=["BAD"], minPixelToMask=45000, crosstalkStr="CROSSTALK", isTrimmed=False, backgroundMethod="None")
def writeCrosstalkCoeffs(outputFileName, coeff, det=None, crosstalkName="Unknown", indent=2)