23 Measure intra-CCD crosstalk coefficients. 26 __all__ = [
"extractCrosstalkRatios",
"measureCrosstalkCoefficients",
27 "MeasureCrosstalkConfig",
"MeasureCrosstalkTask"]
37 from .crosstalk
import calculateBackground, extractAmp
38 from .isrTask
import IsrTask
42 """Extract crosstalk ratios between different amplifiers 44 For pixels above ``threshold``, we calculate the ratio between each 45 target amp and source amp. We return a list of ratios for each pixel 46 for each target/source combination, as a matrix of lists. 50 exposure : `lsst.afw.image.Exposure` 51 Exposure for which to measure crosstalk. 53 Lower limit on pixels for which we measure crosstalk. 54 badPixels : `list` of `str` 55 Mask planes indicating a pixel is bad. 59 ratios : `list` of `list` of `numpy.ndarray` 60 A matrix of pixel arrays. ``ratios[i][j]`` is an array of 61 the fraction of the ``j``-th amp present on the ``i``-th amp. 62 The value is `None` for the diagonal elements. 64 mi = exposure.getMaskedImage()
66 detected = mi.getMask().getPlaneBitMask(
"DETECTED")
67 bad = mi.getMask().getPlaneBitMask(badPixels)
70 ccd = exposure.getDetector()
72 ratios = [[
None for iAmp
in ccd]
for jAmp
in ccd]
74 for ii, iAmp
in enumerate(ccd):
75 iImage = mi.Factory(mi, iAmp.getBBox())
76 iMask = iImage.getMask().getArray()
77 select = (iMask & detected > 0) & (iMask & bad == 0) & np.isfinite(iImage.getImage().getArray())
78 for jj, jAmp
in enumerate(ccd):
81 jImage =
extractAmp(mi.getImage(), jAmp, iAmp.getReadoutCorner())
82 ratios[jj][ii] = (jImage.getArray()[select] - bg)/iImage.getImage().getArray()[select]
88 """Measure crosstalk coefficients from the ratios 90 Given a list of ratios for each target/source amp combination, 91 we measure a robust mean and error. 93 The coefficient errors returned are the (robust) standard deviation of 98 ratios : `list` of `list` of `numpy.ndarray` 99 Matrix of arrays of ratios. 101 Number of rejection iterations. 103 Rejection threshold (sigma). 107 coeff : `numpy.ndarray` 108 Crosstalk coefficients. 109 coeffErr : `numpy.ndarray` 110 Crosstalk coefficient errors. 111 coeffNum : `numpy.ndarray` 112 Number of pixels for each measurement. 114 numAmps = len(ratios)
115 assert all(len(rr) == numAmps
for rr
in ratios)
117 coeff = np.zeros((numAmps, numAmps))
118 coeffErr = np.zeros((numAmps, numAmps))
119 coeffNum = np.zeros((numAmps, numAmps), dtype=int)
121 for ii, jj
in itertools.product(range(numAmps), range(numAmps)):
124 values = np.array(ratios[ii][jj])
125 values = values[np.abs(values) < 1.0]
126 for rej
in range(rejIter):
127 lo, med, hi = np.percentile(values, [25.0, 50.0, 75.0])
128 sigma = 0.741*(hi - lo)
129 good = np.abs(values - med) < rejSigma*sigma
130 if good.sum() == len(good):
132 values = values[good]
134 coeff[ii][jj] = np.mean(values)
135 coeffErr[ii][jj] = np.std(values)
136 coeffNum[ii][jj] = len(values)
138 return coeff, coeffErr, coeffNum
142 """Configuration for MeasureCrosstalkTask""" 143 isr = ConfigurableField(target=IsrTask, doc=
"Instrument signature removal")
144 threshold = Field(dtype=float, default=30000, doc=
"Minimum level for which to measure crosstalk")
145 badMask = ListField(dtype=str, default=[
"SAT",
"BAD",
"INTRP"], doc=
"Mask planes to ignore")
146 rejIter = Field(dtype=int, default=3, doc=
"Number of rejection iterations")
147 rejSigma = Field(dtype=float, default=2.0, doc=
"Rejection threshold (sigma)")
150 Config.setDefaults(self)
151 self.
isr.doWrite =
False 152 self.
isr.growSaturationFootprintSize = 0
156 """Measure intra-CCD crosstalk 158 This Task behaves in a scatter-gather fashion: 159 * Scatter: get ratios for each CCD. 160 * Gather: combine ratios to produce crosstalk coefficients. 162 ConfigClass = MeasureCrosstalkConfig
163 _DefaultName =
"measureCrosstalk" 166 CmdLineTask.__init__(self, *args, **kwargs)
167 self.makeSubtask(
"isr")
170 def _makeArgumentParser(cls):
171 parser = super(MeasureCrosstalkTask, cls)._makeArgumentParser()
172 parser.add_argument(
"--dump-ratios", dest=
"dumpRatios",
173 help=
"Name of pickle file to which to write crosstalk ratios")
178 """Implement scatter/gather 182 coeff : `numpy.ndarray` 183 Crosstalk coefficients. 184 coeffErr : `numpy.ndarray` 185 Crosstalk coefficient errors. 186 coeffNum : `numpy.ndarray` 187 Number of pixels used for crosstalk measurement. 189 kwargs[
"doReturnResults"] =
True 190 results = super(MeasureCrosstalkTask, cls).
parseAndRun(*args, **kwargs)
191 task = cls(config=results.parsedCmd.config, log=results.parsedCmd.log)
192 resultList = [rr.result
for rr
in results.resultList]
193 if results.parsedCmd.dumpRatios:
195 pickle.dump(resultList, open(results.parsedCmd.dumpRatios,
"w"))
196 return task.reduce(resultList)
199 """Get crosstalk ratios for CCD 203 dataRef : `lsst.daf.peristence.ButlerDataRef` 204 Data reference for CCD. 208 ratios : `list` of `list` of `numpy.ndarray` 209 A matrix of pixel arrays. 211 exposure = self.isr.
runDataRef(dataRef).exposure
213 self.log.info(
"Extracted %d pixels from %s",
214 sum(len(jj)
for ii
in ratios
for jj
in ii
if jj
is not None), dataRef.dataId)
218 """Combine ratios to produce crosstalk coefficients 222 ratioList : `list` of `list` of `list` of `numpy.ndarray` 223 A list of matrices of arrays; a list of results from 224 `extractCrosstalkRatios`. 228 coeff : `numpy.ndarray` 229 Crosstalk coefficients. 230 coeffErr : `numpy.ndarray` 231 Crosstalk coefficient errors. 232 coeffNum : `numpy.ndarray` 233 Number of pixels used for crosstalk measurement. 235 numAmps = len(ratioList[0])
236 assert all(len(rr) == numAmps
for rr
in ratioList)
237 assert all(all(len(xx) == numAmps
for xx
in rr)
for rr
in ratioList)
238 ratios = [[
None for jj
in range(numAmps)]
for ii
in range(numAmps)]
239 for ii, jj
in itertools.product(range(numAmps), range(numAmps)):
243 values = [rr[ii][jj]
for rr
in ratioList]
244 num = sum(len(vv)
for vv
in values)
246 raise RuntimeError(
"No values for matrix element %d,%d" % (ii, jj))
247 result = np.concatenate([vv
for vv
in values
if len(vv) > 0])
248 ratios[ii][jj] = result
250 self.config.rejSigma)
251 self.log.info(
"Coefficients:\n%s\n", coeff)
252 self.log.info(
"Errors:\n%s\n", coeffErr)
253 self.log.info(
"Numbers:\n%s\n", coeffNum)
254 return coeff, coeffErr, coeffNum
256 def _getConfigName(self):
257 """Disable config output""" 260 def _getMetadataName(self):
261 """Disable metdata output""" def extractAmp(image, amp, corner)
def __init__(self, args, kwargs)
def parseAndRun(cls, args, kwargs)
def reduce(self, ratioList)
def measureCrosstalkCoefficients(ratios, rejIter=3, rejSigma=2.0)
def calculateBackground(mi, badPixels=["BAD"])
def extractCrosstalkRatios(exposure, threshold=30000, badPixels=["SAT", BAD, INTRP)
def runDataRef(self, dataRef)