27 import lsst.pex.config
29 __all__ = (
"NoiseReplacerConfig",
"NoiseReplacer",
"DummyNoiseReplacer")
33 """Noise replacement configuration.""" 35 noiseSource = lsst.pex.config.ChoiceField(
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 45 noiseOffset = lsst.pex.config.Field(
46 doc=
'Add ann offset to the generated noise.',
47 dtype=float, optional=
False, default=0.0
49 noiseSeedMultiplier = lsst.pex.config.Field(
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
def getNoiseGenerator(self, exposure, noiseImage, noiseMeanVar, exposureId=None)
def insertSource(self, id)
def removeSource(self, id)
def getRandomImage(self, bb)
def removeSource(self, id)
std::shared_ptr< FrameSet > append(FrameSet const &first, FrameSet const &second)
Construct a FrameSet that performs two transformations in series.
Statistics makeStatistics(lsst::afw::math::MaskedVector< EntryT > const &mv, std::vector< WeightPixel > const &vweights, int const flags, StatisticsControl const &sctrl=StatisticsControl())
The makeStatistics() overload to handle lsst::afw::math::MaskedVector<>
def __init__(self, var, mean=None, rand=None)
void randomGaussianImage(ImageT *image, Random &rand)
Set image to random numbers with a gaussian N(0, 1) distribution.
def __init__(self, exposuresById, footprintsByExp)
def getMaskedImage(self, bb)
def __init__(self, config, exposure, footprints, noiseImage=None, exposureId=None, log=None)
def removeSource(self, id)
def __init__(self, rand=None)
def getHeavyFootprint(self, fp)
Backwards-compatibility support for depersisting the old Calib (FluxMag0/FluxMag0Err) objects...
def __init__(self, mean, std, rand=None)
daf::base::PropertyList * list
def getMaskedImage(self, bb)
def insertSource(self, id)
A class that can be used to generate sequences of random numbers according to a number of different a...
def insertSource(self, id)
HeavyFootprint< ImagePixelT, MaskPixelT, VariancePixelT > makeHeavyFootprint(Footprint const &foot, lsst::afw::image::MaskedImage< ImagePixelT, MaskPixelT, VariancePixelT > const &img, HeavyFootprintCtrl const *ctrl=NULL)
Create a HeavyFootprint with footprint defined by the given Footprint and pixel values from the given...