23 Apply intra-CCD crosstalk corrections 32 __all__ = [
"CrosstalkConfig",
"CrosstalkTask",
"subtractCrosstalk",
"writeCrosstalkCoeffs",
37 """Configuration for intra-CCD crosstalk removal""" 38 minPixelToMask = Field(
40 doc=
"Set crosstalk mask plane for pixels over this value.",
43 crosstalkMaskPlane = Field(
45 doc=
"Name for crosstalk mask plane.",
48 crosstalkBackgroundMethod = ChoiceField(
50 doc=
"Type of background subtraction to use when applying correction.",
53 "None":
"Do no background subtraction.",
54 "AMP":
"Subtract amplifier-by-amplifier background levels.",
55 "DETECTOR":
"Subtract detector level background." 61 """Apply intra-CCD crosstalk correction""" 62 ConfigClass = CrosstalkConfig
63 _DefaultName =
'isrCrosstalk' 66 """Placeholder for crosstalk preparation method, e.g., for inter-CCD crosstalk. 70 dataRef : `daf.persistence.butlerSubset.ButlerDataRef` 71 Butler reference of the detector data to be processed. 75 lsst.obs.decam.crosstalk.DecamCrosstalkTask.prepCrosstalk 79 def run(self, exposure, crosstalkSources=None, isTrimmed=False):
80 """Apply intra-CCD crosstalk correction 84 exposure : `lsst.afw.image.Exposure` 85 Exposure for which to remove crosstalk. 86 crosstalkSources : `defaultdict`, optional 87 Image data and crosstalk coefficients from other CCDs/amps that are 88 sources of crosstalk in exposure. 89 The default for intra-CCD crosstalk here is None. 91 The image is already trimmed. 92 This should no longer be needed once DM-15409 is resolved. 97 Raised if called for a detector that does not have a 100 detector = exposure.getDetector()
101 if not detector.hasCrosstalk():
102 raise RuntimeError(
"Attempted to correct crosstalk without crosstalk coefficients")
103 self.log.info(
"Applying crosstalk correction.")
105 crosstalkStr=self.config.crosstalkMaskPlane, isTrimmed=isTrimmed,
106 backgroundMethod=self.config.crosstalkBackgroundMethod)
111 X_FLIP = {lsst.afw.table.LL:
False, lsst.afw.table.LR:
True,
112 lsst.afw.table.UL:
False, lsst.afw.table.UR:
True}
113 Y_FLIP = {lsst.afw.table.LL:
False, lsst.afw.table.LR:
False,
114 lsst.afw.table.UL:
True, lsst.afw.table.UR:
True}
118 def run(self, exposure, crosstalkSources=None):
119 self.log.info(
"Not performing any crosstalk correction")
123 """Return an image of the amp 125 The returned image will have the amp's readout corner in the 130 image : `lsst.afw.image.Image` or `lsst.afw.image.MaskedImage` 131 Image containing the amplifier of interest. 132 amp : `lsst.afw.table.AmpInfoRecord` 133 Amplifier information. 134 corner : `lsst.afw.table.ReadoutCorner` or `None` 135 Corner in which to put the amp's readout corner, or `None` for 138 The image is already trimmed. 139 This should no longer be needed once DM-15409 is resolved. 143 output : `lsst.afw.image.Image` 144 Image of the amplifier in the standard configuration. 146 output = image[amp.getBBox()
if isTrimmed
else amp.getRawDataBBox()]
147 ampCorner = amp.getReadoutCorner()
149 xFlip = X_FLIP[corner] ^ X_FLIP[ampCorner]
150 yFlip = Y_FLIP[corner] ^ Y_FLIP[ampCorner]
155 """Calculate median background in image 157 Getting a great background model isn't important for crosstalk correction, 158 since the crosstalk is at a low level. The median should be sufficient. 162 mi : `lsst.afw.image.MaskedImage` 163 MaskedImage for which to measure background. 164 badPixels : `list` of `str` 165 Mask planes to ignore. 170 Median background level. 174 stats.setAndMask(mask.getPlaneBitMask(badPixels))
179 crosstalkStr="CROSSTALK", isTrimmed=False,
180 backgroundMethod="None"):
181 """Subtract the intra-CCD crosstalk from an exposure 183 We set the mask plane indicated by ``crosstalkStr`` in a target amplifier 184 for pixels in a source amplifier that exceed `minPixelToMask`. Note that 185 the correction is applied to all pixels in the amplifier, but only those 186 that have a substantial crosstalk are masked with ``crosstalkStr``. 188 The uncorrected image is used as a template for correction. This is good 189 enough if the crosstalk is small (e.g., coefficients < ~ 1e-3), but if it's 190 larger you may want to iterate. 192 This method needs unittests (DM-18876), but such testing requires 193 DM-18610 to allow the test detector to have the crosstalk 198 exposure : `lsst.afw.image.Exposure` 199 Exposure for which to subtract crosstalk. 200 badPixels : `list` of `str` 201 Mask planes to ignore. 202 minPixelToMask : `float` 203 Minimum pixel value (relative to the background level) in 204 source amplifier for which to set ``crosstalkStr`` mask plane 207 Mask plane name for pixels greatly modified by crosstalk. 209 The image is already trimmed. 210 This should no longer be needed once DM-15409 is resolved. 211 backgroundMethod : `str` 212 Method used to subtract the background. "AMP" uses 213 amplifier-by-amplifier background levels, "DETECTOR" uses full 214 exposure/maskedImage levels. Any other value results in no 215 background subtraction. 217 mi = exposure.getMaskedImage()
220 ccd = exposure.getDetector()
222 coeffs = ccd.getCrosstalk()
223 assert coeffs.shape == (numAmps, numAmps)
231 backgrounds = [0.0
for amp
in ccd]
232 if backgroundMethod
is None:
234 elif backgroundMethod ==
"AMP":
236 elif backgroundMethod ==
"DETECTOR":
240 crosstalkPlane = mask.addMaskPlane(crosstalkStr)
242 thresholdBackground))
243 footprints.setMask(mask, crosstalkStr)
244 crosstalk = mask.getPlaneBitMask(crosstalkStr)
247 subtrahend = mi.Factory(mi.getBBox())
248 subtrahend.set((0, 0, 0))
249 for ii, iAmp
in enumerate(ccd):
250 iImage = subtrahend[iAmp.getBBox()
if isTrimmed
else iAmp.getRawDataBBox()]
251 for jj, jAmp
in enumerate(ccd):
253 assert coeffs[ii, jj] == 0.0
254 if coeffs[ii, jj] == 0.0:
257 jImage =
extractAmp(mi, jAmp, iAmp.getReadoutCorner(), isTrimmed)
258 jImage.getMask().getArray()[:] &= crosstalk
259 jImage -= backgrounds[jj]
261 iImage.scaledPlus(coeffs[ii, jj], jImage)
265 mask.clearMaskPlane(crosstalkPlane)
270 """Write a yaml file containing the crosstalk coefficients 272 The coeff array is indexed by [i, j] where i and j are amplifiers 273 corresponding to the amplifiers in det 277 outputFileName : `str` 278 Name of output yaml file 279 coeff : `numpy.array(namp, namp)` 280 numpy array of coefficients 281 det : `lsst.afw.cameraGeom.Detector` 282 Used to provide the list of amplifier names; 283 if None use ['0', '1', ...] 285 Name of CCD, used to index the yaml file 286 If all CCDs are identical could be the type (e.g. ITL) 288 Indent width to use when writing the yaml file 292 ampNames = [str(i)
for i
in range(coeff.shape[0])]
294 ampNames = [a.getName()
for a
in det]
296 assert coeff.shape == (len(ampNames), len(ampNames))
300 with open(outputFileName,
"w")
as fd:
301 print(indent*
" " +
"crosstalk :", file=fd)
303 print(indent*
" " +
"%s :" % crosstalkName, file=fd)
306 for i, ampNameI
in enumerate(ampNames):
307 print(indent*
" " +
"%s : {" % ampNameI, file=fd)
309 print(indent*
" ", file=fd, end=
'')
311 for j, ampNameJ
in enumerate(ampNames):
312 print(
"%s : %11.4e, " % (ampNameJ, coeff[i, j]), file=fd,
313 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)
def subtractCrosstalk(exposure, badPixels=["BAD"], minPixelToMask=45000, crosstalkStr="CROSSTALK", isTrimmed=False, backgroundMethod="None")
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())
def calculateBackground(mi, badPixels=["BAD"])
def extractAmp(image, amp, corner, isTrimmed=False)
def prepCrosstalk(self, dataRef)
def writeCrosstalkCoeffs(outputFileName, coeff, det=None, crosstalkName="Unknown", indent=2)