22__all__ = [
"InitialPsfConfig",
"SnapCombineConfig",
"SnapCombineTask"]
30from lsstDebug
import getDebugFrame
36from lsst.utils.timer
import timeMethod
38from .repair
import RepairTask
42 """Describes the initial PSF used for detection and measurement before we do PSF determination."""
44 model = pexConfig.ChoiceField(
47 default=
"SingleGaussian",
49 "SingleGaussian":
"Single Gaussian model",
50 "DoubleGaussian":
"Double Gaussian model",
53 pixelScale = pexConfig.Field(
55 doc=
"Pixel size (arcsec). Only needed if no Wcs is provided",
58 fwhm = pexConfig.Field(
60 doc=
"FWHM of PSF model (arcsec)",
63 size = pexConfig.Field(
65 doc=
"Size of PSF model (pixels)",
71 doRepair = pexConfig.Field(
73 doc=
"Repair images (CR reject and interpolate) before combining",
76 repairPsfFwhm = pexConfig.Field(
78 doc=
"Psf FWHM (pixels) used to detect CRs",
81 doDiffIm = pexConfig.Field(
83 doc=
"Perform difference imaging before combining",
86 doPsfMatch = pexConfig.Field(
88 doc=
"Perform PSF matching for difference imaging (ignored if doDiffIm false)",
91 doMeasurement = pexConfig.Field(
93 doc=
"Measure difference sources (ignored if doDiffIm false)",
96 badMaskPlanes = pexConfig.ListField(
98 doc=
"Mask planes that, if set, the associated pixels are not included in the combined exposure; "
99 "DETECTED excludes cosmic rays",
100 default=(
"DETECTED",),
102 averageKeys = pexConfig.ListField(
104 doc=
"List of float metadata keys to average when combining snaps, e.g. float positions and dates; "
105 "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=
"RepairTask configuration")
118 detection = pexConfig.ConfigurableField(
119 target=SourceDetectionTask, doc=
"SourceDetectionTask configuration"
121 initialPsf = pexConfig.ConfigField(dtype=InitialPsfConfig, doc=
"InitialPsfConfig configuration")
122 measurement = pexConfig.ConfigurableField(
123 target=SingleFrameMeasurementTask, doc=
"SingleFrameMeasurementTask configuration"
127 self.
detection.thresholdPolarity =
"both"
130 if self.
detection.thresholdPolarity !=
"both":
131 raise ValueError(
"detection.thresholdPolarity must be 'both' for SnapCombineTask")
135 """Combine two snaps into a single visit image.
140 The `~lsst.base.lsstDebug` variables in SnapCombineTask are:
143 A dictionary containing debug point names
as keys
with frame number
as value. Valid keys are:
148 Display the first snap after repairing.
150 Display the second snap after repairing.
153 ConfigClass = SnapCombineConfig
154 _DefaultName = "snapCombine"
157 pipeBase.Task.__init__(self, *args, **kwargs)
158 self.makeSubtask(
"repair")
159 self.
schema = afwTable.SourceTable.makeMinimalSchema()
161 self.makeSubtask(
"detection", schema=self.
schema)
162 if self.config.doMeasurement:
163 self.makeSubtask(
"measurement", schema=self.
schema, algMetadata=self.
algMetadata)
166 def run(self, snap0, snap1, defects=None):
167 """Combine two snaps.
175 defects : `list` or `
None`, optional
176 Defect list (
for repair task).
180 result : `lsst.pipe.base.Struct`
181 Results
as a struct
with attributes:
184 Snap-combined exposure.
186 Detected sources,
or `
None`
if detection
not performed.
191 if self.config.doRepair:
192 self.log.info(
"snapCombine repair")
193 psf = self.
makeInitialPsf(snap0, fwhmPix=self.config.repairPsfFwhm)
196 self.repair.run(snap0, defects=defects, keepCRs=
False)
197 self.repair.run(snap1, defects=defects, keepCRs=
False)
199 repair0frame = getDebugFrame(self._display,
"repair0")
201 getDisplay(repair0frame).mtv(snap0)
202 repair1frame = getDebugFrame(self._display,
"repair1")
204 getDisplay(repair1frame).mtv(snap1)
206 if self.config.doDiffIm:
207 if self.config.doPsfMatch:
208 raise NotImplementedError(
"PSF-matching of snaps is not yet supported.")
211 diffExp = afwImage.ExposureF(snap0,
True)
212 diffMi = diffExp.getMaskedImage()
213 diffMi -= snap1.getMaskedImage()
217 table = afwTable.SourceTable.make(self.
schema)
219 detRet = self.detection.run(table, diffExp)
220 sources = detRet.sources
221 if self.config.doMeasurement:
222 self.measurement.measure(diffExp, sources)
224 mask0 = snap0.getMaskedImage().getMask()
225 mask1 = snap1.getMaskedImage().getMask()
226 detRet.positive.setMask(mask0,
"DETECTED")
227 detRet.negative.setMask(mask1,
"DETECTED")
229 maskD = diffExp.getMaskedImage().getMask()
230 detRet.positive.setMask(maskD,
"DETECTED")
231 detRet.negative.setMask(maskD,
"DETECTED_NEGATIVE")
233 combinedExp = self.
addSnaps(snap0, snap1)
235 return pipeBase.Struct(
236 exposure=combinedExp,
241 """Add two snap exposures together, returning a new exposure.
252 combinedExp : `Unknown`
255 self.log.info("snapCombine addSnaps")
257 combinedExp = snap0.Factory(snap0,
True)
258 combinedMi = combinedExp.getMaskedImage()
261 weightMap = combinedMi.getImage().Factory(combinedMi.getBBox())
263 badPixelMask = afwImage.Mask.getPlaneBitMask(self.config.badMaskPlanes)
264 addToCoadd(combinedMi, weightMap, snap0.getMaskedImage(), badPixelMask, weight)
265 addToCoadd(combinedMi, weightMap, snap1.getMaskedImage(), badPixelMask, weight)
270 combinedMi /= weightMap
271 setCoaddEdgeBits(combinedMi.getMask(), weightMap)
276 combinedMetadata = combinedExp.getMetadata()
277 metadata0 = snap0.getMetadata()
278 metadata1 = snap1.getMetadata()
279 self.
fixMetadata(combinedMetadata, metadata0, metadata1)
284 """Fix the metadata of the combined exposure (in place).
286 This implementation handles items specified by config.averageKeys and config.sumKeys,
287 which have data type restrictions. To handle other data types (such
as sexagesimal
288 positions
and ISO dates) you must supplement this method
with your own code.
293 Metadata of combined exposure;
294 on input this
is a deep copy of metadata0 (a PropertySet).
296 Metadata of snap0 (a PropertySet).
298 Metadata of snap1 (a PropertySet).
302 The inputs are presently PropertySets due to ticket
303 they are just PropertyLists that are missing some methods. In particular: comments
and order
304 are preserved
if you alter an existing value
with set(key, value).
307 if self.config.averageKeys:
308 keyDoAvgList += [(key, 1)
for key
in self.config.averageKeys]
309 if self.config.sumKeys:
310 keyDoAvgList += [(key, 0)
for key
in self.config.sumKeys]
311 for key, doAvg
in keyDoAvgList:
312 opStr =
"average" if doAvg
else "sum"
314 val0 = metadata0.getScalar(key)
315 val1 = metadata1.getScalar(key)
317 self.log.warning(
"Could not %s metadata %r: missing from one or both exposures", opStr, key)
321 combinedVal = val0 + val1
325 self.log.warning(
"Could not %s metadata %r: value %r and/or %r not numeric",
326 opStr, key, val0, val1)
329 combinedMetadata.set(key, combinedVal)
332 """Initialise the detection procedure by setting the PSF and WCS.
343 Raised if any of the following occur:
344 - No exposure provided.
345 - No wcs
in exposure.
347 assert exposure,
"No exposure provided"
348 wcs = exposure.getWcs()
349 assert wcs,
"No wcs in exposure"
352 fwhmPix = self.config.initialPsf.fwhm / wcs.getPixelScale().asArcseconds()
354 size = self.config.initialPsf.size
355 model = self.config.initialPsf.model
356 self.log.info(
"installInitialPsf fwhm=%s pixels; size=%s pixels", fwhmPix, size)
357 psfCls = getattr(measAlg, model +
"Psf")
358 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.
makeInitialPsf(self, exposure, fwhmPix=None)
__init__(self, *args, **kwargs)
fixMetadata(self, combinedMetadata, metadata0, metadata1)
addSnaps(self, snap0, snap1)
daf::base::PropertySet * set