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
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  if schema.getVersion() == 0:
124  self.nChildKey = schema.addField('deblend.nchild', type=int,
125  doc='Number of children this object has (defaults to 0)')
126  self.psfKey = schema.addField('deblend.deblended-as-psf', type='Flag',
127  doc='Deblender thought this source looked like a PSF')
128  self.psfCenterKey = schema.addField('deblend.psf-center', type='PointD',
129  doc='If deblended-as-psf, the PSF centroid')
130  self.psfFluxKey = schema.addField('deblend.psf-flux', type='D',
131  doc='If deblended-as-psf, the PSF flux')
132  self.tooManyPeaksKey = schema.addField('deblend.too-many-peaks', type='Flag',
133  doc='Source had too many peaks; ' +
134  'only the brightest were included')
135  self.tooBigKey = schema.addField('deblend.parent-too-big', type='Flag',
136  doc='Parent footprint covered too many pixels')
137  self.deblendFailedKey = schema.addField('deblend.failed', type='Flag',
138  doc="Deblending failed on source")
139 
140  self.deblendSkippedKey = schema.addField('deblend.skipped', type='Flag',
141  doc="Deblender skipped this source")
142 
143  self.deblendRampedTemplateKey = schema.addField(
144  'deblend.ramped_template', type='Flag',
145  doc=('This source was near an image edge and the deblender used ' +
146  '"ramp" edge-handling.'))
147 
148  self.deblendPatchedTemplateKey = schema.addField(
149  'deblend.patched_template', type='Flag',
150  doc=('This source was near an image edge and the deblender used ' +
151  '"patched" edge-handling.'))
152 
153  self.hasStrayFluxKey = schema.addField(
154  'deblend.has_stray_flux', type='Flag',
155  doc=('This source was assigned some stray flux'))
156  else:
157  self.nChildKey = schema.addField('deblend_nChild', type=int,
158  doc='Number of children this object has (defaults to 0)')
159  self.psfKey = schema.addField('deblend_deblendedAsPsf', type='Flag',
160  doc='Deblender thought this source looked like a PSF')
161  self.psfCenterKey = schema.addField('deblend_psfCenter', type='PointD',
162  doc='If deblended-as-psf, the PSF centroid')
163  self.psfFluxKey = schema.addField('deblend_psfFlux', type='D',
164  doc='If deblended-as-psf, the PSF flux')
165  self.tooManyPeaksKey = schema.addField('deblend_tooManyPeaks', type='Flag',
166  doc='Source had too many peaks; ' +
167  'only the brightest were included')
168  self.tooBigKey = schema.addField('deblend_parentTooBig', type='Flag',
169  doc='Parent footprint covered too many pixels')
170  self.deblendFailedKey = schema.addField('deblend_failed', type='Flag',
171  doc="Deblending failed on source")
172 
173  self.deblendSkippedKey = schema.addField('deblend_skipped', type='Flag',
174  doc="Deblender skipped this source")
175 
176  self.deblendRampedTemplateKey = schema.addField(
177  'deblend_rampedTemplate', type='Flag',
178  doc=('This source was near an image edge and the deblender used ' +
179  '"ramp" edge-handling.'))
180 
181  self.deblendPatchedTemplateKey = schema.addField(
182  'deblend_patchedTemplate', type='Flag',
183  doc=('This source was near an image edge and the deblender used ' +
184  '"patched" edge-handling.'))
185 
186  self.hasStrayFluxKey = schema.addField(
187  'deblend_hasStrayFlux', type='Flag',
188  doc=('This source was assigned some stray flux'))
189 
190  self.log.logdebug('Added keys to schema: %s' % ", ".join(str(x) for x in (
191  self.nChildKey, self.psfKey, self.psfCenterKey, self.psfFluxKey,
192  self.tooManyPeaksKey, self.tooBigKey)))
193 
194  @pipeBase.timeMethod
195  def run(self, exposure, sources, psf):
196  """Run deblend().
197 
198  @param[in] exposure Exposure to process
199  @param[in,out] sources SourceCatalog containing sources detected on this exposure.
200  @param[in] psf PSF
201 
202  @return None
203  """
204  self.deblend(exposure, sources, psf)
205 
206  def _getPsfFwhm(self, psf, bbox):
207  # It should be easier to get a PSF's fwhm;
208  # https://dev.lsstcorp.org/trac/ticket/3030
209  return psf.computeShape().getDeterminantRadius() * 2.35
210 
211  @pipeBase.timeMethod
212  def deblend(self, exposure, srcs, psf):
213  """Deblend.
214 
215  @param[in] exposure Exposure to process
216  @param[in,out] srcs SourceCatalog containing sources detected on this exposure.
217  @param[in] psf PSF
218 
219  @return None
220  """
221  self.log.info("Deblending %d sources" % len(srcs))
222 
223  from lsst.meas.deblender.baseline import deblend
224  import lsst.meas.algorithms as measAlg
225 
226  # find the median stdev in the image...
227  mi = exposure.getMaskedImage()
228  stats = afwMath.makeStatistics(mi.getVariance(), mi.getMask(), afwMath.MEDIAN)
229  sigma1 = math.sqrt(stats.getValue(afwMath.MEDIAN))
230  self.log.logdebug('sigma1: %g' % sigma1)
231 
232  schema = srcs.getSchema()
233 
234  n0 = len(srcs)
235  nparents = 0
236  for i,src in enumerate(srcs):
237  #t0 = time.clock()
238 
239  fp = src.getFootprint()
240  pks = fp.getPeaks()
241  if len(pks) < 2:
242  continue
243 
244  toobig = ((self.config.maxFootprintArea > 0) and
245  (fp.getArea() > self.config.maxFootprintArea))
246  src.set(self.tooBigKey, toobig)
247  if toobig:
248  src.set(self.deblendSkippedKey, True)
249  self.log.logdebug('Parent %i: area %i > max %i; skipping' %
250  (int(src.getId()), fp.getArea(), self.config.maxFootprintArea))
251  continue
252 
253  nparents += 1
254  bb = fp.getBBox()
255  psf_fwhm = self._getPsfFwhm(psf, bb)
256 
257  self.log.logdebug('Parent %i: deblending %i peaks' % (int(src.getId()), len(pks)))
258 
259  self.preSingleDeblendHook(exposure, srcs, i, fp, psf, psf_fwhm, sigma1)
260  npre = len(srcs)
261 
262  # This should really be set in deblend, but deblend doesn't have access to the src
263  src.set(self.tooManyPeaksKey, len(fp.getPeaks()) > self.config.maxNumberOfPeaks)
264 
265  try:
266  res = deblend(
267  fp, mi, psf, psf_fwhm, sigma1=sigma1,
268  psfChisqCut1 = self.config.psfChisq1,
269  psfChisqCut2 = self.config.psfChisq2,
270  psfChisqCut2b= self.config.psfChisq2b,
271  maxNumberOfPeaks=self.config.maxNumberOfPeaks,
272  strayFluxToPointSources=self.config.strayFluxToPointSources,
273  assignStrayFlux=self.config.assignStrayFlux,
274  findStrayFlux=(self.config.assignStrayFlux or
275  self.config.findStrayFlux),
276  strayFluxAssignment=self.config.strayFluxRule,
277  rampFluxAtEdge=(self.config.edgeHandling == 'ramp'),
278  patchEdges=(self.config.edgeHandling == 'noclip'),
279  tinyFootprintSize=self.config.tinyFootprintSize,
280  clipStrayFluxFraction=self.config.clipStrayFluxFraction,
281  )
282  src.set(self.deblendFailedKey, False)
283  except Exception as e:
284  self.log.warn("Error deblending source %d: %s" % (src.getId(), e))
285  src.set(self.deblendFailedKey, True)
286  import traceback
287  traceback.print_exc()
288  continue
289 
290  kids = []
291  nchild = 0
292  for j,peak in enumerate(res.peaks):
293  if peak.skip:
294  # skip this source?
295  self.log.logdebug('Skipping out-of-bounds peak at (%i,%i)' %
296  (pks[j].getIx(), pks[j].getIy()))
297  src.set(self.deblendSkippedKey, True)
298  continue
299 
300  heavy = peak.getFluxPortion()
301  if heavy is None:
302  # This can happen for children >= maxNumberOfPeaks
303  self.log.logdebug('Skipping peak at (%i,%i), child %i of %i: no flux portion'
304  % (pks[j].getIx(), pks[j].getIy(), j+1, len(res.peaks)))
305  src.set(self.deblendSkippedKey, True)
306  continue
307  assert(len(heavy.getPeaks()) == 1)
308 
309  src.set(self.deblendSkippedKey, False)
310  child = srcs.addNew(); nchild += 1
311  child.setParent(src.getId())
312  child.setFootprint(heavy)
313  child.set(self.psfKey, peak.deblendedAsPsf)
314  child.set(self.hasStrayFluxKey, peak.strayFlux is not None)
315  if peak.deblendedAsPsf:
316  (cx,cy) = peak.psfFitCenter
317  child.set(self.psfCenterKey, afwGeom.Point2D(cx, cy))
318  child.set(self.psfFluxKey, peak.psfFitFlux)
319  child.set(self.deblendRampedTemplateKey, peak.hasRampedTemplate)
320  child.set(self.deblendPatchedTemplateKey, peak.patched)
321  kids.append(child)
322 
323  src.set(self.nChildKey, nchild)
324 
325  self.postSingleDeblendHook(exposure, srcs, i, npre, kids, fp, psf, psf_fwhm, sigma1, res)
326  #print 'Deblending parent id', src.getId(), 'took', time.clock() - t0
327 
328  n1 = len(srcs)
329  self.log.info('Deblended: of %i sources, %i were deblended, creating %i children, total %i sources' %
330  (n0, nparents, n1-n0, n1))
331 
332  def preSingleDeblendHook(self, exposure, srcs, i, fp, psf, psf_fwhm, sigma1):
333  pass
334 
335  def postSingleDeblendHook(self, exposure, srcs, i, npre, kids, fp, psf, psf_fwhm, sigma1, res):
336  pass
337 
338 
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