30 from .subtractBackground
import SubtractBackgroundTask
32 __all__ = (
"SourceDetectionConfig",
"SourceDetectionTask",
"addExposures")
36 """!Configuration parameters for the SourceDetectionTask
38 minPixels = pexConfig.RangeField(
39 doc=
"detected sources with fewer than the specified number of pixels will be ignored",
40 dtype=int, optional=
False, default=1, min=0,
42 isotropicGrow = pexConfig.Field(
43 doc=
"Pixels should be grown as isotropically as possible (slower)",
44 dtype=bool, optional=
False, default=
False,
46 nSigmaToGrow = pexConfig.Field(
47 doc=
"Grow detections by nSigmaToGrow * sigma; if 0 then do not grow",
48 dtype=float, default=2.4,
50 returnOriginalFootprints = pexConfig.Field(
51 doc=
"Grow detections to set the image mask bits, but return the original (not-grown) footprints",
52 dtype=bool, optional=
False, default=
False,
54 thresholdValue = pexConfig.RangeField(
55 doc=
"Threshold for footprints",
56 dtype=float, optional=
False, default=5.0, min=0.0,
58 includeThresholdMultiplier = pexConfig.RangeField(
59 doc=
"Include threshold relative to thresholdValue",
60 dtype=float, default=1.0, min=0.0,
62 thresholdType = pexConfig.ChoiceField(
63 doc=
"specifies the desired flavor of Threshold",
64 dtype=str, optional=
False, default=
"stdev",
66 "variance":
"threshold applied to image variance",
67 "stdev":
"threshold applied to image std deviation",
68 "value":
"threshold applied to image value",
69 "pixel_stdev":
"threshold applied to per-pixel std deviation",
72 thresholdPolarity = pexConfig.ChoiceField(
73 doc=
"specifies whether to detect positive, or negative sources, or both",
74 dtype=str, optional=
False, default=
"positive",
76 "positive":
"detect only positive sources",
77 "negative":
"detect only negative sources",
78 "both":
"detect both positive and negative sources",
81 adjustBackground = pexConfig.Field(
83 doc=
"Fiddle factor to add to the background; debugging only",
86 reEstimateBackground = pexConfig.Field(
88 doc=
"Estimate the background again after final source detection?",
89 default=
True, optional=
False,
91 background = pexConfig.ConfigurableField(
92 doc=
"Background re-estimation; ignored if reEstimateBackground false",
93 target=SubtractBackgroundTask,
95 tempLocalBackground = pexConfig.ConfigurableField(
96 doc=(
"A seperate background estimation and removal before footprint and peak detection. "
97 "It is added back into the image after detection."),
98 target=SubtractBackgroundTask,
100 doTempLocalBackground = pexConfig.Field(
102 doc=
"Do temporary interpolated background subtraction before footprint detection?",
107 self.tempLocalBackground.binSize = 64
108 self.tempLocalBackground.algorithm =
"AKIMA_SPLINE"
109 self.tempLocalBackground.useApprox =
False
121 \anchor SourceDetectionTask_
123 \brief Detect positive and negative sources on an exposure and return a new \link table.SourceCatalog\endlink.
125 \section meas_algorithms_detection_Contents Contents
127 - \ref meas_algorithms_detection_Purpose
128 - \ref meas_algorithms_detection_Initialize
129 - \ref meas_algorithms_detection_Invoke
130 - \ref meas_algorithms_detection_Config
131 - \ref meas_algorithms_detection_Debug
132 - \ref meas_algorithms_detection_Example
134 \section meas_algorithms_detection_Purpose Description
136 \copybrief SourceDetectionTask
138 \section meas_algorithms_detection_Initialize Task initialisation
140 \copydoc \_\_init\_\_
142 \section meas_algorithms_detection_Invoke Invoking the Task
146 \section meas_algorithms_detection_Config Configuration parameters
148 See \ref SourceDetectionConfig
150 \section meas_algorithms_detection_Debug Debug variables
152 The \link lsst.pipe.base.cmdLineTask.CmdLineTask command line task\endlink interface supports a
153 flag \c -d to import \b debug.py from your \c PYTHONPATH; see \ref baseDebug for more about \b debug.py files.
155 The available variables in SourceDetectionTask are:
159 - If True, display the exposure on ds9's frame 0. +ve detections in blue, -ve detections in cyan
160 - If display > 1, display the convolved exposure on frame 1
163 \section meas_algorithms_detection_Example A complete example of using SourceDetectionTask
165 This code is in \link measAlgTasks.py\endlink in the examples directory, and can be run as \em e.g.
167 examples/measAlgTasks.py --ds9
169 \dontinclude measAlgTasks.py
170 The example also runs the SourceMeasurementTask; see \ref meas_algorithms_measurement_Example for more
173 Import the task (there are some other standard imports; read the file if you're confused)
174 \skipline SourceDetectionTask
176 We need to create our task before processing any data as the task constructor
177 can add an extra column to the schema, but first we need an almost-empty Schema
178 \skipline makeMinimalSchema
179 after which we can call the constructor:
180 \skip SourceDetectionTask.ConfigClass
183 We're now ready to process the data (we could loop over multiple exposures/catalogues using the same
184 task objects). First create the output table:
187 And process the image
189 (You may not be happy that the threshold was set in the config before creating the Task rather than being set
190 separately for each exposure. You \em can reset it just before calling the run method if you must, but we
191 should really implement a better solution).
193 We can then unpack and use the results:
198 To investigate the \ref meas_algorithms_detection_Debug, put something like
202 di = lsstDebug.getInfo(name) # N.b. lsstDebug.Info(name) would call us recursively
203 if name == "lsst.meas.algorithms.detection":
208 lsstDebug.Info = DebugInfo
210 into your debug.py file and run measAlgTasks.py with the \c --debug flag.
212 ConfigClass = SourceDetectionConfig
213 _DefaultName =
"sourceDetection"
216 """!Create the detection task. Most arguments are simply passed onto pipe.base.Task.
218 \param schema An lsst::afw::table::Schema used to create the output lsst.afw.table.SourceCatalog
219 \param **kwds Keyword arguments passed to lsst.pipe.base.task.Task.__init__.
221 If schema is not None and configured for 'both' detections,
222 a 'flags.negative' field will be added to label detections made with a
225 \note This task can add fields to the schema, so any code calling this task must ensure that
226 these columns are indeed present in the input match list; see \ref Example
228 pipeBase.Task.__init__(self, **kwds)
229 if schema
is not None and self.config.thresholdPolarity ==
"both":
231 "flags_negative", type=
"Flag",
232 doc=
"set if source was detected as significantly negative"
235 if self.config.thresholdPolarity ==
"both":
236 self.log.warn(
"Detection polarity set to 'both', but no flag will be "
237 "set to distinguish between positive and negative detections")
239 if self.config.reEstimateBackground:
240 self.makeSubtask(
"background")
241 if self.config.doTempLocalBackground:
242 self.makeSubtask(
"tempLocalBackground")
245 def run(self, table, exposure, doSmooth=True, sigma=None, clearMask=True):
246 """!Run source detection and create a SourceCatalog.
248 \param table lsst.afw.table.SourceTable object that will be used to create the SourceCatalog.
249 \param exposure Exposure to process; DETECTED mask plane will be set in-place.
250 \param doSmooth if True, smooth the image before detection using a Gaussian of width sigma
252 \param sigma sigma of PSF (pixels); used for smoothing and to grow detections;
253 if None then measure the sigma of the PSF of the exposure (default: None)
254 \param clearMask Clear DETECTED{,_NEGATIVE} planes before running detection (default: True)
256 \return a lsst.pipe.base.Struct with:
257 - sources -- an lsst.afw.table.SourceCatalog object
258 - fpSets --- lsst.pipe.base.Struct returned by \link detectFootprints \endlink
260 \throws ValueError if flags.negative is needed, but isn't in table's schema
261 \throws lsst.pipe.base.TaskError if sigma=None, doSmooth=True and the exposure has no PSF
264 If you want to avoid dealing with Sources and Tables, you can use detectFootprints()
265 to just get the afw::detection::FootprintSet%s.
268 raise ValueError(
"Table has incorrect Schema")
269 fpSets = self.
detectFootprints(exposure=exposure, doSmooth=doSmooth, sigma=sigma,
272 table.preallocate(fpSets.numPos + fpSets.numNeg)
274 fpSets.negative.makeSources(sources)
276 for record
in sources:
279 fpSets.positive.makeSources(sources)
280 return pipeBase.Struct(
286 makeSourceCatalog = run
290 """!Detect footprints.
292 \param exposure Exposure to process; DETECTED{,_NEGATIVE} mask plane will be set in-place.
293 \param doSmooth if True, smooth the image before detection using a Gaussian of width sigma
294 \param sigma sigma of PSF (pixels); used for smoothing and to grow detections;
295 if None then measure the sigma of the PSF of the exposure
296 \param clearMask Clear both DETECTED and DETECTED_NEGATIVE planes before running detection
298 \return a lsst.pipe.base.Struct with fields:
299 - positive: lsst.afw.detection.FootprintSet with positive polarity footprints (may be None)
300 - negative: lsst.afw.detection.FootprintSet with negative polarity footprints (may be None)
301 - numPos: number of footprints in positive or 0 if detection polarity was negative
302 - numNeg: number of footprints in negative or 0 if detection polarity was positive
303 - background: re-estimated background. None if reEstimateBackground==False
305 \throws lsst.pipe.base.TaskError if sigma=None and the exposure has no PSF
317 raise RuntimeError(
"No exposure for detection")
319 maskedImage = exposure.getMaskedImage()
320 region = maskedImage.getBBox()
323 mask = maskedImage.getMask()
324 mask &= ~(mask.getPlaneBitMask(
"DETECTED") | mask.getPlaneBitMask(
"DETECTED_NEGATIVE"))
327 if self.config.doTempLocalBackground:
328 tempBgRes = self.tempLocalBackground.run(exposure)
329 tempLocalBkgdImage = tempBgRes.background.getImage()
332 psf = exposure.getPsf()
334 raise pipeBase.TaskError(
"exposure has no PSF; must specify sigma")
335 shape = psf.computeShape()
336 sigma = shape.getDeterminantRadius()
338 self.metadata.set(
"sigma", sigma)
339 self.metadata.set(
"doSmooth", doSmooth)
342 convolvedImage = maskedImage.Factory(maskedImage)
343 middle = convolvedImage
347 psf = exposure.getPsf()
348 kWidth = (int(sigma * 7 + 0.5) // 2) * 2 + 1
349 self.metadata.set(
"smoothingKernelWidth", kWidth)
350 gaussFunc = afwMath.GaussianFunction1D(sigma)
353 convolvedImage = maskedImage.Factory(maskedImage.getBBox())
359 goodBBox = gaussKernel.shrinkBBox(convolvedImage.getBBox())
360 middle = convolvedImage.Factory(convolvedImage, goodBBox, afwImage.PARENT,
False)
364 self.
setEdgeBits(maskedImage, goodBBox, maskedImage.getMask().getPlaneBitMask(
"EDGE"))
366 fpSets = pipeBase.Struct(positive=
None, negative=
None)
368 if self.config.thresholdPolarity !=
"negative":
370 if self.config.reEstimateBackground
or self.config.thresholdPolarity !=
"positive":
373 for polarity, maskName
in ((
"positive",
"DETECTED"), (
"negative",
"DETECTED_NEGATIVE")):
374 fpSet = getattr(fpSets, polarity)
377 fpSet.setRegion(region)
378 if self.config.nSigmaToGrow > 0:
379 nGrow = int((self.config.nSigmaToGrow * sigma) + 0.5)
380 self.metadata.set(
"nGrow", nGrow)
382 fpSet.setMask(maskedImage.getMask(), maskName)
383 if not self.config.returnOriginalFootprints:
384 setattr(fpSets, polarity, fpSet)
386 fpSets.numPos = len(fpSets.positive.getFootprints())
if fpSets.positive
is not None else 0
387 fpSets.numNeg = len(fpSets.negative.getFootprints())
if fpSets.negative
is not None else 0
389 if self.config.thresholdPolarity !=
"negative":
390 self.log.info(
"Detected %d positive sources to %g sigma.",
391 fpSets.numPos, self.config.thresholdValue*self.config.includeThresholdMultiplier)
393 if self.config.doTempLocalBackground:
394 maskedImage += tempLocalBkgdImage
396 fpSets.background =
None
397 if self.config.reEstimateBackground:
398 mi = exposure.getMaskedImage()
399 bkgd = self.background.fitBackground(mi)
401 if self.config.adjustBackground:
402 self.log.warn(
"Fiddling the background by %g", self.config.adjustBackground)
404 bkgd += self.config.adjustBackground
405 fpSets.background = bkgd
406 self.log.info(
"Resubtracting the background after object detection")
408 mi -= bkgd.getImageF()
411 if self.config.thresholdPolarity ==
"positive":
412 if self.config.reEstimateBackground:
413 mask = maskedImage.getMask()
414 mask &= ~mask.getPlaneBitMask(
"DETECTED_NEGATIVE")
416 fpSets.negative =
None
418 self.log.info(
"Detected %d negative sources to %g %s",
419 fpSets.numNeg, self.config.thresholdValue,
420 (
"DN" if self.config.thresholdType ==
"value" else "sigma"))
423 ds9.mtv(exposure, frame=0, title=
"detection")
424 x0, y0 = exposure.getXY0()
426 def plotPeaks(fps, ctype):
429 with ds9.Buffering():
430 for fp
in fps.getFootprints():
431 for pp
in fp.getPeaks():
432 ds9.dot(
"+", pp.getFx() - x0, pp.getFy() - y0, ctype=ctype)
433 plotPeaks(fpSets.positive,
"yellow")
434 plotPeaks(fpSets.negative,
"red")
436 if convolvedImage
and display
and display > 1:
437 ds9.mtv(convolvedImage, frame=1, title=
"PSF smoothed")
442 """!Threshold the convolved image, returning a FootprintSet.
443 Helper function for detect().
445 \param image The (optionally convolved) MaskedImage to threshold
446 \param thresholdParity Parity of threshold
447 \param maskName Name of mask to set
451 parity =
False if thresholdParity ==
"negative" else True
453 threshold.setIncludeMultiplier(self.config.includeThresholdMultiplier)
455 if self.config.thresholdType ==
'stdev':
456 bad = image.getMask().getPlaneBitMask([
'BAD',
'SAT',
'EDGE',
'NO_DATA', ])
458 sctrl.setAndMask(bad)
460 thres = stats.getValue(afwMath.STDEVCLIP) * self.config.thresholdValue
462 threshold.setIncludeMultiplier(self.config.includeThresholdMultiplier)
469 """!Set the edgeBitmask bits for all of maskedImage outside goodBBox
471 \param[in,out] maskedImage image on which to set edge bits in the mask
472 \param[in] goodBBox bounding box of good pixels, in LOCAL coordinates
473 \param[in] edgeBitmask bit mask to OR with the existing mask bits in the region outside goodBBox
475 msk = maskedImage.getMask()
477 mx0, my0 = maskedImage.getXY0()
478 for x0, y0, w, h
in ([0, 0,
479 msk.getWidth(), goodBBox.getBeginY() - my0],
480 [0, goodBBox.getEndY() - my0, msk.getWidth(),
481 maskedImage.getHeight() - (goodBBox.getEndY() - my0)],
483 goodBBox.getBeginX() - mx0, msk.getHeight()],
484 [goodBBox.getEndX() - mx0, 0,
485 maskedImage.getWidth() - (goodBBox.getEndX() - mx0), msk.getHeight()],
489 edgeMask |= edgeBitmask
493 """!Add a set of exposures together.
495 \param[in] exposureList sequence of exposures to add
497 \return an exposure of the same size as each exposure in exposureList,
498 with the metadata from exposureList[0] and a masked image equal to the
499 sum of all the exposure's masked images.
501 \throw LsstException if the exposures do not all have the same dimensions (but does not check xy0)
503 exposure0 = exposureList[0]
504 image0 = exposure0.getMaskedImage()
506 addedImage = image0.Factory(image0,
True)
507 addedImage.setXY0(image0.getXY0())
509 for exposure
in exposureList[1:]:
510 image = exposure.getMaskedImage()
513 addedExposure = exposure0.Factory(addedImage, exposure0.getWcs())
def detectFootprints
Detect footprints.
def addExposures
Add a set of exposures together.
Parameters to control convolution.
A kernel described by a pair of functions: func(x, y) = colFunc(x) * rowFunc(y)
def setEdgeBits
Set the edgeBitmask bits for all of maskedImage outside goodBBox.
Detect positive and negative sources on an exposure and return a new table.SourceCatalog.
An integer coordinate rectangle.
Pass parameters to a Statistics objectA class to pass parameters which control how the stats are calc...
def run
Run source detection and create a SourceCatalog.
Configuration parameters for the SourceDetectionTask.
Custom catalog class for record/table subclasses that are guaranteed to have an ID, and should generally be sorted by that ID.
def __init__
Create the detection task.
Threshold createThreshold(double const value, std::string const typeStr, bool const polarity)
Factory method for creating Threshold objects.
def thresholdImage
Threshold the convolved image, returning a FootprintSet.
Statistics makeStatistics(afwImage::Mask< afwImage::MaskPixel > const &msk, int const flags, StatisticsControl const &sctrl)
Specialization to handle Masks.
void convolve(OutImageT &convolvedImage, InImageT const &inImage, KernelT const &kernel, bool doNormalize, bool doCopyEdge=false)
Old, deprecated version of convolve.