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",
42 'variance_median':
"Mean = 0, variance = median(variance plane)"
44 default=
'measure', optional=
False
47 doc=
'Add ann offset to the generated noise.',
48 dtype=float, optional=
False, default=0.0
52 doc=
"The seed multiplier value to use for random number generation:\n"
53 ">= 1: set the seed deterministically based on exposureId\n"
54 "0: fall back to the afw.math.Random default constructor (which uses a seed value of 1)"
59 r"""Replace sources with noise during measurement.
63 config : `NoiseReplacerConfig`
65 exposure : `lsst.afw.image.Exposure`
66 Image in which sources will be replaced by noise. During operation,
67 the image will be modified in-place to replace all sources. At the end
68 of the measurment procedure, the original sources will be replaced.
70 Mapping of ``id`` to a tuple of ``(parent, Footprint)``. When used in
71 single-frame measurement, ``id`` is the source ID, but in forced
72 photometry this is the reference ID (as that is used to determine
74 noiseImage : `lsst.afw.image.ImageF`
75 An image used as a predictable noise replacement source. Used during
77 log : `lsst.log.log.log.Log` or `logging.Logger`, optional
78 Logger to use for status messages; no status messages will be recorded
83 When measuring a source (or the children associated with a parent source),
84 this class is used to replace its neighbors with noise, using the
85 deblender's definition of the sources as stored in
86 `~lsst.afw.detection.heavyFootprint.HeavyFootprint`\ s attached to the
87 `~lsst.afw.table.SourceRecord`\ s. The algorithm works as follows:
89 #. All pixels in the source `~lsst.afw.detection.Footprint`\ s are replaced
90 with artificially generated noise (in `NoiseReplacer.__init__`).
91 #. Before each source is measured, we restore the original pixel data by
92 inserting that source's
93 `~lsst.afw.detection.heavyFootprint.HeavyFootprint` (from the deblender)
95 #. After measurement, we again replace the source pixels with (the same)
97 #. After measuring all sources, the image is returned to its original
100 This is a functional copy of the code in the older
101 ``ReplaceWithNoiseTask``, but with a slightly different API needed for the
102 new measurement framework; note that it is not an `~lsst.pipe.base.Task`,
103 as the lifetime of a ``NoiseReplacer`` now corresponds to a single
104 exposure, not an entire processing run.
106 When processing the ``footprints`` parameter, this routine should create
107 `~lsst.afw.detection.heavyFootprint.HeavyFootprint`\ s for any non-Heavy
108 `~lsst.afw.detection.Footprint`\ s, and replace them in the dictionary. It
109 should then create a dict of
110 `~lsst.afw.detection.heavyFootprint.HeavyFootprint`\ s containing noise,
111 but only for parent objects, then replace all sources with noise. This
112 should ignore any footprints that lay outside the bounding box of the
113 exposure, and clip those that lie on the border.
115 As the code currently stands, the heavy footprint for a deblended object
116 must be available from the input catalog. If it is not, it cannot be
117 reproduced here. In that case, the topmost parent in the objects parent
118 chain must be used. The heavy footprint for that source is created in
119 this class from the masked image.
122 ConfigClass = NoiseReplacerConfig
125 """Image on which the NoiseReplacer is operating (`lsst.afw.image.Exposure`).
129 """Mapping of ``id`` to a tuple of ``(parent, Footprint)`` (`dict`).
133 """Logger used for status messages.
136 def __init__(self, config, exposure, footprints, noiseImage=None, exposureId=None, log=None):
149 mi = exposure.getMaskedImage()
155 for maskname
in [
'THISDET',
'OTHERDET']:
158 plane = mask.getMaskPlane(maskname)
160 self.
loglog.
debug(
'Mask plane "%s" already existed', maskname)
163 plane = mask.addMaskPlane(maskname)
165 mask.clearMaskPlane(plane)
166 bitmask = mask.getPlaneBitMask(maskname)
167 bitmasks.append(bitmask)
169 self.
loglog.
debug(
'Mask plane "%s": plane %i, bitmask %i = 0x%x',
170 maskname, plane, bitmask, bitmask)
183 for id, fp
in footprints.items():
185 self.
heaviesheavies[id] = fp[1]
198 noisegen = self.
getNoiseGeneratorgetNoiseGenerator(exposure, noiseImage, noiseMeanVar, exposureId=exposureId)
200 self.
loglog.
debug(
'Using noise generator: %s', str(noisegen))
202 fp = footprints[id][1]
203 noiseFp = noisegen.getHeavyFootprint(fp)
213 """Insert the heavy footprint of a given source into the exposure.
218 ID of the source to insert from original dictionary of footprints.
222 Also adjusts the mask plane to show the source of this footprint.
225 mi = self.
exposureexposure.getMaskedImage()
232 while self.
footprintsfootprints[usedid][0] != 0
and usedid
not in self.
heaviesheavies:
234 fp = self.
heaviesheavies[usedid]
236 fp.spans.setMask(mask, self.thisbitmask)
240 """Replace the heavy footprint of a given source with noise.
242 The same artificial noise is used as in the original replacement.
247 ID of the source to replace from original dictionary of footprints.
251 Also restores the mask plane.
256 mi = self.
exposureexposure.getMaskedImage()
263 while self.
footprintsfootprints[usedid][0] != 0
and usedid
not in self.
heaviesheavies:
269 fp.spans.clearMask(mask, self.thisbitmask)
273 """End the NoiseReplacer.
275 Restores original data to the exposure from the heavies dictionary and
276 the mask planes to their original state.
280 mi = self.
exposureexposure.getMaskedImage()
286 self.
heaviesheavies[id].insert(im)
288 mask.removeAndClearMaskPlane(maskname,
True)
297 """Return a generator of artificial noise.
301 noiseGenerator : `lsst.afw.image.noiseReplacer.NoiseGenerator`
303 if noiseImage
is not None:
308 if exposureId
is not None and exposureId != 0:
313 if noiseMeanVar
is not None:
316 noiseMean, noiseVar = noiseMeanVar
317 noiseMean = float(noiseMean)
318 noiseVar = float(noiseVar)
319 noiseStd = math.sqrt(noiseVar)
321 self.
loglog.
debug(
'Using passed-in noise mean = %g, variance = %g -> stdev %g',
322 noiseMean, noiseVar, noiseStd)
326 self.
loglog.
debug(
'Failed to cast passed-in noiseMeanVar to floats: %s',
331 if noiseSource ==
'meta':
333 meta = exposure.getMetadata()
336 bgMean = meta.getAsDouble(
'BGMEAN')
338 noiseStd = math.sqrt(bgMean)
340 self.
loglog.
debug(
'Using noise variance = (BGMEAN = %g) from exposure metadata',
345 self.
loglog.
debug(
'Failed to get BGMEAN from exposure metadata')
347 if noiseSource ==
'variance':
349 self.
loglog.
debug(
'Will draw noise according to the variance plane.')
350 var = exposure.getMaskedImage().getVariance()
353 if noiseSource ==
'variance_median':
355 self.
loglog.
debug(
'Will draw noise using the median of the variance plane.')
356 var = exposure.getMaskedImage().getVariance()
358 varMedian = s.getValue(afwMath.MEDIAN)
360 self.
loglog.
debug(
"Measured from variance: median variance = %g",
365 im = exposure.getMaskedImage().getImage()
367 noiseMean = s.getValue(afwMath.MEANCLIP)
368 noiseStd = s.getValue(afwMath.STDEVCLIP)
370 self.
loglog.
debug(
"Measured from image: clipped mean = %g, stdev = %g",
376 """Make a list of NoiseReplacers behave like a single one.
378 This class provides conenient syntactic sugar for noise replacement across
383 This is only used in the MultiFit driver, but the logic there is already
384 pretty complex, so it's nice to have this to simplify it.
387 def __init__(self, exposuresById, footprintsByExp):
391 for expId, exposure
in exposuresById.items():
392 self.append(
NoiseReplacer(exposure, footprintsByExp[expId]), expId)
395 """Insert original pixels of the given source (by id) into the exposure.
401 """Insert noise pixels of the given source (by id) into the exposure.
407 """Clean-up when the use of the noise replacer is done.
414 r"""Base class for noise generators.
416 Derived classes produce
417 `~lsst.afw.detection.heavyFootprint.HeavyFootprint`\ s filled with noise
418 generated in various ways.
422 This is an abstract base class.
432 return afwImage.MaskedImageF(im)
439 """Generate noise by extracting a sub-image from a user-supplied image.
443 img : `lsst.afw.image.ImageF`
444 An image to use as the basis of noise generation.
448 self.
mimmim = afwImage.MaskedImageF(img)
457 """Abstract base for Gaussian noise generators.
467 rim = afwImage.ImageF(bb.getWidth(), bb.getHeight())
468 rim.setXY0(bb.getMinX(), bb.getMinY())
474 """Generates Gaussian noise with a fixed mean and standard deviation.
478 super(FixedGaussianNoiseGenerator, self).
__init__(rand=rand)
483 return 'FixedGaussianNoiseGenerator: mean=%g, std=%g' % (self.
meanmean, self.
stdstd)
493 """Generates Gaussian noise with variance matching an image variance plane.
497 var : `lsst.afw.image.ImageF`
498 The input variance image.
499 mean : `float` or `lsst.afw.image.Image`, optional.
500 Mean value for the generated noise.
504 super(VariancePlaneNoiseGenerator, self).
__init__(rand=rand)
506 if mean
is not None and mean == 0.:
511 return 'VariancePlaneNoiseGenerator: mean=' + str(self.
meanmean)
516 stdev = afwImage.ImageF(self.
varvar, bb, afwImage.LOCAL,
True)
519 if self.
meanmean
is not None:
525 """A noise replacer which does nothing.
527 This is used when we need to disable noise replacement.
531 This has all the public methods of `NoiseReplacer`, but none of them do
A class that can be used to generate sequences of random numbers according to a number of different a...
def insertSource(self, id)
def removeSource(self, id)
def __init__(self, mean, std, rand=None)
def getRandomImage(self, bb)
def __init__(self, rand=None)
def getMaskedImage(self, bb)
def getMaskedImage(self, bb)
def getHeavyFootprint(self, fp)
def insertSource(self, id)
def removeSource(self, id)
def getNoiseGenerator(self, exposure, noiseImage, noiseMeanVar, exposureId=None)
def __init__(self, config, exposure, footprints, noiseImage=None, exposureId=None, log=None)
def removeSource(self, id)
def insertSource(self, id)
def __init__(self, exposuresById, footprintsByExp)
def __init__(self, var, mean=None, rand=None)
daf::base::PropertyList * list
std::shared_ptr< FrameSet > append(FrameSet const &first, FrameSet const &second)
Construct a FrameSet that performs two transformations in series.
HeavyFootprint< ImagePixelT, MaskPixelT, VariancePixelT > makeHeavyFootprint(Footprint const &foot, lsst::afw::image::MaskedImage< ImagePixelT, MaskPixelT, VariancePixelT > const &img, HeavyFootprintCtrl const *ctrl=nullptr)
Create a HeavyFootprint with footprint defined by the given Footprint and pixel values from the given...
Backwards-compatibility support for depersisting the old Calib (FluxMag0/FluxMag0Err) objects.
void randomGaussianImage(ImageT *image, Random &rand)
Set image to random numbers with a gaussian N(0, 1) distribution.
Statistics makeStatistics(lsst::afw::image::Image< Pixel > const &img, lsst::afw::image::Mask< image::MaskPixel > const &msk, int const flags, StatisticsControl const &sctrl=StatisticsControl())
Handle a watered-down front-end to the constructor (no variance)