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",):