22 from __future__
import absolute_import, division, print_function
24 import lsst.pex.config
as pexConfig
25 import lsst.daf.base
as dafBase
26 import lsst.afw.image
as afwImage
27 import lsst.afw.table
as afwTable
28 import lsst.pipe.base
as pipeBase
29 from lsstDebug
import getDebugFrame
30 from lsst.afw.display
import getDisplay
31 from lsst.coadd.utils
import addToCoadd, setCoaddEdgeBits
32 from lsst.ip.diffim
import SnapPsfMatchTask
33 from lsst.meas.algorithms
import SourceDetectionTask
34 from lsst.meas.base
import SingleFrameMeasurementTask
35 import lsst.meas.algorithms
as measAlg
37 from .repair
import RepairTask
41 """!Describes the initial PSF used for detection and measurement before we do PSF determination."""
43 model = pexConfig.ChoiceField(
46 default=
"SingleGaussian",
48 "SingleGaussian":
"Single Gaussian model",
49 "DoubleGaussian":
"Double Gaussian model",
52 pixelScale = pexConfig.Field(
54 doc=
"Pixel size (arcsec). Only needed if no Wcs is provided",
57 fwhm = pexConfig.Field(
59 doc=
"FWHM of PSF model (arcsec)",
62 size = pexConfig.Field(
64 doc=
"Size of PSF model (pixels)",
70 doRepair = pexConfig.Field(
72 doc=
"Repair images (CR reject and interpolate) before combining",
75 repairPsfFwhm = pexConfig.Field(
77 doc=
"Psf FWHM (pixels) used to detect CRs",
80 doDiffIm = pexConfig.Field(
82 doc=
"Perform difference imaging before combining",
85 doPsfMatch = pexConfig.Field(
87 doc=
"Perform PSF matching for difference imaging (ignored if doDiffIm false)",
90 doMeasurement = pexConfig.Field(
92 doc=
"Measure difference sources (ignored if doDiffIm false)",
95 badMaskPlanes = pexConfig.ListField(
97 doc=
"Mask planes that, if set, the associated pixels are not included in the combined exposure; "
98 "DETECTED excludes cosmic rays",
99 default=(
"DETECTED",),
101 averageKeys = pexConfig.ListField(
103 doc=
"List of float metadata keys to average when combining snaps, e.g. float positions and dates; "
104 "non-float data must be handled by overriding the fixMetadata method",
108 sumKeys = pexConfig.ListField(
110 doc=
"List of float or int metadata keys to sum when combining snaps, e.g. exposure time; "
111 "non-float, non-int data must be handled by overriding the fixMetadata method",
115 repair = pexConfig.ConfigurableField(target=RepairTask, doc=
"")
116 diffim = pexConfig.ConfigurableField(target=SnapPsfMatchTask, doc=
"")
117 detection = pexConfig.ConfigurableField(target=SourceDetectionTask, doc=
"")
118 initialPsf = pexConfig.ConfigField(dtype=InitialPsfConfig, doc=
"")
119 measurement = pexConfig.ConfigurableField(target=SingleFrameMeasurementTask, doc=
"")
122 self.detection.thresholdPolarity =
"both"
125 if self.detection.thresholdPolarity !=
"both":
126 raise ValueError(
"detection.thresholdPolarity must be 'both' for SnapCombineTask")
138 \anchor SnapCombineTask_
140 \brief Combine snaps.
142 \section pipe_tasks_snapcombine_Contents Contents
144 - \ref pipe_tasks_snapcombine_Debug
146 \section pipe_tasks_snapcombine_Debug Debug variables
148 The \link lsst.pipe.base.cmdLineTask.CmdLineTask command line task\endlink interface supports a
149 flag \c -d to import \b debug.py from your \c PYTHONPATH; see <a
150 href="http://lsst-web.ncsa.illinois.edu/~buildbot/doxygen/x_masterDoxyDoc/base_debug.html">
151 Using lsstDebug to control debugging output</a> for more about \b debug.py files.
153 The available variables in SnapCombineTask are:
156 <DD> A dictionary containing debug point names as keys with frame number as value. Valid keys are:
159 <DD> Display the first snap after repairing.
161 <DD> Display the second snap after repairing.
166 ConfigClass = SnapCombineConfig
167 _DefaultName =
"snapCombine"
170 pipeBase.Task.__init__(self, *args, **kwargs)
171 self.makeSubtask(
"repair")
172 self.makeSubtask(
"diffim")
173 self.
schema = afwTable.SourceTable.makeMinimalSchema()
175 self.makeSubtask(
"detection", schema=self.
schema)
176 if self.config.doMeasurement:
177 self.makeSubtask(
"measurement", schema=self.
schema, algMetadata=self.
algMetadata)
180 def run(self, snap0, snap1, defects=None):
183 @param[in] snap0: snapshot exposure 0
184 @param[in] snap1: snapshot exposure 1
185 @defects[in] defect list (for repair task)
186 @return a pipe_base Struct with fields:
187 - exposure: snap-combined exposure
188 - sources: detected sources, or None if detection not performed
193 if self.config.doRepair:
194 self.log.info(
"snapCombine repair")
195 psf = self.
makeInitialPsf(snap0, fwhmPix=self.config.repairPsfFwhm)
198 self.repair.run(snap0, defects=defects, keepCRs=
False)
199 self.repair.run(snap1, defects=defects, keepCRs=
False)
201 repair0frame = getDebugFrame(self._display,
"repair0")
203 getDisplay(repair0frame).mtv(snap0)
204 repair1frame = getDebugFrame(self._display,
"repair1")
206 getDisplay(repair1frame).mtv(snap1)
208 if self.config.doDiffIm:
209 if self.config.doPsfMatch:
210 self.log.info(
"snapCombine psfMatch")
211 diffRet = self.diffim.run(snap0, snap1,
"subtractExposures")
212 diffExp = diffRet.subtractedImage
216 diffKern = diffRet.psfMatchingKernel
217 width, height = diffKern.getDimensions()
222 diffExp = afwImage.ExposureF(snap0,
True)
223 diffMi = diffExp.getMaskedImage()
224 diffMi -= snap1.getMaskedImage()
228 table = afwTable.SourceTable.make(self.
schema)
230 detRet = self.detection.makeSourceCatalog(table, diffExp)
231 sources = detRet.sources
232 fpSets = detRet.fpSets
233 if self.config.doMeasurement:
234 self.measurement.measure(diffExp, sources)
236 mask0 = snap0.getMaskedImage().getMask()
237 mask1 = snap1.getMaskedImage().getMask()
238 fpSets.positive.setMask(mask0,
"DETECTED")
239 fpSets.negative.setMask(mask1,
"DETECTED")
241 maskD = diffExp.getMaskedImage().getMask()
242 fpSets.positive.setMask(maskD,
"DETECTED")
243 fpSets.negative.setMask(maskD,
"DETECTED_NEGATIVE")
245 combinedExp = self.
addSnaps(snap0, snap1)
247 return pipeBase.Struct(
248 exposure=combinedExp,
253 """Add two snap exposures together, returning a new exposure
255 @param[in] snap0 snap exposure 0
256 @param[in] snap1 snap exposure 1
257 @return combined exposure
259 self.log.info(
"snapCombine addSnaps")
261 combinedExp = snap0.Factory(snap0,
True)
262 combinedMi = combinedExp.getMaskedImage()
265 weightMap = combinedMi.getImage().Factory(combinedMi.getBBox())
267 badPixelMask = afwImage.MaskU.getPlaneBitMask(self.config.badMaskPlanes)
268 addToCoadd(combinedMi, weightMap, snap0.getMaskedImage(), badPixelMask, weight)
269 addToCoadd(combinedMi, weightMap, snap1.getMaskedImage(), badPixelMask, weight)
274 combinedMi /= weightMap
275 setCoaddEdgeBits(combinedMi.getMask(), weightMap)
280 combinedMetadata = combinedExp.getMetadata()
281 metadata0 = snap0.getMetadata()
282 metadata1 = snap1.getMetadata()
283 self.
fixMetadata(combinedMetadata, metadata0, metadata1)
288 """Fix the metadata of the combined exposure (in place)
290 This implementation handles items specified by config.averageKeys and config.sumKeys,
291 which have data type restrictions. To handle other data types (such as sexagesimal
292 positions and ISO dates) you must supplement this method with your own code.
294 @param[in,out] combinedMetadata metadata of combined exposure;
295 on input this is a deep copy of metadata0 (a PropertySet)
296 @param[in] metadata0 metadata of snap0 (a PropertySet)
297 @param[in] metadata1 metadata of snap1 (a PropertySet)
299 @note the inputs are presently PropertySets due to ticket #2542. However, in some sense
300 they are just PropertyLists that are missing some methods. In particular: comments and order
301 are preserved if you alter an existing value with set(key, value).
304 if self.config.averageKeys:
305 keyDoAvgList += [(key, 1)
for key
in self.config.averageKeys]
306 if self.config.sumKeys:
307 keyDoAvgList += [(key, 0)
for key
in self.config.sumKeys]
308 for key, doAvg
in keyDoAvgList:
309 opStr =
"average" if doAvg
else "sum"
311 val0 = metadata0.get(key)
312 val1 = metadata1.get(key)
314 self.log.warn(
"Could not %s metadata %r: missing from one or both exposures" % (opStr, key,))
318 combinedVal = val0 + val1
322 self.log.warn(
"Could not %s metadata %r: value %r and/or %r not numeric" %
323 (opStr, key, val0, val1))
326 combinedMetadata.set(key, combinedVal)
329 """Initialise the detection procedure by setting the PSF and WCS
331 @param exposure Exposure to process
334 assert exposure,
"No exposure provided"
335 wcs = exposure.getWcs()
336 assert wcs,
"No wcs in exposure"
339 fwhmPix = self.config.initialPsf.fwhm / wcs.pixelScale().asArcseconds()
341 size = self.config.initialPsf.size
342 model = self.config.initialPsf.model
343 self.log.info(
"installInitialPsf fwhm=%s pixels; size=%s pixels" % (fwhmPix, size))
344 psfCls = getattr(measAlg, model +
"Psf")
345 psf = psfCls(size, size, fwhmPix/(2.0*num.sqrt(2*num.log(2.0))))
Describes the initial PSF used for detection and measurement before we do PSF determination.