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