LSSTApplications  1.1.2+25,10.0+13,10.0+132,10.0+133,10.0+224,10.0+41,10.0+8,10.0-1-g0f53050+14,10.0-1-g4b7b172+19,10.0-1-g61a5bae+98,10.0-1-g7408a83+3,10.0-1-gc1e0f5a+19,10.0-1-gdb4482e+14,10.0-11-g3947115+2,10.0-12-g8719d8b+2,10.0-15-ga3f480f+1,10.0-2-g4f67435,10.0-2-gcb4bc6c+26,10.0-28-gf7f57a9+1,10.0-3-g1bbe32c+14,10.0-3-g5b46d21,10.0-4-g027f45f+5,10.0-4-g86f66b5+2,10.0-4-gc4fccf3+24,10.0-40-g4349866+2,10.0-5-g766159b,10.0-5-gca2295e+25,10.0-6-g462a451+1
LSSTDataManagementBasePackage
deblend.py
Go to the documentation of this file.
1 #
2 # LSST Data Management System
3 # Copyright 2008-2013 LSST Corporation.
4 #
5 # This product includes software developed by the
6 # LSST Project (http://www.lsst.org/).
7 #
8 # This program is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation, either version 3 of the License, or
11 # (at your option) any later version.
12 #
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
17 #
18 # You should have received a copy of the LSST License Statement and
19 # the GNU General Public License along with this program. If not,
20 # see <http://www.lsstcorp.org/LegalNotices/>.
21 #
22 import math
23 import numpy
24 import time
25 
26 import lsst.pex.config as pexConf
27 import lsst.afw.table as afwTable
28 import lsst.pipe.base as pipeBase
29 import lsst.afw.math as afwMath
30 import lsst.afw.geom as afwGeom
31 import lsst.afw.image as afwImage
32 import lsst.afw.detection as afwDet
33 
34 __all__ = 'SourceDeblendConfig', 'SourceDeblendTask'
35 
36 class SourceDeblendConfig(pexConf.Config):
37 
38  edgeHandling = pexConf.ChoiceField(
39  doc='What to do when a peak to be deblended is close to the edge of the image',
40  dtype=str, default='ramp',
41  allowed = {
42  'clip': 'Clip the template at the edge AND the mirror of the edge.',
43  'ramp': 'Ramp down flux at the image edge by the PSF',
44  'noclip': 'Ignore the edge when building the symmetric template.',
45  })
46 
47  strayFluxToPointSources = pexConf.ChoiceField(
48  doc='When the deblender should attribute stray flux to point sources',
49  dtype=str, default='necessary',
50  allowed = {
51  'necessary': 'When there is not an extended object in the footprint',
52  'always': 'Always',
53  'never': 'Never; stray flux will not be attributed to any deblended child if the deblender thinks all peaks look like point sources',
54  }
55  )
56 
57  findStrayFlux = pexConf.Field(dtype=bool, default=True,
58  doc='Find stray flux---flux not claimed by any child in the deblender.')
59 
60  assignStrayFlux = pexConf.Field(dtype=bool, default=True,
61  doc='Assign stray flux to deblend children. Implies findStrayFlux.')
62 
63  strayFluxRule = pexConf.ChoiceField(
64  doc='How to split flux among peaks',
65  dtype=str, default='r-to-peak',
66  allowed = {
67  'r-to-peak': '~ 1/(1+R^2) to the peak',
68  'r-to-footprint': '~ 1/(1+R^2) to the closest pixel in the footprint. CAUTION: this can be computationally expensive on large footprints!',
69  'nearest-footprint': 'Assign 100% to the nearest footprint (using L-1 norm aka Manhattan distance)' })
70 
71  clipStrayFluxFraction = pexConf.Field(dtype=float, default=0.01,
72  doc=('When splitting stray flux, clip fractions below this value to zero.'))
73 
74  psfChisq1 = pexConf.Field(dtype=float, default=1.5, optional=False,
75  doc=('Chi-squared per DOF cut for deciding a source is '+
76  'a PSF during deblending (un-shifted PSF model)'))
77  psfChisq2 = pexConf.Field(dtype=float, default=1.5, optional=False,
78  doc=('Chi-squared per DOF cut for deciding a source is '+
79  'PSF during deblending (shifted PSF model)'))
80  psfChisq2b = pexConf.Field(dtype=float, default=1.5, optional=False,
81  doc=('Chi-squared per DOF cut for deciding a source is '+
82  'a PSF during deblending (shifted PSF model #2)'))
83  maxNumberOfPeaks = pexConf.Field(dtype=int, default=0,
84  doc=("Only deblend the brightest maxNumberOfPeaks peaks in the parent" +
85  " (<= 0: unlimited)"))
86  maxFootprintArea = pexConf.Field(dtype=int, default=100000,
87  doc=('Refuse to deblend parent footprints containing more than this number of pixels (due to speed concerns); 0 means no limit.'))
88 
89  maxFootprintArea = pexConf.Field(dtype=int, default=100000,
90  doc=('Refuse to deblend parent footprints containing more than this number of pixels (due to speed concerns); 0 means no limit.'))
91 
92  tinyFootprintSize = pexConf.Field(dtype=int, default=2,
93  doc=('Footprints smaller in width or height than this value will be ignored; 0 to never ignore.'))
94 
95 ## \addtogroup LSST_task_documentation
96 ## \{
97 ## \page SourceDeblendTask
98 ## \ref SourceDeblendTask_ "SourceDeblendTask"
99 ## \copybrief SourceDeblendTask
100 ## \}
101 
102 class SourceDeblendTask(pipeBase.Task):
103  """!
104 \anchor SourceDeblendTask_
105 
106 \brief Split blended sources into individual sources.
107 
108  This task has no return value; it only modifies the SourceCatalog in-place.
109  """
110  ConfigClass = SourceDeblendConfig
111  _DefaultName = "sourceDeblend"
112 
113  def __init__(self, schema, **kwargs):
114  """Create the task, adding necessary fields to the given schema.
115 
116  @param[in,out] schema Schema object for measurement fields; will be modified in-place.
117  @param **kwds Passed to Task.__init__.
118  """
119  pipeBase.Task.__init__(self, **kwargs)
120  self.addSchemaKeys(schema)
121 
122  def addSchemaKeys(self, schema):
123  self.nChildKey = schema.addField('deblend_nChild', type=int,
124  doc='Number of children this object has (defaults to 0)')
125  self.psfKey = schema.addField('deblend_deblendedAsPsf', type='Flag',
126  doc='Deblender thought this source looked like a PSF')
127  self.psfCenterKey = schema.addField('deblend_psfCenter', type='PointD',
128  doc='If deblended-as-psf, the PSF centroid')
129  self.psfFluxKey = schema.addField('deblend_psfFlux', type='D',
130  doc='If deblended-as-psf, the PSF flux')
131  self.tooManyPeaksKey = schema.addField('deblend_tooManyPeaks', type='Flag',
132  doc='Source had too many peaks; ' +
133  'only the brightest were included')
134  self.tooBigKey = schema.addField('deblend_parentTooBig', type='Flag',
135  doc='Parent footprint covered too many pixels')
136  self.deblendFailedKey = schema.addField('deblend_failed', type='Flag',
137  doc="Deblending failed on source")
138 
139  self.deblendSkippedKey = schema.addField('deblend_skipped', type='Flag',
140  doc="Deblender skipped this source")
141 
142  self.deblendRampedTemplateKey = schema.addField(
143  'deblend_rampedTemplate', type='Flag',
144  doc=('This source was near an image edge and the deblender used ' +
145  '"ramp" edge-handling.'))
146 
147  self.deblendPatchedTemplateKey = schema.addField(
148  'deblend_patchedTemplate', type='Flag',
149  doc=('This source was near an image edge and the deblender used ' +
150  '"patched" edge-handling.'))
151 
152  self.hasStrayFluxKey = schema.addField(
153  'deblend_hasStrayFlux', type='Flag',
154  doc=('This source was assigned some stray flux'))
155 
156  self.log.logdebug('Added keys to schema: %s' % ", ".join(str(x) for x in (
157  self.nChildKey, self.psfKey, self.psfCenterKey, self.psfFluxKey,
158  self.tooManyPeaksKey, self.tooBigKey)))
159 
160  @pipeBase.timeMethod
161  def run(self, exposure, sources, psf):
162  """Run deblend().
163 
164  @param[in] exposure Exposure to process
165  @param[in,out] sources SourceCatalog containing sources detected on this exposure.
166  @param[in] psf PSF
167 
168  @return None
169  """
170  self.deblend(exposure, sources, psf)
171 
172  def _getPsfFwhm(self, psf, bbox):
173  # It should be easier to get a PSF's fwhm;
174  # https://dev.lsstcorp.org/trac/ticket/3030
175  return psf.computeShape().getDeterminantRadius() * 2.35
176 
177  @pipeBase.timeMethod
178  def deblend(self, exposure, srcs, psf):
179  """Deblend.
180 
181  @param[in] exposure Exposure to process
182  @param[in,out] srcs SourceCatalog containing sources detected on this exposure.
183  @param[in] psf PSF
184 
185  @return None
186  """
187  self.log.info("Deblending %d sources" % len(srcs))
188 
189  from lsst.meas.deblender.baseline import deblend
190  import lsst.meas.algorithms as measAlg
191 
192  # find the median stdev in the image...
193  mi = exposure.getMaskedImage()
194  stats = afwMath.makeStatistics(mi.getVariance(), mi.getMask(), afwMath.MEDIAN)
195  sigma1 = math.sqrt(stats.getValue(afwMath.MEDIAN))
196  self.log.logdebug('sigma1: %g' % sigma1)
197 
198  schema = srcs.getSchema()
199 
200  n0 = len(srcs)
201  nparents = 0
202  for i,src in enumerate(srcs):
203  #t0 = time.clock()
204 
205  fp = src.getFootprint()
206  pks = fp.getPeaks()
207  if len(pks) < 2:
208  continue
209 
210  toobig = ((self.config.maxFootprintArea > 0) and
211  (fp.getArea() > self.config.maxFootprintArea))
212  src.set(self.tooBigKey, toobig)
213  if toobig:
214  src.set(self.deblendSkippedKey, True)
215  self.log.logdebug('Parent %i: area %i > max %i; skipping' %
216  (int(src.getId()), fp.getArea(), self.config.maxFootprintArea))
217  continue
218 
219  nparents += 1
220  bb = fp.getBBox()
221  psf_fwhm = self._getPsfFwhm(psf, bb)
222 
223  self.log.logdebug('Parent %i: deblending %i peaks' % (int(src.getId()), len(pks)))
224 
225  self.preSingleDeblendHook(exposure, srcs, i, fp, psf, psf_fwhm, sigma1)
226  npre = len(srcs)
227 
228  # This should really be set in deblend, but deblend doesn't have access to the src
229  src.set(self.tooManyPeaksKey, len(fp.getPeaks()) > self.config.maxNumberOfPeaks)
230 
231  try:
232  res = deblend(
233  fp, mi, psf, psf_fwhm, sigma1=sigma1,
234  psfChisqCut1 = self.config.psfChisq1,
235  psfChisqCut2 = self.config.psfChisq2,
236  psfChisqCut2b= self.config.psfChisq2b,
237  maxNumberOfPeaks=self.config.maxNumberOfPeaks,
238  strayFluxToPointSources=self.config.strayFluxToPointSources,
239  assignStrayFlux=self.config.assignStrayFlux,
240  findStrayFlux=(self.config.assignStrayFlux or
241  self.config.findStrayFlux),
242  strayFluxAssignment=self.config.strayFluxRule,
243  rampFluxAtEdge=(self.config.edgeHandling == 'ramp'),
244  patchEdges=(self.config.edgeHandling == 'noclip'),
245  tinyFootprintSize=self.config.tinyFootprintSize,
246  clipStrayFluxFraction=self.config.clipStrayFluxFraction,
247  )
248  src.set(self.deblendFailedKey, False)
249  except Exception as e:
250  self.log.warn("Error deblending source %d: %s" % (src.getId(), e))
251  src.set(self.deblendFailedKey, True)
252  import traceback
253  traceback.print_exc()
254  continue
255 
256  kids = []
257  nchild = 0
258  for j,peak in enumerate(res.peaks):
259  if peak.skip:
260  # skip this source?
261  self.log.logdebug('Skipping out-of-bounds peak at (%i,%i)' %
262  (pks[j].getIx(), pks[j].getIy()))
263  src.set(self.deblendSkippedKey, True)
264  continue
265 
266  heavy = peak.getFluxPortion()
267  if heavy is None:
268  # This can happen for children >= maxNumberOfPeaks
269  self.log.logdebug('Skipping peak at (%i,%i), child %i of %i: no flux portion'
270  % (pks[j].getIx(), pks[j].getIy(), j+1, len(res.peaks)))
271  src.set(self.deblendSkippedKey, True)
272  continue
273  assert(len(heavy.getPeaks()) == 1)
274 
275  src.set(self.deblendSkippedKey, False)
276  child = srcs.addNew(); nchild += 1
277  child.setParent(src.getId())
278  child.setFootprint(heavy)
279  child.set(self.psfKey, peak.deblendedAsPsf)
280  child.set(self.hasStrayFluxKey, peak.strayFlux is not None)
281  if peak.deblendedAsPsf:
282  (cx,cy) = peak.psfFitCenter
283  child.set(self.psfCenterKey, afwGeom.Point2D(cx, cy))
284  child.set(self.psfFluxKey, peak.psfFitFlux)
285  child.set(self.deblendRampedTemplateKey, peak.hasRampedTemplate)
286  child.set(self.deblendPatchedTemplateKey, peak.patched)
287  kids.append(child)
288 
289  # Child footprints may extend beyond the full extent of their parent's which
290  # results in a failure of the replace-by-noise code to reinstate these pixels
291  # to their original values. The following updates the parent footprint
292  # in-place to ensure it contains the full union of itself and all of its
293  # children's footprints.
294  src.getFootprint().include([child.getFootprint() for child in kids])
295 
296  src.set(self.nChildKey, nchild)
297 
298  self.postSingleDeblendHook(exposure, srcs, i, npre, kids, fp, psf, psf_fwhm, sigma1, res)
299  #print 'Deblending parent id', src.getId(), 'took', time.clock() - t0
300 
301  n1 = len(srcs)
302  self.log.info('Deblended: of %i sources, %i were deblended, creating %i children, total %i sources' %
303  (n0, nparents, n1-n0, n1))
304 
305  def preSingleDeblendHook(self, exposure, srcs, i, fp, psf, psf_fwhm, sigma1):
306  pass
307 
308  def postSingleDeblendHook(self, exposure, srcs, i, npre, kids, fp, psf, psf_fwhm, sigma1, res):
309  pass
310 
311 
Split blended sources into individual sources.
Definition: deblend.py:102
Statistics makeStatistics(afwImage::Mask< afwImage::MaskPixel > const &msk, int const flags, StatisticsControl const &sctrl)
Specialization to handle Masks.
Definition: Statistics.cc:1023