LSSTApplications  8.0.0.0+107,8.0.0.1+13,9.1+18,9.2,master-g084aeec0a4,master-g0aced2eed8+6,master-g15627eb03c,master-g28afc54ef9,master-g3391ba5ea0,master-g3d0fb8ae5f,master-g4432ae2e89+36,master-g5c3c32f3ec+17,master-g60f1e072bb+1,master-g6a3ac32d1b,master-g76a88a4307+1,master-g7bce1f4e06+57,master-g8ff4092549+31,master-g98e65bf68e,master-ga6b77976b1+53,master-gae20e2b580+3,master-gb584cd3397+53,master-gc5448b162b+1,master-gc54cf9771d,master-gc69578ece6+1,master-gcbf758c456+22,master-gcec1da163f+63,master-gcf15f11bcc,master-gd167108223,master-gf44c96c709
LSSTDataManagementBasePackage
replaceWithNoise.py
Go to the documentation of this file.
1 import lsst.pex.config as pexConfig
2 import lsst.pipe.base as pipeBase
3 import lsst.afw.detection as afwDet
4 import lsst.afw.math as afwMath
5 import lsst.afw.image as afwImage
6 
7 class ReplaceWithNoiseConfig(pexConfig.Config):
8  """
9  Configuration for ReplaceWithNoiseTask.
10  """
11  noiseSource = pexConfig.ChoiceField(doc='How do we choose the mean and variance of the Gaussian noise we generate?',
12  dtype=str, allowed={
13  'measure': 'Measure clipped mean and variance from the whole image',
14  'meta': 'Mean = 0, variance = the "BGMEAN" metadata entry',
15  'variance': "Mean = 0, variance = the image's variance",
16  },
17  default='measure',
18  optional=False)
19 
20  noiseOffset = pexConfig.Field(dtype=float, optional=False, default=0.,
21  doc='Add ann offset to the generated noise.')
22 
23  noiseSeed = pexConfig.Field(dtype=int, default=0, doc='The seed value to use for random number generation.')
24 
25 class ReplaceWithNoiseTask(pipeBase.Task):
26  ConfigClass = ReplaceWithNoiseConfig
27  _DefaultName = "replaceWithNoise"
28 
29  def begin(self, exposure, sources, noiseImage=None, noiseMeanVar=None):
30  # creates heavies, replaces all footprints with noise
31  # We need the source table to be sorted by ID to do the parent lookups
32  # (sources.find() below)
33  if not sources.isSorted():
34  sources.sort()
35  mi = exposure.getMaskedImage()
36  im = mi.getImage()
37  mask = mi.getMask()
38 
39  # Add temporary Mask planes for THISDET and OTHERDET
40  self.removeplanes = []
41  bitmasks = []
42  for maskname in ['THISDET', 'OTHERDET']:
43  try:
44  # does it already exist?
45  plane = mask.getMaskPlane(maskname)
46  self.log.logdebug('Mask plane "%s" already existed' % maskname)
47  except:
48  # if not, add it; we should delete it when done.
49  plane = mask.addMaskPlane(maskname)
50  self.removeplanes.append(maskname)
51  mask.clearMaskPlane(plane)
52  bitmask = mask.getPlaneBitMask(maskname)
53  bitmasks.append(bitmask)
54  self.log.logdebug('Mask plane "%s": plane %i, bitmask %i = 0x%x' %
55  (maskname, plane, bitmask, bitmask))
56  self.thisbitmask,self.otherbitmask = bitmasks
57  del bitmasks
58 
59  # Start by creating HeavyFootprints for each source.
60  #
61  # The "getParent()" checks are here because top-level
62  # sources (ie, those with no parents) are not supposed to
63  # have HeavyFootprints, but child sources (ie, those that
64  # have been deblended) should have HeavyFootprints
65  # already.
66  self.heavies = []
67  for source in sources:
68  fp = source.getFootprint()
69  hfp = afwDet.cast_HeavyFootprintF(fp)
70  if source.getParent() and hfp is not None:
71  # this source has been deblended; "fp" should
72  # already be a HeavyFootprint (but may not be
73  # if read from disk).
74  # Swig downcasts it to Footprint, so we have to re-cast.
75  self.heavies.append(hfp)
76  else:
77  # top-level source: copy pixels from the input
78  # image.
79  ### FIXME: the heavy footprint includes the mask
80  ### and variance planes, which we shouldn't need
81  ### (I don't think we ever want to modify them in
82  ### the input image). Copying them around is
83  ### wasteful.
84  heavy = afwDet.makeHeavyFootprint(fp, mi)
85  self.heavies.append(heavy)
86 
87  # We now create a noise HeavyFootprint for each top-level Source.
88  # We'll put the noisy footprints in a map from id -> HeavyFootprint:
89  self.heavyNoise = {}
90  noisegen = self.getNoiseGenerator(exposure, noiseImage, noiseMeanVar)
91  self.log.logdebug('Using noise generator: %s' % (str(noisegen)))
92  for source in sources:
93  if source.getParent():
94  continue
95  fp = source.getFootprint()
96  heavy = noisegen.getHeavyFootprint(fp)
97  self.heavyNoise[source.getId()] = heavy
98  # Also insert the noisy footprint into the image now.
99  # Notice that we're just inserting it into "im", ie,
100  # the Image, not the MaskedImage.
101  heavy.insert(im)
102  # Also set the OTHERDET bit
104 
105  def insertSource(self, exposure, sourcei):
106  # Copy this source's pixels into the image
107  mi = exposure.getMaskedImage()
108  im = mi.getImage()
109  mask = mi.getMask()
110  fp = self.heavies[sourcei]
111  fp.insert(im)
112  afwDet.setMaskFromFootprint(mask, fp, self.thisbitmask)
114 
115  def removeSource(self, exposure, sources, source):
116  # remove a single source
117  # (Replace this source's pixels by noise again.)
118  # Do this by finding the source's top-level ancestor
119  mi = exposure.getMaskedImage()
120  im = mi.getImage()
121  mask = mi.getMask()
122  ancestor = source
123  j = 0
124  while ancestor.getParent():
125  ancestor = sources.find(ancestor.getParent())
126  j += 1
127  if not ancestor or j == 100:
128  raise RuntimeError('Source hierarchy too deep, or (more likely) your Source table is botched.')
129  # Re-insert the noise pixels
130  fp = self.heavyNoise[ancestor.getId()]
131  fp.insert(im)
132  # Clear the THISDET mask plane.
133  afwDet.clearMaskFromFootprint(mask, fp, self.thisbitmask)
135 
136  def end(self, exposure, sources):
137  # restores original image, cleans up temporaries
138  # (ie, replace all the top-level pixels)
139  mi = exposure.getMaskedImage()
140  im = mi.getImage()
141  mask = mi.getMask()
142  for source,heavy in zip(sources,self.heavies):
143  if source.getParent():
144  continue
145  heavy.insert(im)
146  for maskname in self.removeplanes:
147  mask.removeAndClearMaskPlane(maskname, True)
148 
149  del self.removeplanes
150  del self.thisbitmask
151  del self.otherbitmask
152  del self.heavies
153  del self.heavyNoise
154 
155  def getNoiseGenerator(self, exposure, noiseImage, noiseMeanVar):
156  if noiseImage is not None:
157  return ImageNoiseGenerator(noiseImage)
158  rand = None
159  if self.config.noiseSeed:
160  # default algorithm, our seed
161  rand = afwMath.Random(afwMath.Random.MT19937, self.config.noiseSeed)
162  if noiseMeanVar is not None:
163  try:
164  # Assume noiseMeanVar is an iterable of floats
165  noiseMean,noiseVar = noiseMeanVar
166  noiseMean = float(noiseMean)
167  noiseVar = float(noiseVar)
168  noiseStd = math.sqrt(noiseVar)
169  self.log.logdebug('Using passed-in noise mean = %g, variance = %g -> stdev %g' %
170  (noiseMean, noiseVar, noiseStd))
171  return FixedGaussianNoiseGenerator(noiseMean, noiseStd, rand=rand)
172  except:
173  self.log.logdebug('Failed to cast passed-in noiseMeanVar to floats: %s' %
174  (str(noiseMeanVar)))
175  offset = self.config.noiseOffset
176  noiseSource = self.config.noiseSource
177 
178  if noiseSource == 'meta':
179  # check the exposure metadata
180  meta = exposure.getMetadata()
181  # this key name correspond to estimateBackground() in detection.py
182  try:
183  bgMean = meta.getAsDouble('BGMEAN')
184  # We would have to adjust for GAIN if ip_isr didn't make it 1.0
185  noiseStd = math.sqrt(bgMean)
186  self.log.logdebug('Using noise variance = (BGMEAN = %g) from exposure metadata' %
187  (bgMean))
188  return FixedGaussianNoiseGenerator(offset, noiseStd, rand=rand)
189  except:
190  self.log.logdebug('Failed to get BGMEAN from exposure metadata')
191 
192  if noiseSource == 'variance':
193  self.log.logdebug('Will draw noise according to the variance plane.')
194  var = exposure.getMaskedImage().getVariance()
195  return VariancePlaneNoiseGenerator(var, mean=offset, rand=rand)
196 
197  # Compute an image-wide clipped variance.
198  im = exposure.getMaskedImage().getImage()
199  s = afwMath.makeStatistics(im, afwMath.MEANCLIP | afwMath.STDEVCLIP)
200  noiseMean = s.getValue(afwMath.MEANCLIP)
201  noiseStd = s.getValue(afwMath.STDEVCLIP)
202  self.log.logdebug("Measured from image: clipped mean = %g, stdev = %g" %
203  (noiseMean,noiseStd))
204  return FixedGaussianNoiseGenerator(noiseMean + offset, noiseStd, rand=rand)
205 
206 class NoiseGenerator(object):
207  '''
208  Base class for noise generators used by the "doReplaceWithNoise" routine:
209  these produce HeavyFootprints filled with noise generated in various ways.
210 
211  This is an abstract base class.
212  '''
213  def getHeavyFootprint(self, fp):
214  bb = fp.getBBox()
215  mim = self.getMaskedImage(bb)
216  return afwDet.makeHeavyFootprint(fp, mim)
217  def getMaskedImage(self, bb):
218  im = self.getImage(bb)
219  return afwImage.MaskedImageF(im)
220  def getImage(self, bb):
221  return None
222 
224  '''
225  "Generates" noise by cutting out a subimage from a user-supplied noise Image.
226  '''
227  def __init__(self, img):
228  '''
229  img: an afwImage.ImageF
230  '''
231  self.mim = afwImage.MaskedImageF(img)
232  def getMaskedImage(self, bb):
233  return self.mim
234 
236  '''
237  Generates noise using the afwMath.Random() and afwMath.randomGaussianImage() routines.
238 
239  This is an abstract base class.
240  '''
241  def __init__(self, rand=None):
242  if rand is None:
243  rand = afwMath.Random()
244  self.rand = rand
245  def getRandomImage(self, bb):
246  # Create an Image and fill it with Gaussian noise.
247  rim = afwImage.ImageF(bb.getWidth(), bb.getHeight())
248  rim.setXY0(bb.getMinX(), bb.getMinY())
250  return rim
251 
253  '''
254  Generates Gaussian noise with a fixed mean and standard deviation.
255  '''
256  def __init__(self, mean, std, rand=None):
257  super(FixedGaussianNoiseGenerator, self).__init__(rand=rand)
258  self.mean = mean
259  self.std = std
260  def __str__(self):
261  return 'FixedGaussianNoiseGenerator: mean=%g, std=%g' % (self.mean, self.std)
262  def getImage(self, bb):
263  rim = self.getRandomImage(bb)
264  rim *= self.std
265  rim += self.mean
266  return rim
267 
269  '''
270  Generates Gaussian noise whose variance matches that of the variance plane of the image.
271  '''
272  def __init__(self, var, mean=None, rand=None):
273  '''
274  var: an afwImage.ImageF; the variance plane.
275  mean: floating-point or afwImage.Image
276  '''
277  super(VariancePlaneNoiseGenerator, self).__init__(rand=rand)
278  self.var = var
279  if mean is not None and mean == 0.:
280  mean = None
281  self.mean = mean
282  def __str__(self):
283  return 'VariancePlaneNoiseGenerator: mean=' + str(self.mean)
284  def getImage(self, bb):
285  rim = self.getRandomImage(bb)
286  # Use the image's variance plane to scale the noise.
287  stdev = afwImage.ImageF(self.var, bb, afwImage.LOCAL, True)
288  stdev.sqrt()
289  rim *= stdev
290  if self.mean is not None:
291  rim += self.mean
292  return rim
293 
heavyNoise
FIXME: the heavy footprint includes the mask and variance planes, which we shouldn't need (I don't th...
void randomGaussianImage(ImageT *image, Random &rand)
Definition: RandomImage.cc:155
MaskT clearMaskFromFootprint(lsst::afw::image::Mask< MaskT > *mask, Footprint const &footprint, MaskT const bitmask)
(AND ~bitmask) all the Mask&#39;s pixels that are in the Footprint; that is, set to zero in the Mask-inte...
MaskT setMaskFromFootprint(lsst::afw::image::Mask< MaskT > *mask, Footprint const &footprint, MaskT const bitmask)
OR bitmask into all the Mask&#39;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.
Definition: Statistics.cc:1023
HeavyFootprint< ImagePixelT, MaskPixelT, VariancePixelT > makeHeavyFootprint(Footprint const &foot, lsst::afw::image::MaskedImage< ImagePixelT, MaskPixelT, VariancePixelT > const &img, HeavyFootprintCtrl const *ctrl=NULL)