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 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')
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')
138 doc=
"Deblending failed on source")
141 doc=
"Deblender skipped this source")
144 'deblend.ramped_template', type=
'Flag',
145 doc=(
'This source was near an image edge and the deblender used ' +
146 '"ramp" edge-handling.'))
149 'deblend.patched_template', type=
'Flag',
150 doc=(
'This source was near an image edge and the deblender used ' +
151 '"patched" edge-handling.'))
154 'deblend.has_stray_flux', type=
'Flag',
155 doc=(
'This source was assigned some stray flux'))
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')
171 doc=
"Deblending failed on source")
174 doc=
"Deblender skipped this source")
177 'deblend_rampedTemplate', type=
'Flag',
178 doc=(
'This source was near an image edge and the deblender used ' +
179 '"ramp" edge-handling.'))
182 'deblend_patchedTemplate', type=
'Flag',
183 doc=(
'This source was near an image edge and the deblender used ' +
184 '"patched" edge-handling.'))
187 'deblend_hasStrayFlux', type=
'Flag',
188 doc=(
'This source was assigned some stray flux'))
190 self.log.logdebug(
'Added keys to schema: %s' %
", ".join(str(x)
for x
in (
195 def run(self, exposure, sources, psf):
198 @param[in] exposure Exposure to process
199 @param[in,out] sources SourceCatalog containing sources detected on this exposure.
204 self.
deblend(exposure, sources, psf)
209 return psf.computeShape().getDeterminantRadius() * 2.35
215 @param[in] exposure Exposure to process
216 @param[in,out] srcs SourceCatalog containing sources detected on this exposure.
221 self.log.info(
"Deblending %d sources" % len(srcs))
227 mi = exposure.getMaskedImage()
229 sigma1 = math.sqrt(stats.getValue(afwMath.MEDIAN))
230 self.log.logdebug(
'sigma1: %g' % sigma1)
232 schema = srcs.getSchema()
236 for i,src
in enumerate(srcs):
239 fp = src.getFootprint()
244 toobig = ((self.config.maxFootprintArea > 0)
and
245 (fp.getArea() > self.config.maxFootprintArea))
249 self.log.logdebug(
'Parent %i: area %i > max %i; skipping' %
250 (int(src.getId()), fp.getArea(), self.config.maxFootprintArea))
257 self.log.logdebug(
'Parent %i: deblending %i peaks' % (int(src.getId()), len(pks)))
263 src.set(self.
tooManyPeaksKey, len(fp.getPeaks()) > self.config.maxNumberOfPeaks)
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,
283 except Exception
as e:
284 self.log.warn(
"Error deblending source %d: %s" % (src.getId(), e))
287 traceback.print_exc()
292 for j,peak
in enumerate(res.peaks):
295 self.log.logdebug(
'Skipping out-of-bounds peak at (%i,%i)' %
296 (pks[j].getIx(), pks[j].getIy()))
300 heavy = peak.getFluxPortion()
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)))
307 assert(len(heavy.getPeaks()) == 1)
310 child = srcs.addNew(); nchild += 1
311 child.setParent(src.getId())
312 child.setFootprint(heavy)
313 child.set(self.
psfKey, peak.deblendedAsPsf)
315 if peak.deblendedAsPsf:
316 (cx,cy) = peak.psfFitCenter
329 self.log.info(
'Deblended: of %i sources, %i were deblended, creating %i children, total %i sources' %
330 (n0, nparents, n1-n0, n1))
335 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.