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",
109 sumKeys = pexConfig.ListField(
111 doc=
"List of float or int metadata keys to sum when combining snaps, e.g. exposure time; "
112 "non-float, non-int data must be handled by overriding the fixMetadata method",
116 repair = pexConfig.ConfigurableField(target=RepairTask, 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.
schema = afwTable.SourceTable.makeMinimalSchema()
158 self.makeSubtask(
"detection", schema=self.
schema)
159 if self.config.doMeasurement:
160 self.makeSubtask(
"measurement", schema=self.
schema, algMetadata=self.
algMetadata)
163 def run(self, snap0, snap1, defects=None):
164 """Combine two snaps.
172 defects : `list` or `
None`, optional
173 Defect list (
for repair task).
177 result : `lsst.pipe.base.Struct`
178 Results
as a struct
with attributes:
181 Snap-combined exposure.
183 Detected sources,
or `
None`
if detection
not performed.
188 if self.config.doRepair:
189 self.log.info(
"snapCombine repair")
190 psf = self.
makeInitialPsf(snap0, fwhmPix=self.config.repairPsfFwhm)
193 self.repair.run(snap0, defects=defects, keepCRs=
False)
194 self.repair.run(snap1, defects=defects, keepCRs=
False)
196 repair0frame = getDebugFrame(self._display,
"repair0")
198 getDisplay(repair0frame).mtv(snap0)
199 repair1frame = getDebugFrame(self._display,
"repair1")
201 getDisplay(repair1frame).mtv(snap1)
203 if self.config.doDiffIm:
204 if self.config.doPsfMatch:
205 raise NotImplementedError(
"PSF-matching of snaps is not yet supported.")
208 diffExp = afwImage.ExposureF(snap0,
True)
209 diffMi = diffExp.getMaskedImage()
210 diffMi -= snap1.getMaskedImage()
214 table = afwTable.SourceTable.make(self.
schema)
216 detRet = self.detection.run(table, diffExp)
217 sources = detRet.sources
218 if self.config.doMeasurement:
219 self.measurement.measure(diffExp, sources)
221 mask0 = snap0.getMaskedImage().getMask()
222 mask1 = snap1.getMaskedImage().getMask()
223 detRet.positive.setMask(mask0,
"DETECTED")
224 detRet.negative.setMask(mask1,
"DETECTED")
226 maskD = diffExp.getMaskedImage().getMask()
227 detRet.positive.setMask(maskD,
"DETECTED")
228 detRet.negative.setMask(maskD,
"DETECTED_NEGATIVE")
230 combinedExp = self.
addSnaps(snap0, snap1)
232 return pipeBase.Struct(
233 exposure=combinedExp,
238 """Add two snap exposures together, returning a new exposure.
249 combinedExp : `Unknown`
252 self.log.info("snapCombine addSnaps")
254 combinedExp = snap0.Factory(snap0,
True)
255 combinedMi = combinedExp.getMaskedImage()
258 weightMap = combinedMi.getImage().Factory(combinedMi.getBBox())
260 badPixelMask = afwImage.Mask.getPlaneBitMask(self.config.badMaskPlanes)
261 addToCoadd(combinedMi, weightMap, snap0.getMaskedImage(), badPixelMask, weight)
262 addToCoadd(combinedMi, weightMap, snap1.getMaskedImage(), badPixelMask, weight)
267 combinedMi /= weightMap
268 setCoaddEdgeBits(combinedMi.getMask(), weightMap)
273 combinedMetadata = combinedExp.getMetadata()
274 metadata0 = snap0.getMetadata()
275 metadata1 = snap1.getMetadata()
276 self.
fixMetadata(combinedMetadata, metadata0, metadata1)
281 """Fix the metadata of the combined exposure (in place).
283 This implementation handles items specified by config.averageKeys and config.sumKeys,
284 which have data type restrictions. To handle other data types (such
as sexagesimal
285 positions
and ISO dates) you must supplement this method
with your own code.
290 Metadata of combined exposure;
291 on input this
is a deep copy of metadata0 (a PropertySet).
293 Metadata of snap0 (a PropertySet).
295 Metadata of snap1 (a PropertySet).
299 The inputs are presently PropertySets due to ticket
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.getScalar(key)
312 val1 = metadata1.getScalar(key)
314 self.log.warning(
"Could not %s metadata %r: missing from one or both exposures", opStr, key)
318 combinedVal = val0 + val1
322 self.log.warning(
"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.
340 Raised if any of the following occur:
341 - No exposure provided.
342 - No wcs
in exposure.
344 assert exposure,
"No exposure provided"
345 wcs = exposure.getWcs()
346 assert wcs,
"No wcs in exposure"
349 fwhmPix = self.config.initialPsf.fwhm / wcs.getPixelScale().asArcseconds()
351 size = self.config.initialPsf.size
352 model = self.config.initialPsf.model
353 self.log.info(
"installInitialPsf fwhm=%s pixels; size=%s pixels", fwhmPix, size)
354 psfCls = getattr(measAlg, model +
"Psf")
355 psf = psfCls(size, size, fwhmPix/(2.0*num.sqrt(2*num.log(2.0))))
def fixMetadata(self, combinedMetadata, metadata0, metadata1)
def makeInitialPsf(self, exposure, fwhmPix=None)
def __init__(self, *args, **kwargs)
def addSnaps(self, snap0, snap1)