23 from builtins
import str
24 from builtins
import object
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)
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():
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. 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. 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 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. 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 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)
HeavyFootprint< ImagePixelT, MaskPixelT, VariancePixelT > makeHeavyFootprint(Footprint const &foot, lsst::afw::image::MaskedImage< ImagePixelT, MaskPixelT, VariancePixelT > const &img, HeavyFootprintCtrl const *ctrl=NULL)
Generates noise using the afwMath.Random() and afwMath.randomGaussianImage() routines.