29 __all__ = (
"NoiseReplacerConfig",
"NoiseReplacer",
"DummyNoiseReplacer")
33 """Noise replacement configuration."""
36 doc=
'How to choose mean and variance of the Gaussian noise we generate?',
39 'measure':
'Measure clipped mean and variance from the whole image',
40 'meta':
'Mean = 0, variance = the "BGMEAN" metadata entry',
41 'variance':
"Mean = 0, variance = the image's variance",
43 default=
'measure', optional=
False
46 doc=
'Add ann offset to the generated noise.',
47 dtype=float, optional=
False, default=0.0
51 doc=
"The seed multiplier value to use for random number generation:\n"
52 ">= 1: set the seed deterministically based on exposureId\n"
53 "0: fall back to the afw.math.Random default constructor (which uses a seed value of 1)"
58 r"""Replace sources with noise during measurement.
62 config : `NoiseReplacerConfig`
64 exposure : `lsst.afw.image.Exposure`
65 Image in which sources will be replaced by noise. During operation,
66 the image will be modified in-place to replace all sources. At the end
67 of the measurment procedure, the original sources will be replaced.
69 Mapping of ``id`` to a tuple of ``(parent, Footprint)``. When used in
70 single-frame measurement, ``id`` is the source ID, but in forced
71 photometry this is the reference ID (as that is used to determine
73 noiseImage : `lsst.afw.image.ImageF`
74 An image used as a predictable noise replacement source. Used during
76 log : `lsst.log.log.log.Log`, optional
77 Logger to use for status messages; no status messages will be recorded
82 When measuring a source (or the children associated with a parent source),
83 this class is used to replace its neighbors with noise, using the
84 deblender's definition of the sources as stored in
85 `~lsst.afw.detection.heavyFootprint.HeavyFootprint`\ s attached to the
86 `~lsst.afw.table.SourceRecord`\ s. The algorithm works as follows:
88 #. All pixels in the source `~lsst.afw.detection.Footprint`\ s are replaced
89 with artificially generated noise (in `NoiseReplacer.__init__`).
90 #. Before each source is measured, we restore the original pixel data by
91 inserting that source's
92 `~lsst.afw.detection.heavyFootprint.HeavyFootprint` (from the deblender)
94 #. After measurement, we again replace the source pixels with (the same)
96 #. After measuring all sources, the image is returned to its original
99 This is a functional copy of the code in the older
100 ``ReplaceWithNoiseTask``, but with a slightly different API needed for the
101 new measurement framework; note that it is not an `~lsst.pipe.base.Task`,
102 as the lifetime of a ``NoiseReplacer`` now corresponds to a single
103 exposure, not an entire processing run.
105 When processing the ``footprints`` parameter, this routine should create
106 `~lsst.afw.detection.heavyFootprint.HeavyFootprint`\ s for any non-Heavy
107 `~lsst.afw.detection.Footprint`\ s, and replace them in the dictionary. It
108 should then create a dict of
109 `~lsst.afw.detection.heavyFootprint.HeavyFootprint`\ s containing noise,
110 but only for parent objects, then replace all sources with noise. This
111 should ignore any footprints that lay outside the bounding box of the
112 exposure, and clip those that lie on the border.
114 As the code currently stands, the heavy footprint for a deblended object
115 must be available from the input catalog. If it is not, it cannot be
116 reproduced here. In that case, the topmost parent in the objects parent
117 chain must be used. The heavy footprint for that source is created in
118 this class from the masked image.
121 ConfigClass = NoiseReplacerConfig
124 """Image on which the NoiseReplacer is operating (`lsst.afw.image.Exposure`).
128 """Mapping of ``id`` to a tuple of ``(parent, Footprint)`` (`dict`).
132 """Logger used for status messages.
135 def __init__(self, config, exposure, footprints, noiseImage=None, exposureId=None, log=None):
148 mi = exposure.getMaskedImage()
154 for maskname
in [
'THISDET',
'OTHERDET']:
157 plane = mask.getMaskPlane(maskname)
159 self.
log.
debug(
'Mask plane "%s" already existed', maskname)
162 plane = mask.addMaskPlane(maskname)
164 mask.clearMaskPlane(plane)
165 bitmask = mask.getPlaneBitMask(maskname)
166 bitmasks.append(bitmask)
168 self.
log.
debug(
'Mask plane "%s": plane %i, bitmask %i = 0x%x',
169 maskname, plane, bitmask, bitmask)
182 for id, fp
in footprints.items():
197 noisegen = self.
getNoiseGenerator(exposure, noiseImage, noiseMeanVar, exposureId=exposureId)
202 self.
log.
debug(
'Using noise generator: %s', str(noisegen))
204 fp = footprints[id][1]
205 noiseFp = noisegen.getHeavyFootprint(fp)
215 """Insert the heavy footprint of a given source into the exposure.
220 ID of the source to insert from original dictionary of footprints.
224 Also adjusts the mask plane to show the source of this footprint.
238 fp.spans.setMask(mask, self.thisbitmask)
242 """Replace the heavy footprint of a given source with noise.
244 The same artificial noise is used as in the original replacement.
249 ID of the source to replace from original dictionary of footprints.
253 Also restores the mask plane.
271 fp.spans.clearMask(mask, self.thisbitmask)
275 """End the NoiseReplacer.
277 Restores original data to the exposure from the heavies dictionary and
278 the mask planes to their original state.
290 mask.removeAndClearMaskPlane(maskname,
True)
299 """Return a generator of artificial noise.
303 noiseGenerator : `lsst.afw.image.noiseReplacer.NoiseGenerator`
305 if noiseImage
is not None:
310 if exposureId
is not None and exposureId != 0:
315 if noiseMeanVar
is not None:
318 noiseMean, noiseVar = noiseMeanVar
319 noiseMean = float(noiseMean)
320 noiseVar = float(noiseVar)
321 noiseStd = math.sqrt(noiseVar)
323 self.
log.
debug(
'Using passed-in noise mean = %g, variance = %g -> stdev %g',
324 noiseMean, noiseVar, noiseStd)
328 self.
log.
debug(
'Failed to cast passed-in noiseMeanVar to floats: %s',
333 if noiseSource ==
'meta':
335 meta = exposure.getMetadata()
338 bgMean = meta.getAsDouble(
'BGMEAN')
340 noiseStd = math.sqrt(bgMean)
342 self.
log.
debug(
'Using noise variance = (BGMEAN = %g) from exposure metadata',
347 self.
log.
debug(
'Failed to get BGMEAN from exposure metadata')
349 if noiseSource ==
'variance':
351 self.
log.
debug(
'Will draw noise according to the variance plane.')
352 var = exposure.getMaskedImage().getVariance()
356 im = exposure.getMaskedImage().getImage()
358 noiseMean = s.getValue(afwMath.MEANCLIP)
359 noiseStd = s.getValue(afwMath.STDEVCLIP)
361 self.
log.
debug(
"Measured from image: clipped mean = %g, stdev = %g",
367 """Make a list of NoiseReplacers behave like a single one.
369 This class provides conenient syntactic sugar for noise replacement across
374 This is only used in the MultiFit driver, but the logic there is already
375 pretty complex, so it's nice to have this to simplify it.
378 def __init__(self, exposuresById, footprintsByExp):
382 for expId, exposure
in exposuresById.items():
383 self.append(
NoiseReplacer(exposure, footprintsByExp[expId]), expId)
386 """Insert original pixels of the given source (by id) into the exposure.
392 """Insert noise pixels of the given source (by id) into the exposure.
398 """Clean-up when the use of the noise replacer is done.
405 r"""Base class for noise generators.
407 Derived classes produce
408 `~lsst.afw.detection.heavyFootprint.HeavyFootprint`\ s filled with noise
409 generated in various ways.
413 This is an abstract base class.
423 return afwImage.MaskedImageF(im)
430 """Generate noise by extracting a sub-image from a user-supplied image.
434 img : `lsst.afw.image.ImageF`
435 An image to use as the basis of noise generation.
439 self.
mim = afwImage.MaskedImageF(img)
448 """Abstract base for Gaussian noise generators.
458 rim = afwImage.ImageF(bb.getWidth(), bb.getHeight())
459 rim.setXY0(bb.getMinX(), bb.getMinY())
465 """Generates Gaussian noise with a fixed mean and standard deviation.
469 super(FixedGaussianNoiseGenerator, self).
__init__(rand=rand)
474 return 'FixedGaussianNoiseGenerator: mean=%g, std=%g' % (self.
mean, self.
std)
484 """Generates Gaussian noise with variance matching an image variance plane.
488 var : `lsst.afw.image.ImageF`
489 The input variance image.
490 mean : `float` or `lsst.afw.image.Image`, optional.
491 Mean value for the generated noise.
495 super(VariancePlaneNoiseGenerator, self).
__init__(rand=rand)
497 if mean
is not None and mean == 0.:
502 return 'VariancePlaneNoiseGenerator: mean=' + str(self.
mean)
507 stdev = afwImage.ImageF(self.
var, bb, afwImage.LOCAL,
True)
510 if self.
mean is not None:
516 """A noise replacer which does nothing.
518 This is used when we need to disable noise replacement.
522 This has all the public methods of `NoiseReplacer`, but none of them do