LSSTApplications  11.0-13-gbb96280,12.1.rc1,12.1.rc1+1,12.1.rc1+2,12.1.rc1+5,12.1.rc1+8,12.1.rc1-1-g06d7636+1,12.1.rc1-1-g253890b+5,12.1.rc1-1-g3d31b68+7,12.1.rc1-1-g3db6b75+1,12.1.rc1-1-g5c1385a+3,12.1.rc1-1-g83b2247,12.1.rc1-1-g90cb4cf+6,12.1.rc1-1-g91da24b+3,12.1.rc1-2-g3521f8a,12.1.rc1-2-g39433dd+4,12.1.rc1-2-g486411b+2,12.1.rc1-2-g4c2be76,12.1.rc1-2-gc9c0491,12.1.rc1-2-gda2cd4f+6,12.1.rc1-3-g3391c73+2,12.1.rc1-3-g8c1bd6c+1,12.1.rc1-3-gcf4b6cb+2,12.1.rc1-4-g057223e+1,12.1.rc1-4-g19ed13b+2,12.1.rc1-4-g30492a7
LSSTDataManagementBasePackage
dipoleMeasurement.py
Go to the documentation of this file.
1 #
2 # LSST Data Management System
3 # Copyright 2008-2016 AURA/LSST.
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 <https://www.lsstcorp.org/LegalNotices/>.
21 #
22 import numpy as np
23 import lsst.afw.geom as afwGeom
24 import lsst.afw.image as afwImage
25 import lsst.afw.detection as afwDetect
26 import lsst.pex.config as pexConfig
27 from lsst.log import Log
28 import lsst.meas.deblender.baseline as deblendBaseline
29 from lsst.meas.base.pluginRegistry import register
30 from lsst.meas.base import SingleFrameMeasurementTask, SingleFrameMeasurementConfig, \
31  SingleFramePluginConfig, SingleFramePlugin
32 import lsst.afw.display.ds9 as ds9
33 
34 __all__ = ("DipoleMeasurementConfig", "DipoleMeasurementTask", "DipoleAnalysis", "DipoleDeblender",
35  "SourceFlagChecker", "ClassificationDipoleConfig", "ClassificationDipolePlugin")
36 
37 
38 class ClassificationDipoleConfig(SingleFramePluginConfig):
39  """Configuration for classification of detected diaSources as dipole or not"""
40  minSn = pexConfig.Field(
41  doc="Minimum quadrature sum of positive+negative lobe S/N to be considered a dipole",
42  dtype=float, default=np.sqrt(2) * 5.0,
43  )
44  maxFluxRatio = pexConfig.Field(
45  doc="Maximum flux ratio in either lobe to be considered a dipole",
46  dtype = float, default = 0.65
47  )
48 
49 
50 @register("ip_diffim_ClassificationDipole")
51 class ClassificationDipolePlugin(SingleFramePlugin):
52  """A plugin to classify whether a diaSource is a dipole.
53  """
54 
55  ConfigClass = ClassificationDipoleConfig
56 
57  @classmethod
59  return cls.APCORR_ORDER
60 
61  def __init__(self, config, name, schema, metadata):
62  SingleFramePlugin.__init__(self, config, name, schema, metadata)
64  self.keyProbability = schema.addField(name + "_value", type="D",
65  doc="Set to 1 for dipoles, else 0.")
66  self.keyFlag = schema.addField(name + "_flag", type="Flag", doc="Set to 1 for any fatal failure.")
67 
68  def measure(self, measRecord, exposure):
69  passesSn = self.dipoleAnalysis.getSn(measRecord) > self.config.minSn
70  negFlux = np.abs(measRecord.get("ip_diffim_PsfDipoleFlux_neg_flux"))
71  negFluxFlag = measRecord.get("ip_diffim_PsfDipoleFlux_neg_flag")
72  posFlux = np.abs(measRecord.get("ip_diffim_PsfDipoleFlux_pos_flux"))
73  posFluxFlag = measRecord.get("ip_diffim_PsfDipoleFlux_pos_flag")
74 
75  if negFluxFlag or posFluxFlag:
76  self.fail(measRecord)
77  # continue on to classify
78 
79  totalFlux = negFlux + posFlux
80 
81  # If negFlux or posFlux are NaN, these evaluate to False
82  passesFluxNeg = (negFlux / totalFlux) < self.config.maxFluxRatio
83  passesFluxPos = (posFlux / totalFlux) < self.config.maxFluxRatio
84  if (passesSn and passesFluxPos and passesFluxNeg):
85  val = 1.0
86  else:
87  val = 0.0
88 
89  measRecord.set(self.keyProbability, val)
90 
91  def fail(self, measRecord, error=None):
92  measRecord.set(self.keyFlag, True)
93 
94 
95 class DipoleMeasurementConfig(SingleFrameMeasurementConfig):
96  """!Measurement of detected diaSources as dipoles"""
97 
98  def setDefaults(self):
99  SingleFrameMeasurementConfig.setDefaults(self)
100  self.plugins = ["base_CircularApertureFlux",
101  "base_PixelFlags",
102  "base_SkyCoord",
103  "base_PsfFlux",
104  "ip_diffim_NaiveDipoleCentroid",
105  "ip_diffim_NaiveDipoleFlux",
106  "ip_diffim_PsfDipoleFlux",
107  "ip_diffim_ClassificationDipole",
108  ]
109 
110  self.slots.calibFlux = None
111  self.slots.modelFlux = None
112  self.slots.instFlux = None
113  self.slots.shape = None
114  self.slots.centroid = "ip_diffim_NaiveDipoleCentroid"
115  self.doReplaceWithNoise = False
116 
117 ## \addtogroup LSST_task_documentation
118 ## \{
119 ## \page DipoleMeasurementTask
120 ## \ref DipoleMeasurementTask_ "DipoleMeasurementTask"
121 ## \copybrief DipoleMeasurementTask
122 ## \}
123 class DipoleMeasurementTask(SingleFrameMeasurementTask):
124  """!
125 \anchor DipoleMeasurementTask_
126 
127 \brief Measurement of Sources, specifically ones from difference images, for characterization as dipoles
128 
129 \section ip_diffim_dipolemeas_Contents Contents
130 
131  - \ref ip_diffim_dipolemeas_Purpose
132  - \ref ip_diffim_dipolemeas_Initialize
133  - \ref ip_diffim_dipolemeas_IO
134  - \ref ip_diffim_dipolemeas_Config
135  - \ref ip_diffim_dipolemeas_Metadata
136  - \ref ip_diffim_dipolemeas_Debug
137  - \ref ip_diffim_dipolemeas_Example
138 
139 #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
140 
141 \section ip_diffim_dipolemeas_Purpose Description
142 
143 This class provides a default configuration for running Source measurement on image differences.
144 
145 These default plugins include:
146 \dontinclude dipoleMeasurement.py
147 \skip class DipoleMeasurementConfig
148 \until self.doReplaceWithNoise
149 
150 These plugins enabled by default allow the user to test the hypothesis that the Source is a dipole.
151 This includes a set of measurements derived from intermediate base classes
152 DipoleCentroidAlgorithm and DipoleFluxAlgorithm. Their respective algorithm control classes are defined in
153 DipoleCentroidControl and DipoleFluxControl. Each centroid and flux measurement will have _neg (negative)
154 and _pos (positive lobe) fields.
155 
156 The first set of measurements uses a "naive" alrogithm for centroid and flux measurements, implemented in
157 NaiveDipoleCentroidControl and NaiveDipoleFluxControl. The algorithm uses a naive 3x3 weighted moment around
158 the nominal centroids of each peak in the Source Footprint. These algorithms fill the table fields
159 ip_diffim_NaiveDipoleCentroid* and ip_diffim_NaiveDipoleFlux*
160 
161 The second set of measurements undertakes a joint-Psf model on the negative and positive lobe simultaneously.
162 This fit simultaneously solves for the negative and positive lobe centroids and fluxes using non-linear
163 least squares minimization. The fields are stored in table elements ip_diffim_PsfDipoleFlux*.
164 
165 Because this Task is just a config for SourceMeasurementTask, the same result may be acheived by manually
166 editing the config and running SourceMeasurementTask. For example:
167 
168 \code
169 config = SingleFrameMeasurementConfig()
170 config.plugins.names = ["base_PsfFlux",
171  "ip_diffim_PsfDipoleFlux",
172  "ip_diffim_NaiveDipoleFlux",
173  "ip_diffim_NaiveDipoleCentroid",
174  "ip_diffim_ClassificationDipole",
175  "base_CircularApertureFlux",
176  "base_SkyCoord"]
177 
178 config.slots.calibFlux = None
179 config.slots.modelFlux = None
180 config.slots.instFlux = None
181 config.slots.shape = None
182 config.slots.centroid = "ip_diffim_NaiveDipoleCentroid"
183 config.doReplaceWithNoise = False
184 
185 schema = afwTable.SourceTable.makeMinimalSchema()
186 task = SingleFrameMeasurementTask(schema, config=config)
187 
188 task.run(sources, exposure)
189 \endcode
190 
191 
192 #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
193 
194 \section ip_diffim_dipolemeas_Initialize Task initialization
195 
196 \copydoc \_\_init\_\_
197 
198 #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
199 
200 \section ip_diffim_dipolemeas_IO Invoking the Task
201 
202 \copydoc run
203 
204 #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
205 
206 \section ip_diffim_dipolemeas_Config Configuration parameters
207 
208 See \ref DipoleMeasurementConfig
209 
210 #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
211 
212 \section ip_diffim_dipolemeas_Metadata Quantities set in Metadata
213 
214 No specific values are set in the Task metadata. However, the Source schema are modified to store the
215 results of the dipole-specific measurements.
216 
217 
218 #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
219 
220 \section ip_diffim_dipolemeas_Debug Debug variables
221 
222 The \link lsst.pipe.base.cmdLineTask.CmdLineTask command line task\endlink interface supports a
223 flag \c -d/--debug to import \b debug.py from your \c PYTHONPATH. The relevant contents of debug.py
224 for this Task include:
225 
226 \code{.py}
227  import sys
228  import lsstDebug
229  def DebugInfo(name):
230  di = lsstDebug.getInfo(name)
231  if name == "lsst.ip.diffim.dipoleMeasurement":
232  di.display = True # enable debug output
233  di.maskTransparency = 90 # ds9 mask transparency
234  di.displayDiaSources = True # show exposure with dipole results
235  return di
236  lsstDebug.Info = DebugInfo
237  lsstDebug.frame = 1
238 \endcode
239 
240 #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
241 
242 \section ip_diffim_dipolemeas_Example A complete example of using DipoleMeasurementTask
243 
244 This code is dipoleMeasTask.py in the examples directory, and can be run as \em e.g.
245 \code
246 examples/dipoleMeasTask.py
247 examples/dipoleMeasTask.py --debug
248 examples/dipoleMeasTask.py --debug --image /path/to/image.fits
249 \endcode
250 
251 \dontinclude dipoleMeasTask.py
252 Start the processing by parsing the command line, where the user has the option of enabling debugging output
253 and/or sending their own image for demonstration (in case they have not downloaded the afwdata package).
254 \skip main
255 \until run
256 
257 \dontinclude dipoleMeasTask.py
258 The processing occurs in the run function. We first extract an exposure from disk or afwdata, displaying
259 it if requested:
260 \skip args
261 \until mtv
262 
263 Create a default source schema that we will append fields to as we add more algorithms:
264 \skip makeMinimalSchema
265 \until makeMinimalSchema
266 
267 Create the detection and measurement Tasks, with some minor tweaking of their configs:
268 \skip Create
269 \until measureTask
270 
271 Having fully initialied the schema, we create a Source table from it:
272 \skip output
273 \until SourceTable
274 
275 Run detection:
276 \skip Process
277 \until detectionTask
278 
279 Because we are looking for dipoles, we need to merge the positive and negative detections:
280 \skip Merge
281 \until numNeg
282 
283 Finally, perform measurement (both standard and dipole-specialized) on the merged sources:
284 \skip measureTask
285 \until measureTask
286 
287 Optionally display debugging information:
288 \skip Display
289 \until displayDipoles
290 #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
291 
292  """
293  ConfigClass = DipoleMeasurementConfig
294  _DefaultName = "dipoleMeasurement"
295 
296 
297 #########
298 # Other Support classs
299 #########
300 
301 class SourceFlagChecker(object):
302  """!Functor class to check whether a diaSource has flags set that should cause it to be labeled bad."""
303  def __init__(self, sources, badFlags=None):
304  """!Constructor
305 
306  @param sources Sources that will be measured
307  @param badFlags A list of flags that will be used to determine if there was a measurement problem
308 
309  The list of badFlags will be used to make a list of keys to check for measurement flags on. By
310  default the centroid keys are added to this list"""
311 
312  self.badFlags = ['base_PixelFlags_flag_edge', 'base_PixelFlags_flag_interpolatedCenter',
313  'base_PixelFlags_flag_saturatedCenter']
314  if badFlags is not None:
315  for flag in badFlags:
316  self.badFlags.append(flag)
317  self.keys = [sources.getSchema().find(name).key for name in self.badFlags]
318  self.keys.append(sources.table.getCentroidFlagKey())
319 
320  def __call__(self, source):
321  """!Call the source flag checker on a single Source
322 
323  @param source Source that will be examined"""
324  for k in self.keys:
325  if source.get(k):
326  return False
327  return True
328 
329 class DipoleAnalysis(object):
330  """!Functor class that provides (S/N, position, orientation) of measured dipoles"""
331  def __init__(self):
332  """!Constructor"""
333  pass
334 
335  def __call__(self, source):
336  """!Parse information returned from dipole measurement
337 
338  @param source The source that will be examined"""
339  return self.getSn(source), self.getCentroid(source), self.getOrientation(source)
340 
341  def getSn(self, source):
342  """!Get the total signal-to-noise of the dipole; total S/N is from positive and negative lobe
343 
344  @param source The source that will be examined"""
345 
346  posflux = source.get("ip_diffim_PsfDipoleFlux_pos_flux")
347  posfluxErr = source.get("ip_diffim_PsfDipoleFlux_pos_fluxSigma")
348  negflux = source.get("ip_diffim_PsfDipoleFlux_neg_flux")
349  negfluxErr = source.get("ip_diffim_PsfDipoleFlux_neg_fluxSigma")
350 
351  # Not a dipole!
352  if (posflux < 0) is (negflux < 0):
353  return 0
354 
355  return np.sqrt((posflux/posfluxErr)**2 + (negflux/negfluxErr)**2)
356 
357  def getCentroid(self, source):
358  """!Get the centroid of the dipole; average of positive and negative lobe
359 
360  @param source The source that will be examined"""
361 
362  negCenX = source.get("ip_diffim_PsfDipoleFlux_neg_centroid_x")
363  negCenY = source.get("ip_diffim_PsfDipoleFlux_neg_centroid_y")
364  posCenX = source.get("ip_diffim_PsfDipoleFlux_pos_centroid_x")
365  posCenY = source.get("ip_diffim_PsfDipoleFlux_pos_centroid_y")
366  if (np.isinf(negCenX) or np.isinf(negCenY) or np.isinf(posCenX) or np.isinf(posCenY)):
367  return None
368 
369  center = afwGeom.Point2D(0.5*(negCenX+posCenX),
370  0.5*(negCenY+posCenY))
371  return center
372 
373  def getOrientation(self, source):
374  """!Calculate the orientation of dipole; vector from negative to positive lobe
375 
376  @param source The source that will be examined"""
377 
378  negCenX = source.get("ip_diffim_PsfDipoleFlux_neg_centroid_x")
379  negCenY = source.get("ip_diffim_PsfDipoleFlux_neg_centroid_y")
380  posCenX = source.get("ip_diffim_PsfDipoleFlux_pos_centroid_x")
381  posCenY = source.get("ip_diffim_PsfDipoleFlux_pos_centroid_y")
382  if (np.isinf(negCenX) or np.isinf(negCenY) or np.isinf(posCenX) or np.isinf(posCenY)):
383  return None
384 
385  dx, dy = posCenX-negCenX, posCenY-negCenY
386  angle = afwGeom.Angle(np.arctan2(dx, dy), afwGeom.radians)
387  return angle
388 
389  def displayDipoles(self, exposure, sources):
390  """!Display debugging information on the detected dipoles
391 
392  @param exposure Image the dipoles were measured on
393  @param sources The set of diaSources that were measured"""
394 
395  import lsstDebug
396  display = lsstDebug.Info(__name__).display
397  displayDiaSources = lsstDebug.Info(__name__).displayDiaSources
398  maskTransparency = lsstDebug.Info(__name__).maskTransparency
399  if not maskTransparency:
400  maskTransparency = 90
401  ds9.setMaskTransparency(maskTransparency)
402  ds9.mtv(exposure, frame=lsstDebug.frame)
403 
404  if display and displayDiaSources:
405  with ds9.Buffering():
406  for source in sources:
407  cenX, cenY = source.get("ipdiffim_DipolePsfFlux_centroid")
408  if np.isinf(cenX) or np.isinf(cenY):
409  cenX, cenY = source.getCentroid()
410 
411  isdipole = source.get("classification.dipole")
412  if isdipole and np.isfinite(isdipole):
413  # Dipole
414  ctype= "green"
415  else:
416  # Not dipole
417  ctype = "red"
418 
419  ds9.dot("o", cenX, cenY, size=2, ctype=ctype, frame=lsstDebug.frame)
420 
421  negCenX = source.get("ip_diffim_PsfDipoleFlux_neg_centroid_x")
422  negCenY = source.get("ip_diffim_PsfDipoleFlux_neg_centroid_y")
423  posCenX = source.get("ip_diffim_PsfDipoleFlux_pos_centroid_x")
424  posCenY = source.get("ip_diffim_PsfDipoleFlux_pos_centroid_y")
425  if (np.isinf(negCenX) or np.isinf(negCenY) or np.isinf(posCenX) or np.isinf(posCenY)):
426  continue
427 
428  ds9.line([(negCenX, negCenY), (posCenX, posCenY)], ctype="yellow", frame=lsstDebug.frame)
429 
430  lsstDebug.frame += 1
431 
432 
433 
434 class DipoleDeblender(object):
435  """!Functor to deblend a source as a dipole, and return a new source with deblended footprints.
436 
437  This necessarily overrides some of the functionality from
438  meas_algorithms/python/lsst/meas/algorithms/deblend.py since we
439  need a single source that contains the blended peaks, not
440  multiple children sources. This directly calls the core
441  deblending code deblendBaseline.deblend (optionally _fitPsf for
442  debugging).
443 
444  Not actively being used, but there is a unit test for it in
445  dipoleAlgorithm.py.
446  """
447  def __init__(self):
448  # Set up defaults to send to deblender
449 
450  # Always deblend as Psf
451  self.psfChisqCut1 = self.psfChisqCut2 = self.psfChisqCut2b = np.inf
452  self.log = Log.getLogger('lsst.ip.diffim.DipoleDeblender')
453  self.log.setLevel(Log.INFO)
454  self.sigma2fwhm = 2. * np.sqrt(2. * np.log(2.))
455 
456  def __call__(self, source, exposure):
457  fp = source.getFootprint()
458  peaks = fp.getPeaks()
459  peaksF = [pk.getF() for pk in peaks]
460  fbb = fp.getBBox()
461  fmask = afwImage.MaskU(fbb)
462  fmask.setXY0(fbb.getMinX(), fbb.getMinY())
463  afwDetect.setMaskFromFootprint(fmask, fp, 1)
464 
465  psf = exposure.getPsf()
466  psfSigPix = psf.computeShape().getDeterminantRadius()
467  psfFwhmPix = psfSigPix * self.sigma2fwhm
468  subimage = afwImage.ExposureF(exposure, fbb, True)
469  cpsf = deblendBaseline.CachingPsf(psf)
470 
471  # if fewer than 2 peaks, just return a copy of the source
472  if len(peaks) < 2:
473  return source.getTable().copyRecord(source)
474 
475  # make sure you only deblend 2 peaks; take the brighest and faintest
476  speaks = [(p.getPeakValue(), p) for p in peaks]
477  speaks.sort()
478  dpeaks = [speaks[0][1], speaks[-1][1]]
479 
480  # and only set these peaks in the footprint (peaks is mutable)
481  peaks.clear()
482  for peak in dpeaks:
483  peaks.append(peak)
484 
485  if True:
486  # Call top-level deblend task
487  fpres = deblendBaseline.deblend(fp, exposure.getMaskedImage(), psf, psfFwhmPix,
488  log = self.log,
489  psfChisqCut1 = self.psfChisqCut1,
490  psfChisqCut2 = self.psfChisqCut2,
491  psfChisqCut2b = self.psfChisqCut2b)
492  else:
493  # Call lower-level _fit_psf task
494 
495  # Prepare results structure
496  fpres = deblendBaseline.PerFootprint()
497  fpres.peaks = []
498  for pki,pk in enumerate(dpeaks):
499  pkres = deblendBaseline.PerPeak()
500  pkres.peak = pk
501  pkres.pki = pki
502  fpres.peaks.append(pkres)
503 
504  for pki,(pk,pkres,pkF) in enumerate(zip(dpeaks, fpres.peaks, peaksF)):
505  self.log.debug('Peak %i', pki)
506  deblendBaseline._fitPsf(fp, fmask, pk, pkF, pkres, fbb, dpeaks, peaksF, self.log,
507  cpsf, psfFwhmPix,
508  subimage.getMaskedImage().getImage(),
509  subimage.getMaskedImage().getVariance(),
510  self.psfChisqCut1, self.psfChisqCut2, self.psfChisqCut2b)
511 
512 
513  deblendedSource = source.getTable().copyRecord(source)
514  deblendedSource.setParent(source.getId())
515  peakList = deblendedSource.getFootprint().getPeaks()
516  peakList.clear()
517 
518  for i, peak in enumerate(fpres.peaks):
519  if peak.psfFitFlux > 0:
520  suffix = "pos"
521  else:
522  suffix = "neg"
523  c = peak.psfFitCenter
524  self.log.info("deblended.centroid.dipole.psf.%s %f %f",
525  suffix, c[0], c[1])
526  self.log.info("deblended.chi2dof.dipole.%s %f",
527  suffix, peak.psfFitChisq / peak.psfFitDof)
528  self.log.info("deblended.flux.dipole.psf.%s %f",
529  suffix, peak.psfFitFlux * np.sum(peak.templateImage.getArray()))
530  peakList.append(peak.peak)
531  return deblendedSource
def getCentroid
Get the centroid of the dipole; average of positive and negative lobe.
def displayDipoles
Display debugging information on the detected dipoles.
Definition: Log.h:716
Measurement of Sources, specifically ones from difference images, for characterization as dipoles...
Functor class that provides (S/N, position, orientation) of measured dipoles.
Functor to deblend a source as a dipole, and return a new source with deblended footprints.
def __call__
Parse information returned from dipole measurement.
MaskT setMaskFromFootprint(lsst::afw::image::Mask< MaskT > *mask, Footprint const &footprint, MaskT const bitmask)
OR bitmask into all the Mask&#39;s pixels that are in the Footprint.
Functor class to check whether a diaSource has flags set that should cause it to be labeled bad...
Measurement of detected diaSources as dipoles.
def getOrientation
Calculate the orientation of dipole; vector from negative to positive lobe.
def getSn
Get the total signal-to-noise of the dipole; total S/N is from positive and negative lobe...
def __call__
Call the source flag checker on a single Source.