22__all__ = [
"InitialPsfConfig",
"SnapCombineConfig",
"SnapCombineTask"]
30from lsstDebug
import getDebugFrame
37from lsst.utils.timer
import timeMethod
39from .repair
import RepairTask
43 """Describes the initial PSF used for detection and measurement before we do PSF determination."""
45 model = pexConfig.ChoiceField(
48 default=
"SingleGaussian",
50 "SingleGaussian":
"Single Gaussian model",
51 "DoubleGaussian":
"Double Gaussian model",
54 pixelScale = pexConfig.Field(
56 doc=
"Pixel size (arcsec). Only needed if no Wcs is provided",
59 fwhm = pexConfig.Field(
61 doc=
"FWHM of PSF model (arcsec)",
64 size = pexConfig.Field(
66 doc=
"Size of PSF model (pixels)",
72 doRepair = pexConfig.Field(
74 doc=
"Repair images (CR reject and interpolate) before combining",
77 repairPsfFwhm = pexConfig.Field(
79 doc=
"Psf FWHM (pixels) used to detect CRs",
82 doDiffIm = pexConfig.Field(
84 doc=
"Perform difference imaging before combining",
87 doPsfMatch = pexConfig.Field(
89 doc=
"Perform PSF matching for difference imaging (ignored if doDiffIm false)",
92 doMeasurement = pexConfig.Field(
94 doc=
"Measure difference sources (ignored if doDiffIm false)",
97 badMaskPlanes = pexConfig.ListField(
99 doc=
"Mask planes that, if set, the associated pixels are not included in the combined exposure; "
100 "DETECTED excludes cosmic rays",
101 default=(
"DETECTED",),
103 averageKeys = pexConfig.ListField(
105 doc=
"List of float metadata keys to average when combining snaps, e.g. float positions and dates; "
106 "non-float data must be handled by overriding the fixMetadata method",
110 sumKeys = pexConfig.ListField(
112 doc=
"List of float or int metadata keys to sum when combining snaps, e.g. exposure time; "
113 "non-float, non-int data must be handled by overriding the fixMetadata method",
117 repair = pexConfig.ConfigurableField(target=RepairTask, doc=
"")
118 diffim = pexConfig.ConfigurableField(target=SnapPsfMatchTask, doc=
"")
119 detection = pexConfig.ConfigurableField(target=SourceDetectionTask, doc=
"")
120 initialPsf = pexConfig.ConfigField(dtype=InitialPsfConfig, doc=
"")
121 measurement = pexConfig.ConfigurableField(target=SingleFrameMeasurementTask, doc=
"")
124 self.
detection.thresholdPolarity =
"both"
127 if self.
detection.thresholdPolarity !=
"both":
128 raise ValueError(
"detection.thresholdPolarity must be 'both' for SnapCombineTask")
132 """Combine two snaps into a single visit image.
137 The `~lsst.base.lsstDebug` variables in SnapCombineTask are:
140 A dictionary containing debug point names
as keys
with frame number
as value. Valid keys are:
145 Display the first snap after repairing.
147 Display the second snap after repairing.
150 ConfigClass = SnapCombineConfig
151 _DefaultName = "snapCombine"
154 pipeBase.Task.__init__(self, *args, **kwargs)
155 self.makeSubtask(
"repair")
156 self.makeSubtask(
"diffim")
157 self.
schema = afwTable.SourceTable.makeMinimalSchema()
159 self.makeSubtask(
"detection", schema=self.
schema)
160 if self.config.doMeasurement:
161 self.makeSubtask(
"measurement", schema=self.
schema, algMetadata=self.
algMetadata)
164 def run(self, snap0, snap1, defects=None):
165 """Combine two snaps.
173 defects : `list` or `
None`, optional
174 Defect list (
for repair task).
178 result : `lsst.pipe.base.Struct`
179 Results
as a struct
with attributes:
182 Snap-combined exposure.
184 Detected sources,
or `
None`
if detection
not performed.
189 if self.config.doRepair:
190 self.log.info(
"snapCombine repair")
191 psf = self.
makeInitialPsf(snap0, fwhmPix=self.config.repairPsfFwhm)
194 self.repair.run(snap0, defects=defects, keepCRs=
False)
195 self.repair.run(snap1, defects=defects, keepCRs=
False)
197 repair0frame = getDebugFrame(self._display,
"repair0")
199 getDisplay(repair0frame).mtv(snap0)
200 repair1frame = getDebugFrame(self._display,
"repair1")
202 getDisplay(repair1frame).mtv(snap1)
204 if self.config.doDiffIm:
205 if self.config.doPsfMatch:
206 self.log.info(
"snapCombine psfMatch")
207 diffRet = self.diffim.run(snap0, snap1,
"subtractExposures")
208 diffExp = diffRet.subtractedImage
212 diffKern = diffRet.psfMatchingKernel
213 width, height = diffKern.getDimensions()
216 diffExp = afwImage.ExposureF(snap0,
True)
217 diffMi = diffExp.getMaskedImage()
218 diffMi -= snap1.getMaskedImage()
222 table = afwTable.SourceTable.make(self.
schema)
224 detRet = self.detection.run(table, diffExp)
225 sources = detRet.sources
226 fpSets = detRet.fpSets
227 if self.config.doMeasurement:
228 self.measurement.measure(diffExp, sources)
230 mask0 = snap0.getMaskedImage().getMask()
231 mask1 = snap1.getMaskedImage().getMask()
232 fpSets.positive.setMask(mask0,
"DETECTED")
233 fpSets.negative.setMask(mask1,
"DETECTED")
235 maskD = diffExp.getMaskedImage().getMask()
236 fpSets.positive.setMask(maskD,
"DETECTED")
237 fpSets.negative.setMask(maskD,
"DETECTED_NEGATIVE")
239 combinedExp = self.
addSnaps(snap0, snap1)
241 return pipeBase.Struct(
242 exposure=combinedExp,
247 """Add two snap exposures together, returning a new exposure.
258 combinedExp : `Unknown`
261 self.log.info("snapCombine addSnaps")
263 combinedExp = snap0.Factory(snap0,
True)
264 combinedMi = combinedExp.getMaskedImage()
267 weightMap = combinedMi.getImage().Factory(combinedMi.getBBox())
269 badPixelMask = afwImage.Mask.getPlaneBitMask(self.config.badMaskPlanes)
270 addToCoadd(combinedMi, weightMap, snap0.getMaskedImage(), badPixelMask, weight)
271 addToCoadd(combinedMi, weightMap, snap1.getMaskedImage(), badPixelMask, weight)
276 combinedMi /= weightMap
282 combinedMetadata = combinedExp.getMetadata()
283 metadata0 = snap0.getMetadata()
284 metadata1 = snap1.getMetadata()
285 self.
fixMetadata(combinedMetadata, metadata0, metadata1)
290 """Fix the metadata of the combined exposure (in place).
292 This implementation handles items specified by config.averageKeys and config.sumKeys,
293 which have data type restrictions. To handle other data types (such
as sexagesimal
294 positions
and ISO dates) you must supplement this method
with your own code.
299 Metadata of combined exposure;
300 on input this
is a deep copy of metadata0 (a PropertySet).
302 Metadata of snap0 (a PropertySet).
304 Metadata of snap1 (a PropertySet).
308 The inputs are presently PropertySets due to ticket
309 they are just PropertyLists that are missing some methods. In particular: comments
and order
310 are preserved
if you alter an existing value
with set(key, value).
313 if self.config.averageKeys:
314 keyDoAvgList += [(key, 1)
for key
in self.config.averageKeys]
315 if self.config.sumKeys:
316 keyDoAvgList += [(key, 0)
for key
in self.config.sumKeys]
317 for key, doAvg
in keyDoAvgList:
318 opStr =
"average" if doAvg
else "sum"
320 val0 = metadata0.getScalar(key)
321 val1 = metadata1.getScalar(key)
323 self.log.warning(
"Could not %s metadata %r: missing from one or both exposures", opStr, key)
327 combinedVal = val0 + val1
331 self.log.warning(
"Could not %s metadata %r: value %r and/or %r not numeric",
332 opStr, key, val0, val1)
335 combinedMetadata.set(key, combinedVal)
338 """Initialise the detection procedure by setting the PSF and WCS.
349 Raised if any of the following occur:
350 - No exposure provided.
351 - No wcs
in exposure.
353 assert exposure,
"No exposure provided"
354 wcs = exposure.getWcs()
355 assert wcs,
"No wcs in exposure"
358 fwhmPix = self.config.initialPsf.fwhm / wcs.getPixelScale().asArcseconds()
360 size = self.config.initialPsf.size
361 model = self.config.initialPsf.model
362 self.log.info(
"installInitialPsf fwhm=%s pixels; size=%s pixels", fwhmPix, size)
363 psfCls = getattr(measAlg, model +
"Psf")
364 psf = psfCls(size, size, fwhmPix/(2.0*num.sqrt(2*num.log(2.0))))
A class to contain the data, WCS, and other information needed to describe an image of the sky.
Class for storing ordered metadata with comments.
Class for storing generic metadata.
def fixMetadata(self, combinedMetadata, metadata0, metadata1)
def makeInitialPsf(self, exposure, fwhmPix=None)
def __init__(self, *args, **kwargs)
def addSnaps(self, snap0, snap1)
daf::base::PropertySet * set