23 Apply intra-CCD crosstalk corrections 32 __all__ = [
"CrosstalkConfig",
"CrosstalkTask",
"subtractCrosstalk",
"writeCrosstalkCoeffs"]
36 """Configuration for intra-CCD crosstalk removal""" 37 minPixelToMask = Field(
39 doc=
"Set crosstalk mask plane for pixels over this value.",
42 crosstalkMaskPlane = Field(
44 doc=
"Name for crosstalk mask plane.",
47 crosstalkBackgroundMethod = ChoiceField(
49 doc=
"Type of background subtraction to use when applying correction.",
52 "None":
"Do no background subtraction.",
53 "AMP":
"Subtract amplifier-by-amplifier background levels.",
54 "DETECTOR":
"Subtract detector level background." 60 """Apply intra-CCD crosstalk correction""" 61 ConfigClass = CrosstalkConfig
64 """Placeholder for crosstalk preparation method, e.g., for inter-CCD crosstalk. 68 dataRef : `daf.persistence.butlerSubset.ButlerDataRef` 69 Butler reference of the detector data to be processed. 73 lsst.obs.decam.crosstalk.DecamCrosstalkTask.prepCrosstalk 77 def run(self, exposure, crosstalkSources=None, isTrimmed=False):
78 """Apply intra-CCD crosstalk correction 82 exposure : `lsst.afw.image.Exposure` 83 Exposure for which to remove crosstalk. 84 crosstalkSources : `defaultdict`, optional 85 Image data and crosstalk coefficients from other CCDs/amps that are 86 sources of crosstalk in exposure. 87 The default for intra-CCD crosstalk here is None. 89 The image is already trimmed. 90 This should no longer be needed once DM-15409 is resolved. 95 Raised if called for a detector that does not have a 98 detector = exposure.getDetector()
99 if not detector.hasCrosstalk():
100 raise RuntimeError(
"Attempted to correct crosstalk without crosstalk coefficients")
101 self.log.info(
"Applying crosstalk correction")
103 crosstalkStr=self.config.crosstalkMaskPlane, isTrimmed=isTrimmed,
104 backgroundMethod=self.config.crosstalkBackgroundMethod)
109 X_FLIP = {lsst.afw.table.LL:
False, lsst.afw.table.LR:
True,
110 lsst.afw.table.UL:
False, lsst.afw.table.UR:
True}
111 Y_FLIP = {lsst.afw.table.LL:
False, lsst.afw.table.LR:
False,
112 lsst.afw.table.UL:
True, lsst.afw.table.UR:
True}
116 def run(self, exposure, crosstalkSources=None):
117 self.log.info(
"Not performing any crosstalk correction")
121 """Return an image of the amp 123 The returned image will have the amp's readout corner in the 128 image : `lsst.afw.image.Image` or `lsst.afw.image.MaskedImage` 129 Image containing the amplifier of interest. 130 amp : `lsst.afw.table.AmpInfoRecord` 131 Amplifier information. 132 corner : `lsst.afw.table.ReadoutCorner` or `None` 133 Corner in which to put the amp's readout corner, or `None` for 136 The image is already trimmed. 137 This should no longer be needed once DM-15409 is resolved. 141 output : `lsst.afw.image.Image` 142 Image of the amplifier in the standard configuration. 144 output = image[amp.getBBox()
if isTrimmed
else amp.getRawDataBBox()]
145 ampCorner = amp.getReadoutCorner()
147 xFlip = X_FLIP[corner] ^ X_FLIP[ampCorner]
148 yFlip = Y_FLIP[corner] ^ Y_FLIP[ampCorner]
153 """Calculate median background in image 155 Getting a great background model isn't important for crosstalk correction, 156 since the crosstalk is at a low level. The median should be sufficient. 160 mi : `lsst.afw.image.MaskedImage` 161 MaskedImage for which to measure background. 162 badPixels : `list` of `str` 163 Mask planes to ignore. 168 Median background level. 172 stats.setAndMask(mask.getPlaneBitMask(badPixels))
177 crosstalkStr="CROSSTALK", isTrimmed=False,
178 backgroundMethod="None"):
179 """Subtract the intra-CCD crosstalk from an exposure 181 We set the mask plane indicated by ``crosstalkStr`` in a target amplifier 182 for pixels in a source amplifier that exceed `minPixelToMask`. Note that 183 the correction is applied to all pixels in the amplifier, but only those 184 that have a substantial crosstalk are masked with ``crosstalkStr``. 186 The uncorrected image is used as a template for correction. This is good 187 enough if the crosstalk is small (e.g., coefficients < ~ 1e-3), but if it's 188 larger you may want to iterate. 190 This method needs unittests (DM-18876), but such testing requires 191 DM-18610 to allow the test detector to have the crosstalk 196 exposure : `lsst.afw.image.Exposure` 197 Exposure for which to subtract crosstalk. 198 badPixels : `list` of `str` 199 Mask planes to ignore. 200 minPixelToMask : `float` 201 Minimum pixel value (relative to the background level) in 202 source amplifier for which to set ``crosstalkStr`` mask plane 205 Mask plane name for pixels greatly modified by crosstalk. 207 The image is already trimmed. 208 This should no longer be needed once DM-15409 is resolved. 209 backgroundMethod : `str` 210 Method used to subtract the background. "AMP" uses 211 amplifier-by-amplifier background levels, "DETECTOR" uses full 212 exposure/maskedImage levels. Any other value results in no 213 background subtraction. 215 mi = exposure.getMaskedImage()
218 ccd = exposure.getDetector()
220 coeffs = ccd.getCrosstalk()
221 assert coeffs.shape == (numAmps, numAmps)
229 backgrounds = [0.0
for amp
in ccd]
230 if backgroundMethod
is None:
232 elif backgroundMethod ==
"AMP":
234 elif backgroundMethod ==
"DETECTOR":
238 crosstalkPlane = mask.addMaskPlane(crosstalkStr)
240 thresholdBackground))
241 footprints.setMask(mask, crosstalkStr)
242 crosstalk = mask.getPlaneBitMask(crosstalkStr)
245 subtrahend = mi.Factory(mi.getBBox())
246 subtrahend.set((0, 0, 0))
247 for ii, iAmp
in enumerate(ccd):
248 iImage = subtrahend[iAmp.getBBox()
if isTrimmed
else iAmp.getRawDataBBox()]
249 for jj, jAmp
in enumerate(ccd):
251 assert coeffs[ii, jj] == 0.0
252 if coeffs[ii, jj] == 0.0:
255 jImage =
extractAmp(mi, jAmp, iAmp.getReadoutCorner(), isTrimmed)
256 jImage.getMask().getArray()[:] &= crosstalk
257 jImage -= backgrounds[jj]
259 iImage.scaledPlus(coeffs[ii, jj], jImage)
263 mask.clearMaskPlane(crosstalkPlane)
268 """Write a yaml file containing the crosstalk coefficients 270 The coeff array is indexed by [i, j] where i and j are amplifiers 271 corresponding to the amplifiers in det 275 outputFileName : `str` 276 Name of output yaml file 277 coeff : `numpy.array(namp, namp)` 278 numpy array of coefficients 279 det : `lsst.afw.cameraGeom.Detector` 280 Used to provide the list of amplifier names; 281 if None use ['0', '1', ...] 283 Name of CCD, used to index the yaml file 284 If all CCDs are identical could be the type (e.g. ITL) 286 Indent width to use when writing the yaml file 290 ampNames = [str(i)
for i
in range(coeff.shape[0])]
292 ampNames = [a.getName()
for a
in det]
294 assert coeff.shape == (len(ampNames), len(ampNames))
298 with open(outputFileName,
"w")
as fd:
299 print(indent*
" " +
"crosstalk :", file=fd)
301 print(indent*
" " +
"%s :" % crosstalkName, file=fd)
304 for i, ampNameI
in enumerate(ampNames):
305 print(indent*
" " +
"%s : {" % ampNameI, file=fd)
307 print(indent*
" ", file=fd, end=
'')
309 for j, ampNameJ
in enumerate(ampNames):
310 print(
"%s : %11.4e, " % (ampNameJ, coeff[i, j]), file=fd,
311 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)