23 Measure intra-detector crosstalk coefficients.
26 __all__ = [
"MeasureCrosstalkConfig",
"MeasureCrosstalkTask"]
32 from lsstDebug
import getDebugFrame
36 from lsst.pex.config
import Config, Field, ListField, ConfigurableField
39 from .crosstalk
import calculateBackground, extractAmp, writeCrosstalkCoeffs
40 from .isrTask
import IsrTask
44 """Configuration for MeasureCrosstalkTask."""
45 isr = ConfigurableField(
47 doc=
"Instrument signature removal task to use to process data."
52 doc=
"Minimum level of source pixels for which to measure crosstalk."
57 doc=
"Rerun the ISR, even if postISRCCD files are available?"
61 default=[
"SAT",
"BAD",
"INTRP"],
62 doc=
"Mask planes to ignore when identifying source pixels."
67 doc=
"Number of rejection iterations for final coefficient calculation."
72 doc=
"Rejection threshold (sigma) for final coefficient calculation."
77 doc=
"Have the amplifiers been trimmed before measuring CT?"
81 Config.setDefaults(self)
84 self.
isr.doWrite =
False
85 self.
isr.doOverscan =
True
86 self.
isr.doAssembleCcd =
True
87 self.
isr.doBias =
True
88 self.
isr.doVariance =
False
89 self.
isr.doLinearize =
True
90 self.
isr.doCrosstalk =
False
91 self.
isr.doBrighterFatter =
False
92 self.
isr.doDark =
False
93 self.
isr.doStrayLight =
False
94 self.
isr.doFlat =
False
95 self.
isr.doFringe =
False
96 self.
isr.doApplyGains =
False
97 self.
isr.doDefect =
True
98 self.
isr.doSaturationInterpolation =
False
99 self.
isr.growSaturationFootprintSize = 0
103 """Measure intra-detector crosstalk.
107 The crosstalk this method measures assumes that when a bright
108 pixel is found in one detector amplifier, all other detector
109 amplifiers may see an increase in the same pixel location
110 (relative to the readout amplifier) as these other pixels are read
111 out at the same time.
113 After processing each input exposure through a limited set of ISR
114 stages, bright unmasked pixels above the threshold are identified.
115 The potential CT signal is found by taking the ratio of the
116 appropriate background-subtracted pixel value on the other
117 amplifiers to the input value on the source amplifier. If the
118 source amplifier has a large number of bright pixels as well, the
119 background level may be elevated, leading to poor ratio
122 The set of ratios found between each pair of amplifiers across all
123 input exposures is then gathered to produce the final CT
124 coefficients. The sigma-clipped mean and sigma are returned from
125 these sets of ratios, with the coefficient to supply to the ISR
126 CrosstalkTask() being the multiplicative inverse of these values.
128 ConfigClass = MeasureCrosstalkConfig
129 _DefaultName =
"measureCrosstalk"
132 CmdLineTask.__init__(self, *args, **kwargs)
136 def _makeArgumentParser(cls):
137 parser = super(MeasureCrosstalkTask, cls)._makeArgumentParser()
138 parser.add_argument(
"--crosstalkName",
139 help=
"Name for this set of crosstalk coefficients", default=
"Unknown")
140 parser.add_argument(
"--outputFileName",
141 help=
"Name of yaml file to which to write crosstalk coefficients")
142 parser.add_argument(
"--dump-ratios", dest=
"dumpRatios",
143 help=
"Name of pickle file to which to write crosstalk ratios")
148 """Implement scatter/gather
152 coeff : `numpy.ndarray`
153 Crosstalk coefficients.
154 coeffErr : `numpy.ndarray`
155 Crosstalk coefficient errors.
156 coeffNum : `numpy.ndarray`
157 Number of pixels used for crosstalk measurement.
159 kwargs[
"doReturnResults"] =
True
160 results = super(MeasureCrosstalkTask, cls).
parseAndRun(*args, **kwargs)
161 task =
cls(config=results.parsedCmd.config, log=results.parsedCmd.log)
162 resultList = [rr.result
for rr
in results.resultList]
163 if results.parsedCmd.dumpRatios:
165 pickle.dump(resultList, open(results.parsedCmd.dumpRatios,
"wb"))
166 coeff, coeffErr, coeffNum = task.reduce(resultList)
168 outputFileName = results.parsedCmd.outputFileName
169 if outputFileName
is not None:
170 butler = results.parsedCmd.butler
171 dataId = results.parsedCmd.id.idList[0]
172 dataId[
"detector"] = butler.queryMetadata(
"raw", [
"detector"], dataId)[0]
174 det = butler.get(
'raw', dataId).getDetector()
176 crosstalkName=results.parsedCmd.crosstalkName, indent=2)
184 def _getConfigName(self):
185 """Disable config output."""
188 def _getMetadataName(self):
189 """Disable metdata output."""
193 """Get crosstalk ratios for detector.
197 dataRef : `lsst.daf.peristence.ButlerDataRef`
198 Data references for detectors to process.
202 ratios : `list` of `list` of `numpy.ndarray`
203 A matrix of pixel arrays.
206 if not self.
config.doRerunIsr:
208 exposure = dataRef.get(
"postISRCCD")
213 exposure = self.isr.
runDataRef(dataRef).exposure
215 dataId = dataRef.dataId
216 return self.
run(exposure, dataId=dataId)
218 def run(self, exposure, dataId=None):
219 """Extract and return cross talk ratios for an exposure.
223 exposure : `lsst.afw.image.Exposure`
224 Image data to measure crosstalk ratios from.
226 Optional data ID for the exposure to process; used for logging.
230 ratios : `list` of `list` of `numpy.ndarray`
231 A matrix of pixel arrays.
234 self.
log.
info(
"Extracted %d pixels from %s",
235 sum(len(jj)
for ii
in ratios
for jj
in ii
if jj
is not None), dataId)
239 """Extract crosstalk ratios between different amplifiers.
241 For pixels above ``threshold``, we calculate the ratio between
242 each background-subtracted target amp and the source amp. We
243 return a list of ratios for each pixel for each target/source
244 combination, as a matrix of lists.
248 exposure : `lsst.afw.image.Exposure`
249 Exposure for which to measure crosstalk.
250 threshold : `float`, optional
251 Lower limit on pixels for which we measure crosstalk.
252 badPixels : `list` of `str`, optional
253 Mask planes indicating a pixel is bad.
257 ratios : `list` of `list` of `numpy.ndarray`
258 A matrix of pixel arrays. ``ratios[i][j]`` is an array of
259 the fraction of the ``j``-th amp present on the ``i``-th amp.
260 The value is `None` for the diagonal elements.
264 This has been moved into MeasureCrosstalkTask to allow for easier
267 The lsstDebug.Info() method can be rewritten for __name__ =
268 `lsst.ip.isr.measureCrosstalk`, and supports the parameters:
270 debug.display['extract'] : `bool`
271 Display the exposure under consideration, with the pixels used
272 for crosstalk measurement indicated by the DETECTED mask plane.
273 debug.display['pixels'] : `bool`
274 Display a plot of the ratio calculated for each pixel used in this
275 exposure, split by amplifier pairs. The median value is listed
278 if threshold
is None:
279 threshold = self.
config.threshold
280 if badPixels
is None:
283 mi = exposure.getMaskedImage()
285 detected = mi.getMask().getPlaneBitMask(
"DETECTED")
286 bad = mi.getMask().getPlaneBitMask(badPixels)
291 ccd = exposure.getDetector()
292 ratios = [[
None for iAmp
in ccd]
for jAmp
in ccd]
294 for ii, iAmp
in enumerate(ccd):
295 iImage = mi[iAmp.getBBox()]
296 iMask = iImage.mask.array
297 select = (iMask & detected > 0) & (iMask & bad == 0) & np.isfinite(iImage.image.array)
298 for jj, jAmp
in enumerate(ccd):
301 jImage =
extractAmp(mi.image, jAmp, iAmp.getReadoutCorner(), isTrimmed=self.
config.isTrimmed)
302 ratios[jj][ii] = (jImage.array[select] - bg)/iImage.image.array[select]
303 self.
debugPixels(
'pixels', iImage.image.array[select], jImage.array[select] - bg, ii, jj)
307 """Combine ratios to produce crosstalk coefficients.
311 ratioList : `list` of `list` of `list` of `numpy.ndarray`
312 A list of matrices of arrays; a list of results from
313 `extractCrosstalkRatios`.
317 coeff : `numpy.ndarray`
318 Crosstalk coefficients.
319 coeffErr : `numpy.ndarray`
320 Crosstalk coefficient errors.
321 coeffNum : `numpy.ndarray`
322 Number of pixels used for crosstalk measurement.
327 Raised if there is no crosstalk data available.
331 The lsstDebug.Info() method can be rewritten for __name__ =
332 `lsst.ip.isr.measureCrosstalk`, and supports the parameters:
334 debug.display['reduce'] : `bool`
335 Display a histogram of the combined ratio measurements for
336 a pair of source/target amplifiers from all input
347 assert len(rr) == numAmps
348 assert all(len(xx) == numAmps
for xx
in rr)
351 raise RuntimeError(
"Unable to measure crosstalk signal for any amplifier")
353 ratios = [[
None for jj
in range(numAmps)]
for ii
in range(numAmps)]
354 for ii, jj
in itertools.product(range(numAmps), range(numAmps)):
358 values = [rr[ii][jj]
for rr
in ratioList]
359 num = sum(len(vv)
for vv
in values)
361 self.
log.
warn(
"No values for matrix element %d,%d" % (ii, jj))
364 result = np.concatenate([vv
for vv
in values
if len(vv) > 0])
365 ratios[ii][jj] = result
369 self.
log.
info(
"Coefficients:\n%s\n", coeff)
370 self.
log.
info(
"Errors:\n%s\n", coeffErr)
371 self.
log.
info(
"Numbers:\n%s\n", coeffNum)
372 return coeff, coeffErr, coeffNum
375 """Measure crosstalk coefficients from the ratios.
377 Given a list of ratios for each target/source amp combination,
378 we measure a sigma clipped mean and error.
380 The coefficient errors returned are the standard deviation of
381 the final set of clipped input ratios.
385 ratios : `list` of `list` of `numpy.ndarray`
386 Matrix of arrays of ratios.
388 Number of rejection iterations.
390 Rejection threshold (sigma).
394 coeff : `numpy.ndarray`
395 Crosstalk coefficients.
396 coeffErr : `numpy.ndarray`
397 Crosstalk coefficient errors.
398 coeffNum : `numpy.ndarray`
399 Number of pixels for each measurement.
403 This has been moved into MeasureCrosstalkTask to allow for easier
406 The lsstDebug.Info() method can be rewritten for __name__ =
407 `lsst.ip.isr.measureCrosstalk`, and supports the parameters:
409 debug.display['measure'] : `bool`
410 Display a histogram of the combined ratio measurements for
411 a pair of source/target amplifiers from the final set of
412 clipped input ratios.
415 rejIter = self.
config.rejIter
417 rejSigma = self.
config.rejSigma
419 numAmps = len(ratios)
420 assert all(len(rr) == numAmps
for rr
in ratios)
422 coeff = np.zeros((numAmps, numAmps))
423 coeffErr = np.zeros((numAmps, numAmps))
424 coeffNum = np.zeros((numAmps, numAmps), dtype=int)
426 for ii, jj
in itertools.product(range(numAmps), range(numAmps)):
430 values = np.array(ratios[ii][jj])
431 values = values[np.abs(values) < 1.0]
433 coeffNum[ii][jj] = len(values)
436 self.
log.
warn(
"No values for matrix element %d,%d" % (ii, jj))
437 coeff[ii][jj] = np.nan
438 coeffErr[ii][jj] = np.nan
441 for rej
in range(rejIter):
442 lo, med, hi = np.percentile(values, [25.0, 50.0, 75.0])
443 sigma = 0.741*(hi - lo)
444 good = np.abs(values - med) < rejSigma*sigma
445 if good.sum() == len(good):
447 values = values[good]
449 coeff[ii][jj] = np.mean(values)
450 coeffErr[ii][jj] = np.nan
if coeffNum[ii][jj] == 1
else np.std(values)
453 return coeff, coeffErr, coeffNum
456 """Utility function to examine the image being processed.
461 State of processing to view.
462 exposure : `lsst.afw.image.Exposure`
468 display.scale(
'asinh',
'zscale')
469 display.mtv(exposure)
471 prompt =
"Press Enter to continue: "
473 ans = input(prompt).lower()
474 if ans
in (
"",
"c",):
478 """Utility function to examine the CT ratio pixel values.
483 State of processing to view.
484 pixelsIn : `np.ndarray`
485 Pixel values from the potential crosstalk "source".
486 pixelsOut : `np.ndarray`
487 Pixel values from the potential crosstalk "victim".
489 Index of the source amplifier.
491 Index of the target amplifier.
495 if i == j
or len(pixelsIn) == 0
or len(pixelsOut) < 1:
497 import matplotlib.pyplot
as plot
498 figure = plot.figure(1)
501 axes = figure.add_axes((0.1, 0.1, 0.8, 0.8))
502 axes.plot(pixelsIn, pixelsOut / pixelsIn,
'k+')
503 plot.xlabel(
"Source amplifier pixel value")
504 plot.ylabel(
"Measured pixel ratio")
505 plot.title(
"(Source %d -> Victim %d) median ratio: %f" %
506 (i, j, np.median(pixelsOut / pixelsIn)))
509 prompt =
"Press Enter to continue: "
511 ans = input(prompt).lower()
512 if ans
in (
"",
"c",):
517 """Utility function to examine the final CT ratio set.
522 State of processing to view.
523 ratios : `List` of `List` of `np.ndarray`
524 Array of measured CT ratios, indexed by source/victim
527 Index of the source amplifier.
529 Index of the target amplifier.
533 if i == j
or ratios
is None or len(ratios) < 1:
537 if RR
is None or len(RR) < 1:
542 import matplotlib.pyplot
as plot
543 figure = plot.figure(1)
545 plot.hist(x=RR, bins=
'auto', color=
'b', rwidth=0.9)
546 plot.xlabel(
"Measured pixel ratio")
547 plot.axvline(x=value, color=
"k")
548 plot.title(
"(Source %d -> Victim %d) clipped mean ratio: %f" % (i, j, value))
551 prompt =
"Press Enter to continue: "
553 ans = input(prompt).lower()
554 if ans
in (
"",
"c",):