30 noiseSource = lsst.pex.config.ChoiceField(
31 doc=
'How to choose mean and variance of the Gaussian noise we generate?',
34 'measure':
'Measure clipped mean and variance from the whole image',
35 'meta':
'Mean = 0, variance = the "BGMEAN" metadata entry',
36 'variance':
"Mean = 0, variance = the image's variance",
38 default=
'measure', optional=
False
40 noiseOffset = lsst.pex.config.Field(
41 dtype=float, optional=
False, default=0.,
42 doc=
'Add ann offset to the generated noise.'
44 noiseSeed = lsst.pex.config.Field(
46 doc=
'The seed value to use for random number generation.'
51 Class that handles replacing sources with noise during measurement.
53 When measuring a source (or the children associated with a parent source), this class is used
54 to replace its neighbors with noise, using the deblender's definition of the sources as stored
55 in HeavyFootprints attached to the SourceRecords. The algorithm works as follows:
56 - We start by replacing all pixels that are in source Footprints with artificially
57 generated noise (__init__).
58 - When we are about to measure a particular source, we add it back in, by inserting that source's
59 HeavyFootprint (from the deblender) into the image.
60 - When we are done measuring that source, we again replace the HeavyFootprint with (the same)
62 - After measuring all sources, we return the image to its original state.
64 This is a functional copy of the code in the older ReplaceWithNoiseTask, but with a slightly different
65 API needed for the new measurement framework; note that it is not a Task, as the lifetime of a
66 NoiseReplacer now corresponds to a single exposure, not an entire processing run.
69 ConfigClass = NoiseReplacerConfig
71 def __init__(self, config, exposure, footprints, log=None):
73 Initialize the NoiseReplacer.
75 @param[in] config instance of NoiseReplacerConfig
76 @param[in,out] exposure Exposure to be noise replaced. (All sources replaced on return)
77 @param[in] footprints dict of {id: (parent, footprint)};
78 @param[in] log pex.logging.Log object to use for status messages; no status messages
79 will be printed if None
81 'footprints' is a dict of {id: (parent, footprint)}; when used in SFM, the ID will be the
82 source ID, but in forced photometry, this will be the reference ID, as that's what we used to
83 determine the deblend families. This routine should create HeavyFootprints for any non-Heavy
84 Footprints, and replace them in the dict. It should then create a dict of HeavyFootprints
85 containing noise, but only for parent objects, then replace all sources with noise.
86 This should ignore any footprints that lay outside the bounding box of the exposure,
87 and clip those that lie on the border.
89 NOTE: as the code currently stands, the heavy footprint for a deblended object must be available
90 from the input catalog. If it is not, it cannot be reproduced here. In that case, the
91 topmost parent in the objects parent chain must be used. The heavy footprint for that source
92 is created in this class from the masked image.
107 mi = exposure.getMaskedImage()
113 for maskname
in [
'THISDET',
'OTHERDET']:
116 plane = mask.getMaskPlane(maskname)
117 if self.
log: self.log.logdebug(
'Mask plane "%s" already existed' % maskname)
120 plane = mask.addMaskPlane(maskname)
121 self.removeplanes.append(maskname)
122 mask.clearMaskPlane(plane)
123 bitmask = mask.getPlaneBitMask(maskname)
124 bitmasks.append(bitmask)
125 if self.
log: self.log.logdebug(
'Mask plane "%s": plane %i, bitmask %i = 0x%x'
126 % (maskname, plane, bitmask, bitmask))
139 for id
in footprints.keys():
142 self.
heavies[id] = afwDet.cast_HeavyFootprintF(fp[1])
159 if self.
log: self.log.logdebug(
'Using noise generator: %s' % (str(noisegen)))
160 for id
in self.heavies.keys():
161 fp = footprints[id][1]
162 noiseFp = noisegen.getHeavyFootprint(fp)
173 Insert the heavy footprint of a given source into the exposure
175 @param[in] id id for current source to insert from original footprint dict
177 Also adjusts the mask plane to show the source of this footprint.
180 mi = self.exposure.getMaskedImage()
187 while self.
footprints[usedid][0] != 0
and not usedid
in self.heavies.keys():
196 Remove the heavy footprint of a given source and replace with previous noise
198 @param[in] id id for current source to insert from original footprint dict
200 Also restore the mask plane.
205 mi = self.exposure.getMaskedImage()
212 while self.
footprints[usedid][0] != 0
and not usedid
in self.heavies.keys():
223 End the NoiseReplacer.
225 Restore original data to the exposure from the heavies dictionary
226 Restore the mask planes to their original state
230 mi = self.exposure.getMaskedImage()
233 for id
in self.footprints.keys():
239 mask.removeAndClearMaskPlane(maskname,
True)
249 Generate noise image using parameters given
251 if noiseImage
is not None:
257 if noiseMeanVar
is not None:
260 noiseMean,noiseVar = noiseMeanVar
261 noiseMean = float(noiseMean)
262 noiseVar = float(noiseVar)
263 noiseStd = math.sqrt(noiseVar)
264 if self.
log: self.log.logdebug(
'Using passed-in noise mean = %g, variance = %g -> stdev %g'
265 % (noiseMean, noiseVar, noiseStd))
268 if self.
log: self.log.logdebug(
'Failed to cast passed-in noiseMeanVar to floats: %s'
269 % (str(noiseMeanVar)))
273 if noiseSource ==
'meta':
275 meta = exposure.getMetadata()
278 bgMean = meta.getAsDouble(
'BGMEAN')
280 noiseStd = math.sqrt(bgMean)
281 if self.
log: self.log.logdebug(
'Using noise variance = (BGMEAN = %g) from exposure metadata'
285 if self.
log: self.log.logdebug(
'Failed to get BGMEAN from exposure metadata')
287 if noiseSource ==
'variance':
288 if self.
log: self.log.logdebug(
'Will draw noise according to the variance plane.')
289 var = exposure.getMaskedImage().getVariance()
293 im = exposure.getMaskedImage().getImage()
295 noiseMean = s.getValue(afwMath.MEANCLIP)
296 noiseStd = s.getValue(afwMath.STDEVCLIP)
297 if self.
log: self.log.logdebug(
"Measured from image: clipped mean = %g, stdev = %g"
298 % (noiseMean,noiseStd))
302 """Syntactic sugar that makes a list of NoiseReplacers (for multiple exposures)
303 behave like a single one.
305 This is only used in the multifit driver, but the logic there is already pretty
306 complex, so it's nice to have this to simplify it.
309 def __init__(self, exposuresById, footprintsByExp):
313 for expId, exposure
in exposuresById.iteritems():
317 """Insert the original pixels for a given source (by id) into the original exposure.
322 """Insert the noise pixels for a given source (by id) into the original exposure.
327 """Cleanup when the use of the Noise replacer is done.
329 for item
in self: self.
end()
333 Base class for noise generators used by the "doReplaceWithNoise" routine:
334 these produce HeavyFootprints filled with noise generated in various ways.
336 This is an abstract base class.
346 return afwImage.MaskedImageF(im)
352 Generates noise by cutting out a subimage from a user-supplied noise Image.
357 img: an afwImage.ImageF
359 self.
mim = afwImage.MaskedImageF(img)
366 Generates noise using the afwMath.Random() and afwMath.randomGaussianImage() routines.
368 This is an abstract base class.
378 rim = afwImage.ImageF(bb.getWidth(), bb.getHeight())
379 rim.setXY0(bb.getMinX(), bb.getMinY())
385 Generates Gaussian noise with a fixed mean and standard deviation.
389 super(FixedGaussianNoiseGenerator, self).
__init__(rand=rand)
394 return 'FixedGaussianNoiseGenerator: mean=%g, std=%g' % (self.
mean, self.
std)
404 Generates Gaussian noise whose variance matches that of the variance plane of the image.
409 var: an afwImage.ImageF; the variance plane.
410 mean: floating-point or afwImage.Image
412 super(VariancePlaneNoiseGenerator, self).
__init__(rand=rand)
414 if mean
is not None and mean == 0.:
419 return 'VariancePlaneNoiseGenerator: mean=' + str(self.
mean)
424 stdev = afwImage.ImageF(self.
var, bb, afwImage.LOCAL,
True)
427 if self.
mean is not None:
434 A do-nothing standin for NoiseReplacer, used when we want to disable NoiseReplacer
436 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...
void randomGaussianImage(ImageT *image, Random &rand)
MaskT clearMaskFromFootprint(lsst::afw::image::Mask< MaskT > *mask, Footprint const &footprint, MaskT const bitmask)
(AND ~bitmask) all the Mask's pixels that are in the Footprint; that is, set to zero in the Mask-inte...
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.
MaskT setMaskFromFootprint(lsst::afw::image::Mask< MaskT > *mask, Footprint const &footprint, MaskT const bitmask)
OR bitmask into all the Mask's pixels that are in the Footprint.
Statistics makeStatistics(afwImage::Mask< afwImage::MaskPixel > const &msk, int const flags, StatisticsControl const &sctrl)
Specialization to handle Masks.
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.
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.