23 from builtins
import str
24 from builtins
import object
28 import lsst.afw.detection
as afwDet
29 import lsst.afw.image
as afwImage
30 import lsst.afw.math
as afwMath
31 import lsst.pex.config
33 __all__ = (
"NoiseReplacerConfig",
"NoiseReplacer",
"DummyNoiseReplacer")
37 noiseSource = lsst.pex.config.ChoiceField(
38 doc=
'How to choose mean and variance of the Gaussian noise we generate?',
41 'measure':
'Measure clipped mean and variance from the whole image',
42 'meta':
'Mean = 0, variance = the "BGMEAN" metadata entry',
43 'variance':
"Mean = 0, variance = the image's variance",
45 default=
'measure', optional=
False
47 noiseOffset = lsst.pex.config.Field(
48 doc=
'Add ann offset to the generated noise.',
49 dtype=float, optional=
False, default=0.0
51 noiseSeedMultiplier = lsst.pex.config.Field(
53 doc=
"The seed multiplier value to use for random number generation\n"
54 " >= 1: set the seed deterministically based on exposureId\n"
55 " 0: fall back to the afw.math.Random default constructor (which uses a seed value of 1)"
61 Class that handles replacing sources with noise during measurement.
63 When measuring a source (or the children associated with a parent source), this class is used
64 to replace its neighbors with noise, using the deblender's definition of the sources as stored
65 in HeavyFootprints attached to the SourceRecords. The algorithm works as follows:
66 - We start by replacing all pixels that are in source Footprints with artificially
67 generated noise (__init__).
68 - When we are about to measure a particular source, we add it back in, by inserting that source's
69 HeavyFootprint (from the deblender) into the image.
70 - When we are done measuring that source, we again replace the HeavyFootprint with (the same)
72 - After measuring all sources, we return the image to its original state.
74 This is a functional copy of the code in the older ReplaceWithNoiseTask, but with a slightly different
75 API needed for the new measurement framework; note that it is not a Task, as the lifetime of a
76 NoiseReplacer now corresponds to a single exposure, not an entire processing run.
79 ConfigClass = NoiseReplacerConfig
81 def __init__(self, config, exposure, footprints, noiseImage=None, exposureId=None, log=None):
83 Initialize the NoiseReplacer.
85 @param[in] config instance of NoiseReplacerConfig
86 @param[in,out] exposure Exposure to be noise replaced. (All sources replaced on return)
87 @param[in] footprints dict of {id: (parent, footprint)};
88 @param[in] noiseImage an afw.image.ImageF used as a predictable noise replacement source
90 @param[in] log Log object to use for status messages; no status messages
91 will be printed if None
93 'footprints' is a dict of {id: (parent, footprint)}; when used in SFM, the ID will be the
94 source ID, but in forced photometry, this will be the reference ID, as that's what we used to
95 determine the deblend families. This routine should create HeavyFootprints for any non-Heavy
96 Footprints, and replace them in the dict. It should then create a dict of HeavyFootprints
97 containing noise, but only for parent objects, then replace all sources with noise.
98 This should ignore any footprints that lay outside the bounding box of the exposure,
99 and clip those that lie on the border.
101 NOTE: as the code currently stands, the heavy footprint for a deblended object must be available
102 from the input catalog. If it is not, it cannot be reproduced here. In that case, the
103 topmost parent in the objects parent chain must be used. The heavy footprint for that source
104 is created in this class from the masked image.
118 mi = exposure.getMaskedImage()
124 for maskname
in [
'THISDET',
'OTHERDET']:
127 plane = mask.getMaskPlane(maskname)
129 self.log.debug(
'Mask plane "%s" already existed', maskname)
132 plane = mask.addMaskPlane(maskname)
133 self.removeplanes.append(maskname)
134 mask.clearMaskPlane(plane)
135 bitmask = mask.getPlaneBitMask(maskname)
136 bitmasks.append(bitmask)
138 self.log.debug(
'Mask plane "%s": plane %i, bitmask %i = 0x%x',
139 maskname, plane, bitmask, bitmask)
152 for id, fp
in footprints.items():
156 self.
heavies[id] = afwDet.makeHeavyFootprint(fp[1], mi)
167 noisegen = self.
getNoiseGenerator(exposure, noiseImage, noiseMeanVar, exposureId=exposureId)
172 self.log.debug(
'Using noise generator: %s', str(noisegen))
174 fp = footprints[id][1]
175 noiseFp = noisegen.getHeavyFootprint(fp)
186 Insert the heavy footprint of a given source into the exposure
188 @param[in] id id for current source to insert from original footprint dict
190 Also adjusts the mask plane to show the source of this footprint.
193 mi = self.exposure.getMaskedImage()
204 fp.spans.setMask(mask, self.thisbitmask)
209 Remove the heavy footprint of a given source and replace with previous noise
211 @param[in] id id for current source to insert from original footprint dict
213 Also restore the mask plane.
218 mi = self.exposure.getMaskedImage()
231 fp.spans.clearMask(mask, self.thisbitmask)
236 End the NoiseReplacer.
238 Restore original data to the exposure from the heavies dictionary
239 Restore the mask planes to their original state
243 mi = self.exposure.getMaskedImage()
246 for id
in self.footprints.keys():
251 mask.removeAndClearMaskPlane(maskname,
True)
261 Generate noise image using parameters given
263 if noiseImage
is not None:
268 if exposureId
is not None and exposureId != 0:
272 rand = afwMath.Random(afwMath.Random.MT19937, seed)
273 if noiseMeanVar
is not None:
276 noiseMean, noiseVar = noiseMeanVar
277 noiseMean = float(noiseMean)
278 noiseVar = float(noiseVar)
279 noiseStd = math.sqrt(noiseVar)
281 self.log.debug(
'Using passed-in noise mean = %g, variance = %g -> stdev %g',
282 noiseMean, noiseVar, noiseStd)
286 self.log.debug(
'Failed to cast passed-in noiseMeanVar to floats: %s',
291 if noiseSource ==
'meta':
293 meta = exposure.getMetadata()
296 bgMean = meta.getAsDouble(
'BGMEAN')
298 noiseStd = math.sqrt(bgMean)
300 self.log.debug(
'Using noise variance = (BGMEAN = %g) from exposure metadata',
305 self.log.debug(
'Failed to get BGMEAN from exposure metadata')
307 if noiseSource ==
'variance':
309 self.log.debug(
'Will draw noise according to the variance plane.')
310 var = exposure.getMaskedImage().getVariance()
314 im = exposure.getMaskedImage().getImage()
315 s = afwMath.makeStatistics(im, afwMath.MEANCLIP | afwMath.STDEVCLIP)
316 noiseMean = s.getValue(afwMath.MEANCLIP)
317 noiseStd = s.getValue(afwMath.STDEVCLIP)
319 self.log.debug(
"Measured from image: clipped mean = %g, stdev = %g",
325 """Syntactic sugar that makes a list of NoiseReplacers (for multiple exposures)
326 behave like a single one.
328 This is only used in the multifit driver, but the logic there is already pretty
329 complex, so it's nice to have this to simplify it.
332 def __init__(self, exposuresById, footprintsByExp):
336 for expId, exposure
in exposuresById.items():
337 self.append(
NoiseReplacer(exposure, footprintsByExp[expId]), expId)
340 """Insert the original pixels for a given source (by id) into the original exposure.
346 """Insert the noise pixels for a given source (by id) into the original exposure.
352 """Cleanup when the use of the Noise replacer is done.
360 Base class for noise generators used by the "doReplaceWithNoise" routine:
361 these produce HeavyFootprints filled with noise generated in various ways.
363 This is an abstract base class.
369 return afwDet.makeHeavyFootprint(fp, mim)
373 return afwImage.MaskedImageF(im)
381 Generates noise by cutting out a subimage from a user-supplied noise Image.
386 @param[in] img an afwImage.ImageF
388 self.
mim = afwImage.MaskedImageF(img)
389 self.
mean = afwMath.makeStatistics(img, afwMath.MEAN)
390 self.
std = afwMath.makeStatistics(img, afwMath.STDEV)
398 Generates noise using the afwMath.Random() and afwMath.randomGaussianImage() routines.
400 This is an abstract base class.
405 rand = afwMath.Random()
410 rim = afwImage.ImageF(bb.getWidth(), bb.getHeight())
411 rim.setXY0(bb.getMinX(), bb.getMinY())
412 afwMath.randomGaussianImage(rim, self.
rand)
418 Generates Gaussian noise with a fixed mean and standard deviation.
422 super(FixedGaussianNoiseGenerator, self).
__init__(rand=rand)
427 return 'FixedGaussianNoiseGenerator: mean=%g, std=%g' % (self.
mean, self.
std)
438 Generates Gaussian noise whose variance matches that of the variance plane of the image.
443 @param[in] var an afwImage.ImageF; the variance plane.
444 @param[in,out] mean floating-point or afwImage.Image
446 super(VariancePlaneNoiseGenerator, self).
__init__(rand=rand)
448 if mean
is not None and mean == 0.:
453 return 'VariancePlaneNoiseGenerator: mean=' + str(self.
mean)
458 stdev = afwImage.ImageF(self.
var, bb, afwImage.LOCAL,
True)
461 if self.
mean is not None:
468 A do-nothing standin for NoiseReplacer, used when we want to disable NoiseReplacer
470 DummyNoiseReplacer has all the public methods of NoiseReplacer, but none of them do anything.
def __init__
Initialize the NoiseReplacer.
def end
End the NoiseReplacer.
Base class for noise generators used by the "doReplaceWithNoise" routine: these produce HeavyFootprin...
A do-nothing standin for NoiseReplacer, used when we want to disable NoiseReplacer.
Generates Gaussian noise whose variance matches that of the variance plane of the image...
Generates Gaussian noise with a fixed mean and standard deviation.
Class that handles replacing sources with noise during measurement.
def getNoiseGenerator
Generate noise image using parameters given.
heavyNoise
FIXME: the heavy footprint includes the mask and variance planes, which we shouldn't need (I don't th...
def insertSource
Insert the heavy footprint of a given source into the exposure.
def removeSource
Remove the heavy footprint of a given source and replace with previous noise.
Generates noise using the afwMath.Random() and afwMath.randomGaussianImage() routines.