31 __all__ = (
"NoiseReplacerConfig",
"NoiseReplacer",
"DummyNoiseReplacer")
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)" 59 Class that handles replacing sources with noise during measurement. 61 When measuring a source (or the children associated with a parent source), this class is used 62 to replace its neighbors with noise, using the deblender's definition of the sources as stored 63 in HeavyFootprints attached to the SourceRecords. The algorithm works as follows: 64 - We start by replacing all pixels that are in source Footprints with artificially 65 generated noise (__init__). 66 - When we are about to measure a particular source, we add it back in, by inserting that source's 67 HeavyFootprint (from the deblender) into the image. 68 - When we are done measuring that source, we again replace the HeavyFootprint with (the same) 70 - After measuring all sources, we return the image to its original state. 72 This is a functional copy of the code in the older ReplaceWithNoiseTask, but with a slightly different 73 API needed for the new measurement framework; note that it is not a Task, as the lifetime of a 74 NoiseReplacer now corresponds to a single exposure, not an entire processing run. 77 ConfigClass = NoiseReplacerConfig
79 def __init__(self, config, exposure, footprints, noiseImage=None, exposureId=None, log=None):
81 Initialize the NoiseReplacer. 83 @param[in] config instance of NoiseReplacerConfig 84 @param[in,out] exposure Exposure to be noise replaced. (All sources replaced on return) 85 @param[in] footprints dict of {id: (parent, footprint)}; 86 @param[in] noiseImage an afw.image.ImageF used as a predictable noise replacement source 88 @param[in] log Log object to use for status messages; no status messages 89 will be printed if None 91 'footprints' is a dict of {id: (parent, footprint)}; when used in SFM, the ID will be the 92 source ID, but in forced photometry, this will be the reference ID, as that's what we used to 93 determine the deblend families. This routine should create HeavyFootprints for any non-Heavy 94 Footprints, and replace them in the dict. It should then create a dict of HeavyFootprints 95 containing noise, but only for parent objects, then replace all sources with noise. 96 This should ignore any footprints that lay outside the bounding box of the exposure, 97 and clip those that lie on the border. 99 NOTE: as the code currently stands, the heavy footprint for a deblended object must be available 100 from the input catalog. If it is not, it cannot be reproduced here. In that case, the 101 topmost parent in the objects parent chain must be used. The heavy footprint for that source 102 is created in this class from the masked image. 116 mi = exposure.getMaskedImage()
122 for maskname
in [
'THISDET',
'OTHERDET']:
125 plane = mask.getMaskPlane(maskname)
127 self.
log.debug(
'Mask plane "%s" already existed', maskname)
130 plane = mask.addMaskPlane(maskname)
132 mask.clearMaskPlane(plane)
133 bitmask = mask.getPlaneBitMask(maskname)
134 bitmasks.append(bitmask)
136 self.
log.debug(
'Mask plane "%s": plane %i, bitmask %i = 0x%x',
137 maskname, plane, bitmask, bitmask)
150 for id, fp
in footprints.items():
154 self.
heavies[id] = afwDet.makeHeavyFootprint(fp[1], mi)
165 noisegen = self.
getNoiseGenerator(exposure, noiseImage, noiseMeanVar, exposureId=exposureId)
170 self.
log.debug(
'Using noise generator: %s', str(noisegen))
172 fp = footprints[id][1]
173 noiseFp = noisegen.getHeavyFootprint(fp)
184 Insert the heavy footprint of a given source into the exposure 186 @param[in] id id for current source to insert from original footprint dict 188 Also adjusts the mask plane to show the source of this footprint. 202 fp.spans.setMask(mask, self.thisbitmask)
207 Remove the heavy footprint of a given source and replace with previous noise 209 @param[in] id id for current source to insert from original footprint dict 211 Also restore the mask plane. 229 fp.spans.clearMask(mask, self.thisbitmask)
234 End the NoiseReplacer. 236 Restore original data to the exposure from the heavies dictionary 237 Restore the mask planes to their original state 249 mask.removeAndClearMaskPlane(maskname,
True)
259 Generate noise image using parameters given 261 if noiseImage
is not None:
266 if exposureId
is not None and exposureId != 0:
270 rand = afwMath.Random(afwMath.Random.MT19937, seed)
271 if noiseMeanVar
is not None:
274 noiseMean, noiseVar = noiseMeanVar
275 noiseMean = float(noiseMean)
276 noiseVar = float(noiseVar)
277 noiseStd = math.sqrt(noiseVar)
279 self.
log.debug(
'Using passed-in noise mean = %g, variance = %g -> stdev %g',
280 noiseMean, noiseVar, noiseStd)
284 self.
log.debug(
'Failed to cast passed-in noiseMeanVar to floats: %s',
289 if noiseSource ==
'meta':
291 meta = exposure.getMetadata()
294 bgMean = meta.getAsDouble(
'BGMEAN')
296 noiseStd = math.sqrt(bgMean)
298 self.
log.debug(
'Using noise variance = (BGMEAN = %g) from exposure metadata',
303 self.
log.debug(
'Failed to get BGMEAN from exposure metadata')
305 if noiseSource ==
'variance':
307 self.
log.debug(
'Will draw noise according to the variance plane.')
308 var = exposure.getMaskedImage().getVariance()
312 im = exposure.getMaskedImage().getImage()
313 s = afwMath.makeStatistics(im, afwMath.MEANCLIP | afwMath.STDEVCLIP)
314 noiseMean = s.getValue(afwMath.MEANCLIP)
315 noiseStd = s.getValue(afwMath.STDEVCLIP)
317 self.
log.debug(
"Measured from image: clipped mean = %g, stdev = %g",
323 """Syntactic sugar that makes a list of NoiseReplacers (for multiple exposures) 324 behave like a single one. 326 This is only used in the multifit driver, but the logic there is already pretty 327 complex, so it's nice to have this to simplify it. 330 def __init__(self, exposuresById, footprintsByExp):
334 for expId, exposure
in exposuresById.items():
335 self.append(
NoiseReplacer(exposure, footprintsByExp[expId]), expId)
338 """Insert the original pixels for a given source (by id) into the original exposure. 344 """Insert the noise pixels for a given source (by id) into the original exposure. 350 """Cleanup when the use of the Noise replacer is done. 358 Base class for noise generators used by the "doReplaceWithNoise" routine: 359 these produce HeavyFootprints filled with noise generated in various ways. 361 This is an abstract base class. 367 return afwDet.makeHeavyFootprint(fp, mim)
371 return afwImage.MaskedImageF(im)
379 Generates noise by cutting out a subimage from a user-supplied noise Image. 384 @param[in] img an afwImage.ImageF 386 self.
mim = afwImage.MaskedImageF(img)
387 self.
mean = afwMath.makeStatistics(img, afwMath.MEAN)
388 self.
std = afwMath.makeStatistics(img, afwMath.STDEV)
396 Generates noise using the afwMath.Random() and afwMath.randomGaussianImage() routines. 398 This is an abstract base class. 403 rand = afwMath.Random()
408 rim = afwImage.ImageF(bb.getWidth(), bb.getHeight())
409 rim.setXY0(bb.getMinX(), bb.getMinY())
410 afwMath.randomGaussianImage(rim, self.
rand)
416 Generates Gaussian noise with a fixed mean and standard deviation. 420 super(FixedGaussianNoiseGenerator, self).
__init__(rand=rand)
425 return 'FixedGaussianNoiseGenerator: mean=%g, std=%g' % (self.
mean, self.
std)
436 Generates Gaussian noise whose variance matches that of the variance plane of the image. 441 @param[in] var an afwImage.ImageF; the variance plane. 442 @param[in,out] mean floating-point or afwImage.Image 444 super(VariancePlaneNoiseGenerator, self).
__init__(rand=rand)
446 if mean
is not None and mean == 0.:
451 return 'VariancePlaneNoiseGenerator: mean=' + str(self.
mean)
456 stdev = afwImage.ImageF(self.
var, bb, afwImage.LOCAL,
True)
459 if self.
mean is not None:
466 A do-nothing standin for NoiseReplacer, used when we want to disable NoiseReplacer 468 DummyNoiseReplacer has all the public methods of NoiseReplacer, but none of them do anything.
def getNoiseGenerator(self, exposure, noiseImage, noiseMeanVar, exposureId=None)
Generate noise image using parameters given.
def insertSource(self, id)
def removeSource(self, id)
def end(self)
End the NoiseReplacer.
def getRandomImage(self, bb)
def removeSource(self, id)
Remove the heavy footprint of a given source and replace with previous noise.
def __init__(self, var, mean=None, rand=None)
Base class for noise generators used by the "doReplaceWithNoise" routine: these produce HeavyFootprin...
def __init__(self, exposuresById, footprintsByExp)
def getMaskedImage(self, bb)
def __init__(self, config, exposure, footprints, noiseImage=None, exposureId=None, log=None)
Initialize the NoiseReplacer.
def removeSource(self, id)
A do-nothing standin for NoiseReplacer, used when we want to disable NoiseReplacer.
def __init__(self, rand=None)
def getHeavyFootprint(self, fp)
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 __init__(self, mean, std, rand=None)
def getMaskedImage(self, bb)
def insertSource(self, id)
Insert the heavy footprint of a given source into the exposure.
def insertSource(self, id)
Generates noise using the afwMath.Random() and afwMath.randomGaussianImage() routines.