lsst.meas.deblender  22.0.0-1-g7dab645+4d58640374
sourceDeblendTask.py
Go to the documentation of this file.
1 # This file is part of meas_deblender.
2 #
3 # Developed for the LSST Data Management System.
4 # This product includes software developed by the LSST Project
5 # (https://www.lsst.org).
6 # See the COPYRIGHT file at the top-level directory of this distribution
7 # for details of code ownership.
8 #
9 # This program is free software: you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation, either version 3 of the License, or
12 # (at your option) any later version.
13 #
14 # This program is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU General Public License for more details.
18 #
19 # You should have received a copy of the GNU General Public License
20 # along with this program. If not, see <https://www.gnu.org/licenses/>.
21 
22 import math
23 import numpy as np
24 
25 import lsst.log
26 import lsst.pex.config as pexConfig
27 import lsst.pipe.base as pipeBase
28 import lsst.afw.math as afwMath
29 import lsst.geom as geom
30 import lsst.afw.geom.ellipses as afwEll
31 import lsst.afw.image as afwImage
32 import lsst.afw.detection as afwDet
33 import lsst.afw.table as afwTable
34 
35 logger = lsst.log.Log.getLogger("meas.deblender.deblend")
36 
37 __all__ = 'SourceDeblendConfig', 'SourceDeblendTask'
38 
39 
40 class SourceDeblendConfig(pexConfig.Config):
41 
42  edgeHandling = pexConfig.ChoiceField(
43  doc='What to do when a peak to be deblended is close to the edge of the image',
44  dtype=str, default='ramp',
45  allowed={
46  'clip': 'Clip the template at the edge AND the mirror of the edge.',
47  'ramp': 'Ramp down flux at the image edge by the PSF',
48  'noclip': 'Ignore the edge when building the symmetric template.',
49  }
50  )
51 
52  strayFluxToPointSources = pexConfig.ChoiceField(
53  doc='When the deblender should attribute stray flux to point sources',
54  dtype=str, default='necessary',
55  allowed={
56  'necessary': 'When there is not an extended object in the footprint',
57  'always': 'Always',
58  'never': ('Never; stray flux will not be attributed to any deblended child '
59  'if the deblender thinks all peaks look like point sources'),
60  }
61  )
62 
63  assignStrayFlux = pexConfig.Field(dtype=bool, default=True,
64  doc='Assign stray flux (not claimed by any child in the deblender) '
65  'to deblend children.')
66 
67  strayFluxRule = pexConfig.ChoiceField(
68  doc='How to split flux among peaks',
69  dtype=str, default='trim',
70  allowed={
71  'r-to-peak': '~ 1/(1+R^2) to the peak',
72  'r-to-footprint': ('~ 1/(1+R^2) to the closest pixel in the footprint. '
73  'CAUTION: this can be computationally expensive on large footprints!'),
74  'nearest-footprint': ('Assign 100% to the nearest footprint (using L-1 norm aka '
75  'Manhattan distance)'),
76  'trim': ('Shrink the parent footprint to pixels that are not assigned to children')
77  }
78  )
79 
80  clipStrayFluxFraction = pexConfig.Field(dtype=float, default=0.001,
81  doc=('When splitting stray flux, clip fractions below '
82  'this value to zero.'))
83  psfChisq1 = pexConfig.Field(dtype=float, default=1.5, optional=False,
84  doc=('Chi-squared per DOF cut for deciding a source is '
85  'a PSF during deblending (un-shifted PSF model)'))
86  psfChisq2 = pexConfig.Field(dtype=float, default=1.5, optional=False,
87  doc=('Chi-squared per DOF cut for deciding a source is '
88  'PSF during deblending (shifted PSF model)'))
89  psfChisq2b = pexConfig.Field(dtype=float, default=1.5, optional=False,
90  doc=('Chi-squared per DOF cut for deciding a source is '
91  'a PSF during deblending (shifted PSF model #2)'))
92  maxNumberOfPeaks = pexConfig.Field(dtype=int, default=0,
93  doc=("Only deblend the brightest maxNumberOfPeaks peaks in the parent"
94  " (<= 0: unlimited)"))
95  maxFootprintArea = pexConfig.Field(dtype=int, default=1000000,
96  doc=("Maximum area for footprints before they are ignored as large; "
97  "non-positive means no threshold applied"))
98  maxFootprintSize = pexConfig.Field(dtype=int, default=0,
99  doc=("Maximum linear dimension for footprints before they are ignored "
100  "as large; non-positive means no threshold applied"))
101  minFootprintAxisRatio = pexConfig.Field(dtype=float, default=0.0,
102  doc=("Minimum axis ratio for footprints before they are ignored "
103  "as large; non-positive means no threshold applied"))
104  notDeblendedMask = pexConfig.Field(dtype=str, default="NOT_DEBLENDED", optional=True,
105  doc="Mask name for footprints not deblended, or None")
106 
107  tinyFootprintSize = pexConfig.RangeField(dtype=int, default=2, min=2, inclusiveMin=True,
108  doc=('Footprints smaller in width or height than this value '
109  'will be ignored; minimum of 2 due to PSF gradient '
110  'calculation.'))
111 
112  propagateAllPeaks = pexConfig.Field(dtype=bool, default=False,
113  doc=('Guarantee that all peaks produce a child source.'))
114  catchFailures = pexConfig.Field(
115  dtype=bool, default=False,
116  doc=("If True, catch exceptions thrown by the deblender, log them, "
117  "and set a flag on the parent, instead of letting them propagate up"))
118  maskPlanes = pexConfig.ListField(dtype=str, default=["SAT", "INTRP", "NO_DATA"],
119  doc="Mask planes to ignore when performing statistics")
120  maskLimits = pexConfig.DictField(
121  keytype=str,
122  itemtype=float,
123  default={},
124  doc=("Mask planes with the corresponding limit on the fraction of masked pixels. "
125  "Sources violating this limit will not be deblended."),
126  )
127  weightTemplates = pexConfig.Field(
128  dtype=bool, default=False,
129  doc=("If true, a least-squares fit of the templates will be done to the "
130  "full image. The templates will be re-weighted based on this fit."))
131  removeDegenerateTemplates = pexConfig.Field(dtype=bool, default=False,
132  doc=("Try to remove similar templates?"))
133  maxTempDotProd = pexConfig.Field(
134  dtype=float, default=0.5,
135  doc=("If the dot product between two templates is larger than this value, we consider them to be "
136  "describing the same object (i.e. they are degenerate). If one of the objects has been "
137  "labeled as a PSF it will be removed, otherwise the template with the lowest value will "
138  "be removed."))
139  medianSmoothTemplate = pexConfig.Field(dtype=bool, default=True,
140  doc="Apply a smoothing filter to all of the template images")
141 
142  # Testing options
143  # Some obs packages and ci packages run the full pipeline on a small
144  # subset of data to test that the pipeline is functioning properly.
145  # This is not meant as scientific validation, so it can be useful
146  # to only run on a small subset of the data that is large enough to
147  # test the desired pipeline features but not so long that the deblender
148  # is the tall pole in terms of execution times.
149  useCiLimits = pexConfig.Field(
150  dtype=bool, default=False,
151  doc="Limit the number of sources deblended for CI to prevent long build times")
152  ciDeblendChildRange = pexConfig.ListField(
153  dtype=int, default=[2, 10],
154  doc="Only deblend parent Footprints with a number of peaks in the (inclusive) range indicated."
155  "If `useCiLimits==False` then this parameter is ignored.")
156  ciNumParentsToDeblend = pexConfig.Field(
157  dtype=int, default=10,
158  doc="Only use the first `ciNumParentsToDeblend` parent footprints with a total peak count "
159  "within `ciDebledChildRange`. "
160  "If `useCiLimits==False` then this parameter is ignored.")
161 
162 
168 
169 
170 class SourceDeblendTask(pipeBase.Task):
171  """!
172  \anchor SourceDeblendTask_
173 
174  \brief Split blended sources into individual sources.
175 
176  This task has no return value; it only modifies the SourceCatalog in-place.
177  """
178  ConfigClass = SourceDeblendConfig
179  _DefaultName = "sourceDeblend"
180 
181  def __init__(self, schema, peakSchema=None, **kwargs):
182  """!
183  Create the task, adding necessary fields to the given schema.
184 
185  @param[in,out] schema Schema object for measurement fields; will be modified in-place.
186  @param[in] peakSchema Schema of Footprint Peaks that will be passed to the deblender.
187  Any fields beyond the PeakTable minimal schema will be transferred
188  to the main source Schema. If None, no fields will be transferred
189  from the Peaks.
190  @param[in] **kwargs Passed to Task.__init__.
191  """
192  pipeBase.Task.__init__(self, **kwargs)
193  self.schemaschema = schema
194  self.toCopyFromParenttoCopyFromParent = [item.key for item in self.schemaschema
195  if item.field.getName().startswith("merge_footprint")]
196  peakMinimalSchema = afwDet.PeakTable.makeMinimalSchema()
197  if peakSchema is None:
198  # In this case, the peakSchemaMapper will transfer nothing, but we'll still have one
199  # to simplify downstream code
200  self.peakSchemaMapperpeakSchemaMapper = afwTable.SchemaMapper(peakMinimalSchema, schema)
201  else:
202  self.peakSchemaMapperpeakSchemaMapper = afwTable.SchemaMapper(peakSchema, schema)
203  for item in peakSchema:
204  if item.key not in peakMinimalSchema:
205  self.peakSchemaMapperpeakSchemaMapper.addMapping(item.key, item.field)
206  # Because SchemaMapper makes a copy of the output schema you give its ctor, it isn't
207  # updating this Schema in place. That's probably a design flaw, but in the meantime,
208  # we'll keep that schema in sync with the peakSchemaMapper.getOutputSchema() manually,
209  # by adding the same fields to both.
210  schema.addField(item.field)
211  assert schema == self.peakSchemaMapperpeakSchemaMapper.getOutputSchema(), "Logic bug mapping schemas"
212  self.addSchemaKeysaddSchemaKeys(schema)
213 
214  def addSchemaKeys(self, schema):
215  self.nChildKeynChildKey = schema.addField('deblend_nChild', type=np.int32,
216  doc='Number of children this object has (defaults to 0)')
217  self.psfKeypsfKey = schema.addField('deblend_deblendedAsPsf', type='Flag',
218  doc='Deblender thought this source looked like a PSF')
219  self.psfCenterKeypsfCenterKey = afwTable.Point2DKey.addFields(schema, 'deblend_psfCenter',
220  'If deblended-as-psf, the PSF centroid', "pixel")
221  self.psfFluxKeypsfFluxKey = schema.addField('deblend_psf_instFlux', type='D',
222  doc='If deblended-as-psf, the instrumental PSF flux', units='count')
223  self.tooManyPeaksKeytooManyPeaksKey = schema.addField('deblend_tooManyPeaks', type='Flag',
224  doc='Source had too many peaks; '
225  'only the brightest were included')
226  self.tooBigKeytooBigKey = schema.addField('deblend_parentTooBig', type='Flag',
227  doc='Parent footprint covered too many pixels')
228  self.maskedKeymaskedKey = schema.addField('deblend_masked', type='Flag',
229  doc='Parent footprint was predominantly masked')
230 
231  if self.config.catchFailures:
232  self.deblendFailedKeydeblendFailedKey = schema.addField('deblend_failed', type='Flag',
233  doc="Deblending failed on source")
234 
235  self.deblendSkippedKeydeblendSkippedKey = schema.addField('deblend_skipped', type='Flag',
236  doc="Deblender skipped this source")
237 
238  self.deblendRampedTemplateKeydeblendRampedTemplateKey = schema.addField(
239  'deblend_rampedTemplate', type='Flag',
240  doc=('This source was near an image edge and the deblender used '
241  '"ramp" edge-handling.'))
242 
243  self.deblendPatchedTemplateKeydeblendPatchedTemplateKey = schema.addField(
244  'deblend_patchedTemplate', type='Flag',
245  doc=('This source was near an image edge and the deblender used '
246  '"patched" edge-handling.'))
247 
248  self.hasStrayFluxKeyhasStrayFluxKey = schema.addField(
249  'deblend_hasStrayFlux', type='Flag',
250  doc=('This source was assigned some stray flux'))
251 
252  self.log.trace('Added keys to schema: %s', ", ".join(str(x) for x in (
253  self.nChildKeynChildKey, self.psfKeypsfKey, self.psfCenterKeypsfCenterKey, self.psfFluxKeypsfFluxKey,
254  self.tooManyPeaksKeytooManyPeaksKey, self.tooBigKeytooBigKey)))
255  self.peakCenterpeakCenter = afwTable.Point2IKey.addFields(schema, name="deblend_peak_center",
256  doc="Center used to apply constraints in scarlet",
257  unit="pixel")
258  self.peakIdKeypeakIdKey = schema.addField("deblend_peakId", type=np.int32,
259  doc="ID of the peak in the parent footprint. "
260  "This is not unique, but the combination of 'parent'"
261  "and 'peakId' should be for all child sources. "
262  "Top level blends with no parents have 'peakId=0'")
263  self.nPeaksKeynPeaksKey = schema.addField("deblend_nPeaks", type=np.int32,
264  doc="Number of initial peaks in the blend. "
265  "This includes peaks that may have been culled "
266  "during deblending or failed to deblend")
267  self.parentNPeaksKeyparentNPeaksKey = schema.addField("deblend_parentNPeaks", type=np.int32,
268  doc="Same as deblend_n_peaks, but the number of peaks "
269  "in the parent footprint")
270 
271  @pipeBase.timeMethod
272  def run(self, exposure, sources):
273  """!
274  Get the psf from the provided exposure and then run deblend().
275 
276  @param[in] exposure Exposure to process
277  @param[in,out] sources SourceCatalog containing sources detected on this exposure.
278 
279  @return None
280  """
281  psf = exposure.getPsf()
282  assert sources.getSchema() == self.schemaschema
283  self.deblenddeblend(exposure, sources, psf)
284 
285  def _getPsfFwhm(self, psf, bbox):
286  # It should be easier to get a PSF's fwhm;
287  # https://dev.lsstcorp.org/trac/ticket/3030
288  return psf.computeShape().getDeterminantRadius() * 2.35
289 
290  @pipeBase.timeMethod
291  def deblend(self, exposure, srcs, psf):
292  """!
293  Deblend.
294 
295  @param[in] exposure Exposure to process
296  @param[in,out] srcs SourceCatalog containing sources detected on this exposure.
297  @param[in] psf PSF
298 
299  @return None
300  """
301  # Cull footprints if required by ci
302  if self.config.useCiLimits:
303  self.log.info(f"Using CI catalog limits, "
304  f"the original number of sources to deblend was {len(srcs)}.")
305  # Select parents with a number of children in the range
306  # config.ciDeblendChildRange
307  minChildren, maxChildren = self.config.ciDeblendChildRange
308  nPeaks = np.array([len(src.getFootprint().peaks) for src in srcs])
309  childrenInRange = np.where((nPeaks >= minChildren) & (nPeaks <= maxChildren))[0]
310  if len(childrenInRange) < self.config.ciNumParentsToDeblend:
311  raise ValueError("Fewer than ciNumParentsToDeblend children were contained in the range "
312  "indicated by ciDeblendChildRange. Adjust this range to include more "
313  "parents.")
314  # Keep all of the isolated parents and the first
315  # `ciNumParentsToDeblend` children
316  parents = nPeaks == 1
317  children = np.zeros((len(srcs),), dtype=bool)
318  children[childrenInRange[:self.config.ciNumParentsToDeblend]] = True
319  srcs = srcs[parents | children]
320  # We need to update the IdFactory, otherwise the the source ids
321  # will not be sequential
322  idFactory = srcs.getIdFactory()
323  maxId = np.max(srcs["id"])
324  idFactory.notify(maxId)
325 
326  self.log.info("Deblending %d sources" % len(srcs))
327 
328  from lsst.meas.deblender.baseline import deblend
329 
330  # find the median stdev in the image...
331  mi = exposure.getMaskedImage()
332  statsCtrl = afwMath.StatisticsControl()
333  statsCtrl.setAndMask(mi.getMask().getPlaneBitMask(self.config.maskPlanes))
334  stats = afwMath.makeStatistics(mi.getVariance(), mi.getMask(), afwMath.MEDIAN, statsCtrl)
335  sigma1 = math.sqrt(stats.getValue(afwMath.MEDIAN))
336  self.log.trace('sigma1: %g', sigma1)
337 
338  n0 = len(srcs)
339  nparents = 0
340  for i, src in enumerate(srcs):
341  # t0 = time.clock()
342 
343  fp = src.getFootprint()
344  pks = fp.getPeaks()
345 
346  # Since we use the first peak for the parent object, we should propagate its flags
347  # to the parent source.
348  src.assign(pks[0], self.peakSchemaMapperpeakSchemaMapper)
349 
350  if len(pks) < 2:
351  continue
352 
353  if self.isLargeFootprintisLargeFootprint(fp):
354  src.set(self.tooBigKeytooBigKey, True)
355  self.skipParentskipParent(src, mi.getMask())
356  self.log.warn('Parent %i: skipping large footprint (area: %i)',
357  int(src.getId()), int(fp.getArea()))
358  continue
359  if self.isMaskedisMasked(fp, exposure.getMaskedImage().getMask()):
360  src.set(self.maskedKeymaskedKey, True)
361  self.skipParentskipParent(src, mi.getMask())
362  self.log.warn('Parent %i: skipping masked footprint (area: %i)',
363  int(src.getId()), int(fp.getArea()))
364  continue
365 
366  nparents += 1
367  bb = fp.getBBox()
368  psf_fwhm = self._getPsfFwhm_getPsfFwhm(psf, bb)
369 
370  self.log.trace('Parent %i: deblending %i peaks', int(src.getId()), len(pks))
371 
372  self.preSingleDeblendHookpreSingleDeblendHook(exposure, srcs, i, fp, psf, psf_fwhm, sigma1)
373  npre = len(srcs)
374 
375  # This should really be set in deblend, but deblend doesn't have access to the src
376  src.set(self.tooManyPeaksKeytooManyPeaksKey, len(fp.getPeaks()) > self.config.maxNumberOfPeaks)
377 
378  try:
379  res = deblend(
380  fp, mi, psf, psf_fwhm, sigma1=sigma1,
381  psfChisqCut1=self.config.psfChisq1,
382  psfChisqCut2=self.config.psfChisq2,
383  psfChisqCut2b=self.config.psfChisq2b,
384  maxNumberOfPeaks=self.config.maxNumberOfPeaks,
385  strayFluxToPointSources=self.config.strayFluxToPointSources,
386  assignStrayFlux=self.config.assignStrayFlux,
387  strayFluxAssignment=self.config.strayFluxRule,
388  rampFluxAtEdge=(self.config.edgeHandling == 'ramp'),
389  patchEdges=(self.config.edgeHandling == 'noclip'),
390  tinyFootprintSize=self.config.tinyFootprintSize,
391  clipStrayFluxFraction=self.config.clipStrayFluxFraction,
392  weightTemplates=self.config.weightTemplates,
393  removeDegenerateTemplates=self.config.removeDegenerateTemplates,
394  maxTempDotProd=self.config.maxTempDotProd,
395  medianSmoothTemplate=self.config.medianSmoothTemplate
396  )
397  if self.config.catchFailures:
398  src.set(self.deblendFailedKeydeblendFailedKey, False)
399  except Exception as e:
400  if self.config.catchFailures:
401  self.log.warn("Unable to deblend source %d: %s" % (src.getId(), e))
402  src.set(self.deblendFailedKeydeblendFailedKey, True)
403  import traceback
404  traceback.print_exc()
405  continue
406  else:
407  raise
408 
409  kids = []
410  nchild = 0
411  for j, peak in enumerate(res.deblendedParents[0].peaks):
412  heavy = peak.getFluxPortion()
413  if heavy is None or peak.skip:
414  src.set(self.deblendSkippedKeydeblendSkippedKey, True)
415  if not self.config.propagateAllPeaks:
416  # Don't care
417  continue
418  # We need to preserve the peak: make sure we have enough info to create a minimal
419  # child src
420  self.log.trace("Peak at (%i,%i) failed. Using minimal default info for child.",
421  pks[j].getIx(), pks[j].getIy())
422  if heavy is None:
423  # copy the full footprint and strip out extra peaks
424  foot = afwDet.Footprint(src.getFootprint())
425  peakList = foot.getPeaks()
426  peakList.clear()
427  peakList.append(peak.peak)
428  zeroMimg = afwImage.MaskedImageF(foot.getBBox())
429  heavy = afwDet.makeHeavyFootprint(foot, zeroMimg)
430  if peak.deblendedAsPsf:
431  if peak.psfFitFlux is None:
432  peak.psfFitFlux = 0.0
433  if peak.psfFitCenter is None:
434  peak.psfFitCenter = (peak.peak.getIx(), peak.peak.getIy())
435 
436  assert(len(heavy.getPeaks()) == 1)
437 
438  src.set(self.deblendSkippedKeydeblendSkippedKey, False)
439  child = srcs.addNew()
440  nchild += 1
441  for key in self.toCopyFromParenttoCopyFromParent:
442  child.set(key, src.get(key))
443  child.assign(heavy.getPeaks()[0], self.peakSchemaMapperpeakSchemaMapper)
444  child.setParent(src.getId())
445  child.setFootprint(heavy)
446  child.set(self.psfKeypsfKey, peak.deblendedAsPsf)
447  child.set(self.hasStrayFluxKeyhasStrayFluxKey, peak.strayFlux is not None)
448  if peak.deblendedAsPsf:
449  (cx, cy) = peak.psfFitCenter
450  child.set(self.psfCenterKeypsfCenterKey, geom.Point2D(cx, cy))
451  child.set(self.psfFluxKeypsfFluxKey, peak.psfFitFlux)
452  child.set(self.deblendRampedTemplateKeydeblendRampedTemplateKey, peak.hasRampedTemplate)
453  child.set(self.deblendPatchedTemplateKeydeblendPatchedTemplateKey, peak.patched)
454 
455  # Set the position of the peak from the parent footprint
456  # This will make it easier to match the same source across
457  # deblenders and across observations, where the peak
458  # position is unlikely to change unless enough time passes
459  # for a source to move on the sky.
460  child.set(self.peakCenterpeakCenter, geom.Point2I(pks[j].getIx(), pks[j].getIy()))
461  child.set(self.peakIdKeypeakIdKey, pks[j].getId())
462 
463  # The children have a single peak
464  child.set(self.nPeaksKeynPeaksKey, 1)
465  # Set the number of peaks in the parent
466  child.set(self.parentNPeaksKeyparentNPeaksKey, len(pks))
467 
468  kids.append(child)
469 
470  # Child footprints may extend beyond the full extent of their parent's which
471  # results in a failure of the replace-by-noise code to reinstate these pixels
472  # to their original values. The following updates the parent footprint
473  # in-place to ensure it contains the full union of itself and all of its
474  # children's footprints.
475  spans = src.getFootprint().spans
476  for child in kids:
477  spans = spans.union(child.getFootprint().spans)
478  src.getFootprint().setSpans(spans)
479 
480  src.set(self.nChildKeynChildKey, nchild)
481 
482  self.postSingleDeblendHookpostSingleDeblendHook(exposure, srcs, i, npre, kids, fp, psf, psf_fwhm, sigma1, res)
483  # print('Deblending parent id', src.getId(), 'took', time.clock() - t0)
484 
485  n1 = len(srcs)
486  self.log.info('Deblended: of %i sources, %i were deblended, creating %i children, total %i sources'
487  % (n0, nparents, n1-n0, n1))
488 
489  def preSingleDeblendHook(self, exposure, srcs, i, fp, psf, psf_fwhm, sigma1):
490  pass
491 
492  def postSingleDeblendHook(self, exposure, srcs, i, npre, kids, fp, psf, psf_fwhm, sigma1, res):
493  pass
494 
495  def isLargeFootprint(self, footprint):
496  """Returns whether a Footprint is large
497 
498  'Large' is defined by thresholds on the area, size and axis ratio.
499  These may be disabled independently by configuring them to be non-positive.
500 
501  This is principally intended to get rid of satellite streaks, which the
502  deblender or other downstream processing can have trouble dealing with
503  (e.g., multiple large HeavyFootprints can chew up memory).
504  """
505  if self.config.maxFootprintArea > 0 and footprint.getArea() > self.config.maxFootprintArea:
506  return True
507  if self.config.maxFootprintSize > 0:
508  bbox = footprint.getBBox()
509  if max(bbox.getWidth(), bbox.getHeight()) > self.config.maxFootprintSize:
510  return True
511  if self.config.minFootprintAxisRatio > 0:
512  axes = afwEll.Axes(footprint.getShape())
513  if axes.getB() < self.config.minFootprintAxisRatio*axes.getA():
514  return True
515  return False
516 
517  def isMasked(self, footprint, mask):
518  """Returns whether the footprint violates the mask limits"""
519  size = float(footprint.getArea())
520  for maskName, limit in self.config.maskLimits.items():
521  maskVal = mask.getPlaneBitMask(maskName)
522  unmaskedSpan = footprint.spans.intersectNot(mask, maskVal) # spanset of unmasked pixels
523  if (size - unmaskedSpan.getArea())/size > limit:
524  return True
525  return False
526 
527  def skipParent(self, source, mask):
528  """Indicate that the parent source is not being deblended
529 
530  We set the appropriate flags and mask.
531 
532  @param source The source to flag as skipped
533  @param mask The mask to update
534  """
535  fp = source.getFootprint()
536  source.set(self.deblendSkippedKeydeblendSkippedKey, True)
537  if self.config.notDeblendedMask:
538  mask.addMaskPlane(self.config.notDeblendedMask)
539  fp.spans.setMask(mask, mask.getPlaneBitMask(self.config.notDeblendedMask))
540 
541  # Set the center of the parent
542  bbox = fp.getBBox()
543  centerX = int(bbox.getMinX()+bbox.getWidth()/2)
544  centerY = int(bbox.getMinY()+bbox.getHeight()/2)
545  source.set(self.peakCenterpeakCenter, geom.Point2I(centerX, centerY))
546  # There are no deblended children, so nChild = 0
547  source.set(self.nChildKeynChildKey, 0)
548  # But we also want to know how many peaks that we would have
549  # deblended if the parent wasn't skipped.
550  source.set(self.nPeaksKeynPeaksKey, len(fp.peaks))
551  # Top level parents are not a detected peak, so they have no peakId
552  source.set(self.peakIdKeypeakIdKey, 0)
553  # Top level parents also have no parentNPeaks
554  source.set(self.parentNPeaksKeyparentNPeaksKey, 0)
static Log getLogger(std::string const &loggername)
def postSingleDeblendHook(self, exposure, srcs, i, npre, kids, fp, psf, psf_fwhm, sigma1, res)
def preSingleDeblendHook(self, exposure, srcs, i, fp, psf, psf_fwhm, sigma1)
def __init__(self, schema, peakSchema=None, **kwargs)
Create the task, adding necessary fields to the given schema.
def run(self, exposure, sources)
Get the psf from the provided exposure and then run deblend().