23 import lsst.pex.config 
as pexConfig
 
   28 from lsstDebug 
import getDebugFrame
 
   36 from .repair 
import RepairTask
 
   40     """!Describes the initial PSF used for detection and measurement before we do PSF determination.""" 
   42     model = pexConfig.ChoiceField(
 
   45         default=
"SingleGaussian",
 
   47             "SingleGaussian": 
"Single Gaussian model",
 
   48             "DoubleGaussian": 
"Double Gaussian model",
 
   51     pixelScale = pexConfig.Field(
 
   53         doc=
"Pixel size (arcsec).  Only needed if no Wcs is provided",
 
   56     fwhm = pexConfig.Field(
 
   58         doc=
"FWHM of PSF model (arcsec)",
 
   61     size = pexConfig.Field(
 
   63         doc=
"Size of PSF model (pixels)",
 
   69     doRepair = pexConfig.Field(
 
   71         doc=
"Repair images (CR reject and interpolate) before combining",
 
   74     repairPsfFwhm = pexConfig.Field(
 
   76         doc=
"Psf FWHM (pixels) used to detect CRs",
 
   79     doDiffIm = pexConfig.Field(
 
   81         doc=
"Perform difference imaging before combining",
 
   84     doPsfMatch = pexConfig.Field(
 
   86         doc=
"Perform PSF matching for difference imaging (ignored if doDiffIm false)",
 
   89     doMeasurement = pexConfig.Field(
 
   91         doc=
"Measure difference sources (ignored if doDiffIm false)",
 
   94     badMaskPlanes = pexConfig.ListField(
 
   96         doc=
"Mask planes that, if set, the associated pixels are not included in the combined exposure; " 
   97         "DETECTED excludes cosmic rays",
 
   98         default=(
"DETECTED",),
 
  100     averageKeys = pexConfig.ListField(
 
  102         doc=
"List of float metadata keys to average when combining snaps, e.g. float positions and dates; " 
  103         "non-float data must be handled by overriding the fixMetadata method",
 
  107     sumKeys = pexConfig.ListField(
 
  109         doc=
"List of float or int metadata keys to sum when combining snaps, e.g. exposure time; " 
  110         "non-float, non-int data must be handled by overriding the fixMetadata method",
 
  114     repair = pexConfig.ConfigurableField(target=RepairTask, doc=
"")
 
  115     diffim = pexConfig.ConfigurableField(target=SnapPsfMatchTask, doc=
"")
 
  116     detection = pexConfig.ConfigurableField(target=SourceDetectionTask, doc=
"")
 
  117     initialPsf = pexConfig.ConfigField(dtype=InitialPsfConfig, doc=
"")
 
  118     measurement = pexConfig.ConfigurableField(target=SingleFrameMeasurementTask, doc=
"")
 
  121         self.
detection.thresholdPolarity = 
"both" 
  124         if self.
detection.thresholdPolarity != 
"both":
 
  125             raise ValueError(
"detection.thresholdPolarity must be 'both' for SnapCombineTask")
 
  137     \anchor SnapCombineTask_ 
  139     \brief Combine snaps. 
  141     \section pipe_tasks_snapcombine_Contents Contents 
  143      - \ref pipe_tasks_snapcombine_Debug 
  145     \section pipe_tasks_snapcombine_Debug Debug variables 
  147     The \link lsst.pipe.base.cmdLineTask.CmdLineTask command line task\endlink interface supports a 
  148     flag \c -d to import \b debug.py from your \c PYTHONPATH; see <a 
  149     href="http://lsst-web.ncsa.illinois.edu/~buildbot/doxygen/x_masterDoxyDoc/base_debug.html"> 
  150     Using lsstDebug to control debugging output</a> for more about \b debug.py files. 
  152     The available variables in SnapCombineTask are: 
  155       <DD> A dictionary containing debug point names as keys with frame number as value. Valid keys are: 
  158           <DD> Display the first snap after repairing. 
  160           <DD> Display the second snap after repairing. 
  165     ConfigClass = SnapCombineConfig
 
  166     _DefaultName = 
"snapCombine" 
  169         pipeBase.Task.__init__(self, *args, **kwargs)
 
  170         self.makeSubtask(
"repair")
 
  171         self.makeSubtask(
"diffim")
 
  172         self.
schema = afwTable.SourceTable.makeMinimalSchema()
 
  174         self.makeSubtask(
"detection", schema=self.
schema)
 
  175         if self.config.doMeasurement:
 
  176             self.makeSubtask(
"measurement", schema=self.
schema, algMetadata=self.
algMetadata)
 
  179     def run(self, snap0, snap1, defects=None):
 
  182         @param[in] snap0: snapshot exposure 0 
  183         @param[in] snap1: snapshot exposure 1 
  184         @defects[in] defect list (for repair task) 
  185         @return a pipe_base Struct with fields: 
  186         - exposure: snap-combined exposure 
  187         - sources: detected sources, or None if detection not performed 
  192         if self.config.doRepair:
 
  193             self.log.
info(
"snapCombine repair")
 
  194             psf = self.
makeInitialPsf(snap0, fwhmPix=self.config.repairPsfFwhm)
 
  197             self.repair.
run(snap0, defects=defects, keepCRs=
False)
 
  198             self.repair.
run(snap1, defects=defects, keepCRs=
False)
 
  207         if self.config.doDiffIm:
 
  208             if self.config.doPsfMatch:
 
  209                 self.log.
info(
"snapCombine psfMatch")
 
  210                 diffRet = self.diffim.
run(snap0, snap1, 
"subtractExposures")
 
  211                 diffExp = diffRet.subtractedImage
 
  215                 diffKern = diffRet.psfMatchingKernel
 
  216                 width, height = diffKern.getDimensions()
 
  219                 diffExp = afwImage.ExposureF(snap0, 
True)
 
  220                 diffMi = diffExp.getMaskedImage()
 
  221                 diffMi -= snap1.getMaskedImage()
 
  225             table = afwTable.SourceTable.make(self.
schema)
 
  227             detRet = self.detection.
run(table, diffExp)
 
  228             sources = detRet.sources
 
  229             fpSets = detRet.fpSets
 
  230             if self.config.doMeasurement:
 
  231                 self.measurement.
measure(diffExp, sources)
 
  233             mask0 = snap0.getMaskedImage().getMask()
 
  234             mask1 = snap1.getMaskedImage().getMask()
 
  235             fpSets.positive.setMask(mask0, 
"DETECTED")
 
  236             fpSets.negative.setMask(mask1, 
"DETECTED")
 
  238             maskD = diffExp.getMaskedImage().getMask()
 
  239             fpSets.positive.setMask(maskD, 
"DETECTED")
 
  240             fpSets.negative.setMask(maskD, 
"DETECTED_NEGATIVE")
 
  242         combinedExp = self.
addSnaps(snap0, snap1)
 
  244         return pipeBase.Struct(
 
  245             exposure=combinedExp,
 
  250         """Add two snap exposures together, returning a new exposure 
  252         @param[in] snap0 snap exposure 0 
  253         @param[in] snap1 snap exposure 1 
  254         @return combined exposure 
  256         self.log.
info(
"snapCombine addSnaps")
 
  258         combinedExp = snap0.Factory(snap0, 
True)
 
  259         combinedMi = combinedExp.getMaskedImage()
 
  262         weightMap = combinedMi.getImage().
Factory(combinedMi.getBBox())
 
  264         badPixelMask = afwImage.Mask.getPlaneBitMask(self.config.badMaskPlanes)
 
  265         addToCoadd(combinedMi, weightMap, snap0.getMaskedImage(), badPixelMask, weight)
 
  266         addToCoadd(combinedMi, weightMap, snap1.getMaskedImage(), badPixelMask, weight)
 
  271         combinedMi /= weightMap
 
  277         combinedMetadata = combinedExp.getMetadata()
 
  278         metadata0 = snap0.getMetadata()
 
  279         metadata1 = snap1.getMetadata()
 
  280         self.
fixMetadata(combinedMetadata, metadata0, metadata1)
 
  285         """Fix the metadata of the combined exposure (in place) 
  287         This implementation handles items specified by config.averageKeys and config.sumKeys, 
  288         which have data type restrictions. To handle other data types (such as sexagesimal 
  289         positions and ISO dates) you must supplement this method with your own code. 
  291         @param[in,out] combinedMetadata metadata of combined exposure; 
  292             on input this is a deep copy of metadata0 (a PropertySet) 
  293         @param[in] metadata0 metadata of snap0 (a PropertySet) 
  294         @param[in] metadata1 metadata of snap1 (a PropertySet) 
  296         @note the inputs are presently PropertySets due to ticket #2542. However, in some sense 
  297         they are just PropertyLists that are missing some methods. In particular: comments and order 
  298         are preserved if you alter an existing value with set(key, value). 
  301         if self.config.averageKeys:
 
  302             keyDoAvgList += [(key, 1) 
for key 
in self.config.averageKeys]
 
  303         if self.config.sumKeys:
 
  304             keyDoAvgList += [(key, 0) 
for key 
in self.config.sumKeys]
 
  305         for key, doAvg 
in keyDoAvgList:
 
  306             opStr = 
"average" if doAvg 
else "sum" 
  308                 val0 = metadata0.getScalar(key)
 
  309                 val1 = metadata1.getScalar(key)
 
  311                 self.log.
warn(
"Could not %s metadata %r: missing from one or both exposures" % (opStr, key,))
 
  315                 combinedVal = val0 + val1
 
  319                 self.log.
warn(
"Could not %s metadata %r: value %r and/or %r not numeric" %
 
  320                               (opStr, key, val0, val1))
 
  323             combinedMetadata.set(key, combinedVal)
 
  326         """Initialise the detection procedure by setting the PSF and WCS 
  328         @param exposure Exposure to process 
  331         assert exposure, 
"No exposure provided" 
  332         wcs = exposure.getWcs()
 
  333         assert wcs, 
"No wcs in exposure" 
  336             fwhmPix = self.config.initialPsf.fwhm / wcs.getPixelScale().asArcseconds()
 
  338         size = self.config.initialPsf.size
 
  339         model = self.config.initialPsf.model
 
  340         self.log.
info(
"installInitialPsf fwhm=%s pixels; size=%s pixels" % (fwhmPix, size))
 
  341         psfCls = getattr(measAlg, model + 
"Psf")
 
  342         psf = psfCls(size, size, fwhmPix/(2.0*num.sqrt(2*num.log(2.0))))