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