1 from builtins
import str
2 from builtins
import object
32 __all__ = (
"NoiseReplacerConfig",
"NoiseReplacer",
"DummyNoiseReplacer")
36 noiseSource = lsst.pex.config.ChoiceField(
37 doc=
'How to choose mean and variance of the Gaussian noise we generate?',
40 'measure':
'Measure clipped mean and variance from the whole image',
41 'meta':
'Mean = 0, variance = the "BGMEAN" metadata entry',
42 'variance':
"Mean = 0, variance = the image's variance",
44 default=
'measure', optional=
False
46 noiseOffset = lsst.pex.config.Field(
47 doc=
'Add ann offset to the generated noise.',
48 dtype=float, optional=
False, default=0.0
50 noiseSeedMultiplier = lsst.pex.config.Field(
52 doc=
"The seed multiplier value to use for random number generation\n"
53 " >= 1: set the seed deterministically based on exposureId\n"
54 " 0: fall back to the afw.math.Random default constructor (which uses a seed value of 1)"
60 Class that handles replacing sources with noise during measurement.
62 When measuring a source (or the children associated with a parent source), this class is used
63 to replace its neighbors with noise, using the deblender's definition of the sources as stored
64 in HeavyFootprints attached to the SourceRecords. The algorithm works as follows:
65 - We start by replacing all pixels that are in source Footprints with artificially
66 generated noise (__init__).
67 - When we are about to measure a particular source, we add it back in, by inserting that source's
68 HeavyFootprint (from the deblender) into the image.
69 - When we are done measuring that source, we again replace the HeavyFootprint with (the same)
71 - After measuring all sources, we return the image to its original state.
73 This is a functional copy of the code in the older ReplaceWithNoiseTask, but with a slightly different
74 API needed for the new measurement framework; note that it is not a Task, as the lifetime of a
75 NoiseReplacer now corresponds to a single exposure, not an entire processing run.
78 ConfigClass = NoiseReplacerConfig
80 def __init__(self, config, exposure, footprints, noiseImage=None, exposureId=None, log=None):
82 Initialize the NoiseReplacer.
84 @param[in] config instance of NoiseReplacerConfig
85 @param[in,out] exposure Exposure to be noise replaced. (All sources replaced on return)
86 @param[in] footprints dict of {id: (parent, footprint)};
87 @param[in] noiseImage an afw.image.ImageF used as a predictable noise replacement source
89 @param[in] log Log object to use for status messages; no status messages
90 will be printed if None
92 'footprints' is a dict of {id: (parent, footprint)}; when used in SFM, the ID will be the
93 source ID, but in forced photometry, this will be the reference ID, as that's what we used to
94 determine the deblend families. This routine should create HeavyFootprints for any non-Heavy
95 Footprints, and replace them in the dict. It should then create a dict of HeavyFootprints
96 containing noise, but only for parent objects, then replace all sources with noise.
97 This should ignore any footprints that lay outside the bounding box of the exposure,
98 and clip those that lie on the border.
100 NOTE: as the code currently stands, the heavy footprint for a deblended object must be available
101 from the input catalog. If it is not, it cannot be reproduced here. In that case, the
102 topmost parent in the objects parent chain must be used. The heavy footprint for that source
103 is created in this class from the masked image.
117 mi = exposure.getMaskedImage()
123 for maskname
in [
'THISDET',
'OTHERDET']:
126 plane = mask.getMaskPlane(maskname)
128 self.log.debug(
'Mask plane "%s" already existed', maskname)
131 plane = mask.addMaskPlane(maskname)
132 self.removeplanes.append(maskname)
133 mask.clearMaskPlane(plane)
134 bitmask = mask.getPlaneBitMask(maskname)
135 bitmasks.append(bitmask)
137 self.log.debug(
'Mask plane "%s": plane %i, bitmask %i = 0x%x',
138 maskname, plane, bitmask, bitmask)
151 for id, fp
in footprints.items():
153 self.
heavies[id] = afwDet.cast_HeavyFootprintF(fp[1])
166 noisegen = self.
getNoiseGenerator(exposure, noiseImage, noiseMeanVar, exposureId=exposureId)
171 self.log.debug(
'Using noise generator: %s', str(noisegen))
173 fp = footprints[id][1]
174 noiseFp = noisegen.getHeavyFootprint(fp)
185 Insert the heavy footprint of a given source into the exposure
187 @param[in] id id for current source to insert from original footprint dict
189 Also adjusts the mask plane to show the source of this footprint.
192 mi = self.exposure.getMaskedImage()
208 Remove the heavy footprint of a given source and replace with previous noise
210 @param[in] id id for current source to insert from original footprint dict
212 Also restore the mask plane.
217 mi = self.exposure.getMaskedImage()
235 End the NoiseReplacer.
237 Restore original data to the exposure from the heavies dictionary
238 Restore the mask planes to their original state
242 mi = self.exposure.getMaskedImage()
245 for id
in self.footprints.keys():
250 mask.removeAndClearMaskPlane(maskname,
True)
260 Generate noise image using parameters given
262 if noiseImage
is not None:
267 if exposureId
is not None and exposureId != 0:
272 if noiseMeanVar
is not None:
275 noiseMean, noiseVar = noiseMeanVar
276 noiseMean = float(noiseMean)
277 noiseVar = float(noiseVar)
278 noiseStd = math.sqrt(noiseVar)
280 self.log.debug(
'Using passed-in noise mean = %g, variance = %g -> stdev %g',
281 noiseMean, noiseVar, noiseStd)
285 self.log.debug(
'Failed to cast passed-in noiseMeanVar to floats: %s',
290 if noiseSource ==
'meta':
292 meta = exposure.getMetadata()
295 bgMean = meta.getAsDouble(
'BGMEAN')
297 noiseStd = math.sqrt(bgMean)
299 self.log.debug(
'Using noise variance = (BGMEAN = %g) from exposure metadata',
304 self.log.debug(
'Failed to get BGMEAN from exposure metadata')
306 if noiseSource ==
'variance':
308 self.log.debug(
'Will draw noise according to the variance plane.')
309 var = exposure.getMaskedImage().getVariance()
313 im = exposure.getMaskedImage().getImage()
315 noiseMean = s.getValue(afwMath.MEANCLIP)
316 noiseStd = s.getValue(afwMath.STDEVCLIP)
318 self.log.debug(
"Measured from image: clipped mean = %g, stdev = %g",
324 """Syntactic sugar that makes a list of NoiseReplacers (for multiple exposures)
325 behave like a single one.
327 This is only used in the multifit driver, but the logic there is already pretty
328 complex, so it's nice to have this to simplify it.
331 def __init__(self, exposuresById, footprintsByExp):
335 for expId, exposure
in exposuresById.items():
336 self.append(
NoiseReplacer(exposure, footprintsByExp[expId]), expId)
339 """Insert the original pixels for a given source (by id) into the original exposure.
345 """Insert the noise pixels for a given source (by id) into the original exposure.
351 """Cleanup when the use of the Noise replacer is done.
359 Base class for noise generators used by the "doReplaceWithNoise" routine:
360 these produce HeavyFootprints filled with noise generated in various ways.
362 This is an abstract base class.
372 return afwImage.MaskedImageF(im)
380 Generates noise by cutting out a subimage from a user-supplied noise Image.
385 @param[in] img an afwImage.ImageF
387 self.
mim = afwImage.MaskedImageF(img)
397 Generates noise using the afwMath.Random() and afwMath.randomGaussianImage() routines.
399 This is an abstract base class.
409 rim = afwImage.ImageF(bb.getWidth(), bb.getHeight())
410 rim.setXY0(bb.getMinX(), bb.getMinY())
417 Generates Gaussian noise with a fixed mean and standard deviation.
421 super(FixedGaussianNoiseGenerator, self).
__init__(rand=rand)
426 return 'FixedGaussianNoiseGenerator: mean=%g, std=%g' % (self.
mean, self.
std)
437 Generates Gaussian noise whose variance matches that of the variance plane of the image.
442 @param[in] var an afwImage.ImageF; the variance plane.
443 @param[in,out] mean floating-point or afwImage.Image
445 super(VariancePlaneNoiseGenerator, self).
__init__(rand=rand)
447 if mean
is not None and mean == 0.:
452 return 'VariancePlaneNoiseGenerator: mean=' + str(self.
mean)
457 stdev = afwImage.ImageF(self.
var, bb, afwImage.LOCAL,
True)
460 if self.
mean is not None:
467 A do-nothing standin for NoiseReplacer, used when we want to disable NoiseReplacer
469 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.
heavyNoise
FIXME: the heavy footprint includes the mask and variance planes, which we shouldn't need (I don't th...
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.
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.