34 __all__ =
'SourceDeblendConfig',
'SourceDeblendTask'
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',
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.',
47 strayFluxToPointSources = pexConf.ChoiceField(
48 doc=
'When the deblender should attribute stray flux to point sources',
49 dtype=str, default=
'necessary',
51 'necessary':
'When there is not an extended object in the footprint',
53 'never':
'Never; stray flux will not be attributed to any deblended child if the deblender thinks all peaks look like point sources',
57 findStrayFlux = pexConf.Field(dtype=bool, default=
True,
58 doc=
'Find stray flux---flux not claimed by any child in the deblender.')
60 assignStrayFlux = pexConf.Field(dtype=bool, default=
True,
61 doc=
'Assign stray flux to deblend children. Implies findStrayFlux.')
63 strayFluxRule = pexConf.ChoiceField(
64 doc=
'How to split flux among peaks',
65 dtype=str, default=
'r-to-peak',
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)' })
71 clipStrayFluxFraction = pexConf.Field(dtype=float, default=0.01,
72 doc=(
'When splitting stray flux, clip fractions below this value to zero.'))
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.'))
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.'))
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.'))
104 \anchor SourceDeblendTask_
106 \brief Split blended sources into individual sources.
108 This task has no return value; it only modifies the SourceCatalog in-place.
110 ConfigClass = SourceDeblendConfig
111 _DefaultName =
"sourceDeblend"
114 """Create the task, adding necessary fields to the given schema.
116 @param[in,out] schema Schema object for measurement fields; will be modified in-place.
117 @param **kwds Passed to Task.__init__.
119 pipeBase.Task.__init__(self, **kwargs)
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')
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')
137 doc=
"Deblending failed on source")
140 doc=
"Deblender skipped this source")
143 'deblend_rampedTemplate', type=
'Flag',
144 doc=(
'This source was near an image edge and the deblender used ' +
145 '"ramp" edge-handling.'))
148 'deblend_patchedTemplate', type=
'Flag',
149 doc=(
'This source was near an image edge and the deblender used ' +
150 '"patched" edge-handling.'))
153 'deblend_hasStrayFlux', type=
'Flag',
154 doc=(
'This source was assigned some stray flux'))
156 self.log.logdebug(
'Added keys to schema: %s' %
", ".join(str(x)
for x
in (
161 def run(self, exposure, sources, psf):
164 @param[in] exposure Exposure to process
165 @param[in,out] sources SourceCatalog containing sources detected on this exposure.
170 self.
deblend(exposure, sources, psf)
175 return psf.computeShape().getDeterminantRadius() * 2.35
181 @param[in] exposure Exposure to process
182 @param[in,out] srcs SourceCatalog containing sources detected on this exposure.
187 self.log.info(
"Deblending %d sources" % len(srcs))
193 mi = exposure.getMaskedImage()
195 sigma1 = math.sqrt(stats.getValue(afwMath.MEDIAN))
196 self.log.logdebug(
'sigma1: %g' % sigma1)
198 schema = srcs.getSchema()
202 for i,src
in enumerate(srcs):
205 fp = src.getFootprint()
210 toobig = ((self.config.maxFootprintArea > 0)
and
211 (fp.getArea() > self.config.maxFootprintArea))
215 self.log.logdebug(
'Parent %i: area %i > max %i; skipping' %
216 (int(src.getId()), fp.getArea(), self.config.maxFootprintArea))
223 self.log.logdebug(
'Parent %i: deblending %i peaks' % (int(src.getId()), len(pks)))
229 src.set(self.
tooManyPeaksKey, len(fp.getPeaks()) > self.config.maxNumberOfPeaks)
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,
249 except Exception
as e:
250 self.log.warn(
"Error deblending source %d: %s" % (src.getId(), e))
253 traceback.print_exc()
258 for j,peak
in enumerate(res.peaks):
261 self.log.logdebug(
'Skipping out-of-bounds peak at (%i,%i)' %
262 (pks[j].getIx(), pks[j].getIy()))
266 heavy = peak.getFluxPortion()
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)))
273 assert(len(heavy.getPeaks()) == 1)
276 child = srcs.addNew(); nchild += 1
277 child.setParent(src.getId())
278 child.setFootprint(heavy)
279 child.set(self.
psfKey, peak.deblendedAsPsf)
281 if peak.deblendedAsPsf:
282 (cx,cy) = peak.psfFitCenter
294 src.getFootprint().include([child.getFootprint()
for child
in kids])
302 self.log.info(
'Deblended: of %i sources, %i were deblended, creating %i children, total %i sources' %
303 (n0, nparents, n1-n0, n1))
308 def postSingleDeblendHook(self, exposure, srcs, i, npre, kids, fp, psf, psf_fwhm, sigma1, res):
deblendPatchedTemplateKey
Split blended sources into individual sources.
def postSingleDeblendHook
Statistics makeStatistics(afwImage::Mask< afwImage::MaskPixel > const &msk, int const flags, StatisticsControl const &sctrl)
Specialization to handle Masks.